From 099d2b9018e42b7411dfde2e11f235148ecfaaf0 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Thu, 14 Mar 2024 10:40:48 -0700 Subject: [PATCH 001/153] Add Planning Review Decisions * Update Decision # Generation to include NOIs and PRs --- .../planning-review-dialog.component.html | 7 +- .../decision-documents.component.html | 71 ++++ .../decision-documents.component.scss | 37 ++ .../decision-documents.component.spec.ts | 52 +++ .../decision-documents.component.ts | 112 ++++++ ...sion-document-upload-dialog.component.html | 98 +++++ ...sion-document-upload-dialog.component.scss | 49 +++ ...n-document-upload-dialog.component.spec.ts | 49 +++ ...cision-document-upload-dialog.component.ts | 122 ++++++ .../decision-input.component.html | 109 ++++++ .../decision-input.component.scss | 82 ++++ .../decision-input.component.spec.ts | 72 ++++ .../decision-input.component.ts | 271 +++++++++++++ .../decision/decision.component.html | 78 ++++ .../decision/decision.component.scss | 156 ++++++++ .../decision/decision.component.spec.ts | 76 ++++ .../decision/decision.component.ts | 166 ++++++++ .../decision/decision.module.ts | 46 +++ .../release-dialog.component.html | 19 + .../release-dialog.component.scss | 6 + .../release-dialog.component.spec.ts | 31 ++ .../release-dialog.component.ts | 18 + .../revert-to-draft-dialog.component.html | 18 + .../revert-to-draft-dialog.component.scss | 6 + .../revert-to-draft-dialog.component.spec.ts | 30 ++ .../revert-to-draft-dialog.component.ts | 18 + .../header/header.component.spec.ts | 2 + .../planning-review.component.ts | 9 + .../planning-review-decision.dto.ts | 39 ++ .../planning-review-decision.service.spec.ts | 201 ++++++++++ .../planning-review-decision.service.ts | 178 +++++++++ .../planning-review-detail.service.ts | 3 - .../planning-review/planning-review.dto.ts | 1 + services/apps/alcs/src/alcs/alcs.module.ts | 3 + ...lanning-review-decision-document.entity.ts | 38 ++ ...planning-review-decision-outcome.entity.ts | 5 + ...lanning-review-decision.controller.spec.ts | 207 ++++++++++ .../planning-review-decision.controller.ts | 197 ++++++++++ .../planning-review-decision.dto.ts | 91 +++++ .../planning-review-decision.entity.ts | 92 +++++ .../planning-review-decision.module.ts | 26 ++ .../planning-review-decision.service.spec.ts | 368 ++++++++++++++++++ .../planning-review-decision.service.ts | 351 +++++++++++++++++ .../planning-review/planning-review.dto.ts | 4 + .../planning-review/planning-review.entity.ts | 3 + .../planning-review.service.spec.ts | 12 +- .../planning-review.service.ts | 8 +- ...ning-review-decision.automapper.profile.ts | 66 ++++ ...8988258-generate_next_resolution_number.ts | 55 +-- .../1710273748053-add_pr_decisions.ts | 66 ++++ .../1710278788366-seed_pr_dec_outcomes.ts | 18 + ...795-update_resolution_generation_for_pr.ts | 53 +++ ...348044213-remove_default_pr_dec_outcome.ts | 37 ++ 53 files changed, 3893 insertions(+), 39 deletions(-) create mode 100644 alcs-frontend/src/app/features/planning-review/decision/decision-documents/decision-documents.component.html create mode 100644 alcs-frontend/src/app/features/planning-review/decision/decision-documents/decision-documents.component.scss create mode 100644 alcs-frontend/src/app/features/planning-review/decision/decision-documents/decision-documents.component.spec.ts create mode 100644 alcs-frontend/src/app/features/planning-review/decision/decision-documents/decision-documents.component.ts create mode 100644 alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html create mode 100644 alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.scss create mode 100644 alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.spec.ts create mode 100644 alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts create mode 100644 alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-input.component.html create mode 100644 alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-input.component.scss create mode 100644 alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-input.component.spec.ts create mode 100644 alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-input.component.ts create mode 100644 alcs-frontend/src/app/features/planning-review/decision/decision.component.html create mode 100644 alcs-frontend/src/app/features/planning-review/decision/decision.component.scss create mode 100644 alcs-frontend/src/app/features/planning-review/decision/decision.component.spec.ts create mode 100644 alcs-frontend/src/app/features/planning-review/decision/decision.component.ts create mode 100644 alcs-frontend/src/app/features/planning-review/decision/decision.module.ts create mode 100644 alcs-frontend/src/app/features/planning-review/decision/release-dialog/release-dialog.component.html create mode 100644 alcs-frontend/src/app/features/planning-review/decision/release-dialog/release-dialog.component.scss create mode 100644 alcs-frontend/src/app/features/planning-review/decision/release-dialog/release-dialog.component.spec.ts create mode 100644 alcs-frontend/src/app/features/planning-review/decision/release-dialog/release-dialog.component.ts create mode 100644 alcs-frontend/src/app/features/planning-review/decision/revert-to-draft-dialog/revert-to-draft-dialog.component.html create mode 100644 alcs-frontend/src/app/features/planning-review/decision/revert-to-draft-dialog/revert-to-draft-dialog.component.scss create mode 100644 alcs-frontend/src/app/features/planning-review/decision/revert-to-draft-dialog/revert-to-draft-dialog.component.spec.ts create mode 100644 alcs-frontend/src/app/features/planning-review/decision/revert-to-draft-dialog/revert-to-draft-dialog.component.ts create mode 100644 alcs-frontend/src/app/services/planning-review/planning-review-decision/planning-review-decision.dto.ts create mode 100644 alcs-frontend/src/app/services/planning-review/planning-review-decision/planning-review-decision.service.spec.ts create mode 100644 alcs-frontend/src/app/services/planning-review/planning-review-decision/planning-review-decision.service.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision-document/planning-review-decision-document.entity.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision-outcome.entity.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.controller.spec.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.controller.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.dto.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.entity.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.module.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.service.spec.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.service.ts create mode 100644 services/apps/alcs/src/common/automapper/planning-review-decision.automapper.profile.ts create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1710273748053-add_pr_decisions.ts create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1710278788366-seed_pr_dec_outcomes.ts create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1710288340795-update_resolution_generation_for_pr.ts create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1710348044213-remove_default_pr_dec_outcome.ts 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 6fbed0a354..484e386ec6 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 @@ -9,7 +9,10 @@
Planning Review

{{ cardTitle }} - +

@@ -31,7 +34,7 @@

- Due Date: {{ planningReferral.dueDate | momentFormat }} + Due: {{ planningReferral.dueDate | momentFormat }}
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDECPACKDocument Name + {{ element.fileName }} + SourceALC + Visibility +
* = Pending
+
+ C* + Upload Date{{ element.uploadedAt | momentFormat }}File Actions + + + +
No Documents
diff --git a/alcs-frontend/src/app/features/planning-review/decision/decision-documents/decision-documents.component.scss b/alcs-frontend/src/app/features/planning-review/decision/decision-documents/decision-documents.component.scss new file mode 100644 index 0000000000..7a5547e702 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/decision/decision-documents/decision-documents.component.scss @@ -0,0 +1,37 @@ +@use '../../../../../styles/colors'; + +.documents { + margin-top: 12px; + border-collapse: collapse; +} + +.upload-button { + width: 100%; + margin: 4px 0 12px !important; +} + +.mat-mdc-no-data-row { + height: 56px; + color: colors.$grey-dark; +} + +a { + word-break: break-all; +} + +table { + box-shadow: none; + + th { + font-weight: 700; + padding-bottom: 16px; + position: relative; + + .subheading { + font-size: 11px; + line-height: 16px; + font-weight: 400; + position: absolute; + } + } +} diff --git a/alcs-frontend/src/app/features/planning-review/decision/decision-documents/decision-documents.component.spec.ts b/alcs-frontend/src/app/features/planning-review/decision/decision-documents/decision-documents.component.spec.ts new file mode 100644 index 0000000000..e648bca2c9 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/decision/decision-documents/decision-documents.component.spec.ts @@ -0,0 +1,52 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatDialog } from '@angular/material/dialog'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { BehaviorSubject } from 'rxjs'; +import { PlanningReviewDecisionDto } from '../../../../services/planning-review/planning-review-decision/planning-review-decision.dto'; +import { PlanningReviewDecisionService } from '../../../../services/planning-review/planning-review-decision/planning-review-decision.service'; +import { ToastService } from '../../../../services/toast/toast.service'; + +import { DecisionDocumentsComponent } from './decision-documents.component'; + +describe('DecisionDocumentsComponent', () => { + let component: DecisionDocumentsComponent; + let fixture: ComponentFixture; + let mockPRDecService: DeepMocked; + let mockDialog: DeepMocked; + let mockToastService: DeepMocked; + + beforeEach(async () => { + mockPRDecService = createMock(); + mockDialog = createMock(); + mockToastService = createMock(); + mockPRDecService.$decision = new BehaviorSubject(undefined); + + await TestBed.configureTestingModule({ + declarations: [DecisionDocumentsComponent], + providers: [ + { + provide: PlanningReviewDecisionService, + useValue: mockPRDecService, + }, + { + provide: MatDialog, + useValue: mockDialog, + }, + { + provide: ToastService, + useValue: mockToastService, + }, + ], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(DecisionDocumentsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alcs-frontend/src/app/features/planning-review/decision/decision-documents/decision-documents.component.ts b/alcs-frontend/src/app/features/planning-review/decision/decision-documents/decision-documents.component.ts new file mode 100644 index 0000000000..72be901bc2 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/decision/decision-documents/decision-documents.component.ts @@ -0,0 +1,112 @@ +import { Component, EventEmitter, Input, OnChanges, OnDestroy, Output, SimpleChanges, ViewChild } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { MatSort } from '@angular/material/sort'; +import { MatTableDataSource } from '@angular/material/table'; +import { Subject } from 'rxjs'; +import { + PlanningReviewDecisionDocumentDto, + PlanningReviewDecisionDto, +} from '../../../../services/planning-review/planning-review-decision/planning-review-decision.dto'; +import { PlanningReviewDecisionService } from '../../../../services/planning-review/planning-review-decision/planning-review-decision.service'; +import { ToastService } from '../../../../services/toast/toast.service'; +import { ConfirmationDialogService } from '../../../../shared/confirmation-dialog/confirmation-dialog.service'; +import { DecisionDocumentUploadDialogComponent } from '../decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component'; + +@Component({ + selector: 'app-decision-documents', + templateUrl: './decision-documents.component.html', + styleUrls: ['./decision-documents.component.scss'], +}) +export class DecisionDocumentsComponent implements OnDestroy, OnChanges { + $destroy = new Subject(); + + @Input() editable = true; + @Input() loadData = true; + @Input() decision: PlanningReviewDecisionDto | undefined; + @Input() showError = false; + @Output() beforeDocumentUpload = new EventEmitter(); + + displayedColumns: string[] = ['type', 'fileName', 'source', 'visibilityFlags', 'uploadedAt', 'actions']; + private fileId = ''; + areDocumentsReleased = false; + + @ViewChild(MatSort) sort!: MatSort; + dataSource: MatTableDataSource = + new MatTableDataSource(); + + constructor( + private decisionService: PlanningReviewDecisionService, + private dialog: MatDialog, + private toastService: ToastService, + private confirmationDialogService: ConfirmationDialogService, + ) {} + + async openFile(fileUuid: string, fileName: string) { + if (this.decision) { + await this.decisionService.downloadFile(this.decision.uuid, fileUuid, fileName); + } + } + + async downloadFile(fileUuid: string, fileName: string) { + if (this.decision) { + await this.decisionService.downloadFile(this.decision.uuid, fileUuid, fileName, false); + } + } + + async onUploadFile() { + this.beforeDocumentUpload.emit(); + this.openFileDialog(); + } + + onEditFile(element: PlanningReviewDecisionDocumentDto) { + this.openFileDialog(element); + } + + private openFileDialog(existingDocument?: PlanningReviewDecisionDocumentDto) { + if (this.decision) { + this.dialog + .open(DecisionDocumentUploadDialogComponent, { + minWidth: '600px', + maxWidth: '800px', + width: '70%', + data: { + fileId: this.fileId, + decisionUuid: this.decision?.uuid, + existingDocument: existingDocument, + }, + }) + .beforeClosed() + .subscribe((isDirty: boolean) => { + if (isDirty && this.decision) { + this.decisionService.loadDecision(this.decision.uuid); + } + }); + } + } + + onDeleteFile(element: PlanningReviewDecisionDocumentDto) { + this.confirmationDialogService + .openDialog({ + body: 'Are you sure you want to delete the selected file?', + }) + .subscribe(async (accepted) => { + if (accepted && this.decision) { + await this.decisionService.deleteFile(this.decision.uuid, element.uuid); + await this.decisionService.loadDecision(this.decision.uuid); + this.toastService.showSuccessToast('Document deleted'); + } + }); + } + + ngOnDestroy(): void { + this.$destroy.next(); + this.$destroy.complete(); + } + + ngOnChanges(changes: SimpleChanges): void { + this.areDocumentsReleased = true; + if (this.decision) { + this.dataSource = new MatTableDataSource(this.decision.documents); + } + } +} diff --git a/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html b/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html new file mode 100644 index 0000000000..2cbdbbadfa --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html @@ -0,0 +1,98 @@ +
+

{{ title }} Document

+
+
+
+
+
+ Document Upload* +
+ + +
+
+ {{ pendingFile.name }} +  ({{ pendingFile.size | filesize }}) +
+ +
+
+ + +
+ + warning A virus was detected in the file. Choose another file and try again. + +
+ +
+ + Document Name + + +
+ +
+ + +
+
+ + Source + + {{ source }} + + +
+
+ Visible To: +
+ Commissioner +
+
+
+ + +
+ + + +
+
+
diff --git a/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.scss b/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.scss new file mode 100644 index 0000000000..f8e75ea5f5 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.scss @@ -0,0 +1,49 @@ +@use '../../../../../../styles/colors'; + +.form { + display: grid; + grid-template-columns: 1fr 1fr; + row-gap: 32px; + column-gap: 32px; + + .double { + grid-column: 1/3; + } +} + +.full-width { + width: 100%; +} + +a { + word-break: break-all; +} + +.file { + border: 1px solid #000; + border-radius: 8px; + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px; +} + +.upload-button { + margin-top: 6px !important; + + &.error { + border: 2px solid colors.$error-color; + } +} + +.spinner { + display: inline-block; + margin-right: 4px; +} + +:host::ng-deep { + .mdc-button__label { + display: flex; + align-items: center; + } +} diff --git a/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.spec.ts b/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.spec.ts new file mode 100644 index 0000000000..33c3c65019 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.spec.ts @@ -0,0 +1,49 @@ +import { EventEmitter, NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { PlanningReviewDecisionService } from '../../../../../services/planning-review/planning-review-decision/planning-review-decision.service'; +import { ToastService } from '../../../../../services/toast/toast.service'; + +import { DecisionDocumentUploadDialogComponent } from './decision-document-upload-dialog.component'; + +describe('DecisionDocumentUploadDialogComponent', () => { + let component: DecisionDocumentUploadDialogComponent; + let fixture: ComponentFixture; + + let mockPRDecService: DeepMocked; + + beforeEach(async () => { + mockPRDecService = createMock(); + + const mockDialogRef = { + close: jest.fn(), + afterClosed: jest.fn(), + subscribe: jest.fn(), + backdropClick: () => new EventEmitter(), + }; + + await TestBed.configureTestingModule({ + declarations: [DecisionDocumentUploadDialogComponent], + providers: [ + { + provide: PlanningReviewDecisionService, + useValue: mockPRDecService, + }, + { provide: MatDialogRef, useValue: mockDialogRef }, + { provide: MAT_DIALOG_DATA, useValue: {} }, + { provide: ToastService, useValue: {} }, + ], + imports: [MatDialogModule], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(DecisionDocumentUploadDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts b/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts new file mode 100644 index 0000000000..d58cd35f4f --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts @@ -0,0 +1,122 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { Component, Inject, OnInit } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { PlanningReviewDecisionDocumentDto } from '../../../../../services/planning-review/planning-review-decision/planning-review-decision.dto'; +import { PlanningReviewDecisionService } from '../../../../../services/planning-review/planning-review-decision/planning-review-decision.service'; +import { ToastService } from '../../../../../services/toast/toast.service'; +import { DOCUMENT_SOURCE } from '../../../../../shared/document/document.dto'; + +@Component({ + selector: 'app-app-decision-document-upload-dialog', + templateUrl: './decision-document-upload-dialog.component.html', + styleUrls: ['./decision-document-upload-dialog.component.scss'], +}) +export class DecisionDocumentUploadDialogComponent implements OnInit { + title = 'Create'; + isDirty = false; + isSaving = false; + allowsFileEdit = true; + documentType = 'Decision Package'; + + name = new FormControl('', [Validators.required]); + type = new FormControl({ disabled: true, value: undefined }, [Validators.required]); + source = new FormControl({ disabled: true, value: DOCUMENT_SOURCE.ALC }, [Validators.required]); + visibleToComissioner = new FormControl({ disabled: true, value: true }, [Validators.required]); + + documentSources = Object.values(DOCUMENT_SOURCE); + + form = new FormGroup({ + name: this.name, + type: this.type, + source: this.source, + visibleToComissioner: this.visibleToComissioner, + }); + + pendingFile: File | undefined; + existingFile: string | undefined; + showVirusError = false; + + constructor( + @Inject(MAT_DIALOG_DATA) + public data: { fileId: string; decisionUuid: string; existingDocument?: PlanningReviewDecisionDocumentDto }, + protected dialog: MatDialogRef, + private decisionService: PlanningReviewDecisionService, + private toastService: ToastService, + ) {} + + ngOnInit(): void { + if (this.data.existingDocument) { + const document = this.data.existingDocument; + this.title = 'Edit'; + this.form.patchValue({ + name: document.fileName, + }); + this.existingFile = document.fileName; + } + } + + async onSubmit() { + const file = this.pendingFile; + if (file) { + const renamedFile = new File([file], this.name.value ?? file.name); + this.isSaving = true; + if (this.data.existingDocument) { + await this.decisionService.deleteFile(this.data.decisionUuid, this.data.existingDocument.uuid); + } + + try { + await this.decisionService.uploadFile(this.data.decisionUuid, renamedFile); + } catch (err) { + this.toastService.showErrorToast('Document upload failed'); + if (err instanceof HttpErrorResponse && err.status === 403) { + this.showVirusError = true; + this.isSaving = false; + this.pendingFile = undefined; + return; + } + } + + this.dialog.close(true); + this.isSaving = false; + } else if (this.data.existingDocument) { + this.isSaving = true; + await this.decisionService.updateFile(this.data.decisionUuid, this.data.existingDocument.uuid, this.name.value!); + + this.dialog.close(true); + this.isSaving = false; + } + } + + uploadFile(event: Event) { + const element = event.target as HTMLInputElement; + const selectedFiles = element.files; + if (selectedFiles && selectedFiles[0]) { + this.pendingFile = selectedFiles[0]; + this.name.setValue(selectedFiles[0].name); + this.showVirusError = false; + } + } + + onRemoveFile() { + this.pendingFile = undefined; + this.existingFile = undefined; + } + + openFile() { + if (this.pendingFile) { + const fileURL = URL.createObjectURL(this.pendingFile); + window.open(fileURL, '_blank'); + } + } + + async openExistingFile() { + if (this.data.existingDocument) { + await this.decisionService.downloadFile( + this.data.decisionUuid, + this.data.existingDocument.uuid, + this.data.existingDocument.fileName, + ); + } + } +} diff --git a/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-input.component.html b/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-input.component.html new file mode 100644 index 0000000000..a37ce156d7 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-input.component.html @@ -0,0 +1,109 @@ +
+

Decision #{{ index }} Draft

+
+ +

Resolution

+
+
+
+ +
+ +
+
+ + +
+
+
+ Res #{{ resolutionNumberControl.getRawValue() }} / {{ resolutionYearControl.getRawValue() }} + +
+
+
+ + + Decision Date + + + + + + + +
+ + Decision Description + + +
+
+ +
+ +
+
+ +
+
+ +
+
+
+ + +
+
+
diff --git a/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-input.component.scss b/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-input.component.scss new file mode 100644 index 0000000000..2c8b75063c --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-input.component.scss @@ -0,0 +1,82 @@ +@use '../../../../../styles/colors.scss'; + +.bottom-scroller { + margin-top: 36px; + position: absolute; + bottom: 0; + left: 240px; + right: 0; + background-color: #fff; + padding: 16px 94px 16px 48px; + z-index: 2; + border-top: 1px solid colors.$grey-light; + + button:not(:last-child) { + margin-right: 16px !important; + } +} + +form { + padding-bottom: 120px; +} + +.grid, +.grid-2 { + display: grid; + grid-template-columns: calc(50% - 12px) calc(50% - 12px); + grid-column-gap: 24px; + grid-row-gap: 32px; + + .full-width { + grid-column: 1/3; + } +} + +h3 { + margin-top: 36px !important; + margin-bottom: 12px !important; +} + +.resolution-number-wrapper { + display: flex; + align-items: center; + + .resolution-number-btn-wrapper { + width: 100%; + } + + .generate-number-btn { + width: 100%; + } + + .resolution-number { + display: flex; + align-items: center; + font-size: 20px; + color: colors.$grey-dark; + + .delete-icon { + color: colors.$error-color; + } + } +} + +.documents-container { + margin-top: 32px; +} + +:host::ng-deep { + .error-field-outlined.ng-invalid { + border: 1px solid colors.$error-color !important; + + .mat-button-toggle { + border-color: colors.$error-color; + color: colors.$error-color !important; + } + + &.upload-button { + border: 2px solid colors.$error-color; + margin-bottom: 0 !important; + } + } +} diff --git a/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-input.component.spec.ts b/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-input.component.spec.ts new file mode 100644 index 0000000000..3b70768f65 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-input.component.spec.ts @@ -0,0 +1,72 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatDialog } from '@angular/material/dialog'; +import { ActivatedRoute, convertToParamMap, Router } from '@angular/router'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { PlanningReviewDecisionService } from '../../../../services/planning-review/planning-review-decision/planning-review-decision.service'; +import { PlanningReviewDetailService } from '../../../../services/planning-review/planning-review-detail.service'; +import { ToastService } from '../../../../services/toast/toast.service'; +import { StartOfDayPipe } from '../../../../shared/pipes/startOfDay.pipe'; + +import { DecisionInputComponent } from './decision-input.component'; + +describe('DecisionInputComponent', () => { + let component: DecisionInputComponent; + let fixture: ComponentFixture; + let mockPRDecService: DeepMocked; + let mockPRDetailService: DeepMocked; + let mockRouter: DeepMocked; + let mockToastService: DeepMocked; + let mockMatDialog: DeepMocked; + + beforeEach(async () => { + mockPRDecService = createMock(); + mockToastService = createMock(); + mockRouter = createMock(); + mockRouter.navigateByUrl.mockResolvedValue(true); + mockMatDialog = createMock(); + mockPRDetailService = createMock(); + + await TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + declarations: [DecisionInputComponent, StartOfDayPipe], + providers: [ + { + provide: PlanningReviewDecisionService, + useValue: mockPRDecService, + }, + { provide: PlanningReviewDetailService, useValue: mockPRDetailService }, + { + provide: ToastService, + useValue: mockToastService, + }, + { + provide: ActivatedRoute, + useValue: { + snapshot: { + paramMap: convertToParamMap({ uuid: 'fake' }), + }, + }, + }, + { + provide: Router, + useValue: mockRouter, + }, + { + provide: MatDialog, + useValue: mockMatDialog, + }, + ], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(DecisionInputComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-input.component.ts b/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-input.component.ts new file mode 100644 index 0000000000..11a03b252f --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-input.component.ts @@ -0,0 +1,271 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { MatDialog } from '@angular/material/dialog'; +import { ActivatedRoute, Router } from '@angular/router'; +import moment from 'moment'; +import { Subject, takeUntil } from 'rxjs'; +import { + PlanningReviewDecisionDto, + PlanningReviewDecisionOutcomeCodeDto, + UpdatePlanningReviewDecisionDto, +} from '../../../../services/planning-review/planning-review-decision/planning-review-decision.dto'; +import { PlanningReviewDecisionService } from '../../../../services/planning-review/planning-review-decision/planning-review-decision.service'; +import { PlanningReviewDetailService } from '../../../../services/planning-review/planning-review-detail.service'; +import { ToastService } from '../../../../services/toast/toast.service'; +import { formatDateForApi } from '../../../../shared/utils/api-date-formatter'; +import { ReleaseDialogComponent } from '../release-dialog/release-dialog.component'; + +@Component({ + selector: 'app-decision-input', + templateUrl: './decision-input.component.html', + styleUrls: ['./decision-input.component.scss'], +}) +export class DecisionInputComponent implements OnInit, OnDestroy { + $destroy = new Subject(); + isLoading = false; + minDate = new Date(0); + showErrors = false; + index = 1; + requiresDocuments = true; + + fileNumber: string = ''; + uuid: string = ''; + outcomes: PlanningReviewDecisionOutcomeCodeDto[] = []; + + resolutionYears: number[] = []; + existingDecision: PlanningReviewDecisionDto | undefined; + + resolutionNumberControl = new FormControl(null, [Validators.required]); + resolutionYearControl = new FormControl(null, [Validators.required]); + + form = new FormGroup({ + outcome: new FormControl(null, [Validators.required]), + date: new FormControl(undefined, [Validators.required]), + resolutionNumber: this.resolutionNumberControl, + resolutionYear: this.resolutionYearControl, + decisionDescription: new FormControl(undefined, [Validators.required]), + }); + + constructor( + private decisionService: PlanningReviewDecisionService, + public router: Router, + private route: ActivatedRoute, + private toastService: ToastService, + private planningReviewDetailService: PlanningReviewDetailService, + public dialog: MatDialog, + ) {} + + ngOnInit(): void { + this.resolutionYearControl.disable(); + this.setYear(); + + this.extractAndPopulateQueryParams(); + + if (this.fileNumber) { + this.loadData(); + this.setupSubscribers(); + } + } + + private extractAndPopulateQueryParams() { + const fileNumber = this.route.parent?.parent?.snapshot.paramMap.get('fileNumber'); + const uuid = this.route.snapshot.paramMap.get('uuid'); + const index = this.route.snapshot.paramMap.get('index'); + this.index = index ? parseInt(index) : 1; + + if (uuid) { + this.uuid = uuid; + } + + if (fileNumber) { + this.fileNumber = fileNumber; + } + } + + private setYear() { + const year = moment('1974'); + const currentYear = moment().year(); + while (year.year() <= currentYear) { + this.resolutionYears.push(year.year()); + year.add(1, 'year'); + } + this.resolutionYears.reverse(); + } + + ngOnDestroy(): void { + this.decisionService.clearDecision(); + this.decisionService.clearDecisions(); + this.$destroy.next(); + this.$destroy.complete(); + } + + async loadData() { + if (this.uuid) { + await this.decisionService.loadDecision(this.uuid); + } + + await this.decisionService.loadDecisions(this.fileNumber); + + this.outcomes = (await this.decisionService.fetchCodes()) ?? []; + } + + private setupSubscribers() { + this.decisionService.$decision.pipe(takeUntil(this.$destroy)).subscribe((decision) => { + if (decision) { + this.existingDecision = decision; + this.uuid = decision.uuid; + } + + if (this.existingDecision) { + this.patchFormWithExistingData(this.existingDecision); + } else { + this.resolutionYearControl.enable(); + } + }); + } + + private patchFormWithExistingData(existingDecision: PlanningReviewDecisionDto) { + this.form.patchValue({ + outcome: existingDecision.outcome?.code, + date: existingDecision.date ? new Date(existingDecision.date) : undefined, + resolutionYear: existingDecision.resolutionYear, + resolutionNumber: existingDecision.resolutionNumber?.toString(10) || undefined, + decisionDescription: existingDecision.decisionDescription, + }); + + if (!existingDecision.resolutionNumber) { + this.resolutionYearControl.enable(); + } + + if (existingDecision.outcome?.code === 'OTHR') { + this.requiresDocuments = false; + } + } + + async onSubmit(isStayOnPage: boolean = false, isDraft: boolean = true) { + this.isLoading = true; + + try { + await this.saveDecision(isDraft); + } finally { + if (!isStayOnPage) { + this.onCancel(); + } else { + await this.loadData(); + } + + this.isLoading = false; + } + } + + async saveDecision(isDraft: boolean = true) { + const data = this.mapDecisionDataForSave(isDraft); + + if (this.uuid) { + await this.decisionService.update(this.uuid, data); + } + } + + private mapDecisionDataForSave(isDraft: boolean) { + const { date, outcome, resolutionNumber, resolutionYear, decisionDescription } = this.form.getRawValue(); + + const data: UpdatePlanningReviewDecisionDto = { + date: formatDateForApi(date!), + resolutionNumber: parseInt(resolutionNumber!), + resolutionYear: resolutionYear!, + outcomeCode: outcome!, + isDraft, + decisionDescription: decisionDescription, + }; + + return data; + } + + onCancel() { + this.router.navigate([`planning-review/${this.fileNumber}/decision`]); + } + + async onGenerateResolutionNumber() { + const selectedYear = this.form.controls.resolutionYear.getRawValue(); + if (selectedYear) { + const number = await this.decisionService.getNextAvailableResolutionNumber(selectedYear); + if (number) { + this.setResolutionNumber(number); + } else { + this.toastService.showErrorToast('Failed to retrieve resolution number.'); + } + } else { + this.toastService.showErrorToast('Resolution year is not selected. Select a resolution year first.'); + } + } + + private async setResolutionNumber(number: number) { + try { + this.resolutionYearControl.disable(); + this.form.controls.resolutionNumber.setValue(number.toString()); + await this.onSubmit(true); + } catch { + this.resolutionYearControl.enable(); + } + } + + async onDeleteResolutionNumber() { + this.resolutionNumberControl.setValue(null); + await this.onSubmit(true); + this.resolutionYearControl.enable(); + } + + private runValidation() { + this.form.markAllAsTouched(); + this.showErrors = true; + + if ( + !this.form.valid || + !this.existingDecision || + (this.requiresDocuments && this.existingDecision.documents.length === 0) + ) { + this.toastService.showErrorToast('Please correct all errors before submitting the form'); + + // this will ensure that error rendering complete + setTimeout(() => this.scrollToError()); + + return false; + } else { + return true; + } + } + + private scrollToError() { + let elements = document.getElementsByClassName('ng-invalid'); + let elArray = Array.from(elements).filter((el) => el.nodeName !== 'FORM'); + + elArray[0]?.scrollIntoView({ + behavior: 'smooth', + block: 'center', + }); + } + + async onRelease() { + if (this.runValidation()) { + this.dialog + .open(ReleaseDialogComponent, { + minWidth: '600px', + maxWidth: '900px', + maxHeight: '80vh', + width: '90%', + autoFocus: false, + }) + .afterClosed() + .subscribe(async (didAccept) => { + if (didAccept) { + await this.onSubmit(false, false); + await this.planningReviewDetailService.loadReview(this.fileNumber); + } + }); + } + } + + onChangeDecisionOutcome(selectedOutcome: PlanningReviewDecisionOutcomeCodeDto) { + this.requiresDocuments = selectedOutcome.code !== 'OTHR'; + } +} diff --git a/alcs-frontend/src/app/features/planning-review/decision/decision.component.html b/alcs-frontend/src/app/features/planning-review/decision/decision.component.html new file mode 100644 index 0000000000..bfbf8398f7 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/decision/decision.component.html @@ -0,0 +1,78 @@ +
+

Decisions

+
+ +
+
+
+
No Decisions
+
+
+
+
+
+
+

Decision #{{ decisions.length - i }}

+
Res #{{ decision.resolutionNumber }}/{{ decision.resolutionYear }}
+ + + + + + +
+ + + + +
+ +
+
+ +

Resolution

+
+
+
+
Decision Date
+ {{ decision.date | momentFormat }} + +
+
+
Decision Outcome
+ {{ decision.outcome?.label }} + +
+ +
+
Decision Description
+ {{ decision.decisionDescription }} + +
+
+
+ +

Documents

+
+ +
+ +
+ +
+
+
+
diff --git a/alcs-frontend/src/app/features/planning-review/decision/decision.component.scss b/alcs-frontend/src/app/features/planning-review/decision/decision.component.scss new file mode 100644 index 0000000000..6ec9eb54e7 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/decision/decision.component.scss @@ -0,0 +1,156 @@ +@use '../../../../styles/colors'; + +section { + margin-bottom: 64px; +} + +h4 { + margin-bottom: 12px !important; +} + +hr { + margin: 36px 0; + stroke: colors.$grey; +} + +.decision-container { + position: relative; +} + +.decision { + margin: 24px 0; + box-shadow: 0 2px 8px 1px rgba(0, 0, 0, 0.25); +} + +.decision.draft { + border: 2px solid colors.$secondary-color-light; +} + +.decision-section { + background: colors.$grey-light; + padding: 18px; +} + +.decision-section-no-title { + background: colors.$grey-light; + padding: 1px 18px; +} + +.header { + display: flex; + justify-content: space-between; + margin-bottom: 36px; + + .title { + display: flex; + align-items: center; + justify-content: space-between; + gap: 28px; + + .days { + display: inline-block; + margin-right: 6px; + margin-top: 4px; + + .mat-icon { + font-size: 19px !important; + line-height: 21px !important; + width: 19px; + vertical-align: middle; + } + } + } +} + +.loading-overlay { + position: absolute; + z-index: 2; + background-color: colors.$grey; + opacity: 0.4; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +.post-decisions { + padding: 6px 32px; + background-color: colors.$grey-light; + grid-template-columns: 50% 50%; + display: grid; + min-height: 36px; + text-transform: uppercase; + border-radius: 4px 4px 0 0; +} + +.decision-menu { + position: absolute; + top: 0; + right: 0; + height: 36px; + background: colors.$accent-color; + box-shadow: -1px 1px 4px rgba(0, 0, 0, 0.25); + border-radius: 0 4px 0 10px; +} + +.decision-padding { + padding: 18px 32px; +} + +.no-decisions { + margin-top: 16px; + display: flex; + align-items: center; + justify-content: center; + background-color: colors.$grey-light; + height: 72px; +} + +.conditions-link-icon { + position: absolute; +} + +:host ::ng-deep { + .grid-2 { + margin-top: 18px; + margin-bottom: 18px; + display: grid; + grid-template-columns: 50% 50%; + grid-row-gap: 18px; + + .full-width { + grid-column: 1/3; + } + } + + .mat-mdc-table { + background: colors.$grey-light; + } + + .subheading2 { + margin-bottom: 6px !important; + } + + .row { + margin: 16px 0; + } + + .application-pill-wrapper, + .application-pill { + margin-right: 0 !important; + } + + table { + border-spacing: 12px; + margin-left: -12px; + } + + .soil-table-label { + font-weight: 700; + } + + .pre-wrapped-text { + white-space: pre-wrap; + } +} diff --git a/alcs-frontend/src/app/features/planning-review/decision/decision.component.spec.ts b/alcs-frontend/src/app/features/planning-review/decision/decision.component.spec.ts new file mode 100644 index 0000000000..19b40612bc --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/decision/decision.component.spec.ts @@ -0,0 +1,76 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { ActivatedRoute } from '@angular/router'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { BehaviorSubject } from 'rxjs'; +import { PlanningReviewDecisionDto } from '../../../services/planning-review/planning-review-decision/planning-review-decision.dto'; +import { PlanningReviewDecisionService } from '../../../services/planning-review/planning-review-decision/planning-review-decision.service'; +import { PlanningReviewDetailService } from '../../../services/planning-review/planning-review-detail.service'; +import { PlanningReviewDetailedDto } from '../../../services/planning-review/planning-review.dto'; +import { ToastService } from '../../../services/toast/toast.service'; +import { ConfirmationDialogService } from '../../../shared/confirmation-dialog/confirmation-dialog.service'; + +import { DecisionComponent } from './decision.component'; + +describe('DecisionComponent', () => { + let component: DecisionComponent; + let fixture: ComponentFixture; + let mockPRDecisionService: DeepMocked; + let mockPRDetailService: DeepMocked; + + beforeEach(async () => { + mockPRDecisionService = createMock(); + mockPRDecisionService.$decision = new BehaviorSubject(undefined); + mockPRDecisionService.$decisions = new BehaviorSubject([]); + + mockPRDetailService = createMock(); + mockPRDetailService.$planningReview = new BehaviorSubject(undefined); + + await TestBed.configureTestingModule({ + imports: [MatSnackBarModule, MatMenuModule], + declarations: [DecisionComponent], + providers: [ + { + provide: PlanningReviewDecisionService, + useValue: mockPRDecisionService, + }, + { + provide: PlanningReviewDetailService, + useValue: mockPRDetailService, + }, + { + provide: MatDialogRef, + useValue: {}, + }, + { + provide: ConfirmationDialogService, + useValue: {}, + }, + { + provide: ToastService, + useValue: {}, + }, + { + provide: MatDialog, + useValue: {}, + }, + { + provide: ActivatedRoute, + useValue: {}, + }, + ], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(DecisionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alcs-frontend/src/app/features/planning-review/decision/decision.component.ts b/alcs-frontend/src/app/features/planning-review/decision/decision.component.ts new file mode 100644 index 0000000000..9d332f2d44 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/decision/decision.component.ts @@ -0,0 +1,166 @@ +import { Component, ElementRef, OnDestroy, OnInit } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Subject, takeUntil } from 'rxjs'; +import { APPLICATION_DECISION_COMPONENT_TYPE } from '../../../services/application/decision/application-decision-v2/application-decision-v2.dto'; +import { + PlanningReviewDecisionDto, + PlanningReviewDecisionOutcomeCodeDto, +} from '../../../services/planning-review/planning-review-decision/planning-review-decision.dto'; +import { PlanningReviewDecisionService } from '../../../services/planning-review/planning-review-decision/planning-review-decision.service'; +import { PlanningReviewDetailService } from '../../../services/planning-review/planning-review-detail.service'; +import { PlanningReviewDto } from '../../../services/planning-review/planning-review.dto'; +import { ToastService } from '../../../services/toast/toast.service'; +import { + DRAFT_DECISION_TYPE_LABEL, + RELEASED_DECISION_TYPE_LABEL, +} from '../../../shared/application-type-pill/application-type-pill.constants'; +import { ConfirmationDialogService } from '../../../shared/confirmation-dialog/confirmation-dialog.service'; +import { RevertToDraftDialogComponent } from './revert-to-draft-dialog/revert-to-draft-dialog.component'; + +@Component({ + selector: 'app-decision-v2', + templateUrl: './decision.component.html', + styleUrls: ['./decision.component.scss'], +}) +export class DecisionComponent implements OnInit, OnDestroy { + $destroy = new Subject(); + isDraftExists = true; + disabledCreateBtnTooltip = + 'A planning review can only have one decision draft at a time. Please release or delete the existing decision draft to continue.'; + + fileNumber: string = ''; + decisionDate: number | undefined; + decisions: PlanningReviewDecisionDto[] = []; + outcomes: PlanningReviewDecisionOutcomeCodeDto[] = []; + + planningReview: PlanningReviewDto | undefined; + dratDecisionLabel = DRAFT_DECISION_TYPE_LABEL; + releasedDecisionLabel = RELEASED_DECISION_TYPE_LABEL; + + constructor( + public dialog: MatDialog, + private planningReviewDetailService: PlanningReviewDetailService, + private decisionService: PlanningReviewDecisionService, + private toastService: ToastService, + private confirmationDialogService: ConfirmationDialogService, + private router: Router, + private activatedRouter: ActivatedRoute, + private elementRef: ElementRef, + ) {} + + ngOnInit(): void { + this.planningReviewDetailService.$planningReview.pipe(takeUntil(this.$destroy)).subscribe((planningReview) => { + if (planningReview) { + this.fileNumber = planningReview.fileNumber; + this.decisionDate = planningReview.decisionDate; + this.loadDecisions(planningReview.fileNumber); + this.planningReview = planningReview; + } + }); + } + + async loadDecisions(fileNumber: string) { + const codes = await this.decisionService.fetchCodes(); + if (codes) { + this.outcomes = codes; + } + this.decisionService.loadDecisions(fileNumber); + + this.isDraftExists = this.decisions.some((d) => d.isDraft); + + this.decisionService.$decisions.pipe(takeUntil(this.$destroy)).subscribe((decisions) => { + this.decisions = decisions; + this.isDraftExists = this.decisions.some((d) => d.isDraft); + this.scrollToDecision(); + }); + } + + scrollToDecision() { + const decisionUuid = this.activatedRouter.snapshot.queryParamMap.get('uuid'); + + setTimeout(() => { + if (this.decisions.length > 0 && decisionUuid) { + this.scrollToElement(decisionUuid); + } + }); + } + + async onCreate() { + const newDecision = await this.decisionService.create({ + planningReviewFileNumber: this.fileNumber, + }); + + const index = this.decisions.length; + await this.router.navigate([ + `/planning-review/${this.fileNumber}/decision/draft/${newDecision.uuid}/edit/${index + 1}`, + ]); + } + + async onEdit(selectedDecision: PlanningReviewDecisionDto) { + const position = this.decisions.findIndex((decision) => decision.uuid === selectedDecision.uuid); + const index = this.decisions.length - position; + await this.router.navigate([ + `/planning-review/${this.fileNumber}/decision/draft/${selectedDecision.uuid}/edit/${index}`, + ]); + } + + async onRevertToDraft(uuid: string) { + const position = this.decisions.findIndex((decision) => decision.uuid === uuid); + const index = this.decisions.length - position; + this.dialog + .open(RevertToDraftDialogComponent, { + data: { fileNumber: this.fileNumber }, + }) + .beforeClosed() + .subscribe(async (didConfirm) => { + if (didConfirm) { + await this.decisionService.update(uuid, { + isDraft: true, + }); + await this.planningReviewDetailService.loadReview(this.fileNumber); + + await this.router.navigate([`/planning-review/${this.fileNumber}/decision/draft/${uuid}/edit/${index}`]); + } + }); + } + + async deleteDecision(uuid: string) { + this.confirmationDialogService + .openDialog({ + body: 'Are you sure you want to delete the selected decision?', + }) + .subscribe(async (confirmed) => { + if (confirmed) { + this.decisions = this.decisions.map((decision) => { + return { + ...decision, + loading: decision.uuid === uuid, + }; + }); + await this.decisionService.delete(uuid); + await this.planningReviewDetailService.loadReview(this.fileNumber); + this.toastService.showSuccessToast('Decision deleted'); + } + }); + } + + ngOnDestroy(): void { + this.decisionService.clearDecisions(); + this.$destroy.next(); + this.$destroy.complete(); + } + + scrollToElement(elementId: string) { + const id = `#${CSS.escape(elementId)}`; + const element = this.elementRef.nativeElement.querySelector(id); + + if (element) { + element.scrollIntoView({ + behavior: 'smooth', + block: 'start', + inline: 'start', + }); + } + } +} diff --git a/alcs-frontend/src/app/features/planning-review/decision/decision.module.ts b/alcs-frontend/src/app/features/planning-review/decision/decision.module.ts new file mode 100644 index 0000000000..be0cf9b7a4 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/decision/decision.module.ts @@ -0,0 +1,46 @@ +import { NgModule } from '@angular/core'; +import { MatOptionModule } from '@angular/material/core'; +import { MatTabsModule } from '@angular/material/tabs'; +import { RouterModule } from '@angular/router'; +import { SharedModule } from '../../../shared/shared.module'; +import { DecisionDocumentsComponent } from './decision-documents/decision-documents.component'; +import { DecisionDocumentUploadDialogComponent } from './decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component'; +import { DecisionInputComponent } from './decision-input/decision-input.component'; +import { DecisionComponent } from './decision.component'; +import { ReleaseDialogComponent } from './release-dialog/release-dialog.component'; +import { RevertToDraftDialogComponent } from './revert-to-draft-dialog/revert-to-draft-dialog.component'; + +export const decisionChildRoutes = [ + { + path: '', + menuTitle: 'Decision', + component: DecisionComponent, + portalOnly: false, + }, + { + path: 'create', + menuTitle: 'Decision', + component: DecisionInputComponent, + portalOnly: false, + }, + { + path: 'draft/:uuid/edit/:index', + menuTitle: 'Decision', + component: DecisionInputComponent, + portalOnly: false, + }, +]; + +@NgModule({ + declarations: [ + DecisionComponent, + DecisionComponent, + DecisionInputComponent, + DecisionDocumentsComponent, + RevertToDraftDialogComponent, + ReleaseDialogComponent, + DecisionDocumentUploadDialogComponent, + ], + imports: [SharedModule, RouterModule.forChild(decisionChildRoutes), MatTabsModule, MatOptionModule], +}) +export class DecisionModule {} diff --git a/alcs-frontend/src/app/features/planning-review/decision/release-dialog/release-dialog.component.html b/alcs-frontend/src/app/features/planning-review/decision/release-dialog/release-dialog.component.html new file mode 100644 index 0000000000..86fbc042a3 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/decision/release-dialog/release-dialog.component.html @@ -0,0 +1,19 @@ +
+

Confirm Release Decision

+
+ +
+

+ Decisions are not currently visible outside of the ALC so this action finalizes the decision but doesn't release the decision externally. + There are no auto-emails associated with this action.
+ This action does not update the status of the planning review file. +

+
+
+ + +
+ + +
+
diff --git a/alcs-frontend/src/app/features/planning-review/decision/release-dialog/release-dialog.component.scss b/alcs-frontend/src/app/features/planning-review/decision/release-dialog/release-dialog.component.scss new file mode 100644 index 0000000000..756d26e192 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/decision/release-dialog/release-dialog.component.scss @@ -0,0 +1,6 @@ +.grid { + display: grid; + grid-template-columns: 1fr; + grid-column-gap: 24px; + grid-row-gap: 24px; +} diff --git a/alcs-frontend/src/app/features/planning-review/decision/release-dialog/release-dialog.component.spec.ts b/alcs-frontend/src/app/features/planning-review/decision/release-dialog/release-dialog.component.spec.ts new file mode 100644 index 0000000000..b649151a3a --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/decision/release-dialog/release-dialog.component.spec.ts @@ -0,0 +1,31 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { ReleaseDialogComponent } from './release-dialog.component'; + +describe('ReleaseDialogComponent', () => { + let component: ReleaseDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ReleaseDialogComponent], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { + provide: MAT_DIALOG_DATA, + useValue: {}, + }, + ], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(ReleaseDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alcs-frontend/src/app/features/planning-review/decision/release-dialog/release-dialog.component.ts b/alcs-frontend/src/app/features/planning-review/decision/release-dialog/release-dialog.component.ts new file mode 100644 index 0000000000..be2dfeb60a --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/decision/release-dialog/release-dialog.component.ts @@ -0,0 +1,18 @@ +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; + +@Component({ + selector: 'app-release-dialog', + templateUrl: './release-dialog.component.html', + styleUrls: ['./release-dialog.component.scss'], +}) +export class ReleaseDialogComponent { + constructor( + public matDialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) data: any, + ) {} + + onRelease() { + this.matDialogRef.close(true); + } +} diff --git a/alcs-frontend/src/app/features/planning-review/decision/revert-to-draft-dialog/revert-to-draft-dialog.component.html b/alcs-frontend/src/app/features/planning-review/decision/revert-to-draft-dialog/revert-to-draft-dialog.component.html new file mode 100644 index 0000000000..3204415807 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/decision/revert-to-draft-dialog/revert-to-draft-dialog.component.html @@ -0,0 +1,18 @@ +
+

Confirm Revert to Draft

+
+ +
+

+ There are no auto-emails associated with this action.
+ This action does not update the status of the planning review file. +

+
+
+ + +
+ + +
+
diff --git a/alcs-frontend/src/app/features/planning-review/decision/revert-to-draft-dialog/revert-to-draft-dialog.component.scss b/alcs-frontend/src/app/features/planning-review/decision/revert-to-draft-dialog/revert-to-draft-dialog.component.scss new file mode 100644 index 0000000000..756d26e192 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/decision/revert-to-draft-dialog/revert-to-draft-dialog.component.scss @@ -0,0 +1,6 @@ +.grid { + display: grid; + grid-template-columns: 1fr; + grid-column-gap: 24px; + grid-row-gap: 24px; +} diff --git a/alcs-frontend/src/app/features/planning-review/decision/revert-to-draft-dialog/revert-to-draft-dialog.component.spec.ts b/alcs-frontend/src/app/features/planning-review/decision/revert-to-draft-dialog/revert-to-draft-dialog.component.spec.ts new file mode 100644 index 0000000000..fe67054014 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/decision/revert-to-draft-dialog/revert-to-draft-dialog.component.spec.ts @@ -0,0 +1,30 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; + +import { RevertToDraftDialogComponent } from './revert-to-draft-dialog.component'; + +describe('RevertToDraftDialogComponent', () => { + let component: RevertToDraftDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [RevertToDraftDialogComponent], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { provide: MAT_DIALOG_DATA, useValue: {} }, + ], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(RevertToDraftDialogComponent); + component = fixture.componentInstance; + + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alcs-frontend/src/app/features/planning-review/decision/revert-to-draft-dialog/revert-to-draft-dialog.component.ts b/alcs-frontend/src/app/features/planning-review/decision/revert-to-draft-dialog/revert-to-draft-dialog.component.ts new file mode 100644 index 0000000000..fa3ce0f416 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/decision/revert-to-draft-dialog/revert-to-draft-dialog.component.ts @@ -0,0 +1,18 @@ +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; + +@Component({ + selector: 'app-pr-revert-to-draft-dialog', + templateUrl: './revert-to-draft-dialog.component.html', + styleUrls: ['./revert-to-draft-dialog.component.scss'], +}) +export class RevertToDraftDialogComponent { + constructor( + public matDialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) data: { fileNumber: string }, + ) {} + + onConfirm() { + this.matDialogRef.close(true); + } +} diff --git a/alcs-frontend/src/app/features/planning-review/header/header.component.spec.ts b/alcs-frontend/src/app/features/planning-review/header/header.component.spec.ts index 252adb9bbc..89016c03ba 100644 --- a/alcs-frontend/src/app/features/planning-review/header/header.component.spec.ts +++ b/alcs-frontend/src/app/features/planning-review/header/header.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; @@ -12,6 +13,7 @@ describe('HeaderComponent', () => { imports: [RouterTestingModule], declarations: [HeaderComponent], providers: [], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); fixture = TestBed.createComponent(HeaderComponent); diff --git a/alcs-frontend/src/app/features/planning-review/planning-review.component.ts b/alcs-frontend/src/app/features/planning-review/planning-review.component.ts index fdd55b1b22..eba0051892 100644 --- a/alcs-frontend/src/app/features/planning-review/planning-review.component.ts +++ b/alcs-frontend/src/app/features/planning-review/planning-review.component.ts @@ -3,6 +3,7 @@ import { ActivatedRoute } from '@angular/router'; import { Subject, takeUntil } from 'rxjs'; import { PlanningReviewDetailService } from '../../services/planning-review/planning-review-detail.service'; import { PlanningReviewDetailedDto } from '../../services/planning-review/planning-review.dto'; +import { decisionChildRoutes, DecisionModule } from './decision/decision.module'; import { DocumentsComponent } from './documents/documents.component'; import { OverviewComponent } from './overview/overview.component'; import { ReferralComponent } from './referrals/referral.component'; @@ -26,6 +27,14 @@ export const childRoutes = [ icon: 'description', component: DocumentsComponent, }, + { + path: 'decision', + menuTitle: 'Decisions', + icon: 'gavel', + module: DecisionModule, + portalOnly: false, + children: decisionChildRoutes, + }, ]; @Component({ diff --git a/alcs-frontend/src/app/services/planning-review/planning-review-decision/planning-review-decision.dto.ts b/alcs-frontend/src/app/services/planning-review/planning-review-decision/planning-review-decision.dto.ts new file mode 100644 index 0000000000..1c74f5c5af --- /dev/null +++ b/alcs-frontend/src/app/services/planning-review/planning-review-decision/planning-review-decision.dto.ts @@ -0,0 +1,39 @@ +import { BaseCodeDto } from '../../../shared/dto/base.dto'; + +export interface UpdatePlanningReviewDecisionDto { + resolutionNumber?: number; + resolutionYear?: number; + date?: number; + outcomeCode?: string; + decisionDescription?: string | null; + isDraft?: boolean; +} + +export interface CreatePlanningReviewDecisionDto { + planningReviewFileNumber: string; +} + +export interface PlanningReviewDecisionOutcomeCodeDto extends BaseCodeDto {} + +export interface PlanningReviewDecisionDto { + uuid: string; + planningReviewFileNumber: string; + date?: number; + resolutionNumber: number; + resolutionYear: number; + documents: PlanningReviewDecisionDocumentDto[]; + isDraft: boolean; + decisionDescription?: string | null; + createdAt?: number | null; + wasReleased: boolean; + outcome?: PlanningReviewDecisionOutcomeCodeDto; +} + +export interface PlanningReviewDecisionDocumentDto { + uuid: string; + fileName: string; + fileSize: number; + mimeType: string; + uploadedBy: string; + uploadedAt: number; +} diff --git a/alcs-frontend/src/app/services/planning-review/planning-review-decision/planning-review-decision.service.spec.ts b/alcs-frontend/src/app/services/planning-review/planning-review-decision/planning-review-decision.service.spec.ts new file mode 100644 index 0000000000..7f2a333d63 --- /dev/null +++ b/alcs-frontend/src/app/services/planning-review/planning-review-decision/planning-review-decision.service.spec.ts @@ -0,0 +1,201 @@ +import { HttpClient } from '@angular/common/http'; +import { TestBed } from '@angular/core/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { of, throwError } from 'rxjs'; +import { environment } from '../../../../environments/environment'; +import { ToastService } from '../../toast/toast.service'; +import { PlanningReviewDecisionService } from './planning-review-decision.service'; + +describe('PlanningReviewDecisionService', () => { + let service: PlanningReviewDecisionService; + let httpClient: DeepMocked; + let toastService: DeepMocked; + + beforeEach(() => { + httpClient = createMock(); + toastService = createMock(); + + TestBed.configureTestingModule({ + providers: [ + { + provide: HttpClient, + useValue: httpClient, + }, + { + provide: ToastService, + useValue: toastService, + }, + ], + }); + service = TestBed.inject(PlanningReviewDecisionService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should fetch and return a mapped dto', async () => { + httpClient.get.mockReturnValue( + of([ + { + planningReviewFileNumber: '1', + }, + ]), + ); + + const res = await service.fetchByPlanningReview('1'); + + expect(res.length).toEqual(1); + expect(res[0].planningReviewFileNumber).toEqual('1'); + }); + + it('should show a toast message if fetch fails', async () => { + httpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }), + ); + + const res = await service.fetchByPlanningReview('1'); + + expect(res.length).toEqual(0); + expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + + it('should make an http patch and show a success toast when updating', async () => { + httpClient.patch.mockReturnValue( + of({ + planningReviewFileNumber: '1', + }), + ); + + await service.update('1', {}); + + expect(httpClient.patch).toHaveBeenCalledTimes(1); + expect(toastService.showSuccessToast).toHaveBeenCalledTimes(1); + }); + + it('should show a toast message if update fails', async () => { + httpClient.patch.mockReturnValue( + throwError(() => { + new Error(''); + }), + ); + + try { + await service.update('1', {}); + } catch (e) { + //OM NOM NOM + } + + expect(httpClient.patch).toHaveBeenCalledTimes(1); + expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + + it('should make an http post and show a success toast when creating', async () => { + httpClient.post.mockReturnValue( + of({ + planningReviewFileNumber: '1', + }), + ); + + await service.create({ + planningReviewFileNumber: '', + }); + + expect(httpClient.post).toHaveBeenCalledTimes(1); + expect(toastService.showSuccessToast).toHaveBeenCalledTimes(1); + }); + + it('should show a toast message if create fails', async () => { + httpClient.post.mockReturnValue( + throwError(() => { + new Error(''); + }), + ); + + try { + await service.create({ + planningReviewFileNumber: '', + }); + } catch (e) { + //OM NOM NOM + } + + expect(httpClient.post).toHaveBeenCalledTimes(1); + expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + + it('should make an http delete and show a success toast', async () => { + httpClient.delete.mockReturnValue( + of({ + planningReviewFileNumber: '1', + }), + ); + + await service.delete(''); + + expect(httpClient.delete).toHaveBeenCalledTimes(1); + expect(toastService.showSuccessToast).toHaveBeenCalledTimes(1); + }); + + it('should show a toast message if delete fails', async () => { + httpClient.delete.mockReturnValue( + throwError(() => { + new Error(''); + }), + ); + + try { + await service.delete(''); + } catch (e) { + //OM NOM NOM + } + + expect(httpClient.delete).toHaveBeenCalledTimes(1); + expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + + it('should make an http patch call for update file', async () => { + httpClient.patch.mockReturnValue( + of([ + { + fileNumber: '1', + }, + ]), + ); + await service.updateFile('', '', ''); + + expect(httpClient.patch).toHaveBeenCalledTimes(1); + }); + + it('should show a toast warning when uploading a file thats too large', async () => { + const file = createMock(); + Object.defineProperty(file, 'size', { value: environment.maxFileSize + 1 }); + + await service.uploadFile('', file); + + expect(toastService.showWarningToast).toHaveBeenCalledTimes(1); + expect(httpClient.post).toHaveBeenCalledTimes(0); + }); + + it('should make an http delete when deleting a file', async () => { + httpClient.delete.mockReturnValue( + of({ + planningReviewFileNumber: '1', + }), + ); + + await service.deleteFile('', ''); + + expect(httpClient.delete).toHaveBeenCalledTimes(1); + }); + + it('should make an http get when requesting a new resolution number', async () => { + httpClient.get.mockReturnValue(of(1)); + + await service.getNextAvailableResolutionNumber(2023); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + }); +}); diff --git a/alcs-frontend/src/app/services/planning-review/planning-review-decision/planning-review-decision.service.ts b/alcs-frontend/src/app/services/planning-review/planning-review-decision/planning-review-decision.service.ts new file mode 100644 index 0000000000..aeb151a463 --- /dev/null +++ b/alcs-frontend/src/app/services/planning-review/planning-review-decision/planning-review-decision.service.ts @@ -0,0 +1,178 @@ +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { BehaviorSubject, firstValueFrom } from 'rxjs'; +import { environment } from '../../../../environments/environment'; +import { downloadFileFromUrl, openFileInline } from '../../../shared/utils/file'; +import { verifyFileSize } from '../../../shared/utils/file-size-checker'; +import { ToastService } from '../../toast/toast.service'; +import { + CreatePlanningReviewDecisionDto, + PlanningReviewDecisionDto, + PlanningReviewDecisionOutcomeCodeDto, + UpdatePlanningReviewDecisionDto, +} from './planning-review-decision.dto'; + +@Injectable({ + providedIn: 'root', +}) +export class PlanningReviewDecisionService { + private url = `${environment.apiUrl}/planning-review-decision`; + private decision: PlanningReviewDecisionDto | undefined; + private decisions: PlanningReviewDecisionDto[] = []; + $decision = new BehaviorSubject(undefined); + $decisions = new BehaviorSubject([]); + + constructor( + private http: HttpClient, + private toastService: ToastService, + ) {} + + async fetchByPlanningReview(fileNumber: string) { + let decisions: PlanningReviewDecisionDto[] = []; + try { + decisions = await firstValueFrom( + this.http.get(`${this.url}/planning-review/${fileNumber}`), + ); + } catch (err) { + this.toastService.showErrorToast('Failed to fetch decisions'); + } + return decisions; + } + + async fetchCodes() { + try { + return await firstValueFrom(this.http.get(`${this.url}/codes`)); + } catch (err) { + this.toastService.showErrorToast('Failed to fetch decisions'); + } + return; + } + + async update(uuid: string, data: UpdatePlanningReviewDecisionDto) { + try { + const res = await firstValueFrom(this.http.patch(`${this.url}/${uuid}`, data)); + this.toastService.showSuccessToast('Decision updated'); + return res; + } catch (e) { + if (e instanceof HttpErrorResponse && e.status === 400 && e.error?.message) { + this.toastService.showErrorToast(e.error.message); + } else { + this.toastService.showErrorToast('Failed to update decision'); + } + throw e; + } + } + + async create(decision: CreatePlanningReviewDecisionDto) { + try { + const res = await firstValueFrom(this.http.post(`${this.url}`, decision)); + this.toastService.showSuccessToast('Decision created'); + return res; + } catch (e) { + if (e instanceof HttpErrorResponse && e.status === 400 && e.error?.message) { + this.toastService.showErrorToast(e.error.message); + } else { + this.toastService.showErrorToast(`Failed to create decision`); + } + throw e; + } + } + + async delete(uuid: string) { + try { + await firstValueFrom(this.http.delete(`${this.url}/${uuid}`)); + this.toastService.showSuccessToast('Decision deleted'); + } catch (err) { + this.toastService.showErrorToast('Failed to delete meeting'); + } + } + + async uploadFile(decisionUuid: string, file: File) { + const isValidSize = verifyFileSize(file, this.toastService); + if (!isValidSize) { + return; + } + + let formData: FormData = new FormData(); + formData.append('file', file, file.name); + const res = await firstValueFrom(this.http.post(`${this.url}/${decisionUuid}/file`, formData)); + this.toastService.showSuccessToast('Document uploaded'); + return res; + } + + async downloadFile(decisionUuid: string, documentUuid: string, fileName: string, isInline = true) { + const url = `${this.url}/${decisionUuid}/file/${documentUuid}`; + const finalUrl = isInline ? `${url}/open` : `${url}/download`; + const data = await firstValueFrom(this.http.get<{ url: string }>(finalUrl)); + if (isInline) { + openFileInline(data.url, fileName); + } else { + downloadFileFromUrl(data.url, fileName); + } + } + + async updateFile(decisionUuid: string, documentUuid: string, fileName: string) { + try { + await firstValueFrom( + this.http.patch(`${this.url}/${decisionUuid}/file/${documentUuid}`, { + fileName, + }), + ); + this.toastService.showSuccessToast('File updated'); + } catch (err) { + this.toastService.showErrorToast('Failed to update file'); + } + } + + async deleteFile(decisionUuid: string, documentUuid: string) { + const url = `${this.url}/${decisionUuid}/file/${documentUuid}`; + return await firstValueFrom(this.http.delete<{ url: string }>(url)); + } + + async getByUuid(uuid: string) { + let decision: PlanningReviewDecisionDto | undefined; + try { + decision = await firstValueFrom(this.http.get(`${this.url}/${uuid}`)); + } catch (err) { + this.toastService.showErrorToast('Failed to fetch decision'); + } + return decision; + } + + async loadDecision(uuid: string) { + this.clearDecision(); + this.decision = await this.getByUuid(uuid); + this.$decision.next(this.decision); + } + + async loadDecisions(fileNumber: string) { + this.clearDecisions(); + const decisions = await this.fetchByPlanningReview(fileNumber); + const decisionsLength = decisions.length; + + this.decisions = decisions.map((decision, ind) => ({ + ...decision, + index: decisionsLength - ind, + })); + + this.$decisions.next(this.decisions); + } + + clearDecision() { + this.$decision.next(undefined); + } + + clearDecisions() { + this.$decisions.next([]); + } + + async getNextAvailableResolutionNumber(resolutionYear: number) { + let result: number | undefined = undefined; + try { + result = await firstValueFrom(this.http.get(`${this.url}/next-resolution-number/${resolutionYear}`)); + } catch (err) { + this.toastService.showErrorToast('Failed to fetch resolutionNumber'); + } + return result; + } +} diff --git a/alcs-frontend/src/app/services/planning-review/planning-review-detail.service.ts b/alcs-frontend/src/app/services/planning-review/planning-review-detail.service.ts index e2aeb87e96..d47146dda1 100644 --- a/alcs-frontend/src/app/services/planning-review/planning-review-detail.service.ts +++ b/alcs-frontend/src/app/services/planning-review/planning-review-detail.service.ts @@ -7,14 +7,11 @@ import { PlanningReviewService } from './planning-review.service'; export class PlanningReviewDetailService { $planningReview = new BehaviorSubject(undefined); - private selectedFileNumber: string | undefined; - constructor(private planningReviewService: PlanningReviewService) {} async loadReview(fileNumber: string) { this.clearReview(); - this.selectedFileNumber = fileNumber; const planningReview = await this.planningReviewService.fetchDetailedByFileNumber(fileNumber); this.$planningReview.next(planningReview); } 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 0871f29396..b29e725e77 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 @@ -16,6 +16,7 @@ export interface CreatePlanningReviewDto { export interface PlanningReviewDto { uuid: string; fileNumber: string; + decisionDate?: number; legacyId: string | null; open: boolean; localGovernment: ApplicationLocalGovernmentDto; diff --git a/services/apps/alcs/src/alcs/alcs.module.ts b/services/apps/alcs/src/alcs/alcs.module.ts index eda56204d1..2038251041 100644 --- a/services/apps/alcs/src/alcs/alcs.module.ts +++ b/services/apps/alcs/src/alcs/alcs.module.ts @@ -22,6 +22,7 @@ import { MessageModule } from './message/message.module'; import { NotificationSubmissionStatusModule } from './notification/notification-submission-status/notification-submission-status.module'; import { NotificationTimelineModule } from './notification/notification-timeline/notification-timeline.module'; import { NotificationModule } from './notification/notification.module'; +import { PlanningReviewDecisionModule } from './planning-review/planning-review-decision/planning-review-decision.module'; import { PlanningReviewModule } from './planning-review/planning-review.module'; import { SearchModule } from './search/search.module'; import { StaffJournalModule } from './staff-journal/staff-journal.module'; @@ -35,6 +36,7 @@ import { StaffJournalModule } from './staff-journal/staff-journal.module'; BoardModule, CodeModule, PlanningReviewModule, + PlanningReviewDecisionModule, CovenantModule, CommissionerModule, ApplicationDecisionModule, @@ -57,6 +59,7 @@ import { StaffJournalModule } from './staff-journal/staff-journal.module'; { path: 'alcs', module: BoardModule }, { path: 'alcs', module: CodeModule }, { path: 'alcs', module: PlanningReviewModule }, + { path: 'alcs', module: PlanningReviewDecisionModule }, { path: 'alcs', module: CovenantModule }, { path: 'alcs', module: CommissionerModule }, { path: 'alcs', module: ApplicationDecisionModule }, diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision-document/planning-review-decision-document.entity.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision-document/planning-review-decision-document.entity.ts new file mode 100644 index 0000000000..b91910325b --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision-document/planning-review-decision-document.entity.ts @@ -0,0 +1,38 @@ +import { AutoMap } from 'automapper-classes'; +import { + Column, + Entity, + JoinColumn, + ManyToOne, + OneToOne, + PrimaryGeneratedColumn, +} from 'typeorm'; +import { Auditable } from '../../../../common/entities/audit.entity'; +import { Document } from '../../../../document/document.entity'; +import { PlanningReviewDecision } from '../planning-review-decision.entity'; + +@Entity() +export class PlanningReviewDecisionDocument extends Auditable { + constructor(data?: Partial) { + super(); + if (data) { + Object.assign(this, data); + } + } + + @AutoMap() + @PrimaryGeneratedColumn('uuid') + uuid: string; + + @ManyToOne(() => PlanningReviewDecision, { nullable: false }) + decision: PlanningReviewDecision; + + @Column() + decisionUuid: string; + + @OneToOne(() => Document, { + cascade: true, + }) + @JoinColumn() + document: Document; +} diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision-outcome.entity.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision-outcome.entity.ts new file mode 100644 index 0000000000..bbd6440c9b --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision-outcome.entity.ts @@ -0,0 +1,5 @@ +import { Entity } from 'typeorm'; +import { BaseCodeEntity } from '../../../common/entities/base.code.entity'; + +@Entity() +export class PlanningReviewDecisionOutcomeCode extends BaseCodeEntity {} diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.controller.spec.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.controller.spec.ts new file mode 100644 index 0000000000..c511394a09 --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.controller.spec.ts @@ -0,0 +1,207 @@ +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 { PlanningReviewDecisionProfile } from '../../../common/automapper/planning-review-decision.automapper.profile'; +import { PlanningReviewProfile } from '../../../common/automapper/planning-review.automapper.profile'; +import { UserProfile } from '../../../common/automapper/user.automapper.profile'; +import { PlanningReview } from '../planning-review.entity'; +import { PlanningReviewDecisionOutcomeCode } from './planning-review-decision-outcome.entity'; +import { PlanningReviewDecisionController } from './planning-review-decision.controller'; +import { + CreatePlanningReviewDecisionDto, + UpdatePlanningReviewDecisionDto, +} from './planning-review-decision.dto'; +import { PlanningReviewDecision } from './planning-review-decision.entity'; +import { PlanningReviewDecisionService } from './planning-review-decision.service'; + +describe('PlanningReviewDecisionController', () => { + let controller: PlanningReviewDecisionController; + let mockDecisionService: DeepMocked; + + let mockPlanningReview; + let mockDecision; + + beforeEach(async () => { + mockDecisionService = createMock(); + + mockPlanningReview = new PlanningReview(); + mockDecision = new PlanningReviewDecision({ + planningReview: mockPlanningReview, + }); + + const module: TestingModule = await Test.createTestingModule({ + imports: [ + AutomapperModule.forRoot({ + strategyInitializer: classes(), + }), + ], + controllers: [PlanningReviewDecisionController], + providers: [ + PlanningReviewProfile, + PlanningReviewDecisionProfile, + UserProfile, + { + provide: PlanningReviewDecisionService, + useValue: mockDecisionService, + }, + { + provide: ClsService, + useValue: {}, + }, + ...mockKeyCloakProviders, + ], + }).compile(); + + controller = module.get( + PlanningReviewDecisionController, + ); + + mockDecisionService.fetchCodes.mockResolvedValue({ + outcomes: [ + { + code: 'decision-code', + label: 'decision-label', + } as PlanningReviewDecisionOutcomeCode, + ], + }); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); + + it('should get all for planning review', async () => { + mockDecisionService.getByFileNumber.mockResolvedValue([mockDecision]); + + const result = await controller.getByFileNumber('fake-number'); + + expect(mockDecisionService.getByFileNumber).toBeCalledTimes(1); + expect(result[0].uuid).toStrictEqual(mockDecision.uuid); + }); + + it('should get a specific decision', async () => { + mockDecisionService.get.mockResolvedValue(mockDecision); + const result = await controller.get('fake-uuid'); + + expect(mockDecisionService.get).toBeCalledTimes(1); + expect(result.uuid).toStrictEqual(mockDecision.uuid); + }); + + it('should call through for deletion', async () => { + mockDecisionService.delete.mockResolvedValue({} as any); + + await controller.delete('fake-uuid'); + + expect(mockDecisionService.delete).toBeCalledTimes(1); + expect(mockDecisionService.delete).toBeCalledWith('fake-uuid'); + }); + + it('should create the decision if planning review exists', async () => { + mockDecisionService.create.mockResolvedValue(mockDecision); + + const decisionToCreate: CreatePlanningReviewDecisionDto = { + planningReviewFileNumber: mockPlanningReview.fileNumber, + }; + + await controller.create(decisionToCreate); + + expect(mockDecisionService.create).toBeCalledTimes(1); + expect(mockDecisionService.create).toBeCalledWith({ + planningReviewFileNumber: mockPlanningReview.fileNumber, + }); + }); + + it('should update the decision', async () => { + mockDecisionService.update.mockResolvedValue(mockDecision); + + const updates: UpdatePlanningReviewDecisionDto = { + outcomeCode: 'New Outcome', + date: new Date(2022, 2, 2, 2, 2, 2, 2).valueOf(), + isDraft: true, + }; + + await controller.update('fake-uuid', updates); + + expect(mockDecisionService.update).toBeCalledTimes(1); + expect(mockDecisionService.update).toBeCalledWith('fake-uuid', { + outcomeCode: 'New Outcome', + date: updates.date, + isDraft: true, + }); + }); + + it('should call through for attaching the document', async () => { + mockDecisionService.attachDocument.mockResolvedValue({} as any); + await controller.attachDocument('fake-uuid', { + isMultipart: () => true, + body: { + file: {}, + }, + user: { + entity: {}, + }, + }); + + expect(mockDecisionService.attachDocument).toBeCalledTimes(1); + }); + + it('should throw an exception if there is no file for file upload', async () => { + mockDecisionService.attachDocument.mockResolvedValue({} as any); + const promise = controller.attachDocument('fake-uuid', { + file: () => ({}), + isMultipart: () => false, + user: { + entity: {}, + }, + }); + + await expect(promise).rejects.toMatchObject( + new Error('Request is not multipart'), + ); + }); + + it('should call through for getting download url', async () => { + const fakeUrl = 'fake-url'; + mockDecisionService.getDownloadUrl.mockResolvedValue(fakeUrl); + const res = await controller.getDownloadUrl('fake-uuid', 'document-uuid'); + + expect(mockDecisionService.getDownloadUrl).toBeCalledTimes(1); + expect(res.url).toEqual(fakeUrl); + }); + + it('should call through for updating the file', async () => { + mockDecisionService.updateDocument.mockResolvedValue({} as any); + await controller.updateDocument('fake-uuid', 'document-uuid', { + fileName: '', + }); + + expect(mockDecisionService.updateDocument).toBeCalledTimes(1); + }); + + it('should call through for getting open url', async () => { + const fakeUrl = 'fake-url'; + mockDecisionService.getDownloadUrl.mockResolvedValue(fakeUrl); + const res = await controller.getOpenUrl('fake-uuid', 'document-uuid'); + + expect(mockDecisionService.getDownloadUrl).toBeCalledTimes(1); + expect(res.url).toEqual(fakeUrl); + }); + + it('should call through for document deletion', async () => { + mockDecisionService.deleteDocument.mockResolvedValue({} as any); + await controller.deleteDocument('fake-uuid', 'document-uuid'); + + expect(mockDecisionService.deleteDocument).toBeCalledTimes(1); + }); + + it('should call through for resolution number generation', async () => { + mockDecisionService.generateResolutionNumber.mockResolvedValue(1); + await controller.getNextAvailableResolutionNumber(2023); + + expect(mockDecisionService.generateResolutionNumber).toBeCalledTimes(1); + expect(mockDecisionService.generateResolutionNumber).toBeCalledWith(2023); + }); +}); diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.controller.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.controller.ts new file mode 100644 index 0000000000..7df9104110 --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.controller.ts @@ -0,0 +1,197 @@ +import { + BadRequestException, + Body, + Controller, + Delete, + Get, + Param, + Patch, + Post, + 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 { ANY_AUTH_ROLE } from '../../../common/authorization/roles'; +import { RolesGuard } from '../../../common/authorization/roles-guard.service'; +import { UserRoles } from '../../../common/authorization/roles.decorator'; +import { PlanningReviewDecisionOutcomeCode } from './planning-review-decision-outcome.entity'; +import { + CreatePlanningReviewDecisionDto, + PlanningReviewDecisionDto, + PlanningReviewDecisionOutcomeCodeDto, + UpdatePlanningReviewDecisionDto, +} from './planning-review-decision.dto'; +import { PlanningReviewDecision } from './planning-review-decision.entity'; +import { PlanningReviewDecisionService } from './planning-review-decision.service'; + +@ApiOAuth2(config.get('KEYCLOAK.SCOPES')) +@Controller('planning-review-decision') +@UseGuards(RolesGuard) +export class PlanningReviewDecisionController { + constructor( + private plannigReviewDecisionService: PlanningReviewDecisionService, + @InjectMapper() private mapper: Mapper, + ) {} + + @Get('/planning-review/:fileNumber') + @UserRoles(...ANY_AUTH_ROLE) + async getByFileNumber( + @Param('fileNumber') fileNumber, + ): Promise { + const decisions = + await this.plannigReviewDecisionService.getByFileNumber(fileNumber); + + return await this.mapper.mapArrayAsync( + decisions, + PlanningReviewDecision, + PlanningReviewDecisionDto, + ); + } + + @Get('/codes') + @UserRoles(...ANY_AUTH_ROLE) + async getCodes() { + const codes = await this.plannigReviewDecisionService.fetchCodes(); + return await this.mapper.mapArrayAsync( + codes.outcomes, + PlanningReviewDecisionOutcomeCode, + PlanningReviewDecisionOutcomeCodeDto, + ); + } + + @Get('/:uuid') + @UserRoles(...ANY_AUTH_ROLE) + async get(@Param('uuid') uuid: string): Promise { + const decision = await this.plannigReviewDecisionService.get(uuid); + + return this.mapper.mapAsync( + decision, + PlanningReviewDecision, + PlanningReviewDecisionDto, + ); + } + + @Post() + @UserRoles(...ANY_AUTH_ROLE) + async create( + @Body() createDto: CreatePlanningReviewDecisionDto, + ): Promise { + const newDecision = + await this.plannigReviewDecisionService.create(createDto); + + return this.mapper.mapAsync( + newDecision, + PlanningReviewDecision, + PlanningReviewDecisionDto, + ); + } + + @Patch('/:uuid') + @UserRoles(...ANY_AUTH_ROLE) + async update( + @Param('uuid') uuid: string, + @Body() updateDto: UpdatePlanningReviewDecisionDto, + ): Promise { + const updatedDecision = await this.plannigReviewDecisionService.update( + uuid, + updateDto, + ); + + return this.mapper.mapAsync( + updatedDecision, + PlanningReviewDecision, + PlanningReviewDecisionDto, + ); + } + + @Delete('/:uuid') + @UserRoles(...ANY_AUTH_ROLE) + async delete(@Param('uuid') uuid: string) { + return await this.plannigReviewDecisionService.delete(uuid); + } + + @Post('/:uuid/file') + @UserRoles(...ANY_AUTH_ROLE) + async attachDocument(@Param('uuid') decisionUuid: string, @Req() req) { + if (!req.isMultipart()) { + throw new BadRequestException('Request is not multipart'); + } + + const file = req.body.file; + await this.plannigReviewDecisionService.attachDocument( + decisionUuid, + file, + req.user.entity, + ); + return { + uploaded: true, + }; + } + + @Patch('/:uuid/file/:documentUuid') + @UserRoles(...ANY_AUTH_ROLE) + async updateDocument( + @Param('uuid') decisionUuid: string, + @Param('documentUuid') documentUuid: string, + @Body() body: { fileName: string }, + ) { + await this.plannigReviewDecisionService.updateDocument( + documentUuid, + body.fileName, + ); + return { + uploaded: true, + }; + } + + @Get('/:uuid/file/:fileUuid/download') + @UserRoles(...ANY_AUTH_ROLE) + async getDownloadUrl( + @Param('uuid') decisionUuid: string, + @Param('fileUuid') documentUuid: string, + ) { + const downloadUrl = + await this.plannigReviewDecisionService.getDownloadUrl(documentUuid); + return { + url: downloadUrl, + }; + } + + @Get('/:uuid/file/:fileUuid/open') + @UserRoles(...ANY_AUTH_ROLE) + async getOpenUrl( + @Param('uuid') decisionUuid: string, + @Param('fileUuid') documentUuid: string, + ) { + const downloadUrl = await this.plannigReviewDecisionService.getDownloadUrl( + documentUuid, + true, + ); + return { + url: downloadUrl, + }; + } + + @Delete('/:uuid/file/:fileUuid') + @UserRoles(...ANY_AUTH_ROLE) + async deleteDocument( + @Param('uuid') decisionUuid: string, + @Param('fileUuid') documentUuid: string, + ) { + await this.plannigReviewDecisionService.deleteDocument(documentUuid); + return {}; + } + + @Get('next-resolution-number/:resolutionYear') + @UserRoles(...ANY_AUTH_ROLE) + async getNextAvailableResolutionNumber( + @Param('resolutionYear') resolutionYear: number, + ) { + return this.plannigReviewDecisionService.generateResolutionNumber( + resolutionYear, + ); + } +} diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.dto.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.dto.ts new file mode 100644 index 0000000000..8e04fb28c7 --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.dto.ts @@ -0,0 +1,91 @@ +import { AutoMap } from 'automapper-classes'; +import { IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator'; +import { BaseCodeDto } from '../../../common/dtos/base.dto'; +import { PlanningReviewDecisionOutcomeCode } from './planning-review-decision-outcome.entity'; + +export class UpdatePlanningReviewDecisionDto { + @IsNumber() + @IsOptional() + resolutionNumber?: number; + + @IsNumber() + @IsOptional() + resolutionYear?: number; + + @IsNumber() + @IsOptional() + date?: number; + + @IsString() + @IsOptional() + outcomeCode?: string; + + @IsString() + @IsOptional() + decisionDescription?: string | null; + + @IsBoolean() + isDraft: boolean; +} + +export class CreatePlanningReviewDecisionDto { + @IsString() + planningReviewFileNumber: string; +} + +export class PlanningReviewDecisionOutcomeCodeDto extends BaseCodeDto {} + +export class PlanningReviewDecisionDto { + @AutoMap() + uuid: string; + + @AutoMap() + planningReviewFileNumber: string; + + @AutoMap() + date?: number; + + @AutoMap() + resolutionNumber: string; + + @AutoMap() + resolutionYear: number; + + @AutoMap(() => PlanningReviewDecisionOutcomeCodeDto) + outcome?: PlanningReviewDecisionOutcomeCodeDto; + + @AutoMap(() => [PlanningReviewDecisionDocumentDto]) + documents: PlanningReviewDecisionDocumentDto[]; + + @AutoMap(() => Boolean) + isDraft: boolean; + + @AutoMap(() => String) + decisionDescription?: string | null; + + @AutoMap(() => Number) + createdAt?: number | null; + + @AutoMap(() => Boolean) + wasReleased: boolean; +} + +export class PlanningReviewDecisionDocumentDto { + @AutoMap() + uuid: string; + + @AutoMap() + fileName: string; + + @AutoMap() + fileSize: number; + + @AutoMap() + mimeType: string; + + @AutoMap() + uploadedBy: string; + + @AutoMap() + uploadedAt: number; +} diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.entity.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.entity.ts new file mode 100644 index 0000000000..3c39793741 --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.entity.ts @@ -0,0 +1,92 @@ +import { AutoMap } from 'automapper-classes'; +import { + Column, + CreateDateColumn, + Entity, + Index, + ManyToOne, + OneToMany, +} from 'typeorm'; +import { Base } from '../../../common/entities/base.entity'; +import { PlanningReview } from '../planning-review.entity'; +import { PlanningReviewDecisionDocument } from './planning-review-decision-document/planning-review-decision-document.entity'; +import { PlanningReviewDecisionOutcomeCode } from './planning-review-decision-outcome.entity'; + +@Entity() +@Index(['resolutionNumber', 'resolutionYear'], { + unique: true, + where: '"audit_deleted_date_at" is null and "resolution_number" is not null', +}) +export class PlanningReviewDecision extends Base { + constructor(data?: Partial) { + super(); + if (data) { + Object.assign(this, data); + } + } + + @AutoMap() + @Column({ type: 'timestamptz', nullable: true }) + date: Date | null; + + @AutoMap() + @Column({ type: 'boolean', default: false }) + wasReleased: boolean; + + @AutoMap(() => PlanningReviewDecisionOutcomeCode) + @ManyToOne(() => PlanningReviewDecisionOutcomeCode, { + nullable: true, + }) + outcome: PlanningReviewDecisionOutcomeCode | null; + + @AutoMap() + @Column({ nullable: true }) + outcomeCode: string | null; + + @AutoMap() + @Column({ type: 'int4', nullable: true }) + resolutionNumber: number; + + @AutoMap() + @Column({ type: 'smallint' }) + resolutionYear: number; + + @AutoMap() + @Column({ + comment: 'Indicates whether the decision is currently draft or not', + default: false, + }) + isDraft: boolean; + + @AutoMap(() => String) + @Column({ + comment: 'Staff input field for a description of the decision', + nullable: true, + type: 'text', + }) + decisionDescription?: string | null; + + @CreateDateColumn({ + type: 'timestamptz', + nullable: false, + update: false, + comment: + 'Date that indicates when decision was created. It is not editable by user.', + }) + createdAt: Date; + + @AutoMap() + @ManyToOne(() => PlanningReview) + planningReview: PlanningReview; + + @AutoMap() + @Column({ type: 'uuid' }) + planningReviewUuid: string; + + @AutoMap(() => [PlanningReviewDecisionDocument]) + @OneToMany( + () => PlanningReviewDecisionDocument, + (document) => document.decision, + ) + documents: PlanningReviewDecisionDocument[]; +} diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.module.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.module.ts new file mode 100644 index 0000000000..10c147f67a --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.module.ts @@ -0,0 +1,26 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { PlanningReviewDecisionProfile } from '../../../common/automapper/planning-review-decision.automapper.profile'; +import { DocumentModule } from '../../../document/document.module'; +import { PlanningReviewModule } from '../planning-review.module'; +import { PlanningReviewDecisionDocument } from './planning-review-decision-document/planning-review-decision-document.entity'; +import { PlanningReviewDecisionOutcomeCode } from './planning-review-decision-outcome.entity'; +import { PlanningReviewDecisionController } from './planning-review-decision.controller'; +import { PlanningReviewDecision } from './planning-review-decision.entity'; +import { PlanningReviewDecisionService } from './planning-review-decision.service'; + +@Module({ + imports: [ + PlanningReviewModule, + DocumentModule, + TypeOrmModule.forFeature([ + PlanningReviewDecision, + PlanningReviewDecisionDocument, + PlanningReviewDecisionOutcomeCode, + ]), + ], + providers: [PlanningReviewDecisionService, PlanningReviewDecisionProfile], + controllers: [PlanningReviewDecisionController], + exports: [], +}) +export class PlanningReviewDecisionModule {} diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.service.spec.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.service.spec.ts new file mode 100644 index 0000000000..bb442dcce8 --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.service.spec.ts @@ -0,0 +1,368 @@ +import { + ServiceNotFoundException, + ServiceValidationException, +} from '@app/common/exceptions/base.exception'; +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 { v4 } from 'uuid'; +import { DocumentService } from '../../../document/document.service'; +import { PlanningReview } from '../planning-review.entity'; +import { PlanningReviewService } from '../planning-review.service'; +import { PlanningReviewDecisionDocument } from './planning-review-decision-document/planning-review-decision-document.entity'; +import { PlanningReviewDecisionOutcomeCode } from './planning-review-decision-outcome.entity'; +import { + CreatePlanningReviewDecisionDto, + UpdatePlanningReviewDecisionDto, +} from './planning-review-decision.dto'; +import { PlanningReviewDecision } from './planning-review-decision.entity'; +import { PlanningReviewDecisionService } from './planning-review-decision.service'; + +describe('PlanningReviewDecisionService', () => { + let service: PlanningReviewDecisionService; + let mockDecisionRepository: DeepMocked>; + let mockDecisionDocumentRepository: DeepMocked< + Repository + >; + let mockDecisionOutcomeRepository: DeepMocked< + Repository + >; + let mockDocumentService: DeepMocked; + let mockPlanningReviewService: DeepMocked; + const mockFileNumber = '125'; + let mockPlanningReview; + let mockDecision; + + beforeEach(async () => { + mockDocumentService = createMock(); + mockDecisionRepository = createMock(); + mockDecisionDocumentRepository = createMock(); + mockDecisionOutcomeRepository = createMock(); + mockPlanningReviewService = createMock(); + + const module: TestingModule = await Test.createTestingModule({ + imports: [ + AutomapperModule.forRoot({ + strategyInitializer: classes(), + }), + ], + providers: [ + PlanningReviewDecisionService, + { + provide: getRepositoryToken(PlanningReviewDecision), + useValue: mockDecisionRepository, + }, + { + provide: getRepositoryToken(PlanningReviewDecisionDocument), + useValue: mockDecisionDocumentRepository, + }, + { + provide: getRepositoryToken(PlanningReviewDecisionOutcomeCode), + useValue: mockDecisionOutcomeRepository, + }, + { + provide: DocumentService, + useValue: mockDocumentService, + }, + { + provide: PlanningReviewService, + useValue: mockPlanningReviewService, + }, + ], + }).compile(); + + service = module.get( + PlanningReviewDecisionService, + ); + + mockPlanningReview = new PlanningReview({ + uuid: v4(), + fileNumber: mockFileNumber, + }); + mockDecision = new PlanningReviewDecision({ + planningReview: mockPlanningReview, + documents: [], + }); + + mockDecisionRepository.find.mockResolvedValue([mockDecision]); + mockDecisionRepository.findOne.mockResolvedValue(mockDecision); + mockDecisionRepository.findOneOrFail(mockDecision); + mockDecisionRepository.save.mockResolvedValue(mockDecision); + + mockDecisionDocumentRepository.find.mockResolvedValue([]); + + mockPlanningReviewService.getByFileNumber.mockResolvedValue( + mockPlanningReview, + ); + mockPlanningReviewService.update.mockResolvedValue({} as any); + + mockDecisionOutcomeRepository.find.mockResolvedValue([]); + mockDecisionOutcomeRepository.findOneOrFail.mockResolvedValue({} as any); + }); + + describe('PlanningReviewDecisionService Core Tests', () => { + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + it('should get decisions by file number', async () => { + const result = await service.getByFileNumber( + mockPlanningReview.fileNumber, + ); + + expect(result).toStrictEqual([mockDecision]); + }); + + it('should return decisions by uuid', async () => { + const result = await service.get(mockDecision.uuid); + + expect(result).toStrictEqual(mockDecision); + }); + + it('should delete decision with uuid and update planning review', async () => { + mockDecisionRepository.softRemove.mockResolvedValue({} as any); + mockDecisionRepository.findOne.mockResolvedValue({ + ...mockDecision, + reconsiders: 'reconsider-uuid', + modifies: 'modified-uuid', + }); + mockDecisionRepository.find.mockResolvedValue([]); + + await service.delete(mockDecision.uuid); + + expect(mockDecisionRepository.softRemove).toBeCalledTimes(1); + expect(mockPlanningReviewService.update).toHaveBeenCalledTimes(1); + expect(mockPlanningReviewService.update).toHaveBeenCalledWith( + mockPlanningReview.fileNumber, + { + decisionDate: null, + }, + ); + }); + + it('should create a decision', async () => { + mockDecisionRepository.exists.mockResolvedValue(false); + mockPlanningReviewService.getByFileNumber.mockResolvedValue( + new PlanningReview(), + ); + + const decisionToCreate: CreatePlanningReviewDecisionDto = { + planningReviewFileNumber: '', + }; + + await service.create(decisionToCreate); + + expect(mockDecisionRepository.save).toBeCalledTimes(1); + expect(mockPlanningReviewService.update).toHaveBeenCalledTimes(0); + }); + + it('should fail create a draft decision if draft already exists', async () => { + mockDecisionRepository.findOne.mockResolvedValue( + new PlanningReviewDecision({ + documents: [], + }), + ); + mockDecisionRepository.exists.mockResolvedValueOnce(true); + + const decisionToCreate: CreatePlanningReviewDecisionDto = { + planningReviewFileNumber: '', + }; + + await expect(service.create(decisionToCreate)).rejects.toMatchObject( + new ServiceValidationException( + 'Draft decision already exists for this planning review.', + ), + ); + + expect(mockDecisionRepository.save).toBeCalledTimes(0); + expect(mockPlanningReviewService.update).toHaveBeenCalledTimes(0); + }); + + it('should create a decision and NOT update the planning review if this was the second decision', async () => { + mockDecisionRepository.findOne.mockResolvedValue({ + documents: [] as PlanningReviewDecisionDocument[], + } as PlanningReviewDecision); + mockDecisionRepository.exists.mockResolvedValueOnce(false); + + const decisionToCreate: CreatePlanningReviewDecisionDto = { + planningReviewFileNumber: '', + }; + + await service.create(decisionToCreate); + + expect(mockDecisionRepository.save).toBeCalledTimes(1); + expect(mockPlanningReviewService.update).not.toHaveBeenCalled(); + }); + + it('should update the decision and update the planning review if it was the only decision', async () => { + const decisionDate = new Date(2022, 3, 3, 3, 3, 3, 3); + const decisionUpdate: UpdatePlanningReviewDecisionDto = { + date: decisionDate.getTime(), + outcomeCode: 'New Outcome', + isDraft: false, + }; + + const createdDecision = new PlanningReviewDecision({ + date: decisionDate, + isDraft: false, + documents: [], + }); + + mockDecisionRepository.findOne.mockResolvedValue(createdDecision); + mockDecisionRepository.find.mockResolvedValue([createdDecision]); + + await service.update(mockDecision.uuid, decisionUpdate); + + expect(mockDecisionRepository.findOne).toBeCalledTimes(2); + expect(mockDecisionRepository.save).toHaveBeenCalledTimes(1); + expect(mockPlanningReviewService.update).toHaveBeenCalledTimes(1); + expect(mockPlanningReviewService.update).toHaveBeenCalledWith( + mockPlanningReview.fileNumber, + { + decisionDate: decisionDate.getTime(), + }, + ); + }); + + it('should update decision and update the planning review date to null if it is draft decision', async () => { + const decisionDate = new Date(2022, 3, 3, 3, 3, 3, 3); + const decisionUpdate: UpdatePlanningReviewDecisionDto = { + date: decisionDate.getTime(), + outcomeCode: 'New Outcome', + isDraft: true, + }; + + await service.update(mockDecision.uuid, decisionUpdate); + + expect(mockDecisionRepository.findOne).toBeCalledTimes(2); + expect(mockDecisionRepository.save).toBeCalledTimes(1); + expect(mockPlanningReviewService.update).toHaveBeenCalledTimes(1); + expect(mockPlanningReviewService.update).toHaveBeenCalledWith( + mockFileNumber, + { + decisionDate: null, + }, + ); + }); + + it('should not update the planning review dates when updating a draft decision', async () => { + const secondDecision = new PlanningReviewDecision({ + ...mockPlanningReview, + documents: [], + }); + secondDecision.isDraft = true; + secondDecision.uuid = 'second-uuid'; + mockDecisionRepository.find.mockResolvedValue([ + secondDecision, + mockDecision, + ]); + mockDecisionRepository.findOne.mockResolvedValue(secondDecision); + + const decisionUpdate: UpdatePlanningReviewDecisionDto = { + outcomeCode: 'New Outcome', + isDraft: true, + }; + + await service.update(mockDecision.uuid, decisionUpdate); + + expect(mockDecisionRepository.findOne).toBeCalledTimes(2); + expect(mockDecisionRepository.save).toBeCalledTimes(1); + expect(mockPlanningReviewService.update).not.toHaveBeenCalled(); + }); + + it('should call through for get code', async () => { + await service.fetchCodes(); + expect(mockDecisionOutcomeRepository.find).toHaveBeenCalledTimes(1); + }); + }); + + describe('PlanningReviewDecisionService File Tests', () => { + let mockDocument; + beforeEach(() => { + mockDecisionDocumentRepository.findOne.mockResolvedValue(mockDocument); + mockDecisionDocumentRepository.save.mockResolvedValue(mockDocument); + + mockDocument = { + uuid: 'fake-uuid', + decisionUuid: 'decision-uuid', + } as PlanningReviewDecisionDocument; + }); + + it('should call the repository for attaching a file', async () => { + mockDocumentService.create.mockResolvedValue({} as any); + + await service.attachDocument('uuid', {} as any, {} as any); + expect(mockDecisionDocumentRepository.save).toHaveBeenCalledTimes(1); + expect(mockDocumentService.create).toHaveBeenCalledTimes(1); + }); + + it('should throw an exception when attaching a document to a non-existent decision', async () => { + mockDecisionRepository.findOne.mockResolvedValue(null); + await expect( + service.attachDocument('uuid', {} as any, {} as any), + ).rejects.toMatchObject( + new ServiceNotFoundException(`Decision with UUID uuid not found`), + ); + expect(mockDocumentService.create).not.toHaveBeenCalled(); + }); + + it('should call the repository to delete documents', async () => { + mockDecisionDocumentRepository.softRemove.mockResolvedValue({} as any); + + await service.deleteDocument('fake-uuid'); + expect(mockDecisionDocumentRepository.softRemove).toHaveBeenCalledTimes( + 1, + ); + }); + + it('should call the repository to check if portal user can download document', async () => { + mockDecisionDocumentRepository.findOne.mockResolvedValue( + new PlanningReviewDecisionDocument(), + ); + mockDocumentService.getDownloadUrl.mockResolvedValue(''); + + await service.getDownloadForPortal('fake-uuid'); + expect(mockDecisionDocumentRepository.findOne).toHaveBeenCalledTimes(1); + expect(mockDocumentService.getDownloadUrl).toHaveBeenCalledTimes(1); + }); + + it('should throw an exception when document not found for deletion', async () => { + mockDecisionDocumentRepository.findOne.mockResolvedValue(null); + await expect(service.deleteDocument('fake-uuid')).rejects.toMatchObject( + new ServiceNotFoundException( + `Failed to find document with uuid fake-uuid`, + ), + ); + expect(mockDocumentService.softRemove).not.toHaveBeenCalled(); + }); + + it('should call through to document service for update', async () => { + mockDocumentService.update.mockResolvedValue({} as any); + + await service.updateDocument('document-uuid', 'file-name'); + expect(mockDocumentService.update).toHaveBeenCalledTimes(1); + }); + + it('should call through to document service for download', async () => { + const downloadUrl = 'download-url'; + mockDocumentService.getDownloadUrl.mockResolvedValue(downloadUrl); + + const res = await service.getDownloadUrl('fake-uuid'); + + expect(mockDocumentService.getDownloadUrl).toHaveBeenCalledTimes(1); + expect(res).toEqual(downloadUrl); + }); + + it('should throw an exception when document not found for download', async () => { + mockDecisionDocumentRepository.findOne.mockResolvedValue(null); + await expect(service.getDownloadUrl('fake-uuid')).rejects.toMatchObject( + new ServiceNotFoundException( + `Failed to find document with uuid fake-uuid`, + ), + ); + }); + }); +}); diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.service.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.service.ts new file mode 100644 index 0000000000..94c23884be --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.service.ts @@ -0,0 +1,351 @@ +import { + ServiceNotFoundException, + ServiceValidationException, +} from '@app/common/exceptions/base.exception'; +import { MultipartFile } from '@fastify/multipart'; +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { In, IsNull, Repository } from 'typeorm'; +import { + DOCUMENT_SOURCE, + DOCUMENT_SYSTEM, +} from '../../../document/document.dto'; +import { DocumentService } from '../../../document/document.service'; +import { User } from '../../../user/user.entity'; +import { formatIncomingDate } from '../../../utils/incoming-date.formatter'; +import { PlanningReviewService } from '../planning-review.service'; +import { PlanningReviewDecisionDocument } from './planning-review-decision-document/planning-review-decision-document.entity'; +import { PlanningReviewDecisionOutcomeCode } from './planning-review-decision-outcome.entity'; +import { + CreatePlanningReviewDecisionDto, + UpdatePlanningReviewDecisionDto, +} from './planning-review-decision.dto'; +import { PlanningReviewDecision } from './planning-review-decision.entity'; + +@Injectable() +export class PlanningReviewDecisionService { + constructor( + @InjectRepository(PlanningReviewDecision) + private planningReviewDecisionRepository: Repository, + @InjectRepository(PlanningReviewDecisionDocument) + private decisionDocumentRepository: Repository, + @InjectRepository(PlanningReviewDecisionOutcomeCode) + private decisionOutcomeRepository: Repository, + private planningReviewService: PlanningReviewService, + private documentService: DocumentService, + ) {} + + async getByFileNumber(fileNumber: string) { + const planningReview = + await this.planningReviewService.getByFileNumber(fileNumber); + + const decisions = await this.planningReviewDecisionRepository.find({ + where: { + planningReviewUuid: planningReview.uuid, + }, + order: { + createdAt: 'DESC', + }, + relations: { + outcome: true, + }, + }); + + //Query Documents separately as when added to the above joins caused performance issues + for (const decision of decisions) { + decision.documents = await this.decisionDocumentRepository.find({ + where: { + decisionUuid: decision.uuid, + document: { + auditDeletedDateAt: IsNull(), + }, + }, + relations: { + document: true, + }, + }); + } + return decisions; + } + + async get(uuid) { + const decision = await this.planningReviewDecisionRepository.findOne({ + where: { + uuid, + }, + relations: { + outcome: true, + documents: { + document: true, + }, + }, + }); + + if (!decision) { + throw new ServiceNotFoundException( + `Failed to load decision with uuid ${uuid}`, + ); + } + + decision.documents = decision.documents.filter( + (document) => !!document.document, + ); + + return decision; + } + + async update(uuid: string, updateDto: UpdatePlanningReviewDecisionDto) { + const existingDecision: Partial = + await this.getOrFail(uuid); + + const isChangingDraftStatus = + existingDecision.isDraft !== updateDto.isDraft; + existingDecision.resolutionNumber = updateDto.resolutionNumber; + existingDecision.resolutionYear = updateDto.resolutionYear; + existingDecision.decisionDescription = updateDto.decisionDescription; + existingDecision.isDraft = updateDto.isDraft; + existingDecision.wasReleased = + existingDecision.wasReleased || !updateDto.isDraft; + + if (updateDto.outcomeCode) { + existingDecision.outcome = await this.getOutcomeByCode( + updateDto.outcomeCode, + ); + } + let dateHasChanged = false; + if ( + updateDto.date !== undefined && + existingDecision.date !== formatIncomingDate(updateDto.date) + ) { + dateHasChanged = true; + existingDecision.date = formatIncomingDate(updateDto.date); + } + + const updatedDecision = + await this.planningReviewDecisionRepository.save(existingDecision); + + if (dateHasChanged || isChangingDraftStatus) { + await this.updateDecisionDates(updatedDecision); + } + + return this.get(existingDecision.uuid); + } + + async create(createDto: CreatePlanningReviewDecisionDto) { + const isDraftExists = await this.planningReviewDecisionRepository.exists({ + where: { + planningReview: { fileNumber: createDto.planningReviewFileNumber }, + isDraft: true, + }, + }); + + if (isDraftExists) { + throw new ServiceValidationException( + 'Draft decision already exists for this planning review.', + ); + } + + const planningReview = await this.planningReviewService.getByFileNumber( + createDto.planningReviewFileNumber, + ); + + const decision = new PlanningReviewDecision({ + resolutionYear: new Date().getFullYear(), + planningReviewUuid: planningReview.uuid, + }); + + const savedDecision = await this.planningReviewDecisionRepository.save( + decision, + { + transaction: true, + }, + ); + + return this.get(savedDecision.uuid); + } + + async delete(uuid) { + const planningReviewDecision = + await this.planningReviewDecisionRepository.findOne({ + where: { uuid }, + relations: { + outcome: true, + documents: { + document: true, + }, + planningReview: true, + }, + }); + + if (!planningReviewDecision) { + throw new ServiceNotFoundException( + `Failed to find decision with uuid ${uuid}`, + ); + } + + for (const document of planningReviewDecision.documents) { + await this.documentService.softRemove(document.document); + } + await this.planningReviewDecisionRepository.save(planningReviewDecision); + + await this.planningReviewDecisionRepository.softRemove([ + planningReviewDecision, + ]); + await this.updateDecisionDates(planningReviewDecision); + } + + async attachDocument(decisionUuid: string, file: MultipartFile, user: User) { + const decision = await this.getOrFail(decisionUuid); + const document = await this.documentService.create( + `decision/${decision.uuid}`, + file.filename, + file, + user, + DOCUMENT_SOURCE.ALC, + DOCUMENT_SYSTEM.ALCS, + ); + const appDocument = new PlanningReviewDecisionDocument({ + decision, + document, + }); + + return this.decisionDocumentRepository.save(appDocument); + } + + async deleteDocument(decisionDocumentUuid: string) { + const decisionDocument = + await this.getDecisionDocumentOrFail(decisionDocumentUuid); + + await this.decisionDocumentRepository.softRemove(decisionDocument); + return decisionDocument; + } + + async getDownloadUrl(decisionDocumentUuid: string, openInline = false) { + const decisionDocument = + await this.getDecisionDocumentOrFail(decisionDocumentUuid); + + return this.documentService.getDownloadUrl( + decisionDocument.document, + openInline, + ); + } + + async getDownloadForPortal(decisionDocumentUuid: string) { + const decisionDocument = await this.decisionDocumentRepository.findOne({ + where: { + decision: { + isDraft: false, + }, + uuid: decisionDocumentUuid, + }, + relations: { + document: true, + }, + }); + + if (decisionDocument) { + return this.documentService.getDownloadUrl( + decisionDocument.document, + true, // FIXME: Document does not open inline despite flag being true + ); + } + throw new ServiceNotFoundException('Failed to find document'); + } + + getOutcomeByCode(code: string) { + return this.decisionOutcomeRepository.findOneOrFail({ + where: { + code, + }, + }); + } + + async fetchCodes() { + const values = await Promise.all([this.decisionOutcomeRepository.find()]); + + return { + outcomes: values[0], + }; + } + + getMany(modifiesDecisionUuids: string[]) { + return this.planningReviewDecisionRepository.find({ + where: { + uuid: In(modifiesDecisionUuids), + }, + }); + } + + async generateResolutionNumber(resolutionYear: number) { + const result = await this.planningReviewDecisionRepository.query( + `SELECT * FROM alcs.generate_next_resolution_number(${resolutionYear})`, + ); + + return result[0].generate_next_resolution_number; + } + + private async getOrFail(uuid: string) { + const existingDecision = + await this.planningReviewDecisionRepository.findOne({ + where: { + uuid, + }, + relations: { + planningReview: true, + }, + }); + + if (!existingDecision) { + throw new ServiceNotFoundException( + `Decision with UUID ${uuid} not found`, + ); + } + return existingDecision; + } + + private async updateDecisionDates( + planningReviewDecision: PlanningReviewDecision, + ) { + const fileNumber = planningReviewDecision.planningReview.fileNumber; + const existingDecisions = await this.getByFileNumber(fileNumber); + const releasedDecisions = existingDecisions.filter( + (decision) => !decision.isDraft, + ); + if (releasedDecisions.length === 0) { + await this.planningReviewService.update(fileNumber, { + decisionDate: null, + }); + } else { + const decisionDate = existingDecisions[existingDecisions.length - 1].date; + await this.planningReviewService.update(fileNumber, { + decisionDate: decisionDate?.getTime(), + }); + } + } + + private async getDecisionDocumentOrFail(decisionDocumentUuid: string) { + const decisionDocument = await this.decisionDocumentRepository.findOne({ + where: { + uuid: decisionDocumentUuid, + }, + relations: { + document: true, + }, + }); + + if (!decisionDocument) { + throw new ServiceNotFoundException( + `Failed to find document with uuid ${decisionDocumentUuid}`, + ); + } + return decisionDocument; + } + + async updateDocument(documentUuid: string, fileName: string) { + const document = await this.getDecisionDocumentOrFail(documentUuid); + await this.documentService.update(document.document, { + fileName, + source: DOCUMENT_SOURCE.ALC, + }); + } +} 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 39442427c9..998ec30f65 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 @@ -162,4 +162,8 @@ export class UpdatePlanningReviewDto { @IsString() @IsOptional() typeCode?: string; + + @IsNumber() + @IsOptional() + decisionDate?: number | null; } 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 d56139b567..22a9e6c358 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 @@ -67,4 +67,7 @@ export class PlanningReview extends Base { @Column({ type: 'timestamptz', nullable: true }) closedDate: Date | null; + + @Column({ type: 'timestamptz', nullable: true }) + decisionDate: Date | null; } 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 447a0327f4..5c038279cc 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 @@ -99,16 +99,12 @@ describe('PlanningReviewService', () => { expect(mockReferralRepository.save).toHaveBeenCalledTimes(1); }); - it('should call through to the repo for getby', async () => { - const mockFilter = { - uuid: '5', - }; - mockRepository.find.mockResolvedValue([]); + it('should call through to the repo for getByFileNumber', async () => { + mockRepository.findOneOrFail.mockResolvedValue(new PlanningReview()); - await service.getBy(mockFilter); + await service.getByFileNumber('fileNumber'); - expect(mockRepository.find).toHaveBeenCalledTimes(1); - expect(mockRepository.find.mock.calls[0][0]!.where).toEqual(mockFilter); + expect(mockRepository.findOneOrFail).toHaveBeenCalledTimes(1); }); it('should call through to the repo for getDetailedReview', async () => { 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 561b8c1dad..109fde16f2 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 @@ -85,9 +85,11 @@ export class PlanningReviewService { ); } - getBy(findOptions: FindOptionsWhere) { - return this.reviewRepository.find({ - where: findOptions, + getByFileNumber(fileNumber: string) { + return this.reviewRepository.findOneOrFail({ + where: { + fileNumber, + }, relations: this.DEFAULT_RELATIONS, }); } diff --git a/services/apps/alcs/src/common/automapper/planning-review-decision.automapper.profile.ts b/services/apps/alcs/src/common/automapper/planning-review-decision.automapper.profile.ts new file mode 100644 index 0000000000..7bd2a95ee5 --- /dev/null +++ b/services/apps/alcs/src/common/automapper/planning-review-decision.automapper.profile.ts @@ -0,0 +1,66 @@ +import { Injectable } from '@nestjs/common'; +import { createMap, forMember, mapFrom, Mapper } from 'automapper-core'; +import { AutomapperProfile, InjectMapper } from 'automapper-nestjs'; +import { PlanningReviewDecisionDocument } from '../../alcs/planning-review/planning-review-decision/planning-review-decision-document/planning-review-decision-document.entity'; +import { PlanningReviewDecisionOutcomeCode } from '../../alcs/planning-review/planning-review-decision/planning-review-decision-outcome.entity'; +import { + PlanningReviewDecisionDocumentDto, + PlanningReviewDecisionDto, + PlanningReviewDecisionOutcomeCodeDto, +} from '../../alcs/planning-review/planning-review-decision/planning-review-decision.dto'; +import { PlanningReviewDecision } from '../../alcs/planning-review/planning-review-decision/planning-review-decision.entity'; +import { DocumentCode } from '../../document/document-code.entity'; +import { DocumentTypeDto } from '../../document/document.dto'; + +@Injectable() +export class PlanningReviewDecisionProfile extends AutomapperProfile { + constructor(@InjectMapper() mapper: Mapper) { + super(mapper); + } + + override get profile() { + return (mapper: Mapper) => { + createMap( + mapper, + PlanningReviewDecision, + PlanningReviewDecisionDto, + forMember( + (dto) => dto.date, + mapFrom((entity) => entity.date?.getTime()), + ), + ); + createMap( + mapper, + PlanningReviewDecisionOutcomeCode, + PlanningReviewDecisionOutcomeCodeDto, + ); + + createMap( + mapper, + PlanningReviewDecisionDocument, + PlanningReviewDecisionDocumentDto, + forMember( + (dto) => dto.mimeType, + mapFrom((entity) => entity.document.mimeType), + ), + forMember( + (dto) => dto.fileName, + mapFrom((entity) => entity.document.fileName), + ), + forMember( + (dto) => dto.fileSize, + mapFrom((entity) => entity.document.fileSize), + ), + forMember( + (dto) => dto.uploadedBy, + mapFrom((entity) => entity.document.uploadedBy?.name), + ), + forMember( + (dto) => dto.uploadedAt, + mapFrom((entity) => entity.document.uploadedAt.getTime()), + ), + ); + createMap(mapper, DocumentCode, DocumentTypeDto); + }; + } +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1683048988258-generate_next_resolution_number.ts b/services/apps/alcs/src/providers/typeorm/migrations/1683048988258-generate_next_resolution_number.ts index 3f4bcb2fb4..204f4b081d 100644 --- a/services/apps/alcs/src/providers/typeorm/migrations/1683048988258-generate_next_resolution_number.ts +++ b/services/apps/alcs/src/providers/typeorm/migrations/1683048988258-generate_next_resolution_number.ts @@ -8,33 +8,42 @@ export class generateNextResolutionNumber1683048988258 `CREATE OR REPLACE FUNCTION alcs.generate_next_resolution_number(p_resolution_year integer) RETURNS integer LANGUAGE plpgsql - AS $function$ - - declare next_resolution_number integer; - BEGIN + AS $function$ + declare next_resolution_number integer; + BEGIN + select + row_num into next_resolution_number + from + ( select - row_num into next_resolution_number + coalesce(resolution_number, 0) as resolution_number, + row_number() over ( + order by resolution_number) row_num from ( - select - coalesce(resolution_number, 0) as resolution_number, - row_number() over ( - order by resolution_number) row_num - from - alcs.application_decision - where - resolution_year = p_resolution_year - and audit_deleted_date_at is null - ) z + select resolution_number, audit_deleted_date_at + from alcs.application_decision + where resolution_year = p_resolution_year + UNION + select resolution_number, audit_deleted_date_at + from alcs.noi_decision + where resolution_year = p_resolution_year + UNION + select resolution_number, audit_deleted_date_at + from alcs.planning_review_decision + where resolution_year = p_resolution_year + ) as combined where - row_num != resolution_number - order by - row_num offset 0 row fetch next 1 row only; - - return coalesce(next_resolution_number, 1); - END; - $function$ - ; + audit_deleted_date_at is null + ) z + where + row_num != resolution_number + order by + row_num offset 0 row fetch next 1 row only; + + return coalesce(next_resolution_number, 1); + END; + $function$ `, ); } diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1710273748053-add_pr_decisions.ts b/services/apps/alcs/src/providers/typeorm/migrations/1710273748053-add_pr_decisions.ts new file mode 100644 index 0000000000..8c8d5a877f --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1710273748053-add_pr_decisions.ts @@ -0,0 +1,66 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddPrDecisions1710273748053 implements MigrationInterface { + name = 'AddPrDecisions1710273748053'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "alcs"."planning_review_decision_document" ("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(), "decision_uuid" uuid NOT NULL, "document_uuid" uuid, CONSTRAINT "REL_82ba9c2d75bf10e7c6abae2e07" UNIQUE ("document_uuid"), CONSTRAINT "PK_b9304374dee3b46de5eca18dd67" PRIMARY KEY ("uuid"))`, + ); + await queryRunner.query( + `CREATE TABLE "alcs"."planning_review_decision_outcome_code" ("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, CONSTRAINT "UQ_f0ca5d421aa101c87c61e66c0e3" UNIQUE ("description"), CONSTRAINT "PK_ff611ee7c39894dd101e3d52914" PRIMARY KEY ("code"))`, + ); + await queryRunner.query( + `CREATE TABLE "alcs"."planning_review_decision" ("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(), "date" TIMESTAMP WITH TIME ZONE, "was_released" boolean NOT NULL DEFAULT false, "outcome_code" text NOT NULL, "resolution_number" integer, "resolution_year" smallint NOT NULL, "is_draft" boolean NOT NULL DEFAULT false, "decision_description" text, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "planning_review_uuid" uuid NOT NULL, CONSTRAINT "PK_fd29fa62e1b5b13602b851aa180" PRIMARY KEY ("uuid")); COMMENT ON COLUMN "alcs"."planning_review_decision"."is_draft" IS 'Indicates whether the decision is currently draft or not'; COMMENT ON COLUMN "alcs"."planning_review_decision"."decision_description" IS 'Staff input field for a description of the decision'; COMMENT ON COLUMN "alcs"."planning_review_decision"."created_at" IS 'Date that indicates when decision was created. It is not editable by user.'`, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_c85b10d6e99cb1585f56f60ae8" ON "alcs"."planning_review_decision" ("resolution_number", "resolution_year") WHERE "audit_deleted_date_at" is null and "resolution_number" is not null`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review" ADD "decision_date" TIMESTAMP WITH TIME ZONE`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_decision_document" ADD CONSTRAINT "FK_5866953e6d14233cace5d93564d" FOREIGN KEY ("decision_uuid") REFERENCES "alcs"."planning_review_decision"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_decision_document" ADD CONSTRAINT "FK_82ba9c2d75bf10e7c6abae2e079" FOREIGN KEY ("document_uuid") REFERENCES "alcs"."document"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_decision" ADD CONSTRAINT "FK_98f71d634dd9388cf287b02c728" FOREIGN KEY ("outcome_code") REFERENCES "alcs"."planning_review_decision_outcome_code"("code") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_decision" ADD CONSTRAINT "FK_3d54a8ce0b6c8a61d413aeb0080" 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_review_decision" ALTER COLUMN "outcome_code" SET DEFAULT 'ENDO'`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_decision" DROP CONSTRAINT "FK_3d54a8ce0b6c8a61d413aeb0080"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_decision" DROP CONSTRAINT "FK_98f71d634dd9388cf287b02c728"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_decision_document" DROP CONSTRAINT "FK_82ba9c2d75bf10e7c6abae2e079"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_decision_document" DROP CONSTRAINT "FK_5866953e6d14233cace5d93564d"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review" DROP COLUMN "decision_date"`, + ); + await queryRunner.query( + `DROP INDEX "alcs"."IDX_c85b10d6e99cb1585f56f60ae8"`, + ); + await queryRunner.query(`DROP TABLE "alcs"."planning_review_decision"`); + await queryRunner.query( + `DROP TABLE "alcs"."planning_review_decision_outcome_code"`, + ); + await queryRunner.query( + `DROP TABLE "alcs"."planning_review_decision_document"`, + ); + } +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1710278788366-seed_pr_dec_outcomes.ts b/services/apps/alcs/src/providers/typeorm/migrations/1710278788366-seed_pr_dec_outcomes.ts new file mode 100644 index 0000000000..6f65395d8e --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1710278788366-seed_pr_dec_outcomes.ts @@ -0,0 +1,18 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class SeedPrDecOutcomes1710278788366 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + INSERT INTO "alcs"."planning_review_decision_outcome_code" + ("audit_deleted_date_at", "audit_created_at", "audit_updated_at", "audit_created_by", "audit_updated_by", "label", "code", "description") VALUES + (NULL, NOW(), NULL, 'migration-seed', NULL, 'Endorsed', 'ENDO', 'Endorsed'), + (NULL, NOW(), NULL, 'migration-seed', NULL, 'Not Endorsed', 'NEND', 'Not Endorsed'), + (NULL, NOW(), NULL, 'migration-seed', NULL, 'Partially Endorsed', 'PEND', 'Partially Endorsed'), + (NULL, NOW(), NULL, 'migration-seed', NULL, 'Other', 'OTHR', 'Other'); + `); + } + + public async down(): Promise { + //Not needed + } +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1710288340795-update_resolution_generation_for_pr.ts b/services/apps/alcs/src/providers/typeorm/migrations/1710288340795-update_resolution_generation_for_pr.ts new file mode 100644 index 0000000000..de7980dbd8 --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1710288340795-update_resolution_generation_for_pr.ts @@ -0,0 +1,53 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateResolutionGenerationForPr1710288340795 + implements MigrationInterface +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE OR REPLACE FUNCTION alcs.generate_next_resolution_number(p_resolution_year integer) + RETURNS integer + LANGUAGE plpgsql + AS $function$ + declare next_resolution_number integer; + BEGIN\t + select + row_num into next_resolution_number + from + ( + select + coalesce(resolution_number, 0) as resolution_number, + row_number() over ( + order by resolution_number) row_num + from + ( + select resolution_number, audit_deleted_date_at + from alcs.application_decision + where resolution_year = p_resolution_year + UNION + select resolution_number, audit_deleted_date_at + from alcs.notice_of_intent_decision + where resolution_year = p_resolution_year + UNION + select resolution_number, audit_deleted_date_at + from alcs.planning_review_decision + where resolution_year = p_resolution_year + ) as combined + where + audit_deleted_date_at is null + ) z + where + row_num != resolution_number + order by + row_num offset 0 row fetch next 1 row only; + + return coalesce(next_resolution_number, 1); + END; + $function$; + `); + } + + public async down(): Promise { + //No + } +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1710348044213-remove_default_pr_dec_outcome.ts b/services/apps/alcs/src/providers/typeorm/migrations/1710348044213-remove_default_pr_dec_outcome.ts new file mode 100644 index 0000000000..595ef0483c --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1710348044213-remove_default_pr_dec_outcome.ts @@ -0,0 +1,37 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RemoveDefaultPrDecOutcome1710348044213 + implements MigrationInterface +{ + name = 'RemoveDefaultPrDecOutcome1710348044213'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_decision" DROP CONSTRAINT "FK_98f71d634dd9388cf287b02c728"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_decision" ALTER COLUMN "outcome_code" DROP NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_decision" ALTER COLUMN "outcome_code" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_decision" ADD CONSTRAINT "FK_98f71d634dd9388cf287b02c728" FOREIGN KEY ("outcome_code") REFERENCES "alcs"."planning_review_decision_outcome_code"("code") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_decision" DROP CONSTRAINT "FK_98f71d634dd9388cf287b02c728"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_decision" ALTER COLUMN "outcome_code" SET DEFAULT 'ENDO'`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_decision" ALTER COLUMN "outcome_code" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_decision" ADD CONSTRAINT "FK_98f71d634dd9388cf287b02c728" FOREIGN KEY ("outcome_code") REFERENCES "alcs"."planning_review_decision_outcome_code"("code") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + } +} From 9befe337113af7efb32222a1cb5205180ed1a184 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Thu, 14 Mar 2024 13:14:38 -0700 Subject: [PATCH 002/153] Code Review Feedback --- ...8988258-generate_next_resolution_number.ts | 55 ++++++++----------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1683048988258-generate_next_resolution_number.ts b/services/apps/alcs/src/providers/typeorm/migrations/1683048988258-generate_next_resolution_number.ts index 204f4b081d..3f4bcb2fb4 100644 --- a/services/apps/alcs/src/providers/typeorm/migrations/1683048988258-generate_next_resolution_number.ts +++ b/services/apps/alcs/src/providers/typeorm/migrations/1683048988258-generate_next_resolution_number.ts @@ -8,42 +8,33 @@ export class generateNextResolutionNumber1683048988258 `CREATE OR REPLACE FUNCTION alcs.generate_next_resolution_number(p_resolution_year integer) RETURNS integer LANGUAGE plpgsql - AS $function$ - declare next_resolution_number integer; - BEGIN - select - row_num into next_resolution_number - from - ( + AS $function$ + + declare next_resolution_number integer; + BEGIN select - coalesce(resolution_number, 0) as resolution_number, - row_number() over ( - order by resolution_number) row_num + row_num into next_resolution_number from ( - select resolution_number, audit_deleted_date_at - from alcs.application_decision - where resolution_year = p_resolution_year - UNION - select resolution_number, audit_deleted_date_at - from alcs.noi_decision - where resolution_year = p_resolution_year - UNION - select resolution_number, audit_deleted_date_at - from alcs.planning_review_decision - where resolution_year = p_resolution_year - ) as combined + select + coalesce(resolution_number, 0) as resolution_number, + row_number() over ( + order by resolution_number) row_num + from + alcs.application_decision + where + resolution_year = p_resolution_year + and audit_deleted_date_at is null + ) z where - audit_deleted_date_at is null - ) z - where - row_num != resolution_number - order by - row_num offset 0 row fetch next 1 row only; - - return coalesce(next_resolution_number, 1); - END; - $function$ + row_num != resolution_number + order by + row_num offset 0 row fetch next 1 row only; + + return coalesce(next_resolution_number, 1); + END; + $function$ + ; `, ); } From 09c33298c2a5b32033d51723754dc99dc58c3ea7 Mon Sep 17 00:00:00 2001 From: Urmi Kataria Date: Thu, 14 Mar 2024 13:31:22 -0700 Subject: [PATCH 003/153] Noi pdf template fixes --- ...enerate-noi-submission-document.service.ts | 15 ++++++++++++++- .../noi-pfrs-submission-template.docx | Bin 33977 -> 50016 bytes .../noi-pofo-submission-template.docx | Bin 33464 -> 50170 bytes .../noi-roso-submission-template.docx | Bin 33658 -> 49556 bytes 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/services/apps/alcs/src/portal/pdf-generation/generate-noi-submission-document.service.ts b/services/apps/alcs/src/portal/pdf-generation/generate-noi-submission-document.service.ts index 1f12b09769..ff176c8f92 100644 --- a/services/apps/alcs/src/portal/pdf-generation/generate-noi-submission-document.service.ts +++ b/services/apps/alcs/src/portal/pdf-generation/generate-noi-submission-document.service.ts @@ -312,9 +312,22 @@ export class GenerateNoiSubmissionDocumentService { const buildingPlans = documents.filter( (document) => document.type?.code === DOCUMENT_TYPE.BUILDING_PLAN, ); - + const hasFarmStructure = submission?.soilProposedStructures.some( + (structure) => structure.type === 'Farm Structure' + ); pdfData = { ...pdfData, + isSoilAgriParcelActivityVisible: hasFarmStructure, + isSoilStructureFarmUseReasonVisible: hasFarmStructure, + isSoilStructureResidentialUseReasonVisible: !!submission?.soilProposedStructures.some( + (structure) => structure.type === "Residential - Principal Residence" || structure.type === "Residential - Additional Residence" + ), + isSoilStructureResidentialAccessoryUseReasonVisible: !!submission?.soilProposedStructures.some( + (structure) => structure.type === "Residential - Accessory Structure" + ), + isSoilOtherStructureUseReasonVisible: !!submission?.soilProposedStructures.some( + (structure) => structure.type === "Other Structure" + ), fillProjectDuration: submission.fillProjectDuration, soilIsFollowUp: formatBooleanToYesNoString(submission.soilIsFollowUp), soilFollowUpIDs: submission.soilFollowUpIDs, diff --git a/services/templates/pdf/noi-submissions/noi-pfrs-submission-template.docx b/services/templates/pdf/noi-submissions/noi-pfrs-submission-template.docx index 3f4db6eec54420c3878a5a0ceb2b59dfa45be6fc..dfc73105db17913450919089293f5dfc9490b601 100644 GIT binary patch literal 50016 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 cqg|^RlixVo1;1@sB@;DYn9zv7M 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 I_S>uf2l&>evH$=8 literal 33977 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ǗKaEEW`|4jZnb0B5a|KCK- ze`f!k%=MqyNt*wa{Wpf*KePW%VfxQG;H>@M=l(bP*FV$$P5}9f=k)LB*7>jW|6o1+ zGym^ohX2g((f^-Q{~KxKpZR~s&;N54F&h4V5+MHB!QU6wf1dxB(SLn`zk}{_k|5tx T0ssK^yLkZt0Fe3rb@cxLxj&5I diff --git a/services/templates/pdf/noi-submissions/noi-pofo-submission-template.docx b/services/templates/pdf/noi-submissions/noi-pofo-submission-template.docx index 8b4d5ad8f31042fede20ea5089974701454c00c6..7eea8c74356da5acd03cebc8b8bfe0594576983a 100644 GIT binary patch literal 50170 zcmeF2^OGk}x93~awr#t6+O}=mwr$(Cr)}Gqwx;=M+s4lK*}XTO-FyFoyBU#H5m}i( zRK@Fk&dF0J6{JDIP=UaKAc25@h=9h7#gkHifq-@(fPhedAVIW+?d@Dl?OgO#JRMA( zb?H59Z3v6NKqw1p7E|LuS97idnMvKnGU6nzf;3Z8FWlj@`_9iAwPH^s4d1CC;C z75xRVaPatyHx@}n7AcHk54$kk^DMzUZPB=*+6+dJ!b*ih4vHQ)QKM2OvA%ji=VziQ zU|3R76PRL4SU>UdWX7_|0f|&8E1?;AlE;Cd38fV>%*CiO@oN>t@)YJu4bi>e^HNqJEPrtmf!I+)u2NX|bOC@9!F+)(a=4My z7{4!K_tQNoB<$$0qkX%@thbjTq zo326xunB+rF!V+KnyHjH%keq~fg}BnH@-d={}p%I56@Gz=&zM6v4WA0a5hgWh(CU` zdCT+lSF+#Vpg;=$mzePrvD>cy5-Rsst1y4XtnXxM)QZV#Z;(C$(uj}?b~*zBVxv(HHqNvg@{7#YZN5{+u0v2VKP&%BEJ%10tYX`u?CA{pL&%yf{o zzK)}gu=v@oi8Pz__v~?UbOrDKTYc0vksyixeIf!51cVC&3G8m~WWw;jMPg!a>}vbh z@BQQO{zEXpf4$z{+5fX!P0EbiU#lN-EzvD}!0o71@KIsO09mO{iLg7GO@LHK;tg|f z*`UPgTBw2R^Cyz$=i9}F(PL@n0ceaZD>|+G&orPktyT?;3)_1i*EwWqkkS(tp#kuE zo0jfwjcx&oalA&^&CTJC0y+)hC4MKnDe#o~$}8ZlbSWIN@z}DX9?Bh2IG;3AXFDH+ zR-Rj#l|}d~xE@mK;+odc(B5efjZz zTpcrwudnNxT%B;`f?iy)Vv;$dtLvwfd3htNtMimRUGd4cSRjbw_V=Xtp_Y^R$6!re zguGNyIKFji8yrwquQNpgPHdfUNwgnd8sIg$ki0+VV#7rC)iY{gSPDw>hnxOJ-0lsvJN^lD1FSxkyCBjXKRe4y$Fcya(J>HsF#CLiWb&TMc= z{OI=to&)!U(X~aADZDq#z}o02B2E0*OA$LQ)P_-GEz-Z|%5FJwc5fR~Udoe2nYkIR zz8zirD&9Q({BIjNNpe)YY5j9N(f#JgEnB=?0TpGKt{NQQaT9~bzotb#hHA>$?FUpd;kKUz23W8Wfk*c|?R1EJk zr?O-?g9r%zR9LQHW2lkln*yIm0^6Xfsi03W#}vgh%^P2%ABv!LEjQ-qP#5K#G#DC+ z!vCqA)&f4|z}Z=${z13-DDwx$WWY9|Hh^9V_FOfpHp)Arwq^d~s6Ot-8JXcIzEm_k z)$LXy@_CS>Qc-QbaE05`OT)Bx8{By|WpVy>WQJBQs2Zvc=^7tfyq%nk@XM(n0^ybQ zgFYTc(>lmlj?ba`j2fRmVvdtveD+unS;s+vd6=?zm@`qlLvC_dvFBwhKF=A(?&Gi= z1&9Y03$$9;&I6B@Jd-2&J6S$cpM0N$itocCt@I;~5KmNxytUXD&a9FWu##1(EnbAh zK=(H;lq%jmxERWxiwc^>pR!eX$!zyQ*C%)M=~1I?V{vW-=pP>s*tppUINatEnw}s#1>ykyJM7JX;#VH_ZSRaoTqj`axK(}{0o`Xk?(>edEQ8HkM)DY zJrZ@Na~zlxA%Z4Id%sgRCzAv2*?AI~Y0gMW1T#<1YR@q!1|3sipkBuZ;%GK}{d%k7 zpLEgR2*08*^7H2aEXYw;>8h-sq(@P9sPgEGntt4UN2}-OEYo}j0D7M4;KZF(qLeLpd_4LDwr>E*> zHWhXgq6NQtynG(L^^)tp8z7Ol#a~wKyFc>f$OWBHoIr{DLgLxzQDhNW>|Zq*4|C6r zu>XYAbMU|UJU4mdTO3e))Bwpj(C^f9+$Bb{>0&FuJ=oVinl(7%Oxp?3gtYdPV|)rrg<>8fIgf*o6? zg{+^b`Scv6`|Zi2iPq&N9P*))`IYAEHCMmO|4I2eCDkx#K%IR)H=*Y1h*~>a4LqUo z=AK*!T;7w;_`w4z$$M|A?yx8JgS+A_726KUL=Qz=qv}5VJx8z=R{W1CXV#^h&ZAU* z%pD!{2wyYKJ@M@DNL-_ubFFfB=>sF$XpeKqxZ*#fxy&ug>IGtaK#$)S9{+_wADGyL zFecQ`zd?ynXUx@Bu_^rxdTlS>-Gygdy`5kG2H+0-*R0G}$Ft z3~O5_-VhK3uWLB(pR!1WervxgHXYqdvOu@@ytz86;-ZU6(s<~~nyDx67ki8^AJlb& zJo{i0lJ@tRAvoI+Pm6Z|)6lj5Rs}Hymv0ivK};Mcz3EExeah`0(O69XFiX zcs&I6oS^+0Ir`Q2_UQdp%~;O9@pw#%9bKp6WUMEq=L+%&GJ+GH8CSBNiqX*hQ`*ff zzhzro7xWw`y&;O$B;}j#jFozrj;661EgGh6ZOSzv{9Cio+&z5uZUN%bxbRSq%%ht6D}h1RX=D+h5+qe$4s|5`T(M#J2)cW&`800=>+YJ z!E|au2P_KBe!8J*hXwdefkju>YVTH}7z9$hf&^BP*4L~b>%^CNdXQ|4%y80y29ys; zQWN=d8ziX1Iwn~aGNa!0D)9=K^@$@k!iJUsA&qy@5B*fQ5OMa@Bg`zxp)mZNPkcSz z{(0M@&)0J14H?E>JIGTS0i9u*EAP(*)%a5<;0-oYNO26IwS^7SV;-n7vp##gOMKEO zn(v&wFv*z_JLh?xn2)$XX!o$;p5!2i)`%9P{QD&CtM=T+RAHTqPX|vZ1A+@xC#|7L zeRs*}i+Dn?8^72qGUTbr4b7r0{S;X`HpPDOyEAQbxV5?oLu9bI3CQ4|Lod$pDHNdQ zS)~gyLsMKYJa5=HIc8XF<`v>OY^TuyDI5=8x}g^)(A83DdwzD>6lZc!nEe`t(_-R& zU_u7Y-crO~CxY{=p}FOM7m&2X8RR{gFZMvDn4(w*ckPc-IbXK);YyE?`KEjG_Fk^m zgh7fTk5rb)Bsat+ci)=`|6T!K)BA4OHYmUSq?H+706U#Kd^^?O`P?P_I6=7P6d5bT z+ZJikrM4t(tkL+VbVJ6t?LppQyGp#@W zB?vs0n_y|m5E<{XL7B!3Yb~l+#%7n^&TGjJ@-caO^j4X!Aga^kB9@ zQHLRyR8hxGn#(L#rz!CeA7h%9VIk;BCM0c*V4?6n5xrPhIfi%_34TaGbp6gR2w^07 z&=bvjPh@}oWxgNIGg5;E&fA9c<#-m<+njvY7x##LSQ2rRMl908B~90Fv5Ko|!G z-}~V8ykCXO=kr-!@mu=?zWqFQ$dD9m&ZjLA<&@(i>f_){IJd%ND2c6r#Q7!4vVKA> zuyoSz&v2KM0O78J_9{sS7Z+2ad8|otyq1`z(GKnp?+iYCa`rKNQ2Y7DX%~@NXmjoR zcb1U~MD7u{ZH6vIC1DFJL|zpon#GR!_~g6EiKTGCw+OUZqo8835*8=+3jznj+Y(CA z;bc_Um|o%+Eawmw(vHiY*q>yR+^9I$TU)N%<$ad-{6J~p~Mt|;x7rmcL-hB@l zu3txnsRd6NR=a0_#JALR(uU)V*jR<^uqqIdy@y6TJewWcMuBn{Jxj57ky~%?|Mdjl z&5vLPaL;awcFyk^3*ZF?MXOI=^-cixmeK2S#Z=S+v$MkugU#-O$EiQKVbEkz?3oi$__{jcf=!BW2!c-VsyiV^Ra32$+|Czj zI-9|3P-z~|v@5(7+~b|ObMl*)pf%E-IR7ON$*a=iT+?KHg*T{Y+gN_5+XKM5dGQm& zRTn|FgTBJFh3L#X&)ZKm)}K~$%=bxS{)ihmsy_?uv0LjYDh{LClVu{A0OUmKm#f*|(i`>+5YHBwnMDJp0Sk=J+=SmGYD zWMeKfk_(Ro^ATkUTy$hyqB)e36sAcJf;j3q&lnf)-!*B5Ks(gYxSOD@S?B5*m$!TN z*M2ghJad9|&pguo0g%A@rv-rfS(v#d5u2eF+O`2U6V_kkyB^Kwa995bw{Ea*|BKcaG(PS=kez*9sZ#7D%L2lL4(aPJ$)>ofI3- z7)a0(ALd6039@YZ5YriM(dmikeBQEYdE=g&UT?Us~-lW-2j#exIDYC7= z>6=`!!WdUdC<9RK);g8#UCPRx6sc)b@>|R6=-+5qyy9ZKr7FZS6ypZD$8A4!&ev-I zx>{&^ECE}NO4P5zG6X44}-9q z4|qjx)Od_Nklq!%ZiNcr1wQm{2#seAFblo}j8$fa;A8apMVcvQ6Tze6e0Jl9sgZ&@ z$125L=t8YzJ{r~m2Cc`=IO_e1z(q`i8%0@{q`-7^5DkIG8>L+fSArV6lk=`JcvFht zGrN@KmQnLrqp&T7qB^Vw_>4m;(n5;CIxfi^ODvNYF}%A~oLWwJTOqBD=HTM=vdUX4 zBpCaI7+M0NGYAh{N6e_`LKq2ExmF>akv3Z?&GAQ?KC%5ZmnV$PVi{YOrZtbz#s62)?Z* zsW6a(#|pk{DN?;m3z6rDQxMuy72X|- zT^h~Nny8EwkX>ZGVL^s`q<}jzUtIC0)9f3e;pj5qZZb5)*VrSze4BYV_aTN3LWQem zm~K71Wx8Rs_l?uV#SZQpeSIEu#_ap)nw%NGDPEqpf^Ti@Ia?4(w&)+e$zVt4FTfA& zIkaW&ql+eTF-49PbaT5f?Qyxay9&!}4)rFr={o=Dckr&ScorZ+a4pj(OWZuo9^y$e zQRPo0rgJcc+&{1D><4HR({(}-memb`pQ);usMB1zxodpz6JL8=q7bOR?trVQ(0um3 z0z~b zKQd7}HhH#)WO11N7g)nA?f*>;J3WN5;#~s0K)oMncXT(6Zn!v+CgA5a6L57fR)vFm` z>$E;0cg~`1sTg5HJVV9&w14`wcEq`c*X-7ezOHbhNI=Beh)ui>iRm)U3UnH0er7tG z&C5|e#+stl8!4S%?BK>yyH}yD&qP@yn|;BpwODAp83uKr*9;9WT%7xuksWsmp@n%; zFi&pWasuIyyF4}A+tM@sTBB}Sis_cdbuh* zev3DD!A9|zIOysnZX4>5>&qcy_LT|suog#QBt$+am*4O#fIrPglQt3pvCk8tOtc`5VOvELHZ}QPPgYM|{>|pFy{1dJ0wo}Y|+UZ%Tb~Fo` zaho-4R?`m9__K3P=<)Zx|JP2KL!4TEwTPZl($m*p0IZV4XW3Lnw3^BL4A9NBu+27t zglQ)2Ok>I(=jZ7GqyR7S=oV7k(0Sd8(&%c-l(O!KhZOdK-kP^y%iz8d7+ch7lV(d6 z?T~%nT2opYcCC?Wy|D?Eu6_P?tSOv;{0``r5gCQ_cC}t-w*J*m2hP1MYMrq8+F`c1 z_V&h!VDu6#Y-h{+InU|RQT-Dg9mgz~Cw&fRxjM1(Vgc?&L^FP5vyTWSK{N{pp-quq z%~teYs!Pht+n9@LON55zvLM93fe2YHc#Lx^i|wF;@Q#hJ94ltB0sGTh&sYk)d80h* zQ!8(rTlIxgFKv1pr5erll=JEB(reDP_#u&hw$QI`B{bC?#_HpXf=^*ICab5e>X3gD zH4glSFdDs4zrk035}|M+29RgpENAr#mR)xEouhX+?b<)yzf~>5Eh-{A0_bTFUE{a8y?){p$ETerPIUrvQ!AZhq&p${iCG*U=+J%M+<$bAS2L_`Xa@5ri zU4k2eNtWho!QV?7vY!_g*><+PeWW27kQ3w#|+ipGMTzMhK z7Kh9ohPP1YbTbsG?fZjX!rWL#EFcAC)}|&l{o*G~arS1lM`FYmIkLJ0`bJODMD%@<^-Gh>QKs8azyTKy$_b*<7XS3nhmOzw zlxqAkEi%>nq-^cG47*KDS~l!#-Y2R^)AEDNVyvJ^eBJgp;t0MWb>+}*|BWktT+z8j ziDofvXLO_6LY5LtJGdy^!t^S@)k=u~DU(%w&q|^F4VPlw7F4gY+=TG~&d77po;}9! zS_V?r0=-rvM6Z&&*;aB>Al-{TaXk@@IxIZOl|sVZPdkM1VREtf8dzHvEyNawME{p7 zZVvRF$07Hsz;GK7qfP3FA*K$Am5ujQ0z(H`>JUF4xDcNxMBzy$W*L=-;vhmt+W0iPxh@DB z)^LF^x->Z&XLASYlRCq{>rTAGK-)S6pOj~D_WM`jnnYc3a6OT00y z2V`LI(0g#V^hkQFypH$qb)#`F54Bb5C7XBlT&?~~qDRq%d#LJCj^RvE0)U1U-G zkOx>eCBEI7An$yWGo!ksgKjw6Y*FC|T?*>*Hj?!Ia3yfIgZV$4@Aun-_8L}Cr18fy?M z$sbo!e!W`J^cqs*-}xQu7>>%SwP>I5qKuJ^5CcZc+$M6ydm1K7?bPVy_OTdTdn};q zki_ytc8EFTHB?Ne_hdPv?tASBcEaR=Kqs+5b=I8riBOQFrnprJY%F#tQ2Lxu6E!_K zr9J~n>5?DUdz6cRM3vGe>UI5yF_}aJGEeIi78&@VR8?n$Y>-84niL7!VTCHJT!AfK^8D9g7Muxp z2CV!;_C9|4Z%7xlPimElz5iTn3%JHu@CTKfC($HzWsPZOx&uyV<{h1@N~G?|;_Ni% zY)^czm#af}biu#$cfK=zKHF-%0+u`f{i<7$YR28PVhDdCbcVGMr21qrC1l5?7e}Uk z0JLTs`}O_Zb-gjhIeZH4$K{XpMIys&jl4CTMzJu$BXbupcey(9sa7i_?QiE_1)(IC zVC-TaNCb2D?e*+URg3;}PZIN?e+hnGw%`sEF0L~4rTX^aEtMgpl->O&mmiIL|9r%^ zF;JRp^7ZL}>!oEPeAANMbqxef93MvJaj*E|h>G)&XL0KMTNK|IS2$Y z__Boed0bAw0o`J&kV7SaQ7dcA9D({A39%V(mBJn*HFy^{nr35{{36mBvhR<$h%6o4 zoWQiBLa_77_3yLfxR*6nEKyxNWHAIBvZ@J6R;))2kyuq%dNOib^hd2Jw0*6V^*2LJ z%O#T~t@qVM;qlvmq^x>EYrh{54AYU4^r+fnp2;q>8zg43e2wi`J)lr0 zv{ugY`Lz@08sZULJ#{Gx-Fi?ipR+Mc7=lJ5Xrqh@L3JGbPfJnPepxX}W=vbB+w=Xj zLqmB|*1WijO{E*r{G8=VcMlB`F9n{2T|JW5tdvZYl%v_+#O z*o*}*J+sQHMnjE2Keve~+zP&_>n!xj2Th^1E|nQqXPfI3$<@(jvQCG%HoM#7LVZ4x zRw5qgSABy}I&`7e9p_y$-U5iskj4# z1@$bj2ok5oLx47Ywj$w6TH9(X)bM0^0&3U*5i`h0cH48khgbjO&-1BNy zvK&%5tG%F%VO3!4+%3hxUVe zA~*F1ZpsMPn&RFIwx);$#yCNai4B48AHt zfzrUd4zcw@4MMjK5ae8~wmWCKBh`UW5HD<5OKS&*k3(;|ahjN$wX5&@c*}>>ZXHqp zmfHsB!4+bzUC{F0(rR)W_sOow5ZlP^HZqukNI09c*v`#F8}+VPeuDRnG?N&-q{Oim z(qi9yIZ~q$9e3n1Lnn+pAA@o&@v1V5M{?CqgV?def@p!O;H0r{o#|Qtsd-{ zjbb2eiHzuhu3Po3=&#<{VAV+o&O6Fw+)hEv(maa+JxL?Xi4OhjmOEJ+MI3V@!68!m zG!~2NvXjpv`pvzUc@EF?UVDQ)iRSCGwXPYI^ek`!D*~{_U zDeM>FCzGM-!rBOPW|WjGjUDXzdU3`__-0WE&S2xZq=u6jm`aw=_X6%GIWr#B?ZtEWl;DncBHU(?-4Y7EkilOswn7_<8 zBI0v#`UOE>*fzC_M0i$jV|iwcLU~t1t~Lr;FKqKVbhud@IC_T-B5hIW4;?dYTHf7e zCIQ6PQ-i4aU{q>;8u`dbsxeAP%JpYBehD)E(nAd0Hy%&kDPp*`m##ZysVeKq(D+`< zim5bgI_$zshGm9)$*6nT7pj;{@hFY7?XeQlQ4xp83M1VWqPNzMw2-%a*kjK@ODOAl z9Rl&x&ZsJSPMP%%=QbK3v=}_IHpSOhM|Ye*n$g3<7iN0C!x!NF$iK1{ zJpm2wn8Cg%h#DEQ&#sC_#cH9|b#GInHtcT!7mfEa7T?iVCd)Q2nODS3LQvT_Ozos! z3E^Sf|3<#kaVAwh z)nqycm;DIBVjRA0pI(so*-Lnmxa#5`SyBU4q=@PJ`aQLcpJ&UUwbZ4$O%|3VPZ2`APtEgYo5 zb?fCC3v;lFL6;oVR)yi<5Z4ikCe5%AETn7pNGpFx;40C?j`xa#i+~DNxl0n#c!sn- z8T7DHcR6e;7)mwu>PVx0$KExLq}hr%?}Uyh#n!H``Nx&?g-CSq0JVScx!4 z!RuP+RrPt5dbx`YbJ8GTx<#izm^6Ox#FBqyiHhp;%Li`-WdTChYN(c>W0BKugS;CMmrN$`Mo=<{9&{HXwwo;H{vsc)dqFB=m(Nx+i_>tassh=YksE ziw^G#`jx2Pahg?QaZRR#>sn9LlNz<3SITjSNJaex9>fC++7YMR>aOGH%?^(wfeV7^ zRjhoYes}#Fm-v%Y$S;BGFdSc(U`@PL^w6Z&$=3y;LRgmonIQL_E8&!Jb5y&45MFF8 zx$Bgg`?F>3baJ(j*5CB?y5)n0i`^w!5f5ulBbL+Bi_68mEM$Y{C(pu%{=bTmF0^dz z_c;*1Xd>i)$ywd!1=ObAjoOx8;)_>v=gK_~kb>91F*(Uty1RaV^0ZnOhN4r3s)ZtO z;BNvV^UZl~NmYB;Ea4?4=FTuxd1@f5^7FpAEZqQ7Y0C)nd*Ep1u2Twh%kk}5>A7-Q zH8UohS!utM-C=YCRP;W`#N=krY7H?NfA-<*HN8;+q*_jH(6;88`s~jVwR3jZN82gs z6Bk6qwNk$eDEpRIFxnxw(pcT8j$JCaGQMKSo_R4(5z?$Ve2roma~7@_O__2Os4tP! zZs9{U3$zGzy)9xanefwoUMdG(C@nA`QQyCBoJPVGvkeU~zs*+wuyv8iC@gkJ5<{m*Zxey&aYR3-W(uzxR_8ux#`5O?!_L(vA z#dN1-E|u=fd|GS&oB_7{QFYqL@@s)V}Zj zBX8d=w)01v<%Ht|&CCRL&a5 zE`dWuS6az}H@RnI$!PO-Qmh{4bXaJKH=95=_)GesOWl=upQCTf!u;_x^r_7)#NxjCIh~}ANMu7+%(QV zy7mM+_4IKd%k&k5RKKJYtq>Ppg+gUVSWeh=@&nlx<2C2P#jGFa0(=0(pv0y+M#Eev zV4A=3lW#vHt)haA1qWe(6TPS-n|6H>aI2GMqj+zVW@8w>PP)Dic!rQnvOKG$%$x7_ zx|)Vt_$?db5t%MUCVg)r|1-tueKn4Y;3zrGlI(UCA0^us%te?WCK0v0N}LEy@-x}o2hgdb}9ra$O;Gzc9D22wKl5H~>R(T}OZ zBhQjNt?43k`rb#&SiOO6=*D=6@5b`oXM>PV=oP3b!8rx)z?jS=1$aCa#c?y1vz9=s z*48gf-I*X2?^XC_om_GSQc`RP?sDl%A~Rx6EXgTX=j&}?QXY>c;$Ris9=k?|BI>i> z8ZYBXE))DZ?g^_Fd5+cS-9gB2?!$x1;F8$r2ON#e5Js;X*NsUXkEiH#kkLFhN?YQn zTShN^eyw)eKK}g2l~%m_H3ie`gyYQ-1&sdh& zE?@V6>vF3&rH-wj5Ip8hJ%H`NeI|^kg9mZdZr9>O{8Jk2$dL=#i6$Ud^DylPCOIyb zE45a$7hK1mgpRO}cCwZU?ErbOQg<3qLpJf1cVqf)+9AjJQu z$+R#vG%D@sBaEoK1LSt|Xvj=GS5gLV-l1Vlb&#QtX!8 zP7e*=)^D=_2gLe?uLtuMEf-f`#L71F4F|GxKMp9Lc*!El^v%-m&(klPJ?~K?NyjQu z?Kg3=kMrd*1a;3Gh0DM;7RS8A(tM5V5lE~g#=sd7hoV$P9Bv{h!j^T1L4{ftQAI@X z5#=&8!_j_~HjS%>TN+%>hhoK8X9u@_XA$HYI_~Be2gU;s-6By8)L5Va3^79tR=k)j zqVg(GE`lE*s(1?{91Hw>c5HE|u9EkTC6?*UnBr$8$tUFPlNlkiu?ryD9?ROK^?vK7 z=0LOchjToKPmt3FYQ~J|3?!(5>cWW1Tp5hwLW*f}ma>Y{{^h1RulcNBa$Y)b>TRRJ zrJrS?r}RkNYfEbz>d%q7r@d+>6dBE;O9^IF!-|s0H#L>;MznBT$=HqlG(g^oD}X!N zi;m?l^^KK7=H`CByXqDc%gPU1Bg_X4*>DS8oXuJjcrUs$2;i9(^yo^5IY8C6Vo}y^o8pLR082$ma6kN)3O0Xv z0c2}91lAQA&90UNsyhw-dSQK6%acy*t-3?OWWO7}xds42V)W$732!>MMPFl2-z3*n zPuC!J@3&YV-6vstc36|3du7%ao!ma{U)2c0pMV}() za021?nqn=Q$yC1^HV(>EF*yG>HB=-CYy^>Gn1yV+-e3x*E`0QY&hTE~00zke8_Z|r z#Y#OmTZM1I1`Ve6DDjtgm|0PPgkk<)?6aOEryNVR9g#mlYn{jq9jcoN(e)nTVTyL1 zUcve~8$MFa|KtUninE3}ys`}vE3kqw$<~t7)+<0wl}XAf(loC!*FGeRUCYGJy;WJF z(&Dtz(z-{l&zp^|Xz`M|nZE`Xc@LD(6^F4-5ynYnr5ww;A{tvVW~?L(i34*))a0oj z4D_qKt6O?=*bBqp8u_)j6LV{SF)`ghT$Bm3L=lmH^qAR32B7?*=b>iF-315z3qPcH zKty98L3CC_4Q58``Xz^tf!1=)eJuyPJtz)rP2hFxDUgg&@&mP$#uyqmT>~ju8Rtpd zcjP-DRD~^4_WYkmoMfDT;RlrGs$_MW z86cH}Ls0zro5tJPD`+qCF&S&zxokPMg4J^OtL`RlEyqO+I}|~wW(lHq7oZU2WP-S* zbu*U#&6!V6%%8ageOrl<`Secwr6?c?}4?;Qg-f97LT zwi)&tV~m08MiG&V!R+(z8OHxwYcJU_SX=j(@94jL|7Weesh!Ec;XPru$%q(s{o@TD zD_+%+x1k1!XU^?OF!sV78>OdOS zRBBG~3AG4tvWlrr?eWQT-JwMo$a%!HL?LN*F=*3?-R?x|Tv@>}`4{{E+>GGtmB3ZrGFAh_Wa+xpyBlJ`rTNJakVV^j0+i(%I`17YlCJ{HH z_bzK0CsW*|wwMc(NsRqX4NMe7K>rZOSy587BFe?Dvw&KN!2T+DOP&~)(T)j5r9Wh{ z;fYD~mTWOg9ddseOf^F$SD%F3->uU$7M$J`iwCNS1k`o!UI9%zC>cYh+k?`As0t%W zx-l6>g-&WDny`w_%#V&YFT`W_u=DbsRhKE&7wxpxZ^4;)t81uR%jU1)9m+@9fhE_* zV6)W(a8O%1*Jhs)hAkK{*AZ>TgVdvm_Slsd=T$z{Ej+pKgrTpEeVkp0I5vM68=*dN z$i`cs`pUd`&ut(Bv$t<~C|Jk^;XKmvu@YVZk*7TRXfDNQj`TOr*v;+nE><1k%8xjE z;H*`c!}-`xL-m`{Pcak?yXrKoB8xj2Wk+8{UN9Gr!d(%{8I`Q&ONU&G*}6yr@{4)f z$Q+OP<>(Dmp={M5>Up1im z#=na`tDxN~xh)7a$|8y6u)u`B{Njq7a=|ZM&yMp98MVj}COkgiQj2e9o%~$1KD$wS zZyNKRQNwod%i|3fV0jzR)={ra%1XW&dzn<5R2DQ94s-^k#A>?QatOQIN0y2ISbY>X zbZR;2DDcrh%)Lq(;?T33cKCZNZXVH`~$c8hI@n)f}ECxv`F+C2o1ekXzLBos3SJR ztzfJ7M){7T+$FMrWkNveiD*(BZ~k|TIXNG%_WiWBl5Ga6F+UHiPdqfSG5L}4_tn#x z!NK3a1qmPn&Gp2-^kSY|r!S|jXw(9+Jt#sWI_hWjD+-aBv=ld6cs3$UjQlATDC#M2$0UvXeS=csu$PWLM&s%yK4XRf1wq-maq3Z$QjC zhW>TNxv#-by96A7)_Rd=I&Bw#1xpQUwqF=bd{YJ0O>ixXNlb5?^$3U}9{UoCu6W?D z_{@E%xM8qwftcB#gPF=D>}<_!NUgI&=^FeLO8QYgc%&$tWriqDB{QkAh3BD;4O@n0 z@~Q-s##4RtOJ%KbDbLy#V}#8n(DoqRd#p+5b!wQ=q9Gez(YrbE<~l1C86K-!w_Vx7 zEeNO8h7YY^6$lp6_O+n`vp6YmytNZAyoI(pO8FuRP;YB6l*W70kbxHvX`6E;daCLu zI*Xy5W28*cNwV-Mg16cZw-cF|)mMwyYu%xsjqQzh>9vWhex=h0QB7O1Ma*7@@>9AY zNNz&Xg%?8!@D7VA=;@KLr`HR51s%C>_e!FB?AErLch2+$2Co8bBc3>jtuM?B|2Dva zE64Eh2YQG2yk_VQVg(Z3_O`Bp04)8}ff3Sb@rUdUag$xi3)%xt?v9yTa$ufCWMQ$s zq-Et(BF?KUb+y_361teK$0-d}P2g<`6p*sbMTcQ~hVhq5Tu(ux%+>)O4z*$hx{?n5 zUIP&_^6 z;LTb2W-N&7vkUxZOU`RqPzIygj!+UW9_{tf$Ga*1FR%Tv$gILzGg{yD{`HBQU@|1M z+jo)u%%#K$^UM~yrhIpPozfXT>C0~#)@RG9y2$0^zW-KFDV0iLOMj0Cju8R@q5VfR z%X=F7n<79IP9o?3=jMfp6)z9pI(sn>3Z2@-I5cwN*)xgr+!AU77Hf1 z8w(Yb$oo>EZY+d@zpaETVOtcIGJG<@K=ph?NM$vcn^Ze-tc z<`FxTsBn2(9~u*XlLb}BQ5l9yu`-jgxV7FaVd&oy8Sa85<&hj`&M%U<=yL=J&>slQ zIX>nq?{q2cKq4s;X(wf$^y$!JWYGE0jST`~3%H-SW$lagK_k{iV!6+n=F5Q{xKe3m z+EW_zemimd#nA;eibr4BZCYo%Bz=w6e%V>?vcq+Qo5s& zTyVhkf*Z$H;Cnf*+CCcsR|_I`O&X%@TDWu7;Yo4o z`C@!?EWh*hIFjzh!vM(tyg6;!@%v_f94vTTPB!P~_k4Ra_xJe@{jqI+(+G}< z(MURwuK!%o1_*(Y40N)_y0i9(TH7!)OM9Uiy2@{&R$HXH{oJfqBOl!){@*lVQlQ1s%0_UajayzM?FE1 zfsO>_9cJ?g64^M2*dUG&{JqtMMillLgV>7OvY}Mf&dzRQ5bp6;5yj|)Gj+jt?Oo#I z$f7;IcKxyPo1_G}k^y841D-#1{GIMd4FD{MqAjTfc>}Y06lV z;$L0B=}i>)LB&SGHAKRZWDZ&K@`w&P&Ju!CoHRL3?W*lq1=4PTa}U`)ss^V@^0E6Y(nhO&+Xn^SaBcV=d~m+H2~mJMt39Q! zUtT#sxwbh}3d(01rHgglll%Ni8dtL;j9dpPcba1bsWgLL%=?+npK)NEBju}ON-Z&1 zax5ciccMN)O9e0j^Gq3dP-y+agc$}JXoz`i#F0>Pxc_L?0%%Lsp7;8uE$W+66u;ui zii*n5rx<4hT0L0=6M2&Xk z8$}Bxi81mkPEPOypC7~JE^x6dUaDqZ`Y??ZIxSe|cg|rbftHqNItElIFXr)Y{~^*h zQa$huR!!?{bJlgwJ+G3BT;)fUT8f&0wTfy8)ta|f;vNesVA&#Psh*9v=Ywl4R>MO) z?^J4Y)3aU#cr)LLH)`o353s7fR)-Gky@i^jLAhLWb^XCGm~fc$+y(zU`i&6A+J+Ki zJtMaG%QeWacm*LSj2)iGo#lo=syKr3Daht3@}ogs?N!@*tMC4ZPcMBVy^N(+Sr=lp zrQXl^`gPg<3OFszb2q-HZ09+=WpCViIqR!J^}HG9bK|#t>_@MR&@ajFLQpxDHJli+ zWXauCE#XM(&d-7s0+z5AHFKUDitTnzCo&HCu;=@S#7!gH-#z~xRzm-Ec>gpapc#&Q z8fFzBpnv**x?0;6lNlJ()0Re$g0s(<^K>qcg zK)yi)gnw^P4icKqKtQz8|8(F|-n7cUgTT&8;zIw6z4!j7`v2pHU+36coKkj#Ln7H( zhqCt`$toe0kv&g@vSnl(oXE~7Dj6Y3$jS=Y`%vbw$MrmVf4q#c?~ljh6;#&6x&;0~?4YQv2tjY-s19vOAjq&){idRk5A4s_ZFkFsWT8d8J?|TZ zYdVAE(KU~swn@mso4Dx%^n1B(oe5Fp^d)|wV&g^}!qtDNcK^#HIfnU3C2i5O8d2Gh zZxjX!tv`O0@901X@+X&$84gDM6}qJb3=l2uX`JzUfAM9>kA8nj2F^2tRSX4xLXN3u z5GX>9nE(H;|5uRUH()1dF(ql+F!l(u5o9!xpR|MH=#oAvS9{xV$e*xnb$GbHFki>P z6mP(6+VHFT_?WA}O(SZ(sXRM4Gk4!Xn)bxkqWqXBn#f8K1<uDv8c{9o&$3 z$g>XV!_^1wyF)MWaWM+N&9V!8*^G>=FHaxg8Wv)(?}-r}w_;Tlon!2(M5EFqdBlYB z4&w^wK4%ceRQv91`$?=)R4GR;tzVr!IMB^+YF|;n{578F5B@V49E>nCniwlS_M3RB zTuY8zeB<}v?^0-Mt)laQwJVS!aSCxd2zwLnakI=@nVLPXE;TlYSzB90Z27Lu`uNzy zqnqrH^34M1tqjlUq7=RJ<`$W*nC6Yg6<0WkLQuLFSG>>mDK_!f|Q2fIw(cSB5#k# zbTX(yqa!hUU^xb-+*-!}{8*a$@`vTsiNm$+4@Hsl;JmMu1^<#{zM6+YZyMj#h(r!| z9XFqhQx(bv?)vb>Y8%fYIBs^$Oj>}US%|)7dnu2nU{+Vf6fj-QKBE-P^6>t>fvBgQnZ-#+VkbVKj01)TD_!N zOSIjok7f65cUN^+gLn8C8Q&CAvOI*Op<93_(E@W@f~K>*a>d} zzKK$Fnbou9re1gw>cJRtrB0IbJOG$UrOPxX5r3kxc^iScOnbo`;c? zetZ_@Ruq(O6ik7Hu%$SG2phQ6H(E-I(lYN~^S^aHJDXMRLXNMJyi|Sfwi2&V@gg0? z`xuW>9v@qPom$*6(AAdpxA8vjdL2ymA!W$&@#0(kXs>%BjCv5N&0s_~YWH#ABN52z zMoXwfhM?|t&#j1tlSBy8@GO_VAb^3oz7Ug?CcE!n=WP9$U?&QpUud~AAMG6+7}kD4 zi;5~LI@8>dqVRLs+FtB{p!WEmL9vGm4s=OANWP@pkmRI@$Ym^RjCmF``Oi%q?KQk?BT|lli9F6a_8xru{7yTRtz^ zm|m|(sO`K$!L31AcPR%_=(3)|CFU#P4c@Pa%y<9|Uyu#sX|0O<);)wFbjb(Hr%PXD zD(-hh|77m)YnALWUBBP_F+6d&;U4}{ixh|7V~5$@LGH*-(t&BuW-$LEoikE40o+UAg0uBD%o=UMFn}upWA_*{ji0mnPr1 zuaH8nyu#bshYNSC6`rV2ARR-W`;jdWRF8uM8xXd-LAVAmD?a>uGy9lqz`}JV_0*5I zX4Ir$h&u?q>M7kDGvOHP+nZ|Z>JZdmB-~tgvbzZ(pHWb@r)+ZHdj08H&Ah(%eIm%m z{H%^yI7N~gO0m~~d4F}Cr)>f1PU|_W0VdLmO88<*As-1w6qh=>{&ixogjs_25hC)kB*ka zAWES7)I95Fh9wPxgj^80nGl-2*jhK{T=N7KANm8mo*plrw(R@V!yR=971uxY^(x(o z>#ip~_AD_1;dCa-A7*+%dJe@xabQY>CrRs~iD4_K*h}P-FH` ztv=M4`S^bIt@`*;I%qLO0ORxTde6WqBNOaw#1fJ%0H*@=Ty=xrCk6m0)?WiHeiTGJ74wl7c<@@XcSkw)QI3;6Pf!Kq#m*85UyFp z4|Va*ZhD*K94=6muEs%!>z6JeY%VcEtSpFOZ6$**5xxAP`713{nz%VlHAUQG52s9j zX~*CilO^{CPN2fbS807L8F)dbK2)X97wTbWWRgM41Ia?-X_<2Y0jLCpy=D`l*igdc zNJk@`d_Ko;z;q97_<4Ky;FQeb7SUet^motvVNjliE%@;=SRxUKfe=jwF@!+oRoz)7 z?vVr|6VJ8vIWrp%$>JWlKqy6Axps5QS`@T+0Y(yY;hQT%PX*?WA`B}SC3cw0Z-|Wp zo3fv#66QG{CWnL&ms*%6CbD&iuo2*QuDRJFab+p$c3v1nb1TdGqc?m}1Wa@OSnM|q zQaRRz2x2zLM$n+y8TDNM;7a=sYWt4RO3~x&fft~jOMJ>OFbQXCE;?joRgkC>@S7-a zF&hM@EHfV~4cMQ2>qpkomO$bFiP=gLE_!_Gu?)GNSI)jDNhpcbm>{rY^|&nwRC{FbS($?Lxj z{~IgXt|M*m!M>BbRSdET!xb5nM?pdAs3yFc2tEldQD`MLP6e6eJrLn-bOKw5s#?pB z^E&@o90M+@LaDESjsxhQ2Es%M!LGP^+2(pzeFn>cJ6OBxypVI#p8%P>(rbu z8+I73ll{eoSsYWfTeswii zl154e)gDDQc5e18VoDe4e%9c%6ZIx{-#K!mSz6MIC5jR4P9k*2HMyq9=#LWL zHofOFjJwu?@J~!4&J1Ln2t=Fd2(#*-W|ws@cG+LZ`g#wa*_ql}5YcxoF}HDgSW=f) zv79iK?DrJvpZ7gGXXZ0U{f*ljfEfWoSAAbnzUKXfD1mqY>_!dGYo1hWU7cQ$h=WJ@ zeaVTm@G3d+=b#B@)zLEb$Oa@e3sO9uU%!N%b1w_|K=4v9FPA8UoTPcy{{~#PTMvqD zGWqei!RyYPY?=K zReFxQ*w$Jprcn6s>lC~_tK?W_xa{+A8K33y4oj5T?@GtYTcohddcp6=7tuBIXD+%D zO=7fkU3EMIXXeKyg2es3kK=GLyQcc4Z0_B>yj_K56N9$O?MthYe}C^EfDL>M)w!zJ zlw6X1Ubgn+<_mT2SrGO#j#EQYM171HW4S9L{@&Z_c>PVM+Y{e;d4%k8X+0i|e>yV9 zPyGIiq+9(1ViuCpyCET$9sEMdF0?oTaebquyGDwy`WjRF@%XyG!}f_Whj4aAQgvNu z-uzyldHNC9+o&}mVxqH9MKvYg>TpgO?BS2#KfDA@(tww*ci2pKl{U1<9-GwXj#HZC zlm(Lui~vD3&NyF`6IE<|83tMbai(X=BIX@4VtjxA3x+{T9IH4C&( zkT8as&+0BX9ZQ-jwDVvm1&X|TobIzjbzP3OMeY;zRd=}TxRG5`&wuXYS{24?4{kN6 zhMm!KweOc%ybjd76;EE(jG}h%z=D#??@vceGQ1uJ zkP#4kQtQ-n8E?2_o*rl{7di1JyS2}%L++yV=VGPEortI@ru_JSKO*2BI3Qmc(E42&SQe^=?-zeh@CV5G|Qb6k7Esy zhImt!JV)Kn%*#|ZeCp|aEG=(sB41n~V8#z4d1;UrS+l!kNbBGU^Xqc|@u+sax3sfa zqgAEHzoWfXX0xFr;7tlTMDxO2?@O567QrV>Cqp&R1T#~D@4CngRujWTYI_y)uT}}o*Q*>QwT~LYyVdmtQa1jjgRw`IhVL%Z~WF=j%| zKZY0O9ZVw--j9w3Hhz?FV<&SSl!lLRX)(aIv?JAfF5rJQ`|a79g}1dl7??_#OEbNJ zY6|;hK_jdMM_aXpu6XLH3N}9%JGoVVcy)4G0vE@9v1JYrnf_ME6zcY7p=uxz+Nw}t zLE&J&?UJM16tQX1u@r-WWfnT*apikvtCUJUC#e3_crV2l3KK;&H(xkqSNGt3pEjh5 z3=Ab)=v|fV8e$Ay08Y;1x9@AHXXt_}*%33YwkwyQMKOHr_ARSrwaNxcJ~N(+EiDvC zDULfa%BggN@~3xzWtIL+1J!gcV+S+pWljVvmnWIY`GtVm|8(A2@MdghYJ|)f$%T5k z{cF&og!qt2?fO({H0_PI3vG2U3C!4ruRZHMb}MK)9?jnL8Y$srYY~%`|S2bA>6uyP>u6+q`6GJ zxA&bJQi_T0U84i7lMYQRV9%8VOd;m_Vg(zEw$UcCoq%n?P<(lqe#={hL4U-)Z#IyM zxrq93!$=$qhGafBoQ#P%_6J;@kX^sQv0RmW129Q#IH016kQWMoGE$6vjP~E9vwzEo zWR0Z`-@Hu^*yC>zM?Y&;QoeGEKcD3a+63qXUT5Ejl8*gT&vQDh4?{o6-%?Kzk3(E< zhi==^nJ)jXd+gsaPT|M_Rux(A$NE;LK5Urgcv$G0bjN)2`zu7 z;VS;0WQrmxE^+xlgRv#p;p!ayf7ki@`}_Vp;|Sum@|T<3_}KZprppC|X=Fe^EsomDS$Acj=IZ{6dA9P1HI3{@fb z$*O=Q92ySZUt8DdDfFYlr+xl4@1ubH2D~0#u=F2?lbbSv*1|Xh|(*>!E;C>KF$qj{9!|Pq3$O&*rmlRR zxdO%kZ(K}hAz;w2HQ{Z#sx9!wESyEmQn5(Pe*@k>YpKWTli_2jj3nmWwl&C03g%%w zCI=G}Cpswoiv9O5{tXOzRpz}L^1IIb$A=j^d3FFshH~|b$L)6;K|K;yRAH%oOvGOV9E8BZx6Tu**sda`OzQLw{??#Y!Q2a`?5ZNMMXw2_fde4^QJ*y z`zL$-ury+*^u;?Wb{VRYa$<; z|6`9=*GYP~^7%Bp_Q<4BuZR(>1>^c?R<~iWYi+O=>+vuMq#mwWD<(V|udfe0OXrTh z2BuQ1&-L{zco&KMFdg}0JTs6Pk?W_3M?%Ofk~ETzb5Cx(1cPjM`&@wt09lGINt!vr zo_BUB{X-13bmZ1CAr^j`cA=*!kY*bEe^yp?c0yI=Fy+PLZg)4oseY6QerIB#!n;;j#lkiv&fAS#`v2OgO4<5L^nb)TUay zJ$$US-X<~{NTLXCb6HVj-bo09&N`#Fl>}zGCo&pKTIh>V0owJvFLqi#X|@iz6@$)+ za}Rnw-dmT%d~3gI4?x;}T>9kTV%%qyxA&?!w{=k`CONMfrKAZc=REgn>WhkD(rAM?pfI2sPs|BTtyipt!NIzb>cTq5!THl3j zlSF(h)BLM>{dmmLFtMWn{=Kg-QYEtK0Jas2&QZnj5p{p|y+)C4+@*rM=geDomw>`R zw6Edp^U#nEs+Ui_yWVMrPI9n>Z*EkdR6H(()oPHiF|{^gWqrZ#PpugAw!U+n6Cx3N zAfM`@qWF_5BL}Gt;%&6btl9~6|CubKE_)KVbV2F*;5RAcm&HH8 z8-nCshKrVKpN_Nj%klNB&)vT#+I}soLFS`iCH;X+$EH^zG&%BVTA!9l7TW-(dz&(Z zVS4y$bF1z79tp5F*CUn6VJki2vviBXFy)~+fc4};pQ`6l`5wlC@Yhf8DTv~il!$tC zd2}=3C18?j1W|_wixA<6GKDyg1rT>$R^#3SOSjWVW!cxb+h>!$D=n3n2)(6IQ}>m zjS|?`JlGHj{cn2BC&1kk{{>Z&vSl1F|(_MlR1})`L||XIHM# zLC_d4b}^iviyW_l0$wO>G@)?C<-X| zI>~F>yFmVxWZ^#wNA!2dy99VRiz2@ed|>GI_6Ib4UklZHR+^n8#xE<&MVYBHH{-E8o;Ym|EwC({eBS--`7C(!ti@d1$68s-eM2vLv?d9`ei9!a8q%^7ORpH(QrR7 zf~1XcApex-Z$~_P#9RWLxj&`v0Rl_gai;H7EMUIzw^tp_m_7W{?pk;_?>S^#QMDcb zsA$z;_vrxlQ|Pv1GgOS<+gW;E{eKc20_Z=0TSMHvY}~-%58HZy&Y7#zcN*`M`S{Uq z?aMvcVDCrf_bvkYT6~w37)iF31O~Xb%P3YF1klUHSAulH>9gKZ;OG(_b{c3kG@NAW zPSP;DxI~vo{vGY5%ec=zr^Y`lN8}pz?etcZ*uD;l&qIEys9rm#1mH)fpOjd?k62ih zouG}p3i4$`nLbcWI}hC=A51xlkoI66_&9O{FP6*XnId5(f4jYnmKnNE7{Cov_SfF# z(Af$377Z|f^3TK3LBnG3Td@M108=je`asNrE>d3(kw)Aj1CLB8&cfibC0797ca)r~ zY;u%AesO;e!YqhQETH4{)vW|{PF}xEy5zGazFyt?>|thBo8K?KA+ClyB@BkznI%yi zUBWu%VmN^CEZ3j>`t?0wC^ydi!S7RtGn};+2C*5{559Az@KZtcD)Ez!$BR9vkXT*b z`ufCo>)p6Pjssn`7ND3=`A0f-$`k|lHX_1E`2$@J`EiQ$WdDz>o3E{I3-f1zirYUoPW^>}7J2``}|K!Xc#!tC;2BzFGtGNBT z%Bk(ecOLOLJ77yKt*pK_kj%BMKZNQpVjUCw64xtTiY2+k?$y3b6ARuno% z`P|#Jr&YGn4jmWc+BSc$I`fhgSvP)SqH}fI)=Vy;jKi>mWzp+Z-8}j>b8s+W&d`rt zk6>9Ikj!XuBXC6_TukOZBxHLyzOs)`9)w32qy-VVBi)1imDx>Fbt$dqFogeox zmJ_flC%vcttPqttxPO2ZqZG)I7B;79=z;XS^Bf3Bl;lWRKs>_q!r+)gF(Jiz!rjM_ zjExVA%m!+la9w%h>na(i03brKlW5Y;{dEl1QhMcXDDV{#I=~e13qFyJ3gb^SAY7Lg zzNSMbFGP-ze9I?()klo5ALTd}OF~FG{&!(6Da~22N~dGxM4Z+i4{K2Q*E23Bm$1lR?ixMsv=#+n%5Cmy&sad}Qc9}Um)wI= zgdlQMQs~W$0&#yNkrAbBrKHkqrH2>_4O28Tk(bo;cP+aRczQ+@RXkYx;jc9d+PYwU(>oVeh?QEuXC)cEe_X3{t7fmk*MPTi1Qh{aUIWg25zGR4-*M^0IQO zfg9)`P}38{i=?z;6sfod7;8Z!X!Wm)wtkgb*Pbq{Dhr*%*_<6dj)E@h56OCXz`7pM z69FwLxEy^Z5wg%m=|4;Q*>(KaNR`zr4Y%}y{|Z{PSl)pWIr2@2(oqhk%nx$4=m8LZ zIV1}DKgwu!QnZz=N=4^D_+R+b>qaMEJEnA{l^z6oVFqg)?n0yB`H6Bw6Qv(AOGD(U zO)!a=mfgMD?x9eVBH?#)d#C{-3k{S$TDFIZtgQ9aNo*XR{Y7oDaB#8^9AqvBVMu$f z!z057@UadPf~{pRld_<92yi%uYijGY*vxb53lKnWdjZflnLhVDWdn{;>~gy7nAcpd zxdV4R3sY_en&vrUwaLF&&q0JecsB!&O;&M6f zvu!GeWg|v(rV;9BNKiz;`&(&YZp@hdzPznhHQ^|-pKHV3&WCkWYhPN19Q8Eh>0p?a zO)-zo+&LUssIUAk7dnRm$$SkYh9PD#f`Ij1LQAr|-o4%9H*unHve{7)7edNvnW&yP z8AFWF{=S#sN7hqC$XCA;WSpdN*WpE8&yknA&p|RIkawiad@Q1TNWhbni3lYZ#F6oj z$0?xkyZ<7K<$UD$_|uJ!i}j$z*;;tL@3YHK;ehR_5WQ&6H`w#I3zq?h9q z4*vp31DA!Pr_*Qe`oWREx&D$sE)}kwxw*K_+-BLs(D)Eh&7#lY+hhed%VWVXqvtC+ zP|`yNkc#he((KHweTu*sABkMEpbl=pD9yEl#adq<-(`YGN*Wsd6iL@qW`<*iReH>o zD7$P7f}?%nyz#_{Ax`^(D74pib2ggEnE7?{9;c4@>C&myD_ygQoh&&YE{xe3Sc~|1 z5D-IGu2Oo6p+&)-$Wo$2gT;&a_@C|w?r6_`4=(%iTsJiNjNY3pzzob0+2y>j#^vZ! z?Y3BV{WYN;!3!XCi>0}Nj1vmGe>-eIk;Ucwaaff$-eXc9TC*5nNudiF_t^$zGfI); zHIj#JyZ&^UGR7Et8jO@vkcm&+Kx3j%IyTzn2?{;{L)J>Az;+4DZJlgX;lYg0w2AoGNvv`og8LEm0Q)L*t+#Ni1m=`nsMk?5k z%9V;0jD-)iwY*VsNAiTar*pOvny9w|Z9ZZ>Rs>lVi_;Am_XAj97m&RAc2)l?!K?0v zQ%gs!!3NzfS!g@u4bECX1q=M8rUenSWTjx`0#2Y2Z1!X{xgM^Izs$BtE%s?bG^kmpMT4bqD9XAch>PujzU#%b}?c2yF9- zwTGl5U3Hs_YEoOUV^&R0dwu0Nabl>Jzh6F5tmww2f*Tz?U|L-0xZPPSOLPgmF2Ozx zW3-wtC97I2WFD?w@9@Plb^}PIMPw13ANKNpu!_UgxNdl?^l$CAw-w$p8Hvp+U$yPN zDFc|!%;M5c{FAOz%lBR!Ejbzh%zz|ZM~YGaC)qxwG>9AM5ME|uBr~RMy4e9<_yuq{ zXfYnMsROO47-MVM)+q5TbNe)vSUY9fa(BU()`K#U}*eU$XZ(@M!CPE2Gre=^X#KgZpsGKK#zCO-e=E8LgQZ% zBriR7Cm-|0Mw|^yePd=d`8^RsHc>Vg{O7O2?rJ^O6rAnO$<;r^i$Z{FE3YXxCHwNf z`89IBdm_!nWMZNi9yReku*U9_Cf60-DA~huwavDjooua8D@HxbCxnbKb`zK03gV<4 z_gOqN`QAW>BO_55Bu?1)hEhlO&%E8wj2zwB;rOb@3K|Wau=Uq`fwnzQnIQW zzPIOb@`{4O3MgN=Z~ViLWPTTiI*JSe&H**VHlLnjGoV9y?)~+u)Oxwm?@kp7c5khY zdRB|e$7YgsZvk0#Uoqu4@aLbyJy9tReO6DXWL0?chhkX@NG{Jn@M2cb9b2j%u(mLa zq|yABvLX?!!Z>e4aQ$mq;jD&(0J$qarSineLZfC96ZkjEbq~asE2dg@Hf@6A-Rq_N z8~j|~Dnt%j9d2!Zj(c1df}Vc&Q}|?($YWq5y*2YzqU$mBq~J;p78!9dM2-&OOmErF zb#4ca)jf3%I-3w-jGd4JC${=2aOgmp&!u=&`M&}V%s?*!Xk_q+7?>Q6KdW_%AKG4rH#}y>u~Ohg4ZW;D`+H05S87aeRY1po6VG}>yfO#i|c(CPEuhAceRVRB>#mi2?CAwT>|e!sw?LlneeK zmdZp=!wsmnY@Upt)~>y$2F}t(O=T`rz^I3rAmn1vaO{g0fwItoNjBS&>m51B=Q(;B zQ_M6|3+g7gJE7#M^5RAlAbWM+D#b1@d?80&Bt|SGkq!4Lp>KdH(7C(jyp4MgKYK5H zEM|vV$&p$>hB6wV(@-+Yw zO%i5FDkejLH6J9YT9ZQM`M`a>aI4G@3gp8|Mg4r?4g={p)LqEu`GLIP6vxUe+8euzmlE6;vlh7Q(q10E?#u!V@_ZxO~)B5@5Q znGzu0QpR^KC=Q=1ZsjD zIc~Gt*YopZNGI85CswlfYI~Iyl~IFk$ZfP{d%_Uijy|*nJP2`+#7)^aE3{m*^CLfD zXakg9GeVYObMleUA_c5vdDaJkD6xt)SGY~SZ_l1htSs|;RFnb|asgcDyYP{h{D~F4 zUY*j|s~|#Al4=tulr6o|ajnBNECOzYzWjna(20YjhYTKRs-AV6{KNf65Xg9fMA*n_ zGO^M4LU-Sx3)~O8OpGXxCrha#49R-zUgZNI)+0Fpv1$muy!To3z!~yLMNcD6As$eO z@(HX{yT0k2=t`Jlz9m& z)dB=K#Lhgn?INEl706Oma7oL14-+kN!CKV$^MPaih#Ar5u4Q%26wiNKqfMRzefE2T z7pPVkje!>FVJ#E`8P>x2RQIHL`5suLn17fm53e;|tK|UjSyJBwhJ&Q$Cd%!Lh1Doc zWHNU!gKKnRu0271QZsVKYDj4w7 zt`|vq&cY+_7z`#04ck0?C2djNJDOVr919#dKe~)wk5v>n-N>Il{&y?`*T*QO z!oW%;&g{m}ArdmX0A?V8M{-Lm7_1_P4y`v!ahJmBpeBv;4B%NnXlF)@j`d4y{;0R+<4(gWh&@{ToeQ-gmml|Ky}`hCfb zNKgiIRA1XLMve}_%VW*HPFv|+>Ja%9&&r|`n^FS!KhYW1=*d!mArvdPknEw}`myQt z3%>wR-Iy5i#ayaoYGST($M84@I)7G82H4;YPX?qxIT2+aV^}3?V$^8=`zxkdaX{wN zlV%=YB+<*UB11U)c1F!A0gkcnd;vDOe|(ZTnCP4cVWmCQ+!jFT0ZMkXOPm4F;a@~Jpb>V7PGuQ482Dtl>O)4Q_%LkQ&};1+$RhM2uQrU)few_rUL z{swi!a0z_=(xEm11$zY-f1|2Jd3byI(SbUt1jtG9|w1LsKCFNz2JQXCY_P6EwU zyo5WN2@_G`W8J~)$v}3@{soYE=6~~iPW`FLbo@0eC_VJ3&(%x0^A4V2bvIcZq>r@l z%{jOy;}gd6=;gs$G*@0#WkYR%zLu?E>s{kwCi786LTp2szX7wXJl58mU9+p!19?HR zH{pr7VW3jffIJ4){{BrszxZ#+?5!0E;(urgW%7$CsyBf938UP#h|qQe$#IxXQ)9LD zWG70S*{*11u#|B=dmQk@phwPBo!LEY-Z=69(FJa_pR4Ce(Z=B9YNvKZfgTl=nh%-0l7xk6ug|5^ z&1b-d?(`v)(~6mDKm|hycvxoP0*}-O)s{*8{>7c*=@Cwu|AB7MNKsaPYTmHIqu=#& z;%u(`N90HYtsMgVI{h0YW%9udhywND;nUfi#fg^aSlsU3X3Pwb$yOxH2AnPN-R@I>%-w?{0H(CAlZVQ7G zxHz}O8Hb@^G?(&-|EcPK0`x8?&>@Qz^CC2m-;|G@zHpxhK2$=6lGlzsxa8PNRO;nl z9lcAslaW9ec--8pw4!_v&{lizHSGm;C+?=N1B(w(r>SE&IOUUXk_t8TXA$-UTon5e zciolEsNZGx(fGP&x;q7LnF$|J=|i-UufEFan-a0!PB)NPt3xG*$ftVsRXwAN3)czTS_?2|f4LHKz=< z8QdAOpkKD<&eU=V`R)I1>uD6}AhX*1^%{uW9E1ZkwE6n z75HyT&qpCSW!|9ZOlEX~%7|y;+af>E@nOS+%DEnLw5ilD=M;s&wPcM62z2bHt zd|JxJRBf`9Iuv<%@Dl}4OZKN0f~#8sPU+?2zO{)x@7bA4nI%aCS-FTV)R~8!)ek$H znDld~xy9A)UC3rJhnZZk%iGLzQFL}zrcxUlCy(W>GwHpVdq0Mi3VpxFH_Y)s2MEsvI_Sg9dk)um;uRL5MZ z^y~O4B|KNYd{`|bA~$HmxS^UFs>XCynLQZ1wTvUl=;#QRWVtd1Tv4Dx0w|AAXEEKHlnRw__063 zG77q;ud7o1XnN#S@iBPeC zBZ}Pp>}=Aiz978sT(_b~aW_AQ>B(bJ&;rMQrQ{<1wKDtb;6Z$RZ)I=)&G|W2I_}~j z82FxeH%o+}kcXZr9Q4ehH!$Qvq)~Mr)uQAIbgVy6bNw9oW*0w5=6hU=F=SJDL&d$X zeA=}5lxa$NMfEm|H1*XDP&$3Dvi>OIq@IaQfAzV~@!w5>fb(yLF5a}5wN{Mk z&Qoy}f)*q~Ul6_BO3Sscg_jF`6;$^ZHaLu5sm%T=CyLo$UDms^w+dpjV}TML&tLQ| zCsovld%3-S=>=|hj(h*7i2M42(aGj@^!d>2mS5;(|^Sfnu0+E?NQR5ld9k(!|^!N z!^+TL@zs_KbC_e(*QO^HE$t#@$E$}N4#!KPxwpB zLg(h~4pHdd~A`9!3NNp(NQdRHP^w zV|6)=bOTO49lo7;)olCBHQI4q8{A5Z>&6j^pC!8w(ETLKhY+{M_GJ|Rqb6f;+~_Gx6)j31g}@VUu>J>1(0MJsT;VP_fvvCGric!rgq?t>PF9oqrdI@@BKD zlk(hG#KV#OKF)VoT~4-sX5fb%tAY+!`?yN84*_0eeQ1W?TOD$X#66zgcRDF4iQi7X zsHg+Duq8Qyywu~oPxj#yZs+y!+rH0(D+Qs3J;_ZUd4W=}_qa-&677F(dRD(|p=v_BrPTuSXH=N(kZaDln zz%n5fWf_<+8#i=Z!@k~Nfj@W|e0(G~)8M}5Zy1Ov^nsux9SDXQa#H-VPCwO>)btjb z^6}9N9N(MkE)6c7Inj&_BWa!JDF`@Z3kX^#-4YWffsOC(5bg)-R9TVzB*OKIki=U?jw3Ccm)dsSBN&2`SA3eU+0Og3@0`tHve4g zIkqfnrr2#xR&4=R5-Qb!@RtHKd`e;21q?wKkmQVul;9>C&-xUFyZMmZITN+CLA*d@ zdKG&mHvha%ICV$c)8?fxQ!;8YgG{C$!54%uzeCr!%;JR`Ux3|BM$_JS_A;Nd|0-6i zfP{w6nIS^4*{{BEeHPK&7C+V4(MJU*tp?4z5Sp9MOOe%D<^AmOc!%bg0)2Z zO+%{Vv$`OQp0(oiuh$&uuMx^^AXEb@-Njf_k(Cg%vnN7|dc#J}Ao}H#X3$47kVxZp z|Cei>ge?`lKgHmI%w9DFBR3L2xo-&b^(9sArzLyHh+00u$ zPiNVRu9W&@wBa+5v%mQsNYjW85NpfKJAL#W_4ETfnTt;){m;mMwVCcsU~@#+8f?-SJnwnj>U{&W`2VjH{YGQACf|b7 z!x_&$x>jB1;@gmAG$4)N^y~|b_tZXt&>8;H2D9r<`!Jh1!t3Im;@qZ+H@^m_Jwu05 zs<5F1g{^0I$IOYIeTS2ymoH%<^oMuc@DwY-SJ6%1-m=jTd?b^(m~u9H=A|$(J){g& z6p9M-#(|otn=gcXe^%R&UR4w6Ec^!zVa34$da2i=3w3=yr$+9&N$GcE>0*_h<=&=$ ziwRd_$>)Uj37;#_mVV=CIjc6d?CO7cUSByXLs;UaZA$sfCs9?pdkpH zzf0r1qH4W6^Yl%gnZ@hjElJRu5b(1VPfL}bJm|6tby+4!nV1N^M&tO_(CbEjA3aMs zCLA>I5(wb4SLM(iwX|5!+b>Qtf*W`rZT&a=Dh&+94;J0(UN2U8-L}>`b@RL;OD4}V zy$%R_1w8N=UtfBrw)wo6^da{%2unx!DQbz_;*FJwbgjtfci)PO;Aa=`b5;5h`N(2Y z$pzsQ<8O7x)mG%7uAB{s?w=QS7WI!0wp-G=Bf0_%KIHnT=opjhhn>Oib(#Md)V1oQ zzX|nt0}*WZG#V;|gD=SO3OVb&lN=C@eMi0^1Do_9Jkuv*NZ=b_sdHI2%Q5$ zW5YAJ3y`ZDc${uX!Z48W>GlCPa+u&buY*sN=5s-l#lV-TmQba-!c96Mw4*9pp%CJZ zvFLsFOX~Z@l}MnBK(}_qwFdG_f>p2S`I?`75A8I(%nT&uj2`4_k3;47{XBH^wVjEW zDho?V!qRfjul|r^3a!dI7qz21!DK4{hthempj1UmO)uRv{7iC{6#2!KD2Pzq3`x~C z=cT|YG+gWQFGfS?ZLrqb{noA^LRP}!zPjOfm1qtxc;H2NPU^h{|{%kc+$3#vVE`w`+(K_JPZot!a|E_ zOfMz4w8Ir>`Gg+2X2zzNnItqH7_U_TnzK~epLY{{`W+7XZ$SkYYKYDH!O|Ar&`N*t zH@7e3&?GSYSm)S332MF~#-o!p7zwsl#V`ny41Okxz0Wdm&@k2;*IdL;Q5L!ch6{jW zjx5gZfvXLEXOYkSkD^r(&McN0xgE@BqIR@Zff)t@q9@RCum~L<)ltK%-UM$G5~_3ZzNn27_2v zQeR9K4?Ow0IaU4v_c(i?bvWhd41@yB9gqp9@(8FYOkL6qms#Woft714?U}jIWPeyciEbpiB_9cCQ1cQeD$@RyxRj#<9r0{)XM@_#`mi zgV9v7S^#*IAj;e`NtQblQhw0MRbKlJ2CZEKM7ol9goqk@*7R{(-PPQaQv?X%Gv9qI zC8vage&ET~2zo$n3^@AMknpy;>^Tir+kdTyJ`MXm#YE3LQ->4;08ynb!$^8A^Z&Fe zyFqbqsrt3G;0Mft8*s>M;gndq6iuN`&Qi_WC<~ZK5YIN>@^_6h5U3+&9z+rrV zhFrUcf7hY>*)-5)1GJr~H#*l<&0f2gA^e5>bOa4JR81%qtF@_%kaxD6k1T~xj9=Rv zfsH|nCzGZS40t(UQ{A*m&(+Ur{AM|l!i-#QTCNN5)d()6x_L$G!h zpYl@j+{e-3>}hBQ4z^-2xKQ-Stu;NJvT}4N9kUm(pD#0!pWVbi;S>oO{nX z-h2Oo-|u~%y`Oi_%xBHa+G}Rd%v!T1`yOb-VC1iFF+*!X6AvaBav-Wq?#br%BIZYZ zd<+9A2Eat{DH&x!9AJG>{UcUept1GAT}m2Q6iWebTE4(Ida-Rm@w;?#Ae}U#H|-V^ z+3Amj1=VMfmDN$!jt!%9mJD_d`0X_%Q}N~MbSxtDpcD* zyhoK_Q5eEe5X)->Q6DQ)N`~zuuRE2s;wAzYHKSm3YMH`B)-0yKm<;qX?=N<>22H3j zlpG-t(R?HZu*w~@jEaw;QBppS!q4-} zk|2ZUWz#Dm5;M3I|a=o$>~oo8?4zTxZa|z;IVcUJ2bp0Z2V*2q*R#(`gAd4= z&pkXxzu~}xCuJbIy$gw9fmuQRCkfa_`(u8FehRTa|KAbE3i6IcW(-aywNBMP5AH`} zJpl+QK#*nvr@8yjRVViF^L$Od>`w41v?WDhL^xSG3W4tbPhM}Cm>w?3C<30fYvQPk zI^@xMM!q}D0{wQB?IJA%QWEPQ(E12K3{nyuDZn(ZEYd&VhMlDYNVZ-l=q@481YqEO z*$cmUej~kMPeY>P+sx|5&YWQZ`GF2&RPnf4a@bINj~mE4FyP=bhf*E=Sq~20yF|cx ze3UzmEKtTq1kD{(XdHOCVKz@i48+L;`Dfw5qI}&m(#yIAgHy;9Z zb{_fBydpHC?hJyAC+iu z!&P|{YpAjelGuY?LIHA79n{D}vjE8BT^C~+OM+PLsh)2JsR@_aQkj7xzJeP^z(_#C9OHfEm!3=4v@AGg1_z87TkEEw!0)Y(n)ycqRER?=o`RR}-YXCA55 zUjbEN1zXmOzp!=Kkhk^51l{FNi_CEL_8KScrFu4aVnkv%<4-wV~#BerH4Yyy3+`x_(*(X-?YzNZ;$Oh}cF5s9uYUDt$ z9CQrr+}J9v>0L=_W8p?rsC0lu+w@r*jNxqj)E%HoDzGq8J?~|`!(~rD`w{&>qVpvG z6^sKT<-ts7C=7f@;rt{C0Cmlhgbh1!i3#K^EO&kny?AoRQs99hRm9hsxAmcBbZwSD2Ns-@4DLii4z!>ac+MzihQ~^}i zpn!zc&;0q5Z)w+{Od#;i+@j4jR3)DUqxcb8KxT_ zcI3D+{`CsewjTHR2;VX~mlV5z)Xx;k;lcYHp^8Dh`f|?hx3KCs? zMA9}{_AtoK5&nWeMWKi{-Vk{A^2Bk3xwAY@>A7OtA_Ii9i`ihh9wpz|^3g27Ms1Pt zAKca0akY`+jtZU|=&Ag-$EN(n**kI@e$n#<+6fTFHGSvpuFQ}gHVn_oq2cNPXsZ3? zfx@%2ad^Ms@tyP^TYHGcjQb6TvjOYG1G9G8Nw=tE8Q*jhiFD9sEnL)aW)%dye!AlQ zjxxk>cTe~mW_{Kw&Y;IyW|!a#Aj=T0Jbjoh`HoK-AoII@$2Js)ZIZwg3J8*_9SXy4 zq#uzVYw|*6RHD&ZFK{ye5yk$BrJ^7WJ<7k^1mrJYdDld<6lf9gl!BejzBM&{h&d=R z2xzcE22DT|`UH(3Z|br~1eLBHWq>w$Z&>)X;H4lS&!EKtBL&v{l@)$Da9co`%$Ha{ zJlCG;neqbBD%ERnVIK#`K4v~!Td*b*Ay1jQV}@=v(8u)=tKSLMd}Y7? z(zlI}#q|K>ve-h<7x>%jN{K#)p<{Y(4a7RDsf;z&0K2Y;p ziL7@Kf@$mdqlK`$?d2vJ>o2&u0h31U0_)SfMn1Ixw`&4m@BMB|Q=PCJ^yHK|4P>~{ z9ImADr(3WIt9E?S7xf>6<*Ul6dhP^c6m=biVM_>?BU(h{dZecb`0`Lz95lXx;wwdf zck2JG04}WWt)F=m@TP~}=E@VV3EjKB5)oE}?u#9Fm_GtyktK7tGIc9r3(3JFIJ++n zs6ynVlqAcd;d_bi_}X< zXx#o{cc;9qnV(sJk+#4MH_NioO#hkksaoy8c{9^VHQhYEgB9FDVt9&da@c$`-BPQA z1$?<4mCi6DU9`)z(@)p;i_;SZTmv!2T20P(jLUz&nKGqTJt7uE_`KJ*wpSDXuD?Q~r`?K(yv-A;3HMHqO zKc|sjj)W?;mbahQODe*z^^Uz8=nb64t{*{i6ccHSq9);AGlKSg?4RCWz{M1xCs^D= zT?@!!0_O)xdL9$?AC3;acV&YcFTm zClKpE;~=*BQuIxB6_L@n{LRgmU8{G>1&R3HRI(2Wzf`~XZ&ZR(!~JRmbT32L>26 z6TRje1)^hbNYt(in@{=}KYx4lp#E$$UL{pzhxD`?a!+JKur2@ntMF6$^B|N<#Q1D_PY*}iU0hb$a<<_~%!_oxL{34<{$aG6 zfH)Hka*Le@5yw_Uqnt}6>3rcOvIJh)_+qREGA0j2#9~JeKj7;?KZvYtAy;Df(AQGE z#a&E5-(O;n=w>}8+L!QnS!ye`B^AXTa38h#QxTp6YOETHmA|Lp;BnXO8Rri-* zzekd&Ee%~i&#;luLaj#eVX72IO`CahTYlz7k_U1-W_Zlk94xFVrvz9u~TE^sZ&5oRVD|7Q@76^O|MZpV0WCmY7ij9!=!UYYlfJ#|h;-aw>l_}mXG z^tti=wS^3;$y;^>lRX-|hP8#qPe{8&9lgjz;4gKpiyH#uEi{U!a+$+hosvWgOUe*(PlcIevnPIiNrJ%P`M?vZfnNOb?+byZ;d!ji;|CHWFNZRyG77|x^ z(BvwS5CQK#aafv#Zbi{&c4-?&%UDVZ(>WCbz52Qum~Gm61_mpc2I6BF!?V1icV zyNxkMVj)FJ^P>y?k8Q6N%Y&3>=7cFDWd}AUv|m@mvq?lif&>0)3~c+?y|1Gt<$vgvvASMat39M-#YmuV?E%6Cj56dDa=Pn)ahO zex2^#(faY>!uf-dl=!N(SxX?kfVgqp!7}29*TSNbaE14gleBE3i4xQ3TSByQk#ot- zNGSD!@Vd!Q6-~J2px0jmB>ec(hxwZy)RSz5v!hgsIYHY`!#*2$+>EQcp}nV?1g`;qr{9NeEdgb~0T|*?@^@ zC~#K|65?;%WRA-IVr}+ZQU6V3CGx#niBNBq`B%LCt{9m0g8tH!;ii7$5o|j9IXJE@ zdLIJAr1?!HX0VRF1iw<%LSV6qcEUWYBujnEd>1ne2dO`$5^H&ep;IGXDV!N*KK?nql+k)y8-?7e>RL# z0d`&BA{=rM0D*vs?4Pq(SE%hDhi86Dmv@=v!|B=6tNH#icVhQJ%&G+)_i+63C(`M! z6eGPeX%BEJ+3Ap3C_e~g`t4J}7b1tD8&@WMqM8wboj>D+wEjF~W78q=&6n!)^b>yD zB9tu26%Fk?=sE104k_l{qf^z15q$7)Wb=F_Au-NS`0Cn0M5K+acsf2Y2C}kx$t;{% zQe=JY_n2iFe@ON`vD}K)ofR4r^W=(|BA@tS#@2CavW1P>2Y7j%`)S&E3qpjtLgyl%H>}U9BE1j?>7KLkOuU1*i-O)se!%+bY(rSUbD)jYvo?)%n`2TpXc5*EkQM97 zWFY3x9E9FH{b1?$khh%mVEj3D(t2EYKn&MK3g?4oB=f`c>d;nt-IXQ(JsP1*vMt5c z%$?w6^MPFE@z1sKE73zdDQykSUsz`{i+PhgoF@Wkb5xj4)-IGtloS<@Z?&$co7yH{ zZyz^er8E^V-K@H`$~+H7ixH^bRXzJg3@vcBsT- zt1L=9ePcr=ed7#VE8sc;*AH;bM?a3~yh@-qhEn0QJf_G%P&suXvzBxrGnQ~6(-n6i zQxkI`dm`#WhE6`@RBgi5Gp2x@7S>ayWyyU)En~B{zPm?#Re#xZPh|IoqCL7vWbxAJ zbln3yx$gGNXSQ6|U&e7~6o=@J)x6O$h}4>iJ!D+Rk7T@flp*chF>Z2vUvOq~j7pXk z&dAlAlYHM&S7CDRnnm*UWSdC&${_2~fMp+r4aYf=zGKRZ{D+1Up){}424BGuR2QL! z$oPD96-%nlf+|Sx{BRtNU*+oGw#Ck}E9*01Cm4!r-?a%9ixr>ElG87JFZfm|~ zE{8_n;Dgj`_&=}6(^)!U&~2Ja+^f_^mxxXM)$#=WP~w8yn#-&n>`E^1NcS)t|qr8H9;p$()gmKefJsT*W=61C>rHvxUy;@OhNSoh5Jd6>7m zD7h#yw{|`8R{BTF-EzxBin7ZpF+xa16uxvuQc@LIhV6>geupH?cSe@~AYj!nV^dd4 z9E74yr4kl3qO-aB_9)+=Mdvp^{cOdD$kT2_Ve8-Vtasw^z`z@Bt!96Gt>)2V^e?7( zZ0)rCU6YuJ zTcHw59-t4-j7=`{Od0J|rLQYZW*eEN;Yr%;*l86@5_0&mTokred}7@e$uIQ9W=7#l z-QY=P#lU+0T5h6Jm0n84V;d9{!#qAA68jR}qeQ2}9EIq8bD9rk&_|;<8HcWItLE{| zW>B8Hhj~%?+zFNeVId!?E_DkDzHJ+k9kP+Jv*8O}Rhv2J*Vf=kU3cz|s#dLP+Jp_N zsMUE`qV(q}PP3S}(X$lqsCeP2>3Pj6%H~3aUtTMEWq)9IB;wvbZ6=r@2^qW*i_16US~P*IRnMI3(3Ks*SqTIJ6m%{J=|HVw;Y zT*m5CY{vIzNcd`hkoz~}?lE5FWkZt61Nc9wjEZaWqRJrQe_eisvg7*-<)QH3N&c%q z4`69EjQ&=}(AE>S1*?2F1J zy#;Rv((mM12gIn@Eh{WCOi;4s+&*U3_^y6NPnb-T6+>+1`lAJ4XbVQGTM{(15 z29X$DNuaBK%MNB}Qwh|0^!N_KP4io3MQoE>jQv^#s@S!IPJ!$LSkn*mg6mgoiM<9B znQca9Vx@kjv2;A;=I9wp{PDl5GG|-JG zSiQ38ks|rL*ekGT^$2Qn&eW%6v;TUTX_50B-gVn8SMmkSXUNpcX}Kna1g=Ed*MnY$ z=2eUp4f@3+1!$*fMM0_FU7Y^kneB6jEAHoLwLNhtXc=NLuVG6vl;#px$;X+REwdiU z6XLmyT;hSZPa+=JF1cQQY!#&)>1n;QdOFgiaBdmSoO&xfSq zHjL0nwqJ5s5;jyBk4kzephcx|uqz{685%=`_-*X3B$RpVznrWXR#0W1gy3giw?=Ds zo_{#MN~hL)JMy${?|D)5(>>{~{ZgBdDn*z1_jC5&n+Ej3LFV~`ManyhRgJT5wFnbr zCkZO?7BO4?VIpNN4_hs&mazdMK4`bA5$b4>J;b;<-t`#Gz|uU41)6EvWrQQZnAUKOLR3OQ7Vi!wJ?xa+a+cTfEWg)Luuawgq5%LaA zC|DxMP0r`Itf~W%Ms4CHY2M8w>dO#6s@masodq3~wSAmu*Z18yR_cw~unvq5WRk&Y zES!Rc4tP_6ceIuk5GC>{BzCt5wFovJy~i&k`YhXoSJmDk-XW}rB+H5otGr0;;35W` zxOo|qMrZXodWA<$OhnB@AbSmA#5ok|n2LRm=!IN{!oA)@;kKm(vWgWBIoy-}w~6KK z_d7;}7?*VLVH%t)ItL|n(A0Ug;%%S*K+Z@y*Vb%h@HJRTN@9e|^Qv2|LsdNMRO{DZ z9oy88@k^ERzQ+-#&xx!6rQ}rbkTXcfC}4|3^gwa8v}uRQwf@FY$4yydNN!ix^=MKw z{a~O*!b%k?FjmW@rGCE%Egk3EH+9W`GmJ8axngemp&D*XN_a;Sh)6)n3uZiO24Zs! zk@&M{DX^rBXjwA`kL}LXU+h@E@m}V9 z;*#Q3n_TUPjqZW)SAAa^iXef_c1FsAiIB9`^5jeM-gCLvIp_1Z!$_VAP^qi^o546vydb)I#KM|YEIqJbvJfyyFe7iHYGE^+k%G)5cT+RP-E)JXHDPKjo z9ztKN=uhT6B$T`*;X}9u#M+u?Ea{FdJPO!im+`dU7hv)*%lS22RxBB%!*$SB3&V!; zQWMrai>YEMB=0X0y0Phmr`Qr!)$`9~S+fVL28h01dy9Fc92n+{NE5KRQm$-9hj~j{ zXoON%LNwflt3&7@3)OTvEA`Y~gLY3ltUWbu)KONb*a%#}Q@t@p{26ytS5|JG-C z132fJDUJL+XJBfD!idqO?O^ybwN$MFr`e{6M=wQ|L}uDHjIN2En4W`aeJ=m{D!)Xp z3t|j!dTl@M;_$)TgB3-@7{aHe2a@$w%}edjd_e~!Xq5hxw^9b0N8&pAgAq!}jA<{{ zK1U1a4c38qU@xj}Gy(B0vjfkoW+NnM*@RbhFNo{FI_MNzIWLt7N3-eW{GN<2X65$C zosxc;^?LL>^Sro$2Yr=}9kWL*Mp2pOK=!t&WAJkhrPEnEp?>$J%nvT%T#%mF*;Z83;F-x&dM@OxSd4KC_wvkk@_fU${w&AI zy5;XTT*H-&Vh-$()tyOH&hyl9tr4TP8~m1=WVki_ zzMh~KxZ0u@;3JgToQt7q)U1-ZdSN_9g&R1wEoP^-chAit0he^-j+6g?!Q}d7sGSf*kUYh@asph0*l#CaP#Yv8X3xG;-60 zGs5rX;>UhQ3_YHYo4Zb5Dg#_7-lF&$q?nB((K5Ro@8}xKio2>wy&sZwJ8=}S=U&=h znAMe)B6xWJr+UHSgCaAJHVX$oU$NIM9!*1HdoOq6NJ zz0^Ai!>~u{Oo*}7CbGx^4;38=>plos-BcgGb`p=za}V?}eiNeos>)ScI$=c;uYK^7 z9PtaPiU(=E80Y*GaNU>@>XEyf1lO(%Ju^Y=WkCY%6>!&D8R5IX#a=QK^^#uZNSZ2U0QXN%sUpiWH(5BSYM6 z`X_{zVYhuH6zEZZ3~5(ErQ22&lz%v4(z4q%F;>4cRVx{@;X2phk*j?xqdL@v{Mlp5 zgoi&Xo!(Y@T}3~}BNw^=2K0Xn0bpQRfMvkHOr?5EWMboi_T@V;u(~rF`{z$j0yGFS zdsjmZsH2&S{@s{BMgE@zC}K$J_5ex=v?bJkpof4$$I#i#)`gAr?*2Qltcn_22IMS> z0BkJ(0o(y0zf89NKm%>2n;j^`UGD#tlHf(zt=U5eLYViZ@(!0oyPA4 z*+$!$><6W%1jWbt3k&|CaQ+FmG<$Ap=FIWm>TmB9T3-RsF#vrxiu&sZ3$~j4%G}(+ z!PVZu^^axy-*cDy&{C{U2jY5#j&< literal 33464 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 ^uO*R$MiuR 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 DKIzED From 215444e46ae19147146630bfa68585455bc7c0cf Mon Sep 17 00:00:00 2001 From: "to. sandra" <76515860+sandratoh@users.noreply.github.com> Date: Thu, 14 Mar 2024 15:55:45 -0700 Subject: [PATCH 004/153] Open alcs and portal document inline with standardized method (#1513) * Update ALCS CSP to allow file document storage source * Rename function name and update bg color * Update portal public documents and decision documents * Set browser tab title when opening alcs file inline * Change portal backend and frontend to retrieve filename from existing logic * Rename function to match alcs * Update alcs detail page open file logic --- alcs-frontend/nginx.conf | 2 +- .../application-details.component.html | 4 +- .../application-details.component.ts | 8 ++-- .../notice-of-intent-details.component.html | 16 +++---- .../notice-of-intent-details.component.ts | 4 +- .../notification-details.component.html | 8 ++-- .../notification-details.component.ts | 6 +-- .../application-document.service.ts | 11 +++-- .../application-submission.service.ts | 1 - alcs-frontend/src/app/shared/utils/file.ts | 25 +++++++---- .../application-details.component.html | 4 +- .../application-details.component.ts | 8 ++-- .../cove-details/cove-details.component.html | 4 +- .../cove-details/cove-details.component.ts | 8 ++-- .../excl-details/excl-details.component.html | 8 ++-- .../excl-details/excl-details.component.ts | 8 ++-- .../incl-details/incl-details.component.html | 8 ++-- .../incl-details/incl-details.component.ts | 8 ++-- .../naru-details/naru-details.component.html | 2 +- .../naru-details/naru-details.component.ts | 8 ++-- .../nfu-details/nfu-details.component.html | 2 +- .../nfu-details/nfu-details.component.ts | 8 ++-- .../parcel/parcel.component.html | 25 +++-------- .../parcel/parcel.component.ts | 8 ++-- .../pfrs-details/pfrs-details.component.html | 8 ++-- .../pfrs-details/pfrs-details.component.ts | 8 ++-- .../pofo-details/pofo-details.component.html | 6 +-- .../pofo-details/pofo-details.component.ts | 8 ++-- .../roso-details/roso-details.component.html | 6 +-- .../roso-details/roso-details.component.ts | 8 ++-- .../subd-details/subd-details.component.html | 4 +- .../subd-details/subd-details.component.ts | 8 ++-- .../tur-details/tur-details.component.html | 4 +- .../tur-details/tur-details.component.ts | 8 ++-- .../edit-submission/files-step.partial.ts | 8 ++-- .../other-attachments.component.html | 2 +- .../parcel-entry/parcel-entry.component.ts | 8 ++-- .../review-attachments.component.ts | 8 ++-- .../review-submit-fng.component.html | 12 ++--- .../review-submit-fng.component.ts | 8 ++-- .../review-submit.component.html | 16 +++---- .../review-submit/review-submit.component.ts | 8 ++-- .../decisions/decisions.component.html | 8 ++-- .../decisions/decisions.component.ts | 8 ++-- .../submission-documents.component.html | 44 +++++++++---------- .../submission-documents.component.ts | 8 ++-- .../lfng-review/lfng-review.component.html | 41 ++++++++++------- .../lfng-review/lfng-review.component.ts | 8 ++-- .../view-application-submission.component.ts | 8 ++-- .../edit-submission/files-step.partial.ts | 8 ++-- .../other-attachments.component.html | 2 +- .../other-attachments.component.ts | 4 -- .../parcel-entry/parcel-entry.component.ts | 8 ++-- .../additional-information.component.html | 2 +- .../additional-information.component.ts | 8 ++-- .../notice-of-intent-details.component.html | 6 +-- .../notice-of-intent-details.component.ts | 8 ++-- .../parcel/parcel.component.html | 25 +++-------- .../parcel/parcel.component.ts | 8 ++-- .../pfrs-details/pfrs-details.component.html | 8 ++-- .../pfrs-details/pfrs-details.component.ts | 8 ++-- .../pofo-details/pofo-details.component.html | 6 +-- .../pofo-details/pofo-details.component.ts | 8 ++-- .../roso-details/roso-details.component.html | 8 ++-- .../roso-details/roso-details.component.ts | 8 ++-- .../decisions/decisions.component.html | 8 ++-- .../decisions/decisions.component.ts | 8 ++-- .../submission-documents.component.html | 2 +- .../submission-documents.component.ts | 8 ++-- .../edit-submission/files-step.partial.ts | 8 ++-- .../other-attachments.component.html | 2 +- .../proposal/proposal.component.html | 2 +- .../notification-details.component.html | 6 +-- .../notification-details.component.ts | 8 ++-- .../proposal-details.component.html | 4 +- .../proposal-details.component.ts | 8 ++-- .../submission-documents.component.html | 44 +++++++++---------- .../submission-documents.component.ts | 8 ++-- .../decisions/decisions.component.html | 8 ++-- .../decisions/decisions.component.ts | 8 ++-- .../submission-documents.component.html | 44 +++++++++---------- .../submission-documents.component.ts | 7 +-- .../lfng-review/lfng-review.component.html | 2 +- .../lfng-review/lfng-review.component.ts | 7 +-- .../cove-details/cove-details.component.html | 2 +- .../cove-details/cove-details.component.ts | 9 ++-- .../excl-details/excl-details.component.html | 4 +- .../excl-details/excl-details.component.ts | 7 +-- .../incl-details/incl-details.component.html | 4 +- .../incl-details/incl-details.component.ts | 7 +-- .../naru-details/naru-details.component.html | 2 +- .../naru-details/naru-details.component.ts | 7 +-- .../nfu-details/nfu-details.component.html | 2 +- .../nfu-details/nfu-details.component.ts | 7 +-- .../pfrs-details/pfrs-details.component.html | 6 +-- .../pfrs-details/pfrs-details.component.ts | 7 +-- .../pofo-details/pofo-details.component.html | 28 ++++-------- .../pofo-details/pofo-details.component.ts | 7 +-- .../roso-details/roso-details.component.html | 4 +- .../roso-details/roso-details.component.ts | 7 +-- .../subd-details/subd-details.component.html | 2 +- .../subd-details/subd-details.component.ts | 7 +-- .../tur-details/tur-details.component.html | 2 +- .../tur-details/tur-details.component.ts | 7 +-- .../decisions/decisions.component.html | 8 ++-- .../decisions/decisions.component.ts | 8 ++-- .../submission-documents.component.html | 2 +- .../submission-documents.component.ts | 7 +-- .../additional-information.component.ts | 9 ++-- .../pfrs-details/pfrs-details.component.html | 4 +- .../pfrs-details/pfrs-details.component.ts | 9 ++-- .../pofo-details/pofo-details.component.html | 32 ++++---------- .../pofo-details/pofo-details.component.ts | 9 ++-- .../roso-details/roso-details.component.html | 32 ++++---------- .../roso-details/roso-details.component.ts | 9 ++-- .../submission-documents.component.html | 2 +- .../submission-documents.component.ts | 7 +-- .../additional-information.component.ts | 9 ++-- .../application-document.service.ts | 4 +- .../notice-of-intent-document.service.ts | 4 +- .../notification-document.service.ts | 4 +- .../file-drag-drop.component.html | 2 +- .../file-drag-drop.component.ts | 6 +-- .../owner-dialog/owner-dialog.component.ts | 5 ++- .../parcel-owners.component.html | 2 +- .../parcel-owners/parcel-owners.component.ts | 10 +++-- portal-frontend/src/app/shared/utils/file.ts | 8 ++-- .../application-document.controller.spec.ts | 1 - .../application-document.controller.ts | 3 +- ...tice-of-intent-document.controller.spec.ts | 1 - .../notice-of-intent-document.controller.ts | 3 +- .../notification-document.controller.spec.ts | 1 - .../notification-document.controller.ts | 3 +- .../application/public-application.service.ts | 4 +- .../public-notice-of-intent.service.ts | 4 +- .../public-notification.service.ts | 4 +- 136 files changed, 544 insertions(+), 574 deletions(-) diff --git a/alcs-frontend/nginx.conf b/alcs-frontend/nginx.conf index 1da2eb30aa..6ccc98d57e 100644 --- a/alcs-frontend/nginx.conf +++ b/alcs-frontend/nginx.conf @@ -19,7 +19,7 @@ http { add_header 'X-XSS-Protection' '1; mode=block'; add_header 'Strict-Transport-Security' 'max-age=31536000; includeSubDomains; preload'; add_header 'Cache-control' 'no-cache'; - add_header 'Content-Security-Policy' "default-src 'self';img-src 'self';style-src 'unsafe-inline' 'self';connect-src $ENABLED_CONNECT_SRC; font-src 'self' https://fonts.gstatic.com https://fonts.googleapis.com; base-uri 'self'; object-src 'none'; frame-src https://alcs-metabase-test.apps.silver.devops.gov.bc.ca https://alcs-metabase-prod.apps.silver.devops.gov.bc.ca;"; + add_header 'Content-Security-Policy' "default-src 'self';img-src 'self';style-src 'unsafe-inline' 'self';connect-src $ENABLED_CONNECT_SRC; font-src 'self' https://fonts.gstatic.com https://fonts.googleapis.com; base-uri 'self'; object-src https://nrs.objectstore.gov.bc.ca; frame-src https://alcs-metabase-test.apps.silver.devops.gov.bc.ca https://alcs-metabase-prod.apps.silver.devops.gov.bc.ca https://nrs.objectstore.gov.bc.ca;"; add_header 'Permissions-Policy' 'camera=(), geolocation=(), microphone=()'; add_header 'Referrer-Policy' 'same-origin'; diff --git a/alcs-frontend/src/app/features/application/applicant-info/application-details/application-details.component.html b/alcs-frontend/src/app/features/application/applicant-info/application-details/application-details.component.html index d2d5a050b7..aa03160e66 100644 --- a/alcs-frontend/src/app/features/application/applicant-info/application-details/application-details.component.html +++ b/alcs-frontend/src/app/features/application/applicant-info/application-details/application-details.component.html @@ -81,7 +81,7 @@

Primary Contact Information

Authorization Letter(s)
@@ -226,7 +226,7 @@

Optional Documents

{{ file.type?.label }} diff --git a/alcs-frontend/src/app/features/application/applicant-info/application-details/application-details.component.ts b/alcs-frontend/src/app/features/application/applicant-info/application-details/application-details.component.ts index 67eba7fd21..6dfaa088c0 100644 --- a/alcs-frontend/src/app/features/application/applicant-info/application-details/application-details.component.ts +++ b/alcs-frontend/src/app/features/application/applicant-info/application-details/application-details.component.ts @@ -47,8 +47,8 @@ export class ApplicationDetailsComponent implements OnInit, OnChanges, OnDestroy window.location.href = `${environment.portalUrl}/alcs/application/${this.fileNumber}/edit/${step}`; } - async openFile(uuid: string) { - await this.applicationDocumentService.download(uuid, ''); + async openFile(file: ApplicationDocumentDto) { + await this.applicationDocumentService.download(file.uuid, file.fileName); } private async loadDocuments() { @@ -56,10 +56,10 @@ export class ApplicationDetailsComponent implements OnInit, OnChanges, OnDestroy this.otherFiles = documents.filter( (document) => document.type && - [DOCUMENT_TYPE.PHOTOGRAPH, DOCUMENT_TYPE.OTHER, DOCUMENT_TYPE.PROFESSIONAL_REPORT].includes(document.type.code) + [DOCUMENT_TYPE.PHOTOGRAPH, DOCUMENT_TYPE.OTHER, DOCUMENT_TYPE.PROFESSIONAL_REPORT].includes(document.type.code), ); this.authorizationLetters = documents.filter( - (document) => document.type?.code === DOCUMENT_TYPE.AUTHORIZATION_LETTER + (document) => document.type?.code === DOCUMENT_TYPE.AUTHORIZATION_LETTER, ); this.files = documents; } diff --git a/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/notice-of-intent-details.component.html b/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/notice-of-intent-details.component.html index 5262069026..771db3e5f7 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/notice-of-intent-details.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/notice-of-intent-details.component.html @@ -51,7 +51,7 @@

Primary Contact Information

Authorization Letter(s)
@@ -68,21 +68,15 @@

Land Use

Land Use of Parcel(s) under Notice of Intent

-
- Describe all agriculture that currently takes place on the parcel(s). -
+
Describe all agriculture that currently takes place on the parcel(s).
{{ submission.parcelsAgricultureDescription }}
-
- Describe all agricultural improvements made to the parcel(s). -
+
Describe all agricultural improvements made to the parcel(s).
{{ submission.parcelsAgricultureImprovementDescription }}
-
- Describe all other uses that currently take place on the parcel(s). -
+
Describe all other uses that currently take place on the parcel(s).
{{ submission.parcelsNonAgricultureUseDescription }}
@@ -184,7 +178,7 @@

Optional Documents

{{ file.description }}
No optional attachments
diff --git a/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/notice-of-intent-details.component.ts b/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/notice-of-intent-details.component.ts index a49c553730..81f34cccb2 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/notice-of-intent-details.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/notice-of-intent-details.component.ts @@ -47,8 +47,8 @@ export class NoticeOfIntentDetailsComponent implements OnInit, OnChanges, OnDest window.location.href = `${environment.portalUrl}/alcs/notice-of-intent/${this.fileNumber}/edit/${step}`; } - async openFile(uuid: string) { - await this.noiDocumentService.download(uuid, ''); + async openFile(file: NoticeOfIntentDocumentDto) { + await this.noiDocumentService.download(file.uuid, file.fileName); } private async loadDocuments() { diff --git a/alcs-frontend/src/app/features/notification/applicant-info/notification-details/notification-details.component.html b/alcs-frontend/src/app/features/notification/applicant-info/notification-details/notification-details.component.html index 86f34b9c41..a98da9588c 100644 --- a/alcs-frontend/src/app/features/notification/applicant-info/notification-details/notification-details.component.html +++ b/alcs-frontend/src/app/features/notification/applicant-info/notification-details/notification-details.component.html @@ -18,7 +18,7 @@

Transferee(s)

- {{ transferee.phoneNumber | mask : '(000) 000-0000' }} + {{ transferee.phoneNumber | mask: '(000) 000-0000' }}
{{ transferee.email }}
@@ -76,7 +76,7 @@

Purpose of SRW

Upload Terms of the SRW
@@ -91,7 +91,7 @@

Purpose of SRW

Control Number
@@ -115,7 +115,7 @@

Optional Documents

{{ file.type?.label }} diff --git a/alcs-frontend/src/app/features/notification/applicant-info/notification-details/notification-details.component.ts b/alcs-frontend/src/app/features/notification/applicant-info/notification-details/notification-details.component.ts index 81289ff544..599e91b353 100644 --- a/alcs-frontend/src/app/features/notification/applicant-info/notification-details/notification-details.component.ts +++ b/alcs-frontend/src/app/features/notification/applicant-info/notification-details/notification-details.component.ts @@ -42,8 +42,8 @@ export class NotificationDetailsComponent implements OnInit, OnChanges, OnDestro this.$destroy.complete(); } - async openFile(uuid: string) { - await this.notificationDocumentService.download(uuid, ''); + async openFile(file: NotificationDocumentDto) { + await this.notificationDocumentService.download(file.uuid, file.fileName); } private async loadDocuments() { @@ -53,7 +53,7 @@ export class NotificationDetailsComponent implements OnInit, OnChanges, OnDestro this.otherFiles = documents.filter( (document) => document.type && - [DOCUMENT_TYPE.PHOTOGRAPH, DOCUMENT_TYPE.OTHER, DOCUMENT_TYPE.PROFESSIONAL_REPORT].includes(document.type.code) + [DOCUMENT_TYPE.PHOTOGRAPH, DOCUMENT_TYPE.OTHER, DOCUMENT_TYPE.PROFESSIONAL_REPORT].includes(document.type.code), ); this.files = documents; } diff --git a/alcs-frontend/src/app/services/application/application-document/application-document.service.ts b/alcs-frontend/src/app/services/application/application-document/application-document.service.ts index 08af097986..0b8486d93e 100644 --- a/alcs-frontend/src/app/services/application/application-document/application-document.service.ts +++ b/alcs-frontend/src/app/services/application/application-document/application-document.service.ts @@ -14,7 +14,10 @@ import { ApplicationDocumentDto, CreateDocumentDto, UpdateDocumentDto } from './ export class ApplicationDocumentService { private url = `${environment.apiUrl}/application-document`; - constructor(private http: HttpClient, private toastService: ToastService) {} + constructor( + private http: HttpClient, + private toastService: ToastService, + ) {} async listAll(fileNumber: string) { return firstValueFrom(this.http.get(`${this.url}/application/${fileNumber}`)); @@ -22,7 +25,7 @@ export class ApplicationDocumentService { async listByVisibility(fileNumber: string, visibilityFlags: string[]) { return firstValueFrom( - this.http.get(`${this.url}/application/${fileNumber}/${visibilityFlags.join()}`) + this.http.get(`${this.url}/application/${fileNumber}/${visibilityFlags.join()}`), ); } @@ -55,13 +58,13 @@ export class ApplicationDocumentService { async getReviewDocuments(fileNumber: string) { return firstValueFrom( - this.http.get(`${this.url}/application/${fileNumber}/reviewDocuments`) + this.http.get(`${this.url}/application/${fileNumber}/reviewDocuments`), ); } async getApplicantDocuments(fileNumber: string) { return firstValueFrom( - this.http.get(`${this.url}/application/${fileNumber}/applicantDocuments`) + this.http.get(`${this.url}/application/${fileNumber}/applicantDocuments`), ); } diff --git a/alcs-frontend/src/app/services/application/application-submission/application-submission.service.ts b/alcs-frontend/src/app/services/application/application-submission/application-submission.service.ts index cf6527bdc5..ee3bad5a7f 100644 --- a/alcs-frontend/src/app/services/application/application-submission/application-submission.service.ts +++ b/alcs-frontend/src/app/services/application/application-submission/application-submission.service.ts @@ -2,7 +2,6 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { firstValueFrom } from 'rxjs'; import { environment } from '../../../../environments/environment'; -import { openFileInline } from '../../../shared/utils/file'; import { ToastService } from '../../toast/toast.service'; import { ApplicationSubmissionDto, CovenantTransfereeDto, UpdateApplicationSubmissionDto } from '../application.dto'; diff --git a/alcs-frontend/src/app/shared/utils/file.ts b/alcs-frontend/src/app/shared/utils/file.ts index 76fc39d1d0..46fd3ee062 100644 --- a/alcs-frontend/src/app/shared/utils/file.ts +++ b/alcs-frontend/src/app/shared/utils/file.ts @@ -11,14 +11,21 @@ export const downloadFileFromUrl = (url: string, fileName: string) => { }; export const openFileInline = (url: string, fileName: string) => { - const downloadLink = document.createElement('a'); - downloadLink.href = url; - downloadLink.download = fileName; - downloadLink.target = '_blank'; - if (window.webkitURL == null) { - downloadLink.onclick = (event: MouseEvent) => document.body.removeChild(event.target); - downloadLink.style.display = 'none'; - document.body.appendChild(downloadLink); + const newWindow = window.open('', '_blank'); + if (newWindow) { + newWindow.document.title = fileName; + + const object = newWindow.document.createElement('object'); + object.data = url; + object.style.borderWidth = '0'; + object.style.width = '100%'; + object.style.height = '100%'; + + newWindow.document.body.appendChild(object); + newWindow.document.body.style.backgroundColor = 'rgb(14, 14, 14)'; + newWindow.document.body.style.height = '100%'; + newWindow.document.body.style.width = '100%'; + newWindow.document.body.style.margin = '0'; + newWindow.document.body.style.overflow = 'hidden'; } - downloadLink.click(); }; diff --git a/portal-frontend/src/app/features/applications/application-details/application-details.component.html b/portal-frontend/src/app/features/applications/application-details/application-details.component.html index 0a4cd6716b..542065b950 100644 --- a/portal-frontend/src/app/features/applications/application-details/application-details.component.html +++ b/portal-frontend/src/app/features/applications/application-details/application-details.component.html @@ -91,7 +91,7 @@

3. Primary Contact

*ngIf="authorizationLetters.length === 0" > Authorization letters are not required, please remove them @@ -320,7 +320,7 @@

7. Optional Documents

{{ file.type?.label }} diff --git a/portal-frontend/src/app/features/applications/application-details/application-details.component.ts b/portal-frontend/src/app/features/applications/application-details/application-details.component.ts index 5d832e1e89..9110bf9011 100644 --- a/portal-frontend/src/app/features/applications/application-details/application-details.component.ts +++ b/portal-frontend/src/app/features/applications/application-details/application-details.component.ts @@ -13,7 +13,7 @@ import { LocalGovernmentDto } from '../../../services/code/code.dto'; import { CodeService } from '../../../services/code/code.service'; import { DOCUMENT_SOURCE, DOCUMENT_TYPE } from '../../../shared/dto/document.dto'; import { OWNER_TYPE } from '../../../shared/dto/owner.dto'; -import { openFileIframe } from '../../../shared/utils/file'; +import { openFileInline } from '../../../shared/utils/file'; @Component({ selector: 'app-application-details', @@ -83,10 +83,10 @@ export class ApplicationDetailsComponent implements OnInit, OnDestroy { this.$destroy.complete(); } - async openFile(uuid: string) { - const res = await this.applicationDocumentService.openFile(uuid); + async openFile(file: ApplicationDocumentDto) { + const res = await this.applicationDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } diff --git a/portal-frontend/src/app/features/applications/application-details/cove-details/cove-details.component.html b/portal-frontend/src/app/features/applications/application-details/cove-details/cove-details.component.html index f7fde7b295..71b976e111 100644 --- a/portal-frontend/src/app/features/applications/application-details/cove-details/cove-details.component.html +++ b/portal-frontend/src/app/features/applications/application-details/cove-details/cove-details.component.html @@ -43,7 +43,7 @@
Proposal Map / Site Plan
- + {{ map.fileName }} @@ -61,7 +61,7 @@
Draft Covenant
diff --git a/portal-frontend/src/app/features/applications/application-details/cove-details/cove-details.component.ts b/portal-frontend/src/app/features/applications/application-details/cove-details/cove-details.component.ts index 26eb309cfc..2e204f1036 100644 --- a/portal-frontend/src/app/features/applications/application-details/cove-details/cove-details.component.ts +++ b/portal-frontend/src/app/features/applications/application-details/cove-details/cove-details.component.ts @@ -6,7 +6,7 @@ import { ApplicationSubmissionDetailedDto } from '../../../../services/applicati import { CovenantTransfereeDto } from '../../../../services/covenant-transferee/covenant-transferee.dto'; import { CovenantTransfereeService } from '../../../../services/covenant-transferee/covenant-transferee.service'; import { DOCUMENT_TYPE } from '../../../../shared/dto/document.dto'; -import { openFileIframe } from '../../../../shared/utils/file'; +import { openFileInline } from '../../../../shared/utils/file'; @Component({ selector: 'app-cove-details', @@ -52,10 +52,10 @@ export class CoveDetailsComponent { } } - async openFile(uuid: string) { - const res = await this.applicationDocumentService.openFile(uuid); + async openFile(file: ApplicationDocumentDto) { + const res = await this.applicationDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } diff --git a/portal-frontend/src/app/features/applications/application-details/excl-details/excl-details.component.html b/portal-frontend/src/app/features/applications/application-details/excl-details/excl-details.component.html index 95e668cbfd..f8f0626c14 100644 --- a/portal-frontend/src/app/features/applications/application-details/excl-details/excl-details.component.html +++ b/portal-frontend/src/app/features/applications/application-details/excl-details/excl-details.component.html @@ -37,7 +37,7 @@
Proposal Map / Site Plan
- + {{ map.fileName }} @@ -46,7 +46,7 @@
Notice of Public Hearing (Advertisement)
@@ -56,7 +56,7 @@
Proof of Signage
@@ -66,7 +66,7 @@
Report of Public Hearing
diff --git a/portal-frontend/src/app/features/applications/application-details/excl-details/excl-details.component.ts b/portal-frontend/src/app/features/applications/application-details/excl-details/excl-details.component.ts index ccae72c74b..88ef1097f0 100644 --- a/portal-frontend/src/app/features/applications/application-details/excl-details/excl-details.component.ts +++ b/portal-frontend/src/app/features/applications/application-details/excl-details/excl-details.component.ts @@ -4,7 +4,7 @@ import { ApplicationDocumentService } from '../../../../services/application-doc import { ApplicationSubmissionDetailedDto } from '../../../../services/application-submission/application-submission.dto'; import { ApplicationDocumentDto } from '../../../../services/application-document/application-document.dto'; import { DOCUMENT_TYPE } from '../../../../shared/dto/document.dto'; -import { openFileIframe } from '../../../../shared/utils/file'; +import { openFileInline } from '../../../../shared/utils/file'; @Component({ selector: 'app-excl-details', @@ -52,10 +52,10 @@ export class ExclDetailsComponent { } } - async openFile(uuid: string) { - const res = await this.applicationDocumentService.openFile(uuid); + async openFile(file: ApplicationDocumentDto) { + const res = await this.applicationDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/features/applications/application-details/incl-details/incl-details.component.html b/portal-frontend/src/app/features/applications/application-details/incl-details/incl-details.component.html index 128a661d79..039e06d6b8 100644 --- a/portal-frontend/src/app/features/applications/application-details/incl-details/incl-details.component.html +++ b/portal-frontend/src/app/features/applications/application-details/incl-details/incl-details.component.html @@ -27,7 +27,7 @@
Proposal Map / Site Plan
- + {{ map.fileName }} @@ -52,7 +52,7 @@
Notice of Public Hearing (Advertisement)
@@ -62,7 +62,7 @@
Proof of Signage
@@ -72,7 +72,7 @@
Report of Public Hearing
diff --git a/portal-frontend/src/app/features/applications/application-details/incl-details/incl-details.component.ts b/portal-frontend/src/app/features/applications/application-details/incl-details/incl-details.component.ts index cda17dbd0d..fd26c4073e 100644 --- a/portal-frontend/src/app/features/applications/application-details/incl-details/incl-details.component.ts +++ b/portal-frontend/src/app/features/applications/application-details/incl-details/incl-details.component.ts @@ -6,7 +6,7 @@ import { ApplicationSubmissionDetailedDto } from '../../../../services/applicati import { ApplicationDocumentDto } from '../../../../services/application-document/application-document.dto'; import { AuthenticationService } from '../../../../services/authentication/authentication.service'; import { DOCUMENT_TYPE } from '../../../../shared/dto/document.dto'; -import { openFileIframe } from '../../../../shared/utils/file'; +import { openFileInline } from '../../../../shared/utils/file'; @Component({ selector: 'app-incl-details', @@ -72,10 +72,10 @@ export class InclDetailsComponent implements OnInit, OnDestroy { } } - async openFile(uuid: string) { - const res = await this.applicationDocumentService.openFile(uuid); + async openFile(file: ApplicationDocumentDto) { + const res = await this.applicationDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } diff --git a/portal-frontend/src/app/features/applications/application-details/naru-details/naru-details.component.html b/portal-frontend/src/app/features/applications/application-details/naru-details/naru-details.component.html index a4e0460012..54da0f349d 100644 --- a/portal-frontend/src/app/features/applications/application-details/naru-details/naru-details.component.html +++ b/portal-frontend/src/app/features/applications/application-details/naru-details/naru-details.component.html @@ -143,7 +143,7 @@
Proposal Map / Site Plan
- + {{ map.fileName }} diff --git a/portal-frontend/src/app/features/applications/application-details/naru-details/naru-details.component.ts b/portal-frontend/src/app/features/applications/application-details/naru-details/naru-details.component.ts index fc5e952eb0..6c68512e71 100644 --- a/portal-frontend/src/app/features/applications/application-details/naru-details/naru-details.component.ts +++ b/portal-frontend/src/app/features/applications/application-details/naru-details/naru-details.component.ts @@ -4,7 +4,7 @@ import { ApplicationDocumentDto } from '../../../../services/application-documen import { ApplicationDocumentService } from '../../../../services/application-document/application-document.service'; import { ApplicationSubmissionDetailedDto } from '../../../../services/application-submission/application-submission.dto'; import { DOCUMENT_TYPE } from '../../../../shared/dto/document.dto'; -import { openFileIframe } from '../../../../shared/utils/file'; +import { openFileInline } from '../../../../shared/utils/file'; @Component({ selector: 'app-naru-details[applicationSubmission]', @@ -42,10 +42,10 @@ export class NaruDetailsComponent { } } - async openFile(uuid: string) { - const res = await this.applicationDocumentService.openFile(uuid); + async openFile(file: ApplicationDocumentDto) { + const res = await this.applicationDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/features/applications/application-details/nfu-details/nfu-details.component.html b/portal-frontend/src/app/features/applications/application-details/nfu-details/nfu-details.component.html index 77a9bcc71d..a87c462967 100644 --- a/portal-frontend/src/app/features/applications/application-details/nfu-details/nfu-details.component.html +++ b/portal-frontend/src/app/features/applications/application-details/nfu-details/nfu-details.component.html @@ -22,7 +22,7 @@
Proposal Map / Site Plan
- + {{ map.fileName }} diff --git a/portal-frontend/src/app/features/applications/application-details/nfu-details/nfu-details.component.ts b/portal-frontend/src/app/features/applications/application-details/nfu-details/nfu-details.component.ts index 4e9fdd0233..f9cb2416c4 100644 --- a/portal-frontend/src/app/features/applications/application-details/nfu-details/nfu-details.component.ts +++ b/portal-frontend/src/app/features/applications/application-details/nfu-details/nfu-details.component.ts @@ -4,7 +4,7 @@ import { ApplicationDocumentDto } from '../../../../services/application-documen import { ApplicationDocumentService } from '../../../../services/application-document/application-document.service'; import { ApplicationSubmissionDetailedDto } from '../../../../services/application-submission/application-submission.dto'; import { DOCUMENT_TYPE } from '../../../../shared/dto/document.dto'; -import { openFileIframe } from '../../../../shared/utils/file'; +import { openFileInline } from '../../../../shared/utils/file'; @Component({ selector: 'app-nfu-details[applicationSubmission]', @@ -34,10 +34,10 @@ export class NfuDetailsComponent { } } - async openFile(uuid: string) { - const res = await this.applicationDocumentService.openFile(uuid); + async openFile(file: ApplicationDocumentDto) { + const res = await this.applicationDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/features/applications/application-details/parcel/parcel.component.html b/portal-frontend/src/app/features/applications/application-details/parcel/parcel.component.html index 8948b82268..0dc66f22f6 100644 --- a/portal-frontend/src/app/features/applications/application-details/parcel/parcel.component.html +++ b/portal-frontend/src/app/features/applications/application-details/parcel/parcel.component.html @@ -94,7 +94,7 @@

Parcel #{{ parcelInd + 1 }}

Certificate Of Title

{{ parcel.owners[0].firstName }} -
- Last Name -
+
Last Name
{{ parcel.owners[0].lastName }}
-
- Ministry or Department -
+
Ministry or Department
{{ parcel.owners[0].organizationName }} @@ -134,9 +130,7 @@
Government Parcel Contact
{{ parcel.owners[0].email }}
-
- Crown Type -
+
Crown Type
{{ parcel.owners[0].crownLandOwnerType === 'provincial' ? 'Provincial Crown' : '' }} {{ parcel.owners[0].crownLandOwnerType === 'federal' ? 'Federal Crown' : '' }} @@ -146,17 +140,12 @@
Government Parcel Contact
-
+
Land Owner(s)
Organization
Phone
Email
-
- Corporate Summary -
+
Corporate Summary
{{ owner.displayName }}
@@ -166,7 +155,7 @@
Government Parcel Contact
{{ owner.phoneNumber ?? '' | mask : '(000) 000-0000' }}
{{ owner.email }}
- {{ + {{ owner.corporateSummary.fileName }}
diff --git a/portal-frontend/src/app/features/applications/application-details/parcel/parcel.component.ts b/portal-frontend/src/app/features/applications/application-details/parcel/parcel.component.ts index a588acd793..3f85c02251 100644 --- a/portal-frontend/src/app/features/applications/application-details/parcel/parcel.component.ts +++ b/portal-frontend/src/app/features/applications/application-details/parcel/parcel.component.ts @@ -13,7 +13,7 @@ import { ApplicationParcelService } from '../../../../services/application-parce import { ApplicationSubmissionDetailedDto } from '../../../../services/application-submission/application-submission.dto'; import { BaseCodeDto } from '../../../../shared/dto/base.dto'; import { formatBooleanToYesNoString } from '../../../../shared/utils/boolean-helper'; -import { openFileIframe } from '../../../../shared/utils/file'; +import { openFileInline } from '../../../../shared/utils/file'; export class ApplicationParcelBasicValidation { // indicates general validity check state, including owner related information @@ -98,10 +98,10 @@ export class ParcelComponent { } } - async onOpenFile(uuid: string) { - const res = await this.applicationDocumentService.openFile(uuid); + async onOpenFile(file: ApplicationDocumentDto) { + const res = await this.applicationDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } diff --git a/portal-frontend/src/app/features/applications/application-details/pfrs-details/pfrs-details.component.html b/portal-frontend/src/app/features/applications/application-details/pfrs-details/pfrs-details.component.html index 73b18432a7..cb7f40503e 100644 --- a/portal-frontend/src/app/features/applications/application-details/pfrs-details/pfrs-details.component.html +++ b/portal-frontend/src/app/features/applications/application-details/pfrs-details/pfrs-details.component.html @@ -209,7 +209,7 @@
Proposal Map / Site Plan
- + {{ map.fileName }} @@ -217,7 +217,7 @@
Cross Sections
- + {{ file.fileName }} @@ -225,7 +225,7 @@
Reclamation Plan
- + {{ file.fileName }} @@ -257,7 +257,7 @@
Notice of Work
- + {{ file.fileName }} diff --git a/portal-frontend/src/app/features/applications/application-details/pfrs-details/pfrs-details.component.ts b/portal-frontend/src/app/features/applications/application-details/pfrs-details/pfrs-details.component.ts index 9cc2770c6d..df615202ef 100644 --- a/portal-frontend/src/app/features/applications/application-details/pfrs-details/pfrs-details.component.ts +++ b/portal-frontend/src/app/features/applications/application-details/pfrs-details/pfrs-details.component.ts @@ -4,7 +4,7 @@ import { ApplicationDocumentDto } from '../../../../services/application-documen import { ApplicationDocumentService } from '../../../../services/application-document/application-document.service'; import { ApplicationSubmissionDetailedDto } from '../../../../services/application-submission/application-submission.dto'; import { DOCUMENT_TYPE } from '../../../../shared/dto/document.dto'; -import { openFileIframe } from '../../../../shared/utils/file'; +import { openFileInline } from '../../../../shared/utils/file'; @Component({ selector: 'app-pfrs-details[applicationSubmission]', @@ -48,10 +48,10 @@ export class PfrsDetailsComponent { } } - async openFile(uuid: string) { - const res = await this.applicationDocumentService.openFile(uuid); + async openFile(file: ApplicationDocumentDto) { + const res = await this.applicationDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/features/applications/application-details/pofo-details/pofo-details.component.html b/portal-frontend/src/app/features/applications/application-details/pofo-details/pofo-details.component.html index e958c483d3..4218d96047 100644 --- a/portal-frontend/src/app/features/applications/application-details/pofo-details/pofo-details.component.html +++ b/portal-frontend/src/app/features/applications/application-details/pofo-details/pofo-details.component.html @@ -133,7 +133,7 @@
Proposal Map / Site Plan
- + {{ map.fileName }} @@ -141,7 +141,7 @@
Cross Sections
- + {{ file.fileName }} @@ -149,7 +149,7 @@
Reclamation Plan
- + {{ file.fileName }} diff --git a/portal-frontend/src/app/features/applications/application-details/pofo-details/pofo-details.component.ts b/portal-frontend/src/app/features/applications/application-details/pofo-details/pofo-details.component.ts index 274f2ad0fe..7e6efd5432 100644 --- a/portal-frontend/src/app/features/applications/application-details/pofo-details/pofo-details.component.ts +++ b/portal-frontend/src/app/features/applications/application-details/pofo-details/pofo-details.component.ts @@ -4,7 +4,7 @@ import { ApplicationDocumentDto } from '../../../../services/application-documen import { ApplicationDocumentService } from '../../../../services/application-document/application-document.service'; import { ApplicationSubmissionDetailedDto } from '../../../../services/application-submission/application-submission.dto'; import { DOCUMENT_TYPE } from '../../../../shared/dto/document.dto'; -import { openFileIframe } from '../../../../shared/utils/file'; +import { openFileInline } from '../../../../shared/utils/file'; @Component({ selector: 'app-pofo-details[applicationSubmission]', @@ -46,10 +46,10 @@ export class PofoDetailsComponent { } } - async openFile(uuid: string) { - const res = await this.applicationDocumentService.openFile(uuid); + async openFile(file: ApplicationDocumentDto) { + const res = await this.applicationDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/features/applications/application-details/roso-details/roso-details.component.html b/portal-frontend/src/app/features/applications/application-details/roso-details/roso-details.component.html index 30deb8ab54..083051454b 100644 --- a/portal-frontend/src/app/features/applications/application-details/roso-details/roso-details.component.html +++ b/portal-frontend/src/app/features/applications/application-details/roso-details/roso-details.component.html @@ -125,7 +125,7 @@
Proposal Map / Site Plan
- + {{ map.fileName }} @@ -133,7 +133,7 @@
Cross Sections
- + {{ file.fileName }} @@ -141,7 +141,7 @@
Reclamation Plan
- + {{ file.fileName }} diff --git a/portal-frontend/src/app/features/applications/application-details/roso-details/roso-details.component.ts b/portal-frontend/src/app/features/applications/application-details/roso-details/roso-details.component.ts index 209bf3f994..03ca23a8db 100644 --- a/portal-frontend/src/app/features/applications/application-details/roso-details/roso-details.component.ts +++ b/portal-frontend/src/app/features/applications/application-details/roso-details/roso-details.component.ts @@ -4,7 +4,7 @@ import { ApplicationDocumentDto } from '../../../../services/application-documen import { ApplicationDocumentService } from '../../../../services/application-document/application-document.service'; import { ApplicationSubmissionDetailedDto } from '../../../../services/application-submission/application-submission.dto'; import { DOCUMENT_TYPE } from '../../../../shared/dto/document.dto'; -import { openFileIframe } from '../../../../shared/utils/file'; +import { openFileInline } from '../../../../shared/utils/file'; @Component({ selector: 'app-roso-details[applicationSubmission]', @@ -46,10 +46,10 @@ export class RosoDetailsComponent { } } - async openFile(uuid: string) { - const res = await this.applicationDocumentService.openFile(uuid); + async openFile(file: ApplicationDocumentDto) { + const res = await this.applicationDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/features/applications/application-details/subd-details/subd-details.component.html b/portal-frontend/src/app/features/applications/application-details/subd-details/subd-details.component.html index 8f8daccc7f..a744cd42e1 100644 --- a/portal-frontend/src/app/features/applications/application-details/subd-details/subd-details.component.html +++ b/portal-frontend/src/app/features/applications/application-details/subd-details/subd-details.component.html @@ -42,7 +42,7 @@
Proposal Map / Site Plan
- + {{ map.fileName }} @@ -61,7 +61,7 @@
Proof of Homesite Severance Qualification
diff --git a/portal-frontend/src/app/features/applications/application-details/subd-details/subd-details.component.ts b/portal-frontend/src/app/features/applications/application-details/subd-details/subd-details.component.ts index 9718f187ff..6ffc141e7e 100644 --- a/portal-frontend/src/app/features/applications/application-details/subd-details/subd-details.component.ts +++ b/portal-frontend/src/app/features/applications/application-details/subd-details/subd-details.component.ts @@ -5,7 +5,7 @@ import { ApplicationDocumentService } from '../../../../services/application-doc import { ApplicationParcelService } from '../../../../services/application-parcel/application-parcel.service'; import { ApplicationSubmissionDetailedDto } from '../../../../services/application-submission/application-submission.dto'; import { DOCUMENT_TYPE } from '../../../../shared/dto/document.dto'; -import { openFileIframe } from '../../../../shared/utils/file'; +import { openFileInline } from '../../../../shared/utils/file'; @Component({ selector: 'app-subd-details[applicationSubmission]', @@ -56,10 +56,10 @@ export class SubdDetailsComponent { } } - async openFile(uuid: string) { - const res = await this.applicationDocumentService.openFile(uuid); + async openFile(file: ApplicationDocumentDto) { + const res = await this.applicationDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } diff --git a/portal-frontend/src/app/features/applications/application-details/tur-details/tur-details.component.html b/portal-frontend/src/app/features/applications/application-details/tur-details/tur-details.component.html index 6660fa0b79..e33da56b0a 100644 --- a/portal-frontend/src/app/features/applications/application-details/tur-details/tur-details.component.html +++ b/portal-frontend/src/app/features/applications/application-details/tur-details/tur-details.component.html @@ -40,14 +40,14 @@
Proof of Serving Notice
Proposal Map / Site Plan
- + {{ file.fileName }} diff --git a/portal-frontend/src/app/features/applications/application-details/tur-details/tur-details.component.ts b/portal-frontend/src/app/features/applications/application-details/tur-details/tur-details.component.ts index 5f96e46a8e..a9310b8837 100644 --- a/portal-frontend/src/app/features/applications/application-details/tur-details/tur-details.component.ts +++ b/portal-frontend/src/app/features/applications/application-details/tur-details/tur-details.component.ts @@ -4,7 +4,7 @@ import { ApplicationDocumentDto } from '../../../../services/application-documen import { ApplicationDocumentService } from '../../../../services/application-document/application-document.service'; import { ApplicationSubmissionDetailedDto } from '../../../../services/application-submission/application-submission.dto'; import { DOCUMENT_TYPE } from '../../../../shared/dto/document.dto'; -import { openFileIframe } from '../../../../shared/utils/file'; +import { openFileInline } from '../../../../shared/utils/file'; @Component({ selector: 'app-tur-details[applicationSubmission]', @@ -43,10 +43,10 @@ export class TurDetailsComponent { } } - async openFile(uuid: string) { - const res = await this.applicationDocumentService.openFile(uuid); + async openFile(file: ApplicationDocumentDto) { + const res = await this.applicationDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/features/applications/edit-submission/files-step.partial.ts b/portal-frontend/src/app/features/applications/edit-submission/files-step.partial.ts index 6f26e899ba..a84cec216f 100644 --- a/portal-frontend/src/app/features/applications/edit-submission/files-step.partial.ts +++ b/portal-frontend/src/app/features/applications/edit-submission/files-step.partial.ts @@ -9,7 +9,7 @@ import { DOCUMENT_TYPE } from '../../../shared/dto/document.dto'; import { FileHandle } from '../../../shared/file-drag-drop/drag-drop.directive'; import { RemoveFileConfirmationDialogComponent } from '../alcs-edit-submission/remove-file-confirmation-dialog/remove-file-confirmation-dialog.component'; import { StepComponent } from './step.partial'; -import { openFileIframe } from '../../../shared/utils/file'; +import { openFileInline } from '../../../shared/utils/file'; @Component({ selector: 'app-file-step', @@ -83,10 +83,10 @@ export abstract class FilesStepComponent extends StepComponent { } } - async openFile(uuid: string) { - const res = await this.applicationDocumentService.openFile(uuid); + async openFile(file: ApplicationDocumentDto) { + const res = await this.applicationDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/features/applications/edit-submission/other-attachments/other-attachments.component.html b/portal-frontend/src/app/features/applications/edit-submission/other-attachments/other-attachments.component.html index dbe84e3bf2..81c9d139d3 100644 --- a/portal-frontend/src/app/features/applications/edit-submission/other-attachments/other-attachments.component.html +++ b/portal-frontend/src/app/features/applications/edit-submission/other-attachments/other-attachments.component.html @@ -73,7 +73,7 @@

Optional Attachments

File Name - {{ element.fileName }} + {{ element.fileName }} diff --git a/portal-frontend/src/app/features/applications/edit-submission/parcel-details/parcel-entry/parcel-entry.component.ts b/portal-frontend/src/app/features/applications/edit-submission/parcel-details/parcel-entry/parcel-entry.component.ts index 0842b8d691..690c5d32e6 100644 --- a/portal-frontend/src/app/features/applications/edit-submission/parcel-details/parcel-entry/parcel-entry.component.ts +++ b/portal-frontend/src/app/features/applications/edit-submission/parcel-details/parcel-entry/parcel-entry.component.ts @@ -21,7 +21,7 @@ import { OwnerDialogComponent } from '../../../../../shared/owner-dialogs/owner- import { formatBooleanToString } from '../../../../../shared/utils/boolean-helper'; import { RemoveFileConfirmationDialogComponent } from '../../../alcs-edit-submission/remove-file-confirmation-dialog/remove-file-confirmation-dialog.component'; import { ParcelEntryConfirmationDialogComponent } from './parcel-entry-confirmation-dialog/parcel-entry-confirmation-dialog.component'; -import { openFileIframe } from '../../../../../shared/utils/file'; +import { openFileInline } from '../../../../../shared/utils/file'; export interface ParcelEntryFormData { uuid: string; @@ -343,10 +343,10 @@ export class ParcelEntryComponent implements OnInit { } } - async openFile(uuid: string) { - const res = await this.applicationDocumentService.openFile(uuid); + async openFile(file: ApplicationDocumentDto) { + const res = await this.applicationDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } diff --git a/portal-frontend/src/app/features/applications/review-submission/review-attachments/review-attachments.component.ts b/portal-frontend/src/app/features/applications/review-submission/review-attachments/review-attachments.component.ts index 9738bc07bd..f0156a2999 100644 --- a/portal-frontend/src/app/features/applications/review-submission/review-attachments/review-attachments.component.ts +++ b/portal-frontend/src/app/features/applications/review-submission/review-attachments/review-attachments.component.ts @@ -7,7 +7,7 @@ import { ToastService } from '../../../../services/toast/toast.service'; import { DOCUMENT_SOURCE, DOCUMENT_TYPE } from '../../../../shared/dto/document.dto'; import { FileHandle } from '../../../../shared/file-drag-drop/drag-drop.directive'; import { ReviewApplicationFngSteps, ReviewApplicationSteps } from '../review-submission.component'; -import { openFileIframe } from '../../../../shared/utils/file'; +import { openFileInline } from '../../../../shared/utils/file'; @Component({ selector: 'app-review-attachments', @@ -125,10 +125,10 @@ export class ReviewAttachmentsComponent implements OnInit, OnDestroy { } } - async openFile(uuid: string) { - const res = await this.applicationDocumentService.openFile(uuid); + async openFile(file: ApplicationDocumentDto) { + const res = await this.applicationDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } diff --git a/portal-frontend/src/app/features/applications/review-submission/review-submit-fng/review-submit-fng.component.html b/portal-frontend/src/app/features/applications/review-submission/review-submit-fng/review-submit-fng.component.html index b39216223a..b363a806cf 100644 --- a/portal-frontend/src/app/features/applications/review-submission/review-submit-fng/review-submit-fng.component.html +++ b/portal-frontend/src/app/features/applications/review-submission/review-submit-fng/review-submit-fng.component.html @@ -46,7 +46,7 @@

1. Contact Information

Phone Number
- {{ _applicationReview.phoneNumber ?? '' | mask : '(000) 000-0000' }} + {{ _applicationReview.phoneNumber ?? '' | mask : '(000) 000-0000' }}
@@ -85,7 +85,7 @@

3. Attachments

Resolution Document
- {{ + {{ document.fileName }} @@ -95,7 +95,7 @@

3. Attachments

Other Attachments (optional):
@@ -153,7 +153,7 @@

1. Contact Information

Phone Number
- {{ _applicationReview.phoneNumber ?? '' | mask : '(000) 000-0000' }} + {{ _applicationReview.phoneNumber ?? '' | mask : '(000) 000-0000' }}
@@ -196,7 +196,7 @@

3. Attachments

Resolution Document
- {{ + {{ document.fileName }} @@ -206,7 +206,7 @@

3. Attachments

Other Attachments (optional):
diff --git a/portal-frontend/src/app/features/applications/review-submission/review-submit-fng/review-submit-fng.component.ts b/portal-frontend/src/app/features/applications/review-submission/review-submit-fng/review-submit-fng.component.ts index d151847d50..548ea5f8eb 100644 --- a/portal-frontend/src/app/features/applications/review-submission/review-submit-fng/review-submit-fng.component.ts +++ b/portal-frontend/src/app/features/applications/review-submission/review-submit-fng/review-submit-fng.component.ts @@ -16,7 +16,7 @@ import { MOBILE_BREAKPOINT } from '../../../../shared/utils/breakpoints'; import { ReviewApplicationFngSteps } from '../review-submission.component'; import { ToastService } from '../../../../services/toast/toast.service'; import { SubmitConfirmationDialogComponent } from '../submit-confirmation-dialog/submit-confirmation-dialog.component'; -import { openFileIframe } from '../../../../shared/utils/file'; +import { openFileInline } from '../../../../shared/utils/file'; @Component({ selector: 'app-review-submit-fng[stepper]', @@ -121,10 +121,10 @@ export class ReviewSubmitFngComponent implements OnInit, OnDestroy { } } - async openFile(uuid: string) { - const res = await this.applicationDocumentService.openFile(uuid); + async openFile(file: ApplicationDocumentDto) { + const res = await this.applicationDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } diff --git a/portal-frontend/src/app/features/applications/review-submission/review-submit/review-submit.component.html b/portal-frontend/src/app/features/applications/review-submission/review-submit/review-submit.component.html index 464bdc22db..69a0cee602 100644 --- a/portal-frontend/src/app/features/applications/review-submission/review-submit/review-submit.component.html +++ b/portal-frontend/src/app/features/applications/review-submission/review-submit/review-submit.component.html @@ -46,7 +46,7 @@

1. Contact Information

Phone Number
- {{ _applicationReview.phoneNumber ?? '' | mask : '(000) 000-0000' }} + {{ _applicationReview.phoneNumber ?? '' | mask : '(000) 000-0000' }}
@@ -215,7 +215,7 @@

5. Attachments

Resolution Document
- {{ + {{ document.fileName }} @@ -226,7 +226,7 @@

5. Attachments

Staff Report (optional)
- {{ + {{ document.fileName }} 5. Attachments
Other Attachments (optional):
@@ -297,7 +297,7 @@

1. Contact Information

Phone Number
- {{ _applicationReview.phoneNumber ?? '' | mask : '(000) 000-0000' }} + {{ _applicationReview.phoneNumber ?? '' | mask : '(000) 000-0000' }}
@@ -467,7 +467,7 @@

5. Attachments

Resolution Document
- {{ + {{ document.fileName }} @@ -478,7 +478,7 @@

5. Attachments

Staff Report (optional)
- {{ + {{ document.fileName }} 5. Attachments
Other Attachments (optional):
diff --git a/portal-frontend/src/app/features/applications/review-submission/review-submit/review-submit.component.ts b/portal-frontend/src/app/features/applications/review-submission/review-submit/review-submit.component.ts index 36cb5a6718..784a058f99 100644 --- a/portal-frontend/src/app/features/applications/review-submission/review-submit/review-submit.component.ts +++ b/portal-frontend/src/app/features/applications/review-submission/review-submit/review-submit.component.ts @@ -15,7 +15,7 @@ import { DOCUMENT_SOURCE, DOCUMENT_TYPE } from '../../../../shared/dto/document. import { MOBILE_BREAKPOINT } from '../../../../shared/utils/breakpoints'; import { ReviewApplicationSteps } from '../review-submission.component'; import { SubmitConfirmationDialogComponent } from '../submit-confirmation-dialog/submit-confirmation-dialog.component'; -import { openFileIframe } from '../../../../shared/utils/file'; +import { openFileInline } from '../../../../shared/utils/file'; @Component({ selector: 'app-review-submit[stepper]', @@ -129,10 +129,10 @@ export class ReviewSubmitComponent implements OnInit, OnDestroy { } } - async openFile(uuid: string) { - const res = await this.applicationDocumentService.openFile(uuid); + async openFile(file: ApplicationDocumentDto) { + const res = await this.applicationDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } diff --git a/portal-frontend/src/app/features/applications/view-submission/alc-review/decisions/decisions.component.html b/portal-frontend/src/app/features/applications/view-submission/alc-review/decisions/decisions.component.html index 413266b969..2a2f6909c7 100644 --- a/portal-frontend/src/app/features/applications/view-submission/alc-review/decisions/decisions.component.html +++ b/portal-frontend/src/app/features/applications/view-submission/alc-review/decisions/decisions.component.html @@ -20,21 +20,21 @@

Decision #{{ decisions.length - index }}

Decision Document
- {{ document.fileName }} + {{ document.fileName }}  ({{ document.fileSize | filesize }})
-
{{ document.fileSize | filesize }}
- diff --git a/portal-frontend/src/app/features/applications/view-submission/alc-review/decisions/decisions.component.ts b/portal-frontend/src/app/features/applications/view-submission/alc-review/decisions/decisions.component.ts index 1280cc0118..d52199207e 100644 --- a/portal-frontend/src/app/features/applications/view-submission/alc-review/decisions/decisions.component.ts +++ b/portal-frontend/src/app/features/applications/view-submission/alc-review/decisions/decisions.component.ts @@ -1,6 +1,8 @@ import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; import { ApplicationPortalDecisionDto } from '../../../../../services/application-decision/application-decision.dto'; import { ApplicationDecisionService } from '../../../../../services/application-decision/application-decision.service'; +import { openFileInline } from '../../../../../shared/utils/file'; +import { ApplicationDocumentDto } from '../../../../../services/application-document/application-document.dto'; @Component({ selector: 'app-decisions[fileNumber]', @@ -21,10 +23,10 @@ export class DecisionsComponent implements OnInit, OnChanges { this.loadDecisions(); } - async openFile(uuid: string) { - const res = await this.decisionService.openFile(uuid); + async openFile(file: ApplicationDocumentDto) { + const res = await this.decisionService.openFile(file.uuid); if (res) { - window.open(res.url, '_blank'); + openFileInline(res.url, file.fileName); } } diff --git a/portal-frontend/src/app/features/applications/view-submission/alc-review/submission-documents/submission-documents.component.html b/portal-frontend/src/app/features/applications/view-submission/alc-review/submission-documents/submission-documents.component.html index 8dc409f48a..4b5d35bddf 100644 --- a/portal-frontend/src/app/features/applications/view-submission/alc-review/submission-documents/submission-documents.component.html +++ b/portal-frontend/src/app/features/applications/view-submission/alc-review/submission-documents/submission-documents.component.html @@ -1,43 +1,43 @@

Application Documents

-
- - - - diff --git a/portal-frontend/src/app/features/notice-of-intents/edit-submission/other-attachments/other-attachments.component.ts b/portal-frontend/src/app/features/notice-of-intents/edit-submission/other-attachments/other-attachments.component.ts index 6b5c1f18eb..5af2946180 100644 --- a/portal-frontend/src/app/features/notice-of-intents/edit-submission/other-attachments/other-attachments.component.ts +++ b/portal-frontend/src/app/features/notice-of-intents/edit-submission/other-attachments/other-attachments.component.ts @@ -1,7 +1,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; -import { Router } from '@angular/router'; import { takeUntil } from 'rxjs'; import { CodeService } from '../../../../services/code/code.service'; import { @@ -9,7 +8,6 @@ import { NoticeOfIntentDocumentUpdateDto, } from '../../../../services/notice-of-intent-document/notice-of-intent-document.dto'; import { NoticeOfIntentDocumentService } from '../../../../services/notice-of-intent-document/notice-of-intent-document.service'; -import { NoticeOfIntentSubmissionService } from '../../../../services/notice-of-intent-submission/notice-of-intent-submission.service'; import { ToastService } from '../../../../services/toast/toast.service'; import { DOCUMENT_SOURCE, DOCUMENT_TYPE, DocumentTypeDto } from '../../../../shared/dto/document.dto'; import { FileHandle } from '../../../../shared/file-drag-drop/drag-drop.directive'; @@ -37,8 +35,6 @@ export class OtherAttachmentsComponent extends FilesStepComponent implements OnI showVirusError = false; constructor( - private router: Router, - private noticeOfIntentSubmissionService: NoticeOfIntentSubmissionService, private codeService: CodeService, noticeOfIntentDocumentService: NoticeOfIntentDocumentService, dialog: MatDialog, diff --git a/portal-frontend/src/app/features/notice-of-intents/edit-submission/parcels/parcel-entry/parcel-entry.component.ts b/portal-frontend/src/app/features/notice-of-intents/edit-submission/parcels/parcel-entry/parcel-entry.component.ts index bcbbe6636d..406b05d064 100644 --- a/portal-frontend/src/app/features/notice-of-intents/edit-submission/parcels/parcel-entry/parcel-entry.component.ts +++ b/portal-frontend/src/app/features/notice-of-intents/edit-submission/parcels/parcel-entry/parcel-entry.component.ts @@ -20,7 +20,7 @@ import { formatBooleanToString } from '../../../../../shared/utils/boolean-helpe import { RemoveFileConfirmationDialogComponent } from '../../../../applications/alcs-edit-submission/remove-file-confirmation-dialog/remove-file-confirmation-dialog.component'; import { ParcelEntryConfirmationDialogComponent } from './parcel-entry-confirmation-dialog/parcel-entry-confirmation-dialog.component'; import { scrollToElement } from '../../../../../shared/utils/scroll-helper'; -import { openFileIframe } from '../../../../../shared/utils/file'; +import { openFileInline } from '../../../../../shared/utils/file'; export interface ParcelEntryFormData { uuid: string; @@ -341,10 +341,10 @@ export class ParcelEntryComponent implements OnInit { } } - async openFile(uuid: string) { - const res = await this.noticeOfIntentDocumentService.openFile(uuid); + async openFile(file: NoticeOfIntentDocumentDto) { + const res = await this.noticeOfIntentDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } diff --git a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/additional-information/additional-information.component.html b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/additional-information/additional-information.component.html index ef34c2f133..0e3275da11 100644 --- a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/additional-information/additional-information.component.html +++ b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/additional-information/additional-information.component.html @@ -84,7 +84,7 @@
Detailed Building Plan(s)
- + {{ file.fileName }} diff --git a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/additional-information/additional-information.component.ts b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/additional-information/additional-information.component.ts index f0f313b4b6..2febb3e18a 100644 --- a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/additional-information/additional-information.component.ts +++ b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/additional-information/additional-information.component.ts @@ -8,7 +8,7 @@ import { RESIDENTIAL_STRUCTURE_TYPES, STRUCTURE_TYPES, } from '../../edit-submission/additional-information/additional-information.component'; -import { openFileIframe } from '../../../../shared/utils/file'; +import { openFileInline } from '../../../../shared/utils/file'; @Component({ selector: 'app-additional-information', @@ -100,10 +100,10 @@ export class AdditionalInformationComponent { } } - async openFile(uuid: string) { - const res = await this.noticeOfIntentDocumentService.openFile(uuid); + async openFile(file: NoticeOfIntentDocumentDto) { + const res = await this.noticeOfIntentDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/notice-of-intent-details.component.html b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/notice-of-intent-details.component.html index d56d81a163..5fefa1418d 100644 --- a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/notice-of-intent-details.component.html +++ b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/notice-of-intent-details.component.html @@ -42,7 +42,7 @@

2. Primary Contact

Phone
- {{ primaryContact?.phoneNumber ?? '' | mask : '(000) 000-0000' }} + {{ primaryContact?.phoneNumber ?? '' | mask : '(000) 000-0000' }} Invalid Format2. Primary Contact *ngIf="authorizationLetters.length === 0" > Authorization letters are not required, please remove them @@ -215,7 +215,7 @@

7. Optional Documents

{{ file.type?.label }} diff --git a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/notice-of-intent-details.component.ts b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/notice-of-intent-details.component.ts index ccf3768864..47de5e77be 100644 --- a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/notice-of-intent-details.component.ts +++ b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/notice-of-intent-details.component.ts @@ -10,7 +10,7 @@ import { NoticeOfIntentParcelService } from '../../../services/notice-of-intent- import { NoticeOfIntentSubmissionDetailedDto } from '../../../services/notice-of-intent-submission/notice-of-intent-submission.dto'; import { DOCUMENT_SOURCE, DOCUMENT_TYPE } from '../../../shared/dto/document.dto'; import { OWNER_TYPE } from '../../../shared/dto/owner.dto'; -import { openFileIframe } from '../../../shared/utils/file'; +import { openFileInline } from '../../../shared/utils/file'; @Component({ selector: 'app-noi-details', @@ -82,10 +82,10 @@ export class NoticeOfIntentDetailsComponent implements OnInit, OnDestroy { this.$destroy.complete(); } - async openFile(uuid: string) { - const res = await this.noticeOfIntentDocumentService.openFile(uuid); + async openFile(file: NoticeOfIntentDocumentDto) { + const res = await this.noticeOfIntentDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } diff --git a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/parcel/parcel.component.html b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/parcel/parcel.component.html index 32bcc16494..85c2f464a3 100644 --- a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/parcel/parcel.component.html +++ b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/parcel/parcel.component.html @@ -94,7 +94,7 @@

Parcel #{{ parcelInd + 1 }}

Certificate Of Title
Government Parcel Contact {{ parcel.owners[0].firstName }}
-
- Last Name -
+
Last Name
{{ parcel.owners[0].lastName }}
-
- Ministry or Department -
+
Ministry or Department
{{ parcel.owners[0].organizationName }} @@ -134,9 +130,7 @@
Government Parcel Contact
{{ parcel.owners[0].email }}
-
- Crown Type -
+
Crown Type
{{ parcel.owners[0].crownLandOwnerType === 'provincial' ? 'Provincial Crown' : '' }} {{ parcel.owners[0].crownLandOwnerType === 'federal' ? 'Federal Crown' : '' }} @@ -146,17 +140,12 @@
Government Parcel Contact
-
+
Land Owner(s)
Organization
Phone
Email
-
- Corporate Summary -
+
Corporate Summary
{{ owner.displayName }}
@@ -166,7 +155,7 @@
Government Parcel Contact
{{ owner.phoneNumber ?? '' | mask : '(000) 000-0000' }}
{{ owner.email }}
- {{ + {{ owner.corporateSummary.fileName }} diff --git a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/parcel/parcel.component.ts b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/parcel/parcel.component.ts index aa4fa84fd4..ece75c7afb 100644 --- a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/parcel/parcel.component.ts +++ b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/parcel/parcel.component.ts @@ -14,7 +14,7 @@ import { NoticeOfIntentParcelService } from '../../../../services/notice-of-inte import { NoticeOfIntentSubmissionDetailedDto } from '../../../../services/notice-of-intent-submission/notice-of-intent-submission.dto'; import { BaseCodeDto } from '../../../../shared/dto/base.dto'; import { formatBooleanToYesNoString } from '../../../../shared/utils/boolean-helper'; -import { openFileIframe } from '../../../../shared/utils/file'; +import { openFileInline } from '../../../../shared/utils/file'; export class NoticeOfIntentParcelBasicValidation { // indicates general validity check state, including owner related information @@ -97,10 +97,10 @@ export class ParcelComponent { } } - async onOpenFile(uuid: string) { - const res = await this.noticeOfIntentDocumentService.openFile(uuid); + async onOpenFile(file: NoticeOfIntentDocumentDto) { + const res = await this.noticeOfIntentDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } diff --git a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/pfrs-details/pfrs-details.component.html b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/pfrs-details/pfrs-details.component.html index d731bf3f59..fdffe108d2 100644 --- a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/pfrs-details/pfrs-details.component.html +++ b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/pfrs-details/pfrs-details.component.html @@ -171,7 +171,7 @@
Proposal Map / Site Plan
- + {{ map.fileName }} @@ -197,7 +197,7 @@
Cross Sections
@@ -207,7 +207,7 @@
Reclamation Plan
@@ -230,7 +230,7 @@
Notice of Work
diff --git a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/pfrs-details/pfrs-details.component.ts b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/pfrs-details/pfrs-details.component.ts index 1729791499..4d2c713dcf 100644 --- a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/pfrs-details/pfrs-details.component.ts +++ b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/pfrs-details/pfrs-details.component.ts @@ -4,7 +4,7 @@ import { NoticeOfIntentDocumentDto } from '../../../../services/notice-of-intent import { NoticeOfIntentDocumentService } from '../../../../services/notice-of-intent-document/notice-of-intent-document.service'; import { NoticeOfIntentSubmissionDetailedDto } from '../../../../services/notice-of-intent-submission/notice-of-intent-submission.dto'; import { DOCUMENT_TYPE } from '../../../../shared/dto/document.dto'; -import { openFileIframe } from '../../../../shared/utils/file'; +import { openFileInline } from '../../../../shared/utils/file'; @Component({ selector: 'app-pfrs-details[noiSubmission]', @@ -48,10 +48,10 @@ export class PfrsDetailsComponent { } } - async openFile(uuid: string) { - const res = await this.noticeOfIntentDocumentService.openFile(uuid); + async openFile(file: NoticeOfIntentDocumentDto) { + const res = await this.noticeOfIntentDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/pofo-details/pofo-details.component.html b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/pofo-details/pofo-details.component.html index 3bfb5e98b7..30236d2c68 100644 --- a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/pofo-details/pofo-details.component.html +++ b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/pofo-details/pofo-details.component.html @@ -107,7 +107,7 @@
Proposal Map / Site Plan
- + {{ map.fileName }} @@ -125,7 +125,7 @@
Cross Sections
@@ -135,7 +135,7 @@
Reclamation Plan
diff --git a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/pofo-details/pofo-details.component.ts b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/pofo-details/pofo-details.component.ts index bda8999026..326cc78422 100644 --- a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/pofo-details/pofo-details.component.ts +++ b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/pofo-details/pofo-details.component.ts @@ -4,7 +4,7 @@ import { NoticeOfIntentDocumentDto } from '../../../../services/notice-of-intent import { NoticeOfIntentDocumentService } from '../../../../services/notice-of-intent-document/notice-of-intent-document.service'; import { NoticeOfIntentSubmissionDetailedDto } from '../../../../services/notice-of-intent-submission/notice-of-intent-submission.dto'; import { DOCUMENT_TYPE } from '../../../../shared/dto/document.dto'; -import { openFileIframe } from '../../../../shared/utils/file'; +import { openFileInline } from '../../../../shared/utils/file'; @Component({ selector: 'app-pofo-details[noiSubmission]', @@ -48,10 +48,10 @@ export class PofoDetailsComponent { } } - async openFile(uuid: string) { - const res = await this.noticeOfIntentDocumentService.openFile(uuid); + async openFile(file: NoticeOfIntentDocumentDto) { + const res = await this.noticeOfIntentDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/roso-details/roso-details.component.html b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/roso-details/roso-details.component.html index da49f8bb95..ae08104f3f 100644 --- a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/roso-details/roso-details.component.html +++ b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/roso-details/roso-details.component.html @@ -106,7 +106,7 @@
Proposal Map / Site Plan
- + {{ map.fileName }} @@ -123,7 +123,7 @@
Cross Sections
- + {{ file.fileName }} @@ -131,7 +131,7 @@
Reclamation Plan
- + {{ file.fileName }} @@ -150,7 +150,7 @@
Notice of Work
- + {{ file.fileName }} diff --git a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/roso-details/roso-details.component.ts b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/roso-details/roso-details.component.ts index 4edd97841c..880e3fda57 100644 --- a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/roso-details/roso-details.component.ts +++ b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/roso-details/roso-details.component.ts @@ -4,7 +4,7 @@ import { NoticeOfIntentDocumentDto } from '../../../../services/notice-of-intent import { NoticeOfIntentDocumentService } from '../../../../services/notice-of-intent-document/notice-of-intent-document.service'; import { NoticeOfIntentSubmissionDetailedDto } from '../../../../services/notice-of-intent-submission/notice-of-intent-submission.dto'; import { DOCUMENT_TYPE } from '../../../../shared/dto/document.dto'; -import { openFileIframe } from '../../../../shared/utils/file'; +import { openFileInline } from '../../../../shared/utils/file'; @Component({ selector: 'app-roso-details[noiSubmission]', @@ -48,10 +48,10 @@ export class RosoDetailsComponent { } } - async openFile(uuid: string) { - const res = await this.noticeOfIntentDocumentService.openFile(uuid); + async openFile(file: NoticeOfIntentDocumentDto) { + const res = await this.noticeOfIntentDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/features/notice-of-intents/view-submission/alc-review/decisions/decisions.component.html b/portal-frontend/src/app/features/notice-of-intents/view-submission/alc-review/decisions/decisions.component.html index 413266b969..2a2f6909c7 100644 --- a/portal-frontend/src/app/features/notice-of-intents/view-submission/alc-review/decisions/decisions.component.html +++ b/portal-frontend/src/app/features/notice-of-intents/view-submission/alc-review/decisions/decisions.component.html @@ -20,21 +20,21 @@

Decision #{{ decisions.length - index }}

Decision Document
- {{ document.fileName }} + {{ document.fileName }}  ({{ document.fileSize | filesize }})
-
{{ document.fileSize | filesize }}
- diff --git a/portal-frontend/src/app/features/notice-of-intents/view-submission/alc-review/decisions/decisions.component.ts b/portal-frontend/src/app/features/notice-of-intents/view-submission/alc-review/decisions/decisions.component.ts index 02d647de95..b533e1fdb4 100644 --- a/portal-frontend/src/app/features/notice-of-intents/view-submission/alc-review/decisions/decisions.component.ts +++ b/portal-frontend/src/app/features/notice-of-intents/view-submission/alc-review/decisions/decisions.component.ts @@ -1,6 +1,8 @@ import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; import { NoticeOfIntentPortalDecisionDto } from '../../../../../services/notice-of-intent-decision/notice-of-intent-decision.dto'; import { NoticeOfIntentDecisionService } from '../../../../../services/notice-of-intent-decision/notice-of-intent-decision.service'; +import { openFileInline } from '../../../../../shared/utils/file'; +import { ApplicationDocumentDto } from '../../../../../services/application-document/application-document.dto'; @Component({ selector: 'app-decisions[fileNumber]', @@ -21,10 +23,10 @@ export class DecisionsComponent implements OnInit, OnChanges { this.loadDecisions(); } - async openFile(uuid: string) { - const res = await this.decisionService.openFile(uuid); + async openFile(file: ApplicationDocumentDto) { + const res = await this.decisionService.openFile(file.uuid); if (res) { - window.open(res.url, '_blank'); + openFileInline(res.url, file.fileName); } } diff --git a/portal-frontend/src/app/features/notice-of-intents/view-submission/alc-review/submission-documents/submission-documents.component.html b/portal-frontend/src/app/features/notice-of-intents/view-submission/alc-review/submission-documents/submission-documents.component.html index 4be196b160..31621a20d2 100644 --- a/portal-frontend/src/app/features/notice-of-intents/view-submission/alc-review/submission-documents/submission-documents.component.html +++ b/portal-frontend/src/app/features/notice-of-intents/view-submission/alc-review/submission-documents/submission-documents.component.html @@ -11,7 +11,7 @@

Notice of Intent Documents

diff --git a/portal-frontend/src/app/features/notice-of-intents/view-submission/alc-review/submission-documents/submission-documents.component.ts b/portal-frontend/src/app/features/notice-of-intents/view-submission/alc-review/submission-documents/submission-documents.component.ts index 0fb70ef5e1..d4db07b419 100644 --- a/portal-frontend/src/app/features/notice-of-intents/view-submission/alc-review/submission-documents/submission-documents.component.ts +++ b/portal-frontend/src/app/features/notice-of-intents/view-submission/alc-review/submission-documents/submission-documents.component.ts @@ -4,7 +4,7 @@ import { MatTableDataSource } from '@angular/material/table'; import { BehaviorSubject, Subject, takeUntil } from 'rxjs'; import { NoticeOfIntentDocumentDto } from '../../../../../services/notice-of-intent-document/notice-of-intent-document.dto'; import { NoticeOfIntentDocumentService } from '../../../../../services/notice-of-intent-document/notice-of-intent-document.service'; -import { openFileIframe } from '../../../../../shared/utils/file'; +import { openFileInline } from '../../../../../shared/utils/file'; @Component({ selector: 'app-submission-documents', @@ -30,10 +30,10 @@ export class SubmissionDocumentsComponent implements OnInit, OnDestroy { }); } - async openFile(uuid: string) { - const res = await this.noticeOfIntentDocumentService.openFile(uuid); + async openFile(file: NoticeOfIntentDocumentDto) { + const res = await this.noticeOfIntentDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } diff --git a/portal-frontend/src/app/features/notifications/edit-submission/files-step.partial.ts b/portal-frontend/src/app/features/notifications/edit-submission/files-step.partial.ts index 817b3a513f..d5df41de63 100644 --- a/portal-frontend/src/app/features/notifications/edit-submission/files-step.partial.ts +++ b/portal-frontend/src/app/features/notifications/edit-submission/files-step.partial.ts @@ -9,7 +9,7 @@ import { ToastService } from '../../../services/toast/toast.service'; import { DOCUMENT_TYPE } from '../../../shared/dto/document.dto'; import { FileHandle } from '../../../shared/file-drag-drop/drag-drop.directive'; import { StepComponent } from './step.partial'; -import { openFileIframe } from '../../../shared/utils/file'; +import { openFileInline } from '../../../shared/utils/file'; @Component({ selector: 'app-file-step', @@ -69,10 +69,10 @@ export abstract class FilesStepComponent extends StepComponent { } } - async openFile(uuid: string) { - const res = await this.notificationDocumentService.openFile(uuid); + async openFile(file: ApplicationDocumentDto) { + const res = await this.notificationDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/features/notifications/edit-submission/other-attachments/other-attachments.component.html b/portal-frontend/src/app/features/notifications/edit-submission/other-attachments/other-attachments.component.html index 0d30b120a3..7b827e2b48 100644 --- a/portal-frontend/src/app/features/notifications/edit-submission/other-attachments/other-attachments.component.html +++ b/portal-frontend/src/app/features/notifications/edit-submission/other-attachments/other-attachments.component.html @@ -16,7 +16,7 @@

Optional Attachments

diff --git a/portal-frontend/src/app/features/notifications/edit-submission/proposal/proposal.component.html b/portal-frontend/src/app/features/notifications/edit-submission/proposal/proposal.component.html index bda82ea373..3f8d717121 100644 --- a/portal-frontend/src/app/features/notifications/edit-submission/proposal/proposal.component.html +++ b/portal-frontend/src/app/features/notifications/edit-submission/proposal/proposal.component.html @@ -133,7 +133,7 @@

Purpose of SRW

diff --git a/portal-frontend/src/app/features/notifications/notification-details/notification-details.component.html b/portal-frontend/src/app/features/notifications/notification-details/notification-details.component.html index cbe773ddd1..85bcff7e34 100644 --- a/portal-frontend/src/app/features/notifications/notification-details/notification-details.component.html +++ b/portal-frontend/src/app/features/notifications/notification-details/notification-details.component.html @@ -23,7 +23,7 @@

2. Identify Transferee(s)

- {{ transferee.phoneNumber | mask: '(000) 000-0000' }} + {{ transferee.phoneNumber | mask : '(000) 000-0000' }}
{{ transferee.email }}
@@ -60,7 +60,7 @@

3. Primary Contact

Phone
- {{ notificationSubmission.contactPhone ?? '' | mask : '(000) 000-0000' }} + {{ notificationSubmission.contactPhone ?? '' | mask : '(000) 000-0000' }} Invalid Format6. Optional Attachments
{{ file.type?.label }} diff --git a/portal-frontend/src/app/features/notifications/notification-details/notification-details.component.ts b/portal-frontend/src/app/features/notifications/notification-details/notification-details.component.ts index 75155e4402..c7f1ff2c50 100644 --- a/portal-frontend/src/app/features/notifications/notification-details/notification-details.component.ts +++ b/portal-frontend/src/app/features/notifications/notification-details/notification-details.component.ts @@ -8,7 +8,7 @@ import { NotificationDocumentService } from '../../../services/notification-docu import { NotificationSubmissionDetailedDto } from '../../../services/notification-submission/notification-submission.dto'; import { DOCUMENT_SOURCE, DOCUMENT_TYPE } from '../../../shared/dto/document.dto'; import { OWNER_TYPE } from '../../../shared/dto/owner.dto'; -import { openFileIframe } from '../../../shared/utils/file'; +import { openFileInline } from '../../../shared/utils/file'; @Component({ selector: 'app-notification-details', @@ -64,10 +64,10 @@ export class NotificationDetailsComponent implements OnInit, OnDestroy { this.$destroy.complete(); } - async openFile(uuid: string) { - const res = await this.notificationDocumentService.openFile(uuid); + async openFile(file: NotificationDocumentDto) { + const res = await this.notificationDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } diff --git a/portal-frontend/src/app/features/notifications/notification-details/proposal-details/proposal-details.component.html b/portal-frontend/src/app/features/notifications/notification-details/proposal-details/proposal-details.component.html index 2d8b725fd6..d58dafa26e 100644 --- a/portal-frontend/src/app/features/notifications/notification-details/proposal-details/proposal-details.component.html +++ b/portal-frontend/src/app/features/notifications/notification-details/proposal-details/proposal-details.component.html @@ -19,7 +19,7 @@
Upload Terms of the SRW
- + {{ file.fileName }} @@ -39,7 +39,7 @@
Control Number
{{ file.surveyPlanNumber }} diff --git a/portal-frontend/src/app/features/notifications/notification-details/proposal-details/proposal-details.component.ts b/portal-frontend/src/app/features/notifications/notification-details/proposal-details/proposal-details.component.ts index 348c6a7024..c20d99a84f 100644 --- a/portal-frontend/src/app/features/notifications/notification-details/proposal-details/proposal-details.component.ts +++ b/portal-frontend/src/app/features/notifications/notification-details/proposal-details/proposal-details.component.ts @@ -4,7 +4,7 @@ import { NotificationDocumentDto } from '../../../../services/notification-docum import { NotificationDocumentService } from '../../../../services/notification-document/notification-document.service'; import { NotificationSubmissionDetailedDto } from '../../../../services/notification-submission/notification-submission.dto'; import { DOCUMENT_TYPE } from '../../../../shared/dto/document.dto'; -import { openFileIframe } from '../../../../shared/utils/file'; +import { openFileInline } from '../../../../shared/utils/file'; @Component({ selector: 'app-proposal-details[notificationSubmission]', @@ -37,10 +37,10 @@ export class ProposalDetailsComponent { await this.router.navigateByUrl(`notification/${this._notificationSubmission?.fileNumber}/edit/${step}?errors=t`); } - async openFile(uuid: string) { - const res = await this.notificationDocumentService.openFile(uuid); + async openFile(file: NotificationDocumentDto) { + const res = await this.notificationDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/features/notifications/view-submission/alc-review/submission-documents/submission-documents.component.html b/portal-frontend/src/app/features/notifications/view-submission/alc-review/submission-documents/submission-documents.component.html index ff9838f439..e6504b1152 100644 --- a/portal-frontend/src/app/features/notifications/view-submission/alc-review/submission-documents/submission-documents.component.html +++ b/portal-frontend/src/app/features/notifications/view-submission/alc-review/submission-documents/submission-documents.component.html @@ -1,43 +1,43 @@

SRW Documents

-
-
Type +
+ + + + - - - + - - - + + + - - - + + + - + - - - - - + + + +
Type {{ element.type?.label }} Document Name - {{ element.fileName }} + + Document Name + {{ element.fileName }} Source{{ element.source }}Source{{ element.source }} Upload Date{{ element.uploadedAt | date }}Upload Date{{ element.uploadedAt | date }} Actions - +
Documents will be visible here once provided by ALC
Documents will be visible here once provided by ALC
diff --git a/portal-frontend/src/app/features/applications/view-submission/alc-review/submission-documents/submission-documents.component.ts b/portal-frontend/src/app/features/applications/view-submission/alc-review/submission-documents/submission-documents.component.ts index 679fb8aa43..eb0d606fb9 100644 --- a/portal-frontend/src/app/features/applications/view-submission/alc-review/submission-documents/submission-documents.component.ts +++ b/portal-frontend/src/app/features/applications/view-submission/alc-review/submission-documents/submission-documents.component.ts @@ -4,7 +4,7 @@ import { MatTableDataSource } from '@angular/material/table'; import { BehaviorSubject, Subject, takeUntil } from 'rxjs'; import { ApplicationDocumentDto } from '../../../../../services/application-document/application-document.dto'; import { ApplicationDocumentService } from '../../../../../services/application-document/application-document.service'; -import { openFileIframe } from '../../../../../shared/utils/file'; +import { openFileInline } from '../../../../../shared/utils/file'; @Component({ selector: 'app-submission-documents', @@ -30,10 +30,10 @@ export class SubmissionDocumentsComponent implements OnInit, OnDestroy { }); } - async openFile(uuid: string) { - const res = await this.applicationDocumentService.openFile(uuid); + async openFile(file: ApplicationDocumentDto) { + const res = await this.applicationDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } diff --git a/portal-frontend/src/app/features/applications/view-submission/lfng-review/lfng-review.component.html b/portal-frontend/src/app/features/applications/view-submission/lfng-review/lfng-review.component.html index 4b275a4502..6534dad454 100644 --- a/portal-frontend/src/app/features/applications/view-submission/lfng-review/lfng-review.component.html +++ b/portal-frontend/src/app/features/applications/view-submission/lfng-review/lfng-review.component.html @@ -13,20 +13,29 @@

Local/First Nation Gov Review

> Download PDF - -
File Name - {{ element.fileName }} + {{ element.fileName }} Document Name - {{ element.fileName }} + {{ element.fileName }} File Name - {{ element.fileName }} + {{ element.fileName }} File Name - {{ element.fileName }} + {{ element.fileName }}
- - - diff --git a/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.ts b/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.ts index 29e14ebe10..c7f21046bf 100644 --- a/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.ts +++ b/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.ts @@ -1,12 +1,11 @@ import { Component, Input } from '@angular/core'; import { Router } from '@angular/router'; +import { NgSelectComponent } from '@ng-select/ng-select'; import { HomepageSubtaskDto } from '../../../../services/card/card-subtask/card-subtask.dto'; import { CardSubtaskService } from '../../../../services/card/card-subtask/card-subtask.service'; import { AssigneeDto, UserDto } from '../../../../services/user/user.dto'; -import { NgSelectComponent } from '@ng-select/ng-select'; import { MODIFICATION_TYPE_LABEL, - NOTIFICATION_LABEL, RECON_TYPE_LABEL, } from '../../../../shared/application-type-pill/application-type-pill.constants'; import { CardType } from '../../../../shared/card/card.component'; @@ -22,7 +21,6 @@ export class SubtaskTableComponent { MODIFICATION_TYPE_LABEL = MODIFICATION_TYPE_LABEL; RECON_TYPE_LABEL = RECON_TYPE_LABEL; - NOTIFICATION_LABEL = NOTIFICATION_LABEL; CardType = CardType; diff --git a/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.constants.ts b/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.constants.ts index d8dc694408..f75fd2d12c 100644 --- a/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.constants.ts +++ b/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.constants.ts @@ -70,14 +70,6 @@ export const DECISION_CONDITION_SUPERSEDED_LABEL = { textColor: '#000', }; -export const NOTIFICATION_LABEL = { - label: 'Statutory Right of Way', - shortLabel: 'SRW', - backgroundColor: '#fff', - borderColor: '#59ADFA', - textColor: '#313132', -}; - export const OPEN_PR_LABEL = { label: 'Open', shortLabel: 'Open', diff --git a/services/apps/alcs/src/alcs/home/home.controller.ts b/services/apps/alcs/src/alcs/home/home.controller.ts index f5233a2d87..783a5d638c 100644 --- a/services/apps/alcs/src/alcs/home/home.controller.ts +++ b/services/apps/alcs/src/alcs/home/home.controller.ts @@ -383,6 +383,7 @@ export class HomeController { paused: false, title: `${notification.fileNumber} (${notification.applicant})`, parentType: PARENT_TYPE.NOTIFICATION, + appType: notification.type, }); } } @@ -407,6 +408,7 @@ export class HomeController { inquiry.inquirerLastName ?? 'Unknown' })`, parentType: PARENT_TYPE.INQUIRY, + appType: inquiry.type, }); } } diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.service.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.service.ts index 962452a576..6eacf0792b 100644 --- a/services/apps/alcs/src/alcs/inquiry/inquiry.service.ts +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.service.ts @@ -176,6 +176,7 @@ export class InquiryService { }, }, relations: { + type: true, card: { status: true, board: true, diff --git a/services/apps/alcs/src/alcs/notification/notification.service.ts b/services/apps/alcs/src/alcs/notification/notification.service.ts index 4ff8c026ce..8e6e88fdc8 100644 --- a/services/apps/alcs/src/alcs/notification/notification.service.ts +++ b/services/apps/alcs/src/alcs/notification/notification.service.ts @@ -186,6 +186,7 @@ export class NotificationService { }, }, relations: { + type: true, card: { status: true, board: true, From f39211b34f70df7b212443b147ac8037120561c8 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Thu, 4 Apr 2024 15:04:12 -0700 Subject: [PATCH 102/153] Bug Fixes * Add inquiries to unarchive card service * Add error message to PIDs on Parcels page --- .../inquiry/parcel/parcels.component.html | 4 ++ .../inquiry/parcel/parcels.component.scss | 17 +++++ .../apps/alcs/src/alcs/admin/admin.module.ts | 6 +- .../unarchive-card.service.spec.ts | 9 +++ .../unarchive-card/unarchive-card.service.ts | 62 +++++++++++++++---- 5 files changed, 83 insertions(+), 15 deletions(-) diff --git a/alcs-frontend/src/app/features/inquiry/parcel/parcels.component.html b/alcs-frontend/src/app/features/inquiry/parcel/parcels.component.html index 05898b9a8f..45a4a21644 100644 --- a/alcs-frontend/src/app/features/inquiry/parcel/parcels.component.html +++ b/alcs-frontend/src/app/features/inquiry/parcel/parcels.component.html @@ -36,6 +36,10 @@

Parcels

+ + warning + PID must be 9 digits including leading zeroes + diff --git a/alcs-frontend/src/app/features/inquiry/parcel/parcels.component.scss b/alcs-frontend/src/app/features/inquiry/parcel/parcels.component.scss index 4a4f2a7fa0..e13fa1080d 100644 --- a/alcs-frontend/src/app/features/inquiry/parcel/parcels.component.scss +++ b/alcs-frontend/src/app/features/inquiry/parcel/parcels.component.scss @@ -1,3 +1,5 @@ +@use '../../../../styles/colors'; + h5 { margin: 16px 0 !important; } @@ -30,3 +32,18 @@ section { max-width: calc(100vw - 840px); } } + +.error { + display: flex; + color: colors.$error-color; + font-weight: bold; + text-wrap: wrap; + + .mat-icon { + min-width: 24px; + } +} + +.mat-mdc-form-field { + width: 100%; +} diff --git a/services/apps/alcs/src/alcs/admin/admin.module.ts b/services/apps/alcs/src/alcs/admin/admin.module.ts index 9912d54711..527e04bae7 100644 --- a/services/apps/alcs/src/alcs/admin/admin.module.ts +++ b/services/apps/alcs/src/alcs/admin/admin.module.ts @@ -1,13 +1,14 @@ import { forwardRef, Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Configuration } from '../../common/entities/configuration.entity'; +import { ApplicationCeoCriterionCode } from '../application-decision/application-ceo-criterion/application-ceo-criterion.entity'; import { ApplicationDecisionConditionType } from '../application-decision/application-decision-condition/application-decision-condition-code.entity'; import { ApplicationDecisionMakerCode } from '../application-decision/application-decision-maker/application-decision-maker.entity'; +import { ApplicationDecisionModule } from '../application-decision/application-decision.module'; import { ApplicationModule } from '../application/application.module'; import { BoardModule } from '../board/board.module'; import { CardModule } from '../card/card.module'; -import { ApplicationCeoCriterionCode } from '../application-decision/application-ceo-criterion/application-ceo-criterion.entity'; -import { ApplicationDecisionModule } from '../application-decision/application-decision.module'; +import { InquiryModule } from '../inquiry/inquiry.module'; import { NoticeOfIntentDecisionModule } from '../notice-of-intent-decision/notice-of-intent-decision.module'; import { NoticeOfIntentSubtype } from '../notice-of-intent/notice-of-intent-subtype.entity'; import { NoticeOfIntentModule } from '../notice-of-intent/notice-of-intent.module'; @@ -52,6 +53,7 @@ import { UnarchiveCardService } from './unarchive-card/unarchive-card.service'; CardModule, BoardModule, NotificationModule, + InquiryModule, ], controllers: [ HolidayController, 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 6c0b46682e..edd61b7423 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 @@ -5,6 +5,7 @@ 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 { InquiryService } from '../../inquiry/inquiry.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'; @@ -21,6 +22,7 @@ describe('UnarchiveCardService', () => { let mockNOIService: DeepMocked; let mockNOIModificationService: DeepMocked; let mockNotificationService: DeepMocked; + let mockInquiryService: DeepMocked; beforeEach(async () => { mockApplicationService = createMock(); @@ -30,6 +32,7 @@ describe('UnarchiveCardService', () => { mockNOIService = createMock(); mockNOIModificationService = createMock(); mockNotificationService = createMock(); + mockInquiryService = createMock(); const module: TestingModule = await Test.createTestingModule({ imports: [ @@ -67,6 +70,10 @@ describe('UnarchiveCardService', () => { provide: NotificationService, useValue: mockNotificationService, }, + { + provide: InquiryService, + useValue: mockInquiryService, + }, ], }).compile(); @@ -85,6 +92,7 @@ describe('UnarchiveCardService', () => { mockNOIService.getDeletedCards.mockResolvedValue([]); mockNOIModificationService.getDeletedCards.mockResolvedValue([]); mockNotificationService.getDeletedCards.mockResolvedValue([]); + mockInquiryService.getDeletedCards.mockResolvedValue([]); await service.fetchByFileId('uuid'); @@ -97,5 +105,6 @@ describe('UnarchiveCardService', () => { expect(mockNOIService.getDeletedCards).toHaveBeenCalledTimes(1); expect(mockNOIModificationService.getDeletedCards).toHaveBeenCalledTimes(1); expect(mockNotificationService.getDeletedCards).toHaveBeenCalledTimes(1); + expect(mockInquiryService.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 6ddc22a5c3..d0b3589801 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 @@ -2,6 +2,8 @@ import { Injectable } from '@nestjs/common'; 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 { CARD_TYPE } from '../../card/card-type/card-type.entity'; +import { InquiryService } from '../../inquiry/inquiry.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'; @@ -17,6 +19,7 @@ export class UnarchiveCardService { private noticeOfIntentModificationService: NoticeOfIntentModificationService, private notificationService: NotificationService, private planningReferralService: PlanningReferralService, + private inquiryService: InquiryService, ) {} async fetchByFileId(fileId: string) { @@ -26,6 +29,27 @@ export class UnarchiveCardService { status: string; createdAt: number; }[] = []; + await this.fetchAndMapApplications(fileId, result); + + await this.fetchAndMapRecons(fileId, result); + await this.fetchAndMapPlanningReferrals(fileId, result); + await this.fetchAndMapModifications(fileId, result); + await this.fetchAndMapNOIs(fileId, result); + await this.fetchAndMapNotifications(fileId, result); + await this.fetchAndMapInquiries(fileId, result); + + return result; + } + + private async fetchAndMapApplications( + fileId: string, + result: { + cardUuid: string; + type: string; + status: string; + createdAt: number; + }[], + ) { const application = await this.applicationService.getDeletedCard(fileId); if (application) { result.push({ @@ -35,14 +59,6 @@ export class UnarchiveCardService { status: application.card!.status.label, }); } - - await this.fetchAndMapRecons(fileId, result); - await this.fetchAndMapPlanningReferrals(fileId, result); - await this.fetchAndMapModifications(fileId, result); - await this.fetchAndMapNOIs(fileId, result); - await this.fetchAndMapNotifications(fileId, result); - - return result; } private async fetchAndMapModifications( @@ -102,7 +118,7 @@ export class UnarchiveCardService { result.push({ cardUuid: noi.cardUuid, createdAt: noi.auditCreatedAt.getTime(), - type: 'NOI', + type: CARD_TYPE.NOI, status: noi.card!.status.label, }); } @@ -135,7 +151,7 @@ export class UnarchiveCardService { result.push({ cardUuid: notification.cardUuid, createdAt: notification.auditCreatedAt.getTime(), - type: 'NOTI', + type: CARD_TYPE.NOTIFICATION, status: notification.card!.status.label, }); } @@ -150,15 +166,35 @@ export class UnarchiveCardService { createdAt: number; }[], ) { - const notifications = + const planningReferrals = await this.planningReferralService.getDeletedCards(fileId); - for (const referral of notifications) { + for (const referral of planningReferrals) { result.push({ cardUuid: referral.cardUuid, createdAt: referral.auditCreatedAt.getTime(), - type: 'PLAN', + type: CARD_TYPE.PLAN, status: referral.card!.status.label, }); } } + + private async fetchAndMapInquiries( + fileId: string, + result: { + cardUuid: string; + type: string; + status: string; + createdAt: number; + }[], + ) { + const inquiries = await this.inquiryService.getDeletedCards(fileId); + for (const inquiry of inquiries) { + result.push({ + cardUuid: inquiry.cardUuid!, + createdAt: inquiry.auditCreatedAt.getTime(), + type: CARD_TYPE.INQUIRY, + status: inquiry.card!.status.label, + }); + } + } } From 12bc7458b0e0afc9f17dbb24d69d727fd835e9ba Mon Sep 17 00:00:00 2001 From: mhuseinov <61513701+mhuseinov@users.noreply.github.com> Date: Thu, 4 Apr 2024 15:11:29 -0700 Subject: [PATCH 103/153] pgtap (#1581) pgtap test for noi active days count bonus: fixed typo in ETL message --- .../post_launch_commands/planning_reviews.py | 2 +- .../test_calculate_active_days_for_noi.sql | 116 ++++++++++++++++++ 2 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 services/apps/alcs/test/pgtap/test_calculate_active_days_for_noi.sql diff --git a/bin/migrate-oats-data/menu/post_launch_commands/planning_reviews.py b/bin/migrate-oats-data/menu/post_launch_commands/planning_reviews.py index 69a7a5e46e..b9bb13bee0 100644 --- a/bin/migrate-oats-data/menu/post_launch_commands/planning_reviews.py +++ b/bin/migrate-oats-data/menu/post_launch_commands/planning_reviews.py @@ -24,5 +24,5 @@ def planning_review_import(console, args): def planning_review_clean(console): logger.debug("Beginning OATS -> ALCS Planning Review import clean process") - with console.status("[bold green]SRW clean import...\n") as status: + with console.status("[bold green]Planning Review clean import...\n") as status: clean_planning_review() diff --git a/services/apps/alcs/test/pgtap/test_calculate_active_days_for_noi.sql b/services/apps/alcs/test/pgtap/test_calculate_active_days_for_noi.sql new file mode 100644 index 0000000000..17ccb6fb74 --- /dev/null +++ b/services/apps/alcs/test/pgtap/test_calculate_active_days_for_noi.sql @@ -0,0 +1,116 @@ +BEGIN; +-- prepare pgtap +SELECT * from plan(15); + +-- prepare data +-- temp clear existing data +DELETE FROM alcs.holiday_entity; + +-- create holidays +prepare insert_2022_holidays AS +INSERT INTO alcs."holiday_entity" ("uuid", "name", "day") VALUES + ('3fe6132e-ef24-4132-bacf-2a22bdf1a3d1', 'Holiday 1', TO_TIMESTAMP('2023-01-01', 'YYYY-MM-DD')), + ('d93e9d9c-c1f6-4f1d-bcce-781d4d5ecd99', 'Holiday 2', TO_TIMESTAMP('2023-02-02', 'YYYY-MM-DD')), + ('75579109-7394-4561-8e3a-8acd8472f45e', 'Holiday 3', TO_TIMESTAMP('2023-03-03', 'YYYY-MM-DD')), + ('75579109-7394-4561-8e3a-8acd8472f42e', 'Holiday 4', TO_TIMESTAMP('2023-10-10', 'YYYY-MM-DD')); +SELECT lives_ok('insert_2022_holidays', 'should insert holiday_entity'); + +-- create user +prepare insert_users_in_test_calculate_active AS +INSERT INTO alcs."user" (uuid,audit_created_by,audit_updated_by,email,"name",display_name,identity_provider,preferred_username,given_name,family_name,idir_user_guid,idir_user_name,audit_deleted_date_at,audit_created_at,audit_updated_at) VALUES + ('11111111-1111-1111-1111-111111111111','setAuditCreatedBy here',NULL,'some.test.user@bit3.ca','Test User','Test, User LWRS:EX','idir','11111111111111111@idir','User','Test','11111111111111111','UTEST',NULL,'2022-07-26 14:30:04.189077-07','2022-07-26 14:30:04.189077-07'); +SELECT lives_ok('insert_users_in_test_calculate_active', 'should insert user'); + +-- create noi type +prepare insert_noi_type_in_test_calculate_active AS +INSERT INTO alcs.notice_of_intent_type (audit_deleted_date_at,audit_created_at,audit_updated_at,audit_created_by,audit_updated_by,code,description,"label",short_label,background_color,text_color) VALUES + (NULL,'2023-01-01 16:20:41.717',NULL,'alcs-api',NULL,'UNITTEST','UNITTEST','UNITTEST','ROSO','#b2ff59','#000'); +SELECT lives_ok('insert_noi_type_in_test_calculate_active', 'should insert noi_type'); + + +-- create a region +prepare insert_application_region_in_test_calculate_active AS + INSERT INTO alcs.application_region (audit_created_by,audit_updated_by,code,description,"label",audit_deleted_date_at,audit_created_at,audit_updated_at) VALUES + ('migration_seed',NULL,'TEST','NOI test st','Incoming / Prelim Review',NULL,'2023-01-01 14:30:38.573',NULL); +SELECT lives_ok('insert_application_region_in_test_calculate_active', 'should insert application_region'); + +prepare insert_local_government_in_test_calculate_active as + INSERT INTO alcs.local_government (uuid,audit_deleted_date_at,audit_created_at,audit_updated_at,audit_created_by,audit_updated_by,"name",preferred_region_code) VALUES + ('11111111-1111-1111-1111-111111111111',NULL,'2023-01-01 16:28:39.371', NULL,'unit_test',NULL,'Village of Mock','TEST'); +SELECT lives_ok('insert_local_government_in_test_calculate_active', 'should insert local_government'); + +-- create noi status +prepare insert_card_status_in_test_calculate_active AS +INSERT INTO alcs.card_status (audit_created_by,audit_updated_by,code,description,"label",audit_deleted_date_at,audit_created_at,audit_updated_at) VALUES + ('migration_seed',NULL,'TEST','NOI test st','Incoming / Prelim Review',NULL,'2023-01-01 14:30:38.573',NULL); +SELECT lives_ok('insert_card_status_in_test_calculate_active', 'should insert card_status'); + +-- create board +prepare insert_board_in_test_calculate_active AS +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 + ('11111111-1111-1111-1111-111111111111',NULL,'2023-01-01 13:49:58.829', NULL,'unit_test',NULL,'mock','Mock Panel',false); +SELECT lives_ok('insert_board_in_test_calculate_active', 'should insert board'); + +-- create card type +prepare insert_card_type_in_test_calculate_active AS +INSERT INTO alcs.card_type (audit_deleted_date_at,audit_created_at,audit_updated_at,audit_created_by,audit_updated_by,"label",code,description) VALUES + (NULL,'2022-09-22 06:52:12.366',NULL,'migration_seed',NULL,'Mock','MOCK','Mock card type'); +SELECT lives_ok('insert_card_type_in_test_calculate_active', 'should insert card_type'); + +-- create cards +prepare insert_cards_in_test_calculate_active AS +INSERT INTO alcs.card (uuid,audit_deleted_date_at,audit_created_at,audit_updated_at,audit_created_by,audit_updated_by,high_priority,status_code,board_uuid,assignee_uuid,created_at,type_code) VALUES + ('11111111-1111-1111-1111-111111111111',NULL,'2023-01-01 14:39:17.968667-07', NULL,'unit_test',NULL,false,'TEST','11111111-1111-1111-1111-111111111111',NULL,'2023-01-01 14:39:17.968667-07','MOCK'), + ('22222222-2222-2222-2222-222222222222',NULL,'2023-01-01 14:39:17.968667-07', NULL,'unit_test',NULL,false,'TEST','11111111-1111-1111-1111-111111111111',NULL,'2023-01-01 14:39:17.968667-07','MOCK'), + ('33333333-3333-3333-3333-333333333333',NULL,'2023-01-01 14:39:17.968667-07', NULL,'unit_test',NULL,false,'TEST','11111111-1111-1111-1111-111111111111',NULL,'2023-01-01 14:39:17.968667-07','MOCK'), + ('44444444-4444-4444-4444-444444444444',NULL,'2023-01-01 14:39:17.968667-07', NULL,'unit_test',NULL,false,'TEST','11111111-1111-1111-1111-111111111111',NULL,'2023-01-01 14:39:17.968667-07','MOCK'); +SELECT lives_ok('insert_cards_in_test_calculate_active', 'should insert card'); + +-- create nois +prepare insert_noi_in_test_calculate_active AS +INSERT INTO alcs.notice_of_intent (uuid,audit_created_by,audit_updated_by,file_number,region_code,audit_deleted_date_at,audit_created_at,audit_updated_at,created_at,applicant,type_code,date_submitted_to_alc,date_acknowledged_complete,decision_date, card_uuid, local_government_uuid, date_received_all_items) VALUES + ('11111111-1111-1111-1111-111111111111','unit_test','unit_test','11111111-1111-1111-1111-111111111111','TEST',NULL,'2023-01-01 14:33:12.925','2023-01-01 17:37:06.292','2023-01-01 13:06:09.393','unit test 1','UNITTEST', '2023-01-01 13:06:09.393',NULL,NULL, '11111111-1111-1111-1111-111111111111','11111111-1111-1111-1111-111111111111', NULL), + ('22222222-2222-2222-2222-222222222222','unit_test','unit_test','22222222-2222-2222-2222-222222222222','TEST',NULL,'2023-01-01 14:33:12.925','2023-01-01 17:37:06.292','2023-01-01 13:06:09.393','unit test 1','UNITTEST', '2023-01-01 13:06:09.393','2023-01-01 13:06:09.393',NULL, NULL,'11111111-1111-1111-1111-111111111111','2023-01-01 13:06:09.393'), + ('33333333-3333-3333-3333-333333333333','unit_test','unit_test','33333333-3333-3333-3333-333333333333','TEST',NULL,'2023-01-01 14:33:12.925','2023-01-01 17:37:06.292','2023-01-01 13:06:09.393','unit test 2','UNITTEST', '2023-01-01 13:06:09.393','2023-01-01 13:06:09.393','2023-01-07 13:06:09.393','33333333-3333-3333-3333-333333333333','11111111-1111-1111-1111-111111111111','2023-01-02 13:06:09.393'), + ('44444444-4444-4444-4444-444444444444','unit_test','unit_test','44444444-4444-4444-4444-444444444444','TEST',NULL,'2023-01-01 14:33:12.925','2023-01-01 17:37:06.292','2023-01-01 13:06:09.393','unit test 3','UNITTEST', '2023-01-01 13:06:09.393','2023-01-01 13:06:09.393','2023-01-11 13:06:09.393','44444444-4444-4444-4444-444444444444','11111111-1111-1111-1111-111111111111','2023-01-11 13:06:09.393'); +SELECT lives_ok('insert_noi_in_test_calculate_active', 'should insert noi'); + + +-- create notice_of_intent_meeting_type records +prepare insert_notice_of_intent_meeting_type AS +INSERT INTO alcs.notice_of_intent_meeting_type (audit_deleted_date_at,audit_created_at,audit_updated_at,audit_created_by,audit_updated_by,"label",code,description) VALUES + (NULL,'2023-06-12 16:10:29.820',NULL,'migration_seed',NULL,'UNIT TEST TYPE','IRTEST','UNIT TEST TYPE'); +SELECT lives_ok('insert_notice_of_intent_meeting_type', 'should insert noi meeting type'); + + +prepare insert_notice_of_intent_meeting AS +INSERT INTO alcs.notice_of_intent_meeting (audit_deleted_date_at,audit_created_at,audit_updated_at,audit_created_by,audit_updated_by,"uuid",description,start_date,end_date,type_code,notice_of_intent_uuid) VALUES + (NULL,'2023-01-01 17:23:28.033','2023-01-01 17:23:28.033','alcs-api',NULL,'11111111-1111-1111-1111-111111111111',NULL,'2023-01-01 17:23:28.033','2023-01-01 17:23:28.033','IRTEST','11111111-1111-1111-1111-111111111111'), + (NULL,'2023-01-02 17:23:28.033','2023-01-07 17:23:28.033','alcs-api',NULL,'33333333-3333-3333-3333-333333333333',NULL,'2023-01-02 00:00:00.000 -0700','2023-01-03 00:00:00.000 -0700','IRTEST','33333333-3333-3333-3333-333333333333'); +SELECT lives_ok('insert_notice_of_intent_meeting', 'should insert notice_of_intent_meeting'); +-- test cases + +-- no dates set yet +SELECT override.freeze_time('2022-08-02 00:00:00.000 -0700'); +PREPARE actual_result_no_dates AS SELECT * FROM alcs.calculate_noi_active_days('{11111111-1111-1111-1111-111111111111}'::uuid[]); +PREPARE expected_result_no_dates AS VALUES ('11111111-1111-1111-1111-111111111111'::uuid, NULL::integer, NULL::integer); +SELECT results_eq('actual_result_no_dates', 'expected_result_no_dates', 'Should be 0 active days and 0 paused days'); + +-- should return active days +SELECT override.freeze_time('2023-01-01 00:00:00.000 -0700'); +PREPARE actual_result_4_active_days AS SELECT * from alcs.calculate_noi_active_days('{33333333-3333-3333-3333-333333333333}'::uuid[]); +PREPARE expected_result_4_active_days AS VALUES ('33333333-3333-3333-3333-333333333333'::uuid,4,NULL::integer); +SELECT results_eq('actual_result_4_active_days', 'expected_result_4_active_days', 'Should be active days'); + +-- should return 0 active days +SELECT override.freeze_time('2023-01-01 00:00:00.000 -0700'); +PREPARE actual_result_0_active_days AS SELECT * from alcs.calculate_noi_active_days('{44444444-4444-4444-4444-444444444444}'::uuid[]); +PREPARE expected_result_0_active_days AS VALUES ('44444444-4444-4444-4444-444444444444'::uuid,0,NULL::integer); +SELECT results_eq('actual_result_0_active_days', 'expected_result_0_active_days', 'Should be active days'); + + +-- properly finish test +SELECT * FROM finish(); + +-- rollback all changes +ROLLBACK; \ No newline at end of file From 33bb45c307090abbc3af57e01d5c7a07b59d036a Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Thu, 4 Apr 2024 15:54:38 -0700 Subject: [PATCH 104/153] Add backend tests * Improving that coverage! --- ...ication-decision-condition.service.spec.ts | 83 +++++++++++++++++++ .../local-government.service.spec.ts | 32 +++++++ ...-intent-decision-condition.service.spec.ts | 21 +++++ .../common/tracking/tracking.service.spec.ts | 44 ++++++++++ .../portal/public/public.controller.spec.ts | 54 ++++++++++++ .../status/public-status.controller.spec.ts | 50 ++++++++++- 6 files changed, 282 insertions(+), 2 deletions(-) create mode 100644 services/apps/alcs/src/common/tracking/tracking.service.spec.ts diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.service.spec.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.service.spec.ts index b0dc5475b8..bf759c1553 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.service.spec.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.service.spec.ts @@ -184,4 +184,87 @@ describe('ApplicationDecisionConditionService', () => { ).toBeCalledTimes(0); expect(mockApplicationDecisionConditionRepository.save).toBeCalledTimes(0); }); + + it('should call update on the repo and return the updated object', async () => { + const mockCondition = new ApplicationDecisionCondition(); + + mockApplicationDecisionConditionRepository.update.mockResolvedValue( + {} as any, + ); + mockApplicationDecisionConditionRepository.findOneOrFail.mockResolvedValue( + mockCondition, + ); + + const result = await service.update(mockCondition, { + approvalDependant: false, + }); + + expect( + mockApplicationDecisionConditionRepository.update, + ).toHaveBeenCalledTimes(1); + expect( + mockApplicationDecisionConditionRepository.findOneOrFail, + ).toHaveBeenCalledTimes(1); + expect(result).toEqual(mockCondition); + }); + + it('should call findBy on the repo for getPlanNumbers', async () => { + mockApplicationDecisionConditionComponentPlanNumber.findBy.mockResolvedValue( + [], + ); + + const planNumbers = await service.getPlanNumbers('uuid'); + + expect( + mockApplicationDecisionConditionComponentPlanNumber.findBy, + ).toHaveBeenCalledTimes(1); + expect(planNumbers).toBeDefined(); + }); + + it('should create a new join record if one does not exist for updateConditionPlanNumbers', async () => { + mockApplicationDecisionConditionComponentPlanNumber.findOneBy.mockResolvedValue( + null, + ); + mockApplicationDecisionConditionComponentPlanNumber.save.mockResolvedValue( + new ApplicationDecisionConditionComponentPlanNumber(), + ); + + await service.updateConditionPlanNumbers('uuid', 'uuid', 'plan-number'); + + expect( + mockApplicationDecisionConditionComponentPlanNumber.findOneBy, + ).toHaveBeenCalledTimes(1); + expect( + mockApplicationDecisionConditionComponentPlanNumber.save, + ).toHaveBeenCalledTimes(1); + + const savedEntity = + mockApplicationDecisionConditionComponentPlanNumber.save.mock.calls[0][0]; + expect(savedEntity.planNumbers).toEqual('plan-number'); + }); + + it('should update the existing join record if one does exist for updateConditionPlanNumbers', async () => { + const mockNumber = new ApplicationDecisionConditionComponentPlanNumber(); + mockApplicationDecisionConditionComponentPlanNumber.findOneBy.mockResolvedValue( + mockNumber, + ); + mockApplicationDecisionConditionComponentPlanNumber.save.mockResolvedValue( + new ApplicationDecisionConditionComponentPlanNumber(), + ); + + await service.updateConditionPlanNumbers('uuid', 'uuid', 'plan-number'); + + expect( + mockApplicationDecisionConditionComponentPlanNumber.findOneBy, + ).toHaveBeenCalledTimes(1); + expect( + mockApplicationDecisionConditionComponentPlanNumber.save, + ).toHaveBeenCalledTimes(1); + + const savedEntity = + mockApplicationDecisionConditionComponentPlanNumber.save.mock.calls[0][0]; + expect(savedEntity).toBe(mockNumber); + expect(savedEntity.planNumbers).toEqual('plan-number'); + expect(mockNumber.planNumbers).toEqual('plan-number'); + }); }); diff --git a/services/apps/alcs/src/alcs/local-government/local-government.service.spec.ts b/services/apps/alcs/src/alcs/local-government/local-government.service.spec.ts index 054f9575da..40e6ab8d0c 100644 --- a/services/apps/alcs/src/alcs/local-government/local-government.service.spec.ts +++ b/services/apps/alcs/src/alcs/local-government/local-government.service.spec.ts @@ -46,6 +46,30 @@ describe('LocalGovernmentService', () => { expect(mockRepository.find).toHaveBeenCalledTimes(1); }); + it('should call repositories when listActive', async () => { + mockRepository.find.mockResolvedValue([]); + + await service.listActive(); + + expect(mockRepository.find).toHaveBeenCalledTimes(1); + }); + + it('should call repositories when getByName', async () => { + mockRepository.findOne.mockResolvedValue(null); + + await service.getByName('name'); + + expect(mockRepository.findOne).toHaveBeenCalledTimes(1); + }); + + it('should call repositories when getByBuid', async () => { + mockRepository.findOne.mockResolvedValue(null); + + await service.getByGuid('name'); + + expect(mockRepository.findOne).toHaveBeenCalledTimes(1); + }); + it('should call repository on getByUuId', async () => { const uuid = 'fake'; mockRepository.findOneOrFail.mockResolvedValue(new LocalGovernment()); @@ -93,4 +117,12 @@ describe('LocalGovernmentService', () => { expect(mockRepository.save).toHaveBeenCalledTimes(1); }); + + it('should call repositories when fetching for search', async () => { + mockRepository.findAndCount.mockResolvedValue([[], 0]); + + await service.fetch(0, 5, 'text'); + + expect(mockRepository.findAndCount).toHaveBeenCalledTimes(1); + }); }); diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.service.spec.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.service.spec.ts index f4e00a7089..e9abad2064 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.service.spec.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.service.spec.ts @@ -143,4 +143,25 @@ describe('NoticeOfIntentDecisionConditionService', () => { expect(mockNOIDecisionConditionRepository.findOneOrFail).toBeCalledTimes(0); expect(mockNOIDecisionConditionRepository.save).toBeCalledTimes(0); }); + + it('should update on the repo for update', async () => { + const existingCondition = new NoticeOfIntentDecisionCondition(); + mockNOIDecisionConditionRepository.update.mockResolvedValue({} as any); + mockNOIDecisionConditionRepository.findOneOrFail.mockResolvedValue( + existingCondition, + ); + + const result = await service.update(existingCondition, { + administrativeFee: 50, + }); + + expect(result).toBeDefined(); + expect(mockNOIDecisionConditionRepository.update).toBeCalledTimes(1); + expect( + mockNOIDecisionConditionRepository.update.mock.calls[0][1][ + 'administrativeFee' + ], + ).toEqual(50); + expect(mockNOIDecisionConditionRepository.findOneOrFail).toBeCalledTimes(1); + }); }); diff --git a/services/apps/alcs/src/common/tracking/tracking.service.spec.ts b/services/apps/alcs/src/common/tracking/tracking.service.spec.ts new file mode 100644 index 0000000000..70a4758b3d --- /dev/null +++ b/services/apps/alcs/src/common/tracking/tracking.service.spec.ts @@ -0,0 +1,44 @@ +import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; +import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { User } from '../../user/user.entity'; +import { FileViewed } from './file-viewed.entity'; +import { TrackingService } from './tracking.service'; + +describe('TrackingService', () => { + let service: TrackingService; + + let mockRepo: DeepMocked>; + + beforeEach(async () => { + mockRepo = createMock(); + + const module: TestingModule = await Test.createTestingModule({ + providers: [ + TrackingService, + { + provide: getRepositoryToken(FileViewed), + useValue: mockRepo, + }, + ], + }).compile(); + + service = module.get(TrackingService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + it('should insert a record for trackView', async () => { + mockRepo.insert.mockResolvedValue({} as any); + + await service.trackView(new User(), 'fileNumber'); + + expect(mockRepo.insert).toHaveBeenCalledTimes(1); + expect(mockRepo.insert.mock.calls[0][0]['fileNumber']).toEqual( + 'fileNumber', + ); + }); +}); diff --git a/services/apps/alcs/src/portal/public/public.controller.spec.ts b/services/apps/alcs/src/portal/public/public.controller.spec.ts index 15213deeb5..ad3744f294 100644 --- a/services/apps/alcs/src/portal/public/public.controller.spec.ts +++ b/services/apps/alcs/src/portal/public/public.controller.spec.ts @@ -57,6 +57,24 @@ describe('PublicController', () => { expect(mockAppService.getPublicData).toHaveBeenCalledTimes(1); }); + it('should call through to service for loading an application document download', async () => { + mockAppService.getDownloadUrl.mockResolvedValue({} as any); + + const fileId = 'file-id'; + await controller.getApplicationDocumentDownload(fileId, ''); + + expect(mockAppService.getDownloadUrl).toHaveBeenCalledTimes(1); + }); + + it('should call through to service for loading an application document open', async () => { + mockAppService.getInlineUrl.mockResolvedValue({} as any); + + const fileId = 'file-id'; + await controller.getApplicationDocumentOpen(fileId, ''); + + expect(mockAppService.getInlineUrl).toHaveBeenCalledTimes(1); + }); + it('should call through to service for loading a notice of intent', async () => { mockNOIService.getPublicData.mockResolvedValue({} as any); @@ -66,6 +84,24 @@ describe('PublicController', () => { expect(mockNOIService.getPublicData).toHaveBeenCalledTimes(1); }); + it('should call through to service for loading a notice of intent document download', async () => { + mockNOIService.getDownloadUrl.mockResolvedValue({} as any); + + const fileId = 'file-id'; + await controller.getNoticeOfIntentDocumentDownload(fileId, ''); + + expect(mockNOIService.getDownloadUrl).toHaveBeenCalledTimes(1); + }); + + it('should call through to service for loading a notice of intent document open', async () => { + mockNOIService.getInlineUrl.mockResolvedValue({} as any); + + const fileId = 'file-id'; + await controller.getNoticeOfIntentDocumentOpen(fileId, ''); + + expect(mockNOIService.getInlineUrl).toHaveBeenCalledTimes(1); + }); + it('should call through to service for loading a notification', async () => { mockNotificationService.getPublicData.mockResolvedValue({} as any); @@ -74,4 +110,22 @@ describe('PublicController', () => { expect(mockNotificationService.getPublicData).toHaveBeenCalledTimes(1); }); + + it('should call through to service for loading a notice of intent document download', async () => { + mockNotificationService.getDownloadUrl.mockResolvedValue({} as any); + + const fileId = 'file-id'; + await controller.getNotificationDocumentDownload(fileId, ''); + + expect(mockNotificationService.getDownloadUrl).toHaveBeenCalledTimes(1); + }); + + it('should call through to service for loading a notice of intent document open', async () => { + mockNotificationService.getInlineUrl.mockResolvedValue({} as any); + + const fileId = 'file-id'; + await controller.getNotificationDocumentOpen(fileId, ''); + + expect(mockNotificationService.getInlineUrl).toHaveBeenCalledTimes(1); + }); }); diff --git a/services/apps/alcs/src/portal/public/status/public-status.controller.spec.ts b/services/apps/alcs/src/portal/public/status/public-status.controller.spec.ts index 228474bd4b..271737f095 100644 --- a/services/apps/alcs/src/portal/public/status/public-status.controller.spec.ts +++ b/services/apps/alcs/src/portal/public/status/public-status.controller.spec.ts @@ -1,11 +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 { classes } from 'automapper-classes'; +import { AutomapperModule } from 'automapper-nestjs'; import { ClsService } from 'nestjs-cls'; import { mockKeyCloakProviders } from '../../../../test/mocks/mockTypes'; import { ApplicationSubmissionStatusService } from '../../../alcs/application/application-submission-status/application-submission-status.service'; +import { ApplicationSubmissionStatusType } from '../../../alcs/application/application-submission-status/submission-status-type.entity'; +import { NotificationSubmissionStatusType } from '../../../alcs/notification/notification-submission-status/notification-status-type.entity'; import { NotificationSubmissionStatusService } from '../../../alcs/notification/notification-submission-status/notification-submission-status.service'; +import { ApplicationSubmissionProfile } from '../../../common/automapper/application-submission.automapper.profile'; +import { NotificationSubmissionProfile } from '../../../common/automapper/notification-submission.automapper.profile'; import { PublicStatusController } from './public-status.controller'; describe('PublicStatusController', () => { @@ -24,6 +28,8 @@ describe('PublicStatusController', () => { }), ], providers: [ + ApplicationSubmissionProfile, + NotificationSubmissionProfile, { provide: ApplicationSubmissionStatusService, useValue: mockAppStatusService, @@ -47,4 +53,44 @@ describe('PublicStatusController', () => { it('should be defined', () => { expect(controller).toBeDefined(); }); + + it('should be load and map application and noi statuses', async () => { + mockAppStatusService.listStatuses.mockResolvedValue([ + new ApplicationSubmissionStatusType({ + code: 'ALCD', + }), + ]); + mockNotiStatusService.listStatuses.mockResolvedValue([ + new NotificationSubmissionStatusType({ + code: 'ALCR', + }), + ]); + + const statuses = await controller.getStatuses(); + + expect(statuses).toBeDefined(); + expect(statuses.length).toEqual(2); + expect(mockAppStatusService.listStatuses).toHaveBeenCalledTimes(1); + expect(mockNotiStatusService.listStatuses).toHaveBeenCalledTimes(1); + }); + + it('should filter out non public statuses', async () => { + mockAppStatusService.listStatuses.mockResolvedValue([ + new ApplicationSubmissionStatusType({ + code: 'NOTR', + }), + ]); + mockNotiStatusService.listStatuses.mockResolvedValue([ + new NotificationSubmissionStatusType({ + code: 'NOTR', + }), + ]); + + const statuses = await controller.getStatuses(); + + expect(statuses).toBeDefined(); + expect(statuses.length).toEqual(0); + expect(mockAppStatusService.listStatuses).toHaveBeenCalledTimes(1); + expect(mockNotiStatusService.listStatuses).toHaveBeenCalledTimes(1); + }); }); From 401fb3d38d9b57101dc5e64685401cab79693529 Mon Sep 17 00:00:00 2001 From: Liam Stoddard Date: Thu, 4 Apr 2024 16:29:04 -0700 Subject: [PATCH 105/153] inquiry staff journal import + etl bugfix --- .../post_launch/migrate_documents.py | 12 +- .../oats_documents_to_alcs_documents_srw.py | 2 +- .../inquiry/inquiry_migration.py | 6 + .../inquiry/inquiry_staff_journal.py | 150 ++++++++++++++++++ .../sql/inquiry_staff_journal_insert.sql | 9 ++ .../inquiry_staff_journal_insert_count.sql | 5 + .../menu/post_launch_commands/__init__.py | 4 +- .../menu/post_launch_commands/clean_all.py | 13 +- .../menu/post_launch_commands/documents.py | 21 +++ .../menu/post_launch_commands/import_all.py | 26 ++- bin/migrate-oats-data/migrate.py | 8 +- 11 files changed, 246 insertions(+), 10 deletions(-) create mode 100644 bin/migrate-oats-data/inquiry/inquiry_staff_journal.py create mode 100644 bin/migrate-oats-data/inquiry/sql/inquiry_staff_journal_insert.sql create mode 100644 bin/migrate-oats-data/inquiry/sql/inquiry_staff_journal_insert_count.sql diff --git a/bin/migrate-oats-data/documents/post_launch/migrate_documents.py b/bin/migrate-oats-data/documents/post_launch/migrate_documents.py index 6e693d723a..020b38288d 100644 --- a/bin/migrate-oats-data/documents/post_launch/migrate_documents.py +++ b/bin/migrate-oats-data/documents/post_launch/migrate_documents.py @@ -19,12 +19,18 @@ def import_documents(batch_size): import_oats_srw_documents(batch_size) link_srw_documents(batch_size) - import_oats_pr_documents(batch_size), - link_pr_documents(batch_size) def clean_documents(): clean_notification_documents() - clean_planning_review_documents() document_clean() + + +def import_documents_pr_inq(batch_size): + import_oats_pr_documents(batch_size) + link_pr_documents(batch_size) + + +def clean_documents_pr_inq(): + clean_planning_review_documents() document_pr_clean() diff --git a/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_srw.py b/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_srw.py index 10f1c932eb..4385b6e43c 100644 --- a/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_srw.py +++ b/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_srw.py @@ -181,7 +181,7 @@ def document_clean(conn=None): logger.info("Start documents cleaning") with conn.cursor() as cursor: cursor.execute( - f"DELETE FROM alcs.document WHERE audit_created_by = '{OATS_ETL_USER}' AND audit_created_at > '2024-02-08';" + f"DELETE FROM alcs.document WHERE audit_created_by = '{OATS_ETL_USER}' AND audit_created_at > '2024-02-08' AND oats_application_id IS NOT NULL;" ) conn.commit() logger.info(f"Deleted items count = {cursor.rowcount}") diff --git a/bin/migrate-oats-data/inquiry/inquiry_migration.py b/bin/migrate-oats-data/inquiry/inquiry_migration.py index 7102a5c531..458ba4a2d5 100644 --- a/bin/migrate-oats-data/inquiry/inquiry_migration.py +++ b/bin/migrate-oats-data/inquiry/inquiry_migration.py @@ -1,11 +1,17 @@ from .inquiry_base import init_inquiries, clean_inquiries from .inquiry_inquirer_info import process_inquiry_inquirer_fields +from .inquiry_staff_journal import ( + process_inquiry_staff_journal, + clean_inquiry_staff_journal, +) def process_inquiry(batch_size): init_inquiries(batch_size) process_inquiry_inquirer_fields(batch_size) + process_inquiry_staff_journal(batch_size) def clean_inquiry(): + clean_inquiry_staff_journal() clean_inquiries() diff --git a/bin/migrate-oats-data/inquiry/inquiry_staff_journal.py b/bin/migrate-oats-data/inquiry/inquiry_staff_journal.py new file mode 100644 index 0000000000..3d70171530 --- /dev/null +++ b/bin/migrate-oats-data/inquiry/inquiry_staff_journal.py @@ -0,0 +1,150 @@ +from common import ( + BATCH_UPLOAD_SIZE, + OATS_ETL_USER, + setup_and_get_logger, + add_timezone_and_keep_date_part, + DEFAULT_ETL_USER_UUID, +) +from db import inject_conn_pool +from psycopg2.extras import RealDictCursor, execute_batch + +etl_name = "planning_review_staff_journal" +logger = setup_and_get_logger(etl_name) + + +@inject_conn_pool +def process_inquiry_staff_journal(conn=None, batch_size=BATCH_UPLOAD_SIZE): + """ + This function is responsible for initializing entries for inquiries in staff_journal table in ALCS. + + Args: + conn (psycopg2.extensions.connection): PostgreSQL database connection. Provided by the decorator. + batch_size (int): The number of items to process at once. Defaults to BATCH_UPLOAD_SIZE. + """ + + logger.info(f"Start {etl_name}") + with conn.cursor(cursor_factory=RealDictCursor) as cursor: + with open( + "inquiry/sql/inquiry_staff_journal_insert_count.sql", + "r", + encoding="utf-8", + ) as sql_file: + count_query = sql_file.read() + cursor.execute(count_query) + count_total = dict(cursor.fetchone())["count"] + logger.info(f"Total staff journal entry data to insert: {count_total}") + + failed_inserts_count = 0 + successful_inserts_count = 0 + last_entry_id = 0 + with open( + "inquiry/sql/inquiry_staff_journal_insert.sql", + "r", + encoding="utf-8", + ) as sql_file: + submission_sql = sql_file.read() + while True: + cursor.execute( + f"{submission_sql} WHERE oin.issue_note_id > '{last_entry_id}' ORDER BY oin.issue_note_id;" + ) + + rows = cursor.fetchmany(batch_size) + + if not rows: + break + try: + users_to_be_inserted_count = len(rows) + + _insert_entries(conn, batch_size, cursor, rows) + + successful_inserts_count = ( + successful_inserts_count + users_to_be_inserted_count + ) + last_entry_id = dict(rows[-1])["issue_note_id"] + + logger.debug( + f"retrieved/inserted items count: {users_to_be_inserted_count}; total successfully inserted entries so far {successful_inserts_count}; last inserted note_id: {last_entry_id}" + ) + except Exception as err: + logger.exception("") + conn.rollback() + failed_inserts_count = count_total - successful_inserts_count + last_entry_id = last_entry_id + 1 + + logger.info( + f"Finished {etl_name}: total amount of successful inserts {successful_inserts_count}, total failed inserts {failed_inserts_count}" + ) + + +def _insert_entries(conn, batch_size, cursor, rows): + query = _get_insert_query() + parsed_data_list = _prepare_journal_data(rows) + + if len(parsed_data_list) > 0: + execute_batch(cursor, query, parsed_data_list, page_size=batch_size) + + conn.commit() + + +def _get_insert_query(): + query = f""" + INSERT INTO alcs.staff_journal ( + body, + edited, + inquiry_uuid, + created_at, + author_uuid, + audit_created_by + ) + VALUES ( + %(note_text)s, + %(edit)s, + %(uuid)s, + %(note_date)s, + %(user)s, + '{OATS_ETL_USER}' + ) + ON CONFLICT DO NOTHING; + """ + return query + + +def _prepare_journal_data(row_data_list): + data_list = [] + for row in row_data_list: + data = dict(row) + data = _map_revision(data) + data = _map_timezone(data) + data["user"] = DEFAULT_ETL_USER_UUID + data_list.append(dict(data)) + return data_list + + +def _map_revision(data): + revision = data.get("revision_count", "") + # check if edited + if revision == 0: + data["edit"] = False + else: + data["edit"] = True + return data + + +def _map_timezone(data): + date = data.get("note_date", "") + note_date = add_timezone_and_keep_date_part(date) + data["note_date"] = note_date + return data + + +@inject_conn_pool +def clean_inquiry_staff_journal(conn=None): + logger.info("Start staff journal cleaning") + # Only clean inquiries + with conn.cursor() as cursor: + cursor.execute( + f"DELETE FROM alcs.staff_journal asj WHERE asj.audit_created_by = '{OATS_ETL_USER}' AND asj.inquiry_uuid IS NOT NULL" + ) + logger.info(f"Deleted items count = {cursor.rowcount}") + + conn.commit() diff --git a/bin/migrate-oats-data/inquiry/sql/inquiry_staff_journal_insert.sql b/bin/migrate-oats-data/inquiry/sql/inquiry_staff_journal_insert.sql new file mode 100644 index 0000000000..eb3f6f0c28 --- /dev/null +++ b/bin/migrate-oats-data/inquiry/sql/inquiry_staff_journal_insert.sql @@ -0,0 +1,9 @@ +SELECT + oin.note_text, + oin.note_date, + oin.revision_count, + oin.issue_note_id, + i."uuid" +FROM + oats.oats_issue_notes oin + JOIN alcs.inquiry i ON i.file_number = oin.issue_id::TEXT \ No newline at end of file diff --git a/bin/migrate-oats-data/inquiry/sql/inquiry_staff_journal_insert_count.sql b/bin/migrate-oats-data/inquiry/sql/inquiry_staff_journal_insert_count.sql new file mode 100644 index 0000000000..f43fb1301f --- /dev/null +++ b/bin/migrate-oats-data/inquiry/sql/inquiry_staff_journal_insert_count.sql @@ -0,0 +1,5 @@ +SELECT + COUNT(*) +FROM + oats.oats_issue_notes oin + JOIN alcs.inquiry i ON i.file_number = oin.issue_id::TEXT \ No newline at end of file diff --git a/bin/migrate-oats-data/menu/post_launch_commands/__init__.py b/bin/migrate-oats-data/menu/post_launch_commands/__init__.py index 3a21dc1c07..b08242d8c4 100644 --- a/bin/migrate-oats-data/menu/post_launch_commands/__init__.py +++ b/bin/migrate-oats-data/menu/post_launch_commands/__init__.py @@ -1,5 +1,5 @@ -from .import_all import import_all -from .clean_all import clean_all +from .import_all import import_all, import_all_pr_inq +from .clean_all import clean_all, clean_all_pr_inq from .applications import * from .notice_of_intents import * from .srws import * diff --git a/bin/migrate-oats-data/menu/post_launch_commands/clean_all.py b/bin/migrate-oats-data/menu/post_launch_commands/clean_all.py index c6cdf5c9cc..33ca1af6eb 100644 --- a/bin/migrate-oats-data/menu/post_launch_commands/clean_all.py +++ b/bin/migrate-oats-data/menu/post_launch_commands/clean_all.py @@ -3,8 +3,9 @@ ) from noi.post_launch import clean_notice_of_intent from srw.post_launch import clean_srw -from documents.post_launch import clean_documents +from documents.post_launch import clean_documents, clean_documents_pr_inq from planning_review.migrate_planning_review import clean_planning_review +from inquiry.inquiry_migration import clean_inquiry def clean_all(console, args): @@ -17,3 +18,13 @@ def clean_all(console, args): clean_planning_review() console.log("Done") + + +def clean_all_pr_inq(console, args): + with console.status("[bold green]Cleaning previous ETL...\n") as status: + console.log("Cleaning data:") + clean_documents_pr_inq() + clean_planning_review() + clean_inquiry() + + console.log("Done") diff --git a/bin/migrate-oats-data/menu/post_launch_commands/documents.py b/bin/migrate-oats-data/menu/post_launch_commands/documents.py index d0a1edf93e..9e48198fcf 100644 --- a/bin/migrate-oats-data/menu/post_launch_commands/documents.py +++ b/bin/migrate-oats-data/menu/post_launch_commands/documents.py @@ -1,6 +1,8 @@ from documents.post_launch import ( import_documents, clean_documents, + import_documents_pr_inq, + clean_documents_pr_inq, ) from srw.post_launch.srw_migration import srw_survey_plan_update @@ -24,3 +26,22 @@ def document_clean(console): console.log("Beginning ALCS Document clean") with console.status("[bold green]Cleaning ALCS Documents...\n") as status: clean_documents() + + +def document_import_pr_inq(console, args): + console.log("Beginning OATS -> ALCS document import process") + with console.status( + "[bold green]document import (Document related table update in ALCS)...\n" + ) as status: + if args.batch_size: + import_batch_size = args.batch_size + + console.log(f"Processing documents import in batch size = {import_batch_size}") + + import_documents_pr_inq(batch_size=import_batch_size) + + +def document_clean_pr_inq(console): + console.log("Beginning ALCS Document clean") + with console.status("[bold green]Cleaning ALCS Documents...\n") as status: + clean_documents_pr_inq() diff --git a/bin/migrate-oats-data/menu/post_launch_commands/import_all.py b/bin/migrate-oats-data/menu/post_launch_commands/import_all.py index 4fde3a61eb..ca23d31f1e 100644 --- a/bin/migrate-oats-data/menu/post_launch_commands/import_all.py +++ b/bin/migrate-oats-data/menu/post_launch_commands/import_all.py @@ -3,10 +3,14 @@ process_notice_of_intent, ) from srw.post_launch.srw_migration import process_srw, srw_survey_plan_update -from documents.post_launch.migrate_documents import import_documents +from documents.post_launch.migrate_documents import ( + import_documents, + import_documents_pr_inq, +) from planning_review.migrate_planning_review import ( process_planning_review, ) +from inquiry.inquiry_migration import process_inquiry def import_all(console, args): @@ -37,3 +41,23 @@ def import_all(console, args): process_planning_review(batch_size=import_batch_size) console.log("Done") + + +def import_all_pr_inq(console, args): + console.log("Beginning OATS -> ALCS import process") + + with console.status("[bold green]Import OATS into ALCS...\n") as status: + + if args and args.batch_size: + import_batch_size = args.batch_size + + console.log("Process Planning Reviews") + process_planning_review(batch_size=import_batch_size) + + console.log("Process Inquiries") + process_inquiry(batch_size=import_batch_size) + + console.log("Processing Documents") + import_documents_pr_inq(batch_size=import_batch_size) + + console.log("Done") diff --git a/bin/migrate-oats-data/migrate.py b/bin/migrate-oats-data/migrate.py index 25f92cbd5a..d161afb146 100644 --- a/bin/migrate-oats-data/migrate.py +++ b/bin/migrate-oats-data/migrate.py @@ -20,6 +20,10 @@ planning_review_import, inquiry_import, inquiry_clean, + document_import_pr_inq, + document_clean_pr_inq, + import_all_pr_inq, + clean_all_pr_inq, ) from db import connection_pool from common import BATCH_UPLOAD_SIZE, setup_and_get_logger @@ -38,9 +42,9 @@ # Call function corresponding to selected action using match statement match args.command: case "import": - import_all(console, args) + import_all_pr_inq(console, args) case "clean": - clean_all(console, args) + clean_all_pr_inq(console, args) case "noi-import": notice_of_intent_import(console, args) case "noi-clean": From 6a96f793acda7a0de907ad5b366208a20ee18bea Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Mon, 8 Apr 2024 11:58:46 -0700 Subject: [PATCH 106/153] New Release and Revert Dialogs * For NOI and App Decisions --- .../decision-input-v2.component.ts | 7 +- .../decision-v2/decision-v2.component.ts | 5 ++ .../release-dialog.component.html | 39 +++++------ .../release-dialog.component.scss | 21 ++++-- .../release-dialog.component.spec.ts | 28 ++++---- .../release-dialog.component.ts | 68 +++++++++++++------ .../revert-to-draft-dialog.component.html | 25 +++---- .../revert-to-draft-dialog.component.scss | 21 ++++-- .../revert-to-draft-dialog.component.spec.ts | 7 ++ .../revert-to-draft-dialog.component.ts | 16 +++-- .../decision-input-v2.component.ts | 7 +- .../decision-v2/decision-v2.component.ts | 5 ++ .../release-dialog.component.html | 39 +++++------ .../release-dialog.component.scss | 21 ++++-- .../release-dialog.component.spec.ts | 6 +- .../release-dialog.component.ts | 61 +++++++++++------ .../revert-to-draft-dialog.component.html | 25 +++---- .../revert-to-draft-dialog.component.scss | 21 ++++-- .../revert-to-draft-dialog.component.spec.ts | 7 ++ .../revert-to-draft-dialog.component.ts | 23 ++++--- 20 files changed, 291 insertions(+), 161 deletions(-) diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-input-v2.component.ts b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-input-v2.component.ts index e978bdbf15..9a81c77f60 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-input-v2.component.ts +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-input-v2.component.ts @@ -571,11 +571,14 @@ export class DecisionInputV2Component implements OnInit, OnDestroy { if (this.runValidation()) { this.dialog .open(ReleaseDialogComponent, { - minWidth: '600px', - maxWidth: '900px', + minWidth: '1080px', + maxWidth: '1080px', maxHeight: '80vh', width: '90%', autoFocus: false, + data: { + fileNumber: this.fileNumber, + }, }) .afterClosed() .subscribe(async (didAccept) => { diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-v2.component.ts b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-v2.component.ts index bab882683b..e9409657f4 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-v2.component.ts +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-v2.component.ts @@ -161,6 +161,11 @@ export class DecisionV2Component implements OnInit, OnDestroy { const index = this.decisions.length - position; this.dialog .open(RevertToDraftDialogComponent, { + minWidth: '1080px', + maxWidth: '1080px', + maxHeight: '80vh', + width: '90%', + autoFocus: false, data: { fileNumber: this.fileNumber }, }) .beforeClosed() diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/release-dialog/release-dialog.component.html b/alcs-frontend/src/app/features/application/decision/decision-v2/release-dialog/release-dialog.component.html index 4b627f2d46..19bc060ead 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/release-dialog/release-dialog.component.html +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/release-dialog/release-dialog.component.html @@ -1,25 +1,26 @@

Confirm Release Decision

- -
-

- This decision and document will be immediately visible to the Applicant, Local/First Nation Government, and - Public. -

-

- Upon releasing the decision, the application will be updated to the status: - -

- If there is more than one decision, the status will not change -

- If this decision is being released for the first time, the Applicant and Local/First Nation Government will receive - an auto-email containing a link to the decision document(s). Otherwise an auto-email will not be sent. -

-

- If this decision is being released for the first time, the Applicant and Local/First Nation Government will receive - an auto-email containing a link to the decision document(s). Otherwise an auto-email will not be sent. -

+ +

Upon releasing the decision:

+
+
Visibility: Applicant, Local/First Nation Government, and Public
+
+ Portal Status: + + +
+
+ Email (with decision document) sent to: + Primary Contact and Local/First Nation Government + No auto-emails will be sent +
diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/release-dialog/release-dialog.component.scss b/alcs-frontend/src/app/features/application/decision/decision-v2/release-dialog/release-dialog.component.scss index 756d26e192..d3592c7a86 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/release-dialog/release-dialog.component.scss +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/release-dialog/release-dialog.component.scss @@ -1,6 +1,17 @@ -.grid { - display: grid; - grid-template-columns: 1fr; - grid-column-gap: 24px; - grid-row-gap: 24px; +@use '../../../../../../styles/colors.scss'; + +.content { + height: 100%; + color: colors.$black; + margin-top: 24px; +} + +.release-data { + background-color: #FFF6E1; + padding: 12px; + margin: 12px; + + div { + margin: 12px 0; + } } diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/release-dialog/release-dialog.component.spec.ts b/alcs-frontend/src/app/features/application/decision/decision-v2/release-dialog/release-dialog.component.spec.ts index 5662286c0d..7a1a5c9b45 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/release-dialog/release-dialog.component.spec.ts +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/release-dialog/release-dialog.component.spec.ts @@ -3,39 +3,43 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { BehaviorSubject } from 'rxjs'; -import { ApplicationStatusDto } from '../../../../../services/application/application-submission-status/application-submission-status.dto'; -import { ApplicationService } from '../../../../../services/application/application.service'; -import { ReleaseDialogComponent } from './release-dialog.component'; +import { ApplicationSubmissionStatusService } from '../../../../../services/application/application-submission-status/application-submission-status.service'; +import { + ApplicationDecisionDto, + ApplicationDecisionWithLinkedResolutionDto, +} from '../../../../../services/application/decision/application-decision-v2/application-decision-v2.dto'; import { ApplicationDecisionV2Service } from '../../../../../services/application/decision/application-decision-v2/application-decision-v2.service'; -import { ApplicationDecisionDto } from '../../../../../services/application/decision/application-decision-v2/application-decision-v2.dto'; +import { ReleaseDialogComponent } from './release-dialog.component'; describe('ReleaseDialogComponent', () => { let component: ReleaseDialogComponent; let fixture: ComponentFixture; - let mockApplicationService: DeepMocked; let mockApplicationDecisionV2Service: DeepMocked; + let mockSubmissionStatusService: DeepMocked; beforeEach(async () => { - mockApplicationService = createMock(); - mockApplicationService.$applicationStatuses = new BehaviorSubject([]); mockApplicationDecisionV2Service = createMock(); mockApplicationDecisionV2Service.$decision = new BehaviorSubject(undefined); + mockApplicationDecisionV2Service.$decisions = new BehaviorSubject([]); + mockSubmissionStatusService = createMock(); await TestBed.configureTestingModule({ declarations: [ReleaseDialogComponent], providers: [ - { - provide: ApplicationService, - useValue: mockApplicationService, - }, { provide: ApplicationDecisionV2Service, useValue: mockApplicationDecisionV2Service, }, + { + provide: ApplicationSubmissionStatusService, + useValue: mockSubmissionStatusService, + }, { provide: MatDialogRef, useValue: {} }, { provide: MAT_DIALOG_DATA, - useValue: {}, + useValue: { + fileNumber: '12313', + }, }, ], schemas: [NO_ERRORS_SCHEMA], diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/release-dialog/release-dialog.component.ts b/alcs-frontend/src/app/features/application/decision/decision-v2/release-dialog/release-dialog.component.ts index cc1a273f17..e05e890412 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/release-dialog/release-dialog.component.ts +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/release-dialog/release-dialog.component.ts @@ -1,10 +1,11 @@ import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Subject, takeUntil } from 'rxjs'; +import { ApplicationSubmissionStatusService } from '../../../../../services/application/application-submission-status/application-submission-status.service'; import { SUBMISSION_STATUS } from '../../../../../services/application/application.dto'; -import { ApplicationService } from '../../../../../services/application/application.service'; -import { ApplicationPill } from '../../../../../shared/application-type-pill/application-type-pill.component'; import { ApplicationDecisionV2Service } from '../../../../../services/application/decision/application-decision-v2/application-decision-v2.service'; +import { NOI_SUBMISSION_STATUS } from '../../../../../services/notice-of-intent/notice-of-intent.dto'; +import { ApplicationSubmissionStatusPill } from '../../../../../shared/application-submission-status-type-pill/application-submission-status-type-pill.component'; @Component({ selector: 'app-release-dialog', @@ -13,37 +14,30 @@ import { ApplicationDecisionV2Service } from '../../../../../services/applicatio }) export class ReleaseDialogComponent implements OnInit, OnDestroy { $destroy = new Subject(); - mappedType?: ApplicationPill; wasReleased = false; + isCancelled = false; + firstDecision = false; + releasedStatus: ApplicationSubmissionStatusPill | undefined; + cancelledStatus: ApplicationSubmissionStatusPill | undefined; constructor( - private applicationService: ApplicationService, private decisionService: ApplicationDecisionV2Service, + private applicationSubmissionStatusService: ApplicationSubmissionStatusService, public matDialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) data: any + @Inject(MAT_DIALOG_DATA) + private data: { + fileNumber: string; + }, ) {} ngOnInit(): void { - this.applicationService.$applicationStatuses.pipe(takeUntil(this.$destroy)).subscribe((statuses) => { - if (statuses) { - const releasedStatus = statuses.find((status) => status.code === SUBMISSION_STATUS.ALC_DECISION); - if (releasedStatus) { - this.mappedType = { - label: releasedStatus.label, - backgroundColor: releasedStatus.alcsBackgroundColor, - borderColor: releasedStatus.alcsBackgroundColor, - textColor: releasedStatus.alcsColor, - shortLabel: releasedStatus.label, - }; - } - } - }); - this.decisionService.$decision.pipe(takeUntil(this.$destroy)).subscribe((decision) => { if (decision) { this.wasReleased = decision.wasReleased; } }); + + this.calculateStatus(); } ngOnDestroy(): void { @@ -54,4 +48,38 @@ export class ReleaseDialogComponent implements OnInit, OnDestroy { onRelease() { this.matDialogRef.close(true); } + + private async calculateStatus() { + const decisions = this.decisionService.$decisions.getValue(); + this.firstDecision = decisions.length === 1; + + const statuses = await this.applicationSubmissionStatusService.fetchSubmissionStatusesByFileNumber( + this.data.fileNumber, + ); + if (statuses) { + const releasedStatus = statuses.find((status) => status.statusTypeCode === NOI_SUBMISSION_STATUS.ALC_DECISION); + if (releasedStatus) { + this.releasedStatus = { + label: releasedStatus.status.label, + backgroundColor: releasedStatus.status.alcsBackgroundColor, + borderColor: releasedStatus.status.alcsBackgroundColor, + textColor: releasedStatus.status.alcsColor, + shortLabel: releasedStatus.status.label, + }; + } + } + + const cancelled = statuses.find( + (status) => status.effectiveDate && status.statusTypeCode === SUBMISSION_STATUS.CANCELLED, + ); + this.isCancelled = !!cancelled; + + if (cancelled) { + this.cancelledStatus = { + label: cancelled.status.label, + backgroundColor: cancelled.status.alcsBackgroundColor, + textColor: cancelled.status.alcsColor, + }; + } + } } diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.html b/alcs-frontend/src/app/features/application/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.html index af89c6851d..fe3f0e4e02 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.html +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.html @@ -1,24 +1,21 @@

Confirm Revert to Draft

- -
-

- This decision and document will be reverted into draft form, providing you with full editing capabilities but - hiding it from the Applicant, Local/First Nation Government, and the Public. -

-

- Upon reverting the decision to draft, the application will be updated to the status: + +

Upon reverting the decision to a draft:

+
+
Visibility: ALC staff only (with full editing capabilities)
+
+ Portal Status: -

- If there is more than one decision, the status will not change -

- The Applicant and Local/First Nation Government will not receive an auto-email notification. Please complete any - notification manually. -

+
+
+ Email (with decision document) sent to: + No auto-emails will be sent +
diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.scss b/alcs-frontend/src/app/features/application/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.scss index 756d26e192..d3592c7a86 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.scss +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.scss @@ -1,6 +1,17 @@ -.grid { - display: grid; - grid-template-columns: 1fr; - grid-column-gap: 24px; - grid-row-gap: 24px; +@use '../../../../../../styles/colors.scss'; + +.content { + height: 100%; + color: colors.$black; + margin-top: 24px; +} + +.release-data { + background-color: #FFF6E1; + padding: 12px; + margin: 12px; + + div { + margin: 12px 0; + } } diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.spec.ts b/alcs-frontend/src/app/features/application/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.spec.ts index 45e573e56f..70591c9ca1 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.spec.ts +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.spec.ts @@ -2,7 +2,10 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { BehaviorSubject } from 'rxjs'; import { ApplicationSubmissionStatusService } from '../../../../../services/application/application-submission-status/application-submission-status.service'; +import { ApplicationDecisionWithLinkedResolutionDto } from '../../../../../services/application/decision/application-decision-v2/application-decision-v2.dto'; +import { ApplicationDecisionV2Service } from '../../../../../services/application/decision/application-decision-v2/application-decision-v2.service'; import { RevertToDraftDialogComponent } from './revert-to-draft-dialog.component'; @@ -10,15 +13,19 @@ describe('RevertToDraftDialogComponent', () => { let component: RevertToDraftDialogComponent; let fixture: ComponentFixture; let mockAppSubStatusService: DeepMocked; + let mockAppDecService: DeepMocked; beforeEach(async () => { mockAppSubStatusService = createMock(); + mockAppDecService = createMock(); + mockAppDecService.$decisions = new BehaviorSubject([]); await TestBed.configureTestingModule({ declarations: [RevertToDraftDialogComponent], providers: [ { provide: MatDialogRef, useValue: {} }, { provide: ApplicationSubmissionStatusService, useValue: mockAppSubStatusService }, + { provide: ApplicationDecisionV2Service, useValue: mockAppDecService }, { provide: MAT_DIALOG_DATA, useValue: {} }, ], schemas: [NO_ERRORS_SCHEMA], diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.ts b/alcs-frontend/src/app/features/application/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.ts index a26e3917c6..d665bd597d 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.ts +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.ts @@ -2,6 +2,7 @@ import { Component, Inject } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { ApplicationSubmissionStatusService } from '../../../../../services/application/application-submission-status/application-submission-status.service'; import { SUBMISSION_STATUS } from '../../../../../services/application/application.dto'; +import { ApplicationDecisionV2Service } from '../../../../../services/application/decision/application-decision-v2/application-decision-v2.service'; import { ApplicationSubmissionStatusPill } from '../../../../../shared/application-submission-status-type-pill/application-submission-status-type-pill.component'; @Component({ @@ -11,24 +12,25 @@ import { ApplicationSubmissionStatusPill } from '../../../../../shared/applicati }) export class RevertToDraftDialogComponent { mappedType?: ApplicationSubmissionStatusPill; + isOnlyDecision = true; constructor( public matDialogRef: MatDialogRef, private applicationSubmissionStatusService: ApplicationSubmissionStatusService, - @Inject(MAT_DIALOG_DATA) data: { fileNumber: string } + private appDecisionService: ApplicationDecisionV2Service, + @Inject(MAT_DIALOG_DATA) data: { fileNumber: string }, ) { + const decisions = this.appDecisionService.$decisions.getValue(); + this.isOnlyDecision = decisions.length === 1; + this.calculateStatusChange(data.fileNumber); } async calculateStatusChange(fileNumber: string) { const statusHistory = await this.applicationSubmissionStatusService.fetchSubmissionStatusesByFileNumber(fileNumber); const validStatuses = statusHistory - .filter( - (status) => - status.effectiveDate && - status.statusTypeCode !== SUBMISSION_STATUS.ALC_DECISION && - status.effectiveDate < Date.now() - ) + .filter((status) => status.effectiveDate && status.effectiveDate < Date.now()) + .filter((status) => !this.isOnlyDecision || status.statusTypeCode !== SUBMISSION_STATUS.ALC_DECISION) .sort((a, b) => b.status.weight! - a.status.weight!); if (validStatuses && validStatuses.length > 0) { diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.ts index a79f1e477d..caed97c383 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.ts @@ -417,11 +417,14 @@ export class DecisionInputV2Component implements OnInit, OnDestroy { if (this.runValidation()) { this.dialog .open(ReleaseDialogComponent, { - minWidth: '600px', - maxWidth: '900px', + minWidth: '1080px', + maxWidth: '1080px', maxHeight: '80vh', width: '90%', autoFocus: false, + data: { + fileNumber: this.fileNumber, + }, }) .afterClosed() .subscribe(async (didAccept) => { diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-v2.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-v2.component.ts index 7668769580..5e43460b39 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-v2.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-v2.component.ts @@ -133,6 +133,11 @@ export class DecisionV2Component implements OnInit, OnDestroy { const index = this.decisions.length - position; this.dialog .open(RevertToDraftDialogComponent, { + minWidth: '1080px', + maxWidth: '1080px', + maxHeight: '80vh', + width: '90%', + autoFocus: false, data: { fileNumber: this.fileNumber }, }) .beforeClosed() diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/release-dialog/release-dialog.component.html b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/release-dialog/release-dialog.component.html index 4b627f2d46..19bc060ead 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/release-dialog/release-dialog.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/release-dialog/release-dialog.component.html @@ -1,25 +1,26 @@

Confirm Release Decision

- -
-

- This decision and document will be immediately visible to the Applicant, Local/First Nation Government, and - Public. -

-

- Upon releasing the decision, the application will be updated to the status: - -

- If there is more than one decision, the status will not change -

- If this decision is being released for the first time, the Applicant and Local/First Nation Government will receive - an auto-email containing a link to the decision document(s). Otherwise an auto-email will not be sent. -

-

- If this decision is being released for the first time, the Applicant and Local/First Nation Government will receive - an auto-email containing a link to the decision document(s). Otherwise an auto-email will not be sent. -

+ +

Upon releasing the decision:

+
+
Visibility: Applicant, Local/First Nation Government, and Public
+
+ Portal Status: + + +
+
+ Email (with decision document) sent to: + Primary Contact and Local/First Nation Government + No auto-emails will be sent +
diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/release-dialog/release-dialog.component.scss b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/release-dialog/release-dialog.component.scss index 756d26e192..d3592c7a86 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/release-dialog/release-dialog.component.scss +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/release-dialog/release-dialog.component.scss @@ -1,6 +1,17 @@ -.grid { - display: grid; - grid-template-columns: 1fr; - grid-column-gap: 24px; - grid-row-gap: 24px; +@use '../../../../../../styles/colors.scss'; + +.content { + height: 100%; + color: colors.$black; + margin-top: 24px; +} + +.release-data { + background-color: #FFF6E1; + padding: 12px; + margin: 12px; + + div { + margin: 12px 0; + } } diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/release-dialog/release-dialog.component.spec.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/release-dialog/release-dialog.component.spec.ts index 45819d617a..530c83b8e5 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/release-dialog/release-dialog.component.spec.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/release-dialog/release-dialog.component.spec.ts @@ -4,7 +4,10 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { BehaviorSubject } from 'rxjs'; import { NoticeOfIntentDecisionV2Service } from '../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision-v2.service'; -import { NoticeOfIntentDecisionDto } from '../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; +import { + NoticeOfIntentDecisionDto, + NoticeOfIntentDecisionWithLinkedResolutionDto, +} from '../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; import { NoticeOfIntentSubmissionStatusService } from '../../../../../services/notice-of-intent/notice-of-intent-submission-status/notice-of-intent-submission-status.service'; import { ReleaseDialogComponent } from './release-dialog.component'; @@ -18,6 +21,7 @@ describe('ReleaseDialogComponent', () => { mockNOISubmissionStatusService = createMock(); mockNOIDecisionV2Service = createMock(); mockNOIDecisionV2Service.$decision = new BehaviorSubject(undefined); + mockNOIDecisionV2Service.$decisions = new BehaviorSubject([]); await TestBed.configureTestingModule({ declarations: [ReleaseDialogComponent], diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/release-dialog/release-dialog.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/release-dialog/release-dialog.component.ts index ff6be9d9c4..5406ff278f 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/release-dialog/release-dialog.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/release-dialog/release-dialog.component.ts @@ -4,7 +4,7 @@ import { Subject, takeUntil } from 'rxjs'; import { NoticeOfIntentDecisionV2Service } from '../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision-v2.service'; import { NoticeOfIntentSubmissionStatusService } from '../../../../../services/notice-of-intent/notice-of-intent-submission-status/notice-of-intent-submission-status.service'; import { NOI_SUBMISSION_STATUS } from '../../../../../services/notice-of-intent/notice-of-intent.dto'; -import { ApplicationPill } from '../../../../../shared/application-type-pill/application-type-pill.component'; +import { ApplicationSubmissionStatusPill } from '../../../../../shared/application-submission-status-type-pill/application-submission-status-type-pill.component'; @Component({ selector: 'app-release-dialog', @@ -13,14 +13,20 @@ import { ApplicationPill } from '../../../../../shared/application-type-pill/app }) export class ReleaseDialogComponent implements OnInit, OnDestroy { $destroy = new Subject(); - mappedType?: ApplicationPill; wasReleased = false; + isCancelled = false; + firstDecision = false; + releasedStatus: ApplicationSubmissionStatusPill | undefined; + cancelledStatus: ApplicationSubmissionStatusPill | undefined; constructor( - private noticeOfIntentSubmissionStatusService: NoticeOfIntentSubmissionStatusService, private decisionService: NoticeOfIntentDecisionV2Service, + private submissionStatusService: NoticeOfIntentSubmissionStatusService, public matDialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) data: any + @Inject(MAT_DIALOG_DATA) + private data: { + fileNumber: string; + }, ) {} ngOnInit(): void { @@ -29,23 +35,8 @@ export class ReleaseDialogComponent implements OnInit, OnDestroy { this.wasReleased = decision.wasReleased; } }); - this.loadStatuses(); - } - private async loadStatuses() { - const statuses = await this.noticeOfIntentSubmissionStatusService.listStatuses(); - if (statuses) { - const releasedStatus = statuses.find((status) => status.code === NOI_SUBMISSION_STATUS.ALC_DECISION); - if (releasedStatus) { - this.mappedType = { - label: releasedStatus.label, - backgroundColor: releasedStatus.alcsBackgroundColor, - borderColor: releasedStatus.alcsBackgroundColor, - textColor: releasedStatus.alcsColor, - shortLabel: releasedStatus.label, - }; - } - } + this.calculateStatus(); } ngOnDestroy(): void { @@ -56,4 +47,34 @@ export class ReleaseDialogComponent implements OnInit, OnDestroy { onRelease() { this.matDialogRef.close(true); } + + private async calculateStatus() { + const decisions = this.decisionService.$decisions.getValue(); + this.firstDecision = decisions.length === 1; + + const statuses = await this.submissionStatusService.fetchSubmissionStatusesByFileNumber(this.data.fileNumber); + const releasedStatus = statuses.find((status) => status.statusTypeCode === NOI_SUBMISSION_STATUS.ALC_DECISION); + if (releasedStatus) { + this.releasedStatus = { + label: releasedStatus.status.label, + backgroundColor: releasedStatus.status.alcsBackgroundColor, + borderColor: releasedStatus.status.alcsBackgroundColor, + textColor: releasedStatus.status.alcsColor, + shortLabel: releasedStatus.status.label, + }; + } + + const cancelled = statuses.find( + (status) => status.effectiveDate && status.statusTypeCode === NOI_SUBMISSION_STATUS.CANCELLED, + ); + this.isCancelled = !!cancelled; + + if (cancelled) { + this.cancelledStatus = { + label: cancelled.status.label, + backgroundColor: cancelled.status.alcsBackgroundColor, + textColor: cancelled.status.alcsColor, + }; + } + } } diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.html b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.html index af89c6851d..fe3f0e4e02 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.html @@ -1,24 +1,21 @@

Confirm Revert to Draft

- -
-

- This decision and document will be reverted into draft form, providing you with full editing capabilities but - hiding it from the Applicant, Local/First Nation Government, and the Public. -

-

- Upon reverting the decision to draft, the application will be updated to the status: + +

Upon reverting the decision to a draft:

+
+
Visibility: ALC staff only (with full editing capabilities)
+
+ Portal Status: -

- If there is more than one decision, the status will not change -

- The Applicant and Local/First Nation Government will not receive an auto-email notification. Please complete any - notification manually. -

+
+
+ Email (with decision document) sent to: + No auto-emails will be sent +
diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.scss b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.scss index 756d26e192..d3592c7a86 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.scss +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.scss @@ -1,6 +1,17 @@ -.grid { - display: grid; - grid-template-columns: 1fr; - grid-column-gap: 24px; - grid-row-gap: 24px; +@use '../../../../../../styles/colors.scss'; + +.content { + height: 100%; + color: colors.$black; + margin-top: 24px; +} + +.release-data { + background-color: #FFF6E1; + padding: 12px; + margin: 12px; + + div { + margin: 12px 0; + } } diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.spec.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.spec.ts index 0502f749ec..16929eebb7 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.spec.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.spec.ts @@ -2,6 +2,9 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { BehaviorSubject } from 'rxjs'; +import { NoticeOfIntentDecisionV2Service } from '../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision-v2.service'; +import { NoticeOfIntentDecisionWithLinkedResolutionDto } from '../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; import { NoticeOfIntentSubmissionStatusService } from '../../../../../services/notice-of-intent/notice-of-intent-submission-status/notice-of-intent-submission-status.service'; import { RevertToDraftDialogComponent } from './revert-to-draft-dialog.component'; @@ -10,15 +13,19 @@ describe('RevertToDraftDialogComponent', () => { let component: RevertToDraftDialogComponent; let fixture: ComponentFixture; let mockNOISubStatusService: DeepMocked; + let mockNOIDecisionService: DeepMocked; beforeEach(async () => { mockNOISubStatusService = createMock(); + mockNOIDecisionService = createMock(); + mockNOIDecisionService.$decisions = new BehaviorSubject([]); await TestBed.configureTestingModule({ declarations: [RevertToDraftDialogComponent], providers: [ { provide: MatDialogRef, useValue: {} }, { provide: NoticeOfIntentSubmissionStatusService, useValue: mockNOISubStatusService }, + { provide: NoticeOfIntentDecisionV2Service, useValue: mockNOIDecisionService }, { provide: MAT_DIALOG_DATA, useValue: {} }, ], schemas: [NO_ERRORS_SCHEMA], diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.ts index d90fcdc3a1..8f304dbbf4 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component.ts @@ -1,7 +1,8 @@ import { Component, Inject } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { SUBMISSION_STATUS } from '../../../../../services/application/application.dto'; +import { NoticeOfIntentDecisionV2Service } from '../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision-v2.service'; import { NoticeOfIntentSubmissionStatusService } from '../../../../../services/notice-of-intent/notice-of-intent-submission-status/notice-of-intent-submission-status.service'; +import { NOI_SUBMISSION_STATUS } from '../../../../../services/notice-of-intent/notice-of-intent.dto'; import { ApplicationSubmissionStatusPill } from '../../../../../shared/application-submission-status-type-pill/application-submission-status-type-pill.component'; @Component({ @@ -11,26 +12,26 @@ import { ApplicationSubmissionStatusPill } from '../../../../../shared/applicati }) export class RevertToDraftDialogComponent { mappedType?: ApplicationSubmissionStatusPill; + isOnlyDecision = true; constructor( public matDialogRef: MatDialogRef, private noticeOfIntentSubmissionStatusService: NoticeOfIntentSubmissionStatusService, - @Inject(MAT_DIALOG_DATA) data: { fileNumber: string } + private noiDecisionService: NoticeOfIntentDecisionV2Service, + @Inject(MAT_DIALOG_DATA) data: { fileNumber: string }, ) { + const decisions = this.noiDecisionService.$decisions.getValue(); + this.isOnlyDecision = decisions.length === 1; + this.calculateStatusChange(data.fileNumber); } async calculateStatusChange(fileNumber: string) { - const statusHistory = await this.noticeOfIntentSubmissionStatusService.fetchSubmissionStatusesByFileNumber( - fileNumber - ); + const statusHistory = + await this.noticeOfIntentSubmissionStatusService.fetchSubmissionStatusesByFileNumber(fileNumber); const validStatuses = statusHistory - .filter( - (status) => - status.effectiveDate && - status.statusTypeCode !== SUBMISSION_STATUS.ALC_DECISION && - status.effectiveDate < Date.now() - ) + .filter((status) => status.effectiveDate && status.effectiveDate < Date.now()) + .filter((status) => !this.isOnlyDecision || status.statusTypeCode !== NOI_SUBMISSION_STATUS.ALC_DECISION) .sort((a, b) => b.status.weight! - a.status.weight!); if (validStatuses && validStatuses.length > 0) { From cf26fbb60b447b2d1c7bdb503ccd8b4e674f881a Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Mon, 8 Apr 2024 15:34:59 -0700 Subject: [PATCH 107/153] Add Portal Unit Tests * Add component tests to some of the larger portal components --- .../parcel-entry.component.spec.ts | 172 +++++++++++++++++- .../parcel-entry.component.spec.ts | 161 +++++++++++++++- .../search/public-search.component.spec.ts | 118 +++++++++++- .../public/search/public-search.component.ts | 12 +- 4 files changed, 443 insertions(+), 20 deletions(-) diff --git a/portal-frontend/src/app/features/applications/edit-submission/parcel-details/parcel-entry/parcel-entry.component.spec.ts b/portal-frontend/src/app/features/applications/edit-submission/parcel-details/parcel-entry/parcel-entry.component.spec.ts index e372791f7d..4add06d1fc 100644 --- a/portal-frontend/src/app/features/applications/edit-submission/parcel-details/parcel-entry/parcel-entry.component.spec.ts +++ b/portal-frontend/src/app/features/applications/edit-submission/parcel-details/parcel-entry/parcel-entry.component.spec.ts @@ -1,6 +1,7 @@ import { HttpClient } from '@angular/common/http'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Validators } from '@angular/forms'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatDialog } from '@angular/material/dialog'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; @@ -8,7 +9,10 @@ import { BehaviorSubject } from 'rxjs'; import { ApplicationDocumentService } from '../../../../../services/application-document/application-document.service'; import { ApplicationOwnerDto } from '../../../../../services/application-owner/application-owner.dto'; import { ApplicationOwnerService } from '../../../../../services/application-owner/application-owner.service'; -import { ApplicationParcelDto } from '../../../../../services/application-parcel/application-parcel.dto'; +import { + ApplicationParcelDto, + PARCEL_OWNERSHIP_TYPE, +} from '../../../../../services/application-parcel/application-parcel.dto'; import { ApplicationParcelService } from '../../../../../services/application-parcel/application-parcel.service'; import { ParcelService } from '../../../../../services/parcel/parcel.service'; import { ToastService } from '../../../../../services/toast/toast.service'; @@ -23,12 +27,6 @@ describe('ParcelEntryComponent', () => { let mockAppOwnerService: DeepMocked; let mockAppDocService: DeepMocked; - let mockParcel: ApplicationParcelDto = { - isConfirmedByApplicant: false, - uuid: '', - owners: [], - }; - beforeEach(async () => { mockParcelService = createMock(); mockHttpClient = createMock(); @@ -75,12 +73,166 @@ describe('ParcelEntryComponent', () => { fixture = TestBed.createComponent(ParcelEntryComponent); component = fixture.componentInstance; component.$owners = new BehaviorSubject([]); - component.parcel = mockParcel; - - fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); + + describe('No Parcel Selected', () => { + let mockParcel: ApplicationParcelDto = { + isConfirmedByApplicant: false, + uuid: '', + owners: [], + }; + + beforeEach(async () => { + component.parcel = mockParcel; + fixture.detectChanges(); + }); + + it('should have disabled owner input by default', () => { + expect(component.ownerInput.disabled).toBeTruthy(); + }); + + it('should populate parcel values on successful search', async () => { + component.parcelForm.patchValue({ + pidPin: 'pidPin', + searchBy: 'pin', + }); + + mockParcelService.getByPin.mockResolvedValue({ + legalDescription: 'legalDescription', + mapArea: 'mapArea', + pin: 'pin', + pid: 'pid', + }); + fixture.detectChanges(); + + await component.onSearch(); + + const values = component.parcelForm.getRawValue(); + + expect(values.pin).toEqual('pin'); + expect(values.pid).toEqual('pid'); + expect(values.legalDescription).toEqual('legalDescription'); + expect(values.mapArea).toEqual('mapArea'); + }); + + it('should reset key form controls on reset', () => { + component.parcelForm.patchValue({ + pidPin: 'pidPin', + pid: 'pid', + legalDescription: 'legalDescription', + mapArea: 'mapArea', + purchaseDate: 'purchaseDate', + isFarm: 'isFarm', + civicAddress: 'civicAddress', + }); + + component.onReset(); + + const values = component.parcelForm.getRawValue(); + + Object.values(values).forEach((value) => { + expect(value).toBeFalsy(); + }); + }); + + it('should prepare the form for fee simple input when crown input type selected', () => { + component.onChangeParcelType({ + value: PARCEL_OWNERSHIP_TYPE.FEE_SIMPLE, + } as any); + + expect(component.isCrownLand).toBeFalsy(); + expect(component.purchaseDate.disabled).toBeFalsy(); + expect(component.pid.hasValidator(Validators.required)).toBeTruthy(); + }); + + it('should prepare the form for crown input when crown input type selected', () => { + component.onChangeParcelType({ + value: PARCEL_OWNERSHIP_TYPE.CROWN, + } as any); + + expect(component.isCrownLand).toBeTruthy(); + expect(component.purchaseDate.disabled).toBeTruthy(); + expect(component.pid.hasValidator(Validators.required)).toBeFalsy(); + }); + + it('should set the pidPinPlaceholder when changing search by to PID', () => { + component.onChangeSearchBy('pid'); + + expect(component.pidPinPlaceholder).toEqual('Type 9 digit PID'); + }); + + it('should set the pidPinPlaceholder when changing search by to PIN', () => { + component.onChangeSearchBy('pin'); + + expect(component.pidPinPlaceholder).toEqual('Type PIN'); + }); + }); + + describe('Fee Simple', () => { + let mockParcel: ApplicationParcelDto = { + isConfirmedByApplicant: false, + uuid: '', + owners: [], + ownershipTypeCode: PARCEL_OWNERSHIP_TYPE.FEE_SIMPLE, + }; + + beforeEach(async () => { + component.parcel = mockParcel; + fixture.detectChanges(); + }); + + it('should have enabled owner input by default', () => { + expect(component.ownerInput.disabled).toBeFalsy(); + }); + + it('should have search placeholder to pid', () => { + expect(component.pidPinPlaceholder).toEqual('Type 9 digit PID'); + }); + + it('should require certificate of title', () => { + expect(component.isCertificateOfTitleRequired).toBeTruthy(); + }); + }); + + describe('Crown', () => { + let mockParcel: ApplicationParcelDto = { + isConfirmedByApplicant: false, + uuid: '', + owners: [], + ownershipTypeCode: PARCEL_OWNERSHIP_TYPE.CROWN, + }; + + beforeEach(async () => { + component.showErrors = true; + component.parcel = mockParcel; + fixture.detectChanges(); + }); + + it('should have owner input enabled since crown is selected', () => { + expect(component.ownerInput.disabled).toBeFalsy(); + }); + + it('should have pidPin disabled for crown', () => { + expect(component.pidPin.disabled).toBeTruthy(); + }); + + it('should have purchaseDate disabled for crown', () => { + expect(component.purchaseDate.disabled).toBeTruthy(); + }); + + it('should mark all components touched when showErrors is true', () => { + expect(component.parcelForm.touched).toBeTruthy(); + for (const control of Object.values(component.parcelForm.controls)) { + expect(control.touched).toBeTruthy(); + } + }); + + it('should not require certificate of title', () => { + expect(component.isCertificateOfTitleRequired).toBeFalsy(); + }); + }); }); diff --git a/portal-frontend/src/app/features/notice-of-intents/edit-submission/parcels/parcel-entry/parcel-entry.component.spec.ts b/portal-frontend/src/app/features/notice-of-intents/edit-submission/parcels/parcel-entry/parcel-entry.component.spec.ts index bf675ae32a..21b13c5b6d 100644 --- a/portal-frontend/src/app/features/notice-of-intents/edit-submission/parcels/parcel-entry/parcel-entry.component.spec.ts +++ b/portal-frontend/src/app/features/notice-of-intents/edit-submission/parcels/parcel-entry/parcel-entry.component.spec.ts @@ -1,10 +1,12 @@ import { HttpClient } from '@angular/common/http'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Validators } from '@angular/forms'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatDialog } from '@angular/material/dialog'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { BehaviorSubject } from 'rxjs'; +import { PARCEL_OWNERSHIP_TYPE } from '../../../../../services/application-parcel/application-parcel.dto'; import { NoticeOfIntentDocumentService } from '../../../../../services/notice-of-intent-document/notice-of-intent-document.service'; import { NoticeOfIntentOwnerDto } from '../../../../../services/notice-of-intent-owner/notice-of-intent-owner.dto'; import { NoticeOfIntentOwnerService } from '../../../../../services/notice-of-intent-owner/notice-of-intent-owner.service'; @@ -76,11 +78,166 @@ describe('ParcelEntryComponent', () => { component = fixture.componentInstance; component.$owners = new BehaviorSubject([]); component.parcel = mockParcel; - - fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); + + describe('No Parcel Selected', () => { + let mockParcel: NoticeOfIntentParcelDto = { + isConfirmedByApplicant: false, + uuid: '', + owners: [], + }; + + beforeEach(async () => { + component.parcel = mockParcel; + fixture.detectChanges(); + }); + + it('should have disabled owner input by default', () => { + expect(component.ownerInput.disabled).toBeTruthy(); + }); + + it('should populate parcel values on successful search', async () => { + component.parcelForm.patchValue({ + pidPin: 'pidPin', + searchBy: 'pin', + }); + + mockParcelService.getByPin.mockResolvedValue({ + legalDescription: 'legalDescription', + mapArea: 'mapArea', + pin: 'pin', + pid: 'pid', + }); + fixture.detectChanges(); + + await component.onSearch(); + + const values = component.parcelForm.getRawValue(); + + expect(values.pin).toEqual('pin'); + expect(values.pid).toEqual('pid'); + expect(values.legalDescription).toEqual('legalDescription'); + expect(values.mapArea).toEqual('mapArea'); + }); + + it('should reset key form controls on reset', () => { + component.parcelForm.patchValue({ + pidPin: 'pidPin', + pid: 'pid', + legalDescription: 'legalDescription', + mapArea: 'mapArea', + purchaseDate: 'purchaseDate', + isFarm: 'isFarm', + civicAddress: 'civicAddress', + }); + + component.onReset(); + + const values = component.parcelForm.getRawValue(); + + Object.values(values).forEach((value) => { + expect(value).toBeFalsy(); + }); + }); + + it('should prepare the form for fee simple input when crown input type selected', () => { + component.onChangeParcelType({ + value: PARCEL_OWNERSHIP_TYPE.FEE_SIMPLE, + } as any); + + expect(component.isCrownLand).toBeFalsy(); + expect(component.purchaseDate.disabled).toBeFalsy(); + expect(component.pid.hasValidator(Validators.required)).toBeTruthy(); + }); + + it('should prepare the form for crown input when crown input type selected', () => { + component.onChangeParcelType({ + value: PARCEL_OWNERSHIP_TYPE.CROWN, + } as any); + + expect(component.isCrownLand).toBeTruthy(); + expect(component.purchaseDate.disabled).toBeTruthy(); + expect(component.pid.hasValidator(Validators.required)).toBeFalsy(); + }); + + it('should set the pidPinPlaceholder when changing search by to PID', () => { + component.onChangeSearchBy('pid'); + + expect(component.pidPinPlaceholder).toEqual('Type 9 digit PID'); + }); + + it('should set the pidPinPlaceholder when changing search by to PIN', () => { + component.onChangeSearchBy('pin'); + + expect(component.pidPinPlaceholder).toEqual('Type PIN'); + }); + }); + + describe('Fee Simple', () => { + let mockParcel: NoticeOfIntentParcelDto = { + isConfirmedByApplicant: false, + uuid: '', + owners: [], + ownershipTypeCode: PARCEL_OWNERSHIP_TYPE.FEE_SIMPLE, + }; + + beforeEach(async () => { + component.parcel = mockParcel; + fixture.detectChanges(); + }); + + it('should have enabled owner input by default', () => { + expect(component.ownerInput.disabled).toBeFalsy(); + }); + + it('should have search placeholder to pid', () => { + expect(component.pidPinPlaceholder).toEqual('Type 9 digit PID'); + }); + + it('should require certificate of title', () => { + expect(component.isCertificateOfTitleRequired).toBeTruthy(); + }); + }); + + describe('Crown', () => { + let mockParcel: NoticeOfIntentParcelDto = { + isConfirmedByApplicant: false, + uuid: '', + owners: [], + ownershipTypeCode: PARCEL_OWNERSHIP_TYPE.CROWN, + }; + + beforeEach(async () => { + component.showErrors = true; + component.parcel = mockParcel; + fixture.detectChanges(); + }); + + it('should have owner input enabled since crown is selected', () => { + expect(component.ownerInput.disabled).toBeFalsy(); + }); + + it('should have pidPin disabled for crown', () => { + expect(component.pidPin.disabled).toBeTruthy(); + }); + + it('should have purchaseDate disabled for crown', () => { + expect(component.purchaseDate.disabled).toBeTruthy(); + }); + + it('should mark all components touched when showErrors is true', () => { + expect(component.parcelForm.touched).toBeTruthy(); + for (const control of Object.values(component.parcelForm.controls)) { + expect(control.touched).toBeTruthy(); + } + }); + + it('should not require certificate of title', () => { + expect(component.isCertificateOfTitleRequired).toBeFalsy(); + }); + }); }); diff --git a/portal-frontend/src/app/features/public/search/public-search.component.spec.ts b/portal-frontend/src/app/features/public/search/public-search.component.spec.ts index e5eb21ee8e..79641a17fd 100644 --- a/portal-frontend/src/app/features/public/search/public-search.component.spec.ts +++ b/portal-frontend/src/app/features/public/search/public-search.component.spec.ts @@ -3,9 +3,11 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { CodeService } from '../../../services/code/code.service'; +import { SearchRequestDto } from '../../../services/search/search.dto'; import { SearchService } from '../../../services/search/search.service'; import { StatusService } from '../../../services/status/status.service'; import { ToastService } from '../../../services/toast/toast.service'; +import { MOBILE_BREAKPOINT } from '../../../shared/utils/breakpoints'; import { PublicSearchComponent } from './public-search.component'; @@ -60,11 +62,123 @@ describe('PublicSearchComponent', () => { }); mockStatusService.getStatuses.mockResolvedValue([]); - - fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); + + describe('No Saved Search', () => { + beforeEach(async () => { + fixture.detectChanges(); + }); + + it('should set the mobile flag to true when the page resizes to mobile', () => { + window = Object.assign(window, { innerWidth: MOBILE_BREAKPOINT - 1 }); + component.onWindowResize(); + expect(component.isMobile).toBeTruthy(); + }); + + it('should set the mobile flag to false when the page resizes to desktop', () => { + window = Object.assign(window, { innerWidth: MOBILE_BREAKPOINT + 1 }); + component.onWindowResize(); + expect(component.isMobile).toBeFalsy(); + }); + + it('should populate search results on successful search', async () => { + const mockResponse = { + applications: [], + noticeOfIntents: [], + notifications: [], + totalApplications: 5, + totalNoticeOfIntents: 6, + totalNotifications: 7, + }; + mockSearchService.search.mockResolvedValue(mockResponse); + + await component.onSubmit(); + + expect(component.applicationTotal).toEqual(mockResponse.totalApplications); + expect(component.noticeOfIntentTotal).toEqual(mockResponse.totalNoticeOfIntents); + expect(component.notificationTotal).toEqual(mockResponse.totalNotifications); + expect(component.searchResultsHidden).toBeFalsy(); + }); + + it('should search applications when changing table on that page', async () => { + const mockResponse = { + applications: [], + noticeOfIntents: [], + notifications: [], + totalApplications: 5, + totalNoticeOfIntents: 6, + totalNotifications: 7, + }; + mockSearchService.searchApplications.mockResolvedValue({ + data: [], + total: 0, + }); + + await component.onTableChange({ + itemsPerPage: 0, + pageIndex: 0, + sortDirection: '', + sortField: '', + tableType: 'APP', + }); + + expect(mockSearchService.searchApplications).toHaveBeenCalledTimes(1); + }); + + it('should search applications when loading more on app table', async () => { + component.applications = []; + mockSearchService.searchApplications.mockResolvedValue({ + data: [], + total: 0, + }); + + await component.onLoadMore('APP'); + + expect(mockSearchService.searchApplications).toHaveBeenCalledTimes(1); + }); + }); + + describe('Saved Search', () => { + const mockRequest: SearchRequestDto = { + fileTypes: [], + page: 1, + pageSize: 20, + sortDirection: '', + sortField: '', + decisionMakerCode: 'decisionMakerCode', + pid: 'pid', + civicAddress: 'civicAddress', + }; + + beforeEach(async () => { + window.localStorage.removeItem('search'); + const mockResponse = { + applications: [], + noticeOfIntents: [], + notifications: [], + totalApplications: 5, + totalNoticeOfIntents: 6, + totalNotifications: 7, + }; + mockSearchService.search.mockResolvedValue(mockResponse); + sessionStorage.setItem('search', JSON.stringify(mockRequest)); + + fixture.detectChanges(); + }); + + afterEach(async () => { + sessionStorage.removeItem('search'); + }); + + it('should populate the form with the saved search', () => { + expect(component.formEmpty).toBeFalsy(); + expect(component.searchForm.controls.pid.value).toEqual(mockRequest.pid); + expect(component.searchForm.controls.decisionMaker.value).toEqual(mockRequest.decisionMakerCode); + expect(component.searchForm.controls.civicAddress.value).toEqual(mockRequest.civicAddress); + }); + }); }); diff --git a/portal-frontend/src/app/features/public/search/public-search.component.ts b/portal-frontend/src/app/features/public/search/public-search.component.ts index 2a7dc89678..23aae08253 100644 --- a/portal-frontend/src/app/features/public/search/public-search.component.ts +++ b/portal-frontend/src/app/features/public/search/public-search.component.ts @@ -20,9 +20,9 @@ import { SearchService } from '../../../services/search/search.service'; import { StatusService } from '../../../services/status/status.service'; import { ToastService } from '../../../services/toast/toast.service'; import { MOBILE_BREAKPOINT } from '../../../shared/utils/breakpoints'; +import { scrollToElement } from '../../../shared/utils/scroll-helper'; import { FileTypeFilterDropDownComponent } from './file-type-filter-drop-down/file-type-filter-drop-down.component'; import { TableChange } from './search.interface'; -import { scrollToElement } from '../../../shared/utils/scroll-helper'; const STATUS_MAP = { 'Received by ALC': 'RECA', @@ -129,7 +129,7 @@ export class PublicSearchComponent implements OnInit, OnDestroy { private codeService: CodeService, private statusService: StatusService, private toastService: ToastService, - private titleService: Title + private titleService: Title, ) { this.titleService.setTitle('ALC Portal | Public Search'); } @@ -170,7 +170,7 @@ export class PublicSearchComponent implements OnInit, OnDestroy { this.filteredLocalGovernments = this.localGovernmentControl.valueChanges.pipe( startWith(''), - map((value) => this.filterLocalGovernment(value || '')) + map((value) => this.filterLocalGovernment(value || '')), ); const storedSearch = sessionStorage.getItem(SEARCH_SESSION_STORAGE_KEY); @@ -202,10 +202,10 @@ export class PublicSearchComponent implements OnInit, OnDestroy { this.searchResultsHidden = false; this.isLoading = false; + this.mapSearchResults(result); + // push tab activation to next render cycle, after the tabGroup is rendered setTimeout(() => { - this.mapSearchResults(result); - this.setActiveTab(); }); } @@ -367,7 +367,7 @@ export class PublicSearchComponent implements OnInit, OnDestroy { if (this.localGovernments) { const filterValue = value.toLowerCase(); return this.localGovernments.filter((localGovernment) => - localGovernment.name.toLowerCase().includes(filterValue) + localGovernment.name.toLowerCase().includes(filterValue), ); } return []; From d2ca8a3c789258da158c7e39d4fd2370f7335031 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Mon, 8 Apr 2024 16:47:10 -0700 Subject: [PATCH 108/153] More unit tests --- .../additional-information.component.spec.ts | 258 +++++++++++++++++- .../additional-information.component.spec.ts | 86 ++++++ 2 files changed, 340 insertions(+), 4 deletions(-) diff --git a/portal-frontend/src/app/features/notice-of-intents/edit-submission/additional-information/additional-information.component.spec.ts b/portal-frontend/src/app/features/notice-of-intents/edit-submission/additional-information/additional-information.component.spec.ts index 6d66e9bd21..b8b50cfdd3 100644 --- a/portal-frontend/src/app/features/notice-of-intents/edit-submission/additional-information/additional-information.component.spec.ts +++ b/portal-frontend/src/app/features/notice-of-intents/edit-submission/additional-information/additional-information.component.spec.ts @@ -5,12 +5,15 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { BehaviorSubject } from 'rxjs'; import { NoticeOfIntentDocumentDto } from '../../../../services/notice-of-intent-document/notice-of-intent-document.dto'; import { NoticeOfIntentDocumentService } from '../../../../services/notice-of-intent-document/notice-of-intent-document.service'; -import { NoticeOfIntentSubmissionDetailedDto } from '../../../../services/notice-of-intent-submission/notice-of-intent-submission.dto'; +import { + NOI_SUBMISSION_STATUS, + NoticeOfIntentSubmissionDetailedDto, +} from '../../../../services/notice-of-intent-submission/notice-of-intent-submission.dto'; import { NoticeOfIntentSubmissionService } from '../../../../services/notice-of-intent-submission/notice-of-intent-submission.service'; -import { ConfirmationDialogService } from '../../../../shared/confirmation-dialog/confirmation-dialog.service'; import { ToastService } from '../../../../services/toast/toast.service'; +import { ConfirmationDialogService } from '../../../../shared/confirmation-dialog/confirmation-dialog.service'; -import { AdditionalInformationComponent } from './additional-information.component'; +import { AdditionalInformationComponent, STRUCTURE_TYPES } from './additional-information.component'; describe('AdditionalInformationComponent', () => { let component: AdditionalInformationComponent; @@ -18,14 +21,85 @@ describe('AdditionalInformationComponent', () => { let mockNoticeOfIntentSubmissionService: DeepMocked; let mockNoticeOfIntentDocumentService: DeepMocked; let mockDialogService: DeepMocked; + let mockDialog: DeepMocked; + + const emptySubmission = { + applicant: '', + canEdit: false, + canView: false, + createdAt: 0, + eastLandUseType: '', + eastLandUseTypeDescription: '', + fileNumber: '', + fillProjectDuration: null, + lastStatusUpdate: 0, + localGovernmentUuid: '', + northLandUseType: '', + northLandUseTypeDescription: '', + owners: [], + parcelsAgricultureDescription: '', + parcelsAgricultureImprovementDescription: '', + parcelsNonAgricultureUseDescription: '', + primaryContactOwnerUuid: null, + purpose: null, + soilAgriParcelActivity: null, + soilAlreadyPlacedArea: null, + soilAlreadyPlacedAverageDepth: null, + soilAlreadyPlacedMaximumDepth: null, + soilAlreadyPlacedVolume: null, + soilAlreadyRemovedArea: null, + soilAlreadyRemovedAverageDepth: null, + soilAlreadyRemovedMaximumDepth: null, + soilAlreadyRemovedVolume: null, + soilFillTypeToPlace: null, + soilFollowUpIDs: null, + soilHasSubmittedNotice: null, + soilIsAreaWideFilling: null, + soilIsExtractionOrMining: null, + soilIsFollowUp: null, + soilIsRemovingSoilForNewStructure: null, + soilProjectDuration: null, + soilProposedStructures: [], + soilStructureFarmUseReason: null, + soilStructureOtherUseReason: null, + soilStructureResidentialAccessoryUseReason: null, + soilStructureResidentialUseReason: null, + soilToPlaceArea: null, + soilToPlaceAverageDepth: null, + soilToPlaceMaximumDepth: null, + soilToPlaceVolume: null, + soilToRemoveArea: null, + soilToRemoveAverageDepth: null, + soilToRemoveMaximumDepth: null, + soilToRemoveVolume: null, + soilTypeRemoved: null, + southLandUseType: '', + southLandUseTypeDescription: '', + status: { + code: NOI_SUBMISSION_STATUS.IN_PROGRESS, + portalBackgroundColor: '', + portalColor: '', + label: '', + description: '', + }, + submissionStatuses: [], + type: '', + typeCode: '', + updatedAt: 0, + uuid: '', + westLandUseType: '', + westLandUseTypeDescription: '', + }; beforeEach(async () => { mockNoticeOfIntentSubmissionService = createMock(); mockNoticeOfIntentDocumentService = createMock(); + mockDialog = createMock(); + await TestBed.configureTestingModule({ declarations: [AdditionalInformationComponent], providers: [ - { provide: MatDialog, useValue: {} }, + { provide: MatDialog, useValue: mockDialog }, { provide: NoticeOfIntentSubmissionService, useValue: mockNoticeOfIntentSubmissionService, @@ -53,4 +127,180 @@ describe('AdditionalInformationComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should hide all additional inputs by default', () => { + expect(component.isSoilStructureFarmUseReasonVisible).toBeFalsy(); + expect(component.isSoilStructureResidentialUseReasonVisible).toBeFalsy(); + expect(component.isSoilAgriParcelActivityVisible).toBeFalsy(); + expect(component.isSoilStructureResidentialAccessoryUseReasonVisible).toBeFalsy(); + }); + + it('should set the first question based on the NOI Type', () => { + component.$noiSubmission.next({ + ...emptySubmission, + typeCode: 'ROSO', + }); + fixture.detectChanges(); + + expect(component.firstQuestion).toEqual('Are you placing fill in order to build a structure?'); + }); + + it('should set up the form based on the existing proposal with a principal residence', () => { + const mockFilledSubmission: NoticeOfIntentSubmissionDetailedDto = { + ...emptySubmission, + soilIsRemovingSoilForNewStructure: true, + soilStructureFarmUseReason: 'soilStructureFarmUseReason', + soilStructureResidentialUseReason: 'soilStructureResidentialUseReason', + soilProposedStructures: [ + { + type: STRUCTURE_TYPES.PRINCIPAL_RESIDENCE, + area: 5, + }, + ], + }; + + component.$noiSubmission.next({ + ...mockFilledSubmission, + }); + fixture.whenStable(); + + expect(component.form.controls.isRemovingSoilForNewStructure.value).toEqual('true'); + expect(component.form.controls.soilStructureFarmUseReason.value).toEqual(null); + expect(component.form.controls.soilStructureResidentialUseReason.value).toEqual( + 'soilStructureResidentialUseReason', + ); + expect(Object.values(component.structuresForm.controls).length).toEqual(2); + expect(component.isSoilStructureResidentialUseReasonVisible).toBeTruthy(); + expect(component.isSoilAgriParcelActivityVisible).toBeFalsy(); + expect(component.isSoilOtherUseReasonVisible).toBeFalsy(); + expect(component.isSoilStructureResidentialAccessoryUseReasonVisible).toBeFalsy(); + }); + + it('should set up the form based on the existing proposal with a farm structure', () => { + const mockFilledSubmission: NoticeOfIntentSubmissionDetailedDto = { + ...emptySubmission, + soilIsRemovingSoilForNewStructure: true, + soilStructureFarmUseReason: 'soilStructureFarmUseReason', + soilStructureResidentialUseReason: 'soilStructureResidentialUseReason', + soilProposedStructures: [ + { + type: STRUCTURE_TYPES.FARM_STRUCTURE, + area: 5, + }, + ], + }; + + component.$noiSubmission.next({ + ...mockFilledSubmission, + }); + fixture.whenStable(); + + expect(component.form.controls.isRemovingSoilForNewStructure.value).toEqual('true'); + expect(component.form.controls.soilStructureFarmUseReason.value).toEqual('soilStructureFarmUseReason'); + expect(component.form.controls.soilStructureResidentialUseReason.value).toEqual(null); + expect(component.isSoilStructureResidentialUseReasonVisible).toBeFalsy(); + expect(component.isSoilAgriParcelActivityVisible).toBeTruthy(); + expect(component.isSoilOtherUseReasonVisible).toBeFalsy(); + expect(component.isSoilStructureResidentialAccessoryUseReasonVisible).toBeFalsy(); + }); + + it('should set up the form based on the existing proposal with an other structure', () => { + const mockFilledSubmission: NoticeOfIntentSubmissionDetailedDto = { + ...emptySubmission, + soilIsRemovingSoilForNewStructure: true, + soilStructureFarmUseReason: 'soilStructureFarmUseReason', + soilStructureResidentialUseReason: 'soilStructureResidentialUseReason', + soilProposedStructures: [ + { + type: STRUCTURE_TYPES.OTHER_STRUCTURE, + area: 5, + }, + ], + }; + + component.$noiSubmission.next({ + ...mockFilledSubmission, + }); + fixture.whenStable(); + + expect(component.form.controls.isRemovingSoilForNewStructure.value).toEqual('true'); + expect(component.form.controls.soilStructureFarmUseReason.value).toEqual(null); + expect(component.form.controls.soilStructureResidentialUseReason.value).toEqual(null); + expect(component.isSoilStructureResidentialUseReasonVisible).toBeFalsy(); + expect(component.isSoilAgriParcelActivityVisible).toBeFalsy(); + expect(component.isSoilOtherUseReasonVisible).toBeTruthy(); + expect(component.isSoilStructureResidentialAccessoryUseReasonVisible).toBeFalsy(); + }); + + it('should set up the form based on the existing proposal with an accessory structure', () => { + const mockFilledSubmission: NoticeOfIntentSubmissionDetailedDto = { + ...emptySubmission, + soilIsRemovingSoilForNewStructure: true, + soilStructureFarmUseReason: 'soilStructureFarmUseReason', + soilStructureResidentialUseReason: 'soilStructureResidentialUseReason', + soilProposedStructures: [ + { + type: STRUCTURE_TYPES.ACCESSORY_STRUCTURE, + area: 5, + }, + ], + }; + + component.$noiSubmission.next({ + ...mockFilledSubmission, + }); + fixture.whenStable(); + + expect(component.form.controls.isRemovingSoilForNewStructure.value).toEqual('true'); + expect(component.form.controls.soilStructureFarmUseReason.value).toEqual(null); + expect(component.form.controls.soilStructureResidentialUseReason.value).toEqual( + 'soilStructureResidentialUseReason', + ); + expect(component.isSoilStructureResidentialUseReasonVisible).toBeTruthy(); + expect(component.isSoilAgriParcelActivityVisible).toBeFalsy(); + expect(component.isSoilOtherUseReasonVisible).toBeFalsy(); + expect(component.isSoilStructureResidentialAccessoryUseReasonVisible).toBeTruthy(); + }); + + it('should remove controls and reset visibility when deleting a accessory structure', () => { + const mockFilledSubmission: NoticeOfIntentSubmissionDetailedDto = { + ...emptySubmission, + soilIsRemovingSoilForNewStructure: true, + soilStructureFarmUseReason: 'soilStructureFarmUseReason', + soilStructureResidentialUseReason: 'soilStructureResidentialUseReason', + soilProposedStructures: [ + { + type: STRUCTURE_TYPES.ACCESSORY_STRUCTURE, + area: 5, + }, + ], + }; + + component.$noiSubmission.next({ + ...mockFilledSubmission, + }); + fixture.whenStable(); + const mockConfirmDialog = new BehaviorSubject(true); + mockDialog.open.mockReturnValue({ + beforeClosed: () => mockConfirmDialog, + } as any); + + component.onStructureRemove(0); + + expect(Object.keys(component.structuresForm.controls)).toEqual([]); + + expect(component.isSoilStructureResidentialUseReasonVisible).toBeFalsy(); + expect(component.isSoilStructureResidentialAccessoryUseReasonVisible).toBeFalsy(); + }); + + it('should create form fields when adding a structure', () => { + component.$noiSubmission.next({ + ...emptySubmission, + }); + fixture.whenStable(); + + component.onStructureAdd(); + + expect(Object.keys(component.structuresForm.controls).length).toEqual(2); + }); }); diff --git a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/additional-information/additional-information.component.spec.ts b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/additional-information/additional-information.component.spec.ts index 464853c24d..731d544626 100644 --- a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/additional-information/additional-information.component.spec.ts +++ b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/additional-information/additional-information.component.spec.ts @@ -1,6 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { DeepMocked } from '@golevelup/ts-jest'; import { NoticeOfIntentDocumentService } from '../../../../services/notice-of-intent-document/notice-of-intent-document.service'; +import { NOI_SUBMISSION_STATUS } from '../../../../services/notice-of-intent-submission/notice-of-intent-submission.dto'; import { AdditionalInformationComponent } from './additional-information.component'; @@ -9,6 +10,74 @@ describe('RosoAdditionalInformationComponent', () => { let fixture: ComponentFixture; let mockNoiDocumentService: DeepMocked; + const emptySubmission = { + applicant: '', + canEdit: false, + canView: false, + createdAt: 0, + eastLandUseType: '', + eastLandUseTypeDescription: '', + fileNumber: '', + fillProjectDuration: null, + lastStatusUpdate: 0, + localGovernmentUuid: '', + northLandUseType: '', + northLandUseTypeDescription: '', + owners: [], + parcelsAgricultureDescription: '', + parcelsAgricultureImprovementDescription: '', + parcelsNonAgricultureUseDescription: '', + primaryContactOwnerUuid: null, + purpose: null, + soilAgriParcelActivity: null, + soilAlreadyPlacedArea: null, + soilAlreadyPlacedAverageDepth: null, + soilAlreadyPlacedMaximumDepth: null, + soilAlreadyPlacedVolume: null, + soilAlreadyRemovedArea: null, + soilAlreadyRemovedAverageDepth: null, + soilAlreadyRemovedMaximumDepth: null, + soilAlreadyRemovedVolume: null, + soilFillTypeToPlace: null, + soilFollowUpIDs: null, + soilHasSubmittedNotice: null, + soilIsAreaWideFilling: null, + soilIsExtractionOrMining: null, + soilIsFollowUp: null, + soilIsRemovingSoilForNewStructure: null, + soilProjectDuration: null, + soilProposedStructures: [], + soilStructureFarmUseReason: null, + soilStructureOtherUseReason: null, + soilStructureResidentialAccessoryUseReason: null, + soilStructureResidentialUseReason: null, + soilToPlaceArea: null, + soilToPlaceAverageDepth: null, + soilToPlaceMaximumDepth: null, + soilToPlaceVolume: null, + soilToRemoveArea: null, + soilToRemoveAverageDepth: null, + soilToRemoveMaximumDepth: null, + soilToRemoveVolume: null, + soilTypeRemoved: null, + southLandUseType: '', + southLandUseTypeDescription: '', + status: { + code: NOI_SUBMISSION_STATUS.IN_PROGRESS, + portalBackgroundColor: '', + portalColor: '', + label: '', + description: '', + }, + submissionStatuses: [], + type: '', + typeCode: '', + updatedAt: 0, + uuid: '', + westLandUseType: '', + westLandUseTypeDescription: '', + }; + beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [AdditionalInformationComponent], @@ -28,4 +97,21 @@ describe('RosoAdditionalInformationComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should hide all additional inputs by default', () => { + expect(component.isSoilStructureFarmUseReasonVisible).toBeFalsy(); + expect(component.isSoilStructureResidentialUseReasonVisible).toBeFalsy(); + expect(component.isSoilAgriParcelActivityVisible).toBeFalsy(); + expect(component.isSoilStructureResidentialAccessoryUseReasonVisible).toBeFalsy(); + expect(component.isSoilOtherStructureVisible).toBeFalsy(); + }); + + it('should set the first question based on the NOI Type', () => { + component.noiSubmission = { + ...emptySubmission, + typeCode: 'ROSO', + }; + + expect(component.firstQuestion).toEqual('Are you placing fill in order to build a structure?'); + }); }); From a76fa113a3d3c9a8c9a2c489d527c1994ba8a8c4 Mon Sep 17 00:00:00 2001 From: Liam Stoddard Date: Mon, 8 Apr 2024 16:52:14 -0700 Subject: [PATCH 109/153] inquiry documents --- .../alcs_documents_to_inquiry_documents.py | 135 +++++++++++++ ..._documents_to_planning_review_documents.py | 4 +- .../post_launch/migrate_documents.py | 13 ++ ...ats_documents_to_alcs_documents_inquiry.py | 189 ++++++++++++++++++ ...ments_to_alcs_documents_planning_review.py | 4 +- .../alcs_documents_to_inquiry_documents.sql | 30 +++ ...s_documents_to_inquiry_documents_count.sql | 23 +++ .../oats_documents_to_alcs_documents.sql | 28 +++ ...oats_documents_to_alcs_documents_count.sql | 20 ++ .../inquiry-document.entity.ts | 21 ++ .../apps/alcs/src/document/document.entity.ts | 8 + ...6742-add_cols_and_contraint_to_inq_docs.ts | 53 +++++ .../migrations/1712619551207-add_auditor.ts | 15 ++ 13 files changed, 539 insertions(+), 4 deletions(-) create mode 100644 bin/migrate-oats-data/documents/post_launch/alcs_documents_to_inquiry_documents.py create mode 100644 bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_inquiry.py create mode 100644 bin/migrate-oats-data/documents/post_launch/sql/inquiry/alcs_documents_to_inquiry_documents.sql create mode 100644 bin/migrate-oats-data/documents/post_launch/sql/inquiry/alcs_documents_to_inquiry_documents_count.sql create mode 100644 bin/migrate-oats-data/documents/post_launch/sql/inquiry/oats_documents_to_alcs_documents.sql create mode 100644 bin/migrate-oats-data/documents/post_launch/sql/inquiry/oats_documents_to_alcs_documents_count.sql create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1712618156742-add_cols_and_contraint_to_inq_docs.ts create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1712619551207-add_auditor.ts diff --git a/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_inquiry_documents.py b/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_inquiry_documents.py new file mode 100644 index 0000000000..b57adf70c5 --- /dev/null +++ b/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_inquiry_documents.py @@ -0,0 +1,135 @@ +from common import ( + setup_and_get_logger, + BATCH_UPLOAD_SIZE, + OATS_ETL_USER, +) +from db import inject_conn_pool +from psycopg2.extras import RealDictCursor + +etl_name = "link_inquiry_documents_from_alcs" +logger = setup_and_get_logger(etl_name) + +""" + This script connects to postgress version of OATS DB and links data from ALCS documents to ALCS inquiry_document table. + + NOTE: + Before performing document import you need to import Inquiries and Inquiry documents. +""" + + +@inject_conn_pool +def link_inquiry_documents(conn=None, batch_size=BATCH_UPLOAD_SIZE): + """ + function uses a decorator pattern @inject_conn_pool to inject a database connection pool to the function. It fetches the total count of documents and prints it to the console. Then, it fetches the documents to insert in batches using document IDs, constructs an insert query, and processes them. + """ + logger.info(f"Start {etl_name}") + with conn.cursor(cursor_factory=RealDictCursor) as cursor: + with open( + "documents/post_launch/sql/inquiry/alcs_documents_to_inquiry_documents_count.sql", + "r", + encoding="utf-8", + ) as sql_file: + count_query = sql_file.read() + cursor.execute(count_query) + total_count = dict(cursor.fetchone())["count"] + logger.info(f"Total count of documents to transfer: {total_count}") + + failed_inserts_count = 0 + successful_inserts_count = 0 + last_document_id = 0 + + with open( + "documents/post_launch/sql/inquiry/alcs_documents_to_inquiry_documents.sql", + "r", + encoding="utf-8", + ) as sql_file: + documents_to_insert_sql = sql_file.read() + while True: + cursor.execute( + f"{documents_to_insert_sql} WHERE oats_document_id > {last_document_id} ORDER BY oats_document_id;" + ) + rows = cursor.fetchmany(batch_size) + if not rows: + break + try: + documents_to_be_inserted_count = len(rows) + + _insert_records(conn, cursor, rows) + + last_document_id = dict(rows[-1])["oats_document_id"] + successful_inserts_count = ( + successful_inserts_count + documents_to_be_inserted_count + ) + + logger.debug( + f"retrieved/inserted items count: {documents_to_be_inserted_count}; total successfully inserted/updated documents so far {successful_inserts_count}; last inserted oats_document_id: {last_document_id}" + ) + except Exception as e: + conn.rollback() + logger.exception(f"Error {e}") + failed_inserts_count += len(rows) + last_document_id = last_document_id + 1 + + logger.info(f"Total amount of successful inserts: {successful_inserts_count}") + logger.info(f"Total amount of failed inserts: {failed_inserts_count}") + + +def _insert_records(conn, cursor, rows): + number_of_rows_to_insert = len(rows) + + if number_of_rows_to_insert > 0: + insert_query = _compile_insert_query(number_of_rows_to_insert) + rows_to_insert = _prepare_data_to_insert(rows) + cursor.execute(insert_query, rows_to_insert) + conn.commit() + + +def _compile_insert_query(number_of_rows_to_insert): + documents_to_insert = ",".join(["%s"] * number_of_rows_to_insert) + return f""" + INSERT INTO alcs.inquiry_document( + inquiry_uuid, + document_uuid, + type_code, + oats_document_id, + oats_issue_id, + audit_created_by + ) + VALUES{documents_to_insert} + ON CONFLICT (oats_document_id, oats_issue_id) DO UPDATE SET + inquiry_uuid = EXCLUDED.inquiry_uuid, + document_uuid = EXCLUDED.document_uuid, + type_code = EXCLUDED.type_code, + audit_created_by = EXCLUDED.audit_created_by; + """ + + +def _prepare_data_to_insert(rows): + row_without_last_element = [] + for row in rows: + mapped_row = _map_data(row) + row_without_last_element.append(tuple(mapped_row.values())) + + return row_without_last_element + + +def _map_data(row): + return { + "inquiry_uuid": row["inquiry_uuid"], + "document_uuid": row["document_uuid"], + "type_code": row["type_code"], + "oats_document_id": row["oats_document_id"], + "oats_issue_id": row["oats_issue_id"], + "audit_created_by": OATS_ETL_USER, + } + + +@inject_conn_pool +def clean_inquiry_documents(conn=None): + logger.info("Start documents cleaning") + with conn.cursor() as cursor: + cursor.execute( + f"DELETE FROM alcs.inquiry_document WHERE audit_created_by = '{OATS_ETL_USER}';" + ) + conn.commit() + logger.info(f"Deleted items count = {cursor.rowcount}") diff --git a/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_planning_review_documents.py b/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_planning_review_documents.py index 2d59f37237..90cb645bf6 100644 --- a/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_planning_review_documents.py +++ b/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_planning_review_documents.py @@ -6,14 +6,14 @@ from db import inject_conn_pool from psycopg2.extras import RealDictCursor -etl_name = "link_srw_documents_from_alcs" +etl_name = "link_pr_documents_from_alcs" logger = setup_and_get_logger(etl_name) """ This script connects to postgress version of OATS DB and links data from ALCS documents to ALCS planning_review_document table. NOTE: - Before performing document import you need to import SRWs and SRW documents. + Before performing document import you need to import Planning Reviews and Planning Review documents. """ diff --git a/bin/migrate-oats-data/documents/post_launch/migrate_documents.py b/bin/migrate-oats-data/documents/post_launch/migrate_documents.py index 020b38288d..b8c1cbcafc 100644 --- a/bin/migrate-oats-data/documents/post_launch/migrate_documents.py +++ b/bin/migrate-oats-data/documents/post_launch/migrate_documents.py @@ -14,6 +14,15 @@ import_oats_pr_documents, document_pr_clean, ) +from .oats_documents_to_alcs_documents_inquiry import ( + import_oats_inquiry_documents, + document_inquiry_clean, +) + +from .alcs_documents_to_inquiry_documents import ( + clean_inquiry_documents, + link_inquiry_documents, +) def import_documents(batch_size): @@ -29,8 +38,12 @@ def clean_documents(): def import_documents_pr_inq(batch_size): import_oats_pr_documents(batch_size) link_pr_documents(batch_size) + import_oats_inquiry_documents(batch_size) + link_inquiry_documents(batch_size) def clean_documents_pr_inq(): clean_planning_review_documents() document_pr_clean() + clean_inquiry_documents() + document_inquiry_clean() diff --git a/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_inquiry.py b/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_inquiry.py new file mode 100644 index 0000000000..a3e2eed699 --- /dev/null +++ b/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_inquiry.py @@ -0,0 +1,189 @@ +from common import ( + setup_and_get_logger, + BATCH_UPLOAD_SIZE, + OATS_ETL_USER, + add_timezone_and_keep_date_part, + OatsToAlcsDocumentSourceCode, +) +from db import inject_conn_pool +from psycopg2.extras import RealDictCursor +import os + +etl_name = "import_inquiry_documents_from_oats" +logger = setup_and_get_logger(etl_name) + +""" + This script connects to postgress version of OATS DB and transfers data from OATS documents table to ALCS documents table. + + NOTE: + Before performing document import you need to import Inquiries from oats. +""" + + +@inject_conn_pool +def import_oats_inquiry_documents(conn=None, batch_size=BATCH_UPLOAD_SIZE): + """ + function uses a decorator pattern @inject_conn_pool to inject a database connection pool to the function. It fetches the total count of documents and prints it to the console. Then, it fetches the documents to insert in batches using document IDs, constructs an insert query, and processes them. + """ + logger.info(f"Start {etl_name}") + with conn.cursor(cursor_factory=RealDictCursor) as cursor: + with open( + "documents/post_launch/sql/inquiry/oats_documents_to_alcs_documents_count.sql", + "r", + encoding="utf-8", + ) as sql_file: + count_query = sql_file.read() + cursor.execute(count_query) + total_count = dict(cursor.fetchone())["count"] + logger.info(f"Total count of documents to transfer: {total_count}") + + failed_inserts_count = 0 + successful_inserts_count = 0 + last_document_id = 0 + + with open( + "documents/post_launch/sql/inquiry/oats_documents_to_alcs_documents.sql", + "r", + encoding="utf-8", + ) as sql_file: + documents_to_insert_sql = sql_file.read() + while True: + cursor.execute( + f"{documents_to_insert_sql} WHERE document_id > {last_document_id} ORDER BY document_id;" + ) + rows = cursor.fetchmany(batch_size) + if not rows: + break + try: + documents_to_be_inserted_count = len(rows) + + _insert_records(conn, cursor, rows) + + last_document_id = dict(rows[-1])["document_id"] + successful_inserts_count = ( + successful_inserts_count + documents_to_be_inserted_count + ) + + logger.debug( + f"retrieved/inserted items count: {documents_to_be_inserted_count}; total successfully inserted/updated documents so far {successful_inserts_count}; last inserted oats_document_id: {last_document_id}" + ) + except Exception as e: + conn.rollback() + logger.exception(f"Error {e}") + failed_inserts_count += len(rows) + last_document_id = last_document_id + 1 + + logger.info(f"Total amount of successful inserts: {successful_inserts_count}") + logger.info(f"Total amount of failed inserts: {failed_inserts_count}") + + +def _insert_records(conn, cursor, rows): + number_of_rows_to_insert = len(rows) + + if number_of_rows_to_insert > 0: + insert_query = _compile_insert_query(number_of_rows_to_insert) + rows_to_insert = _prepare_data_to_insert(rows) + cursor.execute(insert_query, rows_to_insert) + conn.commit() + + +def _compile_insert_query(number_of_rows_to_insert): + documents_to_insert = ",".join(["%s"] * number_of_rows_to_insert) + return f""" + INSERT INTO alcs."document"( + oats_document_id, + file_name, + oats_issue_id, + audit_created_by, + file_key, + mime_type, + tags, + "system", + uploaded_at, + source + ) + VALUES{documents_to_insert} + ON CONFLICT (oats_document_id) DO UPDATE SET + oats_document_id = EXCLUDED.oats_document_id, + file_name = EXCLUDED.file_name, + oats_issue_id = EXCLUDED.oats_issue_id, + audit_created_by = EXCLUDED.audit_created_by, + file_key = EXCLUDED.file_key, + mime_type = EXCLUDED.mime_type, + tags = EXCLUDED.tags, + "system" = EXCLUDED."system", + uploaded_at = EXCLUDED.uploaded_at, + source = EXCLUDED.source; + """ + + +def _prepare_data_to_insert(rows): + row_without_last_element = [] + for row in rows: + mapped_row = _map_data(row) + row_without_last_element.append(tuple(mapped_row.values())) + + return row_without_last_element + + +def _map_data(row): + return { + "oats_document_id": row["oats_document_id"], + "file_name": row["file_name"], + "oats_issue_id": row["oats_issue_id"], + "audit_created_by": OATS_ETL_USER, + "file_key": row["file_key"], + "mime_type": _get_mime_type(row), + "tags": row["tags"], + "system": _map_system(row), + "file_upload_date": _get_upload_date(row), + "file_source": _get_document_source(row), + } + + +def _map_system(row): + who_created = row["who_created"] + if who_created in ("PROXY_OATS_LOCGOV", "PROXY_OATS_APPLICANT"): + sys = "OATS_P" + else: + sys = "OATS" + return sys + + +def _get_upload_date(data): + upload_date = data.get("uploaded_date", "") + created_date = data.get("when_created", "") + if upload_date: + return add_timezone_and_keep_date_part(upload_date) + else: + return add_timezone_and_keep_date_part(created_date) + + +def _get_document_source(data): + source = data.get("document_source_code", "") + if source: + source = str(OatsToAlcsDocumentSourceCode[source].value) + + return source + + +def _get_mime_type(data): + file_name = data.get("file_name", "") + extension = os.path.splitext(file_name)[-1].lower().strip() + if extension == ".pdf": + return "application/pdf" + else: + return "application/octet-stream" + + +@inject_conn_pool +def document_inquiry_clean(conn=None): + logger.info("Start planning review related documents cleaning") + with conn.cursor() as cursor: + cursor.execute( + f"DELETE FROM alcs.document WHERE audit_created_by = '{OATS_ETL_USER}' AND oats_issue_id IS NOT NULL;" + ) + conn.commit() + logger.info(f"Deleted items count = {cursor.rowcount}") + + conn.commit() diff --git a/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_planning_review.py b/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_planning_review.py index 5b7a5edb8b..a83f7455c9 100644 --- a/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_planning_review.py +++ b/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_planning_review.py @@ -171,9 +171,9 @@ def _get_mime_type(data): file_name = data.get("file_name", "") extension = os.path.splitext(file_name)[-1].lower().strip() if extension == ".pdf": - return "planning_review/pdf" + return "application/pdf" else: - return "planning_review/octet-stream" + return "application/octet-stream" @inject_conn_pool diff --git a/bin/migrate-oats-data/documents/post_launch/sql/inquiry/alcs_documents_to_inquiry_documents.sql b/bin/migrate-oats-data/documents/post_launch/sql/inquiry/alcs_documents_to_inquiry_documents.sql new file mode 100644 index 0000000000..d900d121a6 --- /dev/null +++ b/bin/migrate-oats-data/documents/post_launch/sql/inquiry/alcs_documents_to_inquiry_documents.sql @@ -0,0 +1,30 @@ +with oats_documents_to_map as ( + select n.uuid as inquiry_uuid, + d.uuid as document_uuid, + adc.code, + publicly_viewable_ind as is_public, + app_lg_viewable_ind as is_app_lg, + od.document_id as oats_document_id, + od.issue_id as oats_issue_id, + od."description" + from oats.oats_documents od + join alcs."document" d on d.oats_document_id = od.document_id::text + join alcs.document_code adc on adc.oats_code = od.document_code + join alcs.inquiry n on n.file_number = od.issue_id::text +) +select otm.inquiry_uuid, + otm.document_uuid, + otm.code as type_code, + ( + case + when is_public = 'Y' + and is_app_lg = 'Y' then '{P, A, C, G}'::text [] + when is_public = 'Y' then '{P}'::text [] + when is_app_lg = 'Y' then '{A, C, G}'::text [] + else '{}'::text [] + end + ) as visibility_flags, + oats_document_id, + oats_issue_id, + otm."description" +from oats_documents_to_map otm \ No newline at end of file diff --git a/bin/migrate-oats-data/documents/post_launch/sql/inquiry/alcs_documents_to_inquiry_documents_count.sql b/bin/migrate-oats-data/documents/post_launch/sql/inquiry/alcs_documents_to_inquiry_documents_count.sql new file mode 100644 index 0000000000..49566ba0c1 --- /dev/null +++ b/bin/migrate-oats-data/documents/post_launch/sql/inquiry/alcs_documents_to_inquiry_documents_count.sql @@ -0,0 +1,23 @@ +with oats_documents_to_map as ( + select + n.uuid as inquiry_uuid, + d.uuid as document_uuid, + adc.code, + publicly_viewable_ind as is_public, + app_lg_viewable_ind as is_app_lg, + od.document_id as oats_document_id, + od.issue_id as oats_issue_id + from oats.oats_documents od + + join alcs."document" d + on d.oats_document_id = od.document_id::text + + join alcs.document_code adc + on adc.oats_code = od.document_code + + join alcs.inquiry n + on n.file_number = od.issue_id::text +) +select + count(*) +from oats_documents_to_map otm \ No newline at end of file diff --git a/bin/migrate-oats-data/documents/post_launch/sql/inquiry/oats_documents_to_alcs_documents.sql b/bin/migrate-oats-data/documents/post_launch/sql/inquiry/oats_documents_to_alcs_documents.sql new file mode 100644 index 0000000000..60aa9eb0dd --- /dev/null +++ b/bin/migrate-oats-data/documents/post_launch/sql/inquiry/oats_documents_to_alcs_documents.sql @@ -0,0 +1,28 @@ +with oats_documents_to_insert as ( + select od.issue_id, + document_id, + document_code, + file_name, + od.who_created, + od.document_source_code, + od.uploaded_date, + od.when_created + from oats.oats_documents od + where od.alr_application_id is null + and document_code is not null + and od.issue_id is not null + and od.planning_review_id is null +) +SELECT document_id::text AS oats_document_id, + file_name, + issue_id::text AS oats_issue_id, + 'migrate/issue/' || issue_id || '/' || document_id || '_' || file_name AS file_key, + 'pdf' AS mime_type, + '{"ORCS Classification: 85000"}'::text [] as tags, + who_created, + document_source_code, + uploaded_date, + when_created, + document_id +FROM oats_documents_to_insert oti + JOIN alcs.inquiry n ON n.file_number = oti.issue_id::text \ No newline at end of file diff --git a/bin/migrate-oats-data/documents/post_launch/sql/inquiry/oats_documents_to_alcs_documents_count.sql b/bin/migrate-oats-data/documents/post_launch/sql/inquiry/oats_documents_to_alcs_documents_count.sql new file mode 100644 index 0000000000..055a83e9ca --- /dev/null +++ b/bin/migrate-oats-data/documents/post_launch/sql/inquiry/oats_documents_to_alcs_documents_count.sql @@ -0,0 +1,20 @@ +WITH + oats_documents_to_insert as ( + select + od.issue_id, + document_id, + document_code, + file_name + from + oats.oats_documents od + where + od.alr_application_id is null + and document_code is not null + and od.issue_id is not null + and od.planning_review_id is null + ) +SELECT + count(*) +FROM + oats_documents_to_insert oti + JOIN alcs.inquiry n ON n.file_number = oti.issue_id::text \ No newline at end of file diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.entity.ts b/services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.entity.ts index 1557cd927a..92243cdcc8 100644 --- a/services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.entity.ts +++ b/services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.entity.ts @@ -44,6 +44,27 @@ export class InquiryDocument extends BaseEntity { @Column({ nullable: true, type: 'uuid' }) documentUuid?: string | null; + @Column({ + type: 'text', + nullable: true, + select: false, + comment: + 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.issues to alcs.inquiry_document.', + }) + oatsIssueId?: string | null; + + @Column({ + type: 'text', + nullable: true, + select: false, + comment: + 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.documents/alcs.documents to alcs.inquiry_document.', + }) + oatsDocumentId?: string | null; + + @Column({ type: 'varchar', nullable: true }) + auditCreatedBy?: string | null; + @OneToOne(() => Document) @JoinColumn() document: Document; diff --git a/services/apps/alcs/src/document/document.entity.ts b/services/apps/alcs/src/document/document.entity.ts index d913bc6f14..8e660afde1 100644 --- a/services/apps/alcs/src/document/document.entity.ts +++ b/services/apps/alcs/src/document/document.entity.ts @@ -64,6 +64,14 @@ export class Document extends Base { }) oatsPlanningReviewId?: string | null; + @Column({ + nullable: true, + select: false, + type: 'text', + comment: 'used only for oats etl process', + }) + oatsIssueId?: string | null; + @Column({ nullable: true, type: 'text', diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1712618156742-add_cols_and_contraint_to_inq_docs.ts b/services/apps/alcs/src/providers/typeorm/migrations/1712618156742-add_cols_and_contraint_to_inq_docs.ts new file mode 100644 index 0000000000..65b7b464c2 --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1712618156742-add_cols_and_contraint_to_inq_docs.ts @@ -0,0 +1,53 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddColsAndContraintToInqDocs1712618156742 + implements MigrationInterface +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry_document" ADD "oats_document_id" text`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry_document" ADD "oats_issue_id" text`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."document" ADD "oats_issue_id" text`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "alcs"."inquiry_document"."oats_document_id" IS 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.documents/alcs.documents to alcs.inquiry_document.'`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "alcs"."inquiry_document"."oats_issue_id" IS 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.documents/alcs.documents to alcs.inquiry_document.'`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "alcs"."document"."oats_issue_id" IS 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.documents/alcs.documents to alcs.document.'`, + ); + await queryRunner.query( + `ALTER TABLE alcs.inquiry_document ADD CONSTRAINT unique_oats_issue_ids UNIQUE(oats_document_id, oats_issue_id)`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry_document" DROP CONSTRAINT unique_oats_issue_ids`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "alcs"."inquiry_document"."oats_document_id" IS 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.documents/alcs.documents to alcs.inquiry_document.'`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "alcs"."inquiry_document"."oats_issue_id" IS 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.documents/alcs.documents to alcs.inquiry_document.'`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "alcs"."document"."oats_issue_id" IS 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.documents/alcs.documents to alcs.document.'`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry_document" DROP COLUMN "oats_document_id"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry_document" DROP COLUMN "oats_issue_id"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."document" DROP COLUMN "oats_issue_id"`, + ); + } +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1712619551207-add_auditor.ts b/services/apps/alcs/src/providers/typeorm/migrations/1712619551207-add_auditor.ts new file mode 100644 index 0000000000..1aedf2c06a --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1712619551207-add_auditor.ts @@ -0,0 +1,15 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddAuditor1712619551207 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry_document" ADD "audit_created_by" varchar`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry_document" DROP COLUMN "audit_created_by"`, + ); + } +} From edb9aad52f540400a4d01e181a9b06eaa4a5e6c2 Mon Sep 17 00:00:00 2001 From: mhuseinov <61513701+mhuseinov@users.noreply.github.com> Date: Tue, 9 Apr 2024 10:04:48 -0700 Subject: [PATCH 110/153] Feature/alcs 1739 drag drop doc upload (#1588) Drag&Drop document upload in ALCS: application documents and decisions noi documents and decisions planning review documents and decisions srw documents and decisions inquiry documents and decisions --- ...sion-document-upload-dialog.component.html | 17 ++++-- ...sion-document-upload-dialog.component.scss | 33 ++++++++++ ...cision-document-upload-dialog.component.ts | 15 ++++- .../document-upload-dialog.component.html | 19 ++++-- .../document-upload-dialog.component.scss | 33 ++++++++++ .../document-upload-dialog.component.ts | 15 ++++- .../document-upload-dialog.component.html | 17 ++++-- .../document-upload-dialog.component.scss | 34 +++++++++++ .../document-upload-dialog.component.ts | 15 ++++- .../features/inquiry/inquiry.component.scss | 1 - .../app/features/inquiry/inquiry.module.ts | 2 +- ...sion-document-upload-dialog.component.html | 17 ++++-- ...sion-document-upload-dialog.component.scss | 33 ++++++++++ ...cision-document-upload-dialog.component.ts | 15 ++++- .../document-upload-dialog.component.html | 19 ++++-- .../document-upload-dialog.component.scss | 33 ++++++++++ .../document-upload-dialog.component.ts | 15 ++++- .../document-upload-dialog.component.html | 17 ++++-- .../document-upload-dialog.component.scss | 33 ++++++++++ .../document-upload-dialog.component.ts | 15 ++++- ...sion-document-upload-dialog.component.html | 17 ++++-- ...sion-document-upload-dialog.component.scss | 33 ++++++++++ ...cision-document-upload-dialog.component.ts | 15 ++++- .../document-upload-dialog.component.html | 17 ++++-- .../document-upload-dialog.component.scss | 33 ++++++++++ .../document-upload-dialog.component.ts | 15 ++++- .../drag-drop-file.directive.spec.ts | 18 ++++++ .../drag-drop-file.directive.ts | 61 +++++++++++++++++++ alcs-frontend/src/app/shared/shared.module.ts | 3 + 29 files changed, 558 insertions(+), 52 deletions(-) create mode 100644 alcs-frontend/src/app/shared/drag-drop-file/drag-drop-file.directive.spec.ts create mode 100644 alcs-frontend/src/app/shared/drag-drop-file/drag-drop-file.directive.ts diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html index 645cfe598c..7ea010a0d0 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html @@ -8,18 +8,25 @@

{{ title }} Document

Document Upload*
- + +
or drag and drop them here
+ +
{{ pendingFile.name }} diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.scss b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.scss index ceadfa76d1..7c95496402 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.scss +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.scss @@ -47,3 +47,36 @@ a { align-items: center; } } + +.file-drag-drop { + background: colors.$white; + border-radius: 4px; + + &:hover { + background: colors.$grey-light !important; + } + + button:nth-child(1) { + width: 100%; + background: colors.$white; + padding: 24px; + border: none; + + &:hover { + background: colors.$grey-light !important; + } + } + + .drag-text { + margin-top: 14px; + color: colors.$grey; + } + + .icon { + color: colors.$grey; + font-size: 36px; + height: 36px; + align-content: center; + margin-bottom: 4px; + } +} \ No newline at end of file diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts index ecea8098b1..a2855cb2cc 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts @@ -1,11 +1,12 @@ import { HttpErrorResponse } from '@angular/common/http'; -import { Component, Inject, OnInit } from '@angular/core'; +import { Component, EventEmitter, Inject, OnInit, Output } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { ApplicationDecisionDocumentDto } from '../../../../../../services/application/decision/application-decision-v2/application-decision-v2.dto'; import { ApplicationDecisionV2Service } from '../../../../../../services/application/decision/application-decision-v2/application-decision-v2.service'; import { ToastService } from '../../../../../../services/toast/toast.service'; import { DOCUMENT_SOURCE } from '../../../../../../shared/document/document.dto'; +import { FileHandle } from '../../../../../../shared/drag-drop-file/drag-drop-file.directive'; import { splitExtension } from '../../../../../../shared/utils/file'; @Component({ @@ -20,6 +21,8 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { allowsFileEdit = true; documentType = 'Decision Package'; + @Output() uploadFiles: EventEmitter = new EventEmitter(); + name = new FormControl('', [Validators.required]); type = new FormControl({ disabled: true, value: undefined }, [Validators.required]); source = new FormControl({ disabled: true, value: DOCUMENT_SOURCE.ALC }, [Validators.required]); @@ -116,6 +119,7 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { this.pendingFile = undefined; this.existingFile = undefined; this.extension = ''; + this.name.setValue(''); } openFile() { @@ -134,4 +138,13 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { ); } } + + filesDropped($event: FileHandle) { + this.pendingFile = $event.file; + const { fileName, extension } = splitExtension(this.pendingFile.name); + this.extension = extension; + this.name.setValue(fileName); + this.showVirusError = false; + this.uploadFiles.emit($event); + } } diff --git a/alcs-frontend/src/app/features/application/documents/document-upload-dialog/document-upload-dialog.component.html b/alcs-frontend/src/app/features/application/documents/document-upload-dialog/document-upload-dialog.component.html index 70b0887678..e3b824a029 100644 --- a/alcs-frontend/src/app/features/application/documents/document-upload-dialog/document-upload-dialog.component.html +++ b/alcs-frontend/src/app/features/application/documents/document-upload-dialog/document-upload-dialog.component.html @@ -1,7 +1,7 @@

{{ title }} Document

Superseded - Not associated with Applicant Submission in PortalSuperseded - Not associated with Applicant Submission in Portal
@@ -11,18 +11,25 @@

{{ title }} Document

Document Upload*
- + +
or drag and drop them here
+ +
{{ pendingFile.name }} diff --git a/alcs-frontend/src/app/features/application/documents/document-upload-dialog/document-upload-dialog.component.scss b/alcs-frontend/src/app/features/application/documents/document-upload-dialog/document-upload-dialog.component.scss index e4fa72a650..ba93743c38 100644 --- a/alcs-frontend/src/app/features/application/documents/document-upload-dialog/document-upload-dialog.component.scss +++ b/alcs-frontend/src/app/features/application/documents/document-upload-dialog/document-upload-dialog.component.scss @@ -53,3 +53,36 @@ a { color: #fff; padding: 0 4px; } + +.file-drag-drop { + background: colors.$white; + border-radius: 4px; + + &:hover { + background: colors.$grey-light !important; + } + + button:nth-child(1) { + width: 100%; + background: colors.$white; + padding: 24px; + border: none; + + &:hover { + background: colors.$grey-light !important; + } + } + + .drag-text { + margin-top: 14px; + color: colors.$grey; + } + + .icon { + color: colors.$grey; + font-size: 36px; + height: 36px; + align-content: center; + margin-bottom: 4px; + } +} \ No newline at end of file diff --git a/alcs-frontend/src/app/features/application/documents/document-upload-dialog/document-upload-dialog.component.ts b/alcs-frontend/src/app/features/application/documents/document-upload-dialog/document-upload-dialog.component.ts index 14648f65ca..793a43da4a 100644 --- a/alcs-frontend/src/app/features/application/documents/document-upload-dialog/document-upload-dialog.component.ts +++ b/alcs-frontend/src/app/features/application/documents/document-upload-dialog/document-upload-dialog.component.ts @@ -1,5 +1,5 @@ import { HttpErrorResponse } from '@angular/common/http'; -import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; +import { Component, EventEmitter, Inject, OnDestroy, OnInit, Output } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Subject } from 'rxjs'; @@ -18,6 +18,7 @@ import { DocumentTypeDto, } from '../../../../shared/document/document.dto'; import { splitExtension } from '../../../../shared/utils/file'; +import { FileHandle } from '../../../../shared/drag-drop-file/drag-drop-file.directive'; @Component({ selector: 'app-document-upload-dialog', @@ -28,6 +29,8 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { $destroy = new Subject(); DOCUMENT_TYPE = DOCUMENT_TYPE; + @Output() uploadFiles: EventEmitter = new EventEmitter(); + title = 'Create'; isDirty = false; isSaving = false; @@ -258,6 +261,7 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { this.pendingFile = undefined; this.existingFile = undefined; this.extension = ''; + this.name.setValue(''); } openFile() { @@ -276,6 +280,15 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { } } + filesDropped($event: FileHandle) { + this.pendingFile = $event.file; + const { fileName, extension } = splitExtension(this.pendingFile.name); + this.extension = extension; + this.name.setValue(fileName); + this.showVirusError = false; + this.uploadFiles.emit($event); + } + private async loadDocumentTypes() { const docTypes = await this.applicationDocumentService.fetchTypes(); docTypes.sort((a, b) => (a.label > b.label ? 1 : -1)); diff --git a/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.html b/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.html index 2bf390f45c..fdc228b65b 100644 --- a/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.html +++ b/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.html @@ -8,18 +8,25 @@

{{ title }} Document

Document Upload*
- + +
or drag and drop them here
+ +
{{ pendingFile.name }} diff --git a/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.scss b/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.scss index e4fa72a650..fcdc947261 100644 --- a/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.scss +++ b/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.scss @@ -53,3 +53,37 @@ a { color: #fff; padding: 0 4px; } + + +.file-drag-drop { + background: colors.$white; + border-radius: 4px; + + &:hover { + background: colors.$grey-light !important; + } + + button:nth-child(1) { + width: 100%; + background: colors.$white; + padding: 24px; + border: none; + + &:hover { + background: colors.$grey-light !important; + } + } + + .drag-text { + margin-top: 14px; + color: colors.$grey; + } + + .icon { + color: colors.$grey; + font-size: 36px; + height: 36px; + align-content: center; + margin-bottom: 4px; + } +} \ No newline at end of file diff --git a/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.ts b/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.ts index b312dfb09c..1b0c751f46 100644 --- a/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.ts +++ b/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.ts @@ -1,5 +1,5 @@ import { HttpErrorResponse } from '@angular/common/http'; -import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; +import { Component, EventEmitter, Inject, OnDestroy, OnInit, Output } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Subject } from 'rxjs'; @@ -16,6 +16,7 @@ import { DocumentTypeDto, } from '../../../../shared/document/document.dto'; import { splitExtension } from '../../../../shared/utils/file'; +import { FileHandle } from '../../../../shared/drag-drop-file/drag-drop-file.directive'; @Component({ selector: 'app-document-upload-dialog', @@ -26,6 +27,8 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { $destroy = new Subject(); DOCUMENT_TYPE = DOCUMENT_TYPE; + @Output() uploadFiles: EventEmitter = new EventEmitter(); + title = 'Create'; isDirty = false; isSaving = false; @@ -155,6 +158,7 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { this.pendingFile = undefined; this.existingFile = undefined; this.extension = ''; + this.name.setValue(''); } openFile() { @@ -170,6 +174,15 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { } } + filesDropped($event: FileHandle) { + this.pendingFile = $event.file; + const { fileName, extension } = splitExtension(this.pendingFile.name); + this.extension = extension; + this.name.setValue(fileName); + this.showVirusError = false; + this.uploadFiles.emit($event); + } + private async loadDocumentTypes() { const docTypes = await this.inquiryDocumentService.fetchTypes(); docTypes.sort((a, b) => (a.label > b.label ? 1 : -1)); diff --git a/alcs-frontend/src/app/features/inquiry/inquiry.component.scss b/alcs-frontend/src/app/features/inquiry/inquiry.component.scss index 267cb53afa..141d867134 100644 --- a/alcs-frontend/src/app/features/inquiry/inquiry.component.scss +++ b/alcs-frontend/src/app/features/inquiry/inquiry.component.scss @@ -34,7 +34,6 @@ } .nav-link { - div { padding: 12px 24px; border: 2px solid transparent; diff --git a/alcs-frontend/src/app/features/inquiry/inquiry.module.ts b/alcs-frontend/src/app/features/inquiry/inquiry.module.ts index 2df479ebe1..1f6340c586 100644 --- a/alcs-frontend/src/app/features/inquiry/inquiry.module.ts +++ b/alcs-frontend/src/app/features/inquiry/inquiry.module.ts @@ -8,7 +8,7 @@ import { DetailsComponent } from './detail/details.component'; import { DocumentUploadDialogComponent } from './documents/document-upload-dialog/document-upload-dialog.component'; import { DocumentsComponent } from './documents/documents.component'; import { HeaderComponent } from './header/header.component'; -import { childRoutes, InquiryComponent } from './inquiry.component'; +import { InquiryComponent, childRoutes } from './inquiry.component'; import { OverviewComponent } from './overview/overview.component'; import { ParcelsComponent } from './parcel/parcels.component'; diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html index 645cfe598c..7ea010a0d0 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html @@ -8,18 +8,25 @@

{{ title }} Document

Document Upload*
- + +
or drag and drop them here
+ +
{{ pendingFile.name }} diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.scss b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.scss index ceadfa76d1..27b3b52f32 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.scss +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.scss @@ -47,3 +47,36 @@ a { align-items: center; } } + +.file-drag-drop { + background: colors.$white; + border-radius: 4px; + + &:hover { + background: colors.$grey-light !important; + } + + button:nth-child(1) { + width: 100%; + background: colors.$white; + padding: 24px; + border: none; + + &:hover { + background: colors.$grey-light !important; + } + } + + .drag-text { + margin-top: 14px; + color: colors.$grey; + } + + .icon { + color: colors.$grey; + font-size: 36px; + height: 36px; + align-content: center; + margin-bottom: 4px; + } +} diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts index c87d60126d..216603ce5d 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts @@ -1,11 +1,12 @@ import { HttpErrorResponse } from '@angular/common/http'; -import { Component, Inject, OnInit } from '@angular/core'; +import { Component, EventEmitter, Inject, OnInit, Output } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { NoticeOfIntentDecisionV2Service } from '../../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision-v2.service'; import { NoticeOfIntentDecisionDocumentDto } from '../../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; import { ToastService } from '../../../../../../services/toast/toast.service'; import { DOCUMENT_SOURCE } from '../../../../../../shared/document/document.dto'; +import { FileHandle } from '../../../../../../shared/drag-drop-file/drag-drop-file.directive'; import { splitExtension } from '../../../../../../shared/utils/file'; @Component({ @@ -20,6 +21,8 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { allowsFileEdit = true; documentType = 'Decision Package'; + @Output() uploadFiles: EventEmitter = new EventEmitter(); + name = new FormControl('', [Validators.required]); type = new FormControl({ disabled: true, value: undefined }, [Validators.required]); source = new FormControl({ disabled: true, value: DOCUMENT_SOURCE.ALC }, [Validators.required]); @@ -116,6 +119,7 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { this.pendingFile = undefined; this.existingFile = undefined; this.extension = ''; + this.name.setValue(''); } openFile() { @@ -134,4 +138,13 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { ); } } + + filesDropped($event: FileHandle) { + this.pendingFile = $event.file; + const { fileName, extension } = splitExtension(this.pendingFile.name); + this.extension = extension; + this.name.setValue(fileName); + this.showVirusError = false; + this.uploadFiles.emit($event); + } } diff --git a/alcs-frontend/src/app/features/notice-of-intent/documents/document-upload-dialog/document-upload-dialog.component.html b/alcs-frontend/src/app/features/notice-of-intent/documents/document-upload-dialog/document-upload-dialog.component.html index 220c3f6f85..25a0845a98 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/documents/document-upload-dialog/document-upload-dialog.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/documents/document-upload-dialog/document-upload-dialog.component.html @@ -1,7 +1,7 @@

{{ title }} Document

Superseded - Not associated with Applicant Submission in PortalSuperseded - Not associated with Applicant Submission in Portal
@@ -11,18 +11,25 @@

{{ title }} Document

Document Upload*
- + +
or drag and drop them here
+ +
{{ pendingFile.name }} diff --git a/alcs-frontend/src/app/features/notice-of-intent/documents/document-upload-dialog/document-upload-dialog.component.scss b/alcs-frontend/src/app/features/notice-of-intent/documents/document-upload-dialog/document-upload-dialog.component.scss index e4fa72a650..ba93743c38 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/documents/document-upload-dialog/document-upload-dialog.component.scss +++ b/alcs-frontend/src/app/features/notice-of-intent/documents/document-upload-dialog/document-upload-dialog.component.scss @@ -53,3 +53,36 @@ a { color: #fff; padding: 0 4px; } + +.file-drag-drop { + background: colors.$white; + border-radius: 4px; + + &:hover { + background: colors.$grey-light !important; + } + + button:nth-child(1) { + width: 100%; + background: colors.$white; + padding: 24px; + border: none; + + &:hover { + background: colors.$grey-light !important; + } + } + + .drag-text { + margin-top: 14px; + color: colors.$grey; + } + + .icon { + color: colors.$grey; + font-size: 36px; + height: 36px; + align-content: center; + margin-bottom: 4px; + } +} \ No newline at end of file diff --git a/alcs-frontend/src/app/features/notice-of-intent/documents/document-upload-dialog/document-upload-dialog.component.ts b/alcs-frontend/src/app/features/notice-of-intent/documents/document-upload-dialog/document-upload-dialog.component.ts index 2a32cbf38a..2f4a9ff3a1 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/documents/document-upload-dialog/document-upload-dialog.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/documents/document-upload-dialog/document-upload-dialog.component.ts @@ -1,5 +1,5 @@ import { HttpErrorResponse } from '@angular/common/http'; -import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; +import { Component, EventEmitter, Inject, OnDestroy, OnInit, Output } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Subject } from 'rxjs'; @@ -17,6 +17,7 @@ import { DOCUMENT_TYPE, DocumentTypeDto, } from '../../../../shared/document/document.dto'; +import { FileHandle } from '../../../../shared/drag-drop-file/drag-drop-file.directive'; import { splitExtension } from '../../../../shared/utils/file'; @Component({ @@ -28,6 +29,8 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { $destroy = new Subject(); DOCUMENT_TYPE = DOCUMENT_TYPE; + @Output() uploadFiles: EventEmitter = new EventEmitter(); + title = 'Create'; isDirty = false; isSaving = false; @@ -209,6 +212,7 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { this.pendingFile = undefined; this.existingFile = undefined; this.extension = ''; + this.name.setValue(''); } openFile() { @@ -224,6 +228,15 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { } } + filesDropped($event: FileHandle) { + this.pendingFile = $event.file; + const { fileName, extension } = splitExtension(this.pendingFile.name); + this.extension = extension; + this.name.setValue(fileName); + this.showVirusError = false; + this.uploadFiles.emit($event); + } + private async prepareCertificateOfTitleUpload(uuid?: string) { const parcels = await this.noiParcelService.fetchParcels(this.data.fileId); if (parcels.length > 0) { diff --git a/alcs-frontend/src/app/features/notification/documents/document-upload-dialog/document-upload-dialog.component.html b/alcs-frontend/src/app/features/notification/documents/document-upload-dialog/document-upload-dialog.component.html index e7847c3adb..56e04b839c 100644 --- a/alcs-frontend/src/app/features/notification/documents/document-upload-dialog/document-upload-dialog.component.html +++ b/alcs-frontend/src/app/features/notification/documents/document-upload-dialog/document-upload-dialog.component.html @@ -8,18 +8,25 @@

{{ title }} Document

Document Upload*
- + +
or drag and drop them here
+ +
{{ pendingFile.name }} diff --git a/alcs-frontend/src/app/features/notification/documents/document-upload-dialog/document-upload-dialog.component.scss b/alcs-frontend/src/app/features/notification/documents/document-upload-dialog/document-upload-dialog.component.scss index e4fa72a650..ba93743c38 100644 --- a/alcs-frontend/src/app/features/notification/documents/document-upload-dialog/document-upload-dialog.component.scss +++ b/alcs-frontend/src/app/features/notification/documents/document-upload-dialog/document-upload-dialog.component.scss @@ -53,3 +53,36 @@ a { color: #fff; padding: 0 4px; } + +.file-drag-drop { + background: colors.$white; + border-radius: 4px; + + &:hover { + background: colors.$grey-light !important; + } + + button:nth-child(1) { + width: 100%; + background: colors.$white; + padding: 24px; + border: none; + + &:hover { + background: colors.$grey-light !important; + } + } + + .drag-text { + margin-top: 14px; + color: colors.$grey; + } + + .icon { + color: colors.$grey; + font-size: 36px; + height: 36px; + align-content: center; + margin-bottom: 4px; + } +} \ No newline at end of file diff --git a/alcs-frontend/src/app/features/notification/documents/document-upload-dialog/document-upload-dialog.component.ts b/alcs-frontend/src/app/features/notification/documents/document-upload-dialog/document-upload-dialog.component.ts index 816419dead..0118619579 100644 --- a/alcs-frontend/src/app/features/notification/documents/document-upload-dialog/document-upload-dialog.component.ts +++ b/alcs-frontend/src/app/features/notification/documents/document-upload-dialog/document-upload-dialog.component.ts @@ -1,5 +1,5 @@ import { HttpErrorResponse } from '@angular/common/http'; -import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; +import { Component, EventEmitter, Inject, OnDestroy, OnInit, Output } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Subject } from 'rxjs'; @@ -14,6 +14,7 @@ import { DocumentTypeDto, } from '../../../../shared/document/document.dto'; import { splitExtension } from '../../../../shared/utils/file'; +import { FileHandle } from '../../../../shared/drag-drop-file/drag-drop-file.directive'; @Component({ selector: 'app-document-upload-dialog', @@ -24,6 +25,8 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { $destroy = new Subject(); DOCUMENT_TYPE = DOCUMENT_TYPE; + @Output() uploadFiles: EventEmitter = new EventEmitter(); + title = 'Create'; isDirty = false; isSaving = false; @@ -166,6 +169,7 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { this.pendingFile = undefined; this.existingFile = undefined; this.extension = ''; + this.name.setValue(''); } openFile() { @@ -184,6 +188,15 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { } } + filesDropped($event: FileHandle) { + this.pendingFile = $event.file; + const { fileName, extension } = splitExtension(this.pendingFile.name); + this.extension = extension; + this.name.setValue(fileName); + this.showVirusError = false; + this.uploadFiles.emit($event); + } + private async loadDocumentTypes() { const docTypes = await this.notificationDocumentService.fetchTypes(); docTypes.sort((a, b) => (a.label > b.label ? 1 : -1)); diff --git a/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html b/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html index 0b3e7ea42a..c0ed9811c1 100644 --- a/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html +++ b/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html @@ -8,18 +8,25 @@

{{ title }} Document

Document Upload*
- + +
or drag and drop them here
+ +
{{ pendingFile.name }} diff --git a/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.scss b/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.scss index f8e75ea5f5..5a86cf7e2c 100644 --- a/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.scss +++ b/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.scss @@ -47,3 +47,36 @@ a { align-items: center; } } + +.file-drag-drop { + background: colors.$white; + border-radius: 4px; + + &:hover { + background: colors.$grey-light !important; + } + + button:nth-child(1) { + width: 100%; + background: colors.$white; + padding: 24px; + border: none; + + &:hover { + background: colors.$grey-light !important; + } + } + + .drag-text { + margin-top: 14px; + color: colors.$grey; + } + + .icon { + color: colors.$grey; + font-size: 36px; + height: 36px; + align-content: center; + margin-bottom: 4px; + } +} \ No newline at end of file diff --git a/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts b/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts index 605894007e..8cb4121902 100644 --- a/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts +++ b/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts @@ -1,11 +1,12 @@ import { HttpErrorResponse } from '@angular/common/http'; -import { Component, Inject, OnInit } from '@angular/core'; +import { Component, EventEmitter, Inject, OnInit, Output } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { PlanningReviewDecisionDocumentDto } from '../../../../../services/planning-review/planning-review-decision/planning-review-decision.dto'; import { PlanningReviewDecisionService } from '../../../../../services/planning-review/planning-review-decision/planning-review-decision.service'; import { ToastService } from '../../../../../services/toast/toast.service'; import { DOCUMENT_SOURCE } from '../../../../../shared/document/document.dto'; +import { FileHandle } from '../../../../../shared/drag-drop-file/drag-drop-file.directive'; import { splitExtension } from '../../../../../shared/utils/file'; @Component({ @@ -20,6 +21,8 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { allowsFileEdit = true; documentType = 'Decision Package'; + @Output() uploadFiles: EventEmitter = new EventEmitter(); + name = new FormControl('', [Validators.required]); type = new FormControl({ disabled: true, value: undefined }, [Validators.required]); source = new FormControl({ disabled: true, value: DOCUMENT_SOURCE.ALC }, [Validators.required]); @@ -113,6 +116,7 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { this.pendingFile = undefined; this.existingFile = undefined; this.extension = ''; + this.name.setValue(''); } openFile() { @@ -131,4 +135,13 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { ); } } + + filesDropped($event: FileHandle) { + this.pendingFile = $event.file; + const { fileName, extension } = splitExtension(this.pendingFile.name); + this.extension = extension; + this.name.setValue(fileName); + this.showVirusError = false; + this.uploadFiles.emit($event); + } } diff --git a/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.html b/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.html index 52f5c1a2d4..e1e82bbaa9 100644 --- a/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.html +++ b/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.html @@ -8,18 +8,25 @@

{{ title }} Document

Document Upload*
- + +
or drag and drop them here
+ +
{{ pendingFile.name }} diff --git a/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.scss b/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.scss index e4fa72a650..312d8fadba 100644 --- a/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.scss +++ b/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.scss @@ -53,3 +53,36 @@ a { color: #fff; padding: 0 4px; } + +.file-drag-drop { + background: colors.$white; + border-radius: 4px; + + &:hover { + background: colors.$grey-light !important; + } + + button:nth-child(1) { + width: 100%; + background: colors.$white; + padding: 24px; + border: none; + + &:hover { + background: colors.$grey-light !important; + } + } + + .drag-text { + margin-top: 14px; + color: colors.$grey; + } + + .icon { + color: colors.$grey; + font-size: 36px; + height: 36px; + align-content: center; + margin-bottom: 4px; + } +} diff --git a/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.ts b/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.ts index 986784e9df..705de633f9 100644 --- a/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.ts +++ b/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.ts @@ -1,5 +1,5 @@ import { HttpErrorResponse } from '@angular/common/http'; -import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; +import { Component, EventEmitter, Inject, OnDestroy, OnInit, Output } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Subject } from 'rxjs'; @@ -10,6 +10,7 @@ import { import { PlanningReviewDocumentService } from '../../../../services/planning-review/planning-review-document/planning-review-document.service'; import { ToastService } from '../../../../services/toast/toast.service'; import { DOCUMENT_SOURCE, DOCUMENT_TYPE, DocumentTypeDto } from '../../../../shared/document/document.dto'; +import { FileHandle } from '../../../../shared/drag-drop-file/drag-drop-file.directive'; import { splitExtension } from '../../../../shared/utils/file'; @Component({ @@ -21,6 +22,8 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { $destroy = new Subject(); DOCUMENT_TYPE = DOCUMENT_TYPE; + @Output() uploadFiles: EventEmitter = new EventEmitter(); + title = 'Create'; isDirty = false; isSaving = false; @@ -155,6 +158,7 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { this.pendingFile = undefined; this.existingFile = undefined; this.extension = ''; + this.name.setValue(''); } openFile() { @@ -173,6 +177,15 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { } } + filesDropped($event: FileHandle) { + this.pendingFile = $event.file; + const { fileName, extension } = splitExtension(this.pendingFile.name); + this.extension = extension; + this.name.setValue(fileName); + this.showVirusError = false; + this.uploadFiles.emit($event); + } + private async loadDocumentTypes() { const docTypes = await this.planningReviewDocumentService.fetchTypes(); docTypes.sort((a, b) => (a.label > b.label ? 1 : -1)); diff --git a/alcs-frontend/src/app/shared/drag-drop-file/drag-drop-file.directive.spec.ts b/alcs-frontend/src/app/shared/drag-drop-file/drag-drop-file.directive.spec.ts new file mode 100644 index 0000000000..f2444f8226 --- /dev/null +++ b/alcs-frontend/src/app/shared/drag-drop-file/drag-drop-file.directive.spec.ts @@ -0,0 +1,18 @@ +import { DomSanitizer } from '@angular/platform-browser'; +import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { DragDropDirective } from './drag-drop-file.directive'; + +describe('DragDropDirective', () => { + let directive: DragDropDirective; + let sanitizer: DeepMocked; + + beforeEach(() => { + sanitizer = createMock(); + sanitizer.bypassSecurityTrustUrl.mockReturnValue('mock'); + directive = new DragDropDirective(sanitizer); + }); + + it('should create an instance', () => { + expect(directive).toBeTruthy(); + }); +}); diff --git a/alcs-frontend/src/app/shared/drag-drop-file/drag-drop-file.directive.ts b/alcs-frontend/src/app/shared/drag-drop-file/drag-drop-file.directive.ts new file mode 100644 index 0000000000..713fcbc6f3 --- /dev/null +++ b/alcs-frontend/src/app/shared/drag-drop-file/drag-drop-file.directive.ts @@ -0,0 +1,61 @@ +import { Directive, EventEmitter, HostBinding, HostListener, Input, Output } from '@angular/core'; +import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; + +export interface FileHandle { + file: File; + url: SafeUrl; +} + +@Directive({ + selector: '[appDragDropFile]', +}) +export class DragDropDirective { + private backgroundColor = '#fff'; + private borderStyle = '0.15rem dashed #56565650'; + + @Input() disabled = false; + @Output() files: EventEmitter = new EventEmitter(); + + @HostBinding('style.background') private background = this.backgroundColor; + @HostBinding('style.border') private border = this.borderStyle; + + constructor(private sanitizer: DomSanitizer) {} + + @HostListener('dragover', ['$event']) + public onDragOver(evt: DragEvent): void { + if (!this.disabled) { + evt.preventDefault(); + evt.stopPropagation(); + this.background = '#acd2ed30'; + this.border = '0.15rem solid #0c2e4650 !important'; + } + } + + @HostListener('dragleave', ['$event']) + public onDragLeave(evt: DragEvent): void { + evt.preventDefault(); + evt.stopPropagation(); + this.background = this.backgroundColor; + this.border = this.borderStyle; + } + + @HostListener('drop', ['$event']) + public onDrop(dragEvent: DragEvent): void { + if (!this.disabled) { + dragEvent.preventDefault(); + dragEvent.stopPropagation(); + this.background = this.backgroundColor; + this.border = this.borderStyle; + + if (!dragEvent.dataTransfer) { + return; + } + + for (let i = 0; i < dragEvent.dataTransfer.files.length; i++) { + const file = dragEvent.dataTransfer.files[i]; + const url = this.sanitizer.bypassSecurityTrustUrl(window.URL.createObjectURL(file)); + this.files.emit({ file, url }); + } + } + } +} diff --git a/alcs-frontend/src/app/shared/shared.module.ts b/alcs-frontend/src/app/shared/shared.module.ts index 5a4dce9a07..b397eed418 100644 --- a/alcs-frontend/src/app/shared/shared.module.ts +++ b/alcs-frontend/src/app/shared/shared.module.ts @@ -72,6 +72,7 @@ import { TimelineComponent } from './timeline/timeline.component'; import { DATE_FORMATS } from './utils/date-format'; import { ExtensionsDatepickerFormatter } from './utils/extensions-datepicker-formatter'; import { WarningBannerComponent } from './warning-banner/warning-banner.component'; +import { DragDropDirective } from './drag-drop-file/drag-drop-file.directive'; @NgModule({ declarations: [ @@ -111,6 +112,7 @@ import { WarningBannerComponent } from './warning-banner/warning-banner.componen TableColumnNoDataPipe, InlineChairReviewOutcomeComponent, InlineButtonToggleComponent, + DragDropDirective ], imports: [ CommonModule, @@ -208,6 +210,7 @@ import { WarningBannerComponent } from './warning-banner/warning-banner.componen InlineChairReviewOutcomeComponent, MatSlideToggleModule, InlineButtonToggleComponent, + DragDropDirective ], }) export class SharedModule { From db55dcff005704f53bc2b1b43591bd8bed742624 Mon Sep 17 00:00:00 2001 From: Liam Stoddard Date: Tue, 9 Apr 2024 10:39:40 -0700 Subject: [PATCH 111/153] MR feedback --- .../alcs_documents_to_inquiry_documents.py | 11 ++++------- .../alcs_documents_to_notification_documents.py | 11 ++++------- .../alcs_documents_to_planning_review_documents.py | 11 ++++------- .../oats_documents_to_alcs_documents_inquiry.py | 12 +++++------- ...ts_documents_to_alcs_documents_planning_review.py | 11 ++++------- .../oats_documents_to_alcs_documents_srw.py | 11 ++++------- 6 files changed, 25 insertions(+), 42 deletions(-) diff --git a/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_inquiry_documents.py b/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_inquiry_documents.py index b57adf70c5..708a933c40 100644 --- a/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_inquiry_documents.py +++ b/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_inquiry_documents.py @@ -9,17 +9,14 @@ etl_name = "link_inquiry_documents_from_alcs" logger = setup_and_get_logger(etl_name) -""" - This script connects to postgress version of OATS DB and links data from ALCS documents to ALCS inquiry_document table. - - NOTE: - Before performing document import you need to import Inquiries and Inquiry documents. -""" - @inject_conn_pool def link_inquiry_documents(conn=None, batch_size=BATCH_UPLOAD_SIZE): """ + This script connects to postgres version of OATS DB and links data from ALCS documents to ALCS inquiry_document table. + + NOTE: + Before performing document import you need to import Inquiries and Inquiry documents. function uses a decorator pattern @inject_conn_pool to inject a database connection pool to the function. It fetches the total count of documents and prints it to the console. Then, it fetches the documents to insert in batches using document IDs, constructs an insert query, and processes them. """ logger.info(f"Start {etl_name}") diff --git a/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_notification_documents.py b/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_notification_documents.py index 715c9496bc..018607fb03 100644 --- a/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_notification_documents.py +++ b/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_notification_documents.py @@ -9,17 +9,14 @@ etl_name = "link_srw_documents_from_alcs" logger = setup_and_get_logger(etl_name) -""" - This script connects to postgress version of OATS DB and links data from ALCS documents to ALCS notification_document table. - - NOTE: - Before performing document import you need to import SRWs and SRW documents. -""" - @inject_conn_pool def link_srw_documents(conn=None, batch_size=BATCH_UPLOAD_SIZE): """ + This script connects to postgress version of OATS DB and links data from ALCS documents to ALCS notification_document table. + + NOTE: + Before performing document import you need to import SRWs and SRW documents. function uses a decorator pattern @inject_conn_pool to inject a database connection pool to the function. It fetches the total count of documents and prints it to the console. Then, it fetches the documents to insert in batches using document IDs, constructs an insert query, and processes them. """ logger.info(f"Start {etl_name}") diff --git a/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_planning_review_documents.py b/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_planning_review_documents.py index 90cb645bf6..3c17808a21 100644 --- a/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_planning_review_documents.py +++ b/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_planning_review_documents.py @@ -9,17 +9,14 @@ etl_name = "link_pr_documents_from_alcs" logger = setup_and_get_logger(etl_name) -""" - This script connects to postgress version of OATS DB and links data from ALCS documents to ALCS planning_review_document table. - - NOTE: - Before performing document import you need to import Planning Reviews and Planning Review documents. -""" - @inject_conn_pool def link_pr_documents(conn=None, batch_size=BATCH_UPLOAD_SIZE): """ + This script connects to postgress version of OATS DB and links data from ALCS documents to ALCS planning_review_document table. + + NOTE: + Before performing document import you need to import Planning Reviews and Planning Review documents. function uses a decorator pattern @inject_conn_pool to inject a database connection pool to the function. It fetches the total count of documents and prints it to the console. Then, it fetches the documents to insert in batches using document IDs, constructs an insert query, and processes them. """ logger.info(f"Start {etl_name}") diff --git a/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_inquiry.py b/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_inquiry.py index a3e2eed699..3c40362570 100644 --- a/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_inquiry.py +++ b/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_inquiry.py @@ -12,17 +12,15 @@ etl_name = "import_inquiry_documents_from_oats" logger = setup_and_get_logger(etl_name) -""" + +@inject_conn_pool +def import_oats_inquiry_documents(conn=None, batch_size=BATCH_UPLOAD_SIZE): + """ This script connects to postgress version of OATS DB and transfers data from OATS documents table to ALCS documents table. NOTE: Before performing document import you need to import Inquiries from oats. -""" - -@inject_conn_pool -def import_oats_inquiry_documents(conn=None, batch_size=BATCH_UPLOAD_SIZE): - """ function uses a decorator pattern @inject_conn_pool to inject a database connection pool to the function. It fetches the total count of documents and prints it to the console. Then, it fetches the documents to insert in batches using document IDs, constructs an insert query, and processes them. """ logger.info(f"Start {etl_name}") @@ -178,7 +176,7 @@ def _get_mime_type(data): @inject_conn_pool def document_inquiry_clean(conn=None): - logger.info("Start planning review related documents cleaning") + logger.info("Start inquiry related documents cleaning") with conn.cursor() as cursor: cursor.execute( f"DELETE FROM alcs.document WHERE audit_created_by = '{OATS_ETL_USER}' AND oats_issue_id IS NOT NULL;" diff --git a/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_planning_review.py b/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_planning_review.py index a83f7455c9..01c4f34930 100644 --- a/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_planning_review.py +++ b/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_planning_review.py @@ -12,17 +12,14 @@ etl_name = "import_pr_documents_from_oats" logger = setup_and_get_logger(etl_name) -""" - This script connects to postgress version of OATS DB and transfers data from OATS documents table to ALCS documents table. - - NOTE: - Before performing document import you need to import Planning Reviews from oats. -""" - @inject_conn_pool def import_oats_pr_documents(conn=None, batch_size=BATCH_UPLOAD_SIZE): """ + This script connects to postgress version of OATS DB and transfers data from OATS documents table to ALCS documents table. + + NOTE: + Before performing document import you need to import Planning Reviews from oats. function uses a decorator pattern @inject_conn_pool to inject a database connection pool to the function. It fetches the total count of documents and prints it to the console. Then, it fetches the documents to insert in batches using document IDs, constructs an insert query, and processes them. """ logger.info(f"Start {etl_name}") diff --git a/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_srw.py b/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_srw.py index 4385b6e43c..27739fcfd9 100644 --- a/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_srw.py +++ b/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_srw.py @@ -12,17 +12,14 @@ etl_name = "import_srw_documents_from_oats" logger = setup_and_get_logger(etl_name) -""" - This script connects to postgress version of OATS DB and transfers data from OATS documents table to ALCS documents table. - - NOTE: - Before performing document import you need to import SRWs from oats. -""" - @inject_conn_pool def import_oats_srw_documents(conn=None, batch_size=BATCH_UPLOAD_SIZE): """ + This script connects to postgress version of OATS DB and transfers data from OATS documents table to ALCS documents table. + + NOTE: + Before performing document import you need to import SRWs from oats. function uses a decorator pattern @inject_conn_pool to inject a database connection pool to the function. It fetches the total count of documents and prints it to the console. Then, it fetches the documents to insert in batches using document IDs, constructs an insert query, and processes them. """ logger.info(f"Start {etl_name}") From 45466fd7ba97dc95fb9efe65ddc103b63ee501ef Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Tue, 9 Apr 2024 10:58:32 -0700 Subject: [PATCH 112/153] More tests! * Also delete dead create code --- .../staff-journal.service.spec.ts | 55 ++++++++- .../application-decision-v2.service.spec.ts | 51 ++++++-- .../services/inquiry/inquiry.service.spec.ts | 86 ++++++++++++++ .../app/services/inquiry/inquiry.service.ts | 32 ++--- .../services/message/message.service.spec.ts | 55 +++++++++ .../notice-of-intent-parcel.service.spec.ts | 13 +- .../notice-of-intent/notice-of-intent.dto.ts | 9 -- .../notice-of-intent.service.spec.ts | 111 ++++++++++++------ .../notice-of-intent.service.ts | 23 +--- .../notification-detail.service.spec.ts | 43 +++++++ .../notification/notification.service.spec.ts | 105 ++++++++++++++++- .../app/services/toast/toast.service.spec.ts | 40 ++++++- .../notice-of-intent.controller.spec.ts | 26 +--- .../notice-of-intent.controller.ts | 26 +--- .../notice-of-intent/notice-of-intent.dto.ts | 33 +----- 15 files changed, 525 insertions(+), 183 deletions(-) create mode 100644 alcs-frontend/src/app/services/message/message.service.spec.ts diff --git a/alcs-frontend/src/app/services/application/application-staff-journal/staff-journal.service.spec.ts b/alcs-frontend/src/app/services/application/application-staff-journal/staff-journal.service.spec.ts index 6fabd946c4..941ae3fe1c 100644 --- a/alcs-frontend/src/app/services/application/application-staff-journal/staff-journal.service.spec.ts +++ b/alcs-frontend/src/app/services/application/application-staff-journal/staff-journal.service.spec.ts @@ -39,7 +39,7 @@ describe('ApplicationStaffJournalService', () => { { uuid: '1', }, - ]) + ]), ); const res = await service.fetchNotes('1'); @@ -55,7 +55,7 @@ describe('ApplicationStaffJournalService', () => { { uuid: '1', }, - ]) + ]), ); await service.createNoteForApplication({ body: '', applicationUuid: '' }); @@ -70,7 +70,7 @@ describe('ApplicationStaffJournalService', () => { { uuid: '1', }, - ]) + ]), ); await service.createNoteForNoticeOfIntent({ body: '', noticeOfIntentUuid: '' }); @@ -79,13 +79,58 @@ describe('ApplicationStaffJournalService', () => { expect(toastService.showSuccessToast).toHaveBeenCalledTimes(1); }); + it('should create a note for notifications', async () => { + httpClient.post.mockReturnValue( + of([ + { + uuid: '1', + }, + ]), + ); + + await service.createNoteForNotification({ body: '', notificationUuid: '' }); + + expect(httpClient.post).toHaveBeenCalledTimes(1); + expect(toastService.showSuccessToast).toHaveBeenCalledTimes(1); + }); + + it('should create a note for planning reviews', async () => { + httpClient.post.mockReturnValue( + of([ + { + uuid: '1', + }, + ]), + ); + + await service.createNoteForPlanningReview({ body: '', planningReviewUuid: '' }); + + expect(httpClient.post).toHaveBeenCalledTimes(1); + expect(toastService.showSuccessToast).toHaveBeenCalledTimes(1); + }); + + it('should create a note for inquiries', async () => { + httpClient.post.mockReturnValue( + of([ + { + uuid: '1', + }, + ]), + ); + + await service.createNoteForInquiry({ body: '', inquiryUuid: '' }); + + expect(httpClient.post).toHaveBeenCalledTimes(1); + expect(toastService.showSuccessToast).toHaveBeenCalledTimes(1); + }); + it('should update a note', async () => { httpClient.patch.mockReturnValue( of([ { uuid: '1', }, - ]) + ]), ); await service.updateNote({ body: '', uuid: '' }); @@ -100,7 +145,7 @@ describe('ApplicationStaffJournalService', () => { { uuid: '1', }, - ]) + ]), ); await service.deleteNote(''); diff --git a/alcs-frontend/src/app/services/application/decision/application-decision-v2/application-decision-v2.service.spec.ts b/alcs-frontend/src/app/services/application/decision/application-decision-v2/application-decision-v2.service.spec.ts index 9af77d3153..708fd31855 100644 --- a/alcs-frontend/src/app/services/application/decision/application-decision-v2/application-decision-v2.service.spec.ts +++ b/alcs-frontend/src/app/services/application/decision/application-decision-v2/application-decision-v2.service.spec.ts @@ -34,13 +34,31 @@ describe('ApplicationDecisionV2Service', () => { expect(service).toBeTruthy(); }); + it('should fetch and return an codes', async () => { + httpClient.get.mockReturnValue( + of({ + ceoCriterion: [ + { + code: 'CODE', + }, + ], + }), + ); + + const res = await service.fetchCodes(); + + expect(res.ceoCriterion.length).toEqual(1); + expect(res.ceoCriterion[0].code).toEqual('CODE'); + expect(httpClient.get).toHaveBeenCalledTimes(1); + }); + it('should fetch and return an applicationDto', async () => { httpClient.get.mockReturnValue( of([ { applicationFileNumber: '1', }, - ]) + ]), ); const res = await service.fetchByApplication('1'); @@ -49,11 +67,24 @@ describe('ApplicationDecisionV2Service', () => { expect(res[0].applicationFileNumber).toEqual('1'); }); + it('should fetch and return an decision', async () => { + httpClient.get.mockReturnValue( + of({ + applicationFileNumber: '1', + }), + ); + + const res = await service.getByUuid('1'); + + expect(res).toBeDefined(); + expect(res!.applicationFileNumber).toEqual('1'); + }); + it('should show a toast message if fetch fails', async () => { httpClient.get.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); const res = await service.fetchByApplication('1'); @@ -66,7 +97,7 @@ describe('ApplicationDecisionV2Service', () => { httpClient.patch.mockReturnValue( of({ applicationFileNumber: '1', - }) + }), ); await service.update('1', {}); @@ -79,7 +110,7 @@ describe('ApplicationDecisionV2Service', () => { httpClient.patch.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); try { @@ -96,7 +127,7 @@ describe('ApplicationDecisionV2Service', () => { httpClient.post.mockReturnValue( of({ applicationFileNumber: '1', - }) + }), ); await service.create({ @@ -119,7 +150,7 @@ describe('ApplicationDecisionV2Service', () => { httpClient.post.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); try { @@ -146,7 +177,7 @@ describe('ApplicationDecisionV2Service', () => { httpClient.delete.mockReturnValue( of({ applicationFileNumber: '1', - }) + }), ); await service.delete(''); @@ -159,7 +190,7 @@ describe('ApplicationDecisionV2Service', () => { httpClient.delete.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); try { @@ -178,7 +209,7 @@ describe('ApplicationDecisionV2Service', () => { { fileNumber: '1', }, - ]) + ]), ); await service.updateFile('', '', ''); @@ -199,7 +230,7 @@ describe('ApplicationDecisionV2Service', () => { httpClient.delete.mockReturnValue( of({ applicationFileNumber: '1', - }) + }), ); await service.deleteFile('', ''); diff --git a/alcs-frontend/src/app/services/inquiry/inquiry.service.spec.ts b/alcs-frontend/src/app/services/inquiry/inquiry.service.spec.ts index b8d39c8b1c..084256998f 100644 --- a/alcs-frontend/src/app/services/inquiry/inquiry.service.spec.ts +++ b/alcs-frontend/src/app/services/inquiry/inquiry.service.spec.ts @@ -33,6 +33,36 @@ describe('PlanningReviewService', () => { expect(service).toBeTruthy(); }); + it('should call get for fetchTypes', async () => { + httpClient.get.mockReturnValue( + of([ + { + code: 'CODE', + }, + ]), + ); + + const res = await service.fetchTypes(); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(res?.length).toEqual(1); + expect(res![0].code).toEqual('CODE'); + }); + + it('should show an error toast message if fetchTypes fails', async () => { + httpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }), + ); + + const res = await service.fetchTypes(); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(res).toBeUndefined(); + expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + it('should call post for create', async () => { httpClient.post.mockReturnValue( of({ @@ -73,6 +103,34 @@ describe('PlanningReviewService', () => { expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); }); + it('should fetch planning review by file number', async () => { + httpClient.get.mockReturnValue( + of({ + fileNumber: '1', + }), + ); + + const res = await service.fetch('1'); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(res).toBeDefined(); + expect(res!.fileNumber).toEqual('1'); + }); + + it('should show an error toast message if fetch by file number fails', async () => { + httpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }), + ); + + const res = await service.fetch('1'); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(res).toBeUndefined(); + expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + it('should fetch planning reviews by card', async () => { httpClient.get.mockReturnValue( of({ @@ -100,4 +158,32 @@ describe('PlanningReviewService', () => { expect(res).toBeUndefined(); expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); }); + + it('should call patch for update', async () => { + httpClient.patch.mockReturnValue( + of({ + fileNumber: '1', + }), + ); + + const res = await service.update('1', {}); + + expect(httpClient.patch).toHaveBeenCalledTimes(1); + expect(res).toBeDefined(); + expect(res!.fileNumber).toEqual('1'); + }); + + it('should show an error toast message if update fails', async () => { + httpClient.patch.mockReturnValue( + throwError(() => { + new Error(''); + }), + ); + + const res = await service.update('1', {}); + + expect(httpClient.patch).toHaveBeenCalledTimes(1); + expect(res).toBeUndefined(); + expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); + }); }); diff --git a/alcs-frontend/src/app/services/inquiry/inquiry.service.ts b/alcs-frontend/src/app/services/inquiry/inquiry.service.ts index eeb37e2925..0e2cb0b21a 100644 --- a/alcs-frontend/src/app/services/inquiry/inquiry.service.ts +++ b/alcs-frontend/src/app/services/inquiry/inquiry.service.ts @@ -16,51 +16,51 @@ export class InquiryService { private toastService: ToastService, ) {} - async create(meeting: CreateInquiryDto) { + async fetchTypes() { try { - const res = await firstValueFrom(this.http.post(`${this.url}`, meeting)); - this.toastService.showSuccessToast('Inquiry created'); - return res; + return await firstValueFrom(this.http.get(`${this.url}/types`)); } catch (err) { console.error(err); - this.toastService.showErrorToast('Failed to create inquiry'); + this.toastService.showErrorToast('Failed to fetch inquiry types'); } return; } - async fetchByCardUuid(id: string) { + async create(meeting: CreateInquiryDto) { try { - return await firstValueFrom(this.http.get(`${this.url}/card/${id}`)); + const res = await firstValueFrom(this.http.post(`${this.url}`, meeting)); + this.toastService.showSuccessToast('Inquiry created'); + return res; } catch (err) { console.error(err); - this.toastService.showErrorToast('Failed to fetch inquiry'); + this.toastService.showErrorToast('Failed to create inquiry'); } return; } - async fetchTypes() { + async fetch(fileNumber: string) { try { - return await firstValueFrom(this.http.get(`${this.url}/types`)); + return await firstValueFrom(this.http.get(`${this.url}/${fileNumber}`)); } catch (err) { console.error(err); - this.toastService.showErrorToast('Failed to fetch inquiry types'); + this.toastService.showErrorToast('Failed to update inquiry'); } return; } - async update(fileNumber: string, updateDto: UpdateInquiryDto) { + async fetchByCardUuid(id: string) { try { - return await firstValueFrom(this.http.patch(`${this.url}/${fileNumber}`, updateDto)); + return await firstValueFrom(this.http.get(`${this.url}/card/${id}`)); } catch (err) { console.error(err); - this.toastService.showErrorToast('Failed to update inquiry'); + this.toastService.showErrorToast('Failed to fetch inquiry'); } return; } - async fetch(fileNumber: string) { + async update(fileNumber: string, updateDto: UpdateInquiryDto) { try { - return await firstValueFrom(this.http.get(`${this.url}/${fileNumber}`)); + return await firstValueFrom(this.http.patch(`${this.url}/${fileNumber}`, updateDto)); } catch (err) { console.error(err); this.toastService.showErrorToast('Failed to update inquiry'); diff --git a/alcs-frontend/src/app/services/message/message.service.spec.ts b/alcs-frontend/src/app/services/message/message.service.spec.ts new file mode 100644 index 0000000000..80c08f3927 --- /dev/null +++ b/alcs-frontend/src/app/services/message/message.service.spec.ts @@ -0,0 +1,55 @@ +import { HttpClient } from '@angular/common/http'; +import { TestBed } from '@angular/core/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { of } from 'rxjs'; +import { MessageService } from './message.service'; + +describe('MessageService', () => { + let service: MessageService; + let httpClient: DeepMocked; + + beforeEach(() => { + httpClient = createMock(); + + TestBed.configureTestingModule({ + providers: [ + { + provide: HttpClient, + useValue: httpClient, + }, + ], + }); + service = TestBed.inject(MessageService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should call get for fetchMyNotifications', async () => { + httpClient.get.mockReturnValue(of([])); + + const res = await service.fetchMyNotifications(); + + expect(res).toBeDefined(); + expect(httpClient.get).toHaveBeenCalledTimes(1); + }); + + it('should call post for markRead', async () => { + httpClient.post.mockReturnValue(of([])); + + const res = await service.markRead('uuid'); + + expect(res).toBeDefined(); + expect(httpClient.post).toHaveBeenCalledTimes(1); + }); + + it('should call post for markAllRead', async () => { + httpClient.post.mockReturnValue(of([])); + + const res = await service.markAllRead(); + + expect(res).toBeDefined(); + expect(httpClient.post).toHaveBeenCalledTimes(1); + }); +}); diff --git a/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent-parcel/notice-of-intent-parcel.service.spec.ts b/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent-parcel/notice-of-intent-parcel.service.spec.ts index 89e25f0b2a..fee87bf80f 100644 --- a/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent-parcel/notice-of-intent-parcel.service.spec.ts +++ b/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent-parcel/notice-of-intent-parcel.service.spec.ts @@ -1,8 +1,8 @@ import { HttpClient } from '@angular/common/http'; import { TestBed } from '@angular/core/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { ToastService } from '../../toast/toast.service'; import { of } from 'rxjs'; +import { ToastService } from '../../toast/toast.service'; import { NoticeOfIntentParcelService } from './notice-of-intent-parcel.service'; @@ -37,6 +37,15 @@ describe('NoticeOfIntentParcelService', () => { const result = await service.fetchParcels('1'); expect(result).toEqual([]); - expect(mockHttpClient.get).toBeCalledTimes(1); + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + }); + + it('should call post for setting parcel area', async () => { + mockHttpClient.post.mockReturnValue(of([])); + + const result = await service.setParcelArea('1', 5); + + expect(result).toEqual([]); + expect(mockHttpClient.post).toHaveBeenCalledTimes(1); }); }); diff --git a/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent.dto.ts b/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent.dto.ts index ed209995c2..a525bb2bc1 100644 --- a/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent.dto.ts +++ b/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent.dto.ts @@ -11,15 +11,6 @@ export interface NoticeOfIntentSubtypeDto extends BaseCodeDto { isActive: boolean; } -export interface CreateNoticeOfIntentDto { - fileNumber: string; - localGovernmentUuid: string; - regionCode: string; - applicant: string; - boardCode: string; - dateSubmittedToAlc: number; -} - export interface NoticeOfIntentTypeDto extends BaseCodeDto { shortLabel: string; backgroundColor: string; diff --git a/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent.service.spec.ts b/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent.service.spec.ts index 1f8ac7de48..0f97aedb3d 100644 --- a/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent.service.spec.ts +++ b/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent.service.spec.ts @@ -34,45 +34,33 @@ describe('NoticeOfIntentService', () => { expect(service).toBeTruthy(); }); - it('should create covenants', async () => { - httpClient.post.mockReturnValue( - of({ - fileNumber: '1', - }) + it('should make a get request for fetching subTypes', async () => { + httpClient.get.mockReturnValue( + of([ + { + code: 'MAGIC', + }, + ]), ); - const res = await service.create({ - applicant: '', - boardCode: '', - fileNumber: '', - localGovernmentUuid: '', - regionCode: '', - dateSubmittedToAlc: 0, - }); + const res = await service.listSubtypes(); - expect(httpClient.post).toHaveBeenCalledTimes(1); - expect(res.fileNumber).toEqual('1'); + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(res.length).toEqual(1); + expect(res[0].code).toEqual('MAGIC'); }); - it('should show an error toast message if create fails', async () => { - httpClient.post.mockReturnValue( + it('should show an error toast when listSubtypes fails', async () => { + httpClient.get.mockReturnValue( throwError(() => { return new Error('Error'); - }) + }), ); - const promise = service.create({ - applicant: '', - boardCode: '', - fileNumber: '', - localGovernmentUuid: '', - regionCode: '', - dateSubmittedToAlc: 0, - }); - - await expect(promise).rejects.toMatchObject(new Error('Error')); + const res = await service.listSubtypes(); - expect(httpClient.post).toHaveBeenCalledTimes(1); + expect(res).toEqual([]); + expect(httpClient.get).toHaveBeenCalledTimes(1); expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); }); @@ -80,7 +68,7 @@ describe('NoticeOfIntentService', () => { httpClient.get.mockReturnValue( of({ fileNumber: '1', - }) + }), ); const res = await service.fetchByCardUuid('1'); @@ -94,7 +82,7 @@ describe('NoticeOfIntentService', () => { httpClient.get.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); await service.fetchByCardUuid('1'); @@ -103,11 +91,68 @@ describe('NoticeOfIntentService', () => { expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); }); + it('should fetch by file number', async () => { + httpClient.get.mockReturnValue( + of({ + fileNumber: '1', + }), + ); + + const res = await service.fetchByFileNumber('1'); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(res).toBeDefined(); + expect(res!.fileNumber).toEqual('1'); + }); + + it('should call get for searchByFileNumber', async () => { + httpClient.get.mockReturnValue( + of([ + { + fileNumber: '1', + }, + ]), + ); + + const res = await service.searchByFileNumber('1'); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(res.length).toEqual(1); + expect(res[0].fileNumber).toEqual('1'); + }); + + it('should show an error toast message if fetch by file number fails', async () => { + httpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }), + ); + + await service.fetchByFileNumber('1'); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + + it('should call post for update', async () => { + httpClient.post.mockReturnValue( + of({ + fileNumber: '1', + }), + ); + + await service.update('file-number', { + summary: 'summary', + }); + + expect(httpClient.post).toHaveBeenCalledTimes(1); + }); + it('should call post for cancel', async () => { httpClient.post.mockReturnValue( of({ fileNumber: '1', - }) + }), ); await service.cancel('file-number'); @@ -119,7 +164,7 @@ describe('NoticeOfIntentService', () => { httpClient.post.mockReturnValue( of({ fileNumber: '1', - }) + }), ); await service.uncancel('file-number'); diff --git a/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent.service.ts b/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent.service.ts index 2c2556c7a4..36cfebaa50 100644 --- a/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent.service.ts +++ b/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent.service.ts @@ -1,15 +1,10 @@ -import { HttpClient, HttpErrorResponse } from '@angular/common/http'; +import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { firstValueFrom } from 'rxjs'; import { environment } from '../../../environments/environment'; import { ApplicationDto } from '../application/application.dto'; import { ToastService } from '../toast/toast.service'; -import { - CreateNoticeOfIntentDto, - NoticeOfIntentDto, - NoticeOfIntentSubtypeDto, - UpdateNoticeOfIntentDto, -} from './notice-of-intent.dto'; +import { NoticeOfIntentDto, NoticeOfIntentSubtypeDto, UpdateNoticeOfIntentDto } from './notice-of-intent.dto'; @Injectable({ providedIn: 'root', @@ -32,20 +27,6 @@ export class NoticeOfIntentService { return []; } - async create(createDto: CreateNoticeOfIntentDto) { - try { - return await firstValueFrom(this.http.post(`${this.url}`, createDto)); - } catch (e) { - console.error(e); - if (e instanceof HttpErrorResponse && e.status === 400) { - this.toastService.showErrorToast(`Application/NOI with File ID ${createDto.fileNumber} already exists`); - } else { - this.toastService.showErrorToast('Failed to create Notice of Intent'); - } - throw e; - } - } - async fetchByCardUuid(id: string) { try { return await firstValueFrom(this.http.get(`${this.url}/card/${id}`)); diff --git a/alcs-frontend/src/app/services/notification/notification-detail.service.spec.ts b/alcs-frontend/src/app/services/notification/notification-detail.service.spec.ts index 7bda936631..df5ec80255 100644 --- a/alcs-frontend/src/app/services/notification/notification-detail.service.spec.ts +++ b/alcs-frontend/src/app/services/notification/notification-detail.service.spec.ts @@ -46,6 +46,49 @@ describe('NotificationDetailService', () => { expect(res!.fileNumber).toEqual('1'); }); + it('should call cancel on the service and reload for cancel', async () => { + notificationService.fetchByFileNumber.mockResolvedValue({ + fileNumber: '1', + } as NotificationDto); + notificationService.cancel.mockResolvedValue({} as any); + + await service.cancel('1'); + const res = await firstValueFrom(service.$notification); + + expect(notificationService.cancel).toHaveBeenCalledTimes(1); + expect(notificationService.fetchByFileNumber).toHaveBeenCalledTimes(1); + expect(res).toBeDefined(); + expect(res!.fileNumber).toEqual('1'); + }); + + it('should call uncancel on the service and reload for uncancel', async () => { + notificationService.fetchByFileNumber.mockResolvedValue({ + fileNumber: '1', + } as NotificationDto); + notificationService.uncancel.mockResolvedValue({} as any); + + await service.uncancel('1'); + const res = await firstValueFrom(service.$notification); + + expect(notificationService.uncancel).toHaveBeenCalledTimes(1); + expect(notificationService.fetchByFileNumber).toHaveBeenCalledTimes(1); + expect(res).toBeDefined(); + expect(res!.fileNumber).toEqual('1'); + }); + + it('should call resend response on the service for resend', async () => { + notificationService.resendResponse.mockResolvedValue({ + fileNumber: '1', + } as NotificationDto); + + await service.resendResponse('1'); + const res = await firstValueFrom(service.$notification); + + expect(notificationService.resendResponse).toHaveBeenCalledTimes(1); + expect(res).toBeDefined(); + expect(res!.fileNumber).toEqual('1'); + }); + it('should publish the updated noi for update', async () => { notificationService.update.mockResolvedValue({ fileNumber: '1', diff --git a/alcs-frontend/src/app/services/notification/notification.service.spec.ts b/alcs-frontend/src/app/services/notification/notification.service.spec.ts index f38a0de771..79845711ad 100644 --- a/alcs-frontend/src/app/services/notification/notification.service.spec.ts +++ b/alcs-frontend/src/app/services/notification/notification.service.spec.ts @@ -34,11 +34,38 @@ describe('NotificationService', () => { expect(service).toBeTruthy(); }); + it('should fetch by file number', async () => { + httpClient.get.mockReturnValue( + of({ + fileNumber: '1', + }), + ); + + const res = await service.fetchByFileNumber('1'); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(res).toBeDefined(); + expect(res!.fileNumber).toEqual('1'); + }); + + it('should show an error toast message if fetch by file number fails', async () => { + httpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }), + ); + + await service.searchByFileNumber('1'); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + it('should fetch by card', async () => { httpClient.get.mockReturnValue( of({ fileNumber: '1', - }) + }), ); const res = await service.fetchByCardUuid('1'); @@ -52,7 +79,7 @@ describe('NotificationService', () => { httpClient.get.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); await service.fetchByCardUuid('1'); @@ -61,39 +88,107 @@ describe('NotificationService', () => { expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); }); + it('should search by file number', async () => { + httpClient.get.mockReturnValue( + of([ + { + fileNumber: '1', + }, + ]), + ); + + const res = await service.searchByFileNumber('1'); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(res.length).toEqual(1); + expect(res[0].fileNumber).toEqual('1'); + }); + + it('should show an error toast message if search by file number fails', async () => { + httpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }), + ); + + await service.searchByFileNumber('1'); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + it('should call post for cancel', async () => { httpClient.post.mockReturnValue( of({ fileNumber: '1', - }) + }), + ); + + await service.cancel('file-number'); + + expect(httpClient.post).toHaveBeenCalledTimes(1); + }); + + it('should show an error toast message if cancel fails', async () => { + httpClient.post.mockReturnValue( + throwError(() => { + new Error(''); + }), ); await service.cancel('file-number'); expect(httpClient.post).toHaveBeenCalledTimes(1); + expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); }); it('should call post for uncancel', async () => { httpClient.post.mockReturnValue( of({ fileNumber: '1', - }) + }), + ); + + await service.uncancel('file-number'); + + expect(httpClient.post).toHaveBeenCalledTimes(1); + }); + + it('should show an error toast message if uncancel fails', async () => { + httpClient.post.mockReturnValue( + throwError(() => { + new Error(''); + }), ); await service.uncancel('file-number'); expect(httpClient.post).toHaveBeenCalledTimes(1); + expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); }); it('should call post for resend', async () => { httpClient.post.mockReturnValue( of({ fileNumber: '1', - }) + }), + ); + + await service.resendResponse('file-number'); + + expect(httpClient.post).toHaveBeenCalledTimes(1); + }); + + it('should show an error toast message if resend fails', async () => { + httpClient.post.mockReturnValue( + throwError(() => { + new Error(''); + }), ); await service.resendResponse('file-number'); expect(httpClient.post).toHaveBeenCalledTimes(1); + expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); }); }); diff --git a/alcs-frontend/src/app/services/toast/toast.service.spec.ts b/alcs-frontend/src/app/services/toast/toast.service.spec.ts index 7216bbbb44..25bedc36c4 100644 --- a/alcs-frontend/src/app/services/toast/toast.service.spec.ts +++ b/alcs-frontend/src/app/services/toast/toast.service.spec.ts @@ -1,14 +1,23 @@ import { TestBed } from '@angular/core/testing'; -import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ToastService } from './toast.service'; describe('ToastService', () => { let service: ToastService; + let mockSnackbar: DeepMocked; beforeEach(() => { + mockSnackbar = createMock(); + TestBed.configureTestingModule({ - imports: [MatSnackBarModule], + providers: [ + { + provide: MatSnackBar, + useValue: mockSnackbar, + }, + ], }); service = TestBed.inject(ToastService); }); @@ -16,4 +25,31 @@ describe('ToastService', () => { it('should be created', () => { expect(service).toBeTruthy(); }); + + it('should call the toast service with success for success toast', () => { + mockSnackbar.open.mockReturnValue({} as any); + + service.showSuccessToast('Toast'); + + expect(mockSnackbar.open).toHaveBeenCalledTimes(1); + expect(mockSnackbar.open.mock.calls[0][2]?.panelClass).toEqual('success'); + }); + + it('should call the toast service with warning for warning toast', () => { + mockSnackbar.open.mockReturnValue({} as any); + + service.showWarningToast('Toast'); + + expect(mockSnackbar.open).toHaveBeenCalledTimes(1); + expect(mockSnackbar.open.mock.calls[0][2]?.panelClass).toEqual('warning'); + }); + + it('should call the toast service with error for error toast', () => { + mockSnackbar.open.mockReturnValue({} as any); + + service.showErrorToast('Toast'); + + expect(mockSnackbar.open).toHaveBeenCalledTimes(1); + expect(mockSnackbar.open.mock.calls[0][2]?.panelClass).toEqual('error'); + }); }); diff --git a/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent.controller.spec.ts b/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent.controller.spec.ts index 5b55026925..fc2387221b 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent.controller.spec.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent.controller.spec.ts @@ -1,8 +1,7 @@ -import { Status } from '@grpc/grpc-js/build/src/constants'; -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 { TrackingService } from '../../common/tracking/tracking.service'; @@ -10,7 +9,6 @@ import { NoticeOfIntentOwner } from '../../portal/notice-of-intent-submission/no import { NoticeOfIntentSubmission } from '../../portal/notice-of-intent-submission/notice-of-intent-submission.entity'; import { StatusEmailService } from '../../providers/email/status-email.service'; import { User } from '../../user/user.entity'; -import { Board } from '../board/board.entity'; import { BoardService } from '../board/board.service'; import { NOI_SUBMISSION_STATUS } from './notice-of-intent-submission-status/notice-of-intent-status.dto'; import { NoticeOfIntentSubmissionToSubmissionStatus } from './notice-of-intent-submission-status/notice-of-intent-status.entity'; @@ -84,26 +82,6 @@ describe('NoticeOfIntentController', () => { expect(controller).toBeDefined(); }); - it('should call board service then main service for create', async () => { - mockBoardService.getOneOrFail.mockResolvedValue({} as Board); - mockService.create.mockResolvedValue(new NoticeOfIntent()); - mockService.mapToDtos.mockResolvedValue([]); - - await controller.create({ - applicant: 'fake-applicant', - localGovernmentUuid: 'local-gov-uuid', - fileNumber: 'file-number', - regionCode: 'region-code', - boardCode: 'fake', - dateSubmittedToAlc: 0, - typeCode: '', - }); - - expect(mockBoardService.getOneOrFail).toHaveBeenCalledTimes(1); - expect(mockService.create).toHaveBeenCalledTimes(1); - expect(mockService.mapToDtos).toHaveBeenCalledTimes(1); - }); - it('should call through to service for get', async () => { mockTrackingService.trackView.mockResolvedValue(); mockService.getByFileNumber.mockResolvedValue(new NoticeOfIntent()); diff --git a/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent.controller.ts b/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent.controller.ts index 1b03fdff75..09efdcfc2b 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent.controller.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent.controller.ts @@ -11,10 +11,7 @@ import { ApiOAuth2 } from '@nestjs/swagger'; import { Mapper } from 'automapper-core'; import { InjectMapper } from 'automapper-nestjs'; import * as config from 'config'; -import { - generateCANCApplicationHtml, - generateCANCNoticeOfIntentHtml, -} from '../../../../../templates/emails/cancelled'; +import { generateCANCNoticeOfIntentHtml } from '../../../../../templates/emails/cancelled'; import { ROLES_ALLOWED_APPLICATIONS, ROLES_ALLOWED_BOARDS, @@ -23,7 +20,6 @@ import { RolesGuard } from '../../common/authorization/roles-guard.service'; import { UserRoles } from '../../common/authorization/roles.decorator'; import { TrackingService } from '../../common/tracking/tracking.service'; import { StatusEmailService } from '../../providers/email/status-email.service'; -import { formatIncomingDate } from '../../utils/incoming-date.formatter'; import { BoardService } from '../board/board.service'; import { PARENT_TYPE } from '../card/card-subtask/card-subtask.dto'; import { NOI_SUBMISSION_STATUS } from './notice-of-intent-submission-status/notice-of-intent-status.dto'; @@ -31,7 +27,6 @@ import { NoticeOfIntentSubmissionStatusService } from './notice-of-intent-submis import { NoticeOfIntentSubmissionService } from './notice-of-intent-submission/notice-of-intent-submission.service'; import { NoticeOfIntentSubtype } from './notice-of-intent-subtype.entity'; import { - CreateNoticeOfIntentDto, NoticeOfIntentSubtypeDto, UpdateNoticeOfIntentDto, } from './notice-of-intent.dto'; @@ -72,25 +67,6 @@ export class NoticeOfIntentController { ); } - @Post() - @UserRoles(...ROLES_ALLOWED_BOARDS) - async create(@Body() createDto: CreateNoticeOfIntentDto) { - const board = await this.boardService.getOneOrFail({ - code: createDto.boardCode, - }); - - const createdNoi = await this.noticeOfIntentService.create( - { - ...createDto, - dateSubmittedToAlc: formatIncomingDate(createDto.dateSubmittedToAlc), - }, - board, - ); - - const mapped = this.noticeOfIntentService.mapToDtos([createdNoi]); - return mapped[0]; - } - @Get('/card/:uuid') @UserRoles(...ROLES_ALLOWED_BOARDS) async getByCard(@Param('uuid') cardUuid: string) { diff --git a/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent.dto.ts b/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent.dto.ts index f00781e88d..74338f7fea 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent.dto.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent.dto.ts @@ -2,7 +2,6 @@ import { AutoMap } from 'automapper-classes'; import { IsArray, IsBoolean, - IsNotEmpty, IsNumber, IsOptional, IsString, @@ -13,47 +12,19 @@ import { NoticeOfIntentOwnerDto } from '../../portal/notice-of-intent-submission import { NoticeOfIntentSubmissionDetailedDto } from '../../portal/notice-of-intent-submission/notice-of-intent-submission.dto'; import { CardDto } from '../card/card.dto'; import { ApplicationRegionDto } from '../code/application-code/application-region/application-region.dto'; -import { NoticeOfIntentTypeDto } from './notice-of-intent-type/notice-of-intent-type.dto'; import { LocalGovernmentDto } from '../local-government/local-government.dto'; +import { NoticeOfIntentTypeDto } from './notice-of-intent-type/notice-of-intent-type.dto'; export class AlcsNoticeOfIntentSubmissionDto extends NoticeOfIntentSubmissionDetailedDto { primaryContact?: NoticeOfIntentOwnerDto; } + export class NoticeOfIntentSubtypeDto extends BaseCodeDto { @AutoMap() @IsBoolean() isActive: boolean; } -export class CreateNoticeOfIntentDto { - @IsNumber() - dateSubmittedToAlc: number; - - @IsString() - @IsNotEmpty() - fileNumber: string; - - @IsString() - @IsNotEmpty() - applicant: string; - - @IsString() - @IsNotEmpty() - localGovernmentUuid: string; - - @IsString() - @IsNotEmpty() - regionCode: string; - - @IsString() - @IsNotEmpty() - boardCode: string; - - @IsString() - @IsOptional() - typeCode: string; -} - export class NoticeOfIntentDto { @AutoMap() uuid: string; From 9e821c6218c3b38f837e556da42a4cc2ca82fa42 Mon Sep 17 00:00:00 2001 From: Liam Stoddard Date: Tue, 9 Apr 2024 15:58:37 -0700 Subject: [PATCH 113/153] Init inquiry parcels --- .../inquiry/inquiry_migration.py | 3 + .../inquiry/parcels/inquiry_parcel_insert.py | 145 ++++++++++++++++++ .../sql/parcels/inquiry_parcel_insert.sql | 16 ++ .../parcels/inquiry_parcel_insert_count.sql | 11 ++ .../alcs/src/alcs/inquiry/inquiry.entity.ts | 18 +++ ...12701842929-add_prop_ids_to_inq_parcels.ts | 33 ++++ 6 files changed, 226 insertions(+) create mode 100644 bin/migrate-oats-data/inquiry/parcels/inquiry_parcel_insert.py create mode 100644 bin/migrate-oats-data/inquiry/sql/parcels/inquiry_parcel_insert.sql create mode 100644 bin/migrate-oats-data/inquiry/sql/parcels/inquiry_parcel_insert_count.sql create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1712701842929-add_prop_ids_to_inq_parcels.ts diff --git a/bin/migrate-oats-data/inquiry/inquiry_migration.py b/bin/migrate-oats-data/inquiry/inquiry_migration.py index 458ba4a2d5..e6423f692f 100644 --- a/bin/migrate-oats-data/inquiry/inquiry_migration.py +++ b/bin/migrate-oats-data/inquiry/inquiry_migration.py @@ -4,14 +4,17 @@ process_inquiry_staff_journal, clean_inquiry_staff_journal, ) +from .parcels.inquiry_parcel_insert import init_inquiry_parcels, clean_inquiry_parcels def process_inquiry(batch_size): init_inquiries(batch_size) process_inquiry_inquirer_fields(batch_size) process_inquiry_staff_journal(batch_size) + init_inquiry_parcels(batch_size) def clean_inquiry(): + clean_inquiry_parcels() clean_inquiry_staff_journal() clean_inquiries() diff --git a/bin/migrate-oats-data/inquiry/parcels/inquiry_parcel_insert.py b/bin/migrate-oats-data/inquiry/parcels/inquiry_parcel_insert.py new file mode 100644 index 0000000000..bfb259e94c --- /dev/null +++ b/bin/migrate-oats-data/inquiry/parcels/inquiry_parcel_insert.py @@ -0,0 +1,145 @@ +from common import ( + setup_and_get_logger, + BATCH_UPLOAD_SIZE, + OATS_ETL_USER, + add_timezone_and_keep_date_part, + OatsToAlcsOwnershipType, + to_alcs_format, + get_now_with_offset, +) +from db import inject_conn_pool +from psycopg2.extras import RealDictCursor + +etl_name = "init_inquiry_parcels" +logger = setup_and_get_logger(etl_name) + + +@inject_conn_pool +def init_inquiry_parcels(conn=None, batch_size=BATCH_UPLOAD_SIZE): + logger.info(f"Start {etl_name}") + + with conn.cursor(cursor_factory=RealDictCursor) as cursor: + with open( + "inquiry/sql/parcels/inquiry_parcel_insert_count.sql", + "r", + encoding="utf-8", + ) as sql_file: + count_query = sql_file.read() + cursor.execute(count_query) + count_total = dict(cursor.fetchone())["count"] + logger.info(f"Total Inquiry Parcels to insert: {count_total}") + + failed_inserts = 0 + successful_inserts_count = 0 + last_subject_property_id = 0 + + with open( + "inquiry/sql/parcels/inquiry_parcel_insert.sql", + "r", + encoding="utf-8", + ) as sql_file: + inquiry_sql = sql_file.read() + while True: + cursor.execute( + f"{inquiry_sql} WHERE osp.subject_property_id > {last_subject_property_id} ORDER BY osp.subject_property_id;" + ) + + rows = cursor.fetchmany(batch_size) + + if not rows: + break + try: + records_to_be_inserted_count = len(rows) + + _insert_records(conn, cursor, rows, successful_inserts_count) + + successful_inserts_count = ( + successful_inserts_count + records_to_be_inserted_count + ) + + last_subject_property_id = dict(rows[-1])["subject_property_id"] + + logger.debug( + f"inserted items count: {records_to_be_inserted_count}; total successfully inserted inquiry parcels so far {successful_inserts_count}; last inserted subject_property_id: {last_subject_property_id}" + ) + except Exception as err: + logger.exception(err) + conn.rollback() + failed_inserts = count_total - successful_inserts_count + last_subject_property_id = last_subject_property_id + 1 + + logger.info( + f"Finished {etl_name}: total amount of successful inserts {successful_inserts_count}, total failed inserts {failed_inserts}" + ) + + +def _insert_records(conn, cursor, rows, insert_index): + number_of_rows_to_insert = len(rows) + + if number_of_rows_to_insert > 0: + insert_query = _compile_inquiry_insert_query(number_of_rows_to_insert) + rows_to_insert = _prepare_data_to_insert(rows, insert_index) + cursor.execute(insert_query, rows_to_insert) + conn.commit() + + +def _prepare_data_to_insert(rows, insert_index): + row_without_last_element = [] + for row in rows: + mapped_row = _map_data(row, insert_index) + row_without_last_element.append(tuple(mapped_row.values())) + insert_index += 1 + + return row_without_last_element + + +def _map_data(row, insert_index): + return { + "inquiry_uuid": row["inquiry_uuid"], + "audit_created_by": OATS_ETL_USER, + "civic_address": _map_address(row), + "pid": str(row["pid"]).zfill(9) if row["pid"] is not None else None, + "pin": row["pin"], + "oats_subject_property_id": row["subject_property_id"], + "oats_property_id": row["property_id"], + "audit_created_at": to_alcs_format(get_now_with_offset(insert_index)), + } + + +def _map_address(data): + addr = data.get("civic_address", "") + if addr: + return addr + else: + return "No data found in OATS" + + +def _compile_inquiry_insert_query(number_of_rows_to_insert): + parcels_to_insert = ",".join(["%s"] * number_of_rows_to_insert) + return f""" + INSERT INTO alcs.inquiry_parcel + ( + inquiry_uuid, + audit_created_by, + civic_address, + pid, + pin, + oats_subject_property_id, + oats_property_id, + audit_created_at + ) + VALUES{parcels_to_insert} + ON CONFLICT DO NOTHING + """ + + +@inject_conn_pool +def clean_inquiry_parcels(conn=None): + logger.info("Start inquiry parcel cleaning") + with conn.cursor() as cursor: + cursor.execute( + "DELETE FROM alcs.inquiry_parcel ip WHERE ip.audit_created_by = 'oats_etl'" + ) + logger.info(f"Deleted items count = {cursor.rowcount}") + conn.commit() + logger.info("Done inquiry parcel cleaning") diff --git a/bin/migrate-oats-data/inquiry/sql/parcels/inquiry_parcel_insert.sql b/bin/migrate-oats-data/inquiry/sql/parcels/inquiry_parcel_insert.sql new file mode 100644 index 0000000000..aca17c7dc8 --- /dev/null +++ b/bin/migrate-oats-data/inquiry/sql/parcels/inquiry_parcel_insert.sql @@ -0,0 +1,16 @@ +WITH parcels_to_insert AS ( + SELECT i.uuid, + osp.subject_property_id + FROM alcs.inquiry i + JOIN oats.oats_subject_properties osp ON osp.issue_id = i.file_number::bigint + WHERE osp.alr_application_land_ind = 'Y' -- ensure that only parcels related to application/issue are selected +) +SELECT uuid AS inquiry_uuid, + op.civic_address, + op.pid, + op.pin, + osp.subject_property_id, + op.property_id +FROM parcels_to_insert pti + JOIN oats.oats_subject_properties osp ON osp.subject_property_id = pti.subject_property_id + JOIN oats.oats_properties op ON op.property_id = osp.property_id \ No newline at end of file diff --git a/bin/migrate-oats-data/inquiry/sql/parcels/inquiry_parcel_insert_count.sql b/bin/migrate-oats-data/inquiry/sql/parcels/inquiry_parcel_insert_count.sql new file mode 100644 index 0000000000..ac64a7679c --- /dev/null +++ b/bin/migrate-oats-data/inquiry/sql/parcels/inquiry_parcel_insert_count.sql @@ -0,0 +1,11 @@ +WITH parcels_to_insert AS ( + SELECT i.uuid, + osp.subject_property_id + FROM alcs.inquiry i + JOIN oats.oats_subject_properties osp ON osp.issue_id = i.file_number::bigint + WHERE osp.alr_application_land_ind = 'Y' -- ensure that only parcels related to application/issue are selected +) +SELECT COUNT(*) +FROM parcels_to_insert pti + JOIN oats.oats_subject_properties osp ON osp.subject_property_id = pti.subject_property_id + JOIN oats.oats_properties op ON op.property_id = osp.property_id \ No newline at end of file diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.entity.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.entity.ts index d7a62c0fd2..bf593ecedc 100644 --- a/services/apps/alcs/src/alcs/inquiry/inquiry.entity.ts +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.entity.ts @@ -97,6 +97,24 @@ export class Inquiry extends Base { @Type(() => Card) card: Card; + @Column({ + type: 'text', + nullable: true, + select: false, + comment: + 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.oats_subject_property_id to alcs.inquiry_parcel.', + }) + oatsSubjectPropertyId?: string | null; + + @Column({ + type: 'text', + nullable: true, + select: false, + comment: + 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.oats_property_id to alcs.inquiry_parcel.', + }) + oatsPropertyId?: string | null; + @AutoMap(() => InquiryParcel) @OneToMany(() => InquiryParcel, (incParcel) => incParcel.inquiry, { cascade: true, diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1712701842929-add_prop_ids_to_inq_parcels.ts b/services/apps/alcs/src/providers/typeorm/migrations/1712701842929-add_prop_ids_to_inq_parcels.ts new file mode 100644 index 0000000000..28898b03ed --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1712701842929-add_prop_ids_to_inq_parcels.ts @@ -0,0 +1,33 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddPropIdsToInqParcels1712701842929 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry_parcel" ADD "oats_subject_property_id" text`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry_parcel" ADD "oats_property_id" text`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "alcs"."inquiry_parcel"."oats_subject_property_id" IS 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.oats_subject_properties to alcs.inquiry_parcel.'`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "alcs"."inquiry_parcel"."oats_property_id" IS 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.oats_properties to alcs.inquiry_parcel.'`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry_document" DROP COLUMN "oats_subject_property_id"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry_document" DROP COLUMN "oats_property_id"`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "alcs"."inquiry_parcel"."oats_subject_property_id" IS 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.oats_subject_properties to alcs.inquiry_parcel.'`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "alcs"."inquiry_parcel"."oats_property_id" IS 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.oats_properties to alcs.inquiry_parcel.'`, + ); + } +} From 39deca4bca6239382bb865b0afdbef7d26fcd685 Mon Sep 17 00:00:00 2001 From: mhuseinov <61513701+mhuseinov@users.noreply.github.com> Date: Tue, 9 Apr 2024 16:09:08 -0700 Subject: [PATCH 114/153] trim redundant space, update DistrictOfBarriere applications (#1593) remove redundant spaces in local government names update District Of Barriere applications and submissions --- .../1712686499450-District_of_Barriere.ts | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1712686499450-District_of_Barriere.ts diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1712686499450-District_of_Barriere.ts b/services/apps/alcs/src/providers/typeorm/migrations/1712686499450-District_of_Barriere.ts new file mode 100644 index 0000000000..9aa77c36e9 --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1712686499450-District_of_Barriere.ts @@ -0,0 +1,61 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class DistrictOfBarriere1712686499450 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + // trim leading and trailing spaces in local government name + await queryRunner.query( + ` + UPDATE alcs.local_government + SET "name" = TRIM("name"); + `, + ); + + await queryRunner.query( + ` + UPDATE + alcs.application_submission + SET + local_government_uuid = ( + SELECT + uuid + FROM + alcs.local_government lg + WHERE + lg."name" = 'District of Barriere' + LIMIT 1) + WHERE + alcs.application_submission.file_number IN ( + '52375', + '53090', + '53091', + '55749'); + `, + ); + + await queryRunner.query( + ` + UPDATE + alcs.application + SET + local_government_uuid = ( + SELECT + uuid + FROM + alcs.local_government lg + WHERE + lg."name" = 'District of Barriere' + LIMIT 1) + WHERE + alcs.application.file_number IN ( + '52375', + '53090', + '53091', + '55749'); + `, + ); + } + + public async down(): Promise { + // no + } +} From e77968823bc07d44502adef403d01e9b97945649 Mon Sep 17 00:00:00 2001 From: Liam Stoddard Date: Tue, 9 Apr 2024 16:12:33 -0700 Subject: [PATCH 115/153] MR feedback --- bin/migrate-oats-data/inquiry/parcels/inquiry_parcel_insert.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/migrate-oats-data/inquiry/parcels/inquiry_parcel_insert.py b/bin/migrate-oats-data/inquiry/parcels/inquiry_parcel_insert.py index bfb259e94c..69d2eff02b 100644 --- a/bin/migrate-oats-data/inquiry/parcels/inquiry_parcel_insert.py +++ b/bin/migrate-oats-data/inquiry/parcels/inquiry_parcel_insert.py @@ -138,7 +138,7 @@ def clean_inquiry_parcels(conn=None): logger.info("Start inquiry parcel cleaning") with conn.cursor() as cursor: cursor.execute( - "DELETE FROM alcs.inquiry_parcel ip WHERE ip.audit_created_by = 'oats_etl'" + f"DELETE FROM alcs.inquiry_parcel ip WHERE ip.audit_created_by = '{OATS_ETL_USER}'" ) logger.info(f"Deleted items count = {cursor.rowcount}") conn.commit() From 98ef85a4e5348afe9654e775e558e8831b910345 Mon Sep 17 00:00:00 2001 From: "to. sandra" <76515860+sandratoh@users.noreply.github.com> Date: Wed, 10 Apr 2024 09:53:17 -0700 Subject: [PATCH 116/153] Add maintenance banner to ALCS and Portal (#1591) * Change maintenance mode to use mat slide toggle * Fix toggle styling * Add app wide maintenance banner config * Add maintenance banner to portal * Save and retrieve maintenance banner message * Add basic confirmation dialog before displaying banner * Clean up * Add portal config endpoint and services * Create maintenance banner component to show in header * Retrieve maintenance banner from config for both alcs and portal * Create custom dialog to display maintenance banner message * Reload header maintenance banner data without screen refresh * Add unit tests * Code review feedback * Return undefined in no config is found * Add migration and throw error in frontend to handle unfound config * Flip order of migration down logic --- alcs-frontend/src/app/app.module.ts | 2 + .../src/app/features/admin/admin.component.ts | 4 +- .../src/app/features/admin/admin.module.ts | 4 +- .../configuration.component.html | 52 ++++++------ .../configuration.component.scss | 68 ++++++++++++++-- .../configuration.component.spec.ts | 9 ++- .../configuration/configuration.component.ts | 79 ++++++++++++++++--- ...-banner-confirmation-dialog.component.html | 9 +++ ...-banner-confirmation-dialog.component.scss | 0 ...nner-confirmation-dialog.component.spec.ts | 37 +++++++++ ...ce-banner-confirmation-dialog.component.ts | 16 ++++ .../admin-configuration.service.ts | 2 + .../maintenance/maintenance.service.spec.ts | 47 +++++++++++ .../maintenance/maintenance.service.ts | 35 ++++++++ .../app/shared/header/header.component.html | 1 + .../src/app/shared/header/header.component.ts | 27 +++++-- .../maintenance-banner.component.html | 8 ++ .../maintenance-banner.component.scss | 19 +++++ .../maintenance-banner.component.spec.ts | 21 +++++ .../maintenance-banner.component.ts | 10 +++ alcs-frontend/src/styles/colors.scss | 1 + portal-frontend/src/app/app.component.ts | 2 +- portal-frontend/src/app/app.module.ts | 2 + .../maintenance/maintenance.service.spec.ts | 11 ++- .../maintenance/maintenance.service.ts | 15 +++- .../app/shared/header/header.component.html | 1 + .../shared/header/header.component.spec.ts | 9 ++- .../src/app/shared/header/header.component.ts | 17 +++- .../maintenance-banner.component.html | 8 ++ .../maintenance-banner.component.scss | 24 ++++++ .../maintenance-banner.component.spec.ts | 21 +++++ .../maintenance-banner.component.ts | 10 +++ .../maintenance-interceptor.service.ts | 3 +- .../warning-banner.component.scss | 5 ++ portal-frontend/src/styles/colors.scss | 1 + services/apps/alcs/src/alcs/alcs.module.ts | 3 + .../maintenance.controller.spec.ts | 41 ++++++++++ .../maintenance/maintenance.controller.ts | 21 +++++ .../alcs/maintenance/maintenance.module.ts | 12 +++ .../common/entities/configuration.entity.ts | 2 + .../common/maintenance/maintenance.module.ts | 11 +++ .../maintenance/maintenance.service.spec.ts | 70 ++++++++++++++++ .../common/maintenance/maintenance.service.ts | 38 +++++++++ .../maintenance.controller.spec.ts | 41 ++++++++++ .../maintenance/maintenance.controller.ts | 14 ++++ .../portal/maintenance/maintenance.module.ts | 12 +++ .../apps/alcs/src/portal/portal.module.ts | 3 + ...707802345-add-maintenance-banner-config.ts | 27 +++++++ 48 files changed, 813 insertions(+), 62 deletions(-) create mode 100644 alcs-frontend/src/app/features/admin/configuration/maintenance-banner-confirmation-dialog/maintenance-banner-confirmation-dialog.component.html create mode 100644 alcs-frontend/src/app/features/admin/configuration/maintenance-banner-confirmation-dialog/maintenance-banner-confirmation-dialog.component.scss create mode 100644 alcs-frontend/src/app/features/admin/configuration/maintenance-banner-confirmation-dialog/maintenance-banner-confirmation-dialog.component.spec.ts create mode 100644 alcs-frontend/src/app/features/admin/configuration/maintenance-banner-confirmation-dialog/maintenance-banner-confirmation-dialog.component.ts create mode 100644 alcs-frontend/src/app/services/maintenance/maintenance.service.spec.ts create mode 100644 alcs-frontend/src/app/services/maintenance/maintenance.service.ts create mode 100644 alcs-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.html create mode 100644 alcs-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.scss create mode 100644 alcs-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.spec.ts create mode 100644 alcs-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.ts create mode 100644 portal-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.html create mode 100644 portal-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.scss create mode 100644 portal-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.spec.ts create mode 100644 portal-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.ts create mode 100644 services/apps/alcs/src/alcs/maintenance/maintenance.controller.spec.ts create mode 100644 services/apps/alcs/src/alcs/maintenance/maintenance.controller.ts create mode 100644 services/apps/alcs/src/alcs/maintenance/maintenance.module.ts create mode 100644 services/apps/alcs/src/common/maintenance/maintenance.module.ts create mode 100644 services/apps/alcs/src/common/maintenance/maintenance.service.spec.ts create mode 100644 services/apps/alcs/src/common/maintenance/maintenance.service.ts create mode 100644 services/apps/alcs/src/portal/maintenance/maintenance.controller.spec.ts create mode 100644 services/apps/alcs/src/portal/maintenance/maintenance.controller.ts create mode 100644 services/apps/alcs/src/portal/maintenance/maintenance.module.ts create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1712707802345-add-maintenance-banner-config.ts diff --git a/alcs-frontend/src/app/app.module.ts b/alcs-frontend/src/app/app.module.ts index de3d0d8ec8..e759aad0ea 100644 --- a/alcs-frontend/src/app/app.module.ts +++ b/alcs-frontend/src/app/app.module.ts @@ -18,6 +18,7 @@ import { ConfirmationDialogComponent } from './shared/confirmation-dialog/confir import { HeaderComponent } from './shared/header/header.component'; import { NotificationsComponent } from './shared/header/notifications/notifications.component'; import { SearchBarComponent } from './shared/header/search-bar/search-bar.component'; +import { MaintenanceBannerComponent } from './shared/header/maintenance-banner/maintenance-banner.component'; import { SharedModule } from './shared/shared.module'; @NgModule({ @@ -31,6 +32,7 @@ import { SharedModule } from './shared/shared.module'; ConfirmationDialogComponent, NotificationsComponent, SearchBarComponent, + MaintenanceBannerComponent, ], imports: [BrowserModule, BrowserAnimationsModule, SharedModule.forRoot(), AppRoutingModule, MomentDateModule], providers: [ diff --git a/alcs-frontend/src/app/features/admin/admin.component.ts b/alcs-frontend/src/app/features/admin/admin.component.ts index 4870e0872d..b5d4035ce3 100644 --- a/alcs-frontend/src/app/features/admin/admin.component.ts +++ b/alcs-frontend/src/app/features/admin/admin.component.ts @@ -68,8 +68,8 @@ export const childRoutes = [ }, { path: 'configuration', - menuTitle: 'Portal Maintenance', - icon: 'settings_configuration', + menuTitle: 'Maintenance', + icon: 'settings_applications', component: ConfigurationComponent, }, ]; diff --git a/alcs-frontend/src/app/features/admin/admin.module.ts b/alcs-frontend/src/app/features/admin/admin.module.ts index 0a11a00ca6..992239e917 100644 --- a/alcs-frontend/src/app/features/admin/admin.module.ts +++ b/alcs-frontend/src/app/features/admin/admin.module.ts @@ -1,4 +1,4 @@ -import { CdkDropList, DragDropModule } from '@angular/cdk/drag-drop'; +import { DragDropModule } from '@angular/cdk/drag-drop'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { MatPaginatorModule } from '@angular/material/paginator'; @@ -23,6 +23,7 @@ import { LocalGovernmentComponent } from './local-government/local-government.co import { NoiSubtypeDialogComponent } from './noi-subtype/noi-subtype-dialog/noi-subtype-dialog.component'; import { NoiSubtypeComponent } from './noi-subtype/noi-subtype.component'; import { UnarchiveComponent } from './unarchive/unarchive.component'; +import { MaintenanceBannerConfirmationDialogComponent } from './configuration/maintenance-banner-confirmation-dialog/maintenance-banner-confirmation-dialog.component'; const routes: Routes = [ { @@ -52,6 +53,7 @@ const routes: Routes = [ BoardManagementComponent, BoardManagementDialogComponent, ConfigurationComponent, + MaintenanceBannerConfirmationDialogComponent, ], imports: [CommonModule, SharedModule.forRoot(), RouterModule.forChild(routes), MatPaginatorModule, DragDropModule], }) diff --git a/alcs-frontend/src/app/features/admin/configuration/configuration.component.html b/alcs-frontend/src/app/features/admin/configuration/configuration.component.html index 688d10b2f6..31faaeb83a 100644 --- a/alcs-frontend/src/app/features/admin/configuration/configuration.component.html +++ b/alcs-frontend/src/app/features/admin/configuration/configuration.component.html @@ -1,29 +1,33 @@
-

Portal Maintenance

-
-
Maintenance Mode
-
- - {{ maintenanceMode === 'true' ? 'On' : 'Off' }} - - -
Maintenance +
+
+
Banner (ALCS, Portal, Public Search)
+ +
+
+ Off + On +
+
+
+
Maintenance Mode (Portal, Public Search)
+
+ Off + On - - On - Off - -
- - -
-
diff --git a/alcs-frontend/src/app/features/admin/configuration/configuration.component.scss b/alcs-frontend/src/app/features/admin/configuration/configuration.component.scss index 74e9f8d3e1..b315aaa148 100644 --- a/alcs-frontend/src/app/features/admin/configuration/configuration.component.scss +++ b/alcs-frontend/src/app/features/admin/configuration/configuration.component.scss @@ -1,18 +1,70 @@ @use '../../../../styles/colors'; -.value { +.row { display: flex; - align-items: center; + margin: 24px 0; + align-items: flex-start; } -.subheading2 { - margin-top: 36px !important; +.label-input-container { + display: flex; + flex-direction: column; + gap: 8px; + width: 100%; +} + +.toggle-label { + font-weight: 700; + flex-grow: 1; +} + +.toggle { + display: flex; + gap: 4px; + color: colors.$black; + font-weight: 700; +} + +.inactive { + color: colors.$grey; + font-weight: 400; } -.editing { - max-width: 600px; +:host::ng-deep { + .toggle { + mat-label { + margin-right: 8px; + } + + & .mdc-form-field { + display: flex; + } + + & .mdc-label { + font-size: 16px; + padding-left: 12px; + } + + & .mat-mdc-slide-toggle .mdc-form-field { + color: colors.$grey; + } + + & .mat-mdc-slide-toggle-checked .mdc-form-field { + color: colors.$black; + + .mdc-label { + font-weight: 700 !important; + } + } + } + + app-inline-text { + .editing { + width: 100%; - &.hidden { - display: none; + mat-form-field { + width: 100%; + } + } } } diff --git a/alcs-frontend/src/app/features/admin/configuration/configuration.component.spec.ts b/alcs-frontend/src/app/features/admin/configuration/configuration.component.spec.ts index 09bcb6b4da..e82ebee434 100644 --- a/alcs-frontend/src/app/features/admin/configuration/configuration.component.spec.ts +++ b/alcs-frontend/src/app/features/admin/configuration/configuration.component.spec.ts @@ -2,18 +2,19 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { AdminConfigurationService } from '../../../services/admin-configuration/admin-configuration.service'; -import { UnarchiveCardService } from '../../../services/unarchive-card/unarchive-card.service'; -import { ConfirmationDialogService } from '../../../shared/confirmation-dialog/confirmation-dialog.service'; import { ConfigurationComponent } from './configuration.component'; +import { MaintenanceService } from '../../../services/maintenance/maintenance.service'; describe('ConfigurationComponent', () => { let component: ConfigurationComponent; let fixture: ComponentFixture; let mockConfigurationService: DeepMocked; + let mockMaintenanceService: DeepMocked; beforeEach(async () => { mockConfigurationService = createMock(); + mockMaintenanceService = createMock(); await TestBed.configureTestingModule({ declarations: [ConfigurationComponent], @@ -22,6 +23,10 @@ describe('ConfigurationComponent', () => { provide: AdminConfigurationService, useValue: mockConfigurationService, }, + { + provide: MaintenanceService, + useValue: mockMaintenanceService, + }, ], schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); diff --git a/alcs-frontend/src/app/features/admin/configuration/configuration.component.ts b/alcs-frontend/src/app/features/admin/configuration/configuration.component.ts index 7aefb733bb..222e970e44 100644 --- a/alcs-frontend/src/app/features/admin/configuration/configuration.component.ts +++ b/alcs-frontend/src/app/features/admin/configuration/configuration.component.ts @@ -3,6 +3,11 @@ import { AdminConfigurationService, CONFIG_VALUE, } from '../../../services/admin-configuration/admin-configuration.service'; +import { ConfirmationDialogService } from '../../../shared/confirmation-dialog/confirmation-dialog.service'; +import { MatSlideToggleChange } from '@angular/material/slide-toggle'; +import { MatDialog } from '@angular/material/dialog'; +import { MaintenanceBannerConfirmationDialogComponent } from './maintenance-banner-confirmation-dialog/maintenance-banner-confirmation-dialog.component'; +import { MaintenanceService } from '../../../services/maintenance/maintenance.service'; @Component({ selector: 'app-configuration', @@ -10,30 +15,82 @@ import { styleUrls: ['./configuration.component.scss'], }) export class ConfigurationComponent implements OnInit { - maintenanceMode = 'false'; - isEditing = false; + maintenanceBanner = false; + maintenanceBannerMessage = ''; + maintenanceMode = false; - constructor(private adminConfigurationService: AdminConfigurationService) {} + constructor( + private dialog: MatDialog, + private adminConfigurationService: AdminConfigurationService, + private maintenanceService: MaintenanceService, + protected confirmationDialogService: ConfirmationDialogService, + ) {} ngOnInit(): void { this.loadConfigs(); } - toggleEdit() { - this.isEditing = !this.isEditing; + private async updateConfig(configName: CONFIG_VALUE, configValue: string) { + await this.adminConfigurationService.setConfiguration(configName, configValue); + this.loadConfigs(); } - async onSave() { - await this.adminConfigurationService.setConfiguration(CONFIG_VALUE.PORTAL_MAINTENANCE_MODE, this.maintenanceMode); - this.loadConfigs(); - this.isEditing = false; + private getConfigValue( + configs: { name: CONFIG_VALUE; value: string }[], + configName: CONFIG_VALUE, + isBoolean?: boolean, + ): T { + const config = configs.find((config) => config.name === configName); + + if (!config) { + throw new Error(`Configuration ${configName} not found.`); + } + + return (isBoolean ? config.value === 'true' : config.value) as T; } private async loadConfigs() { const configs = await this.adminConfigurationService.listConfigurations(); if (configs) { - const maintenanceConfig = configs.find((config) => config.name === CONFIG_VALUE.PORTAL_MAINTENANCE_MODE); - this.maintenanceMode = maintenanceConfig!.value; + this.maintenanceMode = this.getConfigValue(configs, CONFIG_VALUE.PORTAL_MAINTENANCE_MODE, true); + this.maintenanceBanner = this.getConfigValue(configs, CONFIG_VALUE.APP_MAINTENANCE_BANNER, true); + this.maintenanceBannerMessage = this.getConfigValue(configs, CONFIG_VALUE.APP_MAINTENANCE_BANNER_MESSAGE); + } + } + + onToggleMaintenanceBanner(event: MatSlideToggleChange) { + if (event.checked) { + this.dialog + .open(MaintenanceBannerConfirmationDialogComponent, { + data: { + message: this.maintenanceBannerMessage, + }, + }) + .beforeClosed() + .subscribe((didConfirm) => { + if (didConfirm) { + this.updateConfig(CONFIG_VALUE.APP_MAINTENANCE_BANNER, this.maintenanceBanner.toString()); + this.maintenanceService.setBannerMessage(this.maintenanceBannerMessage); + this.maintenanceService.setShowBanner(true); + } else { + this.loadConfigs(); + } + }); + } else { + this.updateConfig(CONFIG_VALUE.APP_MAINTENANCE_BANNER, this.maintenanceBanner.toString()); + this.maintenanceService.setShowBanner(false); + } + } + + onToggleMaintenanceMode() { + this.updateConfig(CONFIG_VALUE.PORTAL_MAINTENANCE_MODE, this.maintenanceMode.toString()); + } + + updateMaintenanceBannerMessage(message: string | null) { + this.updateConfig(CONFIG_VALUE.APP_MAINTENANCE_BANNER_MESSAGE, message || ''); + + if (this.maintenanceBanner === true) { + this.maintenanceService.setBannerMessage(message || ''); } } } diff --git a/alcs-frontend/src/app/features/admin/configuration/maintenance-banner-confirmation-dialog/maintenance-banner-confirmation-dialog.component.html b/alcs-frontend/src/app/features/admin/configuration/maintenance-banner-confirmation-dialog/maintenance-banner-confirmation-dialog.component.html new file mode 100644 index 0000000000..fc9586eeae --- /dev/null +++ b/alcs-frontend/src/app/features/admin/configuration/maintenance-banner-confirmation-dialog/maintenance-banner-confirmation-dialog.component.html @@ -0,0 +1,9 @@ +
+

Please confirm the banner message before turning this on. Are you sure you want enable the banner?

+
+

Banner Message: {{ data.message }}

+
+
+ + +
diff --git a/alcs-frontend/src/app/features/admin/configuration/maintenance-banner-confirmation-dialog/maintenance-banner-confirmation-dialog.component.scss b/alcs-frontend/src/app/features/admin/configuration/maintenance-banner-confirmation-dialog/maintenance-banner-confirmation-dialog.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/alcs-frontend/src/app/features/admin/configuration/maintenance-banner-confirmation-dialog/maintenance-banner-confirmation-dialog.component.spec.ts b/alcs-frontend/src/app/features/admin/configuration/maintenance-banner-confirmation-dialog/maintenance-banner-confirmation-dialog.component.spec.ts new file mode 100644 index 0000000000..2441755aa4 --- /dev/null +++ b/alcs-frontend/src/app/features/admin/configuration/maintenance-banner-confirmation-dialog/maintenance-banner-confirmation-dialog.component.spec.ts @@ -0,0 +1,37 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; +import { MaintenanceBannerConfirmationDialogComponent } from './maintenance-banner-confirmation-dialog.component'; + +describe('MaintenanceBannerConfirmationDialogComponent', () => { + let component: MaintenanceBannerConfirmationDialogComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [MaintenanceBannerConfirmationDialogComponent], + providers: [ + { + provide: MatDialog, + useValue: {}, + }, + { + provide: MatDialogRef, + useValue: {}, + }, + { + provide: MAT_DIALOG_DATA, + useValue: {}, + }, + ], + schemas: [NO_ERRORS_SCHEMA], + }); + fixture = TestBed.createComponent(MaintenanceBannerConfirmationDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alcs-frontend/src/app/features/admin/configuration/maintenance-banner-confirmation-dialog/maintenance-banner-confirmation-dialog.component.ts b/alcs-frontend/src/app/features/admin/configuration/maintenance-banner-confirmation-dialog/maintenance-banner-confirmation-dialog.component.ts new file mode 100644 index 0000000000..9a17f0458b --- /dev/null +++ b/alcs-frontend/src/app/features/admin/configuration/maintenance-banner-confirmation-dialog/maintenance-banner-confirmation-dialog.component.ts @@ -0,0 +1,16 @@ +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; + +@Component({ + selector: 'app-maintenance-banner-confirmation-dialog', + templateUrl: './maintenance-banner-confirmation-dialog.component.html', + styleUrls: ['./maintenance-banner-confirmation-dialog.component.scss'], +}) +export class MaintenanceBannerConfirmationDialogComponent { + constructor( + @Inject(MAT_DIALOG_DATA) + protected data: { + message: string; + }, + ) {} +} diff --git a/alcs-frontend/src/app/services/admin-configuration/admin-configuration.service.ts b/alcs-frontend/src/app/services/admin-configuration/admin-configuration.service.ts index 7df32c13bd..a0fc5ac586 100644 --- a/alcs-frontend/src/app/services/admin-configuration/admin-configuration.service.ts +++ b/alcs-frontend/src/app/services/admin-configuration/admin-configuration.service.ts @@ -7,6 +7,8 @@ import { ToastService } from '../toast/toast.service'; export enum CONFIG_VALUE { PORTAL_MAINTENANCE_MODE = 'portal_maintenance_mode', + APP_MAINTENANCE_BANNER = 'app_maintenance_banner', + APP_MAINTENANCE_BANNER_MESSAGE = 'app_maintenance_banner_message', } @Injectable({ diff --git a/alcs-frontend/src/app/services/maintenance/maintenance.service.spec.ts b/alcs-frontend/src/app/services/maintenance/maintenance.service.spec.ts new file mode 100644 index 0000000000..3576d25a38 --- /dev/null +++ b/alcs-frontend/src/app/services/maintenance/maintenance.service.spec.ts @@ -0,0 +1,47 @@ +import { TestBed } from '@angular/core/testing'; +import { HttpClient } from '@angular/common/http'; +import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { of } from 'rxjs'; +import { MaintenanceService } from './maintenance.service'; + +describe('MaintenanceService', () => { + let service: MaintenanceService; + let mockHttpClient: DeepMocked; + + beforeEach(() => { + mockHttpClient = createMock(); + + TestBed.configureTestingModule({ + providers: [ + { + provide: HttpClient, + useValue: mockHttpClient, + }, + ], + }); + service = TestBed.inject(MaintenanceService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should make a get request for getBanner', async () => { + mockHttpClient.get.mockReturnValue(of({})); + + const res = await service.getBanner(); + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(res).toBeDefined(); + }); + + it('should set show banner', () => { + service.setShowBanner(true); + expect(service.$showBanner.value).toEqual(true); + }); + + it('should set banner message', () => { + const mockMessage = 'Test Message'; + service.setBannerMessage(mockMessage); + expect(service.$bannerMessage.value).toEqual(mockMessage); + }); +}); diff --git a/alcs-frontend/src/app/services/maintenance/maintenance.service.ts b/alcs-frontend/src/app/services/maintenance/maintenance.service.ts new file mode 100644 index 0000000000..b6efeb4ebc --- /dev/null +++ b/alcs-frontend/src/app/services/maintenance/maintenance.service.ts @@ -0,0 +1,35 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { BehaviorSubject, firstValueFrom } from 'rxjs'; +import { environment } from '../../../environments/environment'; + +@Injectable({ + providedIn: 'root', +}) +export class MaintenanceService { + private baseUrl = environment.apiUrl; + + $showBanner = new BehaviorSubject(false); + $bannerMessage = new BehaviorSubject(''); + + constructor(private http: HttpClient) {} + + async getBanner() { + try { + return await firstValueFrom( + this.http.get<{ showBanner: boolean; message: string }>(`${this.baseUrl}/maintenance/banner`), + ); + } catch (e) { + console.error(e); + return undefined; + } + } + + setShowBanner(showBanner: boolean) { + this.$showBanner.next(showBanner); + } + + setBannerMessage(message: string) { + this.$bannerMessage.next(message); + } +} diff --git a/alcs-frontend/src/app/shared/header/header.component.html b/alcs-frontend/src/app/shared/header/header.component.html index cacacb7c7a..444bcfdccf 100644 --- a/alcs-frontend/src/app/shared/header/header.component.html +++ b/alcs-frontend/src/app/shared/header/header.component.html @@ -1,3 +1,4 @@ +{{ maintenanceBannerMessage }}
diff --git a/alcs-frontend/src/app/shared/header/header.component.ts b/alcs-frontend/src/app/shared/header/header.component.ts index a8f0e184de..bb174d0973 100644 --- a/alcs-frontend/src/app/shared/header/header.component.ts +++ b/alcs-frontend/src/app/shared/header/header.component.ts @@ -7,9 +7,9 @@ import { AuthenticationService, ICurrentUser, ROLES } from '../../services/authe import { BoardService, BoardWithFavourite } from '../../services/board/board.service'; import { MessageDto } from '../../services/message/message.dto'; import { MessageService } from '../../services/message/message.service'; -import { ToastService } from '../../services/toast/toast.service'; import { UserDto } from '../../services/user/user.dto'; import { UserService } from '../../services/user/user.service'; +import { MaintenanceService } from '../../services/maintenance/maintenance.service'; @Component({ selector: 'app-header', @@ -26,15 +26,17 @@ export class HeaderComponent implements OnInit { notifications: MessageDto[] = []; isCommissioner = false; isAdmin = false; + showMaintenanceBanner = true; + maintenanceBannerMessage = ''; constructor( private authService: AuthenticationService, private applicationService: ApplicationService, private boardService: BoardService, - private toastService: ToastService, private userService: UserService, private router: Router, - private notificationService: MessageService + private notificationService: MessageService, + private maintenanceService: MaintenanceService, ) {} ngOnInit(): void { @@ -52,9 +54,10 @@ export class HeaderComponent implements OnInit { if (this.hasRoles) { this.applicationService.setup(); this.loadNotifications(); + this.setMaintenanceBanner(); const overlappingRoles = ROLES_ALLOWED_APPLICATIONS.filter((value) => - currentUser.client_roles!.includes(value) + currentUser.client_roles!.includes(value), ); this.allowedSearch = overlappingRoles.length > 0; } @@ -66,8 +69,22 @@ export class HeaderComponent implements OnInit { }); this.boardService.$boards.subscribe( - (dms) => (this.sortedBoards = dms.sort((x, y) => this.sortDecisionMakers(x, y))) + (dms) => (this.sortedBoards = dms.sort((x, y) => this.sortDecisionMakers(x, y))), ); + + this.maintenanceService.$showBanner.subscribe((showBanner) => { + this.showMaintenanceBanner = showBanner; + }); + + this.maintenanceService.$bannerMessage.subscribe((message) => { + this.maintenanceBannerMessage = message; + }); + } + + private async setMaintenanceBanner() { + const maintenanceBanner = await this.maintenanceService.getBanner(); + this.showMaintenanceBanner = maintenanceBanner?.showBanner || false; + this.maintenanceBannerMessage = maintenanceBanner?.message || ''; } private sortDecisionMakers(x: BoardWithFavourite, y: BoardWithFavourite) { diff --git a/alcs-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.html b/alcs-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.html new file mode 100644 index 0000000000..1c876cba56 --- /dev/null +++ b/alcs-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.html @@ -0,0 +1,8 @@ +
+
+ warning_amber +
+
+ +
+
diff --git a/alcs-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.scss b/alcs-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.scss new file mode 100644 index 0000000000..0e537eac97 --- /dev/null +++ b/alcs-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.scss @@ -0,0 +1,19 @@ +@use '../../../../styles/colors'; + +.maintenance-banner { + background-color: colors.$maintenance-banner; + padding: 8px 24px; + display: flex; + align-items: center; + gap: 8px; + + .maintenance-description { + min-width: 0; + word-break: break-word; + } + + .icon { + display: flex; + justify-content: center; + } +} diff --git a/alcs-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.spec.ts b/alcs-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.spec.ts new file mode 100644 index 0000000000..836e3a78ae --- /dev/null +++ b/alcs-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MaintenanceBannerComponent } from './maintenance-banner.component'; + +describe('MaintenanceBannerComponent', () => { + let component: MaintenanceBannerComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [MaintenanceBannerComponent] + }); + fixture = TestBed.createComponent(MaintenanceBannerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alcs-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.ts b/alcs-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.ts new file mode 100644 index 0000000000..de7db15fd0 --- /dev/null +++ b/alcs-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-maintenance-banner', + templateUrl: './maintenance-banner.component.html', + styleUrls: ['./maintenance-banner.component.scss'] +}) +export class MaintenanceBannerComponent { + +} diff --git a/alcs-frontend/src/styles/colors.scss b/alcs-frontend/src/styles/colors.scss index 31e2b8a612..c4bc30bdd2 100644 --- a/alcs-frontend/src/styles/colors.scss +++ b/alcs-frontend/src/styles/colors.scss @@ -22,6 +22,7 @@ $bg-color: #f5f7e7; $error-color: #c6242a; $success-color: #2e8540; $warning-color: #fcba19; +$maintenance-banner: #fddc8b; $dark-contrast-text: #000000; $light-contrast-text: $white; diff --git a/portal-frontend/src/app/app.component.ts b/portal-frontend/src/app/app.component.ts index 005cfa5c79..a0fdf755fe 100644 --- a/portal-frontend/src/app/app.component.ts +++ b/portal-frontend/src/app/app.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; +import { NavigationEnd, Router } from '@angular/router'; @Component({ selector: 'app-root', diff --git a/portal-frontend/src/app/app.module.ts b/portal-frontend/src/app/app.module.ts index 1dcfcee683..ea4ca3fbdd 100644 --- a/portal-frontend/src/app/app.module.ts +++ b/portal-frontend/src/app/app.module.ts @@ -20,6 +20,7 @@ import { MaintenanceInterceptorService } from './shared/maintenance/maintenance- import { SharedModule } from './shared/shared.module'; import { WarningBannerComponent } from './shared/warning-banner/warning-banner.component'; import { MaintenanceComponent } from './features/maintenance/maintenance.component'; +import { MaintenanceBannerComponent } from './shared/header/maintenance-banner/maintenance-banner.component'; @NgModule({ declarations: [ @@ -30,6 +31,7 @@ import { MaintenanceComponent } from './features/maintenance/maintenance.compone AuthorizationComponent, ConfirmationDialogComponent, MaintenanceComponent, + MaintenanceBannerComponent, ], imports: [ BrowserModule, diff --git a/portal-frontend/src/app/services/maintenance/maintenance.service.spec.ts b/portal-frontend/src/app/services/maintenance/maintenance.service.spec.ts index 2ae3a07b8d..c3b78ecb11 100644 --- a/portal-frontend/src/app/services/maintenance/maintenance.service.spec.ts +++ b/portal-frontend/src/app/services/maintenance/maintenance.service.spec.ts @@ -1,8 +1,7 @@ import { HttpClient } from '@angular/common/http'; import { TestBed } from '@angular/core/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { of, throwError } from 'rxjs'; -import { ToastService } from '../toast/toast.service'; +import { of } from 'rxjs'; import { MaintenanceService } from './maintenance.service'; describe('MaintenanceService', () => { @@ -35,4 +34,12 @@ describe('MaintenanceService', () => { expect(mockHttpClient.get).toHaveBeenCalledTimes(1); expect(res).toBeDefined(); }); + + it('should make a get request for getBanner', async () => { + mockHttpClient.get.mockReturnValue(of({})); + + const res = await service.getBanner(); + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(res).toBeDefined(); + }); }); diff --git a/portal-frontend/src/app/services/maintenance/maintenance.service.ts b/portal-frontend/src/app/services/maintenance/maintenance.service.ts index 643c76804b..497acb827a 100644 --- a/portal-frontend/src/app/services/maintenance/maintenance.service.ts +++ b/portal-frontend/src/app/services/maintenance/maintenance.service.ts @@ -7,15 +7,26 @@ import { environment } from '../../../environments/environment'; providedIn: 'root', }) export class MaintenanceService { - private baseUrl = `${environment.apiUrl}/inbox`; + private baseUrl = environment.apiUrl; constructor(private http: HttpClient) {} async check() { try { - return await firstValueFrom(this.http.get(`${this.baseUrl}`)); + return await firstValueFrom(this.http.get(`${this.baseUrl}/inbox`)); } catch (e) { return undefined; } } + + async getBanner() { + try { + return await firstValueFrom( + this.http.get<{ showBanner: boolean; message: string }>(`${this.baseUrl}/maintenance/banner`) + ); + } catch (e) { + console.error(e); + return undefined; + } + } } diff --git a/portal-frontend/src/app/shared/header/header.component.html b/portal-frontend/src/app/shared/header/header.component.html index ed493c1bc9..8b783c8bb6 100644 --- a/portal-frontend/src/app/shared/header/header.component.html +++ b/portal-frontend/src/app/shared/header/header.component.html @@ -12,6 +12,7 @@
+ {{ maintenanceBannerMessage }}
diff --git a/portal-frontend/src/app/shared/header/header.component.spec.ts b/portal-frontend/src/app/shared/header/header.component.spec.ts index e3f99ff09f..95fd84c488 100644 --- a/portal-frontend/src/app/shared/header/header.component.spec.ts +++ b/portal-frontend/src/app/shared/header/header.component.spec.ts @@ -3,7 +3,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { BehaviorSubject } from 'rxjs'; import { UserDto } from '../../services/authentication/authentication.dto'; -import { AuthenticationService, ICurrentUser } from '../../services/authentication/authentication.service'; +import { AuthenticationService } from '../../services/authentication/authentication.service'; +import { MaintenanceService } from '../../services/maintenance/maintenance.service'; import { HeaderComponent } from './header.component'; @@ -11,10 +12,12 @@ describe('HeaderComponent', () => { let component: HeaderComponent; let fixture: ComponentFixture; let mockAuthService: DeepMocked; + let mockMaintenanceService: DeepMocked; beforeEach(async () => { mockAuthService = createMock(); mockAuthService.$currentProfile = new BehaviorSubject(undefined); + mockMaintenanceService = createMock(); await TestBed.configureTestingModule({ declarations: [HeaderComponent], @@ -23,6 +26,10 @@ describe('HeaderComponent', () => { provide: AuthenticationService, useValue: mockAuthService, }, + { + provide: MaintenanceService, + useValue: mockMaintenanceService, + }, ], schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); diff --git a/portal-frontend/src/app/shared/header/header.component.ts b/portal-frontend/src/app/shared/header/header.component.ts index fb63ebacd8..7240ccb0a6 100644 --- a/portal-frontend/src/app/shared/header/header.component.ts +++ b/portal-frontend/src/app/shared/header/header.component.ts @@ -2,7 +2,8 @@ import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { Subject, takeUntil } from 'rxjs'; import { UserDto } from '../../services/authentication/authentication.dto'; -import { AuthenticationService, ICurrentUser } from '../../services/authentication/authentication.service'; +import { AuthenticationService } from '../../services/authentication/authentication.service'; +import { MaintenanceService } from '../../services/maintenance/maintenance.service'; @Component({ selector: 'app-header', @@ -19,10 +20,14 @@ export class HeaderComponent implements OnInit, OnDestroy { title = 'Provincial Agricultural Land Commission Portal'; user: UserDto | undefined; + showMaintenanceBanner = false; + maintenanceBannerMessage = ''; + constructor( private authenticationService: AuthenticationService, private router: Router, - private changeDetectorRef: ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef, + private maintenanceService: MaintenanceService ) {} ngOnInit(): void { @@ -32,12 +37,20 @@ export class HeaderComponent implements OnInit, OnDestroy { this.changeDetectorRef.detectChanges(); }); + this.setMaintenanceBanner(); + this.router.events.pipe(takeUntil(this.$destroy)).subscribe(() => { const url = window.location.href; this.isOnPublicPage = url.includes('public'); }); } + private async setMaintenanceBanner() { + const maintenanceBanner = await this.maintenanceService.getBanner(); + this.showMaintenanceBanner = maintenanceBanner?.showBanner || false; + this.maintenanceBannerMessage = maintenanceBanner?.message || ''; + } + async onLogout() { await this.authenticationService.logout(); } diff --git a/portal-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.html b/portal-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.html new file mode 100644 index 0000000000..1c876cba56 --- /dev/null +++ b/portal-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.html @@ -0,0 +1,8 @@ +
+
+ warning_amber +
+
+ +
+
diff --git a/portal-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.scss b/portal-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.scss new file mode 100644 index 0000000000..994965933e --- /dev/null +++ b/portal-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.scss @@ -0,0 +1,24 @@ +@use '../../../../styles/functions' as *; +@use '../../../../styles/colors' as colors; + +.maintenance-banner { + background-color: colors.$maintenance-banner; + padding: rem(8) rem(24); + display: flex; + align-items: center; + gap: rem(8); + + .maintenance-description { + min-width: 0; + word-break: break-word; + } + + .icon { + display: flex; + justify-content: center; + + @media screen and (min-width: $desktopBreakpoint) { + align-items: center; + } + } +} diff --git a/portal-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.spec.ts b/portal-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.spec.ts new file mode 100644 index 0000000000..836e3a78ae --- /dev/null +++ b/portal-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MaintenanceBannerComponent } from './maintenance-banner.component'; + +describe('MaintenanceBannerComponent', () => { + let component: MaintenanceBannerComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [MaintenanceBannerComponent] + }); + fixture = TestBed.createComponent(MaintenanceBannerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.ts b/portal-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.ts new file mode 100644 index 0000000000..de7db15fd0 --- /dev/null +++ b/portal-frontend/src/app/shared/header/maintenance-banner/maintenance-banner.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-maintenance-banner', + templateUrl: './maintenance-banner.component.html', + styleUrls: ['./maintenance-banner.component.scss'] +}) +export class MaintenanceBannerComponent { + +} diff --git a/portal-frontend/src/app/shared/maintenance/maintenance-interceptor.service.ts b/portal-frontend/src/app/shared/maintenance/maintenance-interceptor.service.ts index 160423d115..54eaafc8ad 100644 --- a/portal-frontend/src/app/shared/maintenance/maintenance-interceptor.service.ts +++ b/portal-frontend/src/app/shared/maintenance/maintenance-interceptor.service.ts @@ -2,12 +2,13 @@ import { HttpErrorResponse, HttpHandler, HttpInterceptor, HttpRequest } from '@a import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; import { catchError, EMPTY, throwError } from 'rxjs'; +import { MaintenanceService } from '../../services/maintenance/maintenance.service'; @Injectable({ providedIn: 'root', }) export class MaintenanceInterceptorService implements HttpInterceptor { - constructor(private router: Router) {} + constructor(private router: Router, private maintenanceService: MaintenanceService) {} intercept(request: HttpRequest, next: HttpHandler) { return next.handle(request).pipe( diff --git a/portal-frontend/src/app/shared/warning-banner/warning-banner.component.scss b/portal-frontend/src/app/shared/warning-banner/warning-banner.component.scss index f49c017f38..994ec089b6 100644 --- a/portal-frontend/src/app/shared/warning-banner/warning-banner.component.scss +++ b/portal-frontend/src/app/shared/warning-banner/warning-banner.component.scss @@ -25,3 +25,8 @@ } } } + +.maintenance { + margin-top: unset; + background-color: colors.$maintenance-banner; +} diff --git a/portal-frontend/src/styles/colors.scss b/portal-frontend/src/styles/colors.scss index 993b330b00..831beeaad5 100644 --- a/portal-frontend/src/styles/colors.scss +++ b/portal-frontend/src/styles/colors.scss @@ -23,3 +23,4 @@ $link-color: #1a5a96; $error-color: #c6242a; $success-color: #2e8540; $warning-color: #fcba19; +$maintenance-banner: #fddc8b; diff --git a/services/apps/alcs/src/alcs/alcs.module.ts b/services/apps/alcs/src/alcs/alcs.module.ts index d5bd663cda..220e2c09b8 100644 --- a/services/apps/alcs/src/alcs/alcs.module.ts +++ b/services/apps/alcs/src/alcs/alcs.module.ts @@ -28,6 +28,7 @@ import { PlanningReviewTimelineModule } from './planning-review/planning-review- import { PlanningReviewModule } from './planning-review/planning-review.module'; import { SearchModule } from './search/search.module'; import { StaffJournalModule } from './staff-journal/staff-journal.module'; +import { MaintenanceModule } from './maintenance/maintenance.module'; @Module({ imports: [ @@ -56,6 +57,7 @@ import { StaffJournalModule } from './staff-journal/staff-journal.module'; InquiryModule, MeetingModule, PlanningReviewTimelineModule, + MaintenanceModule, RouterModule.register([ { path: 'alcs', module: ApplicationModule }, { path: 'alcs', module: CommentModule }, @@ -84,6 +86,7 @@ import { StaffJournalModule } from './staff-journal/staff-journal.module'; { path: 'alcs', module: InquiryModule }, { path: 'alcs', module: MeetingModule }, { path: 'alcs', module: PlanningReviewTimelineModule }, + { path: 'alcs', module: MaintenanceModule }, ]), ], controllers: [], diff --git a/services/apps/alcs/src/alcs/maintenance/maintenance.controller.spec.ts b/services/apps/alcs/src/alcs/maintenance/maintenance.controller.spec.ts new file mode 100644 index 0000000000..f40c80f5a1 --- /dev/null +++ b/services/apps/alcs/src/alcs/maintenance/maintenance.controller.spec.ts @@ -0,0 +1,41 @@ +import { DeepMocked, createMock } from '@golevelup/nestjs-testing'; +import { Test, TestingModule } from '@nestjs/testing'; +import { ClsService } from 'nestjs-cls'; +import { MaintenanceController } from './maintenance.controller'; +import { MaintenanceService } from '../../common/maintenance/maintenance.service'; +import { mockKeyCloakProviders } from '../../../test/mocks/mockTypes'; + +describe('MaintenanceController', () => { + let controller: MaintenanceController; + let mockMaintenanceService: DeepMocked; + + beforeEach(async () => { + mockMaintenanceService = createMock(); + + const module: TestingModule = await Test.createTestingModule({ + controllers: [MaintenanceController], + providers: [ + { provide: MaintenanceService, useValue: mockMaintenanceService }, + { + provide: ClsService, + useValue: {}, + }, + ...mockKeyCloakProviders, + ], + }).compile(); + + controller = module.get(MaintenanceController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); + + it('should call through to service for getting maintenance banner', async () => { + mockMaintenanceService.getBanner.mockResolvedValue({} as any); + + await controller.getMaintenanceBanner(); + + expect(mockMaintenanceService.getBanner).toHaveBeenCalledTimes(1); + }); +}); diff --git a/services/apps/alcs/src/alcs/maintenance/maintenance.controller.ts b/services/apps/alcs/src/alcs/maintenance/maintenance.controller.ts new file mode 100644 index 0000000000..70efad17d9 --- /dev/null +++ b/services/apps/alcs/src/alcs/maintenance/maintenance.controller.ts @@ -0,0 +1,21 @@ +import { Controller, Get, UseGuards } from '@nestjs/common'; +import { ApiOAuth2 } from '@nestjs/swagger'; +import * as config from 'config'; + +import { MaintenanceService } from '../../common/maintenance/maintenance.service'; +import { RolesGuard } from '../../common/authorization/roles-guard.service'; +import { UserRoles } from '../../common/authorization/roles.decorator'; +import { ANY_AUTH_ROLE } from '../../common/authorization/roles'; + +@ApiOAuth2(config.get('KEYCLOAK.SCOPES')) +@Controller('maintenance') +@UseGuards(RolesGuard) +export class MaintenanceController { + constructor(private maintenanceService: MaintenanceService) {} + + @Get('/banner') + @UserRoles(...ANY_AUTH_ROLE) + async getMaintenanceBanner() { + return this.maintenanceService.getBanner(); + } +} diff --git a/services/apps/alcs/src/alcs/maintenance/maintenance.module.ts b/services/apps/alcs/src/alcs/maintenance/maintenance.module.ts new file mode 100644 index 0000000000..0dcb11f991 --- /dev/null +++ b/services/apps/alcs/src/alcs/maintenance/maintenance.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Configuration } from '../../common/entities/configuration.entity'; +import { MaintenanceService } from '../../common/maintenance/maintenance.service'; +import { MaintenanceController } from './maintenance.controller'; + +@Module({ + imports: [TypeOrmModule.forFeature([Configuration])], + providers: [MaintenanceService], + controllers: [MaintenanceController], +}) +export class MaintenanceModule {} diff --git a/services/apps/alcs/src/common/entities/configuration.entity.ts b/services/apps/alcs/src/common/entities/configuration.entity.ts index c6ac80a4c9..77dd7aed07 100644 --- a/services/apps/alcs/src/common/entities/configuration.entity.ts +++ b/services/apps/alcs/src/common/entities/configuration.entity.ts @@ -2,6 +2,8 @@ import { BaseEntity, Column, Entity, PrimaryColumn } from 'typeorm'; export enum CONFIG_VALUE { PORTAL_MAINTENANCE_MODE = 'portal_maintenance_mode', + APP_MAINTENANCE_BANNER = 'app_maintenance_banner', + APP_MAINTENANCE_BANNER_MESSAGE = 'app_maintenance_banner_message', } @Entity({ comment: 'Stores real time config values editable by ALCS Admin.' }) diff --git a/services/apps/alcs/src/common/maintenance/maintenance.module.ts b/services/apps/alcs/src/common/maintenance/maintenance.module.ts new file mode 100644 index 0000000000..6da5238846 --- /dev/null +++ b/services/apps/alcs/src/common/maintenance/maintenance.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { MaintenanceService } from './maintenance.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Configuration } from '../entities/configuration.entity'; + +@Module({ + imports: [TypeOrmModule.forFeature([Configuration])], + providers: [MaintenanceService], + exports: [MaintenanceService], +}) +export class MaintenanceModule {} diff --git a/services/apps/alcs/src/common/maintenance/maintenance.service.spec.ts b/services/apps/alcs/src/common/maintenance/maintenance.service.spec.ts new file mode 100644 index 0000000000..8907e484d9 --- /dev/null +++ b/services/apps/alcs/src/common/maintenance/maintenance.service.spec.ts @@ -0,0 +1,70 @@ +import { DeepMocked, createMock } from '@golevelup/nestjs-testing'; +import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { MaintenanceService } from './maintenance.service'; +import { CONFIG_VALUE, Configuration } from '../entities/configuration.entity'; + +describe('MaintenanceService', () => { + let service: MaintenanceService; + let mockRepo: DeepMocked>; + + beforeEach(async () => { + mockRepo = createMock(); + + const module: TestingModule = await Test.createTestingModule({ + providers: [ + MaintenanceService, + { + provide: getRepositoryToken(Configuration), + useValue: mockRepo, + }, + ], + }).compile(); + + service = module.get(MaintenanceService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + it('should return correct banner info', async () => { + const mockBanner = new Configuration({ + name: CONFIG_VALUE.APP_MAINTENANCE_BANNER, + value: 'true', + }); + + const mockBannerMessage = new Configuration({ + name: CONFIG_VALUE.APP_MAINTENANCE_BANNER_MESSAGE, + value: 'Test message', + }); + + mockRepo.findOne + .mockReturnValueOnce(Promise.resolve(mockBanner)) + .mockReturnValueOnce(Promise.resolve(mockBannerMessage)); + + const res = await service.getBanner(); + expect(mockRepo.findOne).toHaveBeenCalledTimes(2); + expect(res).toEqual({ + showBanner: true, + message: 'Test message', + }); + }); + + it('should return only banner status if the banner is not shown', async () => { + const mockBanner = new Configuration({ + name: CONFIG_VALUE.APP_MAINTENANCE_BANNER, + value: 'false', + }); + + mockRepo.findOne.mockResolvedValue(mockBanner); + + const res = await service.getBanner(); + expect(mockRepo.findOne).toHaveBeenCalledTimes(1); + expect(res).toEqual({ + showBanner: false, + message: null, + }); + }); +}); diff --git a/services/apps/alcs/src/common/maintenance/maintenance.service.ts b/services/apps/alcs/src/common/maintenance/maintenance.service.ts new file mode 100644 index 0000000000..291f54574b --- /dev/null +++ b/services/apps/alcs/src/common/maintenance/maintenance.service.ts @@ -0,0 +1,38 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { CONFIG_VALUE, Configuration } from '../entities/configuration.entity'; + +@Injectable() +export class MaintenanceService { + constructor( + @InjectRepository(Configuration) + private configurationRepository: Repository, + ) {} + + async getBanner() { + const bannerStatus = await this.getByName( + CONFIG_VALUE.APP_MAINTENANCE_BANNER, + ); + + const showBanner = bannerStatus?.value === 'true'; + let message: string | null = null; + + if (showBanner) { + const bannerMessage = await this.getByName( + CONFIG_VALUE.APP_MAINTENANCE_BANNER_MESSAGE, + ); + message = bannerMessage?.value || null; + } + + return { showBanner, message }; + } + + private getByName(config: CONFIG_VALUE) { + return this.configurationRepository.findOne({ + where: { + name: config, + }, + }); + } +} diff --git a/services/apps/alcs/src/portal/maintenance/maintenance.controller.spec.ts b/services/apps/alcs/src/portal/maintenance/maintenance.controller.spec.ts new file mode 100644 index 0000000000..f40c80f5a1 --- /dev/null +++ b/services/apps/alcs/src/portal/maintenance/maintenance.controller.spec.ts @@ -0,0 +1,41 @@ +import { DeepMocked, createMock } from '@golevelup/nestjs-testing'; +import { Test, TestingModule } from '@nestjs/testing'; +import { ClsService } from 'nestjs-cls'; +import { MaintenanceController } from './maintenance.controller'; +import { MaintenanceService } from '../../common/maintenance/maintenance.service'; +import { mockKeyCloakProviders } from '../../../test/mocks/mockTypes'; + +describe('MaintenanceController', () => { + let controller: MaintenanceController; + let mockMaintenanceService: DeepMocked; + + beforeEach(async () => { + mockMaintenanceService = createMock(); + + const module: TestingModule = await Test.createTestingModule({ + controllers: [MaintenanceController], + providers: [ + { provide: MaintenanceService, useValue: mockMaintenanceService }, + { + provide: ClsService, + useValue: {}, + }, + ...mockKeyCloakProviders, + ], + }).compile(); + + controller = module.get(MaintenanceController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); + + it('should call through to service for getting maintenance banner', async () => { + mockMaintenanceService.getBanner.mockResolvedValue({} as any); + + await controller.getMaintenanceBanner(); + + expect(mockMaintenanceService.getBanner).toHaveBeenCalledTimes(1); + }); +}); diff --git a/services/apps/alcs/src/portal/maintenance/maintenance.controller.ts b/services/apps/alcs/src/portal/maintenance/maintenance.controller.ts new file mode 100644 index 0000000000..4bc1d75b29 --- /dev/null +++ b/services/apps/alcs/src/portal/maintenance/maintenance.controller.ts @@ -0,0 +1,14 @@ +import { Controller, Get } from '@nestjs/common'; +import { Public } from 'nest-keycloak-connect'; +import { MaintenanceService } from '../../common/maintenance/maintenance.service'; + +@Public() +@Controller('maintenance') +export class MaintenanceController { + constructor(private maintenanceService: MaintenanceService) {} + + @Get('/banner') + async getMaintenanceBanner() { + return this.maintenanceService.getBanner(); + } +} diff --git a/services/apps/alcs/src/portal/maintenance/maintenance.module.ts b/services/apps/alcs/src/portal/maintenance/maintenance.module.ts new file mode 100644 index 0000000000..59ba06a178 --- /dev/null +++ b/services/apps/alcs/src/portal/maintenance/maintenance.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { MaintenanceController } from './maintenance.controller'; +import { Configuration } from '../../common/entities/configuration.entity'; +import { MaintenanceService } from '../../common/maintenance/maintenance.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([Configuration])], + providers: [MaintenanceService], + controllers: [MaintenanceController], +}) +export class MaintenanceModule {} diff --git a/services/apps/alcs/src/portal/portal.module.ts b/services/apps/alcs/src/portal/portal.module.ts index 58a3691a92..2521912385 100644 --- a/services/apps/alcs/src/portal/portal.module.ts +++ b/services/apps/alcs/src/portal/portal.module.ts @@ -21,6 +21,7 @@ import { ParcelModule } from './parcel/parcel.module'; import { PdfGenerationModule } from './pdf-generation/pdf-generation.module'; import { NotificationSubmissionModule } from './notification-submission/notification-submission.module'; import { PublicModule } from './public/public.module'; +import { MaintenanceModule } from './maintenance/maintenance.module'; @Module({ imports: [ @@ -44,6 +45,7 @@ import { PublicModule } from './public/public.module'; PublicModule, CodeModule, InboxModule, + MaintenanceModule, RouterModule.register([ { path: 'portal', module: ApplicationSubmissionModule }, { path: 'portal', module: NoticeOfIntentSubmissionModule }, @@ -58,6 +60,7 @@ import { PublicModule } from './public/public.module'; { path: 'portal', module: NotificationSubmissionModule }, { path: 'portal', module: PortalNotificationDocumentModule }, { path: 'portal', module: InboxModule }, + { path: 'portal', module: MaintenanceModule }, ]), ], controllers: [CodeController], diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1712707802345-add-maintenance-banner-config.ts b/services/apps/alcs/src/providers/typeorm/migrations/1712707802345-add-maintenance-banner-config.ts new file mode 100644 index 0000000000..1d124dac5f --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1712707802345-add-maintenance-banner-config.ts @@ -0,0 +1,27 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddMaintenanceBannerConfig1712707802345 + implements MigrationInterface +{ + name = 'AddMaintenanceBannerConfig1712707802345'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + INSERT INTO "alcs"."configuration" ("name", "value") VALUES ('app_maintenance_banner', 'false'); + `); + + await queryRunner.query(` + INSERT INTO "alcs"."configuration" ("name", "value") VALUES ('app_maintenance_banner_message', ''); + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + DELETE FROM "alcs"."configuration" WHERE "name" = 'app_maintenance_banner_message'; + `); + + await queryRunner.query(` + DELETE FROM "alcs"."configuration" WHERE "name" = 'app_maintenance_banner'; + `); + } +} From 6cce5762a86c2984f8327226b16c0da69bd29685 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Wed, 10 Apr 2024 10:56:44 -0700 Subject: [PATCH 117/153] Caches users in Redis * Cache users in redis for loading them for each request --- .../authorization.controller.spec.ts | 2 +- .../authorization/roles-guard.service.spec.ts | 51 ++++++++++++++++--- .../authorization/roles-guard.service.ts | 40 ++++++++++++++- .../apps/alcs/src/user/user.service.spec.ts | 17 +++++-- services/apps/alcs/src/user/user.service.ts | 23 ++++++++- services/apps/alcs/test/mocks/mockTypes.ts | 5 ++ 6 files changed, 124 insertions(+), 14 deletions(-) diff --git a/services/apps/alcs/src/common/authorization/authorization.controller.spec.ts b/services/apps/alcs/src/common/authorization/authorization.controller.spec.ts index 681120b3c9..150f85807f 100644 --- a/services/apps/alcs/src/common/authorization/authorization.controller.spec.ts +++ b/services/apps/alcs/src/common/authorization/authorization.controller.spec.ts @@ -39,6 +39,7 @@ describe('AuthorizationController', () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AuthorizationController], providers: [ + ...mockKeyCloakProviders, { provide: AuthorizationService, useValue: mockService, @@ -55,7 +56,6 @@ describe('AuthorizationController', () => { provide: ClsService, useValue: {}, }, - ...mockKeyCloakProviders, ], }).compile(); diff --git a/services/apps/alcs/src/common/authorization/roles-guard.service.spec.ts b/services/apps/alcs/src/common/authorization/roles-guard.service.spec.ts index 059822dee8..20016a5a83 100644 --- a/services/apps/alcs/src/common/authorization/roles-guard.service.spec.ts +++ b/services/apps/alcs/src/common/authorization/roles-guard.service.spec.ts @@ -1,5 +1,5 @@ +import { RedisService } from '@app/common/redis/redis.service'; import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; -import { ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { Test, TestingModule } from '@nestjs/testing'; import { @@ -10,11 +10,11 @@ import { } from 'nest-keycloak-connect'; import { KeycloakMultiTenantService } from 'nest-keycloak-connect/services/keycloak-multitenant.service'; import { ClsService } from 'nestjs-cls'; +import { mockAppLoggerService } from '../../../test/mocks/mockLogger'; import { User } from '../../user/user.entity'; import { UserService } from '../../user/user.service'; -import { mockAppLoggerService } from '../../../test/mocks/mockLogger'; -import { RolesGuard } from './roles-guard.service'; import { AUTH_ROLE } from './roles'; +import { RolesGuard } from './roles-guard.service'; describe('RolesGuard', () => { let guard: RolesGuard; @@ -22,6 +22,7 @@ describe('RolesGuard', () => { let mockContext; let mockClsService: DeepMocked; let mockUserService: DeepMocked; + let mockRedisService: DeepMocked; const mockUser = {}; @@ -38,14 +39,15 @@ describe('RolesGuard', () => { } as any; beforeEach(async () => { - mockContext = createMock(); + mockContext = createMock(); mockContext.switchToHttp.mockReturnValue(mockHttpContext); - reflector = createMock(); + reflector = createMock(); reflector.get.mockReturnValue([AUTH_ROLE.ADMIN, AUTH_ROLE.LUP]); - mockClsService = createMock(); - mockUserService = createMock(); + mockClsService = createMock(); + mockUserService = createMock(); + mockRedisService = createMock(); mockUserService.getByGuid.mockResolvedValue(mockUser as User); @@ -77,6 +79,10 @@ describe('RolesGuard', () => { provide: Reflector, useValue: reflector, }, + { + provide: RedisService, + useValue: mockRedisService, + }, ], }).compile(); @@ -90,6 +96,11 @@ describe('RolesGuard', () => { }); it('should accept and set the user into CLS if users has all of the roles', async () => { + const mockRedisClient = { + get: jest.fn(), + }; + mockRedisService.getClient.mockReturnValue(mockRedisClient as any); + const isAllowed = await guard.canActivate(mockContext); expect(isAllowed).toBeTruthy(); expect(mockClsService.set).toHaveBeenCalledWith('userGuids', { @@ -127,7 +138,33 @@ describe('RolesGuard', () => { }), } as any); + const mockRedisClient = { + get: jest.fn(), + }; + mockRedisService.getClient.mockReturnValue(mockRedisClient as any); + + const isAllowed = await guard.canActivate(mockContext); + expect(isAllowed).toBeTruthy(); + }); + + it('should not call the user service if there is a the cached redis user', async () => { + mockContext.switchToHttp.mockReturnValue({ + getRequest: () => ({ + user: { + client_roles: [AUTH_ROLE.ADMIN], + }, + }), + } as any); + + const mockRedisClient = { + get: jest.fn(), + }; + mockRedisClient.get.mockResolvedValue(JSON.stringify(new User())); + mockRedisService.getClient.mockReturnValue(mockRedisClient as any); + const isAllowed = await guard.canActivate(mockContext); expect(isAllowed).toBeTruthy(); + expect(mockUserService.getByGuid).toHaveBeenCalledTimes(0); + expect(mockRedisClient.get).toHaveBeenCalledTimes(1); }); }); diff --git a/services/apps/alcs/src/common/authorization/roles-guard.service.ts b/services/apps/alcs/src/common/authorization/roles-guard.service.ts index d8fad2d4b1..0ec9c421c1 100644 --- a/services/apps/alcs/src/common/authorization/roles-guard.service.ts +++ b/services/apps/alcs/src/common/authorization/roles-guard.service.ts @@ -1,3 +1,4 @@ +import { RedisService } from '@app/common/redis/redis.service'; import { CanActivate, ExecutionContext, @@ -33,6 +34,7 @@ export class RolesGuard implements CanActivate { private reflector: Reflector, private cls: ClsService, private userService: UserService, + private redisService: RedisService, ) { this.keyCloakGuard = new KeyCloakRoleGuard( singleTenant, @@ -87,10 +89,46 @@ export class RolesGuard implements CanActivate { } if (matchingRoles.length > 0 || requiredRoles.length === 0) { - request.user.entity = await this.userService.getByGuid(userGuids); + request.user.entity = await this.getAndCacheUser(userGuids); return true; } return false; } + + private async getAndCacheUser(userGuids: UserGuids) { + const redisClient = this.redisService.getClient(); + const bceidUser = await redisClient.get( + `user/bceid/${userGuids.bceidGuid}`, + ); + if (bceidUser) { + return JSON.parse(bceidUser); + } + + const idirUser = await redisClient.get( + `user/idir/${userGuids.idirUserGuid}`, + ); + if (idirUser) { + return JSON.parse(idirUser); + } + + const user = await this.userService.getByGuid(userGuids); + if (user) { + const serialized = JSON.stringify(user); + if (user.bceidGuid) { + await redisClient.setEx( + `user/bceid/${user.bceidGuid}`, + 600, + serialized, + ); + } else if (user.idirUserGuid) { + await redisClient.setEx( + `user/idir/${user.idirUserGuid}`, + 600, + serialized, + ); + } + } + return user; + } } diff --git a/services/apps/alcs/src/user/user.service.spec.ts b/services/apps/alcs/src/user/user.service.spec.ts index 475ad53d43..9277972bca 100644 --- a/services/apps/alcs/src/user/user.service.spec.ts +++ b/services/apps/alcs/src/user/user.service.spec.ts @@ -1,10 +1,11 @@ import { CONFIG_TOKEN } from '@app/common/config/config.module'; import { ServiceNotFoundException } from '@app/common/exceptions/base.exception'; -import { classes } from 'automapper-classes'; -import { AutomapperModule } from 'automapper-nestjs'; +import { RedisService } from '@app/common/redis/redis.service'; 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 * as config from 'config'; import { Repository } from 'typeorm'; import { initUserMockEntity } from '../../test/mocks/mockEntities'; @@ -19,6 +20,7 @@ describe('UserService', () => { let mockUserRepository: DeepMocked>; let mockGovernmentRepository: DeepMocked>; let emailServiceMock: DeepMocked; + let mockRedisService: DeepMocked; const email = 'bruce.wayne@gotham.com'; const mockUser = initUserMockEntity(); @@ -28,6 +30,7 @@ describe('UserService', () => { emailServiceMock = createMock(); mockUserRepository = createMock(); mockGovernmentRepository = createMock(); + mockRedisService = createMock(); const module: TestingModule = await Test.createTestingModule({ providers: [ @@ -40,6 +43,10 @@ describe('UserService', () => { provide: getRepositoryToken(LocalGovernment), useValue: mockGovernmentRepository, }, + { + provide: RedisService, + useValue: mockRedisService, + }, { provide: EmailService, useValue: emailServiceMock }, UserProfile, { @@ -118,9 +125,13 @@ describe('UserService', () => { mockUserEntity.settings = { favoriteBoards: ['CEO', 'SOI'] }; mockUserEntity.email = 'some test email'; + const mockClient = { setEx: jest.fn() }; + mockRedisService.getClient.mockReturnValue(mockClient as any); const result = await service.update('fake-uuid', mockUserEntity); expect(result).toStrictEqual(mockUserEntity); + expect(mockRedisService.getClient).toHaveBeenCalledTimes(1); + expect(mockClient.setEx).toHaveBeenCalledTimes(1); }); it('should fail when user does not exist', async () => { @@ -138,7 +149,7 @@ describe('UserService', () => { const prefix = env === 'production' ? '' : `[${env}]`; const subject = `${prefix} Access Requested to ALCS`; const body = `A new user ${email}: ${userIdentifier} has requested access to ALCS.
-CSS`; +CSS`; await service.sendNewUserRequestEmail(email, userIdentifier); diff --git a/services/apps/alcs/src/user/user.service.ts b/services/apps/alcs/src/user/user.service.ts index f2343f4cbb..cbabe36aa0 100644 --- a/services/apps/alcs/src/user/user.service.ts +++ b/services/apps/alcs/src/user/user.service.ts @@ -1,8 +1,9 @@ import { CONFIG_TOKEN } from '@app/common/config/config.module'; import { ServiceNotFoundException } from '@app/common/exceptions/base.exception'; -import { Mapper } from 'automapper-core'; +import { RedisService } from '@app/common/redis/redis.service'; import { Inject, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { Mapper } from 'automapper-core'; import { InjectMapper } from 'automapper-nestjs'; import { IConfig } from 'config'; import { Repository } from 'typeorm'; @@ -26,6 +27,7 @@ export class UserService { @InjectRepository(LocalGovernment) private localGovernmentRepository: Repository, @Inject(CONFIG_TOKEN) private config: IConfig, + private redisService: RedisService, ) {} async getAssignableUsers() { @@ -88,6 +90,23 @@ export class UserService { } const updatedUser = Object.assign(existingUser, updates); + + const serialized = JSON.stringify(updatedUser); + const redisClient = this.redisService.getClient(); + if (updatedUser.bceidGuid) { + await redisClient.setEx( + `user/bceid/${updatedUser.bceidGuid}`, + 600, + serialized, + ); + } else if (updatedUser.idirUserGuid) { + await redisClient.setEx( + `user/idir/${updatedUser.idirUserGuid}`, + 600, + serialized, + ); + } + return this.userRepository.save(updatedUser); } @@ -110,7 +129,7 @@ export class UserService { const prefix = env === 'production' ? '' : `[${env}]`; const subject = `${prefix} Access Requested to ALCS`; const body = `A new user ${email}: ${userIdentifier} has requested access to ALCS.
-CSS`; +CSS`; await this.emailService.sendEmail({ to: this.config.get('EMAIL.DEFAULT_ADMINS'), diff --git a/services/apps/alcs/test/mocks/mockTypes.ts b/services/apps/alcs/test/mocks/mockTypes.ts index 296b4c0a2b..bef47f5fee 100644 --- a/services/apps/alcs/test/mocks/mockTypes.ts +++ b/services/apps/alcs/test/mocks/mockTypes.ts @@ -1,3 +1,4 @@ +import { RedisService } from '@app/common/redis/redis.service'; import { KEYCLOAK_CONNECT_OPTIONS, KEYCLOAK_INSTANCE, @@ -33,4 +34,8 @@ export const mockKeyCloakProviders = [ provide: UserService, useValue: {}, }, + { + provide: RedisService, + useValue: {}, + }, ]; From b5b18383a0579bb1c3cac6b047eb02ce4fa913a6 Mon Sep 17 00:00:00 2001 From: mhuseinov <61513701+mhuseinov@users.noreply.github.com> Date: Thu, 11 Apr 2024 10:33:15 -0700 Subject: [PATCH 118/153] Bugfix/alcs 1826 - planning review advanced search (#1596) fix search by planning review type fix sorting and paging fix navigation to planning review from advanced search result --- .../planning-review-search-table.component.ts | 2 +- .../planning-review/planning-review.dto.ts | 14 ++++++++ ...ing-review-advanced-search.service.spec.ts | 2 +- ...planning-review-advanced-search.service.ts | 17 +++++++++ .../src/alcs/search/search.controller.spec.ts | 23 ++++++++++++ .../alcs/src/alcs/search/search.controller.ts | 36 ++++++++++++++++--- 6 files changed, 88 insertions(+), 6 deletions(-) diff --git a/alcs-frontend/src/app/features/search/planning-review-search-table/planning-review-search-table.component.ts b/alcs-frontend/src/app/features/search/planning-review-search-table/planning-review-search-table.component.ts index 1d2a9cb760..9aeb07a46a 100644 --- a/alcs-frontend/src/app/features/search/planning-review-search-table/planning-review-search-table.component.ts +++ b/alcs-frontend/src/app/features/search/planning-review-search-table/planning-review-search-table.component.ts @@ -73,7 +73,7 @@ export class PlanningReviewSearchTableComponent { } onSelectRecord(record: SearchResult) { - const url = this.router.serializeUrl(this.router.createUrlTree([`/planning-review/${record.referenceId}`])); + const url = this.router.serializeUrl(this.router.createUrlTree([`/planning-review/${record.fileNumber}`])); window.open(url, '_blank'); } 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 be665ac4b1..daf6d490a1 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 @@ -13,6 +13,20 @@ import { ApplicationRegionDto } from '../code/application-code/application-regio import { LocalGovernmentDto } from '../local-government/local-government.dto'; import { PlanningReviewMeetingDto } from './planning-review-meeting/planning-review-meeting.dto'; +export enum PLANNING_REVIEW_TYPES { + AAPP = 'AAPP', + MISC = 'MISC', + BAPP = 'BAPP', + ALRB = 'ALRB', + RGSP = 'RGSP', + CLUP = 'CLUP', + OCPP = 'OCPP', + TPPP = 'TPPP', + UEPP = 'UEPP', + ZBPP = 'ZBPP', + PARK = 'PARK', +} + export class PlanningReviewTypeDto extends BaseCodeDto { @AutoMap() shortLabel: string; diff --git a/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.spec.ts b/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.spec.ts index 7d4003cbbf..08d5b9b54d 100644 --- a/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.spec.ts +++ b/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.spec.ts @@ -88,7 +88,7 @@ describe('PlanningReviewAdvancedSearchService', () => { expect(result).toEqual({ data: [], total: 0 }); expect(mockPRSearchView.createQueryBuilder).toHaveBeenCalledTimes(1); - expect(mockQuery.andWhere).toHaveBeenCalledTimes(8); + expect(mockQuery.andWhere).toHaveBeenCalledTimes(9); }); it('should call compileSearchQuery method correctly', async () => { diff --git a/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.ts b/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.ts index 332d38f954..bd292ee84b 100644 --- a/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.ts +++ b/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.ts @@ -100,6 +100,7 @@ export class PlanningReviewAdvancedSearchService { query = this.compileSearchByNameQuery(searchDto, query); query = this.compileDecisionSearchQuery(searchDto, query); query = this.compileDateRangeSearchQuery(searchDto, query); + query = this.compileSearchByTypeQuery(searchDto, query); return query; } @@ -187,4 +188,20 @@ export class PlanningReviewAdvancedSearchService { } return query; } + + private compileSearchByTypeQuery( + searchDto: SearchRequestDto, + query: SelectQueryBuilder, + ) { + if (searchDto.fileTypes.length > 0) { + query = query.andWhere( + 'planningReviewSearch.planning_review_type_code 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 2a5cf907fa..ea32f22ca3 100644 --- a/services/apps/alcs/src/alcs/search/search.controller.spec.ts +++ b/services/apps/alcs/src/alcs/search/search.controller.spec.ts @@ -311,4 +311,27 @@ describe('SearchController', () => { expect(result.inquiries).toBeDefined(); expect(result.totalInquiries).toBe(0); }); + + it('should call advanced search to retrieve Planning Review only when Planning Review file type selected', async () => { + const mockSearchRequestDto = { + pageSize: 1, + page: 1, + sortField: '1', + sortDirection: 'ASC', + fileTypes: ['MISC'], + }; + + const result = await controller.advancedSearch( + mockSearchRequestDto as SearchRequestDto, + ); + + expect( + mockPlanningReviewAdvancedSearchService.search, + ).toHaveBeenCalledTimes(1); + expect(mockPlanningReviewAdvancedSearchService.search).toHaveBeenCalledWith( + mockSearchRequestDto, + ); + expect(result.inquiries).toBeDefined(); + expect(result.totalInquiries).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 75b6b79f6e..f4472d5949 100644 --- a/services/apps/alcs/src/alcs/search/search.controller.ts +++ b/services/apps/alcs/src/alcs/search/search.controller.ts @@ -14,9 +14,11 @@ import { Application } from '../application/application.entity'; 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'; +import { INQUIRY_TYPES } from '../inquiry/inquiry.dto'; import { Inquiry } from '../inquiry/inquiry.entity'; import { NoticeOfIntent } from '../notice-of-intent/notice-of-intent.entity'; import { Notification } from '../notification/notification.entity'; +import { PLANNING_REVIEW_TYPES } from '../planning-review/planning-review.dto'; import { PlanningReview } from '../planning-review/planning-review.entity'; import { ApplicationAdvancedSearchService } from './application/application-advanced-search.service'; import { ApplicationSubmissionSearchView } from './application/application-search-view.entity'; @@ -40,7 +42,6 @@ import { SearchResultDto, } from './search.dto'; import { SearchService } from './search.service'; -import { INQUIRY_TYPES } from '../inquiry/inquiry.dto'; @ApiOAuth2(config.get('KEYCLOAK.SCOPES')) @UseGuards(RolesGuard) @@ -254,6 +255,28 @@ export class SearchController { }; } + @Post('/advanced/planning-reviews') + @UserRoles(...ROLES_ALLOWED_APPLICATIONS) + async advancedSearchPlanningReviews( + @Body() searchDto: SearchRequestDto, + ): Promise> { + const planningReviews = + await this.planningReviewSearchService.search(searchDto); + + const mappedSearchResult = await this.mapAdvancedSearchResults( + null, + null, + planningReviews, + null, + null, + ); + + return { + total: mappedSearchResult.totalPlanningReviews, + data: mappedSearchResult.planningReviews, + }; + } + @Post('/advanced/inquiries') @UserRoles(...ROLES_ALLOWED_APPLICATIONS) async advancedSearchInquiries( @@ -296,9 +319,14 @@ export class SearchController { notificationTypeSpecified = searchDto.fileTypes.includes('SRW'); - planningReviewTypeSpecified = searchDto.fileTypes.some((searchType) => - ['PLAN'].includes(searchType), - ); + planningReviewTypeSpecified = + searchDto.fileTypes.filter((searchType) => + Object.values(PLANNING_REVIEW_TYPES).includes( + PLANNING_REVIEW_TYPES[ + searchType as keyof typeof PLANNING_REVIEW_TYPES + ], + ), + ).length > 0; inquiriesTypeSpecified = searchDto.fileTypes.filter((searchType) => From e18b23a188f3a4c28e654966eced6e7852fe2951 Mon Sep 17 00:00:00 2001 From: "to. sandra" <76515860+sandratoh@users.noreply.github.com> Date: Thu, 11 Apr 2024 15:26:09 -0700 Subject: [PATCH 119/153] Add character limit to inline-text component (#1598) --- .../admin/configuration/configuration.component.html | 1 + .../inline-text/inline-text.component.html | 10 ++++++++-- .../inline-text/inline-text.component.scss | 11 +++++++++++ .../inline-text/inline-text.component.ts | 1 + 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/alcs-frontend/src/app/features/admin/configuration/configuration.component.html b/alcs-frontend/src/app/features/admin/configuration/configuration.component.html index 31faaeb83a..5dd793359d 100644 --- a/alcs-frontend/src/app/features/admin/configuration/configuration.component.html +++ b/alcs-frontend/src/app/features/admin/configuration/configuration.component.html @@ -6,6 +6,7 @@

Maintenance

diff --git a/alcs-frontend/src/app/shared/inline-editors/inline-text/inline-text.component.html b/alcs-frontend/src/app/shared/inline-editors/inline-text/inline-text.component.html index 64e6cbaae3..51da2b329d 100644 --- a/alcs-frontend/src/app/shared/inline-editors/inline-text/inline-text.component.html +++ b/alcs-frontend/src/app/shared/inline-editors/inline-text/inline-text.component.html @@ -4,7 +4,7 @@ {{ value }} - + {{ value | mask: mask }} - +
diff --git a/alcs-frontend/src/app/shared/inline-editors/inline-text/inline-text.component.scss b/alcs-frontend/src/app/shared/inline-editors/inline-text/inline-text.component.scss index 6487e5e781..b5ff963fe4 100644 --- a/alcs-frontend/src/app/shared/inline-editors/inline-text/inline-text.component.scss +++ b/alcs-frontend/src/app/shared/inline-editors/inline-text/inline-text.component.scss @@ -27,12 +27,23 @@ .button-container { display: flex; justify-content: flex-end; + position: relative; button:not(:last-child) { margin-right: 2px !important; } } +.char-limit { + position: absolute; + left: 0; + top: 0; + bottom: 0; + display: flex; + align-items: center; + color: colors.$grey; +} + .add { cursor: pointer; } diff --git a/alcs-frontend/src/app/shared/inline-editors/inline-text/inline-text.component.ts b/alcs-frontend/src/app/shared/inline-editors/inline-text/inline-text.component.ts index 1cb6fef6ee..bc8ea5440d 100644 --- a/alcs-frontend/src/app/shared/inline-editors/inline-text/inline-text.component.ts +++ b/alcs-frontend/src/app/shared/inline-editors/inline-text/inline-text.component.ts @@ -21,6 +21,7 @@ export class InlineTextComponent implements AfterContentChecked { @Input() required = false; @Output() save = new EventEmitter(); @Input() mask?: string | undefined; + @Input() maxLength: number | null = null; @ViewChild('editInput') textInput!: ElementRef; From 10eaceea849b96b77cb42c8aa0f67f5c6e72604d Mon Sep 17 00:00:00 2001 From: mhuseinov <61513701+mhuseinov@users.noreply.github.com> Date: Mon, 15 Apr 2024 08:41:00 -0700 Subject: [PATCH 120/153] refactor maintenance guard (#1597) maintenance mode guard update --- .../alcs/src/portal/guards/maintenance.guard.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/services/apps/alcs/src/portal/guards/maintenance.guard.ts b/services/apps/alcs/src/portal/guards/maintenance.guard.ts index cd69aec2e0..1ab920b96f 100644 --- a/services/apps/alcs/src/portal/guards/maintenance.guard.ts +++ b/services/apps/alcs/src/portal/guards/maintenance.guard.ts @@ -6,6 +6,7 @@ import { Injectable, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import * as config from 'config'; import { FastifyRequest } from 'fastify'; import { Repository } from 'typeorm'; import { @@ -30,13 +31,11 @@ export class MaintenanceGuard implements CanActivate { const routeUrl = req.routeOptions.url; - if ( - routeUrl && - (routeUrl.startsWith('/portal') || - routeUrl.startsWith('/public') || - routeUrl.startsWith('/api/portal') || - routeUrl.startsWith('/api/public')) - ) { + const apiPrefix = config.get('ALCS.API_PREFIX'); + const prefixes = ['/portal', '/public'].map((item) => + apiPrefix ? `/${apiPrefix}${item}` : item, + ); + if (routeUrl && prefixes.some((prefix) => routeUrl.startsWith(prefix))) { const maintenanceMode = await this.configurationRepository.findOne({ where: { name: CONFIG_VALUE.PORTAL_MAINTENANCE_MODE, From b46f938733135ccb566233401894aa8b89829e7a Mon Sep 17 00:00:00 2001 From: mhuseinov <61513701+mhuseinov@users.noreply.github.com> Date: Mon, 15 Apr 2024 09:48:29 -0700 Subject: [PATCH 121/153] sort by status in planning review (#1599) --- .../planning-review-advanced-search.service.spec.ts | 8 +++++++- .../planning-review-advanced-search.service.ts | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.spec.ts b/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.spec.ts index 08d5b9b54d..088e8db422 100644 --- a/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.spec.ts +++ b/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.spec.ts @@ -11,7 +11,13 @@ describe('PlanningReviewAdvancedSearchService', () => { let service: PlanningReviewAdvancedSearchService; let mockPRSearchView: DeepMocked>; let mockLocalGovernmentRepository: DeepMocked>; - const sortFields = ['fileId', 'type', 'government', 'dateSubmitted']; + const sortFields = [ + 'fileId', + 'type', + 'government', + 'dateSubmitted', + 'status', + ]; const mockSearchDto: SearchRequestDto = { fileNumber: '123', diff --git a/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.ts b/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.ts index bd292ee84b..2af8db9c5d 100644 --- a/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.ts +++ b/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.ts @@ -58,6 +58,9 @@ export class PlanningReviewAdvancedSearchService { case 'government': return '"planningReviewSearch"."local_government_name"'; + case 'status': + return '"planningReviewSearch"."open"'; + default: case 'dateSubmitted': return '"planningReviewSearch"."date_submitted_to_alc"'; From 3f6828feb051ad6a8b0c08b9673255cb2ec5e1a6 Mon Sep 17 00:00:00 2001 From: mhuseinov <61513701+mhuseinov@users.noreply.github.com> Date: Mon, 15 Apr 2024 11:57:10 -0700 Subject: [PATCH 122/153] replace "more actions" button with "view details" (#1600) replace "more actions" button with "view details" on application and noi --- .../application-dialog.component.html | 26 ++++++------------- .../application-dialog.component.ts | 10 +++---- .../notice-of-intent-dialog.component.html | 25 ++++++------------ 3 files changed, 19 insertions(+), 42 deletions(-) diff --git a/alcs-frontend/src/app/features/board/dialogs/application/application-dialog.component.html b/alcs-frontend/src/app/features/board/dialogs/application/application-dialog.component.html index 4d8fd736c7..29fb97720d 100644 --- a/alcs-frontend/src/app/features/board/dialogs/application/application-dialog.component.html +++ b/alcs-frontend/src/app/features/board/dialogs/application/application-dialog.component.html @@ -14,24 +14,14 @@

- - - - - - +
diff --git a/alcs-frontend/src/app/features/board/dialogs/application/application-dialog.component.ts b/alcs-frontend/src/app/features/board/dialogs/application/application-dialog.component.ts index bbc1e2ab34..4b9dc74724 100644 --- a/alcs-frontend/src/app/features/board/dialogs/application/application-dialog.component.ts +++ b/alcs-frontend/src/app/features/board/dialogs/application/application-dialog.component.ts @@ -1,5 +1,5 @@ import { Component, Inject, OnInit } from '@angular/core'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Router } from '@angular/router'; import { ApplicationSubmissionToSubmissionStatusDto, @@ -41,7 +41,7 @@ export class ApplicationDialogComponent extends CardDialogComponent implements O toastService: ToastService, cardService: CardService, authService: AuthenticationService, - private applicationSubmissionStatusService: ApplicationSubmissionStatusService + private applicationSubmissionStatusService: ApplicationSubmissionStatusService, ) { super(authService, dialogRef, cardService, confirmationDialogService, toastService, userService, boardService); } @@ -65,7 +65,7 @@ export class ApplicationDialogComponent extends CardDialogComponent implements O try { submissionStatus = await this.applicationSubmissionStatusService.fetchCurrentStatusByFileNumber( fileNumber, - false + false, ); } catch (e) { console.warn(`No statuses for ${fileNumber}. Is it a manually created submission?`); @@ -119,8 +119,4 @@ export class ApplicationDialogComponent extends CardDialogComponent implements O }); } } - - async goToDetailpage() { - await this.router.navigateByUrl(`application/${this.application.fileNumber}`); - } } diff --git a/alcs-frontend/src/app/features/board/dialogs/notice-of-intent/notice-of-intent-dialog.component.html b/alcs-frontend/src/app/features/board/dialogs/notice-of-intent/notice-of-intent-dialog.component.html index 22f154ed21..23aab8ffdc 100644 --- a/alcs-frontend/src/app/features/board/dialogs/notice-of-intent/notice-of-intent-dialog.component.html +++ b/alcs-frontend/src/app/features/board/dialogs/notice-of-intent/notice-of-intent-dialog.component.html @@ -18,23 +18,14 @@

- - - - - +
From 07b4dd9781a32b82126c1620f2ef70f97631bab5 Mon Sep 17 00:00:00 2001 From: mhuseinov <61513701+mhuseinov@users.noreply.github.com> Date: Mon, 15 Apr 2024 12:38:49 -0700 Subject: [PATCH 123/153] Feature/alcs 1691 pid pin UI (#1601) pid pin search ui improvement unit tests remove redundant logs from refresh token --- .../parcel-entry/parcel-entry.component.html | 15 +- .../parcel-entry.component.spec.ts | 61 +++++- .../parcel-entry/parcel-entry.component.html | 15 +- .../parcel-entry.component.spec.ts | 55 +++++ .../parcel-entry/parcel-entry.component.html | 13 +- .../parcel-entry.component.spec.ts | 192 +++++++++++++++++- .../authentication/token-refresh.service.ts | 2 - 7 files changed, 342 insertions(+), 11 deletions(-) diff --git a/portal-frontend/src/app/features/applications/edit-submission/parcel-details/parcel-entry/parcel-entry.component.html b/portal-frontend/src/app/features/applications/edit-submission/parcel-details/parcel-entry/parcel-entry.component.html index da4cec4313..11556089df 100644 --- a/portal-frontend/src/app/features/applications/edit-submission/parcel-details/parcel-entry/parcel-entry.component.html +++ b/portal-frontend/src/app/features/applications/edit-submission/parcel-details/parcel-entry/parcel-entry.component.html @@ -56,11 +56,22 @@ mat-flat-button color="primary" (click)="onSearch()" - [disabled]="!parcelType.getRawValue() || _disabled || (isCrownLand && !searchBy.getRawValue())" + [disabled]="!parcelType.getRawValue() || _disabled || pidPin.invalid || !pidPin.getRawValue()" > Search
+
+ warning +
Please provide all 9 numbers, including leading zeros
+
Phone Number:
-
{{ selectedOwner.phoneNumber ?? '' | mask : '(000) 000-0000' }}
+
{{ selectedOwner.phoneNumber ?? '' | mask: '(000) 000-0000' }}
Email:
diff --git a/portal-frontend/src/app/features/applications/edit-submission/parcel-details/parcel-entry/parcel-entry.component.spec.ts b/portal-frontend/src/app/features/applications/edit-submission/parcel-details/parcel-entry/parcel-entry.component.spec.ts index 4add06d1fc..52cbfd6503 100644 --- a/portal-frontend/src/app/features/applications/edit-submission/parcel-details/parcel-entry/parcel-entry.component.spec.ts +++ b/portal-frontend/src/app/features/applications/edit-submission/parcel-details/parcel-entry/parcel-entry.component.spec.ts @@ -4,7 +4,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { Validators } from '@angular/forms'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatDialog } from '@angular/material/dialog'; -import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { By } from '@angular/platform-browser'; +import { DeepMocked, createMock } from '@golevelup/ts-jest'; import { BehaviorSubject } from 'rxjs'; import { ApplicationDocumentService } from '../../../../../services/application-document/application-document.service'; import { ApplicationOwnerDto } from '../../../../../services/application-owner/application-owner.dto'; @@ -196,6 +197,34 @@ describe('ParcelEntryComponent', () => { it('should require certificate of title', () => { expect(component.isCertificateOfTitleRequired).toBeTruthy(); }); + + it('should have search button disabled if no pid', () => { + const button = fixture.debugElement.query(By.css('.lookup-search-button')).nativeElement; + expect(component.pidPin.getRawValue()).toBe(''); + expect(button.disabled).toBeTruthy(); + }); + + it('should have search button disabled if pid is invalid', () => { + component.pidPin.setErrors({ invalid: true }); + const button = fixture.debugElement.query(By.css('.lookup-search-button')).nativeElement; + expect(component.pidPin.valid).toBeFalsy(); + expect(button.disabled).toBeTruthy(); + }); + + it('should have search button enabled if pid is valid', () => { + component._disabled = false; + component.parcelForm.controls.parcelType.setValue('SMPL'); + component.pidPin.setValue('123456789'); + fixture.detectChanges(); + + const button = fixture.debugElement.query(By.css('.lookup-search-button')).nativeElement; + expect(component.pidPin.valid).toBeTruthy(); + expect(!component.parcelType.getRawValue()).toBeFalsy(); + expect(!component.pidPin.getRawValue()).toBeFalsy(); + expect(component.pidPin.invalid).toBeFalsy(); + expect(component._disabled).toBeFalsy(); + expect(button.disabled).toBeFalsy(); + }); }); describe('Crown', () => { @@ -234,5 +263,35 @@ describe('ParcelEntryComponent', () => { it('should not require certificate of title', () => { expect(component.isCertificateOfTitleRequired).toBeFalsy(); }); + + it('should have search button disabled if no pid', () => { + const button = fixture.debugElement.query(By.css('.lookup-search-button')).nativeElement; + expect(component.pidPin.getRawValue()).toBe(''); + expect(button.disabled).toBeTruthy(); + }); + + it('should have search button disabled if pid is invalid', () => { + component.pidPin.setErrors({ invalid: true }); + const button = fixture.debugElement.query(By.css('.lookup-search-button')).nativeElement; + expect(component.pidPin.valid).toBeFalsy(); + expect(button.disabled).toBeTruthy(); + }); + + it('should have search button enabled if pid is valid', () => { + component._disabled = false; + component.isCrownLand = true; + component.searchBy.setValue('pid'); + component.parcelForm.controls.parcelType.setValue('CRWN'); + component.pidPin.setValue('123456789'); + + fixture.detectChanges(); + + const button = fixture.debugElement.query(By.css('.lookup-search-button')).nativeElement; + expect(!component.parcelType.getRawValue()).toBeFalsy(); + expect(!component.pidPin.getRawValue()).toBeFalsy(); + expect(component.pidPin.invalid).toBeFalsy(); + expect(component._disabled).toBeFalsy(); + expect(button.disabled).toBeFalsy(); + }); }); }); diff --git a/portal-frontend/src/app/features/notice-of-intents/edit-submission/parcels/parcel-entry/parcel-entry.component.html b/portal-frontend/src/app/features/notice-of-intents/edit-submission/parcels/parcel-entry/parcel-entry.component.html index 0ec0f65047..e5c9ca8b18 100644 --- a/portal-frontend/src/app/features/notice-of-intents/edit-submission/parcels/parcel-entry/parcel-entry.component.html +++ b/portal-frontend/src/app/features/notice-of-intents/edit-submission/parcels/parcel-entry/parcel-entry.component.html @@ -56,11 +56,22 @@ mat-flat-button color="primary" (click)="onSearch()" - [disabled]="!parcelType.getRawValue() || (!!isCrownLand && !searchBy.getRawValue())" + [disabled]="!parcelType.getRawValue() || pidPin.invalid || !pidPin.getRawValue()" > Search
+
+ warning +
Please provide all 9 numbers, including leading zeros
+
Phone Number:
-
{{ selectedOwner.phoneNumber ?? '' | mask : '(000) 000-0000' }}
+
{{ selectedOwner.phoneNumber ?? '' | mask: '(000) 000-0000' }}
Email:
diff --git a/portal-frontend/src/app/features/notice-of-intents/edit-submission/parcels/parcel-entry/parcel-entry.component.spec.ts b/portal-frontend/src/app/features/notice-of-intents/edit-submission/parcels/parcel-entry/parcel-entry.component.spec.ts index 21b13c5b6d..fb49bbdd4d 100644 --- a/portal-frontend/src/app/features/notice-of-intents/edit-submission/parcels/parcel-entry/parcel-entry.component.spec.ts +++ b/portal-frontend/src/app/features/notice-of-intents/edit-submission/parcels/parcel-entry/parcel-entry.component.spec.ts @@ -15,6 +15,7 @@ import { NoticeOfIntentParcelService } from '../../../../../services/notice-of-i import { ParcelService } from '../../../../../services/parcel/parcel.service'; import { ToastService } from '../../../../../services/toast/toast.service'; import { ParcelEntryComponent } from './parcel-entry.component'; +import { By } from '@angular/platform-browser'; describe('ParcelEntryComponent', () => { let component: ParcelEntryComponent; @@ -201,6 +202,32 @@ describe('ParcelEntryComponent', () => { it('should require certificate of title', () => { expect(component.isCertificateOfTitleRequired).toBeTruthy(); }); + + it('should have search button disabled if no pid', () => { + const button = fixture.debugElement.query(By.css('.lookup-search-button')).nativeElement; + expect(component.pidPin.getRawValue()).toBe(''); + expect(button.disabled).toBeTruthy(); + }); + + it('should have search button disabled if pid is invalid', () => { + component.pidPin.setErrors({ invalid: true }); + const button = fixture.debugElement.query(By.css('.lookup-search-button')).nativeElement; + expect(component.pidPin.valid).toBeFalsy(); + expect(button.disabled).toBeTruthy(); + }); + + it('should have search button enabled if pid is valid', () => { + component.parcelForm.controls.parcelType.setValue('SMPL'); + component.pidPin.setValue('123456789'); + fixture.detectChanges(); + + const button = fixture.debugElement.query(By.css('.lookup-search-button')).nativeElement; + expect(component.pidPin.valid).toBeTruthy(); + expect(!component.parcelType.getRawValue()).toBeFalsy(); + expect(!component.pidPin.getRawValue()).toBeFalsy(); + expect(component.pidPin.invalid).toBeFalsy(); + expect(button.disabled).toBeFalsy(); + }); }); describe('Crown', () => { @@ -239,5 +266,33 @@ describe('ParcelEntryComponent', () => { it('should not require certificate of title', () => { expect(component.isCertificateOfTitleRequired).toBeFalsy(); }); + + it('should have search button disabled if no pid', () => { + const button = fixture.debugElement.query(By.css('.lookup-search-button')).nativeElement; + expect(component.pidPin.getRawValue()).toBe(''); + expect(button.disabled).toBeTruthy(); + }); + + it('should have search button disabled if pid is invalid', () => { + component.pidPin.setErrors({ invalid: true }); + const button = fixture.debugElement.query(By.css('.lookup-search-button')).nativeElement; + expect(component.pidPin.valid).toBeFalsy(); + expect(button.disabled).toBeTruthy(); + }); + + it('should have search button enabled if pid is valid', () => { + component.isCrownLand = true; + component.searchBy.setValue('pid'); + component.parcelForm.controls.parcelType.setValue('CRWN'); + component.pidPin.setValue('123456789'); + + fixture.detectChanges(); + + const button = fixture.debugElement.query(By.css('.lookup-search-button')).nativeElement; + expect(!component.parcelType.getRawValue()).toBeFalsy(); + expect(!component.pidPin.getRawValue()).toBeFalsy(); + expect(component.pidPin.invalid).toBeFalsy(); + expect(button.disabled).toBeFalsy(); + }); }); }); diff --git a/portal-frontend/src/app/features/notifications/edit-submission/parcels/parcel-entry/parcel-entry.component.html b/portal-frontend/src/app/features/notifications/edit-submission/parcels/parcel-entry/parcel-entry.component.html index 6284e8f509..1813c23880 100644 --- a/portal-frontend/src/app/features/notifications/edit-submission/parcels/parcel-entry/parcel-entry.component.html +++ b/portal-frontend/src/app/features/notifications/edit-submission/parcels/parcel-entry/parcel-entry.component.html @@ -56,11 +56,22 @@ mat-flat-button color="primary" (click)="onSearch()" - [disabled]="!parcelType.getRawValue() || (!!isCrownLand && !searchBy.getRawValue())" + [disabled]="!parcelType.getRawValue() || pidPin.invalid || !pidPin.getRawValue()" > Search
+
+ warning +
Please provide all 9 numbers, including leading zeros
+
+ Type of soil approved to be removed + Type, origin and quality of fill approved to be placed diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-components/decision-component/pofo-input/pofo-input.component.html b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-components/decision-component/pofo-input/pofo-input.component.html index 7f5e2a3cb7..4b33f8722c 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-components/decision-component/pofo-input/pofo-input.component.html +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-components/decision-component/pofo-input/pofo-input.component.html @@ -28,13 +28,13 @@
+ Type, origin and quality of fill approved to be placed diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-components/decision-component/roso-input/roso-input.component.html b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-components/decision-component/roso-input/roso-input.component.html index 5109ab646a..de69effab1 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-components/decision-component/roso-input/roso-input.component.html +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-components/decision-component/roso-input/roso-input.component.html @@ -28,12 +28,12 @@
+ Type of soil approved to be removed diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/pfrs-input/pfrs-input.component.html b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/pfrs-input/pfrs-input.component.html index 417b19c5c1..d32065b36e 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/pfrs-input/pfrs-input.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/pfrs-input/pfrs-input.component.html @@ -40,22 +40,22 @@
+ Type of soil approved to be removed + Type, origin and quality of fill approved to be placed diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/pofo-input/pofo-input.component.html b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/pofo-input/pofo-input.component.html index 06eb57d13f..1968fcf56e 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/pofo-input/pofo-input.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/pofo-input/pofo-input.component.html @@ -28,12 +28,12 @@
+ Type, origin and quality of fill approved to be placed diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/roso-input/roso-input.component.html b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/roso-input/roso-input.component.html index 5109ab646a..de69effab1 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/roso-input/roso-input.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/roso-input/roso-input.component.html @@ -28,12 +28,12 @@ + Type of soil approved to be removed From 8622508b6a2197ddbdb27018b7c3da16b88fd1fa Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Tue, 16 Apr 2024 11:19:25 -0700 Subject: [PATCH 125/153] Change Additional Structures form to use ID instead of Index * Re-enable remove from any position * Switch everything from index to ID * Update tests --- portal-frontend/package-lock.json | 27 ++++++- portal-frontend/package.json | 2 + .../additional-information.component.html | 53 ++++++------- .../additional-information.component.spec.ts | 2 +- .../additional-information.component.ts | 79 +++++++++++-------- 5 files changed, 99 insertions(+), 64 deletions(-) diff --git a/portal-frontend/package-lock.json b/portal-frontend/package-lock.json index aef166c5f5..2e6d6a94a1 100644 --- a/portal-frontend/package-lock.json +++ b/portal-frontend/package-lock.json @@ -19,11 +19,13 @@ "@angular/platform-browser-dynamic": "^17.3.3", "@angular/router": "^17.3.3", "@bcgov/bc-sans": "^2.1.0", + "@types/uuid": "^9.0.8", "jwt-decode": "^4.0.0", "ngx-mask": "^17.0.7", "rxjs": "~7.8.1", "source-map-support": "^0.5.21", "tslib": "^2.6.2", + "uuid": "^9.0.1", "zone.js": "~0.14.4" }, "devDependencies": { @@ -6167,6 +6169,11 @@ "integrity": "sha512-95Sfz4nvMAb0Nl9DTxN3j64adfwfbBPEYq14VN7zT5J5O2M9V6iZMIIQU1U+pJyl9agHYHNCqhCXgyEtIRRa5A==", "dev": true }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==" + }, "node_modules/@types/ws": { "version": "8.5.10", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", @@ -15859,6 +15866,15 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/socks": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.1.tgz", @@ -16890,10 +16906,13 @@ } }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { "uuid": "dist/bin/uuid" } diff --git a/portal-frontend/package.json b/portal-frontend/package.json index b342a7c751..156ed95f22 100644 --- a/portal-frontend/package.json +++ b/portal-frontend/package.json @@ -24,11 +24,13 @@ "@angular/platform-browser-dynamic": "^17.3.3", "@angular/router": "^17.3.3", "@bcgov/bc-sans": "^2.1.0", + "@types/uuid": "^9.0.8", "jwt-decode": "^4.0.0", "ngx-mask": "^17.0.7", "rxjs": "~7.8.1", "source-map-support": "^0.5.21", "tslib": "^2.6.2", + "uuid": "^9.0.1", "zone.js": "~0.14.4" }, "devDependencies": { diff --git a/portal-frontend/src/app/features/notice-of-intents/edit-submission/additional-information/additional-information.component.html b/portal-frontend/src/app/features/notice-of-intents/edit-submission/additional-information/additional-information.component.html index 78c8608f3c..528aca4a34 100644 --- a/portal-frontend/src/app/features/notice-of-intents/edit-submission/additional-information/additional-information.component.html +++ b/portal-frontend/src/app/features/notice-of-intents/edit-submission/additional-information/additional-information.component.html @@ -17,7 +17,7 @@

Additional Proposal Information

Notice of Intent DocumentsNotice of Intent Documents @@ -43,7 +43,7 @@

Additional Proposal Information

(isRemovingSoilForNewStructure.dirty || isRemovingSoilForNewStructure.touched) }" value="true" - >Yes + >Yes Additional Proposal Information (isRemovingSoilForNewStructure.dirty || isRemovingSoilForNewStructure.touched) }" value="false" - >No + >No Note: The form will be updated with additional required questions if you are building a structure + >Note: The form will be updated with additional required questions if you are building a structure @@ -67,7 +67,7 @@

Additional Proposal Information

- Provide the total floor area (m2) of the proposed structure(s) + Provide the total floor area (m2) of the proposed structure(s)
Type +
+ + + + - - - + - - - + + + - - - + + + - + - - - - - + + + +
Type {{ element.type?.label }} Document Name - {{ element.fileName }} + + Document Name + {{ element.fileName }} Source{{ element.source }}Source{{ element.source }} Upload Date{{ element.uploadedAt | date }}Upload Date{{ element.uploadedAt | date }} Actions - +
Documents will be visible here once provided by ALC
Documents will be visible here once provided by ALC
diff --git a/portal-frontend/src/app/features/notifications/view-submission/alc-review/submission-documents/submission-documents.component.ts b/portal-frontend/src/app/features/notifications/view-submission/alc-review/submission-documents/submission-documents.component.ts index 505cf75369..bd045b8df6 100644 --- a/portal-frontend/src/app/features/notifications/view-submission/alc-review/submission-documents/submission-documents.component.ts +++ b/portal-frontend/src/app/features/notifications/view-submission/alc-review/submission-documents/submission-documents.component.ts @@ -4,7 +4,7 @@ import { MatTableDataSource } from '@angular/material/table'; import { BehaviorSubject, Subject, takeUntil } from 'rxjs'; import { NotificationDocumentDto } from '../../../../../services/notification-document/notification-document.dto'; import { NotificationDocumentService } from '../../../../../services/notification-document/notification-document.service'; -import { openFileIframe } from '../../../../../shared/utils/file'; +import { openFileInline } from '../../../../../shared/utils/file'; @Component({ selector: 'app-submission-documents', @@ -30,10 +30,10 @@ export class SubmissionDocumentsComponent implements OnInit, OnDestroy { }); } - async openFile(uuid: string) { - const res = await this.notificationDocumentService.openFile(uuid); + async openFile(file: NotificationDocumentDto) { + const res = await this.notificationDocumentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } diff --git a/portal-frontend/src/app/features/public/application/alc-review/decisions/decisions.component.html b/portal-frontend/src/app/features/public/application/alc-review/decisions/decisions.component.html index bcd9a775c9..3faa3101cb 100644 --- a/portal-frontend/src/app/features/public/application/alc-review/decisions/decisions.component.html +++ b/portal-frontend/src/app/features/public/application/alc-review/decisions/decisions.component.html @@ -20,21 +20,21 @@

Decision #{{ applicationDecisions.length - index }}

Decision Document
- {{ document.fileName }} + {{ document.fileName }}  ({{ document.fileSize | filesize }})
-
{{ document.fileSize | filesize }}
- diff --git a/portal-frontend/src/app/features/public/application/alc-review/decisions/decisions.component.ts b/portal-frontend/src/app/features/public/application/alc-review/decisions/decisions.component.ts index 2210cea9c3..ac7eee7179 100644 --- a/portal-frontend/src/app/features/public/application/alc-review/decisions/decisions.component.ts +++ b/portal-frontend/src/app/features/public/application/alc-review/decisions/decisions.component.ts @@ -1,6 +1,8 @@ import { Component, Input } from '@angular/core'; import { ApplicationPortalDecisionDto } from '../../../../../services/application-decision/application-decision.dto'; import { ApplicationDecisionService } from '../../../../../services/application-decision/application-decision.service'; +import { openFileInline } from '../../../../../shared/utils/file'; +import { ApplicationDocumentDto } from '../../../../../services/application-document/application-document.dto'; @Component({ selector: 'app-public-decisions', @@ -12,10 +14,10 @@ export class PublicDecisionsComponent { constructor(private decisionService: ApplicationDecisionService) {} - async openFile(uuid: string) { - const res = await this.decisionService.openFile(uuid); + async openFile(file: ApplicationDocumentDto) { + const res = await this.decisionService.openFile(file.uuid); if (res) { - window.open(res.url, '_blank'); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/features/public/application/alc-review/submission-documents/submission-documents.component.html b/portal-frontend/src/app/features/public/application/alc-review/submission-documents/submission-documents.component.html index 8dc409f48a..4b5d35bddf 100644 --- a/portal-frontend/src/app/features/public/application/alc-review/submission-documents/submission-documents.component.html +++ b/portal-frontend/src/app/features/public/application/alc-review/submission-documents/submission-documents.component.html @@ -1,43 +1,43 @@

Application Documents

-
- - - - diff --git a/portal-frontend/src/app/features/public/notice-of-intent/alc-review/submission-documents/submission-documents.component.ts b/portal-frontend/src/app/features/public/notice-of-intent/alc-review/submission-documents/submission-documents.component.ts index 321c2ab75b..cc7d5dda7b 100644 --- a/portal-frontend/src/app/features/public/notice-of-intent/alc-review/submission-documents/submission-documents.component.ts +++ b/portal-frontend/src/app/features/public/notice-of-intent/alc-review/submission-documents/submission-documents.component.ts @@ -5,6 +5,7 @@ import { Subject } from 'rxjs'; import { PublicNoticeOfIntentSubmissionDto } from '../../../../../services/public/public-notice-of-intent.dto'; import { PublicDocumentDto } from '../../../../../services/public/public.dto'; import { PublicService } from '../../../../../services/public/public.service'; +import { openFileInline } from '../../../../../shared/utils/file'; @Component({ selector: 'app-submission-documents', @@ -27,10 +28,10 @@ export class PublicSubmissionDocumentsComponent implements OnInit, OnDestroy { this.dataSource = new MatTableDataSource(this.documents); } - async openFile(uuid: string) { - const res = await this.publicService.getNoticeOfIntentOpenFileUrl(this.submission.fileNumber, uuid); + async openFile(file: PublicDocumentDto) { + const res = await this.publicService.getNoticeOfIntentOpenFileUrl(this.submission.fileNumber, file.uuid); if (res) { - window.open(res.url, '_blank'); + openFileInline(res.url, file.fileName); } } diff --git a/portal-frontend/src/app/features/public/notice-of-intent/submission/additional-information/additional-information.component.ts b/portal-frontend/src/app/features/public/notice-of-intent/submission/additional-information/additional-information.component.ts index a21f812bd0..7e72ef939c 100644 --- a/portal-frontend/src/app/features/public/notice-of-intent/submission/additional-information/additional-information.component.ts +++ b/portal-frontend/src/app/features/public/notice-of-intent/submission/additional-information/additional-information.component.ts @@ -8,6 +8,7 @@ import { RESIDENTIAL_STRUCTURE_TYPES, STRUCTURE_TYPES, } from '../../../../notice-of-intents/edit-submission/additional-information/additional-information.component'; +import { openFileInline } from '../../../../../shared/utils/file'; @Component({ selector: 'app-additional-information', @@ -70,8 +71,10 @@ export class AdditionalInformationComponent implements OnInit { ); } - async openFile(uuid: string) { - const res = await this.publicService.getNoticeOfIntentOpenFileUrl(this.noiSubmission.fileNumber, uuid); - window.open(res?.url, '_blank'); + async openFile(file: PublicDocumentDto) { + const res = await this.publicService.getNoticeOfIntentOpenFileUrl(this.noiSubmission.fileNumber, file.uuid); + if (res) { + openFileInline(res.url, file.fileName); + } } } diff --git a/portal-frontend/src/app/features/public/notice-of-intent/submission/pfrs-details/pfrs-details.component.html b/portal-frontend/src/app/features/public/notice-of-intent/submission/pfrs-details/pfrs-details.component.html index 367d047d9a..815fdaaef6 100644 --- a/portal-frontend/src/app/features/public/notice-of-intent/submission/pfrs-details/pfrs-details.component.html +++ b/portal-frontend/src/app/features/public/notice-of-intent/submission/pfrs-details/pfrs-details.component.html @@ -159,7 +159,7 @@
Proposal Map / Site Plan
- + {{ map.fileName }} @@ -184,7 +184,7 @@
Cross Sections
- + {{ file.fileName }} diff --git a/portal-frontend/src/app/features/public/notice-of-intent/submission/pfrs-details/pfrs-details.component.ts b/portal-frontend/src/app/features/public/notice-of-intent/submission/pfrs-details/pfrs-details.component.ts index 36e47a3c9c..d758bc38c3 100644 --- a/portal-frontend/src/app/features/public/notice-of-intent/submission/pfrs-details/pfrs-details.component.ts +++ b/portal-frontend/src/app/features/public/notice-of-intent/submission/pfrs-details/pfrs-details.component.ts @@ -4,6 +4,7 @@ import { PublicNoticeOfIntentSubmissionDto } from '../../../../../services/publi import { PublicDocumentDto } from '../../../../../services/public/public.dto'; import { PublicService } from '../../../../../services/public/public.service'; import { DOCUMENT_TYPE } from '../../../../../shared/dto/document.dto'; +import { openFileInline } from '../../../../../shared/utils/file'; @Component({ selector: 'app-pfrs-details[noiSubmission]', @@ -23,8 +24,10 @@ export class PfrsDetailsComponent { constructor(private router: Router, private publicService: PublicService) {} - async openFile(uuid: string) { - const res = await this.publicService.getNoticeOfIntentOpenFileUrl(this.noiSubmission.fileNumber, uuid); - window.open(res?.url, '_blank'); + async openFile(file: PublicDocumentDto) { + const res = await this.publicService.getNoticeOfIntentOpenFileUrl(this.noiSubmission.fileNumber, file.uuid); + if (res) { + openFileInline(res.url, file.fileName); + } } } diff --git a/portal-frontend/src/app/features/public/notice-of-intent/submission/pofo-details/pofo-details.component.html b/portal-frontend/src/app/features/public/notice-of-intent/submission/pofo-details/pofo-details.component.html index 00a51e94a8..5409a3b115 100644 --- a/portal-frontend/src/app/features/public/notice-of-intent/submission/pofo-details/pofo-details.component.html +++ b/portal-frontend/src/app/features/public/notice-of-intent/submission/pofo-details/pofo-details.component.html @@ -32,9 +32,7 @@
{{ noiSubmission.soilProjectDuration }}
- +
@@ -59,18 +57,14 @@
{{ noiSubmission.soilToPlaceMaximumDepth }} m - +
Average Depth
{{ noiSubmission.soilToPlaceAverageDepth }} m - +
@@ -80,42 +74,34 @@
{{ noiSubmission.soilAlreadyPlacedVolume }} m3 - +
Area
{{ noiSubmission.soilAlreadyPlacedArea }} m2 - +
Maximum Depth
{{ noiSubmission.soilAlreadyPlacedMaximumDepth }} m - +
Average Depth
{{ noiSubmission.soilAlreadyPlacedAverageDepth }} m - +
Proposal Map / Site Plan
- + {{ map.fileName }} @@ -132,7 +118,7 @@
Cross Sections
- + {{ file.fileName }} diff --git a/portal-frontend/src/app/features/public/notice-of-intent/submission/pofo-details/pofo-details.component.ts b/portal-frontend/src/app/features/public/notice-of-intent/submission/pofo-details/pofo-details.component.ts index 04a3e7a5cf..92b7c2b089 100644 --- a/portal-frontend/src/app/features/public/notice-of-intent/submission/pofo-details/pofo-details.component.ts +++ b/portal-frontend/src/app/features/public/notice-of-intent/submission/pofo-details/pofo-details.component.ts @@ -4,6 +4,7 @@ import { PublicNoticeOfIntentSubmissionDto } from '../../../../../services/publi import { PublicDocumentDto } from '../../../../../services/public/public.dto'; import { PublicService } from '../../../../../services/public/public.service'; import { DOCUMENT_TYPE } from '../../../../../shared/dto/document.dto'; +import { openFileInline } from '../../../../../shared/utils/file'; @Component({ selector: 'app-pofo-details[noiSubmission]', @@ -23,8 +24,10 @@ export class PofoDetailsComponent { constructor(private router: Router, private publicService: PublicService) {} - async openFile(uuid: string) { - const res = await this.publicService.getNoticeOfIntentOpenFileUrl(this.noiSubmission.fileNumber, uuid); - window.open(res?.url, '_blank'); + async openFile(file: PublicDocumentDto) { + const res = await this.publicService.getNoticeOfIntentOpenFileUrl(this.noiSubmission.fileNumber, file.uuid); + if (res) { + openFileInline(res.url, file.fileName); + } } } diff --git a/portal-frontend/src/app/features/public/notice-of-intent/submission/roso-details/roso-details.component.html b/portal-frontend/src/app/features/public/notice-of-intent/submission/roso-details/roso-details.component.html index 1dedd56018..7ff9ffeecd 100644 --- a/portal-frontend/src/app/features/public/notice-of-intent/submission/roso-details/roso-details.component.html +++ b/portal-frontend/src/app/features/public/notice-of-intent/submission/roso-details/roso-details.component.html @@ -32,9 +32,7 @@
{{ noiSubmission.soilProjectDuration }}
- +
@@ -59,18 +57,14 @@
{{ noiSubmission.soilToRemoveMaximumDepth }} m - +
Average Depth
{{ noiSubmission.soilToRemoveAverageDepth }} m - +
@@ -80,42 +74,34 @@
{{ noiSubmission.soilAlreadyRemovedVolume }} m3 - +
Area
{{ noiSubmission.soilAlreadyRemovedArea }} m2 - +
Maximum Depth
{{ noiSubmission.soilAlreadyRemovedMaximumDepth }} m - +
Average Depth
{{ noiSubmission.soilAlreadyRemovedAverageDepth }} m - +
Proposal Map / Site Plan
- + {{ map.fileName }} @@ -132,7 +118,7 @@
Cross Sections
- + {{ file.fileName }} diff --git a/portal-frontend/src/app/features/public/notice-of-intent/submission/roso-details/roso-details.component.ts b/portal-frontend/src/app/features/public/notice-of-intent/submission/roso-details/roso-details.component.ts index dbfba33f0b..38c80b0385 100644 --- a/portal-frontend/src/app/features/public/notice-of-intent/submission/roso-details/roso-details.component.ts +++ b/portal-frontend/src/app/features/public/notice-of-intent/submission/roso-details/roso-details.component.ts @@ -4,6 +4,7 @@ import { PublicNoticeOfIntentSubmissionDto } from '../../../../../services/publi import { PublicDocumentDto } from '../../../../../services/public/public.dto'; import { PublicService } from '../../../../../services/public/public.service'; import { DOCUMENT_TYPE } from '../../../../../shared/dto/document.dto'; +import { openFileInline } from '../../../../../shared/utils/file'; @Component({ selector: 'app-roso-details[noiSubmission]', @@ -23,8 +24,10 @@ export class RosoDetailsComponent { constructor(private router: Router, private publicService: PublicService) {} - async openFile(uuid: string) { - const res = await this.publicService.getNoticeOfIntentOpenFileUrl(this.noiSubmission.fileNumber, uuid); - window.open(res?.url, '_blank'); + async openFile(file: PublicDocumentDto) { + const res = await this.publicService.getNoticeOfIntentOpenFileUrl(this.noiSubmission.fileNumber, file.uuid); + if (res) { + openFileInline(res.url, file.fileName); + } } } diff --git a/portal-frontend/src/app/features/public/notification/alc-review/submission-documents/submission-documents.component.html b/portal-frontend/src/app/features/public/notification/alc-review/submission-documents/submission-documents.component.html index 50da3b831d..1d674b50c0 100644 --- a/portal-frontend/src/app/features/public/notification/alc-review/submission-documents/submission-documents.component.html +++ b/portal-frontend/src/app/features/public/notification/alc-review/submission-documents/submission-documents.component.html @@ -11,7 +11,7 @@

Notification Documents

diff --git a/portal-frontend/src/app/features/public/notification/alc-review/submission-documents/submission-documents.component.ts b/portal-frontend/src/app/features/public/notification/alc-review/submission-documents/submission-documents.component.ts index 0770fff841..43115f8e63 100644 --- a/portal-frontend/src/app/features/public/notification/alc-review/submission-documents/submission-documents.component.ts +++ b/portal-frontend/src/app/features/public/notification/alc-review/submission-documents/submission-documents.component.ts @@ -5,6 +5,7 @@ import { Subject } from 'rxjs'; import { PublicNotificationSubmissionDto } from '../../../../../services/public/public-notification.dto'; import { PublicDocumentDto } from '../../../../../services/public/public.dto'; import { PublicService } from '../../../../../services/public/public.service'; +import { openFileInline } from '../../../../../shared/utils/file'; @Component({ selector: 'app-submission-documents', @@ -27,10 +28,10 @@ export class PublicSubmissionDocumentsComponent implements OnInit, OnDestroy { this.dataSource = new MatTableDataSource(this.documents); } - async openFile(uuid: string) { - const res = await this.publicService.getNotificationOpenFileUrl(this.submission.fileNumber, uuid); + async openFile(file: PublicDocumentDto) { + const res = await this.publicService.getNotificationOpenFileUrl(this.submission.fileNumber, file.uuid); if (res) { - window.open(res.url, '_blank'); + openFileInline(res.url, file.fileName); } } diff --git a/portal-frontend/src/app/features/public/notification/submission/additional-information/additional-information.component.ts b/portal-frontend/src/app/features/public/notification/submission/additional-information/additional-information.component.ts index a21f812bd0..7e72ef939c 100644 --- a/portal-frontend/src/app/features/public/notification/submission/additional-information/additional-information.component.ts +++ b/portal-frontend/src/app/features/public/notification/submission/additional-information/additional-information.component.ts @@ -8,6 +8,7 @@ import { RESIDENTIAL_STRUCTURE_TYPES, STRUCTURE_TYPES, } from '../../../../notice-of-intents/edit-submission/additional-information/additional-information.component'; +import { openFileInline } from '../../../../../shared/utils/file'; @Component({ selector: 'app-additional-information', @@ -70,8 +71,10 @@ export class AdditionalInformationComponent implements OnInit { ); } - async openFile(uuid: string) { - const res = await this.publicService.getNoticeOfIntentOpenFileUrl(this.noiSubmission.fileNumber, uuid); - window.open(res?.url, '_blank'); + async openFile(file: PublicDocumentDto) { + const res = await this.publicService.getNoticeOfIntentOpenFileUrl(this.noiSubmission.fileNumber, file.uuid); + if (res) { + openFileInline(res.url, file.fileName); + } } } diff --git a/portal-frontend/src/app/services/application-document/application-document.service.ts b/portal-frontend/src/app/services/application-document/application-document.service.ts index e5dd88c9ae..b7b69eab01 100644 --- a/portal-frontend/src/app/services/application-document/application-document.service.ts +++ b/portal-frontend/src/app/services/application-document/application-document.service.ts @@ -51,9 +51,7 @@ export class ApplicationDocumentService { async openFile(fileUuid: string) { try { - return await firstValueFrom( - this.httpClient.get<{ url: string; fileName: string }>(`${this.serviceUrl}/${fileUuid}/open`) - ); + return await firstValueFrom(this.httpClient.get<{ url: string }>(`${this.serviceUrl}/${fileUuid}/open`)); } catch (e) { console.error(e); this.toastService.showErrorToast('Failed to open the document, please try again'); diff --git a/portal-frontend/src/app/services/notice-of-intent-document/notice-of-intent-document.service.ts b/portal-frontend/src/app/services/notice-of-intent-document/notice-of-intent-document.service.ts index 1860044eb3..40461754d6 100644 --- a/portal-frontend/src/app/services/notice-of-intent-document/notice-of-intent-document.service.ts +++ b/portal-frontend/src/app/services/notice-of-intent-document/notice-of-intent-document.service.ts @@ -51,9 +51,7 @@ export class NoticeOfIntentDocumentService { async openFile(fileUuid: string) { try { - return await firstValueFrom( - this.httpClient.get<{ url: string; fileName: string }>(`${this.serviceUrl}/${fileUuid}/open`) - ); + return await firstValueFrom(this.httpClient.get<{ url: string }>(`${this.serviceUrl}/${fileUuid}/open`)); } catch (e) { console.error(e); this.toastService.showErrorToast('Failed to open the document, please try again'); diff --git a/portal-frontend/src/app/services/notification-document/notification-document.service.ts b/portal-frontend/src/app/services/notification-document/notification-document.service.ts index e94af3b6d5..698ecbae19 100644 --- a/portal-frontend/src/app/services/notification-document/notification-document.service.ts +++ b/portal-frontend/src/app/services/notification-document/notification-document.service.ts @@ -51,9 +51,7 @@ export class NotificationDocumentService { async openFile(fileUuid: string) { try { - return await firstValueFrom( - this.httpClient.get<{ url: string; fileName: string }>(`${this.serviceUrl}/${fileUuid}/open`) - ); + return await firstValueFrom(this.httpClient.get<{ url: string }>(`${this.serviceUrl}/${fileUuid}/open`)); } catch (e) { console.error(e); this.toastService.showErrorToast('Failed to open the document, please try again'); diff --git a/portal-frontend/src/app/shared/file-drag-drop/file-drag-drop.component.html b/portal-frontend/src/app/shared/file-drag-drop/file-drag-drop.component.html index b1e5905ec3..6a2efffad4 100644 --- a/portal-frontend/src/app/shared/file-drag-drop/file-drag-drop.component.html +++ b/portal-frontend/src/app/shared/file-drag-drop/file-drag-drop.component.html @@ -7,7 +7,7 @@ error: !!file.errorMessage && !disabled }" > - {{ file.fileName }} + {{ file.fileName }}
( diff --git a/portal-frontend/src/app/shared/file-drag-drop/file-drag-drop.component.ts b/portal-frontend/src/app/shared/file-drag-drop/file-drag-drop.component.ts index b7fc499990..0acc17688c 100644 --- a/portal-frontend/src/app/shared/file-drag-drop/file-drag-drop.component.ts +++ b/portal-frontend/src/app/shared/file-drag-drop/file-drag-drop.component.ts @@ -11,7 +11,7 @@ import { FileHandle } from './drag-drop.directive'; export class FileDragDropComponent implements OnInit { @Output() uploadFiles: EventEmitter = new EventEmitter(); @Output() deleteFile: EventEmitter = new EventEmitter(); - @Output() openFile: EventEmitter = new EventEmitter(); + @Output() openFile: EventEmitter = new EventEmitter(); @Output() beforeFileUploadOpened: EventEmitter = new EventEmitter(); @Input() allowMultiple = false; @@ -46,8 +46,8 @@ export class FileDragDropComponent implements OnInit { this.uploadFiles.emit($event); } - fileOpened(uuid: string) { - this.openFile.emit(uuid); + fileOpened(file: ApplicationDocumentDto) { + this.openFile.emit(file); } onFileUploadClicked() { diff --git a/portal-frontend/src/app/shared/owner-dialogs/owner-dialog/owner-dialog.component.ts b/portal-frontend/src/app/shared/owner-dialogs/owner-dialog/owner-dialog.component.ts index ca87800bdc..f4403ac5f7 100644 --- a/portal-frontend/src/app/shared/owner-dialogs/owner-dialog/owner-dialog.component.ts +++ b/portal-frontend/src/app/shared/owner-dialogs/owner-dialog/owner-dialog.component.ts @@ -19,6 +19,7 @@ import { ConfirmationDialogService } from '../../confirmation-dialog/confirmatio import { DOCUMENT_SOURCE, DOCUMENT_TYPE, DocumentTypeDto } from '../../dto/document.dto'; import { OWNER_TYPE } from '../../dto/owner.dto'; import { FileHandle } from '../../file-drag-drop/drag-drop.directive'; +import { openFileInline } from '../../utils/file'; @Component({ selector: 'app-owner-dialog', @@ -240,11 +241,11 @@ export class OwnerDialogComponent { async openCorporateSummary() { if (this.pendingFile) { const fileURL = URL.createObjectURL(this.pendingFile); - window.open(fileURL, '_blank'); + openFileInline(fileURL, this.pendingFile.name); } else if (this.existingUuid && this.data.existingOwner?.corporateSummary?.uuid) { const res = await this.data.documentService.openFile(this.data.existingOwner?.corporateSummary?.uuid); if (res) { - window.open(res.url, '_blank'); + openFileInline(res.url, this.data.existingOwner?.corporateSummary?.fileName); } } } diff --git a/portal-frontend/src/app/shared/owner-dialogs/parcel-owners/parcel-owners.component.html b/portal-frontend/src/app/shared/owner-dialogs/parcel-owners/parcel-owners.component.html index cbe7737996..5efc61f9de 100644 --- a/portal-frontend/src/app/shared/owner-dialogs/parcel-owners/parcel-owners.component.html +++ b/portal-frontend/src/app/shared/owner-dialogs/parcel-owners/parcel-owners.component.html @@ -38,7 +38,7 @@
([]); overlayRef: OverlayRef | null = null; + selectedRecord: string | undefined; constructor( private planningReviewDocumentService: PlanningReviewDocumentService, @@ -62,6 +63,7 @@ export class EvidentiaryRecordComponent implements OnChanges { if (!this.sortable) { return; } + this.selectedRecord = record.uuid; this.overlayRef?.detach(); $event.preventDefault(); const positionStrategy = this.overlay @@ -92,12 +94,19 @@ export class EvidentiaryRecordComponent implements OnChanges { const currentIndex = this.documents.findIndex((item) => item.uuid === record.uuid); this.moveItem(currentIndex, this.documents.length - 1); this.overlayRef?.detach(); + this.selectedRecord = undefined; } sendToTop(record: ApplicationDocumentDto) { const currentIndex = this.documents.findIndex((item) => item.uuid === record.uuid); this.moveItem(currentIndex, 0); this.overlayRef?.detach(); + this.selectedRecord = undefined; + } + + clearMenu() { + this.overlayRef?.detach(); + this.selectedRecord = undefined; } private moveItem(currentIndex: number, targetIndex: number) { diff --git a/alcs-frontend/src/app/shared/application-document/application-document.component.html b/alcs-frontend/src/app/shared/application-document/application-document.component.html index b6928902a4..85d323e5e9 100644 --- a/alcs-frontend/src/app/shared/application-document/application-document.component.html +++ b/alcs-frontend/src/app/shared/application-document/application-document.component.html @@ -70,7 +70,12 @@

{{ tableTitle }}

([]); overlayRef: OverlayRef | null = null; + selectedRecord: string | undefined; constructor( private applicationDocumentService: ApplicationDocumentService, @@ -63,6 +64,7 @@ export class ApplicationDocumentComponent implements OnChanges { } this.overlayRef?.detach(); $event.preventDefault(); + this.selectedRecord = record.uuid; const positionStrategy = this.overlay .position() .flexibleConnectedTo({ x: $event.x, y: $event.y }) @@ -91,12 +93,19 @@ export class ApplicationDocumentComponent implements OnChanges { const currentIndex = this.documents.findIndex((item) => item.uuid === record.uuid); this.moveItem(currentIndex, this.documents.length - 1); this.overlayRef?.detach(); + this.selectedRecord = undefined; } sendToTop(record: ApplicationDocumentDto) { const currentIndex = this.documents.findIndex((item) => item.uuid === record.uuid); this.moveItem(currentIndex, 0); this.overlayRef?.detach(); + this.selectedRecord = undefined; + } + + clearMenu() { + this.overlayRef?.detach(); + this.selectedRecord = undefined; } private moveItem(currentIndex: number, targetIndex: number) { From 3785d5f1f037878b582e90decd66017024cd7fdc Mon Sep 17 00:00:00 2001 From: Liam Stoddard Date: Wed, 3 Apr 2024 14:36:09 -0700 Subject: [PATCH 089/153] init planning reviews --- .../common/alcs_planning_review_enum.py | 7 + .../oats_to_alcs_planning_review_type.py | 12 +- .../planning_review/decisions/__init__.py | 4 + .../init_planning_review_decisions.py | 188 ++++++++++++++++++ .../migrate_planning_review.py | 4 +- .../referrals/planning_review_card_update.py | 2 +- .../planning_review_decisions_insert.sql | 11 + ...planning_review_decisions_insert_count.sql | 5 + 8 files changed, 230 insertions(+), 3 deletions(-) create mode 100644 bin/migrate-oats-data/planning_review/decisions/__init__.py create mode 100644 bin/migrate-oats-data/planning_review/decisions/init_planning_review_decisions.py create mode 100644 bin/migrate-oats-data/planning_review/sql/decisions/planning_review_decisions_insert.sql create mode 100644 bin/migrate-oats-data/planning_review/sql/decisions/planning_review_decisions_insert_count.sql diff --git a/bin/migrate-oats-data/common/alcs_planning_review_enum.py b/bin/migrate-oats-data/common/alcs_planning_review_enum.py index 790f47eb14..9e76a36ca7 100644 --- a/bin/migrate-oats-data/common/alcs_planning_review_enum.py +++ b/bin/migrate-oats-data/common/alcs_planning_review_enum.py @@ -13,3 +13,10 @@ class AlcsPlanningReviewTypes(Enum): UEPP = "Utility/Energy Planning" ZBPP = "Zoning Bylaw" PARK = "Parks Planning" + + +class AlcsPlanningReviewOutcomes(Enum): + ENDO = "Endorsed" + NEND = "Not Endorsed" + PEND = "Partially Endorsed" + OTHR = "Other" diff --git a/bin/migrate-oats-data/common/oats_to_alcs_planning_review_type.py b/bin/migrate-oats-data/common/oats_to_alcs_planning_review_type.py index bbdcfeeffe..2c130a92a5 100644 --- a/bin/migrate-oats-data/common/oats_to_alcs_planning_review_type.py +++ b/bin/migrate-oats-data/common/oats_to_alcs_planning_review_type.py @@ -1,5 +1,8 @@ from enum import Enum -from .alcs_planning_review_enum import AlcsPlanningReviewTypes +from .alcs_planning_review_enum import ( + AlcsPlanningReviewTypes, + AlcsPlanningReviewOutcomes, +) class OatsToAlcsPlanningReviewType(Enum): @@ -15,3 +18,10 @@ class OatsToAlcsPlanningReviewType(Enum): UTIL = "UEPP" AAP = "AAPP" APC = "MISC" + + +class OatsToAlcsDecisionOutcomes(Enum): + END = AlcsPlanningReviewOutcomes.ENDO + NOTEND = AlcsPlanningReviewOutcomes.NEND + PART = AlcsPlanningReviewOutcomes.PEND + UNC = AlcsPlanningReviewOutcomes.OTHR diff --git a/bin/migrate-oats-data/planning_review/decisions/__init__.py b/bin/migrate-oats-data/planning_review/decisions/__init__.py new file mode 100644 index 0000000000..b8e3524d4a --- /dev/null +++ b/bin/migrate-oats-data/planning_review/decisions/__init__.py @@ -0,0 +1,4 @@ +from .init_planning_review_decisions import ( + clean_planning_decisions, + process_planning_review_decisions, +) diff --git a/bin/migrate-oats-data/planning_review/decisions/init_planning_review_decisions.py b/bin/migrate-oats-data/planning_review/decisions/init_planning_review_decisions.py new file mode 100644 index 0000000000..be5eaf6d70 --- /dev/null +++ b/bin/migrate-oats-data/planning_review/decisions/init_planning_review_decisions.py @@ -0,0 +1,188 @@ +from common import ( + BATCH_UPLOAD_SIZE, + OATS_ETL_USER, + setup_and_get_logger, + add_timezone_and_keep_date_part, + OatsToAlcsDecisionOutcomes, + AlcsPlanningReviewOutcomes, +) +from db import inject_conn_pool +from psycopg2.extras import RealDictCursor, execute_batch +from datetime import datetime + +etl_name = "init_planning_review_decision" +logger = setup_and_get_logger(etl_name) + + +@inject_conn_pool +def process_planning_review_decisions(conn=None, batch_size=BATCH_UPLOAD_SIZE): + """ + This function is responsible for populating alcs.planning_referral in ALCS. + + Args: + conn (psycopg2.extensions.connection): PostgreSQL database connection. Provided by the decorator. + batch_size (int): The number of items to process at once. Defaults to BATCH_UPLOAD_SIZE. + """ + + logger.info("Start insert planning decision fields") + with conn.cursor(cursor_factory=RealDictCursor) as cursor: + with open( + "planning_review/sql/decisions/planning_review_decisions_insert_count.sql", + "r", + encoding="utf-8", + ) as sql_file: + count_query = sql_file.read() + cursor.execute(count_query) + count_total = dict(cursor.fetchone())["count"] + logger.info(f"Total Planning decision data to insert: {count_total}") + + failed_inserts_count = 0 + successful_inserts_count = 0 + last_planning_review_id = 0 + + with open( + "planning_review/sql/decisions/planning_review_decisions_insert.sql", + "r", + encoding="utf-8", + ) as sql_file: + query = sql_file.read() + while True: + cursor.execute( + f""" + {query} + WHERE opd.planning_decision_id > {last_planning_review_id} ORDER BY opd.planning_decision_id; + """ + ) + + rows = cursor.fetchmany(batch_size) + + if not rows: + break + try: + inserted_data = _insert_base_fields(conn, batch_size, cursor, rows) + + successful_inserts_count = successful_inserts_count + len( + inserted_data + ) + last_planning_review_id = dict(inserted_data[-1])[ + "planning_decision_id" + ] + + logger.debug( + f"Retrieved/updated items count: {len(inserted_data)}; total successfully inserted planning referral so far {successful_inserts_count}; last updated planning_decision_id: {last_planning_review_id}" + ) + except Exception as err: + # this is NOT going to be caused by actual data insert failure. This code is only executed when the code error appears or connection to DB is lost + logger.exception(err) + conn.rollback() + failed_inserts_count = count_total - successful_inserts_count + last_planning_review_id = last_planning_review_id + 1 + + logger.info( + f"Finished {etl_name}: total amount of successful inserts {successful_inserts_count}, total failed inserts {failed_inserts_count}" + ) + + +def _insert_base_fields(conn, batch_size, cursor, rows): + parsed_data_list = _prepare_oats_planning_review_data(rows) + + execute_batch( + cursor, + query, + parsed_data_list, + page_size=batch_size, + ) + + conn.commit() + return parsed_data_list + + +query = f""" + INSERT INTO alcs.planning_review_decision ( + planning_review_uuid, + decision_description, + audit_created_by, + was_released, + outcome_code, + resolution_number, + resolution_year, + date + ) + VALUES ( + %(review_uuid)s, + %(description)s, + '{OATS_ETL_USER}', + %(was_released)s, + %(outcome_code)s, + %(resolution_number)s, + %(resolution_year)s, + %(date)s + ) +""" + + +def _prepare_oats_planning_review_data(row_data_list): + mapped_data_list = [] + for row in row_data_list: + mapped_data_list.append( + { + "planning_decision_id": row["planning_decision_id"], + "review_uuid": row["uuid"], + "description": row["description"], + "date": _map_date(row), + "was_released": True, + "outcome_code": _map_outcome_code(row), + "resolution_number": row["resolution_number"], + "resolution_year": _map_resolution_year(row), + } + ) + + return mapped_data_list + + +def _map_date(data): + date = data.get("decision_date", "") + d_date = add_timezone_and_keep_date_part(date) + return d_date + + +def _map_outcome_code(data): + oats_code = data.get("planning_acceptance_code", "") + try: + alcs_name = OatsToAlcsDecisionOutcomes[oats_code].value.value + for outcome in AlcsPlanningReviewOutcomes: + if outcome.value == alcs_name: + return outcome.name + except KeyError: + file_number = data.get("planning_decision_id") + logger.info( + f"Key Error for{file_number}, no match to {oats_code} in ALCS, Override to OTHR" + ) + return "OTHR" + + +def _map_resolution_year(data): + full_date = data.get("decision_date", "") + if full_date is None: + return None + if isinstance(full_date, str): + date_object = datetime.strptime(full_date, "%Y-%m-%d %H:%M:%S.%f") + else: + date_object = full_date + year = date_object.year + return year + # date_object = datetime.strptime(full_date, "%Y-%m-%d %H:%M:%S.%f") + # year = date_object.year + # return year + + +@inject_conn_pool +def clean_planning_decisions(conn=None): + logger.info("Start planning_decision cleaning") + with conn.cursor() as cursor: + cursor.execute( + f"DELETE FROM alcs.planning_review_decision nos WHERE nos.audit_created_by = '{OATS_ETL_USER}' and nos.audit_updated_by is NULL" + ) + logger.info(f"Deleted items count = {cursor.rowcount}") + + conn.commit() diff --git a/bin/migrate-oats-data/planning_review/migrate_planning_review.py b/bin/migrate-oats-data/planning_review/migrate_planning_review.py index d6ae76bac9..482d682a30 100644 --- a/bin/migrate-oats-data/planning_review/migrate_planning_review.py +++ b/bin/migrate-oats-data/planning_review/migrate_planning_review.py @@ -14,6 +14,7 @@ clean_planning_review_cards, update_planning_review_cards, ) +from .decisions import process_planning_review_decisions, clean_planning_decisions def process_planning_review(batch_size): @@ -26,10 +27,11 @@ def process_planning_review(batch_size): process_planning_review_referral(batch_size) # planning review cards are updated to remove pr.uuid from audit_updated_by column as they are now matched by referrals update_planning_review_cards(batch_size) + process_planning_review_decisions(batch_size) def clean_planning_review(): clean_planning_review_staff_journal() clean_planning_referrals() - clean_planning_review_cards() + clean_planning_decisions() clean_initial_planning_review() diff --git a/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py b/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py index 3e72f14ca3..26276f7cd9 100644 --- a/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py +++ b/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py @@ -95,7 +95,7 @@ def _update_base_fields(conn, batch_size, cursor, rows): _rx_items_query = """ UPDATE alcs.card SET board_uuid = %(board_uuid)s, - audit_updated_by = %(audit_updated_by)s, + audit_updated_by = %(audit_updated_by)s WHERE alcs.card.uuid = %(uuid)s """ diff --git a/bin/migrate-oats-data/planning_review/sql/decisions/planning_review_decisions_insert.sql b/bin/migrate-oats-data/planning_review/sql/decisions/planning_review_decisions_insert.sql new file mode 100644 index 0000000000..cbd40c3352 --- /dev/null +++ b/bin/migrate-oats-data/planning_review/sql/decisions/planning_review_decisions_insert.sql @@ -0,0 +1,11 @@ +SELECT + opd.when_created, + opd.decision_date, + opd.description, + opd.planning_acceptance_code, + pr."uuid", + opd.resolution_number, + opd.planning_decision_id +FROM + oats.oats_planning_decisions opd + JOIN alcs.planning_review pr ON opd.planning_review_id::TEXT = pr.file_number \ No newline at end of file diff --git a/bin/migrate-oats-data/planning_review/sql/decisions/planning_review_decisions_insert_count.sql b/bin/migrate-oats-data/planning_review/sql/decisions/planning_review_decisions_insert_count.sql new file mode 100644 index 0000000000..054eebc737 --- /dev/null +++ b/bin/migrate-oats-data/planning_review/sql/decisions/planning_review_decisions_insert_count.sql @@ -0,0 +1,5 @@ +SELECT + COUNT(*) +FROM + oats.oats_planning_decisions opd + JOIN alcs.planning_review pr ON opd.planning_review_id::TEXT = pr.file_number \ No newline at end of file From 2a6aa22ed5c343909b782a43ca0ffebe220536ea Mon Sep 17 00:00:00 2001 From: Liam Stoddard Date: Wed, 3 Apr 2024 14:54:12 -0700 Subject: [PATCH 090/153] bugfixes to cards --- .../planning_review/migrate_planning_review.py | 1 + .../planning_review/referrals/planning_review_card_init.py | 2 +- .../planning_review/referrals/planning_review_card_update.py | 4 +++- .../sql/referrals/update_planning_review_cards.sql | 3 ++- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bin/migrate-oats-data/planning_review/migrate_planning_review.py b/bin/migrate-oats-data/planning_review/migrate_planning_review.py index 482d682a30..5ec87537c1 100644 --- a/bin/migrate-oats-data/planning_review/migrate_planning_review.py +++ b/bin/migrate-oats-data/planning_review/migrate_planning_review.py @@ -35,3 +35,4 @@ def clean_planning_review(): clean_planning_referrals() clean_planning_decisions() clean_initial_planning_review() + clean_planning_review_cards() diff --git a/bin/migrate-oats-data/planning_review/referrals/planning_review_card_init.py b/bin/migrate-oats-data/planning_review/referrals/planning_review_card_init.py index 8d7a1ae6a1..36225baacd 100644 --- a/bin/migrate-oats-data/planning_review/referrals/planning_review_card_init.py +++ b/bin/migrate-oats-data/planning_review/referrals/planning_review_card_init.py @@ -137,7 +137,7 @@ def clean_planning_review_cards(conn=None): logger.info("Start card cleaning") with conn.cursor() as cursor: cursor.execute( - f"DELETE FROM alcs.card nos WHERE nos.audit_created_by = '{OATS_ETL_USER}' and nos.audit_updated_by is NULL" + f"DELETE FROM alcs.card nos WHERE nos.audit_created_by = '{OATS_ETL_USER}' AND nos.audit_updated_by is NULL AND nos.type_code = 'PLAN'" ) logger.info(f"Deleted items count = {cursor.rowcount}") diff --git a/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py b/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py index 26276f7cd9..96e4f231c7 100644 --- a/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py +++ b/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py @@ -95,7 +95,8 @@ def _update_base_fields(conn, batch_size, cursor, rows): _rx_items_query = """ UPDATE alcs.card SET board_uuid = %(board_uuid)s, - audit_updated_by = %(audit_updated_by)s + audit_updated_by = %(audit_updated_by)s, + audit_deleted_date_at = %(deleted_date)s WHERE alcs.card.uuid = %(uuid)s """ @@ -108,6 +109,7 @@ def _prepare_oats_planning_review_data(row_data_list): "uuid": row["uuid"], "board_uuid": "e7b18852-4f8f-419e-83e3-60e706b4a494", "audit_updated_by": None, + "deleted_date": row["audit_created_at"], } ) diff --git a/bin/migrate-oats-data/planning_review/sql/referrals/update_planning_review_cards.sql b/bin/migrate-oats-data/planning_review/sql/referrals/update_planning_review_cards.sql index dee7cca035..51c7872340 100644 --- a/bin/migrate-oats-data/planning_review/sql/referrals/update_planning_review_cards.sql +++ b/bin/migrate-oats-data/planning_review/sql/referrals/update_planning_review_cards.sql @@ -1,7 +1,8 @@ SELECT ac."uuid", pr2.open, - ac.status_code + ac.status_code, + ac.audit_created_at FROM alcs.planning_referral pr JOIN alcs.card ac ON pr.card_uuid = ac."uuid" From b4a0a392db60941104e19c957ff010ef3824942c Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Wed, 3 Apr 2024 14:56:12 -0700 Subject: [PATCH 091/153] Pass req instead of user entity to get function * Woops! --- .../apps/alcs/src/alcs/notification/notification.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/apps/alcs/src/alcs/notification/notification.controller.ts b/services/apps/alcs/src/alcs/notification/notification.controller.ts index 6e51749531..016b6b9536 100644 --- a/services/apps/alcs/src/alcs/notification/notification.controller.ts +++ b/services/apps/alcs/src/alcs/notification/notification.controller.ts @@ -131,7 +131,7 @@ export class NotificationController { ); } - return this.get(fileNumber, req.user.entity); + return this.get(fileNumber, req); } @Get('/search/:fileNumber') From 9a0ed6662c650a3a4ffde883e17c9f3784a57233 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Wed, 3 Apr 2024 15:15:55 -0700 Subject: [PATCH 092/153] Update coverage rules * Exclude DTOs, Modules, and Entities * Exclude Command and Import Folder (Ansley said to keep not delete) --- .codeclimate.yml | 5 +++++ services/package.json | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.codeclimate.yml b/.codeclimate.yml index 7eb36b5db7..49516a960b 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -45,3 +45,8 @@ exclude_patterns: - "**/migrations/" - "**/*.spec.ts" - "**/*.d.ts" + - "**/*.dto.ts" + - "**/*.module.ts", + - "**/commands/" + - "**/import/" + - "**/*.entity.ts" diff --git a/services/package.json b/services/package.json index f3c4826831..5806ddadd1 100644 --- a/services/package.json +++ b/services/package.json @@ -124,7 +124,12 @@ "!**/*.module.ts", "!**/main.ts", "!**/mockTypes.ts", - "!**/*orm.config.ts" + "!**/*orm.config.ts", + "!**/commands/**", + "!**/test/**", + "!**/import/**", + "!**/*.dto.ts", + "!**/*.entity.ts" ], "coverageDirectory": "./coverage", "testEnvironment": "node", From b44be42186e8113e8fc008dd50856de43abbd4dd Mon Sep 17 00:00:00 2001 From: mhuseinov <61513701+mhuseinov@users.noreply.github.com> Date: Wed, 3 Apr 2024 15:19:13 -0700 Subject: [PATCH 093/153] fix pgtap test (#1573) --- .../apps/alcs/test/pgtap/test_calculate_paused_time.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/apps/alcs/test/pgtap/test_calculate_paused_time.sql b/services/apps/alcs/test/pgtap/test_calculate_paused_time.sql index 8028099d54..651e8debd6 100644 --- a/services/apps/alcs/test/pgtap/test_calculate_paused_time.sql +++ b/services/apps/alcs/test/pgtap/test_calculate_paused_time.sql @@ -22,8 +22,8 @@ SELECT lives_ok('insert_users_in_test_calculate_paused', 'should insert user'); -- create application type prepare insert_application_type_in_test_calculate_paused AS -INSERT INTO alcs.application_type (audit_deleted_date_at,audit_created_at,audit_updated_at,audit_created_by,audit_updated_by,code,description,"label",short_label,background_color,text_color) VALUES - (NULL,'2022-08-02 16:20:41.717',NULL,'alcs-api',NULL,'UNITTEST','UNITTEST','UNITTEST','Fill','#b2ff59','#000'); +INSERT INTO alcs.application_type (audit_deleted_date_at,audit_created_at,audit_updated_at,audit_created_by,audit_updated_by,code,description,"label",short_label,background_color,text_color, portal_order) VALUES + (NULL,'2022-08-02 16:20:41.717',NULL,'alcs-api',NULL,'UNITTEST','UNITTEST','UNITTEST','Fill','#b2ff59','#000',0); SELECT lives_ok('insert_application_type_in_test_calculate_paused', 'should insert application_type'); -- create a region @@ -35,7 +35,7 @@ SELECT lives_ok('insert_application_region_in_test_calculate_paused', 'should in prepare insert_application_local_government_in_test_calculate_paused as INSERT INTO alcs.local_government (uuid,audit_deleted_date_at,audit_created_at,audit_updated_at,audit_created_by,audit_updated_by,"name",preferred_region_code) VALUES ('11111111-1111-1111-1111-111111111111',NULL,'2022-09-29 16:28:39.371', NULL,'unit_test',NULL,'Village of Mock','TEST'); -SELECT lives_ok('insert_local_government_in_test_calculate_paused', 'should insert local_government'); +SELECT lives_ok('insert_application_local_government_in_test_calculate_paused', 'should insert local_government'); -- create application status prepare insert_card_status_in_test_calculate_paused AS From 903dc27d234ca04783c8a5fdd36eaba87a0dc90d Mon Sep 17 00:00:00 2001 From: mhuseinov <61513701+mhuseinov@users.noreply.github.com> Date: Wed, 3 Apr 2024 15:19:43 -0700 Subject: [PATCH 094/153] migrations to create cards for applications and nois (#1570) --- ...159113804-create-cards-for-applications.ts | 79 +++++++++++++++++++ .../1712164553675-create-cards-for-nois.ts | 78 ++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1712159113804-create-cards-for-applications.ts create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1712164553675-create-cards-for-nois.ts diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1712159113804-create-cards-for-applications.ts b/services/apps/alcs/src/providers/typeorm/migrations/1712159113804-create-cards-for-applications.ts new file mode 100644 index 0000000000..100e38b86b --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1712159113804-create-cards-for-applications.ts @@ -0,0 +1,79 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class CreateCardsForApplications1712159113804 + implements MigrationInterface +{ + public async up(queryRunner: QueryRunner): Promise { + // create backup table + queryRunner.query(`CREATE TABLE public.backup_application_with_etl_created_cards ( + file_number TEXT PRIMARY KEY, + audit_created_by TEXT, + board_uuid UUID, + status_code TEXT, + type_code TEXT); + `); + queryRunner.query( + `COMMENT ON TABLE public.backup_application_with_etl_created_cards IS 'This is a backup table for applications that had cards created by ETL. Delete once confirmed that migration is successful.';`, + ); + + // populate backup table with applications that will have cards created for them + queryRunner.query(` + INSERT INTO public.backup_application_with_etl_created_cards (audit_created_by, board_uuid, status_code, type_code, file_number) + WITH single_status AS + ( select alcs.application_submission_to_submission_status.submission_uuid, + MAX(alcs.application_submission_status_type.weight) as Max_Status_Weight + from alcs.application_submission_to_submission_status + left join alcs.application_submission_status_type on alcs.application_submission_status_type.code = alcs.application_submission_to_submission_status.status_type_code + where alcs.application_submission_to_submission_status.effective_date IS NOT NULL + AND alcs.application_submission_status_type.weight >= 5 + group by alcs.application_submission_to_submission_status.submission_uuid + ), + application_with_card_fields AS ( + select + 'oats_etl' AS audit_created_by, -- default etl user + 'bb70eb85-6250-49b9-9a5c-e3c2e0b9f3a2'::uuid AS board_uuid, -- vetting board + 'SUBM' AS status_code, -- first column of vetting board + 'APP' AS type_code, -- application type + alcs.application.file_number AS external_ref -- this is required to + + from alcs.application + left join alcs.application_region ON region_code = alcs.application_region.code + left join alcs.local_government ON alcs.local_government.uuid = alcs.application.local_government_uuid + left join alcs.application_type ON alcs.application_type.code = alcs.application.type_code + left join alcs.application_submission ON alcs.application.file_number = alcs.application_submission.file_number + left join single_status ON alcs.application_submission.uuid = single_status.submission_uuid + left join alcs.application_submission_status_type ON single_status.Max_Status_Weight = alcs.application_submission_status_type.weight + where alcs.application.date_submitted_to_alc IS NOT NULL + and alcs.application.date_submitted_to_alc >= timestamp with time zone '2020-01-01 00:00:00.000Z' + AND alcs.application_submission.is_draft = FALSE and alcs.application_submission_status_type.label <> 'Cancelled' + and alcs.application_submission_status_type.label <> 'Decision Released' + and alcs.application.card_uuid IS NULL + order by alcs.application.file_number) + SELECT * FROM application_with_card_fields; + `); + + // create temp column on cards that links application to card + queryRunner.query(`ALTER TABLE alcs.card ADD COLUMN external_ref TEXT;`); + + // create cards for applications + queryRunner.query(` + INSERT INTO alcs.card (audit_created_by, board_uuid, status_code, type_code, external_ref) + SELECT audit_created_by, board_uuid, status_code, type_code, file_number FROM public.backup_application_with_etl_created_cards; + `); + + // link cards to applications + queryRunner.query(` + UPDATE alcs.application + SET card_uuid = alcs.card.uuid + FROM alcs.card + WHERE alcs.application.file_number = alcs.card.external_ref AND alcs.card.external_ref IS NOT NULL; + `); + + // drop temp column + queryRunner.query(`ALTER TABLE alcs.card DROP COLUMN external_ref;`); + } + + public async down(): Promise { + // nope + } +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1712164553675-create-cards-for-nois.ts b/services/apps/alcs/src/providers/typeorm/migrations/1712164553675-create-cards-for-nois.ts new file mode 100644 index 0000000000..73bb1a6f36 --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1712164553675-create-cards-for-nois.ts @@ -0,0 +1,78 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class CreateCardsForNois1712164553675 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + // create backup table + queryRunner.query(`CREATE TABLE public.backup_noi_with_etl_created_cards ( + file_number TEXT PRIMARY KEY, + audit_created_by TEXT, + board_uuid UUID, + status_code TEXT, + type_code TEXT); + `); + queryRunner.query( + `COMMENT ON TABLE public.backup_noi_with_etl_created_cards IS 'This is a backup table for NOIs that had cards created by ETL. Delete once confirmed that migration is successful.';`, + ); + + // populate backup table with NOIs that will have cards created for them + queryRunner.query(` + INSERT INTO public.backup_noi_with_etl_created_cards (audit_created_by, board_uuid, status_code, type_code, file_number) + WITH single_status AS + ( select alcs.notice_of_intent_submission_to_submission_status.submission_uuid, + MAX(alcs.notice_of_intent_submission_status_type.weight) as Max_Status_Weight + from alcs.notice_of_intent_submission_to_submission_status + left join alcs.notice_of_intent_submission_status_type on alcs.notice_of_intent_submission_status_type.code = alcs.notice_of_intent_submission_to_submission_status.status_type_code + where alcs.notice_of_intent_submission_to_submission_status.effective_date IS NOT NULL + AND alcs.notice_of_intent_submission_status_type.weight >= 1 + group by alcs.notice_of_intent_submission_to_submission_status.submission_uuid + ), + noi_with_card_fields AS ( + select + 'oats_etl' AS audit_created_by, + 'bb70eb85-6250-49b9-9a5c-e3c2e0b9f3a2'::uuid AS board_uuid, + 'SUBM' AS status_code, + 'NOI' AS type_code, + alcs.notice_of_intent.file_number AS external_ref + from alcs.notice_of_intent + left join alcs.application_region ON region_code = alcs.application_region.code + left join alcs.local_government ON alcs.local_government.uuid = alcs.notice_of_intent.local_government_uuid + left join alcs.notice_of_intent_type ON alcs.notice_of_intent_type.code = alcs.notice_of_intent.type_code + left join alcs.notice_of_intent_submission ON alcs.notice_of_intent.file_number = alcs.notice_of_intent_submission.file_number + left join single_status ON alcs.notice_of_intent_submission.uuid = single_status.submission_uuid + left join alcs.notice_of_intent_submission_status_type ON single_status.Max_Status_Weight = alcs.notice_of_intent_submission_status_type.weight + where alcs.notice_of_intent.date_submitted_to_alc IS NOT NULL + and alcs.notice_of_intent.date_submitted_to_alc >= timestamp with time zone '2018-01-01 00:00:00.000Z' + AND alcs.notice_of_intent_submission.is_draft = FALSE + and alcs.notice_of_intent_submission_status_type.label <> 'Cancelled' + and alcs.notice_of_intent_submission_status_type.label <> 'Decision Released' + and alcs.notice_of_intent.card_uuid IS NULL + order by alcs.notice_of_intent.file_number + ) + SELECT * FROM noi_with_card_fields; + `); + + // create temp column on cards that links NOI to card + queryRunner.query(`ALTER TABLE alcs.card ADD COLUMN external_ref TEXT;`); + + // create cards for NOIs + queryRunner.query(` + INSERT INTO alcs.card (audit_created_by, board_uuid, status_code, type_code, external_ref) + SELECT audit_created_by, board_uuid, status_code, type_code, file_number FROM public.backup_noi_with_etl_created_cards; + `); + + // link cards to NOIs + queryRunner.query(` + UPDATE alcs.notice_of_intent + SET card_uuid = alcs.card.uuid + FROM alcs.card + WHERE alcs.notice_of_intent.file_number = alcs.card.external_ref AND alcs.card.external_ref IS NOT NULL; + `); + + // drop temp column + queryRunner.query(`ALTER TABLE alcs.card DROP COLUMN external_ref;`); + } + + public async down(): Promise { + // nope + } +} From f70cb942002724b0b33d053d4293b68ef9bf71a5 Mon Sep 17 00:00:00 2001 From: mhuseinov <61513701+mhuseinov@users.noreply.github.com> Date: Wed, 3 Apr 2024 15:32:13 -0700 Subject: [PATCH 095/153] fix pr auditCreatedBy declaration (#1577) --- .../planning-review-document/planning-review-document.entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.entity.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.entity.ts index 1fd9cd8f91..e25d46df9e 100644 --- a/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.entity.ts +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.entity.ts @@ -76,7 +76,7 @@ export class PlanningReviewDocument extends BaseEntity { }) oatsDocumentId?: string | null; - @Column({ nullable: true }) + @Column({ type: 'varchar', nullable: true }) auditCreatedBy?: string | null; @OneToOne(() => Document) From e62e30845fa0c79b3f2ee076f44ac346cfb9cb72 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Wed, 3 Apr 2024 15:59:39 -0700 Subject: [PATCH 096/153] Clean up V1 Decisions and Covenants * Remove remaining artifacts * Remove NOI and App Creation Dialogs --- .../ceo-criterion-dialog.component.ts | 6 +- .../ceo-criterion/ceo-criterion.component.ts | 4 +- .../decision-maker-dialog.component.ts | 6 +- .../decision-maker.component.ts | 4 +- .../noi-subtype-dialog.component.ts | 7 +- .../decision-v1-dialog.component.html | 159 ------- .../decision-v1-dialog.component.scss | 11 - .../decision-v1-dialog.component.spec.ts | 62 --- .../decision-v1-dialog.component.ts | 318 -------------- .../decision-v1/decision-v1.component.html | 145 ------- .../decision-v1/decision-v1.component.scss | 140 ------- .../decision-v1/decision-v1.component.spec.ts | 64 --- .../decision-v1/decision-v1.component.ts | 230 ----------- .../decision-documents.component.ts | 43 +- .../decision/decision.component.html | 3 +- .../application/decision/decision.module.ts | 17 +- ...edit-modification-dialog.component.spec.ts | 8 +- .../edit-modification-dialog.component.ts | 8 +- ...t-reconsideration-dialog.component.spec.ts | 8 +- .../edit-reconsideration-dialog.component.ts | 8 +- .../authorization/authorization.component.ts | 6 +- .../src/app/features/board/board.module.ts | 6 +- ...reate-app-modification-dialog.component.ts | 6 +- .../create-application-dialog.component.scss | 31 -- ...reate-application-dialog.component.spec.ts | 54 --- .../create-application-dialog.component.ts | 85 ---- .../create/create-application-dialog.html | 147 ------- ...reate-noi-modification-dialog.component.ts | 4 +- ...ate-notice-of-intent-dialog.component.html | 109 ----- ...ate-notice-of-intent-dialog.component.scss | 7 - ...-notice-of-intent-dialog.component.spec.ts | 54 --- ...reate-notice-of-intent-dialog.component.ts | 94 ----- ...create-reconsideration-dialog.component.ts | 6 +- .../condition/condition.component.ts | 4 +- .../conditions/conditions.component.ts | 12 +- .../decision-dialog.component.html | 96 ----- .../decision-dialog.component.scss | 11 - .../decision-dialog.component.spec.ts | 52 --- .../decision-dialog.component.ts | 174 -------- .../decision-v1/decision-v1.component.html | 105 ----- .../decision-v1/decision-v1.component.scss | 142 ------- .../decision-v1/decision-v1.component.spec.ts | 64 --- .../decision-v1/decision-v1.component.ts | 208 ---------- .../basic/basic.component.spec.ts | 2 +- .../basic/basic.component.ts | 2 +- .../pfrs/pfrs.component.spec.ts | 2 +- .../decision-component/pfrs/pfrs.component.ts | 2 +- .../pofo/pofo.component.spec.ts | 2 +- .../decision-component/pofo/pofo.component.ts | 2 +- .../roso/roso.component.spec.ts | 2 +- .../decision-component/roso/roso.component.ts | 2 +- .../decision-documents.component.spec.ts | 2 +- .../decision-documents.component.ts | 2 +- .../decision-component.component.ts | 2 +- .../decision-components.component.spec.ts | 4 +- .../decision-components.component.ts | 2 +- .../decision-conditions.component.spec.ts | 2 +- .../decision-conditions.component.ts | 23 +- ...cision-document-upload-dialog.component.ts | 2 +- .../decision-input-v2.component.ts | 2 +- .../decision-v2/decision-v2.component.spec.ts | 2 +- .../decision-v2/decision-v2.component.ts | 4 +- .../release-dialog.component.spec.ts | 2 +- .../decision/decision.component.html | 3 +- .../decision/decision.module.ts | 4 - .../overview/overview.component.spec.ts | 6 +- ...edit-modification-dialog.component.spec.ts | 6 +- .../edit-modification-dialog.component.ts | 6 +- ...lanning-review-search-table.component.html | 46 +-- .../application-decision-maker.service.ts | 7 +- .../application-modification.dto.ts | 2 +- .../application-reconsideration.dto.ts | 3 +- .../application/application.service.ts | 50 +-- .../application-decision.service.spec.ts | 194 --------- .../application-decision.service.ts | 129 ------ .../application-decision.dto.ts | 0 .../card/card-subtask/card-subtask.dto.ts | 2 +- .../ceo-criterion/ceo-criterion.service.ts | 7 +- .../noi-subtype/noi-subtype.service.ts | 7 +- ...ce-of-intent-decision-component.service.ts | 9 +- ...ce-of-intent-decision-condition.service.ts | 9 +- .../notice-of-intent-decision-v2.service.ts | 11 +- .../notice-of-intent-decision.dto.ts | 0 .../notice-of-intent-decision.service.spec.ts | 192 --------- .../notice-of-intent-decision.service.ts | 119 ------ .../notice-of-intent-modification.dto.ts | 2 +- .../notice-of-intent.service.ts | 9 +- .../card/card-subtask/card-subtask.dto.ts | 2 - .../src/alcs/import/noi-import.service.ts | 11 +- ...e-of-intent-decision-v1.controller.spec.ts | 211 ---------- ...notice-of-intent-decision-v1.controller.ts | 187 --------- ...tice-of-intent-decision-v1.service.spec.ts | 333 --------------- .../notice-of-intent-decision-v1.service.ts | 388 ------------------ .../notice-of-intent-decision.module.ts | 13 +- ...ice-of-intent-modification.service.spec.ts | 6 +- .../notice-of-intent-modification.service.ts | 49 ++- ...plication-submission.automapper.profile.ts | 6 +- 97 files changed, 243 insertions(+), 4579 deletions(-) delete mode 100644 alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1-dialog/decision-v1-dialog.component.html delete mode 100644 alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1-dialog/decision-v1-dialog.component.scss delete mode 100644 alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1-dialog/decision-v1-dialog.component.spec.ts delete mode 100644 alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1-dialog/decision-v1-dialog.component.ts delete mode 100644 alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1.component.html delete mode 100644 alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1.component.scss delete mode 100644 alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1.component.spec.ts delete mode 100644 alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1.component.ts delete mode 100644 alcs-frontend/src/app/features/board/dialogs/application/create/create-application-dialog.component.scss delete mode 100644 alcs-frontend/src/app/features/board/dialogs/application/create/create-application-dialog.component.spec.ts delete mode 100644 alcs-frontend/src/app/features/board/dialogs/application/create/create-application-dialog.component.ts delete mode 100644 alcs-frontend/src/app/features/board/dialogs/application/create/create-application-dialog.html delete mode 100644 alcs-frontend/src/app/features/board/dialogs/notice-of-intent/create/create-notice-of-intent-dialog.component.html delete mode 100644 alcs-frontend/src/app/features/board/dialogs/notice-of-intent/create/create-notice-of-intent-dialog.component.scss delete mode 100644 alcs-frontend/src/app/features/board/dialogs/notice-of-intent/create/create-notice-of-intent-dialog.component.spec.ts delete mode 100644 alcs-frontend/src/app/features/board/dialogs/notice-of-intent/create/create-notice-of-intent-dialog.component.ts delete mode 100644 alcs-frontend/src/app/features/notice-of-intent/decision/decision-dialog/decision-dialog.component.html delete mode 100644 alcs-frontend/src/app/features/notice-of-intent/decision/decision-dialog/decision-dialog.component.scss delete mode 100644 alcs-frontend/src/app/features/notice-of-intent/decision/decision-dialog/decision-dialog.component.spec.ts delete mode 100644 alcs-frontend/src/app/features/notice-of-intent/decision/decision-dialog/decision-dialog.component.ts delete mode 100644 alcs-frontend/src/app/features/notice-of-intent/decision/decision-v1/decision-v1.component.html delete mode 100644 alcs-frontend/src/app/features/notice-of-intent/decision/decision-v1/decision-v1.component.scss delete mode 100644 alcs-frontend/src/app/features/notice-of-intent/decision/decision-v1/decision-v1.component.spec.ts delete mode 100644 alcs-frontend/src/app/features/notice-of-intent/decision/decision-v1/decision-v1.component.ts delete mode 100644 alcs-frontend/src/app/services/application/decision/application-decision-v1/application-decision.service.spec.ts delete mode 100644 alcs-frontend/src/app/services/application/decision/application-decision-v1/application-decision.service.ts rename alcs-frontend/src/app/services/application/decision/{application-decision-v1 => application-decision-v2}/application-decision.dto.ts (100%) rename alcs-frontend/src/app/services/notice-of-intent/{decision => decision-v2}/notice-of-intent-decision.dto.ts (100%) delete mode 100644 alcs-frontend/src/app/services/notice-of-intent/decision/notice-of-intent-decision.service.spec.ts delete mode 100644 alcs-frontend/src/app/services/notice-of-intent/decision/notice-of-intent-decision.service.ts delete mode 100644 services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-v1/notice-of-intent-decision-v1.controller.spec.ts delete mode 100644 services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-v1/notice-of-intent-decision-v1.controller.ts delete mode 100644 services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-v1/notice-of-intent-decision-v1.service.spec.ts delete mode 100644 services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-v1/notice-of-intent-decision-v1.service.ts diff --git a/alcs-frontend/src/app/features/admin/ceo-criterion/ceo-criterion-dialog/ceo-criterion-dialog.component.ts b/alcs-frontend/src/app/features/admin/ceo-criterion/ceo-criterion-dialog/ceo-criterion-dialog.component.ts index 85ad4b28ce..ec69d1eaee 100644 --- a/alcs-frontend/src/app/features/admin/ceo-criterion/ceo-criterion-dialog/ceo-criterion-dialog.component.ts +++ b/alcs-frontend/src/app/features/admin/ceo-criterion/ceo-criterion-dialog/ceo-criterion-dialog.component.ts @@ -1,6 +1,6 @@ import { Component, Inject } from '@angular/core'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { CeoCriterionDto } from '../../../../services/application/decision/application-decision-v1/application-decision.dto'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { CeoCriterionDto } from '../../../../services/application/decision/application-decision-v2/application-decision.dto'; import { CeoCriterionService } from '../../../../services/ceo-criterion/ceo-criterion.service'; @Component({ @@ -20,7 +20,7 @@ export class CeoCriterionDialogComponent { constructor( @Inject(MAT_DIALOG_DATA) public data: CeoCriterionDto | undefined, private dialogRef: MatDialogRef, - private ceoCriterionService: CeoCriterionService + private ceoCriterionService: CeoCriterionService, ) { if (data) { this.description = data.description; diff --git a/alcs-frontend/src/app/features/admin/ceo-criterion/ceo-criterion.component.ts b/alcs-frontend/src/app/features/admin/ceo-criterion/ceo-criterion.component.ts index e6d8bb183f..9a74aa1d4e 100644 --- a/alcs-frontend/src/app/features/admin/ceo-criterion/ceo-criterion.component.ts +++ b/alcs-frontend/src/app/features/admin/ceo-criterion/ceo-criterion.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { Subject } from 'rxjs'; -import { CeoCriterionDto } from '../../../services/application/decision/application-decision-v1/application-decision.dto'; +import { CeoCriterionDto } from '../../../services/application/decision/application-decision-v2/application-decision.dto'; import { CeoCriterionService } from '../../../services/ceo-criterion/ceo-criterion.service'; import { ConfirmationDialogService } from '../../../shared/confirmation-dialog/confirmation-dialog.service'; import { CeoCriterionDialogComponent } from './ceo-criterion-dialog/ceo-criterion-dialog.component'; @@ -20,7 +20,7 @@ export class CeoCriterionComponent implements OnInit { constructor( private ceoCriterionService: CeoCriterionService, public dialog: MatDialog, - private confirmationDialogService: ConfirmationDialogService + private confirmationDialogService: ConfirmationDialogService, ) {} ngOnInit(): void { diff --git a/alcs-frontend/src/app/features/admin/decision-maker/decision-maker-dialog/decision-maker-dialog.component.ts b/alcs-frontend/src/app/features/admin/decision-maker/decision-maker-dialog/decision-maker-dialog.component.ts index 0d92afcd79..b513df334b 100644 --- a/alcs-frontend/src/app/features/admin/decision-maker/decision-maker-dialog/decision-maker-dialog.component.ts +++ b/alcs-frontend/src/app/features/admin/decision-maker/decision-maker-dialog/decision-maker-dialog.component.ts @@ -1,7 +1,7 @@ import { Component, Inject } from '@angular/core'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { DecisionMakerDto } from '../../../../services/application/decision/application-decision-v1/application-decision.dto'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { ApplicationDecisionMakerService } from '../../../../services/application/application-decision-maker/application-decision-maker.service'; +import { DecisionMakerDto } from '../../../../services/application/decision/application-decision-v2/application-decision.dto'; @Component({ selector: 'app-decision-maker-dialog', @@ -20,7 +20,7 @@ export class DecisionMakerDialogComponent { constructor( @Inject(MAT_DIALOG_DATA) public data: DecisionMakerDto | undefined, private dialogRef: MatDialogRef, - private decisionMakerService: ApplicationDecisionMakerService + private decisionMakerService: ApplicationDecisionMakerService, ) { if (data) { this.description = data.description; diff --git a/alcs-frontend/src/app/features/admin/decision-maker/decision-maker.component.ts b/alcs-frontend/src/app/features/admin/decision-maker/decision-maker.component.ts index e6c5dc27f8..6d53395948 100644 --- a/alcs-frontend/src/app/features/admin/decision-maker/decision-maker.component.ts +++ b/alcs-frontend/src/app/features/admin/decision-maker/decision-maker.component.ts @@ -1,8 +1,8 @@ import { Component, OnInit } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { Subject } from 'rxjs'; -import { DecisionMakerDto } from '../../../services/application/decision/application-decision-v1/application-decision.dto'; import { ApplicationDecisionMakerService } from '../../../services/application/application-decision-maker/application-decision-maker.service'; +import { DecisionMakerDto } from '../../../services/application/decision/application-decision-v2/application-decision.dto'; import { ConfirmationDialogService } from '../../../shared/confirmation-dialog/confirmation-dialog.service'; import { DecisionMakerDialogComponent } from './decision-maker-dialog/decision-maker-dialog.component'; @@ -20,7 +20,7 @@ export class DecisionMakerComponent implements OnInit { constructor( private decisionMakerService: ApplicationDecisionMakerService, public dialog: MatDialog, - private confirmationDialogService: ConfirmationDialogService + private confirmationDialogService: ConfirmationDialogService, ) {} ngOnInit(): void { diff --git a/alcs-frontend/src/app/features/admin/noi-subtype/noi-subtype-dialog/noi-subtype-dialog.component.ts b/alcs-frontend/src/app/features/admin/noi-subtype/noi-subtype-dialog/noi-subtype-dialog.component.ts index 930a2c435f..9380bf02d4 100644 --- a/alcs-frontend/src/app/features/admin/noi-subtype/noi-subtype-dialog/noi-subtype-dialog.component.ts +++ b/alcs-frontend/src/app/features/admin/noi-subtype/noi-subtype-dialog/noi-subtype-dialog.component.ts @@ -1,10 +1,7 @@ import { Component, Inject } from '@angular/core'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { CeoCriterionDto } from '../../../../services/application/decision/application-decision-v1/application-decision.dto'; -import { CeoCriterionService } from '../../../../services/ceo-criterion/ceo-criterion.service'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { NoiSubtypeService } from '../../../../services/noi-subtype/noi-subtype.service'; import { NoticeOfIntentSubtypeDto } from '../../../../services/notice-of-intent/notice-of-intent.dto'; -import { NoticeOfIntentService } from '../../../../services/notice-of-intent/notice-of-intent.service'; @Component({ selector: 'app-noi-subtype-dialog', @@ -23,7 +20,7 @@ export class NoiSubtypeDialogComponent { constructor( @Inject(MAT_DIALOG_DATA) public data: NoticeOfIntentSubtypeDto | undefined, private dialogRef: MatDialogRef, - private noiSubtypeService: NoiSubtypeService + private noiSubtypeService: NoiSubtypeService, ) { if (data) { this.description = data.description; diff --git a/alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1-dialog/decision-v1-dialog.component.html b/alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1-dialog/decision-v1-dialog.component.html deleted file mode 100644 index c11ebdeea3..0000000000 --- a/alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1-dialog/decision-v1-dialog.component.html +++ /dev/null @@ -1,159 +0,0 @@ -
-

{{ isEdit ? 'Edit' : 'Create' }} Decision

-
- - -
- - Decision Date - - - - -
- - Resolution Number - -  #  - -
/
- -
-
- - -
- - - - - {{ item.number }} - {{ item.label }} - - - {{ item.number }} - {{ item.label }} - - - - Criterion 8 Modification - - Time Extension - Other - - - - Audit Date - - - - -
- Chair Review - - Required - Not Needed - -
- - Chair Review Date - - - - -
- Chair Review Outcome - - Reconsider - Stay - -
-
-
- -
- - -
-
- diff --git a/alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1-dialog/decision-v1-dialog.component.scss b/alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1-dialog/decision-v1-dialog.component.scss deleted file mode 100644 index 15f8d20486..0000000000 --- a/alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1-dialog/decision-v1-dialog.component.scss +++ /dev/null @@ -1,11 +0,0 @@ -.grid { - display: grid; - grid-template-columns: 1fr 1fr; - grid-column-gap: 24px; - grid-row-gap: 24px; -} - -.resolution-number-wrapper { - display: grid; - grid-template-columns: 1fr 16px 0.7fr; -} diff --git a/alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1-dialog/decision-v1-dialog.component.spec.ts b/alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1-dialog/decision-v1-dialog.component.spec.ts deleted file mode 100644 index bae5a4887f..0000000000 --- a/alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1-dialog/decision-v1-dialog.component.spec.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ReactiveFormsModule } from '@angular/forms'; -import { MatButtonToggleModule } from '@angular/material/button-toggle'; -import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { MatSnackBarModule } from '@angular/material/snack-bar'; -import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { NgSelectModule } from '@ng-select/ng-select'; -import { BehaviorSubject } from 'rxjs'; -import { ApplicationModificationDto } from '../../../../../services/application/application-modification/application-modification.dto'; -import { ApplicationModificationService } from '../../../../../services/application/application-modification/application-modification.service'; -import { ApplicationReconsiderationDto } from '../../../../../services/application/application-reconsideration/application-reconsideration.dto'; -import { ApplicationReconsiderationService } from '../../../../../services/application/application-reconsideration/application-reconsideration.service'; -import { ApplicationDecisionService } from '../../../../../services/application/decision/application-decision-v1/application-decision.service'; -import { MomentPipe } from '../../../../../shared/pipes/moment.pipe'; -import { StartOfDayPipe } from '../../../../../shared/pipes/startOfDay.pipe'; -import { DecisionV1DialogComponent } from './decision-v1-dialog.component'; - -describe('DecisionDialogComponent', () => { - let component: DecisionV1DialogComponent; - let fixture: ComponentFixture; - let mockModificationService: DeepMocked; - let mockReconService: DeepMocked; - - beforeEach(async () => { - mockModificationService = createMock(); - mockModificationService.$modifications = new BehaviorSubject([]); - - mockReconService = createMock(); - mockReconService.$reconsiderations = new BehaviorSubject([]); - - await TestBed.configureTestingModule({ - declarations: [DecisionV1DialogComponent, MomentPipe, StartOfDayPipe], - providers: [ - { - provide: ApplicationDecisionService, - useValue: {}, - }, - { - provide: ApplicationModificationService, - useValue: mockModificationService, - }, - { - provide: ApplicationReconsiderationService, - useValue: mockReconService, - }, - { provide: MAT_DIALOG_DATA, useValue: { meetingType: { code: 'fake', label: 'fake' }, outcomes: [] } }, - { provide: MatDialogRef, useValue: {} }, - ], - imports: [MatDialogModule, MatSnackBarModule, ReactiveFormsModule, NgSelectModule, MatButtonToggleModule], - schemas: [NO_ERRORS_SCHEMA], - }).compileComponents(); - - fixture = TestBed.createComponent(DecisionV1DialogComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1-dialog/decision-v1-dialog.component.ts b/alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1-dialog/decision-v1-dialog.component.ts deleted file mode 100644 index 884066d755..0000000000 --- a/alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1-dialog/decision-v1-dialog.component.ts +++ /dev/null @@ -1,318 +0,0 @@ -import { Component, Inject, OnInit } from '@angular/core'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { MatButtonToggleChange } from '@angular/material/button-toggle'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import moment from 'moment'; -import { combineLatestWith } from 'rxjs'; -import { ApplicationModificationDto } from '../../../../../services/application/application-modification/application-modification.dto'; -import { ApplicationModificationService } from '../../../../../services/application/application-modification/application-modification.service'; -import { - ApplicationReconsiderationDto, - RECONSIDERATION_TYPE, -} from '../../../../../services/application/application-reconsideration/application-reconsideration.dto'; -import { ApplicationReconsiderationService } from '../../../../../services/application/application-reconsideration/application-reconsideration.service'; -import { - ApplicationDecisionDto, - CeoCriterion, - CeoCriterionDto, - CreateApplicationDecisionDto, - DecisionMaker, - DecisionMakerDto, - DecisionOutcomeCodeDto, -} from '../../../../../services/application/decision/application-decision-v1/application-decision.dto'; -import { ApplicationDecisionService } from '../../../../../services/application/decision/application-decision-v1/application-decision.service'; -import { formatDateForApi } from '../../../../../shared/utils/api-date-formatter'; - -export enum PostDecisionType { - Modification = 'modification', - Reconsideration = 'reconsideration', -} - -type MappedPostDecision = { - label: string; - uuid: string; - type: PostDecisionType; -}; - -@Component({ - selector: 'app-decision-v1-dialog', - templateUrl: './decision-v1-dialog.component.html', - styleUrls: ['./decision-v1-dialog.component.scss'], -}) -export class DecisionV1DialogComponent implements OnInit { - isLoading = false; - isEdit = false; - minDate = new Date(0); - - postDecisions: MappedPostDecision[] = []; - ceoCriterion: CeoCriterionDto[] = []; - outcomes: DecisionOutcomeCodeDto[] = []; - - resolutionYears: number[] = []; - - form = new FormGroup({ - outcome: new FormControl(null, [Validators.required]), - date: new FormControl(undefined, [Validators.required]), - decisionMaker: new FormControl(null, [Validators.required]), - postDecision: new FormControl(null), - resolutionNumber: new FormControl(null, [Validators.required]), - resolutionYear: new FormControl(null, [Validators.required]), - ceoCriterion: new FormControl(null), - chairReviewRequired: new FormControl('true', [Validators.required]), - chairReviewDate: new FormControl(null), - chairReviewOutcome: new FormControl(null), - auditDate: new FormControl(null), - criterionModification: new FormControl([]), - }); - - constructor( - @Inject(MAT_DIALOG_DATA) - public data: { - isFirstDecision: boolean; - fileNumber: string; - outcomes: DecisionOutcomeCodeDto[]; - decisionMakers: DecisionMakerDto[]; - ceoCriterion: CeoCriterionDto[]; - existingDecision?: ApplicationDecisionDto; - minDate?: Date; - }, - private dialogRef: MatDialogRef, - private decisionService: ApplicationDecisionService, - private reconsiderationService: ApplicationReconsiderationService, - private modificationService: ApplicationModificationService - ) { - this.ceoCriterion = this.data.ceoCriterion; - if (data.minDate) { - this.minDate = data.minDate; - } - - if (!data.isFirstDecision) { - this.form.controls.postDecision.addValidators([Validators.required]); - this.form.controls.decisionMaker.disable(); - this.form.controls.outcome.disable(); - } - - this.outcomes = data.outcomes.filter((outcome) => outcome.isFirstDecision === data.isFirstDecision); - - this.modificationService.$modifications - .pipe(combineLatestWith(this.reconsiderationService.$reconsiderations)) - .subscribe(([modifications, reconsiderations]) => { - this.mapPostDecisionsToControls(modifications, reconsiderations, data.existingDecision); - }); - - if (data.existingDecision) { - this.patchFormWithExistingData(data.existingDecision); - } - } - - ngOnInit(): void { - const year = moment('1974'); - const currentYear = moment().year(); - while (year.year() <= currentYear) { - this.resolutionYears.push(year.year()); - year.add(1, 'year'); - } - this.resolutionYears.reverse(); - if (!this.isEdit) { - this.form.patchValue({ - resolutionYear: currentYear, - }); - } - } - - async onSubmit() { - this.isLoading = true; - const { - date, - outcome, - decisionMaker, - resolutionNumber, - resolutionYear, - ceoCriterion, - criterionModification, - chairReviewRequired, - auditDate, - chairReviewDate, - chairReviewOutcome, - postDecision, - } = this.form.getRawValue(); - - const selectedDecision = this.postDecisions.find((postDec) => postDec.uuid === postDecision); - const isPostDecisionReconsideration = - selectedDecision && selectedDecision.type === PostDecisionType.Reconsideration; - - const data: CreateApplicationDecisionDto = { - date: formatDateForApi(date!), - resolutionNumber: parseInt(resolutionNumber!), - resolutionYear: resolutionYear!, - chairReviewRequired: chairReviewRequired === 'true', - auditDate: auditDate ? formatDateForApi(auditDate) : auditDate, - chairReviewDate: chairReviewDate ? formatDateForApi(chairReviewDate) : chairReviewDate, - outcomeCode: outcome!, - decisionMakerCode: decisionMaker, - ceoCriterionCode: ceoCriterion, - chairReviewOutcomeCode: chairReviewOutcome, - applicationFileNumber: this.data.fileNumber, - modifiesUuid: isPostDecisionReconsideration ? null : postDecision!, - reconsidersUuid: isPostDecisionReconsideration ? postDecision! : null, - }; - if (ceoCriterion && ceoCriterion === CeoCriterion.MODIFICATION) { - data.isTimeExtension = criterionModification?.includes('isTimeExtension'); - data.isOther = criterionModification?.includes('isOther'); - } else { - data.isTimeExtension = null; - data.isOther = null; - } - - try { - if (this.data.existingDecision) { - await this.decisionService.update(this.data.existingDecision.uuid, data); - } else { - await this.decisionService.create({ - ...data, - applicationFileNumber: this.data.fileNumber, - }); - } - this.dialogRef.close(true); - } finally { - this.isLoading = false; - } - } - - onSelectDecisionMaker(decisionMaker: DecisionMakerDto) { - if (decisionMaker.code === DecisionMaker.CEO) { - this.form.controls['ceoCriterion'].setValidators([Validators.required]); - } else { - this.form.patchValue({ - ceoCriterion: null, - criterionModification: [], - }); - this.form.controls['ceoCriterion'].clearValidators(); - } - this.form.controls['ceoCriterion'].updateValueAndValidity(); - } - - onSelectCeoCriterion(ceoCriterion: CeoCriterionDto) { - if (ceoCriterion.code === CeoCriterion.MODIFICATION) { - this.form.controls['criterionModification'].setValidators([Validators.required]); - } else { - this.form.patchValue({ - criterionModification: [], - }); - this.form.controls['criterionModification'].clearValidators(); - } - this.form.controls['criterionModification'].updateValueAndValidity(); - } - - onSelectChairReviewRequired($event: MatButtonToggleChange) { - if ($event.value === 'false') { - this.form.patchValue({ - chairReviewOutcome: null, - chairReviewDate: null, - }); - } - } - - onSelectPostDecision(postDecision: { type: PostDecisionType }) { - if (postDecision.type === PostDecisionType.Modification) { - this.form.controls.ceoCriterion.disable(); - this.form.controls.outcome.disable(); - this.form.controls.decisionMaker.disable(); - this.ceoCriterion = this.data.ceoCriterion.filter( - (ceoCriterion) => ceoCriterion.code === CeoCriterion.MODIFICATION - ); - this.form.patchValue({ - decisionMaker: DecisionMaker.CEO, - ceoCriterion: CeoCriterion.MODIFICATION, - outcome: 'VARY', - }); - } else { - this.form.controls.decisionMaker.enable(); - this.form.controls.outcome.enable(); - this.form.controls.ceoCriterion.enable(); - this.ceoCriterion = this.data.ceoCriterion.filter( - (ceoCriterion) => ceoCriterion.code !== CeoCriterion.MODIFICATION - ); - } - } - - private mapPostDecisionsToControls( - modifications: ApplicationModificationDto[], - reconsiderations: ApplicationReconsiderationDto[], - existingDecision?: ApplicationDecisionDto - ) { - const mappedModifications = modifications - .filter( - (modification) => - (existingDecision && existingDecision.modifies?.uuid === modification.uuid) || - (modification.reviewOutcome.code === 'PRC' && !modification.resultingDecision) - ) - .map((modification, index) => ({ - label: `Modification Request #${modifications.length - index} - ${modification.modifiesDecisions - .map((dec) => `#${dec.resolutionNumber}/${dec.resolutionYear}`) - .join(', ')}`, - uuid: modification.uuid, - type: PostDecisionType.Modification, - })); - - const mappedRecons = reconsiderations - .filter( - (reconsideration) => - (existingDecision && existingDecision.reconsiders?.uuid === reconsideration.uuid) || - (reconsideration.type.code === RECONSIDERATION_TYPE.T_33 && - reconsideration.reviewOutcome?.code === 'PRC' && - !reconsideration.resultingDecision) || - (reconsideration.type.code === RECONSIDERATION_TYPE.T_33_1 && !reconsideration.resultingDecision) - ) - .map((reconsideration, index) => ({ - label: `Reconsideration Request #${reconsiderations.length - index} - ${reconsideration.reconsidersDecisions - .map((dec) => `#${dec.resolutionNumber}/${dec.resolutionYear}`) - .join(', ')}`, - uuid: reconsideration.uuid, - type: PostDecisionType.Reconsideration, - })); - this.postDecisions = [...mappedModifications, ...mappedRecons]; - } - - private patchFormWithExistingData(existingDecision: ApplicationDecisionDto) { - this.isEdit = true; - this.form.patchValue({ - outcome: existingDecision.outcome.code, - decisionMaker: existingDecision.decisionMaker?.code, - ceoCriterion: existingDecision.ceoCriterion?.code, - date: new Date(existingDecision.date), - resolutionYear: existingDecision.resolutionYear, - resolutionNumber: existingDecision.resolutionNumber.toString(10), - chairReviewRequired: existingDecision.chairReviewRequired ? 'true' : 'false', - chairReviewDate: existingDecision.chairReviewDate ? new Date(existingDecision.chairReviewDate) : undefined, - auditDate: existingDecision.auditDate ? new Date(existingDecision.auditDate) : undefined, - postDecision: existingDecision.modifies?.uuid || existingDecision.reconsiders?.uuid, - }); - if (existingDecision.reconsiders) { - this.onSelectPostDecision({ - type: PostDecisionType.Reconsideration, - }); - } - if (existingDecision.modifies) { - this.onSelectPostDecision({ - type: PostDecisionType.Modification, - }); - } - const selectedCriterion = []; - if (existingDecision.isTimeExtension) { - selectedCriterion.push('isTimeExtension'); - } - if (existingDecision.isOther) { - selectedCriterion.push('isOther'); - } - this.form.patchValue({ - criterionModification: selectedCriterion, - }); - - if (existingDecision.chairReviewOutcome !== null) { - this.form.patchValue({ - chairReviewOutcome: existingDecision.chairReviewOutcome?.code, - }); - } - } -} diff --git a/alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1.component.html b/alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1.component.html deleted file mode 100644 index cb1ffd387e..0000000000 --- a/alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1.component.html +++ /dev/null @@ -1,145 +0,0 @@ -
-

Decision

-
- -
-
-
-
No Decisions
-
-
-
- -
-
-
-
- Modified By:  - {{ decision.modifiedByResolutions.join(', ') }} - N/A -
-
- Reconsidered By:  - {{ decision.reconsideredByResolutions.join(', ') }} - N/A -
-
- -
- - - -
-
-
- Decision #{{ decisions.length - i }} - - - - calendar_month - {{ application.activeDays }} - - - pause - {{ application.pausedDays }} - - - - - - -  - - - - -  - - - Res #{{ decision.resolutionNumber }}/{{ decision.resolutionYear }} -
-
-
-
Decision Date
- {{ decision.date | momentFormat }} -
-
-
Decision Outcome
- {{ decision.outcome.label }} {{ decision.modifies?.linkedResolutions?.join(', ') }} - {{ decision.reconsiders?.linkedResolutions?.join(', ') }} -
-
-
-
Decision Maker
- {{ decision.decisionMaker ? decision.decisionMaker.label : '(Unset)' }} -
-
-
CEO Criterion
- {{ decision.ceoCriterion?.label }} - - - Time extension - , - Other -
-
-
-
Chair Review Date
- - - - {{ decision.chairReviewDate | momentFormat }} -
-
-
Chair Review Outcome
- {{ decision.chairReviewOutcome?.label }} - (Unset) -
-
-
-
Audit Date
- {{ decision.auditDate | momentFormat }} - - - -
-
-
Documents
-
-
File Name
-
File Upload Date
-
File Actions
-
No Documents
- - -
{{ document.uploadedAt | momentFormat }}
-
- - -
-
-
-
-
-
diff --git a/alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1.component.scss b/alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1.component.scss deleted file mode 100644 index be4a460f09..0000000000 --- a/alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1.component.scss +++ /dev/null @@ -1,140 +0,0 @@ -@use '../../../../../styles/colors'; - -h3 { - margin-bottom: 24px !important; -} - -section { - margin-bottom: 64px; -} - -.decision-container { - position: relative; -} - -.decision { - margin: 24px 0; - box-shadow: 0 2px 8px 1px rgba(0, 0, 0, 0.25); -} - -.loading-overlay { - position: absolute; - z-index: 2; - background-color: colors.$grey; - opacity: 0.4; - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; -} - -.days { - display: inline-block; - margin-right: 6px; - - .mat-icon { - font-size: 19px !important; - line-height: 21px !important; - width: 19px; - vertical-align: middle; - } -} - -.post-decisions { - padding: 6px 32px; - background-color: colors.$accent-color-light; - grid-template-columns: 1fr 1fr 100px; - display: grid; - min-height: 36px; - text-transform: uppercase; - border-radius: 4px 4px 0 0; - box-shadow: -1px 1px 4px rgba(0, 0, 0, 0.25); -} - -.decision-menu { - position: absolute; - top: 0; - right: 0; - height: 36px; - background: colors.$accent-color; - box-shadow: -1px 1px 4px rgba(0, 0, 0, 0.25); - border-radius: 0 4px 0 10px; - - button { - color: colors.$white; - width: 36px; - height: 36px; - line-height: 36px; - } - - mat-icon { - position: absolute; - top: 8px; - left: 8px; - font-size: 21px; - width: 20px; - height: 36px; - } -} - -.decision-padding { - padding: 18px 32px 32px 32px; -} - -.decision-content { - margin-top: 16px; - margin-bottom: 32px; - display: grid; - grid-template-columns: 1fr 1fr 100px; - grid-row-gap: 24px; - - .subheading2 { - margin-bottom: 6px !important; - } - - & > div { - font-size: 16px; - } -} - -.decision-documents { - margin-top: 4px; - display: grid; - grid-template-columns: 1fr 1fr 100px; - grid-row-gap: 4px; - - div { - display: flex; - align-items: center; - font-size: 16px !important; - } -} - -.file-actions { - margin-left: -12px; -} - -.delete-file-icon { - color: colors.$error-color; -} - -.no-decisions { - margin-top: 16px; - display: flex; - align-items: center; - justify-content: center; - background-color: colors.$grey-light; - height: 72px; -} - -.no-files { - grid-column: 1/4; - margin-top: 16px; - padding: 16px; - display: flex; - align-items: center; - justify-content: center; - text-align: center; - background-color: colors.$grey-light; -} diff --git a/alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1.component.spec.ts b/alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1.component.spec.ts deleted file mode 100644 index 8f018fcb8d..0000000000 --- a/alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1.component.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MatDialog, MatDialogRef } from '@angular/material/dialog'; -import { MatSnackBarModule } from '@angular/material/snack-bar'; -import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { BehaviorSubject } from 'rxjs'; -import { ApplicationDetailService } from '../../../../services/application/application-detail.service'; -import { ApplicationDto } from '../../../../services/application/application.dto'; -import { ApplicationDecisionService } from '../../../../services/application/decision/application-decision-v1/application-decision.service'; -import { ToastService } from '../../../../services/toast/toast.service'; -import { ConfirmationDialogService } from '../../../../shared/confirmation-dialog/confirmation-dialog.service'; - -import { DecisionV1Component } from './decision-v1.component'; - -describe('DecisionComponent', () => { - let component: DecisionV1Component; - let fixture: ComponentFixture; - let mockAppDetailService: DeepMocked; - - beforeEach(async () => { - mockAppDetailService = createMock(); - mockAppDetailService.$application = new BehaviorSubject(undefined); - - await TestBed.configureTestingModule({ - imports: [MatSnackBarModule], - declarations: [DecisionV1Component], - providers: [ - { - provide: ApplicationDetailService, - useValue: mockAppDetailService, - }, - { - provide: ApplicationDecisionService, - useValue: {}, - }, - { - provide: MatDialogRef, - useValue: {}, - }, - { - provide: ConfirmationDialogService, - useValue: {}, - }, - { - provide: ToastService, - useValue: {}, - }, - { - provide: MatDialog, - useValue: {}, - }, - ], - schemas: [NO_ERRORS_SCHEMA], - }).compileComponents(); - - fixture = TestBed.createComponent(DecisionV1Component); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1.component.ts b/alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1.component.ts deleted file mode 100644 index dbf902080a..0000000000 --- a/alcs-frontend/src/app/features/application/decision/decision-v1/decision-v1.component.ts +++ /dev/null @@ -1,230 +0,0 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; -import { MatDialog } from '@angular/material/dialog'; -import { Subject, takeUntil } from 'rxjs'; -import { ApplicationDetailService } from '../../../../services/application/application-detail.service'; -import { ApplicationDto } from '../../../../services/application/application.dto'; -import { - ApplicationDecisionDto, - CeoCriterionDto, - DecisionMakerDto, - DecisionOutcomeCodeDto, -} from '../../../../services/application/decision/application-decision-v1/application-decision.dto'; -import { ApplicationDecisionService } from '../../../../services/application/decision/application-decision-v1/application-decision.service'; -import { ToastService } from '../../../../services/toast/toast.service'; -import { - MODIFICATION_TYPE_LABEL, - RECON_TYPE_LABEL, -} from '../../../../shared/application-type-pill/application-type-pill.constants'; -import { ConfirmationDialogService } from '../../../../shared/confirmation-dialog/confirmation-dialog.service'; -import { formatDateForApi } from '../../../../shared/utils/api-date-formatter'; -import { DecisionV1DialogComponent } from './decision-v1-dialog/decision-v1-dialog.component'; - -type LoadingDecision = ApplicationDecisionDto & { - reconsideredByResolutions: string[]; - modifiedByResolutions: string[]; - loading: boolean; -}; - -@Component({ - selector: 'app-decision-v1', - templateUrl: './decision-v1.component.html', - styleUrls: ['./decision-v1.component.scss'], -}) -export class DecisionV1Component implements OnInit, OnDestroy { - $destroy = new Subject(); - fileNumber: string = ''; - decisionDate: number | undefined; - decisions: LoadingDecision[] = []; - outcomes: DecisionOutcomeCodeDto[] = []; - decisionMakers: DecisionMakerDto[] = []; - ceoCriterion: CeoCriterionDto[] = []; - isPaused = true; - - reconLabel = RECON_TYPE_LABEL; - modificationLabel = MODIFICATION_TYPE_LABEL; - application: ApplicationDto | undefined; - - constructor( - public dialog: MatDialog, - private applicationDetailService: ApplicationDetailService, - private decisionService: ApplicationDecisionService, - private toastService: ToastService, - private confirmationDialogService: ConfirmationDialogService - ) {} - - ngOnInit(): void { - this.applicationDetailService.$application.pipe(takeUntil(this.$destroy)).subscribe((application) => { - if (application) { - this.fileNumber = application.fileNumber; - this.decisionDate = application.decisionDate; - this.isPaused = application.paused; - this.loadDecisions(application.fileNumber); - this.application = application; - } - }); - } - - async loadDecisions(fileNumber: string) { - const codes = await this.decisionService.fetchCodes(); - this.outcomes = codes.outcomes; - this.decisionMakers = codes.decisionMakers; - this.ceoCriterion = codes.ceoCriterion; - - const loadedDecision = await this.decisionService.fetchByApplication(fileNumber); - - this.decisions = loadedDecision.map((decision) => ({ - ...decision, - reconsideredByResolutions: decision.reconsideredBy?.flatMap((r) => r.linkedResolutions) || [], - modifiedByResolutions: decision.modifiedBy?.flatMap((r) => r.linkedResolutions) || [], - loading: false, - })); - } - - onCreate() { - let minDate = new Date(0); - if (this.decisions.length > 0) { - minDate = new Date(this.decisions[this.decisions.length - 1].date); - } - - this.dialog - .open(DecisionV1DialogComponent, { - minWidth: '600px', - maxWidth: '900px', - maxHeight: '80vh', - width: '90%', - autoFocus: false, - data: { - isFirstDecision: this.decisions.length === 0, - minDate, - fileNumber: this.fileNumber, - outcomes: this.outcomes, - decisionMakers: this.decisionMakers, - ceoCriterion: this.ceoCriterion, - }, - }) - .afterClosed() - .subscribe((didCreate) => { - if (didCreate) { - this.applicationDetailService.loadApplication(this.fileNumber); - } - }); - } - - onEdit(decision: LoadingDecision) { - const decisionIndex = this.decisions.indexOf(decision); - let minDate = new Date(0); - if (decisionIndex !== this.decisions.length - 1) { - minDate = new Date(this.decisions[this.decisions.length - 1].date); - } - this.dialog - .open(DecisionV1DialogComponent, { - minWidth: '600px', - maxWidth: '900px', - maxHeight: '80vh', - width: '90%', - autoFocus: false, - data: { - isFirstDecision: decisionIndex === this.decisions.length - 1, - minDate, - fileNumber: this.fileNumber, - outcomes: this.outcomes, - decisionMakers: this.decisionMakers, - ceoCriterion: this.ceoCriterion, - existingDecision: decision, - }, - }) - .afterClosed() - .subscribe((didModify) => { - if (didModify) { - this.applicationDetailService.loadApplication(this.fileNumber); - } - }); - } - - async deleteDecision(uuid: string) { - this.confirmationDialogService - .openDialog({ - body: 'Are you sure you want to delete the selected decision?', - }) - .subscribe(async (confirmed) => { - if (confirmed) { - this.decisions = this.decisions.map((decision) => { - return { - ...decision, - loading: decision.uuid === uuid, - }; - }); - await this.decisionService.delete(uuid); - await this.applicationDetailService.loadApplication(this.fileNumber); - this.toastService.showSuccessToast('Decision deleted'); - } - }); - } - - async attachFile(decisionUuid: string, event: Event) { - this.decisions = this.decisions.map((decision) => { - return { - ...decision, - loading: decision.uuid === decisionUuid, - }; - }); - const element = event.target as HTMLInputElement; - const fileList = element.files; - if (fileList && fileList.length > 0) { - const file: File = fileList[0]; - const uploadedFile = await this.decisionService.uploadFile(decisionUuid, file); - if (uploadedFile) { - await this.loadDecisions(this.fileNumber); - } - } - } - - async downloadFile(decisionUuid: string, decisionDocumentUuid: string, fileName: string) { - await this.decisionService.downloadFile(decisionUuid, decisionDocumentUuid, fileName, false); - } - - async openFile(decisionUuid: string, decisionDocumentUuid: string, fileName: string) { - await this.decisionService.downloadFile(decisionUuid, decisionDocumentUuid, fileName); - } - - async deleteFile(decisionUuid: string, decisionDocumentUuid: string, fileName: string) { - this.confirmationDialogService - .openDialog({ - body: `Are you sure you want to delete the file ${fileName}?`, - }) - .subscribe(async (confirmed) => { - if (confirmed) { - this.decisions = this.decisions.map((decision) => { - return { - ...decision, - loading: decision.uuid === decisionUuid, - }; - }); - - await this.decisionService.deleteFile(decisionUuid, decisionDocumentUuid); - await this.loadDecisions(this.fileNumber); - this.toastService.showSuccessToast('File deleted'); - } - }); - } - - async onSaveChairReviewDate(decisionUuid: string, chairReviewDate: number) { - await this.decisionService.update(decisionUuid, { - chairReviewDate: formatDateForApi(chairReviewDate), - chairReviewRequired: true, - }); - await this.loadDecisions(this.fileNumber); - } - - async onSaveAuditDate(decisionUuid: string, auditReviewDate: number) { - await this.decisionService.update(decisionUuid, { - auditDate: formatDateForApi(auditReviewDate), - }); - await this.loadDecisions(this.fileNumber); - } - - ngOnDestroy(): void { - this.$destroy.next(); - this.$destroy.complete(); - } -} diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-documents/decision-documents.component.ts b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-documents/decision-documents.component.ts index d4488468f7..dbf5b30e51 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-documents/decision-documents.component.ts +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-documents/decision-documents.component.ts @@ -3,9 +3,9 @@ import { MatDialog } from '@angular/material/dialog'; import { MatSort } from '@angular/material/sort'; import { MatTableDataSource } from '@angular/material/table'; import { Subject, takeUntil } from 'rxjs'; -import { ApplicationDecisionDocumentDto } from '../../../../../services/application/decision/application-decision-v1/application-decision.dto'; import { ApplicationDecisionDto } from '../../../../../services/application/decision/application-decision-v2/application-decision-v2.dto'; import { ApplicationDecisionV2Service } from '../../../../../services/application/decision/application-decision-v2/application-decision-v2.service'; +import { ApplicationDecisionDocumentDto } from '../../../../../services/application/decision/application-decision-v2/application-decision.dto'; import { ToastService } from '../../../../../services/toast/toast.service'; import { ConfirmationDialogService } from '../../../../../shared/confirmation-dialog/confirmation-dialog.service'; import { DecisionDocumentUploadDialogComponent } from '../decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component'; @@ -27,12 +27,11 @@ export class DecisionDocumentsComponent implements OnInit, OnDestroy { displayedColumns: string[] = ['type', 'fileName', 'source', 'visibilityFlags', 'uploadedAt', 'actions']; documents: ApplicationDecisionDocumentDto[] = []; - private fileId = ''; areDocumentsReleased = false; - @ViewChild(MatSort) sort!: MatSort; dataSource: MatTableDataSource = new MatTableDataSource(); + private fileId = ''; constructor( private decisionService: ApplicationDecisionV2Service, @@ -78,6 +77,25 @@ export class DecisionDocumentsComponent implements OnInit, OnDestroy { this.openFileDialog(element); } + onDeleteFile(element: ApplicationDecisionDocumentDto) { + this.confirmationDialogService + .openDialog({ + body: 'Are you sure you want to delete the selected file?', + }) + .subscribe(async (accepted) => { + if (accepted && this.decision) { + await this.decisionService.deleteFile(this.decision.uuid, element.uuid); + await this.decisionService.loadDecision(this.decision.uuid); + this.toastService.showSuccessToast('Document deleted'); + } + }); + } + + ngOnDestroy(): void { + this.$destroy.next(); + this.$destroy.complete(); + } + private openFileDialog(existingDocument?: ApplicationDecisionDocumentDto) { if (this.decision) { this.dialog @@ -99,23 +117,4 @@ export class DecisionDocumentsComponent implements OnInit, OnDestroy { }); } } - - onDeleteFile(element: ApplicationDecisionDocumentDto) { - this.confirmationDialogService - .openDialog({ - body: 'Are you sure you want to delete the selected file?', - }) - .subscribe(async (accepted) => { - if (accepted && this.decision) { - await this.decisionService.deleteFile(this.decision.uuid, element.uuid); - await this.decisionService.loadDecision(this.decision.uuid); - this.toastService.showSuccessToast('Document deleted'); - } - }); - } - - ngOnDestroy(): void { - this.$destroy.next(); - this.$destroy.complete(); - } } diff --git a/alcs-frontend/src/app/features/application/decision/decision.component.html b/alcs-frontend/src/app/features/application/decision/decision.component.html index 51c5de2e2c..d7c1a661d2 100644 --- a/alcs-frontend/src/app/features/application/decision/decision.component.html +++ b/alcs-frontend/src/app/features/application/decision/decision.component.html @@ -1,6 +1,5 @@
- - +
diff --git a/alcs-frontend/src/app/features/application/decision/decision.module.ts b/alcs-frontend/src/app/features/application/decision/decision.module.ts index 35f913d36c..1fc001a847 100644 --- a/alcs-frontend/src/app/features/application/decision/decision.module.ts +++ b/alcs-frontend/src/app/features/application/decision/decision.module.ts @@ -1,19 +1,12 @@ -import { NgIf } from '@angular/common'; import { NgModule } from '@angular/core'; -import { ReactiveFormsModule } from '@angular/forms'; import { MatOptionModule } from '@angular/material/core'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatInputModule } from '@angular/material/input'; -import { MatSelectModule } from '@angular/material/select'; -import { MatTableModule } from '@angular/material/table'; import { MatTabsModule } from '@angular/material/tabs'; import { RouterModule } from '@angular/router'; -import { NgxMaskDirective } from 'ngx-mask'; import { SharedModule } from '../../../shared/shared.module'; import { ConditionComponent } from './conditions/condition/condition.component'; import { ConditionsComponent } from './conditions/conditions.component'; -import { DecisionV1DialogComponent } from './decision-v1/decision-v1-dialog/decision-v1-dialog.component'; -import { DecisionV1Component } from './decision-v1/decision-v1.component'; +import { BasicComponent } from './decision-v2/decision-component/basic/basic.component'; +import { ExpiryDateComponent } from './decision-v2/decision-component/expiry-date/expiry-date.component'; import { InclExclComponent } from './decision-v2/decision-component/incl-excl/incl-excl.component'; import { NaruComponent } from './decision-v2/decision-component/naru/naru.component'; import { NfupComponent } from './decision-v2/decision-component/nfup/nfup.component'; @@ -21,9 +14,9 @@ import { PfrsComponent } from './decision-v2/decision-component/pfrs/pfrs.compon import { PofoComponent } from './decision-v2/decision-component/pofo/pofo.component'; import { RosoComponent } from './decision-v2/decision-component/roso/roso.component'; import { SubdComponent } from './decision-v2/decision-component/subd/subd.component'; -import { ExpiryDateComponent } from './decision-v2/decision-component/expiry-date/expiry-date.component'; import { DecisionDocumentsComponent } from './decision-v2/decision-documents/decision-documents.component'; import { DecisionComponentComponent } from './decision-v2/decision-input/decision-components/decision-component/decision-component.component'; +import { ExpiryDateInputComponent } from './decision-v2/decision-input/decision-components/decision-component/expiry-date-input/expiry-date-input.component'; import { InclExclInputComponent } from './decision-v2/decision-input/decision-components/decision-component/incl-excl-input/incl-excl-input.component'; import { NaruInputComponent } from './decision-v2/decision-input/decision-components/decision-component/naru-input/naru-input.component'; import { NfuInputComponent } from './decision-v2/decision-input/decision-components/decision-component/nfu-input/nfu-input.component'; @@ -31,7 +24,6 @@ import { PfrsInputComponent } from './decision-v2/decision-input/decision-compon import { PofoInputComponent } from './decision-v2/decision-input/decision-components/decision-component/pofo-input/pofo-input.component'; import { RosoInputComponent } from './decision-v2/decision-input/decision-components/decision-component/roso-input/roso-input.component'; import { SubdInputComponent } from './decision-v2/decision-input/decision-components/decision-component/subd-input/subd-input.component'; -import { ExpiryDateInputComponent } from './decision-v2/decision-input/decision-components/decision-component/expiry-date-input/expiry-date-input.component'; import { DecisionComponentsComponent } from './decision-v2/decision-input/decision-components/decision-components.component'; import { DecisionConditionComponent } from './decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component'; import { DecisionConditionsComponent } from './decision-v2/decision-input/decision-conditions/decision-conditions.component'; @@ -41,7 +33,6 @@ import { DecisionV2Component } from './decision-v2/decision-v2.component'; import { ReleaseDialogComponent } from './decision-v2/release-dialog/release-dialog.component'; import { RevertToDraftDialogComponent } from './decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component'; import { DecisionComponent } from './decision.component'; -import { BasicComponent } from './decision-v2/decision-component/basic/basic.component'; export const decisionChildRoutes = [ { @@ -75,8 +66,6 @@ export const decisionChildRoutes = [ DecisionComponent, DecisionV2Component, DecisionInputV2Component, - DecisionV1Component, - DecisionV1DialogComponent, ReleaseDialogComponent, DecisionComponentComponent, DecisionComponentsComponent, diff --git a/alcs-frontend/src/app/features/application/post-decision/edit-modification-dialog/edit-modification-dialog.component.spec.ts b/alcs-frontend/src/app/features/application/post-decision/edit-modification-dialog/edit-modification-dialog.component.spec.ts index 1f78ba7065..6488b47d7e 100644 --- a/alcs-frontend/src/app/features/application/post-decision/edit-modification-dialog/edit-modification-dialog.component.spec.ts +++ b/alcs-frontend/src/app/features/application/post-decision/edit-modification-dialog/edit-modification-dialog.component.spec.ts @@ -1,9 +1,9 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ApplicationModificationService } from '../../../../services/application/application-modification/application-modification.service'; -import { ApplicationDecisionService } from '../../../../services/application/decision/application-decision-v1/application-decision.service'; +import { ApplicationDecisionV2Service } from '../../../../services/application/decision/application-decision-v2/application-decision-v2.service'; import { ToastService } from '../../../../services/toast/toast.service'; import { EditModificationDialogComponent } from './edit-modification-dialog.component'; @@ -11,7 +11,7 @@ import { EditModificationDialogComponent } from './edit-modification-dialog.comp describe('EditModificationDialogComponent', () => { let component: EditModificationDialogComponent; let fixture: ComponentFixture; - let mockDecisionService: DeepMocked; + let mockDecisionService: DeepMocked; beforeEach(async () => { mockDecisionService = createMock(); @@ -24,7 +24,7 @@ describe('EditModificationDialogComponent', () => { useValue: {}, }, { - provide: ApplicationDecisionService, + provide: ApplicationDecisionV2Service, useValue: mockDecisionService, }, { diff --git a/alcs-frontend/src/app/features/application/post-decision/edit-modification-dialog/edit-modification-dialog.component.ts b/alcs-frontend/src/app/features/application/post-decision/edit-modification-dialog/edit-modification-dialog.component.ts index 10b2da46c3..33a40b6d62 100644 --- a/alcs-frontend/src/app/features/application/post-decision/edit-modification-dialog/edit-modification-dialog.component.ts +++ b/alcs-frontend/src/app/features/application/post-decision/edit-modification-dialog/edit-modification-dialog.component.ts @@ -1,12 +1,12 @@ import { Component, Inject, OnInit } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { ApplicationModificationDto, ApplicationModificationUpdateDto, } from '../../../../services/application/application-modification/application-modification.dto'; import { ApplicationModificationService } from '../../../../services/application/application-modification/application-modification.service'; -import { ApplicationDecisionService } from '../../../../services/application/decision/application-decision-v1/application-decision.service'; +import { ApplicationDecisionV2Service } from '../../../../services/application/decision/application-decision-v2/application-decision-v2.service'; import { ToastService } from '../../../../services/toast/toast.service'; import { formatDateForApi } from '../../../../shared/utils/api-date-formatter'; @@ -39,8 +39,8 @@ export class EditModificationDialogComponent implements OnInit { }, private dialogRef: MatDialogRef, private modificationService: ApplicationModificationService, - private decisionService: ApplicationDecisionService, - private toastService: ToastService + private decisionService: ApplicationDecisionV2Service, + private toastService: ToastService, ) { this.form.patchValue({ submittedDate: new Date(data.existingModification.submittedDate), diff --git a/alcs-frontend/src/app/features/application/post-decision/edit-reconsideration-dialog/edit-reconsideration-dialog.component.spec.ts b/alcs-frontend/src/app/features/application/post-decision/edit-reconsideration-dialog/edit-reconsideration-dialog.component.spec.ts index 250a93bf1f..e0764329db 100644 --- a/alcs-frontend/src/app/features/application/post-decision/edit-reconsideration-dialog/edit-reconsideration-dialog.component.spec.ts +++ b/alcs-frontend/src/app/features/application/post-decision/edit-reconsideration-dialog/edit-reconsideration-dialog.component.spec.ts @@ -1,9 +1,9 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ApplicationReconsiderationService } from '../../../../services/application/application-reconsideration/application-reconsideration.service'; -import { ApplicationDecisionService } from '../../../../services/application/decision/application-decision-v1/application-decision.service'; +import { ApplicationDecisionV2Service } from '../../../../services/application/decision/application-decision-v2/application-decision-v2.service'; import { ToastService } from '../../../../services/toast/toast.service'; import { EditReconsiderationDialogComponent } from './edit-reconsideration-dialog.component'; @@ -11,7 +11,7 @@ import { EditReconsiderationDialogComponent } from './edit-reconsideration-dialo describe('EditReconsiderationDialogComponent', () => { let component: EditReconsiderationDialogComponent; let fixture: ComponentFixture; - let mockDecisionService: DeepMocked; + let mockDecisionService: DeepMocked; beforeEach(async () => { mockDecisionService = createMock(); @@ -24,7 +24,7 @@ describe('EditReconsiderationDialogComponent', () => { useValue: {}, }, { - provide: ApplicationDecisionService, + provide: ApplicationDecisionV2Service, useValue: mockDecisionService, }, { diff --git a/alcs-frontend/src/app/features/application/post-decision/edit-reconsideration-dialog/edit-reconsideration-dialog.component.ts b/alcs-frontend/src/app/features/application/post-decision/edit-reconsideration-dialog/edit-reconsideration-dialog.component.ts index 6627a0f684..a50d44708a 100644 --- a/alcs-frontend/src/app/features/application/post-decision/edit-reconsideration-dialog/edit-reconsideration-dialog.component.ts +++ b/alcs-frontend/src/app/features/application/post-decision/edit-reconsideration-dialog/edit-reconsideration-dialog.component.ts @@ -1,13 +1,13 @@ import { Component, Inject, OnInit } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { ApplicationReconsiderationDetailedDto, RECONSIDERATION_TYPE, UpdateApplicationReconsiderationDto, } from '../../../../services/application/application-reconsideration/application-reconsideration.dto'; import { ApplicationReconsiderationService } from '../../../../services/application/application-reconsideration/application-reconsideration.service'; -import { ApplicationDecisionService } from '../../../../services/application/decision/application-decision-v1/application-decision.service'; +import { ApplicationDecisionV2Service } from '../../../../services/application/decision/application-decision-v2/application-decision-v2.service'; import { ToastService } from '../../../../services/toast/toast.service'; import { BaseCodeDto } from '../../../../shared/dto/base.dto'; import { formatDateForApi } from '../../../../shared/utils/api-date-formatter'; @@ -50,8 +50,8 @@ export class EditReconsiderationDialogComponent implements OnInit { }, private dialogRef: MatDialogRef, private reconsiderationService: ApplicationReconsiderationService, - private decisionService: ApplicationDecisionService, - private toastService: ToastService + private decisionService: ApplicationDecisionV2Service, + private toastService: ToastService, ) { this.codes = data.codes; diff --git a/alcs-frontend/src/app/features/authorization/authorization.component.ts b/alcs-frontend/src/app/features/authorization/authorization.component.ts index 9fba80a8e2..0b8f12cad5 100644 --- a/alcs-frontend/src/app/features/authorization/authorization.component.ts +++ b/alcs-frontend/src/app/features/authorization/authorization.component.ts @@ -8,7 +8,11 @@ import { AuthenticationService } from '../../services/authentication/authenticat template: `<>`, }) export class AuthorizationComponent implements OnInit { - constructor(private route: ActivatedRoute, private router: Router, private authService: AuthenticationService) {} + constructor( + private route: ActivatedRoute, + private router: Router, + private authService: AuthenticationService, + ) {} async ngOnInit() { const token = this.route.snapshot.queryParamMap.get('t'); diff --git a/alcs-frontend/src/app/features/board/board.module.ts b/alcs-frontend/src/app/features/board/board.module.ts index 5a93728b5f..90590cedb7 100644 --- a/alcs-frontend/src/app/features/board/board.module.ts +++ b/alcs-frontend/src/app/features/board/board.module.ts @@ -14,16 +14,14 @@ import { StatusFilterPipe } from '../../shared/drag-drop-board/status-filter.pip import { MentionTextareaComponent } from '../../shared/mention-textarea/mention-textarea.component'; import { SharedModule } from '../../shared/shared.module'; import { BoardComponent } from './board.component'; -import { CardDialogComponent } from './dialogs/card-dialog/card-dialog.component'; 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 { CardDialogComponent } from './dialogs/card-dialog/card-dialog.component'; import { CreateInquiryDialogComponent } from './dialogs/inquiry/create/create-inquiry-dialog.component'; import { InquiryDialogComponent } from './dialogs/inquiry/inquiry-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'; @@ -46,7 +44,6 @@ const routes: Routes = [ DragDropBoardComponent, StatusFilterPipe, ApplicationDialogComponent, - CreateApplicationDialogComponent, CardComponent, CommentComponent, CommentsComponent, @@ -59,7 +56,6 @@ const routes: Routes = [ CreateAppModificationDialogComponent, AppModificationDialogComponent, NoticeOfIntentDialogComponent, - CreateNoticeOfIntentDialogComponent, CreateNoiModificationDialogComponent, NoiModificationDialogComponent, NotificationDialogComponent, diff --git a/alcs-frontend/src/app/features/board/dialogs/app-modification/create/create-app-modification-dialog.component.ts b/alcs-frontend/src/app/features/board/dialogs/app-modification/create/create-app-modification-dialog.component.ts index e0cf371cbf..53623bd5f7 100644 --- a/alcs-frontend/src/app/features/board/dialogs/app-modification/create/create-app-modification-dialog.component.ts +++ b/alcs-frontend/src/app/features/board/dialogs/app-modification/create/create-app-modification-dialog.component.ts @@ -1,7 +1,7 @@ import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { MatOptionSelectionChange } from '@angular/material/core'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; import { debounceTime, distinctUntilChanged, Observable, startWith, Subject, switchMap, takeUntil } from 'rxjs'; import { ApplicationRegionDto, ApplicationTypeDto } from '../../../../../services/application/application-code.dto'; @@ -11,7 +11,7 @@ import { ApplicationModificationCreateDto } from '../../../../../services/applic import { ApplicationModificationService } from '../../../../../services/application/application-modification/application-modification.service'; import { ApplicationDto } from '../../../../../services/application/application.dto'; import { ApplicationService } from '../../../../../services/application/application.service'; -import { ApplicationDecisionService } from '../../../../../services/application/decision/application-decision-v1/application-decision.service'; +import { ApplicationDecisionV2Service } from '../../../../../services/application/decision/application-decision-v2/application-decision-v2.service'; import { ToastService } from '../../../../../services/toast/toast.service'; @Component({ @@ -59,7 +59,7 @@ export class CreateAppModificationDialogComponent implements OnInit, OnDestroy { private applicationService: ApplicationService, private modificationService: ApplicationModificationService, private localGovernmentService: ApplicationLocalGovernmentService, - private decisionService: ApplicationDecisionService, + private decisionService: ApplicationDecisionV2Service, private toastService: ToastService, private router: Router, private activatedRoute: ActivatedRoute, diff --git a/alcs-frontend/src/app/features/board/dialogs/application/create/create-application-dialog.component.scss b/alcs-frontend/src/app/features/board/dialogs/application/create/create-application-dialog.component.scss deleted file mode 100644 index ff9b852dc6..0000000000 --- a/alcs-frontend/src/app/features/board/dialogs/application/create/create-application-dialog.component.scss +++ /dev/null @@ -1,31 +0,0 @@ -.two-item-row { - display: grid; - grid-template-columns: 1fr 1fr; - grid-column-gap: 24px; - grid-row-gap: 24px; - margin-bottom: 24px; -} - -.date-picker { - width: 100%; - margin: 4px; -} - -mat-dialog-actions { - button:not(:last-child) { - margin-right: 24px !important; - } -} - -.application-type { - display: flex; - align-items: center; -} - -.application-color { - display: inline-block; - height: 18px; - width: 40px; - margin-right: 8px; - border-radius: 2px; -} diff --git a/alcs-frontend/src/app/features/board/dialogs/application/create/create-application-dialog.component.spec.ts b/alcs-frontend/src/app/features/board/dialogs/application/create/create-application-dialog.component.spec.ts deleted file mode 100644 index 80f9b63e8e..0000000000 --- a/alcs-frontend/src/app/features/board/dialogs/application/create/create-application-dialog.component.spec.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; -import { MatDividerModule } from '@angular/material/divider'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatInputModule } from '@angular/material/input'; -import { MatSelectModule } from '@angular/material/select'; -import { MatSnackBarModule } from '@angular/material/snack-bar'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { CreateApplicationDialogComponent } from './create-application-dialog.component'; - -describe('CreateApplicationDialogComponent', () => { - let component: CreateApplicationDialogComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [CreateApplicationDialogComponent], - imports: [ - MatDialogModule, - HttpClientTestingModule, - MatFormFieldModule, - MatDividerModule, - MatInputModule, - MatSelectModule, - BrowserAnimationsModule, - MatSnackBarModule, - ], - providers: [ - { provide: MatDialogRef, useValue: {} }, - { provide: MAT_DIALOG_DATA, useValue: {} }, - ], - schemas: [NO_ERRORS_SCHEMA], - }).compileComponents(); - - fixture = TestBed.createComponent(CreateApplicationDialogComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - it('should render the form fields', () => { - fixture.detectChanges(); - - const compiled = fixture.debugElement.nativeElement; - expect(compiled.querySelector('#applicant')).toBeTruthy(); - expect(compiled.querySelector('#fileNumber')).toBeTruthy(); - expect(compiled.querySelector('.card-type')).toBeTruthy(); - }); -}); diff --git a/alcs-frontend/src/app/features/board/dialogs/application/create/create-application-dialog.component.ts b/alcs-frontend/src/app/features/board/dialogs/application/create/create-application-dialog.component.ts deleted file mode 100644 index d23960b1ab..0000000000 --- a/alcs-frontend/src/app/features/board/dialogs/application/create/create-application-dialog.component.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { Subject, takeUntil } from 'rxjs'; -import { ApplicationRegionDto, ApplicationTypeDto } 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 { ApplicationDto } from '../../../../../services/application/application.dto'; -import { ApplicationService } from '../../../../../services/application/application.service'; -import { ToastService } from '../../../../../services/toast/toast.service'; -import { formatDateForApi } from '../../../../../shared/utils/api-date-formatter'; - -@Component({ - selector: 'app-create-card-dialog', - templateUrl: './create-application-dialog.html', - styleUrls: ['./create-application-dialog.component.scss'], -}) -export class CreateApplicationDialogComponent implements OnInit, OnDestroy { - $destroy = new Subject(); - applicationTypes: ApplicationTypeDto[] = []; - regions: ApplicationRegionDto[] = []; - localGovernments: ApplicationLocalGovernmentDto[] = []; - isLoading = false; - - createForm = new FormGroup({ - fileNumber: new FormControl('', [Validators.required]), - applicant: new FormControl('', [Validators.required]), - type: new FormControl(null, [Validators.required]), - localGovernment: new FormControl(null, [Validators.required]), - region: new FormControl(null, [Validators.required]), - receivedDate: new FormControl(undefined, [Validators.required]), - }); - - constructor( - @Inject(MAT_DIALOG_DATA) public data: ApplicationDto, - private dialogRef: MatDialogRef, - private applicationService: ApplicationService, - private localGovernmentService: ApplicationLocalGovernmentService, - private toastService: ToastService - ) {} - - ngOnInit(): void { - this.applicationService.$applicationTypes.pipe(takeUntil(this.$destroy)).subscribe((types) => { - this.applicationTypes = types; - }); - - this.applicationService.$applicationRegions.pipe(takeUntil(this.$destroy)).subscribe((regions) => { - this.regions = regions; - }); - - this.localGovernmentService.list().then((res) => { - this.localGovernments = res; - }); - } - - onSelectGovernment(value: ApplicationLocalGovernmentDto) { - this.createForm.patchValue({ - region: value.preferredRegionCode, - }); - } - - async onSubmit() { - try { - this.isLoading = true; - const formValues = this.createForm.getRawValue(); - await this.applicationService.createApplication({ - typeCode: formValues.type!, - applicant: formValues.applicant!, - fileNumber: formValues.fileNumber!.trim(), - regionCode: formValues.region || undefined, - dateSubmittedToAlc: formatDateForApi(formValues.receivedDate!), - localGovernmentUuid: formValues.localGovernment!, - }); - this.dialogRef.close(true); - this.toastService.showSuccessToast('Application created'); - } finally { - this.isLoading = false; - } - } - - ngOnDestroy(): void { - this.$destroy.next(); - this.$destroy.complete(); - } -} diff --git a/alcs-frontend/src/app/features/board/dialogs/application/create/create-application-dialog.html b/alcs-frontend/src/app/features/board/dialogs/application/create/create-application-dialog.html deleted file mode 100644 index 6456984972..0000000000 --- a/alcs-frontend/src/app/features/board/dialogs/application/create/create-application-dialog.html +++ /dev/null @@ -1,147 +0,0 @@ -
-

Create Application

-
-
- -
- - File ID - - - This field is required. - - - - Applicant Last Name - - - This field is required. - - -
- - -
-
- {{ item.label }} -
-
- -
-
- {{ item.label }} -
-
-
-
-
- - Date Submitted to ALC - - - - -
-
- - -
{{ item.name }}
-
-
-
-
- - -
{{ item.label }}
-
-
-
-
-
- - - - - - diff --git a/alcs-frontend/src/app/features/board/dialogs/noi-modification/create/create-noi-modification-dialog.component.ts b/alcs-frontend/src/app/features/board/dialogs/noi-modification/create/create-noi-modification-dialog.component.ts index 7f2c0e7479..23630927ec 100644 --- a/alcs-frontend/src/app/features/board/dialogs/noi-modification/create/create-noi-modification-dialog.component.ts +++ b/alcs-frontend/src/app/features/board/dialogs/noi-modification/create/create-noi-modification-dialog.component.ts @@ -8,7 +8,7 @@ import { ApplicationRegionDto, ApplicationTypeDto } from '../../../../../service 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 { NoticeOfIntentDecisionService } from '../../../../../services/notice-of-intent/decision/notice-of-intent-decision.service'; +import { NoticeOfIntentDecisionV2Service } from '../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision-v2.service'; import { NoticeOfIntentModificationCreateDto } from '../../../../../services/notice-of-intent/notice-of-intent-modification/notice-of-intent-modification.dto'; import { NoticeOfIntentModificationService } from '../../../../../services/notice-of-intent/notice-of-intent-modification/notice-of-intent-modification.service'; import { NoticeOfIntentDto } from '../../../../../services/notice-of-intent/notice-of-intent.dto'; @@ -57,7 +57,7 @@ export class CreateNoiModificationDialogComponent implements OnInit, OnDestroy { private applicationService: ApplicationService, private modificationService: NoticeOfIntentModificationService, private localGovernmentService: ApplicationLocalGovernmentService, - private decisionService: NoticeOfIntentDecisionService, + private decisionService: NoticeOfIntentDecisionV2Service, private toastService: ToastService, private router: Router, private activatedRoute: ActivatedRoute, diff --git a/alcs-frontend/src/app/features/board/dialogs/notice-of-intent/create/create-notice-of-intent-dialog.component.html b/alcs-frontend/src/app/features/board/dialogs/notice-of-intent/create/create-notice-of-intent-dialog.component.html deleted file mode 100644 index 5a3a41bc2a..0000000000 --- a/alcs-frontend/src/app/features/board/dialogs/notice-of-intent/create/create-notice-of-intent-dialog.component.html +++ /dev/null @@ -1,109 +0,0 @@ -
-

Create Notice of Intent

-
-
- -
- - File ID - - - This field is required. - - - - Applicant Name - - - This field is required. - - - -
- - -
{{ item.name }}
-
-
-
-
- - -
- - - Date Submitted to ALC - - - - -
-
- -
- - -
-
- diff --git a/alcs-frontend/src/app/features/board/dialogs/notice-of-intent/create/create-notice-of-intent-dialog.component.scss b/alcs-frontend/src/app/features/board/dialogs/notice-of-intent/create/create-notice-of-intent-dialog.component.scss deleted file mode 100644 index e47b56d77e..0000000000 --- a/alcs-frontend/src/app/features/board/dialogs/notice-of-intent/create/create-notice-of-intent-dialog.component.scss +++ /dev/null @@ -1,7 +0,0 @@ -.two-item-row { - display: grid; - grid-template-columns: 1fr 1fr; - grid-column-gap: 24px; - grid-row-gap: 24px; - margin-bottom: 24px; -} diff --git a/alcs-frontend/src/app/features/board/dialogs/notice-of-intent/create/create-notice-of-intent-dialog.component.spec.ts b/alcs-frontend/src/app/features/board/dialogs/notice-of-intent/create/create-notice-of-intent-dialog.component.spec.ts deleted file mode 100644 index e1aec78bd1..0000000000 --- a/alcs-frontend/src/app/features/board/dialogs/notice-of-intent/create/create-notice-of-intent-dialog.component.spec.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; -import { MatDividerModule } from '@angular/material/divider'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatInputModule } from '@angular/material/input'; -import { MatSelectModule } from '@angular/material/select'; -import { MatSnackBarModule } from '@angular/material/snack-bar'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; - -import { CreateNoticeOfIntentDialogComponent } from './create-notice-of-intent-dialog.component'; - -describe('CreateCovenantDialogComponent', () => { - let component: CreateNoticeOfIntentDialogComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [CreateNoticeOfIntentDialogComponent], - imports: [ - MatDialogModule, - HttpClientTestingModule, - MatFormFieldModule, - MatDividerModule, - MatInputModule, - MatSelectModule, - BrowserAnimationsModule, - MatSnackBarModule, - ], - providers: [ - { provide: MatDialogRef, useValue: {} }, - { provide: MAT_DIALOG_DATA, useValue: {} }, - ], - schemas: [NO_ERRORS_SCHEMA], - }).compileComponents(); - - fixture = TestBed.createComponent(CreateNoticeOfIntentDialogComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - it('should render the form fields', () => { - fixture.detectChanges(); - - const compiled = fixture.debugElement.nativeElement; - expect(compiled.querySelector('#applicant')).toBeTruthy(); - expect(compiled.querySelector('#fileNumber')).toBeTruthy(); - }); -}); diff --git a/alcs-frontend/src/app/features/board/dialogs/notice-of-intent/create/create-notice-of-intent-dialog.component.ts b/alcs-frontend/src/app/features/board/dialogs/notice-of-intent/create/create-notice-of-intent-dialog.component.ts deleted file mode 100644 index 052257cb69..0000000000 --- a/alcs-frontend/src/app/features/board/dialogs/notice-of-intent/create/create-notice-of-intent-dialog.component.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -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 { CreateNoticeOfIntentDto } from '../../../../../services/notice-of-intent/notice-of-intent.dto'; -import { NoticeOfIntentService } from '../../../../../services/notice-of-intent/notice-of-intent.service'; -import { ToastService } from '../../../../../services/toast/toast.service'; -import { formatDateForApi } from '../../../../../shared/utils/api-date-formatter'; - -@Component({ - selector: 'app-create-notice-of-intent-dialog', - templateUrl: './create-notice-of-intent-dialog.component.html', - styleUrls: ['./create-notice-of-intent-dialog.component.scss'], -}) -export class CreateNoticeOfIntentDialogComponent implements OnInit, OnDestroy { - $destroy = new Subject(); - regions: ApplicationRegionDto[] = []; - localGovernments: ApplicationLocalGovernmentDto[] = []; - isLoading: boolean = false; - - fileNumberControl = new FormControl('', [Validators.required]); - regionControl = new FormControl(null, [Validators.required]); - localGovernmentControl = new FormControl(null, [Validators.required]); - applicantControl = new FormControl('', [Validators.required]); - dateSubmitted = new FormControl(null, [Validators.required]); - - createForm = new FormGroup({ - fileNumber: this.fileNumberControl, - region: this.regionControl, - localGovernment: this.localGovernmentControl, - applicant: this.applicantControl, - dateSubmitted: this.dateSubmitted, - }); - - constructor( - @Inject(MAT_DIALOG_DATA) public data: any, - private dialogRef: MatDialogRef, - private noticeOfIntentService: NoticeOfIntentService, - private cardService: CardService, - private applicationService: ApplicationService, - private localGovernmentService: ApplicationLocalGovernmentService, - private toastService: ToastService - ) {} - - ngOnInit(): void { - this.cardService.fetchCodes(); - - this.localGovernmentService.list().then((res) => { - this.localGovernments = res; - }); - - this.applicationService.$applicationRegions.pipe(takeUntil(this.$destroy)).subscribe((regions) => { - this.regions = regions; - }); - } - - async onSubmit() { - try { - this.isLoading = true; - const formValues = this.createForm.getRawValue(); - const noticeOfIntent: CreateNoticeOfIntentDto = { - fileNumber: formValues.fileNumber!.trim(), - regionCode: formValues.region!, - localGovernmentUuid: formValues.localGovernment!, - applicant: formValues.applicant!.trim(), - boardCode: this.data.currentBoardCode, - dateSubmittedToAlc: formatDateForApi(formValues.dateSubmitted!), - }; - - await this.noticeOfIntentService.create(noticeOfIntent); - - this.dialogRef.close(true); - this.toastService.showSuccessToast('Notice of Intent created'); - } finally { - this.isLoading = false; - } - } - - onSelectGovernment(value: ApplicationLocalGovernmentDto) { - this.createForm.patchValue({ - region: value.preferredRegionCode, - }); - } - - ngOnDestroy(): void { - this.$destroy.next(); - this.$destroy.complete(); - } -} diff --git a/alcs-frontend/src/app/features/board/dialogs/reconsiderations/create/create-reconsideration-dialog.component.ts b/alcs-frontend/src/app/features/board/dialogs/reconsiderations/create/create-reconsideration-dialog.component.ts index 7388c750ec..df05f4cefa 100644 --- a/alcs-frontend/src/app/features/board/dialogs/reconsiderations/create/create-reconsideration-dialog.component.ts +++ b/alcs-frontend/src/app/features/board/dialogs/reconsiderations/create/create-reconsideration-dialog.component.ts @@ -1,7 +1,7 @@ import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { MatOptionSelectionChange } from '@angular/material/core'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; import { debounceTime, distinctUntilChanged, Observable, startWith, Subject, switchMap, takeUntil } from 'rxjs'; import { ApplicationRegionDto, ApplicationTypeDto } from '../../../../../services/application/application-code.dto'; @@ -14,7 +14,7 @@ import { import { ApplicationReconsiderationService } from '../../../../../services/application/application-reconsideration/application-reconsideration.service'; import { ApplicationDto } from '../../../../../services/application/application.dto'; import { ApplicationService } from '../../../../../services/application/application.service'; -import { ApplicationDecisionService } from '../../../../../services/application/decision/application-decision-v1/application-decision.service'; +import { ApplicationDecisionV2Service } from '../../../../../services/application/decision/application-decision-v2/application-decision-v2.service'; import { CardService } from '../../../../../services/card/card.service'; import { ToastService } from '../../../../../services/toast/toast.service'; import { parseStringToBoolean } from '../../../../../shared/utils/boolean-helper'; @@ -73,7 +73,7 @@ export class CreateReconsiderationDialogComponent implements OnInit, OnDestroy { private reconsiderationService: ApplicationReconsiderationService, private localGovernmentService: ApplicationLocalGovernmentService, private toastService: ToastService, - private decisionService: ApplicationDecisionService, + private decisionService: ApplicationDecisionV2Service, private router: Router, private activatedRoute: ActivatedRoute, ) {} diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/conditions/condition/condition.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/conditions/condition/condition.component.ts index 2f5e487808..3ca76feebe 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/conditions/condition/condition.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/conditions/condition/condition.component.ts @@ -1,7 +1,7 @@ import { AfterViewInit, Component, Input, OnInit } from '@angular/core'; import moment from 'moment'; import { NoticeOfIntentDecisionConditionService } from '../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision-condition/notice-of-intent-decision-condition.service'; -import { UpdateNoticeOfIntentDecisionConditionDto } from '../../../../../services/notice-of-intent/decision/notice-of-intent-decision.dto'; +import { UpdateNoticeOfIntentDecisionConditionDto } from '../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; import { DECISION_CONDITION_COMPLETE_LABEL, DECISION_CONDITION_INCOMPLETE_LABEL, @@ -52,7 +52,7 @@ export class ConditionComponent implements OnInit, AfterViewInit { async onUpdateCondition( field: keyof UpdateNoticeOfIntentDecisionConditionDto, - value: string[] | string | number | null + value: string[] | string | number | null, ) { const condition = this.condition; diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/conditions/conditions.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/conditions/conditions.component.ts index 68c58b233b..29de5474e2 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/conditions/conditions.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/conditions/conditions.component.ts @@ -7,7 +7,7 @@ import { NoticeOfIntentDecisionCodesDto, NoticeOfIntentDecisionConditionDto, NoticeOfIntentDecisionWithLinkedResolutionDto, -} from '../../../../services/notice-of-intent/decision/notice-of-intent-decision.dto'; +} from '../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; import { NoticeOfIntentDetailService } from '../../../../services/notice-of-intent/notice-of-intent-detail.service'; import { NoticeOfIntentDto } from '../../../../services/notice-of-intent/notice-of-intent.dto'; import { @@ -60,7 +60,7 @@ export class ConditionsComponent implements OnInit { constructor( private noticeOfIntentDetailService: NoticeOfIntentDetailService, private decisionService: NoticeOfIntentDecisionV2Service, - private activatedRouter: ActivatedRoute + private activatedRouter: ActivatedRoute, ) { this.today = moment().startOf('day').toDate().getTime(); } @@ -98,7 +98,7 @@ export class ConditionsComponent implements OnInit { private sortConditions( decision: NoticeOfIntentDecisionWithLinkedResolutionDto, - conditions: DecisionConditionWithStatus[] + conditions: DecisionConditionWithStatus[], ) { decision.conditions = conditions.sort((a, b) => { const order = [CONDITION_STATUS.INCOMPLETE, CONDITION_STATUS.COMPLETE, CONDITION_STATUS.SUPERSEDED]; @@ -116,7 +116,7 @@ export class ConditionsComponent implements OnInit { private mapConditions( decision: NoticeOfIntentDecisionWithLinkedResolutionDto, - decisions: NoticeOfIntentDecisionWithLinkedResolutionDto[] + decisions: NoticeOfIntentDecisionWithLinkedResolutionDto[], ) { return decision.conditions.map((condition) => { const status = this.getStatus(condition, decision); @@ -126,7 +126,7 @@ export class ConditionsComponent implements OnInit { status, conditionComponentsLabels: condition.components?.map((c) => { const matchingType = this.codes.decisionComponentTypes.find( - (type) => type.code === c.noticeOfIntentDecisionComponentTypeCode + (type) => type.code === c.noticeOfIntentDecisionComponentTypeCode, ); const componentsDecision = decisions.find((d) => d.uuid === c.noticeOfIntentDecisionUuid); @@ -148,7 +148,7 @@ export class ConditionsComponent implements OnInit { private getStatus( condition: NoticeOfIntentDecisionConditionDto, - decision: NoticeOfIntentDecisionWithLinkedResolutionDto + decision: NoticeOfIntentDecisionWithLinkedResolutionDto, ) { let status = ''; if (condition.supersededDate && condition.supersededDate <= this.today) { diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-dialog/decision-dialog.component.html b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-dialog/decision-dialog.component.html deleted file mode 100644 index 6908fc6af1..0000000000 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-dialog/decision-dialog.component.html +++ /dev/null @@ -1,96 +0,0 @@ -
-

{{ isEdit ? 'Edit' : 'Create' }} Decision

-
-
- -
- - Decision Date - - - - -
- - Resolution Number - -  #  - -
/
- -
-
- - -
- - -
- Decision Maker* - - CEO Delegate - CEO - -
- - - Decision Maker Name - - - - - Audit Date - - - - -
-
- -
- - -
-
- diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-dialog/decision-dialog.component.scss b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-dialog/decision-dialog.component.scss deleted file mode 100644 index 15f8d20486..0000000000 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-dialog/decision-dialog.component.scss +++ /dev/null @@ -1,11 +0,0 @@ -.grid { - display: grid; - grid-template-columns: 1fr 1fr; - grid-column-gap: 24px; - grid-row-gap: 24px; -} - -.resolution-number-wrapper { - display: grid; - grid-template-columns: 1fr 16px 0.7fr; -} diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-dialog/decision-dialog.component.spec.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-dialog/decision-dialog.component.spec.ts deleted file mode 100644 index c152c16842..0000000000 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-dialog/decision-dialog.component.spec.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ReactiveFormsModule } from '@angular/forms'; -import { MatButtonToggleModule } from '@angular/material/button-toggle'; -import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; -import { MatSnackBarModule } from '@angular/material/snack-bar'; -import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { NgSelectModule } from '@ng-select/ng-select'; -import { BehaviorSubject } from 'rxjs'; -import { NoticeOfIntentDecisionService } from '../../../../services/notice-of-intent/decision/notice-of-intent-decision.service'; -import { NoticeOfIntentModificationDto } from '../../../../services/notice-of-intent/notice-of-intent-modification/notice-of-intent-modification.dto'; -import { NoticeOfIntentModificationService } from '../../../../services/notice-of-intent/notice-of-intent-modification/notice-of-intent-modification.service'; -import { MomentPipe } from '../../../../shared/pipes/moment.pipe'; -import { StartOfDayPipe } from '../../../../shared/pipes/startOfDay.pipe'; -import { DecisionDialogComponent } from './decision-dialog.component'; - -describe('DecisionDialogComponent', () => { - let component: DecisionDialogComponent; - let fixture: ComponentFixture; - let mockNOIModificationService: DeepMocked; - - beforeEach(async () => { - mockNOIModificationService = createMock(); - mockNOIModificationService.$modifications = new BehaviorSubject([]); - - await TestBed.configureTestingModule({ - declarations: [DecisionDialogComponent, MomentPipe, StartOfDayPipe], - providers: [ - { - provide: NoticeOfIntentDecisionService, - useValue: {}, - }, - { - provide: NoticeOfIntentModificationService, - useValue: mockNOIModificationService, - }, - { provide: MAT_DIALOG_DATA, useValue: { meetingType: { code: 'fake', label: 'fake' }, outcomes: [] } }, - { provide: MatDialogRef, useValue: {} }, - ], - imports: [MatDialogModule, MatSnackBarModule, ReactiveFormsModule, NgSelectModule, MatButtonToggleModule], - schemas: [NO_ERRORS_SCHEMA], - }).compileComponents(); - - fixture = TestBed.createComponent(DecisionDialogComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-dialog/decision-dialog.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-dialog/decision-dialog.component.ts deleted file mode 100644 index a3aa9ceb03..0000000000 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-dialog/decision-dialog.component.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { Component, Inject, OnInit } from '@angular/core'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import moment from 'moment'; -import { - CreateNoticeOfIntentDecisionDto, - NoticeOfIntentDecisionDto, - NoticeOfIntentDecisionOutcomeCodeDto, -} from '../../../../services/notice-of-intent/decision/notice-of-intent-decision.dto'; -import { NoticeOfIntentDecisionService } from '../../../../services/notice-of-intent/decision/notice-of-intent-decision.service'; -import { NoticeOfIntentModificationDto } from '../../../../services/notice-of-intent/notice-of-intent-modification/notice-of-intent-modification.dto'; -import { NoticeOfIntentModificationService } from '../../../../services/notice-of-intent/notice-of-intent-modification/notice-of-intent-modification.service'; -import { formatDateForApi } from '../../../../shared/utils/api-date-formatter'; - -export enum PostDecisionType { - Modification = 'modification', - Reconsideration = 'reconsideration', -} - -type MappedPostDecision = { - label: string; - uuid: string; - type: PostDecisionType; -}; - -@Component({ - selector: 'app-notice-of-intent-decision-dialog', - templateUrl: './decision-dialog.component.html', - styleUrls: ['./decision-dialog.component.scss'], -}) -export class DecisionDialogComponent implements OnInit { - isLoading = false; - isEdit = false; - minDate = new Date(0); - - postDecisions: MappedPostDecision[] = []; - outcomes: NoticeOfIntentDecisionOutcomeCodeDto[] = []; - - resolutionYears: number[] = []; - - form = new FormGroup({ - outcome: new FormControl(null, [Validators.required]), - date: new FormControl(undefined, [Validators.required]), - decisionMaker: new FormControl('CEO Delegate', [Validators.required]), - decisionMakerName: new FormControl(null, []), - postDecision: new FormControl({ disabled: true, value: null }), - resolutionNumber: new FormControl(null, [Validators.required]), - resolutionYear: new FormControl(null, [Validators.required]), - auditDate: new FormControl(null), - }); - - constructor( - @Inject(MAT_DIALOG_DATA) - public data: { - isFirstDecision: boolean; - fileNumber: string; - outcomes: NoticeOfIntentDecisionOutcomeCodeDto[]; - minDate?: Date; - existingDecision?: NoticeOfIntentDecisionDto; - }, - private dialogRef: MatDialogRef, - private decisionService: NoticeOfIntentDecisionService, - private noticeOfIntentModificationService: NoticeOfIntentModificationService - ) { - if (data.minDate) { - this.minDate = data.minDate; - } - - if (!data.isFirstDecision) { - this.form.controls.postDecision.enable(); - this.form.controls.postDecision.setValidators([Validators.required]); - this.loadModifications(); - - this.noticeOfIntentModificationService.$modifications.subscribe((modifications) => { - this.mapModifications(modifications, data.existingDecision); - }); - } - - this.outcomes = data.outcomes; - if (data.existingDecision) { - this.patchFormWithExistingData(data.existingDecision); - } - } - - ngOnInit(): void { - const year = moment('1974'); - const currentYear = moment().year(); - while (year.year() <= currentYear) { - this.resolutionYears.push(year.year()); - year.add(1, 'year'); - } - this.resolutionYears.reverse(); - if (!this.isEdit) { - this.form.patchValue({ - resolutionYear: currentYear, - }); - } - } - - async onSubmit() { - this.isLoading = true; - const { - date, - outcome, - decisionMaker, - decisionMakerName, - resolutionNumber, - resolutionYear, - auditDate, - postDecision, - } = this.form.getRawValue(); - - const data: CreateNoticeOfIntentDecisionDto = { - date: formatDateForApi(date!), - resolutionNumber: parseInt(resolutionNumber!), - resolutionYear: resolutionYear!, - auditDate: auditDate ? formatDateForApi(auditDate) : auditDate, - outcomeCode: outcome!, - decisionMaker: decisionMaker!, - decisionMakerName: decisionMakerName!, - fileNumber: this.data.fileNumber, - modifiesUuid: postDecision ?? undefined, - isDraft: false, - }; - - try { - if (this.data.existingDecision) { - await this.decisionService.update(this.data.existingDecision.uuid, data); - } else { - await this.decisionService.create(data); - } - this.dialogRef.close(true); - } finally { - this.isLoading = false; - } - } - - private patchFormWithExistingData(existingDecision: NoticeOfIntentDecisionDto) { - this.isEdit = true; - this.form.patchValue({ - outcome: existingDecision.outcome.code, - decisionMaker: existingDecision.decisionMaker, - decisionMakerName: existingDecision.decisionMakerName, - date: new Date(existingDecision.date!), - resolutionYear: existingDecision.resolutionYear, - resolutionNumber: existingDecision.resolutionNumber.toString(10), - auditDate: existingDecision.auditDate ? new Date(existingDecision.auditDate) : undefined, - postDecision: existingDecision.modifies?.uuid, - }); - } - - async loadModifications() { - await this.noticeOfIntentModificationService.fetchByFileNumber(this.data.fileNumber); - } - - private mapModifications( - modifications: NoticeOfIntentModificationDto[], - existingDecision?: NoticeOfIntentDecisionDto - ) { - this.postDecisions = modifications - .filter( - (modification) => - (existingDecision && existingDecision.modifies?.uuid === modification.uuid) || - (modification.reviewOutcome.code === 'PRC' && modification.resultingDecision === null) - ) - .map((modification, index) => ({ - label: `Modification Request #${modifications.length - index} - ${modification.modifiesDecisions - .map((dec) => `#${dec.resolutionNumber}/${dec.resolutionYear}`) - .join(', ')}`, - uuid: modification.uuid, - type: PostDecisionType.Modification, - })); - } -} diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v1/decision-v1.component.html b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v1/decision-v1.component.html deleted file mode 100644 index 4609dbca54..0000000000 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v1/decision-v1.component.html +++ /dev/null @@ -1,105 +0,0 @@ -
-

Decision

-
- -
-
-
-
No Decisions
-
-
-
- -
-
-
-
- Modified By:  - {{ decision.modifiedByResolutions.join(', ') }} - N/A -
-
- -
- - - -
-
-
- Decision #{{ decisions.length - i }} - - - calendar_month - {{ noticeOfIntent.activeDays >= MAX_ACTIVE_DAYS ? '61+' : noticeOfIntent.activeDays }} - - - - - - Res #{{ decision.resolutionNumber }}/{{ decision.resolutionYear }} -
-
-
-
Decision Date
- {{ decision.date! | momentFormat }} -
-
-
Decision Outcome
- {{ decision.outcome.label }} -
-
-
Decision Maker
- {{ decision.decisionMaker }} -
-
-
Decision Maker Name
- {{ decision.decisionMakerName ?? 'No Data' }} -
-
-
Audit Date
- {{ decision.auditDate | momentFormat }} - - - -
-
-
Documents
-
-
File Name
-
File Upload Date
-
File Actions
-
No Documents
- - -
{{ document.uploadedAt | momentFormat }}
-
- - -
-
-
-
-
-
diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v1/decision-v1.component.scss b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v1/decision-v1.component.scss deleted file mode 100644 index f80d0c776b..0000000000 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v1/decision-v1.component.scss +++ /dev/null @@ -1,142 +0,0 @@ -@use '../../../../../styles/colors'; - -h3 { - margin-bottom: 24px !important; -} - -section { - margin-bottom: 64px; -} - -.decision-container { - position: relative; -} - -.decision { - margin: 24px 0; - box-shadow: 0 2px 8px 1px rgba(0, 0, 0, 0.25); -} - -.loading-overlay { - position: absolute; - z-index: 2; - background-color: colors.$grey; - opacity: 0.4; - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; -} - -.days { - display: inline-block; - margin-right: 16px; - margin-left: 16px; - - .mat-icon { - font-size: 19px !important; - line-height: 21px !important; - width: 19px; - vertical-align: middle; - } -} - -.post-decisions { - padding: 6px 32px; - background-color: colors.$grey-light; - grid-template-columns: 1fr 1fr; - display: grid; - min-height: 36px; - text-transform: uppercase; - border-radius: 4px 4px 0 0; - box-shadow: -1px 1px 4px rgba(0, 0, 0, 0.25); -} - -.decision-menu { - position: absolute; - top: 0; - right: 0; - height: 36px; - background: colors.$accent-color; - box-shadow: -1px 1px 4px rgba(0, 0, 0, 0.25); - border-radius: 0 4px 0 10px; - - button { - color: colors.$white; - width: 36px; - height: 36px; - line-height: 36px; - } - - mat-icon { - position: absolute; - top: 8px; - left: 8px; - font-size: 21px; - width: 20px; - height: 36px; - } -} - -.decision-padding { - padding: 18px 32px 32px 32px; -} - -.decision-content { - margin-top: 16px; - margin-bottom: 32px; - margin-right: 100px; - display: grid; - grid-template-columns: 1fr 1fr; - grid-row-gap: 24px; - - .subheading2 { - margin-bottom: 6px !important; - } - - & > div { - font-size: 16px; - } -} - -.decision-documents { - margin-top: 4px; - display: grid; - grid-template-columns: 1fr 1fr 100px; - grid-row-gap: 4px; - - div { - display: flex; - align-items: center; - font-size: 16px !important; - } -} - -.file-actions { - margin-left: -12px; -} - -.delete-file-icon { - color: colors.$error-color; -} - -.no-decisions { - margin-top: 16px; - display: flex; - align-items: center; - justify-content: center; - background-color: colors.$grey-light; - height: 72px; -} - -.no-files { - grid-column: 1/4; - margin-top: 16px; - padding: 16px; - display: flex; - align-items: center; - justify-content: center; - text-align: center; - background-color: colors.$grey-light; -} diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v1/decision-v1.component.spec.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v1/decision-v1.component.spec.ts deleted file mode 100644 index 0ea55942b9..0000000000 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v1/decision-v1.component.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MatDialog, MatDialogRef } from '@angular/material/dialog'; -import { MatSnackBarModule } from '@angular/material/snack-bar'; -import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { BehaviorSubject } from 'rxjs'; -import { NoticeOfIntentDecisionService } from '../../../../services/notice-of-intent/decision/notice-of-intent-decision.service'; -import { NoticeOfIntentDetailService } from '../../../../services/notice-of-intent/notice-of-intent-detail.service'; -import { NoticeOfIntentDto } from '../../../../services/notice-of-intent/notice-of-intent.dto'; -import { ToastService } from '../../../../services/toast/toast.service'; -import { ConfirmationDialogService } from '../../../../shared/confirmation-dialog/confirmation-dialog.service'; - -import { DecisionV1Component } from './decision-v1.component'; - -describe('DecisionV1Component', () => { - let component: DecisionV1Component; - let fixture: ComponentFixture; - let mockNOIDetailService: DeepMocked; - - beforeEach(async () => { - mockNOIDetailService = createMock(); - mockNOIDetailService.$noticeOfIntent = new BehaviorSubject(undefined); - - await TestBed.configureTestingModule({ - imports: [MatSnackBarModule], - declarations: [DecisionV1Component], - providers: [ - { - provide: NoticeOfIntentDetailService, - useValue: mockNOIDetailService, - }, - { - provide: NoticeOfIntentDecisionService, - useValue: {}, - }, - { - provide: MatDialogRef, - useValue: {}, - }, - { - provide: ConfirmationDialogService, - useValue: {}, - }, - { - provide: ToastService, - useValue: {}, - }, - { - provide: MatDialog, - useValue: {}, - }, - ], - schemas: [NO_ERRORS_SCHEMA], - }).compileComponents(); - - fixture = TestBed.createComponent(DecisionV1Component); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v1/decision-v1.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v1/decision-v1.component.ts deleted file mode 100644 index 309c2b87c5..0000000000 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v1/decision-v1.component.ts +++ /dev/null @@ -1,208 +0,0 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; -import { MatDialog } from '@angular/material/dialog'; -import { Subject, takeUntil } from 'rxjs'; -import { - NoticeOfIntentDecisionDto, - NoticeOfIntentDecisionOutcomeCodeDto, -} from '../../../../services/notice-of-intent/decision/notice-of-intent-decision.dto'; -import { NoticeOfIntentDecisionService } from '../../../../services/notice-of-intent/decision/notice-of-intent-decision.service'; -import { NoticeOfIntentDetailService } from '../../../../services/notice-of-intent/notice-of-intent-detail.service'; -import { NoticeOfIntentDto } from '../../../../services/notice-of-intent/notice-of-intent.dto'; -import { ToastService } from '../../../../services/toast/toast.service'; -import { MODIFICATION_TYPE_LABEL } from '../../../../shared/application-type-pill/application-type-pill.constants'; -import { ConfirmationDialogService } from '../../../../shared/confirmation-dialog/confirmation-dialog.service'; -import { formatDateForApi } from '../../../../shared/utils/api-date-formatter'; -import { DecisionDialogComponent } from '../decision-dialog/decision-dialog.component'; - -type LoadingDecision = NoticeOfIntentDecisionDto & { - modifiedByResolutions: string[]; - loading: boolean; -}; - -@Component({ - selector: 'app-noi-decision-v1', - templateUrl: './decision-v1.component.html', - styleUrls: ['./decision-v1.component.scss'], -}) -export class DecisionV1Component implements OnInit, OnDestroy { - $destroy = new Subject(); - fileNumber: string = ''; - decisionDate: number | undefined; - decisions: LoadingDecision[] = []; - outcomes: NoticeOfIntentDecisionOutcomeCodeDto[] = []; - isPaused = true; - MAX_ACTIVE_DAYS = 61; - - noticeOfIntent: NoticeOfIntentDto | undefined; - modificationLabel = MODIFICATION_TYPE_LABEL; - - constructor( - public dialog: MatDialog, - private noticeOfIntentDetailService: NoticeOfIntentDetailService, - private decisionService: NoticeOfIntentDecisionService, - private toastService: ToastService, - private confirmationDialogService: ConfirmationDialogService - ) {} - - ngOnInit(): void { - this.noticeOfIntentDetailService.$noticeOfIntent.pipe(takeUntil(this.$destroy)).subscribe((noticeOfIntent) => { - if (noticeOfIntent) { - this.fileNumber = noticeOfIntent.fileNumber; - this.decisionDate = noticeOfIntent.decisionDate; - this.isPaused = noticeOfIntent.paused; - this.loadDecisions(noticeOfIntent.fileNumber); - this.noticeOfIntent = noticeOfIntent; - } - }); - } - - async loadDecisions(fileNumber: string) { - const codes = await this.decisionService.fetchCodes(); - this.outcomes = codes.outcomes; - - const loadedDecision = await this.decisionService.fetchByFileNumber(fileNumber); - - this.decisions = loadedDecision.map((decision) => ({ - ...decision, - loading: false, - modifiedByResolutions: decision.modifiedBy?.flatMap((r) => r.linkedResolutions) || [], - })); - } - - onCreate() { - let minDate = new Date(0); - if (this.decisions.length > 0) { - minDate = new Date(this.decisions[this.decisions.length - 1].date!); - } - - this.dialog - .open(DecisionDialogComponent, { - minWidth: '600px', - maxWidth: '900px', - maxHeight: '80vh', - width: '90%', - autoFocus: false, - data: { - isFirstDecision: this.decisions.length === 0, - minDate, - fileNumber: this.fileNumber, - outcomes: this.outcomes, - }, - }) - .afterClosed() - .subscribe((didCreate) => { - if (didCreate) { - this.noticeOfIntentDetailService.load(this.fileNumber); - } - }); - } - - onEdit(decision: LoadingDecision) { - const decisionIndex = this.decisions.indexOf(decision); - let minDate = new Date(0); - if (decisionIndex !== this.decisions.length - 1) { - minDate = new Date(this.decisions[this.decisions.length - 1].date!); - } - this.dialog - .open(DecisionDialogComponent, { - minWidth: '600px', - maxWidth: '900px', - maxHeight: '80vh', - width: '90%', - autoFocus: false, - data: { - isFirstDecision: decisionIndex === this.decisions.length - 1, - minDate, - fileNumber: this.fileNumber, - outcomes: this.outcomes, - existingDecision: decision, - }, - }) - .afterClosed() - .subscribe((didModify) => { - if (didModify) { - this.noticeOfIntentDetailService.load(this.fileNumber); - } - }); - } - - async deleteDecision(uuid: string) { - this.confirmationDialogService - .openDialog({ - body: 'Are you sure you want to delete the selected decision?', - }) - .subscribe(async (confirmed) => { - if (confirmed) { - this.decisions = this.decisions.map((decision) => { - return { - ...decision, - loading: decision.uuid === uuid, - }; - }); - await this.decisionService.delete(uuid); - await this.noticeOfIntentDetailService.load(this.fileNumber); - this.toastService.showSuccessToast('Decision deleted'); - } - }); - } - - async attachFile(decisionUuid: string, event: Event) { - this.decisions = this.decisions.map((decision) => { - return { - ...decision, - loading: decision.uuid === decisionUuid, - }; - }); - const element = event.target as HTMLInputElement; - const fileList = element.files; - if (fileList && fileList.length > 0) { - const file: File = fileList[0]; - const uploadedFile = await this.decisionService.uploadFile(decisionUuid, file); - if (uploadedFile) { - await this.loadDecisions(this.fileNumber); - } - } - } - - async downloadFile(decisionUuid: string, decisionDocumentUuid: string, fileName: string) { - await this.decisionService.downloadFile(decisionUuid, decisionDocumentUuid, fileName, false); - } - - async openFile(decisionUuid: string, decisionDocumentUuid: string, fileName: string) { - await this.decisionService.downloadFile(decisionUuid, decisionDocumentUuid, fileName); - } - - async deleteFile(decisionUuid: string, decisionDocumentUuid: string, fileName: string) { - this.confirmationDialogService - .openDialog({ - body: `Are you sure you want to delete the file ${fileName}?`, - }) - .subscribe(async (confirmed) => { - if (confirmed) { - this.decisions = this.decisions.map((decision) => { - return { - ...decision, - loading: decision.uuid === decisionUuid, - }; - }); - - await this.decisionService.deleteFile(decisionUuid, decisionDocumentUuid); - await this.loadDecisions(this.fileNumber); - this.toastService.showSuccessToast('File deleted'); - } - }); - } - - async onSaveAuditDate(decisionUuid: string, auditReviewDate: number) { - await this.decisionService.update(decisionUuid, { - isDraft: false, - auditDate: formatDateForApi(auditReviewDate), - }); - await this.loadDecisions(this.fileNumber); - } - - ngOnDestroy(): void { - this.$destroy.next(); - this.$destroy.complete(); - } -} diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/basic/basic.component.spec.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/basic/basic.component.spec.ts index f31dc65a8a..26f6a328a1 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/basic/basic.component.spec.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/basic/basic.component.spec.ts @@ -1,6 +1,6 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { NoticeOfIntentDecisionComponentDto } from '../../../../../../services/notice-of-intent/decision/notice-of-intent-decision.dto'; +import { NoticeOfIntentDecisionComponentDto } from '../../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; import { BasicComponent } from './basic.component'; diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/basic/basic.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/basic/basic.component.ts index 5e6df76c2e..3231e2c1e8 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/basic/basic.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/basic/basic.component.ts @@ -1,5 +1,5 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { NoticeOfIntentDecisionComponentDto } from '../../../../../../services/notice-of-intent/decision/notice-of-intent-decision.dto'; +import { NoticeOfIntentDecisionComponentDto } from '../../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; @Component({ selector: 'app-noi-basic', diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/pfrs/pfrs.component.spec.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/pfrs/pfrs.component.spec.ts index c1a21e1f61..6c97006a5a 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/pfrs/pfrs.component.spec.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/pfrs/pfrs.component.spec.ts @@ -1,6 +1,6 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { NoticeOfIntentDecisionComponentDto } from '../../../../../../services/notice-of-intent/decision/notice-of-intent-decision.dto'; +import { NoticeOfIntentDecisionComponentDto } from '../../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; import { PfrsComponent } from './pfrs.component'; diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/pfrs/pfrs.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/pfrs/pfrs.component.ts index 8b68a6244b..5f26f01cfe 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/pfrs/pfrs.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/pfrs/pfrs.component.ts @@ -1,5 +1,5 @@ import { Component, Input } from '@angular/core'; -import { NoticeOfIntentDecisionComponentDto } from '../../../../../../services/notice-of-intent/decision/notice-of-intent-decision.dto'; +import { NoticeOfIntentDecisionComponentDto } from '../../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; @Component({ selector: 'app-noi-pfrs', diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/pofo/pofo.component.spec.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/pofo/pofo.component.spec.ts index f0e65a2b36..8436c3cc65 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/pofo/pofo.component.spec.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/pofo/pofo.component.spec.ts @@ -1,6 +1,6 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { NoticeOfIntentDecisionComponentDto } from '../../../../../../services/notice-of-intent/decision/notice-of-intent-decision.dto'; +import { NoticeOfIntentDecisionComponentDto } from '../../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; import { PofoComponent } from './pofo.component'; diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/pofo/pofo.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/pofo/pofo.component.ts index de962933fc..31bfe7c3f5 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/pofo/pofo.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/pofo/pofo.component.ts @@ -1,5 +1,5 @@ import { Component, Input } from '@angular/core'; -import { NoticeOfIntentDecisionComponentDto } from '../../../../../../services/notice-of-intent/decision/notice-of-intent-decision.dto'; +import { NoticeOfIntentDecisionComponentDto } from '../../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; @Component({ selector: 'app-noi-pofo', diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/roso/roso.component.spec.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/roso/roso.component.spec.ts index d89da3b470..54506c6e35 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/roso/roso.component.spec.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/roso/roso.component.spec.ts @@ -1,6 +1,6 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { NoticeOfIntentDecisionComponentDto } from '../../../../../../services/notice-of-intent/decision/notice-of-intent-decision.dto'; +import { NoticeOfIntentDecisionComponentDto } from '../../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; import { RosoComponent } from './roso.component'; diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/roso/roso.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/roso/roso.component.ts index 4a9ef8fd27..295501de9e 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/roso/roso.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-component/roso/roso.component.ts @@ -1,5 +1,5 @@ import { Component, Input } from '@angular/core'; -import { NoticeOfIntentDecisionComponentDto } from '../../../../../../services/notice-of-intent/decision/notice-of-intent-decision.dto'; +import { NoticeOfIntentDecisionComponentDto } from '../../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; @Component({ selector: 'app-noi-roso', diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-documents/decision-documents.component.spec.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-documents/decision-documents.component.spec.ts index 2eb09e08dc..dea91a3338 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-documents/decision-documents.component.spec.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-documents/decision-documents.component.spec.ts @@ -4,7 +4,7 @@ import { MatDialog } from '@angular/material/dialog'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { BehaviorSubject } from 'rxjs'; import { NoticeOfIntentDecisionV2Service } from '../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision-v2.service'; -import { NoticeOfIntentDecisionDto } from '../../../../../services/notice-of-intent/decision/notice-of-intent-decision.dto'; +import { NoticeOfIntentDecisionDto } from '../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; import { ToastService } from '../../../../../services/toast/toast.service'; import { DecisionDocumentsComponent } from './decision-documents.component'; diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-documents/decision-documents.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-documents/decision-documents.component.ts index 4024972c59..fdf8582b9e 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-documents/decision-documents.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-documents/decision-documents.component.ts @@ -7,7 +7,7 @@ import { NoticeOfIntentDecisionV2Service } from '../../../../../services/notice- import { NoticeOfIntentDecisionDocumentDto, NoticeOfIntentDecisionDto, -} from '../../../../../services/notice-of-intent/decision/notice-of-intent-decision.dto'; +} from '../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; import { ToastService } from '../../../../../services/toast/toast.service'; import { ConfirmationDialogService } from '../../../../../shared/confirmation-dialog/confirmation-dialog.service'; import { DecisionDocumentUploadDialogComponent } from '../decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component'; diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/decision-component.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/decision-component.component.ts index 04bb05687d..d2b0029504 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/decision-component.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/decision-component.component.ts @@ -8,7 +8,7 @@ import { PfrsDecisionComponentDto, PofoDecisionComponentDto, RosoDecisionComponentDto, -} from '../../../../../../../services/notice-of-intent/decision/notice-of-intent-decision.dto'; +} from '../../../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; import { ToastService } from '../../../../../../../services/toast/toast.service'; import { AG_CAP_OPTIONS, AG_CAP_SOURCE_OPTIONS } from '../../../../../../../shared/dto/ag-cap.types.dto'; import { formatDateForApi } from '../../../../../../../shared/utils/api-date-formatter'; diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-components.component.spec.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-components.component.spec.ts index f76e8a6cbd..e34d4b2d46 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-components.component.spec.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-components.component.spec.ts @@ -4,7 +4,7 @@ import { MatMenuModule } from '@angular/material/menu'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { BehaviorSubject } from 'rxjs'; import { NoticeOfIntentDecisionV2Service } from '../../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision-v2.service'; -import { NoticeOfIntentDecisionDto } from '../../../../../../services/notice-of-intent/decision/notice-of-intent-decision.dto'; +import { NoticeOfIntentDecisionDto } from '../../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; import { NoticeOfIntentDetailService } from '../../../../../../services/notice-of-intent/notice-of-intent-detail.service'; import { NoticeOfIntentSubmissionService } from '../../../../../../services/notice-of-intent/notice-of-intent-submission/notice-of-intent-submission.service'; import { NoticeOfIntentDto } from '../../../../../../services/notice-of-intent/notice-of-intent.dto'; @@ -24,7 +24,7 @@ describe('DecisionComponentsComponent', () => { beforeEach(async () => { mockNoticeOfIntentDecisionV2Service = createMock(); mockNoticeOfIntentDecisionV2Service.$decision = new BehaviorSubject( - undefined + undefined, ); mockToastService = createMock(); diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-components.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-components.component.ts index 822e2d4ec4..123a8ac402 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-components.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-components.component.ts @@ -15,7 +15,7 @@ import { NoticeOfIntentDecisionCodesDto, NoticeOfIntentDecisionComponentDto, NoticeOfIntentDecisionComponentTypeDto, -} from '../../../../../../services/notice-of-intent/decision/notice-of-intent-decision.dto'; +} from '../../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; import { NoticeOfIntentDetailService } from '../../../../../../services/notice-of-intent/notice-of-intent-detail.service'; import { NoticeOfIntentSubmissionService } from '../../../../../../services/notice-of-intent/notice-of-intent-submission/notice-of-intent-submission.service'; import { diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-conditions.component.spec.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-conditions.component.spec.ts index 0c274c5231..88377045b9 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-conditions.component.spec.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-conditions.component.spec.ts @@ -7,7 +7,7 @@ import { NoticeOfIntentDecisionV2Service } from '../../../../../../services/noti import { NoticeOfIntentDecisionDto, NoticeOfIntentDecisionWithLinkedResolutionDto, -} from '../../../../../../services/notice-of-intent/decision/notice-of-intent-decision.dto'; +} from '../../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; import { ConfirmationDialogService } from '../../../../../../shared/confirmation-dialog/confirmation-dialog.service'; import { DecisionConditionsComponent } from './decision-conditions.component'; diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-conditions.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-conditions.component.ts index c5823ada0e..7371165a2a 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-conditions.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-conditions.component.ts @@ -17,7 +17,7 @@ import { NoticeOfIntentDecisionConditionDto, NoticeOfIntentDecisionDto, UpdateNoticeOfIntentDecisionConditionDto, -} from '../../../../../../services/notice-of-intent/decision/notice-of-intent-decision.dto'; +} from '../../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; import { ConfirmationDialogService } from '../../../../../../shared/confirmation-dialog/confirmation-dialog.service'; import { DecisionConditionComponent } from './decision-condition/decision-condition.component'; @@ -49,7 +49,7 @@ export class DecisionConditionsComponent implements OnInit, OnChanges, OnDestroy constructor( private decisionService: NoticeOfIntentDecisionV2Service, - private confirmationDialogService: ConfirmationDialogService + private confirmationDialogService: ConfirmationDialogService, ) {} ngOnInit(): void { @@ -63,10 +63,10 @@ export class DecisionConditionsComponent implements OnInit, OnChanges, OnDestroy decision.uuid, decision.components, decision.resolutionNumber, - decision.resolutionYear + decision.resolutionYear, ); const otherDecisionsComponents = mappedComponents.filter( - (component) => component.decisionUuid !== selectedDecision?.uuid + (component) => component.decisionUuid !== selectedDecision?.uuid, ); otherDecisionComponents.push(...otherDecisionsComponents); } @@ -78,7 +78,7 @@ export class DecisionConditionsComponent implements OnInit, OnChanges, OnDestroy selectedDecision.uuid, this.components, selectedDecision.resolutionNumber, - selectedDecision.resolutionYear + selectedDecision.resolutionYear, ); this.selectableComponents = [...this.allComponents, ...updatedComponents]; @@ -128,7 +128,7 @@ export class DecisionConditionsComponent implements OnInit, OnChanges, OnDestroy this.decision.uuid, this.components, this.decision.resolutionNumber, - this.decision.resolutionYear + this.decision.resolutionYear, ); this.selectableComponents = [...this.allComponents, ...updatedComponents]; const validComponentIds = this.selectableComponents.map((component) => component.tempId); @@ -136,7 +136,7 @@ export class DecisionConditionsComponent implements OnInit, OnChanges, OnDestroy this.mappedConditions = this.mappedConditions.map((condition) => { if (condition.componentsToCondition) { condition.componentsToCondition = condition.componentsToCondition.filter((component) => - validComponentIds.includes(component.tempId) + validComponentIds.includes(component.tempId), ); } return condition; @@ -153,8 +153,9 @@ export class DecisionConditionsComponent implements OnInit, OnChanges, OnDestroy private populateConditions(conditions: NoticeOfIntentDecisionConditionDto[]) { this.mappedConditions = conditions.map((condition) => { const selectedComponents = this.selectableComponents - .filter((component) => - condition.components?.map((conditionComponent) => conditionComponent.uuid).includes(component.uuid) + .filter( + (component) => + condition.components?.map((conditionComponent) => conditionComponent.uuid).includes(component.uuid), ) .map((e) => ({ componentDecisionUuid: e.decisionUuid, @@ -174,11 +175,11 @@ export class DecisionConditionsComponent implements OnInit, OnChanges, OnDestroy decisionUuid: string, components: NoticeOfIntentDecisionComponentDto[], decisionNumber: number | null, - decisionYear: number | null + decisionYear: number | null, ) { return components.map((component) => { const matchingType = this.codes.decisionComponentTypes.find( - (type) => type.code === component.noticeOfIntentDecisionComponentTypeCode + (type) => type.code === component.noticeOfIntentDecisionComponentTypeCode, ); return { uuid: component.uuid, diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts index d0a844876d..c87d60126d 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts @@ -3,7 +3,7 @@ import { Component, Inject, OnInit } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { NoticeOfIntentDecisionV2Service } from '../../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision-v2.service'; -import { NoticeOfIntentDecisionDocumentDto } from '../../../../../../services/notice-of-intent/decision/notice-of-intent-decision.dto'; +import { NoticeOfIntentDecisionDocumentDto } from '../../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; import { ToastService } from '../../../../../../services/toast/toast.service'; import { DOCUMENT_SOURCE } from '../../../../../../shared/document/document.dto'; import { splitExtension } from '../../../../../../shared/utils/file'; diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.ts index 25933b3ad8..a79f1e477d 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.ts @@ -14,7 +14,7 @@ import { NoticeOfIntentDecisionDto, NoticeOfIntentDecisionOutcomeCodeDto, UpdateNoticeOfIntentDecisionConditionDto, -} from '../../../../../services/notice-of-intent/decision/notice-of-intent-decision.dto'; +} from '../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; import { NoticeOfIntentDetailService } from '../../../../../services/notice-of-intent/notice-of-intent-detail.service'; import { NoticeOfIntentModificationDto } from '../../../../../services/notice-of-intent/notice-of-intent-modification/notice-of-intent-modification.dto'; import { NoticeOfIntentModificationService } from '../../../../../services/notice-of-intent/notice-of-intent-modification/notice-of-intent-modification.service'; diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-v2.component.spec.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-v2.component.spec.ts index a7c5031a77..5ea1e1d7dc 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-v2.component.spec.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-v2.component.spec.ts @@ -11,7 +11,7 @@ import { NoticeOfIntentDecisionV2Service } from '../../../../services/notice-of- import { NoticeOfIntentDecisionDto, NoticeOfIntentDecisionWithLinkedResolutionDto, -} from '../../../../services/notice-of-intent/decision/notice-of-intent-decision.dto'; +} from '../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; import { NoticeOfIntentDetailService } from '../../../../services/notice-of-intent/notice-of-intent-detail.service'; import { NoticeOfIntentDto } from '../../../../services/notice-of-intent/notice-of-intent.dto'; import { ToastService } from '../../../../services/toast/toast.service'; diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-v2.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-v2.component.ts index db779c6964..7668769580 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-v2.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-v2.component.ts @@ -8,7 +8,7 @@ import { NOI_DECISION_COMPONENT_TYPE, NoticeOfIntentDecisionDto, NoticeOfIntentDecisionOutcomeCodeDto, -} from '../../../../services/notice-of-intent/decision/notice-of-intent-decision.dto'; +} from '../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; import { NoticeOfIntentDetailService } from '../../../../services/notice-of-intent/notice-of-intent-detail.service'; import { NoticeOfIntentDto } from '../../../../services/notice-of-intent/notice-of-intent.dto'; import { ToastService } from '../../../../services/toast/toast.service'; @@ -60,7 +60,7 @@ export class DecisionV2Component implements OnInit, OnDestroy { private noticeOfIntentDecisionComponentService: NoticeOfIntentDecisionComponentService, private router: Router, private activatedRouter: ActivatedRoute, - private elementRef: ElementRef + private elementRef: ElementRef, ) {} ngOnInit(): void { diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/release-dialog/release-dialog.component.spec.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/release-dialog/release-dialog.component.spec.ts index 2bca511f5d..45819d617a 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/release-dialog/release-dialog.component.spec.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/release-dialog/release-dialog.component.spec.ts @@ -4,7 +4,7 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { BehaviorSubject } from 'rxjs'; import { NoticeOfIntentDecisionV2Service } from '../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision-v2.service'; -import { NoticeOfIntentDecisionDto } from '../../../../../services/notice-of-intent/decision/notice-of-intent-decision.dto'; +import { NoticeOfIntentDecisionDto } from '../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; import { NoticeOfIntentSubmissionStatusService } from '../../../../../services/notice-of-intent/notice-of-intent-submission-status/notice-of-intent-submission-status.service'; import { ReleaseDialogComponent } from './release-dialog.component'; diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision.component.html b/alcs-frontend/src/app/features/notice-of-intent/decision/decision.component.html index 93d6cc768d..32a52b94d7 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision.component.html @@ -1,6 +1,5 @@
- - +
diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision.module.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision.module.ts index af64437137..141d9cd512 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision.module.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision.module.ts @@ -4,8 +4,6 @@ import { NgxMaskDirective } from 'ngx-mask'; import { SharedModule } from '../../../shared/shared.module'; import { ConditionComponent } from './conditions/condition/condition.component'; import { ConditionsComponent } from './conditions/conditions.component'; -import { DecisionDialogComponent } from './decision-dialog/decision-dialog.component'; -import { DecisionV1Component } from './decision-v1/decision-v1.component'; import { BasicComponent } from './decision-v2/decision-component/basic/basic.component'; import { PfrsComponent } from './decision-v2/decision-component/pfrs/pfrs.component'; import { PofoComponent } from './decision-v2/decision-component/pofo/pofo.component'; @@ -57,8 +55,6 @@ export const decisionChildRoutes = [ DecisionComponent, DecisionV2Component, DecisionInputV2Component, - DecisionV1Component, - DecisionDialogComponent, ReleaseDialogComponent, DecisionComponentComponent, DecisionComponentsComponent, diff --git a/alcs-frontend/src/app/features/notice-of-intent/overview/overview.component.spec.ts b/alcs-frontend/src/app/features/notice-of-intent/overview/overview.component.spec.ts index 80f525239d..af9c2be5ba 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/overview/overview.component.spec.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/overview/overview.component.spec.ts @@ -3,24 +3,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MatDialog } from '@angular/material/dialog'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { BehaviorSubject } from 'rxjs'; -import { NoticeOfIntentDecisionService } from '../../../services/notice-of-intent/decision/notice-of-intent-decision.service'; import { NoticeOfIntentDetailService } from '../../../services/notice-of-intent/notice-of-intent-detail.service'; +import { NoticeOfIntentSubmissionStatusService } from '../../../services/notice-of-intent/notice-of-intent-submission-status/notice-of-intent-submission-status.service'; import { NoticeOfIntentTimelineService } from '../../../services/notice-of-intent/notice-of-intent-timeline/notice-of-intent-timeline.service'; import { NoticeOfIntentDto } from '../../../services/notice-of-intent/notice-of-intent.dto'; import { ConfirmationDialogService } from '../../../shared/confirmation-dialog/confirmation-dialog.service'; import { OverviewComponent } from './overview.component'; -import { NoticeOfIntentSubmissionStatusService } from '../../../services/notice-of-intent/notice-of-intent-submission-status/notice-of-intent-submission-status.service'; describe('OverviewComponent', () => { let component: OverviewComponent; let fixture: ComponentFixture; let mockNOIDetailService: DeepMocked; - let mockNOIDecisionService: DeepMocked; beforeEach(async () => { - mockNOIDecisionService = createMock(); - mockNOIDetailService = createMock(); mockNOIDetailService.$noticeOfIntent = new BehaviorSubject(undefined); await TestBed.configureTestingModule({ diff --git a/alcs-frontend/src/app/features/notice-of-intent/post-decision/edit-modification-dialog/edit-modification-dialog.component.spec.ts b/alcs-frontend/src/app/features/notice-of-intent/post-decision/edit-modification-dialog/edit-modification-dialog.component.spec.ts index 8498c401e3..34d6157f91 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/post-decision/edit-modification-dialog/edit-modification-dialog.component.spec.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/post-decision/edit-modification-dialog/edit-modification-dialog.component.spec.ts @@ -2,7 +2,7 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { NoticeOfIntentDecisionService } from '../../../../services/notice-of-intent/decision/notice-of-intent-decision.service'; +import { NoticeOfIntentDecisionV2Service } from '../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision-v2.service'; import { NoticeOfIntentModificationService } from '../../../../services/notice-of-intent/notice-of-intent-modification/notice-of-intent-modification.service'; import { ToastService } from '../../../../services/toast/toast.service'; @@ -11,7 +11,7 @@ import { EditModificationDialogComponent } from './edit-modification-dialog.comp describe('EditModificationDialogComponent', () => { let component: EditModificationDialogComponent; let fixture: ComponentFixture; - let mockDecisionService: DeepMocked; + let mockDecisionService: DeepMocked; beforeEach(async () => { mockDecisionService = createMock(); @@ -24,7 +24,7 @@ describe('EditModificationDialogComponent', () => { useValue: {}, }, { - provide: NoticeOfIntentDecisionService, + provide: NoticeOfIntentDecisionV2Service, useValue: mockDecisionService, }, { diff --git a/alcs-frontend/src/app/features/notice-of-intent/post-decision/edit-modification-dialog/edit-modification-dialog.component.ts b/alcs-frontend/src/app/features/notice-of-intent/post-decision/edit-modification-dialog/edit-modification-dialog.component.ts index 3f27035cda..47d74ca649 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/post-decision/edit-modification-dialog/edit-modification-dialog.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/post-decision/edit-modification-dialog/edit-modification-dialog.component.ts @@ -1,7 +1,7 @@ import { Component, Inject, OnInit } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { NoticeOfIntentDecisionService } from '../../../../services/notice-of-intent/decision/notice-of-intent-decision.service'; +import { NoticeOfIntentDecisionV2Service } from '../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision-v2.service'; import { NoticeOfIntentModificationDto, NoticeOfIntentModificationUpdateDto, @@ -37,8 +37,8 @@ export class EditModificationDialogComponent implements OnInit { }, private dialogRef: MatDialogRef, private modificationService: NoticeOfIntentModificationService, - private decisionService: NoticeOfIntentDecisionService, - private toastService: ToastService + private decisionService: NoticeOfIntentDecisionV2Service, + private toastService: ToastService, ) { this.form.patchValue({ submittedDate: new Date(data.existingModification.submittedDate), diff --git a/alcs-frontend/src/app/features/search/planning-review-search-table/planning-review-search-table.component.html b/alcs-frontend/src/app/features/search/planning-review-search-table/planning-review-search-table.component.html index 3534373d59..8e329d8fa1 100644 --- a/alcs-frontend/src/app/features/search/planning-review-search-table/planning-review-search-table.component.html +++ b/alcs-frontend/src/app/features/search/planning-review-search-table/planning-review-search-table.component.html @@ -1,67 +1,67 @@
Type +
+ + + + - - - + - - - + + + - - - + + + - + - - - - - + + + +
Type {{ element.type?.label }} Document Name - {{ element.fileName }} + + Document Name + {{ element.fileName }} Source{{ element.source }}Source{{ element.source }} Upload Date{{ element.uploadedAt | date }}Upload Date{{ element.uploadedAt | date }} Actions - +
Documents will be visible here once provided by ALC
Documents will be visible here once provided by ALC
diff --git a/portal-frontend/src/app/features/public/application/alc-review/submission-documents/submission-documents.component.ts b/portal-frontend/src/app/features/public/application/alc-review/submission-documents/submission-documents.component.ts index 5364c65182..61a58bd1ce 100644 --- a/portal-frontend/src/app/features/public/application/alc-review/submission-documents/submission-documents.component.ts +++ b/portal-frontend/src/app/features/public/application/alc-review/submission-documents/submission-documents.component.ts @@ -5,6 +5,7 @@ import { Subject } from 'rxjs'; import { PublicApplicationSubmissionDto } from '../../../../../services/public/public-application.dto'; import { PublicDocumentDto } from '../../../../../services/public/public.dto'; import { PublicService } from '../../../../../services/public/public.service'; +import { openFileInline } from '../../../../../shared/utils/file'; @Component({ selector: 'app-submission-documents[applicationSubmission]', @@ -29,10 +30,10 @@ export class PublicSubmissionDocumentsComponent implements OnInit, OnDestroy { this.dataSource = new MatTableDataSource(this.applicationDocuments); } - async openFile(uuid: string) { - const res = await this.publicService.getApplicationDownloadFileUrl(this.applicationSubmission.fileNumber, uuid); + async openFile(file: PublicDocumentDto) { + const res = await this.publicService.getApplicationOpenFileUrl(this.applicationSubmission.fileNumber, file.uuid); if (res) { - window.open(res.url, '_blank'); + openFileInline(res.url, file.fileName); } } diff --git a/portal-frontend/src/app/features/public/application/lfng-review/lfng-review.component.html b/portal-frontend/src/app/features/public/application/lfng-review/lfng-review.component.html index de4aafdf39..27051ce4c8 100644 --- a/portal-frontend/src/app/features/public/application/lfng-review/lfng-review.component.html +++ b/portal-frontend/src/app/features/public/application/lfng-review/lfng-review.component.html @@ -145,7 +145,7 @@

Attachments

Resolution Document
diff --git a/portal-frontend/src/app/features/public/application/lfng-review/lfng-review.component.ts b/portal-frontend/src/app/features/public/application/lfng-review/lfng-review.component.ts index c2a3ea1617..7846534630 100644 --- a/portal-frontend/src/app/features/public/application/lfng-review/lfng-review.component.ts +++ b/portal-frontend/src/app/features/public/application/lfng-review/lfng-review.component.ts @@ -7,6 +7,7 @@ import { import { PublicDocumentDto } from '../../../../services/public/public.dto'; import { PublicService } from '../../../../services/public/public.service'; import { DOCUMENT_TYPE } from '../../../../shared/dto/document.dto'; +import { openFileInline } from '../../../../shared/utils/file'; @Component({ selector: 'app-public-lfng-review', @@ -29,10 +30,10 @@ export class PublicLfngReviewComponent implements OnInit { ); } - async openFile(uuid: string) { - const res = await this.publicService.getApplicationOpenFileUrl(this.applicationSubmission.fileNumber, uuid); + async openFile(file: PublicDocumentDto) { + const res = await this.publicService.getApplicationOpenFileUrl(this.applicationSubmission.fileNumber, file.uuid); if (res) { - window.open(res.url, '_blank'); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/features/public/application/submission/cove-details/cove-details.component.html b/portal-frontend/src/app/features/public/application/submission/cove-details/cove-details.component.html index e48358fe16..dd124b497e 100644 --- a/portal-frontend/src/app/features/public/application/submission/cove-details/cove-details.component.html +++ b/portal-frontend/src/app/features/public/application/submission/cove-details/cove-details.component.html @@ -34,7 +34,7 @@
Proposal Map / Site Plan
- + {{ map.fileName }} diff --git a/portal-frontend/src/app/features/public/application/submission/cove-details/cove-details.component.ts b/portal-frontend/src/app/features/public/application/submission/cove-details/cove-details.component.ts index f094e9c5bd..3b0287891f 100644 --- a/portal-frontend/src/app/features/public/application/submission/cove-details/cove-details.component.ts +++ b/portal-frontend/src/app/features/public/application/submission/cove-details/cove-details.component.ts @@ -3,6 +3,7 @@ import { PublicApplicationSubmissionDto } from '../../../../../services/public/p import { PublicDocumentDto, PublicOwnerDto } from '../../../../../services/public/public.dto'; import { PublicService } from '../../../../../services/public/public.service'; import { DOCUMENT_TYPE } from '../../../../../shared/dto/document.dto'; +import { openFileInline } from '../../../../../shared/utils/file'; @Component({ selector: 'app-cove-details', @@ -21,8 +22,10 @@ export class CoveDetailsComponent { constructor(private publicService: PublicService) {} - async openFile(uuid: string) { - const res = await this.publicService.getApplicationOpenFileUrl(uuid, this.applicationSubmission.fileNumber); - window.open(res?.url, '_blank'); + async openFile(file: PublicDocumentDto) { + const res = await this.publicService.getApplicationOpenFileUrl(this.applicationSubmission.fileNumber, file.uuid); + if (res) { + openFileInline(res.url, file.fileName); + } } } diff --git a/portal-frontend/src/app/features/public/application/submission/excl-details/excl-details.component.html b/portal-frontend/src/app/features/public/application/submission/excl-details/excl-details.component.html index 244752b806..262530e5f8 100644 --- a/portal-frontend/src/app/features/public/application/submission/excl-details/excl-details.component.html +++ b/portal-frontend/src/app/features/public/application/submission/excl-details/excl-details.component.html @@ -34,7 +34,7 @@
Proposal Map / Site Plan
- + {{ map.fileName }} @@ -43,7 +43,7 @@
Report of Public Hearing
diff --git a/portal-frontend/src/app/features/public/application/submission/excl-details/excl-details.component.ts b/portal-frontend/src/app/features/public/application/submission/excl-details/excl-details.component.ts index a26e563831..f597ea401c 100644 --- a/portal-frontend/src/app/features/public/application/submission/excl-details/excl-details.component.ts +++ b/portal-frontend/src/app/features/public/application/submission/excl-details/excl-details.component.ts @@ -4,6 +4,7 @@ import { PublicApplicationSubmissionDto } from '../../../../../services/public/p import { PublicDocumentDto } from '../../../../../services/public/public.dto'; import { PublicService } from '../../../../../services/public/public.service'; import { DOCUMENT_TYPE } from '../../../../../shared/dto/document.dto'; +import { openFileInline } from '../../../../../shared/utils/file'; @Component({ selector: 'app-excl-details', @@ -25,10 +26,10 @@ export class ExclDetailsComponent { constructor(private publicService: PublicService) {} - async openFile(uuid: string) { - const res = await this.publicService.getApplicationOpenFileUrl(this.applicationSubmission.fileNumber, uuid); + async openFile(file: PublicDocumentDto) { + const res = await this.publicService.getApplicationOpenFileUrl(this.applicationSubmission.fileNumber, file.uuid); if (res) { - window.open(res?.url, '_blank'); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/features/public/application/submission/incl-details/incl-details.component.html b/portal-frontend/src/app/features/public/application/submission/incl-details/incl-details.component.html index 7ea70a788a..3473c9fc28 100644 --- a/portal-frontend/src/app/features/public/application/submission/incl-details/incl-details.component.html +++ b/portal-frontend/src/app/features/public/application/submission/incl-details/incl-details.component.html @@ -27,7 +27,7 @@
Proposal Map / Site Plan
- + {{ map.fileName }} @@ -48,7 +48,7 @@
Report of Public Hearing
- + {{ file.fileName }} diff --git a/portal-frontend/src/app/features/public/application/submission/incl-details/incl-details.component.ts b/portal-frontend/src/app/features/public/application/submission/incl-details/incl-details.component.ts index bbfc25ed99..d0b0a03bf1 100644 --- a/portal-frontend/src/app/features/public/application/submission/incl-details/incl-details.component.ts +++ b/portal-frontend/src/app/features/public/application/submission/incl-details/incl-details.component.ts @@ -4,6 +4,7 @@ import { PublicApplicationSubmissionDto } from '../../../../../services/public/p import { PublicDocumentDto } from '../../../../../services/public/public.dto'; import { PublicService } from '../../../../../services/public/public.service'; import { DOCUMENT_TYPE } from '../../../../../shared/dto/document.dto'; +import { openFileInline } from '../../../../../shared/utils/file'; @Component({ selector: 'app-incl-details', @@ -25,10 +26,10 @@ export class InclDetailsComponent { constructor(private router: Router, private publicService: PublicService) {} - async openFile(uuid: string) { - const res = await this.publicService.getApplicationOpenFileUrl(this.applicationSubmission.fileNumber, uuid); + async openFile(file: PublicDocumentDto) { + const res = await this.publicService.getApplicationOpenFileUrl(this.applicationSubmission.fileNumber, file.uuid); if (res) { - window.open(res?.url, '_blank'); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/features/public/application/submission/naru-details/naru-details.component.html b/portal-frontend/src/app/features/public/application/submission/naru-details/naru-details.component.html index 6c7539fe04..d19c9a205e 100644 --- a/portal-frontend/src/app/features/public/application/submission/naru-details/naru-details.component.html +++ b/portal-frontend/src/app/features/public/application/submission/naru-details/naru-details.component.html @@ -135,7 +135,7 @@
Proposal Map / Site Plan
- + {{ map.fileName }} diff --git a/portal-frontend/src/app/features/public/application/submission/naru-details/naru-details.component.ts b/portal-frontend/src/app/features/public/application/submission/naru-details/naru-details.component.ts index 1ad7be3a22..426d98a570 100644 --- a/portal-frontend/src/app/features/public/application/submission/naru-details/naru-details.component.ts +++ b/portal-frontend/src/app/features/public/application/submission/naru-details/naru-details.component.ts @@ -4,6 +4,7 @@ import { PublicApplicationSubmissionDto } from '../../../../../services/public/p import { PublicDocumentDto } from '../../../../../services/public/public.dto'; import { PublicService } from '../../../../../services/public/public.service'; import { DOCUMENT_TYPE } from '../../../../../shared/dto/document.dto'; +import { openFileInline } from '../../../../../shared/utils/file'; @Component({ selector: 'app-naru-details[applicationSubmission]', @@ -20,10 +21,10 @@ export class NaruDetailsComponent { constructor(private router: Router, private publicService: PublicService) {} - async openFile(uuid: string) { - const res = await this.publicService.getApplicationOpenFileUrl(this.applicationSubmission.fileNumber, uuid); + async openFile(file: PublicDocumentDto) { + const res = await this.publicService.getApplicationOpenFileUrl(this.applicationSubmission.fileNumber, file.uuid); if (res) { - window.open(res?.url, '_blank'); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/features/public/application/submission/nfu-details/nfu-details.component.html b/portal-frontend/src/app/features/public/application/submission/nfu-details/nfu-details.component.html index f79da77058..0192fa6e3c 100644 --- a/portal-frontend/src/app/features/public/application/submission/nfu-details/nfu-details.component.html +++ b/portal-frontend/src/app/features/public/application/submission/nfu-details/nfu-details.component.html @@ -22,7 +22,7 @@
Proposal Map / Site Plan
- + {{ map.fileName }} diff --git a/portal-frontend/src/app/features/public/application/submission/nfu-details/nfu-details.component.ts b/portal-frontend/src/app/features/public/application/submission/nfu-details/nfu-details.component.ts index 6fb3aaa0c8..d26d1d97ea 100644 --- a/portal-frontend/src/app/features/public/application/submission/nfu-details/nfu-details.component.ts +++ b/portal-frontend/src/app/features/public/application/submission/nfu-details/nfu-details.component.ts @@ -4,6 +4,7 @@ import { PublicApplicationSubmissionDto } from '../../../../../services/public/p import { PublicDocumentDto } from '../../../../../services/public/public.dto'; import { PublicService } from '../../../../../services/public/public.service'; import { DOCUMENT_TYPE } from '../../../../../shared/dto/document.dto'; +import { openFileInline } from '../../../../../shared/utils/file'; @Component({ selector: 'app-nfu-details[applicationSubmission]', @@ -21,10 +22,10 @@ export class NfuDetailsComponent { constructor(private router: Router, private publicService: PublicService) {} - async openFile(uuid: string) { - const res = await this.publicService.getApplicationOpenFileUrl(this.applicationSubmission.fileNumber, uuid); + async openFile(file: PublicDocumentDto) { + const res = await this.publicService.getApplicationOpenFileUrl(this.applicationSubmission.fileNumber, file.uuid); if (res) { - window.open(res?.url, '_blank'); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/features/public/application/submission/pfrs-details/pfrs-details.component.html b/portal-frontend/src/app/features/public/application/submission/pfrs-details/pfrs-details.component.html index d8b9ad3cc9..94e04b31d9 100644 --- a/portal-frontend/src/app/features/public/application/submission/pfrs-details/pfrs-details.component.html +++ b/portal-frontend/src/app/features/public/application/submission/pfrs-details/pfrs-details.component.html @@ -173,7 +173,7 @@
Proposal Map / Site Plan
- + {{ map.fileName }} @@ -181,7 +181,7 @@
Cross Sections
- + {{ file.fileName }} @@ -189,7 +189,7 @@
Reclamation Plan
- + {{ file.fileName }} diff --git a/portal-frontend/src/app/features/public/application/submission/pfrs-details/pfrs-details.component.ts b/portal-frontend/src/app/features/public/application/submission/pfrs-details/pfrs-details.component.ts index ebe8f634d8..4abf71a849 100644 --- a/portal-frontend/src/app/features/public/application/submission/pfrs-details/pfrs-details.component.ts +++ b/portal-frontend/src/app/features/public/application/submission/pfrs-details/pfrs-details.component.ts @@ -4,6 +4,7 @@ import { PublicApplicationSubmissionDto } from '../../../../../services/public/p import { PublicDocumentDto } from '../../../../../services/public/public.dto'; import { PublicService } from '../../../../../services/public/public.service'; import { DOCUMENT_TYPE } from '../../../../../shared/dto/document.dto'; +import { openFileInline } from '../../../../../shared/utils/file'; @Component({ selector: 'app-pfrs-details[applicationSubmission]', @@ -23,10 +24,10 @@ export class PfrsDetailsComponent { constructor(private router: Router, private publicService: PublicService) {} - async openFile(uuid: string) { - const res = await this.publicService.getApplicationOpenFileUrl(this.applicationSubmission.fileNumber, uuid); + async openFile(file: PublicDocumentDto) { + const res = await this.publicService.getApplicationOpenFileUrl(this.applicationSubmission.fileNumber, file.uuid); if (res) { - window.open(res?.url, '_blank'); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/features/public/application/submission/pofo-details/pofo-details.component.html b/portal-frontend/src/app/features/public/application/submission/pofo-details/pofo-details.component.html index 2d9e254aa8..3a11c73f05 100644 --- a/portal-frontend/src/app/features/public/application/submission/pofo-details/pofo-details.component.html +++ b/portal-frontend/src/app/features/public/application/submission/pofo-details/pofo-details.component.html @@ -51,18 +51,14 @@
{{ applicationSubmission.soilToPlaceMaximumDepth }} m - +
Average Depth
{{ applicationSubmission.soilToPlaceAverageDepth }} m - +
@@ -72,36 +68,28 @@
{{ applicationSubmission.soilAlreadyPlacedVolume }} m3 - +
Area
{{ applicationSubmission.soilAlreadyPlacedArea }} m2 - +
Maximum Depth
{{ applicationSubmission.soilAlreadyPlacedMaximumDepth }} m - +
Average Depth
{{ applicationSubmission.soilAlreadyPlacedAverageDepth }} m - +
@@ -127,7 +115,7 @@
Proposal Map / Site Plan
- + {{ map.fileName }} @@ -135,7 +123,7 @@
Cross Sections
- + {{ file.fileName }} diff --git a/portal-frontend/src/app/features/public/application/submission/pofo-details/pofo-details.component.ts b/portal-frontend/src/app/features/public/application/submission/pofo-details/pofo-details.component.ts index f08f625509..3aa24798a2 100644 --- a/portal-frontend/src/app/features/public/application/submission/pofo-details/pofo-details.component.ts +++ b/portal-frontend/src/app/features/public/application/submission/pofo-details/pofo-details.component.ts @@ -4,6 +4,7 @@ import { PublicApplicationSubmissionDto } from '../../../../../services/public/p import { PublicDocumentDto } from '../../../../../services/public/public.dto'; import { PublicService } from '../../../../../services/public/public.service'; import { DOCUMENT_TYPE } from '../../../../../shared/dto/document.dto'; +import { openFileInline } from '../../../../../shared/utils/file'; @Component({ selector: 'app-pofo-details[applicationSubmission]', @@ -23,10 +24,10 @@ export class PofoDetailsComponent { constructor(private router: Router, private publicService: PublicService) {} - async openFile(uuid: string) { - const res = await this.publicService.getApplicationOpenFileUrl(this.applicationSubmission.fileNumber, uuid); + async openFile(file: PublicDocumentDto) { + const res = await this.publicService.getApplicationOpenFileUrl(this.applicationSubmission.fileNumber, file.uuid); if (res) { - window.open(res?.url, '_blank'); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/features/public/application/submission/roso-details/roso-details.component.html b/portal-frontend/src/app/features/public/application/submission/roso-details/roso-details.component.html index d532fd963d..a358a4490e 100644 --- a/portal-frontend/src/app/features/public/application/submission/roso-details/roso-details.component.html +++ b/portal-frontend/src/app/features/public/application/submission/roso-details/roso-details.component.html @@ -107,7 +107,7 @@
Proposal Map / Site Plan
- + {{ map.fileName }} @@ -115,7 +115,7 @@
Cross Sections
- + {{ file.fileName }} diff --git a/portal-frontend/src/app/features/public/application/submission/roso-details/roso-details.component.ts b/portal-frontend/src/app/features/public/application/submission/roso-details/roso-details.component.ts index 44f33dea4a..51f664616a 100644 --- a/portal-frontend/src/app/features/public/application/submission/roso-details/roso-details.component.ts +++ b/portal-frontend/src/app/features/public/application/submission/roso-details/roso-details.component.ts @@ -4,6 +4,7 @@ import { PublicApplicationSubmissionDto } from '../../../../../services/public/p import { PublicDocumentDto } from '../../../../../services/public/public.dto'; import { PublicService } from '../../../../../services/public/public.service'; import { DOCUMENT_TYPE } from '../../../../../shared/dto/document.dto'; +import { openFileInline } from '../../../../../shared/utils/file'; @Component({ selector: 'app-roso-details[applicationSubmission]', @@ -23,10 +24,10 @@ export class RosoDetailsComponent { constructor(private router: Router, private publicService: PublicService) {} - async openFile(uuid: string) { - const res = await this.publicService.getApplicationOpenFileUrl(this.applicationSubmission.fileNumber, uuid); + async openFile(file: PublicDocumentDto) { + const res = await this.publicService.getApplicationOpenFileUrl(this.applicationSubmission.fileNumber, file.uuid); if (res) { - window.open(res?.url, '_blank'); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/features/public/application/submission/subd-details/subd-details.component.html b/portal-frontend/src/app/features/public/application/submission/subd-details/subd-details.component.html index cb8fbe2991..ba73f0e702 100644 --- a/portal-frontend/src/app/features/public/application/submission/subd-details/subd-details.component.html +++ b/portal-frontend/src/app/features/public/application/submission/subd-details/subd-details.component.html @@ -38,7 +38,7 @@
Proposal Map / Site Plan
- + {{ map.fileName }} diff --git a/portal-frontend/src/app/features/public/application/submission/subd-details/subd-details.component.ts b/portal-frontend/src/app/features/public/application/submission/subd-details/subd-details.component.ts index 82d9e4e921..5b75bad0a7 100644 --- a/portal-frontend/src/app/features/public/application/submission/subd-details/subd-details.component.ts +++ b/portal-frontend/src/app/features/public/application/submission/subd-details/subd-details.component.ts @@ -4,6 +4,7 @@ import { PublicApplicationSubmissionDto } from '../../../../../services/public/p import { PublicDocumentDto } from '../../../../../services/public/public.dto'; import { PublicService } from '../../../../../services/public/public.service'; import { DOCUMENT_TYPE } from '../../../../../shared/dto/document.dto'; +import { openFileInline } from '../../../../../shared/utils/file'; @Component({ selector: 'app-subd-details[applicationSubmission]', @@ -21,10 +22,10 @@ export class SubdDetailsComponent { constructor(private router: Router, private publicService: PublicService) {} - async openFile(uuid: string) { - const res = await this.publicService.getApplicationOpenFileUrl(this.applicationSubmission.fileNumber, uuid); + async openFile(file: PublicDocumentDto) { + const res = await this.publicService.getApplicationOpenFileUrl(this.applicationSubmission.fileNumber, file.uuid); if (res) { - window.open(res?.url, '_blank'); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/features/public/application/submission/tur-details/tur-details.component.html b/portal-frontend/src/app/features/public/application/submission/tur-details/tur-details.component.html index 1c51ac5f19..f483a12b85 100644 --- a/portal-frontend/src/app/features/public/application/submission/tur-details/tur-details.component.html +++ b/portal-frontend/src/app/features/public/application/submission/tur-details/tur-details.component.html @@ -33,7 +33,7 @@
Proposal Map / Site Plan
- + {{ file.fileName }} diff --git a/portal-frontend/src/app/features/public/application/submission/tur-details/tur-details.component.ts b/portal-frontend/src/app/features/public/application/submission/tur-details/tur-details.component.ts index fcc8e94615..58caec09af 100644 --- a/portal-frontend/src/app/features/public/application/submission/tur-details/tur-details.component.ts +++ b/portal-frontend/src/app/features/public/application/submission/tur-details/tur-details.component.ts @@ -4,6 +4,7 @@ import { PublicApplicationSubmissionDto } from '../../../../../services/public/p import { PublicDocumentDto } from '../../../../../services/public/public.dto'; import { PublicService } from '../../../../../services/public/public.service'; import { DOCUMENT_TYPE } from '../../../../../shared/dto/document.dto'; +import { openFileInline } from '../../../../../shared/utils/file'; @Component({ selector: 'app-tur-details[applicationSubmission]', @@ -20,10 +21,10 @@ export class TurDetailsComponent { constructor(private router: Router, private publicService: PublicService) {} - async openFile(uuid: string) { - const res = await this.publicService.getApplicationOpenFileUrl(this.applicationSubmission.fileNumber, uuid); + async openFile(file: PublicDocumentDto) { + const res = await this.publicService.getApplicationOpenFileUrl(this.applicationSubmission.fileNumber, file.uuid); if (res) { - window.open(res?.url, '_blank'); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/features/public/notice-of-intent/alc-review/decisions/decisions.component.html b/portal-frontend/src/app/features/public/notice-of-intent/alc-review/decisions/decisions.component.html index 413266b969..2a2f6909c7 100644 --- a/portal-frontend/src/app/features/public/notice-of-intent/alc-review/decisions/decisions.component.html +++ b/portal-frontend/src/app/features/public/notice-of-intent/alc-review/decisions/decisions.component.html @@ -20,21 +20,21 @@

Decision #{{ decisions.length - index }}

Decision Document
- {{ document.fileName }} + {{ document.fileName }}  ({{ document.fileSize | filesize }})
-
{{ document.fileSize | filesize }}
- diff --git a/portal-frontend/src/app/features/public/notice-of-intent/alc-review/decisions/decisions.component.ts b/portal-frontend/src/app/features/public/notice-of-intent/alc-review/decisions/decisions.component.ts index 32abf6af5e..d0277bf4f1 100644 --- a/portal-frontend/src/app/features/public/notice-of-intent/alc-review/decisions/decisions.component.ts +++ b/portal-frontend/src/app/features/public/notice-of-intent/alc-review/decisions/decisions.component.ts @@ -1,6 +1,8 @@ import { Component, Input } from '@angular/core'; import { NoticeOfIntentPortalDecisionDto } from '../../../../../services/notice-of-intent-decision/notice-of-intent-decision.dto'; import { NoticeOfIntentDecisionService } from '../../../../../services/notice-of-intent-decision/notice-of-intent-decision.service'; +import { openFileInline } from '../../../../../shared/utils/file'; +import { ApplicationDocumentDto } from '../../../../../services/application-document/application-document.dto'; @Component({ selector: 'app-public-decisions', @@ -12,10 +14,10 @@ export class PublicDecisionsComponent { constructor(private decisionService: NoticeOfIntentDecisionService) {} - async openFile(uuid: string) { - const res = await this.decisionService.openFile(uuid); + async openFile(file: ApplicationDocumentDto) { + const res = await this.decisionService.openFile(file.uuid); if (res) { - window.open(res.url, '_blank'); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/features/public/notice-of-intent/alc-review/submission-documents/submission-documents.component.html b/portal-frontend/src/app/features/public/notice-of-intent/alc-review/submission-documents/submission-documents.component.html index c5429a7155..d22fd2a4d6 100644 --- a/portal-frontend/src/app/features/public/notice-of-intent/alc-review/submission-documents/submission-documents.component.html +++ b/portal-frontend/src/app/features/public/notice-of-intent/alc-review/submission-documents/submission-documents.component.html @@ -11,7 +11,7 @@

NOI Documents

Document Name - {{ element.fileName }} + {{ element.fileName }} Document Name - {{ element.fileName }} + {{ element.fileName }} Corporate Summary
- {{ + {{ element.corporateSummary.fileName }}
diff --git a/portal-frontend/src/app/shared/owner-dialogs/parcel-owners/parcel-owners.component.ts b/portal-frontend/src/app/shared/owner-dialogs/parcel-owners/parcel-owners.component.ts index bddf22ec3c..1b00395ede 100644 --- a/portal-frontend/src/app/shared/owner-dialogs/parcel-owners/parcel-owners.component.ts +++ b/portal-frontend/src/app/shared/owner-dialogs/parcel-owners/parcel-owners.component.ts @@ -11,7 +11,9 @@ import { NoticeOfIntentOwnerService } from '../../../services/notice-of-intent-o import { OWNER_TYPE } from '../../dto/owner.dto'; import { CrownOwnerDialogComponent } from '../crown-owner-dialog/crown-owner-dialog.component'; import { OwnerDialogComponent } from '../owner-dialog/owner-dialog.component'; -import { openFileIframe } from '../../utils/file'; +import { openFileInline } from '../../utils/file'; +import { ApplicationDocumentDto } from '../../../services/application-document/application-document.dto'; +import { NoticeOfIntentDocumentDto } from '../../../services/notice-of-intent-document/notice-of-intent-document.dto'; @Component({ selector: 'app-parcel-owners[owners][fileId][submissionUuid][ownerService]', @@ -105,10 +107,10 @@ export class ParcelOwnersComponent { this.onOwnerRemoved.emit(uuid); } - async onOpenFile(uuid: string) { - const res = await this.documentService.openFile(uuid); + async onOpenFile(file: ApplicationDocumentDto | NoticeOfIntentDocumentDto) { + const res = await this.documentService.openFile(file.uuid); if (res) { - openFileIframe(res); + openFileInline(res.url, file.fileName); } } } diff --git a/portal-frontend/src/app/shared/utils/file.ts b/portal-frontend/src/app/shared/utils/file.ts index 5aaee70e01..8b2def5875 100644 --- a/portal-frontend/src/app/shared/utils/file.ts +++ b/portal-frontend/src/app/shared/utils/file.ts @@ -12,19 +12,19 @@ export const openPdfFile = (fileName: string, data: any) => { downloadLink.click(); }; -export const openFileIframe = (data: { url: string; fileName: string }) => { +export const openFileInline = (url: string, fileName: string) => { const newWindow = window.open('', '_blank'); if (newWindow) { - newWindow.document.title = data.fileName; + newWindow.document.title = fileName; const object = newWindow.document.createElement('object'); - object.data = data.url; + object.data = url; object.style.borderWidth = '0'; object.style.width = '100%'; object.style.height = '100%'; newWindow.document.body.appendChild(object); - newWindow.document.body.style.backgroundColor = 'rgb(82, 86, 89)'; + newWindow.document.body.style.backgroundColor = 'rgb(14, 14, 14)'; newWindow.document.body.style.height = '100%'; newWindow.document.body.style.width = '100%'; newWindow.document.body.style.margin = '0'; diff --git a/services/apps/alcs/src/portal/application-document/application-document.controller.spec.ts b/services/apps/alcs/src/portal/application-document/application-document.controller.spec.ts index 420e84cfda..6d7cefb4d9 100644 --- a/services/apps/alcs/src/portal/application-document/application-document.controller.spec.ts +++ b/services/apps/alcs/src/portal/application-document/application-document.controller.spec.ts @@ -138,7 +138,6 @@ describe('ApplicationDocumentController', () => { expect(appDocumentService.getInlineUrl).toHaveBeenCalledTimes(1); expect(res.url).toEqual(fakeUrl); - expect(res.fileName).toEqual(mockDocument.document.fileName); }); it('should call through for download', async () => { diff --git a/services/apps/alcs/src/portal/application-document/application-document.controller.ts b/services/apps/alcs/src/portal/application-document/application-document.controller.ts index 821fde6f82..09394a6491 100644 --- a/services/apps/alcs/src/portal/application-document/application-document.controller.ts +++ b/services/apps/alcs/src/portal/application-document/application-document.controller.ts @@ -75,8 +75,7 @@ export class ApplicationDocumentController { if (canAccessDocument) { const url = await this.applicationDocumentService.getInlineUrl(document); - const { fileName } = document.document; - return { url, fileName }; + return { url }; } throw new NotFoundException('Failed to find document'); diff --git a/services/apps/alcs/src/portal/notice-of-intent-document/notice-of-intent-document.controller.spec.ts b/services/apps/alcs/src/portal/notice-of-intent-document/notice-of-intent-document.controller.spec.ts index 73c73b9c39..b38071e642 100644 --- a/services/apps/alcs/src/portal/notice-of-intent-document/notice-of-intent-document.controller.spec.ts +++ b/services/apps/alcs/src/portal/notice-of-intent-document/notice-of-intent-document.controller.spec.ts @@ -122,7 +122,6 @@ describe('NoticeOfIntentDocumentController', () => { expect(noiDocumentService.getInlineUrl).toHaveBeenCalledTimes(1); expect(res.url).toEqual(fakeUrl); - expect(res.fileName).toEqual(mockDocument.document.fileName); }); it('should call through for download', async () => { diff --git a/services/apps/alcs/src/portal/notice-of-intent-document/notice-of-intent-document.controller.ts b/services/apps/alcs/src/portal/notice-of-intent-document/notice-of-intent-document.controller.ts index 5ccfe49ff8..4c287b96a2 100644 --- a/services/apps/alcs/src/portal/notice-of-intent-document/notice-of-intent-document.controller.ts +++ b/services/apps/alcs/src/portal/notice-of-intent-document/notice-of-intent-document.controller.ts @@ -80,8 +80,7 @@ export class NoticeOfIntentDocumentController { if (canAccessDocument) { const url = await this.noticeOfIntentDocumentService.getInlineUrl(document); - const { fileName } = document.document; - return { url, fileName }; + return { url }; } throw new NotFoundException('Failed to find document'); diff --git a/services/apps/alcs/src/portal/notification-document/notification-document.controller.spec.ts b/services/apps/alcs/src/portal/notification-document/notification-document.controller.spec.ts index 88891e0ae8..594e79a2d2 100644 --- a/services/apps/alcs/src/portal/notification-document/notification-document.controller.spec.ts +++ b/services/apps/alcs/src/portal/notification-document/notification-document.controller.spec.ts @@ -140,7 +140,6 @@ describe('NotificationDocumentController', () => { 1, ); expect(res.url).toEqual(fakeUrl); - expect(res.fileName).toEqual(mockDocument.document.fileName); }); it('should call through for download', async () => { diff --git a/services/apps/alcs/src/portal/notification-document/notification-document.controller.ts b/services/apps/alcs/src/portal/notification-document/notification-document.controller.ts index 555cde8cd6..54c990bb0a 100644 --- a/services/apps/alcs/src/portal/notification-document/notification-document.controller.ts +++ b/services/apps/alcs/src/portal/notification-document/notification-document.controller.ts @@ -78,8 +78,7 @@ export class NotificationDocumentController { if (canAccessDocument) { const url = await this.notificationDocumentService.getInlineUrl(document); - const { fileName } = document.document; - return { url, fileName }; + return { url }; } throw new NotFoundException('Failed to find document'); diff --git a/services/apps/alcs/src/portal/public/application/public-application.service.ts b/services/apps/alcs/src/portal/public/application/public-application.service.ts index de67bbbdee..c12b5f003e 100644 --- a/services/apps/alcs/src/portal/public/application/public-application.service.ts +++ b/services/apps/alcs/src/portal/public/application/public-application.service.ts @@ -153,8 +153,6 @@ export class PublicApplicationService { const url = await this.applicationDocumentService.getInlineUrl(document); - return { - url, - }; + return { url }; } } diff --git a/services/apps/alcs/src/portal/public/notice-of-intent/public-notice-of-intent.service.ts b/services/apps/alcs/src/portal/public/notice-of-intent/public-notice-of-intent.service.ts index 46538b48d5..8a060b31b9 100644 --- a/services/apps/alcs/src/portal/public/notice-of-intent/public-notice-of-intent.service.ts +++ b/services/apps/alcs/src/portal/public/notice-of-intent/public-notice-of-intent.service.ts @@ -114,8 +114,6 @@ export class PublicNoticeOfIntentService { const url = await this.noticeOfIntentDocumentService.getInlineUrl(document); - return { - url, - }; + return { url }; } } diff --git a/services/apps/alcs/src/portal/public/notification/public-notification.service.ts b/services/apps/alcs/src/portal/public/notification/public-notification.service.ts index a7a9e1a119..0b8b742fcd 100644 --- a/services/apps/alcs/src/portal/public/notification/public-notification.service.ts +++ b/services/apps/alcs/src/portal/public/notification/public-notification.service.ts @@ -99,8 +99,6 @@ export class PublicNotificationService { const url = await this.notificationDocumentService.getInlineUrl(document); - return { - url, - }; + return { url }; } } From 3cc3b149ea204ab22231913196adb9236ff51037 Mon Sep 17 00:00:00 2001 From: Tristan Slater <1631008+trslater@users.noreply.github.com> Date: Fri, 15 Mar 2024 10:56:06 -0700 Subject: [PATCH 005/153] Fix typo --- .../notification-timeline.controller.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/services/apps/alcs/src/alcs/notification/notification-timeline/notification-timeline.controller.ts b/services/apps/alcs/src/alcs/notification/notification-timeline/notification-timeline.controller.ts index 96810e700f..8abd82df7f 100644 --- a/services/apps/alcs/src/alcs/notification/notification-timeline/notification-timeline.controller.ts +++ b/services/apps/alcs/src/alcs/notification/notification-timeline/notification-timeline.controller.ts @@ -13,11 +13,13 @@ import { NotificationTimelineService } from './notification-timeline.service'; @Controller('notification-timeline') @UseGuards(RolesGuard) export class NotificationTimelineController { - constructor(private noiTimelineService: NotificationTimelineService) {} + constructor( + private notificationTimelineService: NotificationTimelineService, + ) {} @Get('/:fileNumber') @UserRoles(...ROLES_ALLOWED_APPLICATIONS) async fetchTimelineEvents(@Param('fileNumber') fileNumber: string) { - return await this.noiTimelineService.getTimelineEvents(fileNumber); + return await this.notificationTimelineService.getTimelineEvents(fileNumber); } } From 054fcb86dd0075c3d3a298cb9f40c1469915e24a Mon Sep 17 00:00:00 2001 From: Tristan Slater <1631008+trslater@users.noreply.github.com> Date: Fri, 15 Mar 2024 11:13:34 -0700 Subject: [PATCH 006/153] Determine response sent from events rather than status - Check for any 'response sent' event in events - Use this to determine if response sent - Use event date for sent date, rather than last event date --- .../features/notification/intake/intake.component.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/alcs-frontend/src/app/features/notification/intake/intake.component.ts b/alcs-frontend/src/app/features/notification/intake/intake.component.ts index 19f5046699..08530013e6 100644 --- a/alcs-frontend/src/app/features/notification/intake/intake.component.ts +++ b/alcs-frontend/src/app/features/notification/intake/intake.component.ts @@ -11,6 +11,7 @@ import { } from '../../../services/notification/notification.dto'; import { ToastService } from '../../../services/toast/toast.service'; import { ConfirmationDialogService } from '../../../shared/confirmation-dialog/confirmation-dialog.service'; +import { NotificationTimelineService } from '../../../services/notification/notification-timeline/notification-timeline.service'; @Component({ selector: 'app-intake', @@ -28,9 +29,10 @@ export class IntakeComponent implements OnInit { constructor( private notificationDetailService: NotificationDetailService, private notificationSubmissionService: NotificationSubmissionService, + private notificationTimelineService: NotificationTimelineService, private localGovernmentService: ApplicationLocalGovernmentService, private confirmationDialogService: ConfirmationDialogService, - private toastService: ToastService + private toastService: ToastService, ) {} ngOnInit(): void { @@ -114,8 +116,11 @@ export class IntakeComponent implements OnInit { const submission = await this.notificationSubmissionService.fetchSubmission(fileNumber); this.contactEmail = submission.contactEmail; - this.responseSent = submission.status.code === NOTIFICATION_STATUS.ALC_RESPONSE; - this.responseDate = submission.lastStatusUpdate; + const events = await this.notificationTimelineService.fetchByFileNumber(fileNumber); + const alcResponseEvent = events.find((event) => /alc response sent/i.test(event.htmlText)); + + this.responseSent = alcResponseEvent !== undefined; + this.responseDate = alcResponseEvent?.startDate ?? null; } async resendResponse() { From 931b2d6d5fbecf18aeff27d07e5b54f5a2297e7c Mon Sep 17 00:00:00 2001 From: Tristan Slater <1631008+trslater@users.noreply.github.com> Date: Fri, 15 Mar 2024 12:01:00 -0700 Subject: [PATCH 007/153] Add mock for notification timeline service --- .../features/notification/intake/intake.component.spec.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/alcs-frontend/src/app/features/notification/intake/intake.component.spec.ts b/alcs-frontend/src/app/features/notification/intake/intake.component.spec.ts index 2d94713a8e..845bade2d8 100644 --- a/alcs-frontend/src/app/features/notification/intake/intake.component.spec.ts +++ b/alcs-frontend/src/app/features/notification/intake/intake.component.spec.ts @@ -9,6 +9,7 @@ import { NotificationSubmissionService } from '../../../services/notification/no import { NotificationDto } from '../../../services/notification/notification.dto'; import { ConfirmationDialogService } from '../../../shared/confirmation-dialog/confirmation-dialog.service'; import { IntakeComponent } from './intake.component'; +import { NotificationTimelineService } from '../../../services/notification/notification-timeline/notification-timeline.service'; describe('IntakeComponent', () => { let component: IntakeComponent; @@ -16,11 +17,13 @@ describe('IntakeComponent', () => { let mockDetailService: DeepMocked; let mockLgService: DeepMocked; let mockSubmissionService: DeepMocked; + let mockTimelineService: DeepMocked; beforeEach(async () => { mockDetailService = createMock(); mockLgService = createMock(); mockSubmissionService = createMock(); + mockTimelineService = createMock(); mockDetailService.$notification = new BehaviorSubject(undefined); @@ -39,6 +42,10 @@ describe('IntakeComponent', () => { provide: NotificationSubmissionService, useValue: mockSubmissionService, }, + { + provide: NotificationTimelineService, + useValue: mockTimelineService, + }, { provide: ConfirmationDialogService, useValue: {}, From e7176342c7f73d9cdacafe9ffad2c30bd1d1c049 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Mon, 18 Mar 2024 09:45:59 -0700 Subject: [PATCH 008/153] Check confirm flag before deleting Transferee --- .../transferees/transferees.component.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/portal-frontend/src/app/features/notifications/edit-submission/transferees/transferees.component.ts b/portal-frontend/src/app/features/notifications/edit-submission/transferees/transferees.component.ts index 0ec050870c..8fc9c17736 100644 --- a/portal-frontend/src/app/features/notifications/edit-submission/transferees/transferees.component.ts +++ b/portal-frontend/src/app/features/notifications/edit-submission/transferees/transferees.component.ts @@ -1,14 +1,13 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; -import { Router } from '@angular/router'; import { takeUntil } from 'rxjs'; import { NotificationSubmissionService } from '../../../../services/notification-submission/notification-submission.service'; import { NotificationTransfereeDto } from '../../../../services/notification-transferee/notification-transferee.dto'; import { NotificationTransfereeService } from '../../../../services/notification-transferee/notification-transferee.service'; +import { ConfirmationDialogService } from '../../../../shared/confirmation-dialog/confirmation-dialog.service'; import { EditNotificationSteps } from '../edit-submission.component'; import { StepComponent } from '../step.partial'; import { TransfereeDialogComponent } from './transferee-dialog/transferee-dialog.component'; -import { ConfirmationDialogService } from '../../../../shared/confirmation-dialog/confirmation-dialog.service'; @Component({ selector: 'app-transferees', @@ -25,7 +24,6 @@ export class TransfereesComponent extends StepComponent implements OnInit, OnDes private submissionUuid = ''; constructor( - private router: Router, private notificationTransfereeService: NotificationTransfereeService, private notificationSubmissionService: NotificationSubmissionService, private confDialogService: ConfirmationDialogService, @@ -95,8 +93,10 @@ export class TransfereesComponent extends StepComponent implements OnInit, OnDes body: `This action will remove ${selectedTransferee?.firstName} ${selectedTransferee?.lastName} and its usage from the entire notification of SRW. Are you sure you want to remove this transferee? `, }) .subscribe(async (confirmed) => { - await this.notificationTransfereeService.delete(uuid); - await this.loadTransferees(this.submissionUuid); + if (confirmed) { + await this.notificationTransfereeService.delete(uuid); + await this.loadTransferees(this.submissionUuid); + } }); } } From 05b6f0659972d25bc0e875b9478877e2106b02a2 Mon Sep 17 00:00:00 2001 From: Tristan Slater <1631008+trslater@users.noreply.github.com> Date: Mon, 18 Mar 2024 10:35:26 -0700 Subject: [PATCH 009/153] Fix not scrolling to spinner on initial search - Remove duplicate spinner - Restructure template conditionals so same spinner is used for both initial load and subsequent loads - Show 'Search Results' title while loading to be consistent with subsequent loads - Scroll to now always-visible wrapper div - Use hack to delay scrolling until spinner is visible --- .../public/search/public-search.component.html | 15 +++++++-------- .../public/search/public-search.component.ts | 5 ++++- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/portal-frontend/src/app/features/public/search/public-search.component.html b/portal-frontend/src/app/features/public/search/public-search.component.html index 341a983c1b..5f4dd39d51 100644 --- a/portal-frontend/src/app/features/public/search/public-search.component.html +++ b/portal-frontend/src/app/features/public/search/public-search.component.html @@ -127,19 +127,18 @@

Search by one or more of the following fields:

- +
-
- -
-
-

Search Results

-
+
+

Search Results

+
- + Applications: {{ applicationTotal }} { + scrollToElement({ id: `searchResultsWrapper`, center: false }); + }); const result = await this.searchService.search(searchParams); this.searchResultsHidden = false; this.isLoading = false; From dd1820ff042fb02fbfac271c1e8671cd6da82422 Mon Sep 17 00:00:00 2001 From: Liam Stoddard Date: Mon, 18 Mar 2024 11:59:51 -0700 Subject: [PATCH 010/153] init commit --- .../planning_review/planning_review_base.py | 0 .../sql/planning_review_base_insert.sql | 116 ++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 bin/migrate-oats-data/planning_review/planning_review_base.py create mode 100644 bin/migrate-oats-data/planning_review/sql/planning_review_base_insert.sql diff --git a/bin/migrate-oats-data/planning_review/planning_review_base.py b/bin/migrate-oats-data/planning_review/planning_review_base.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bin/migrate-oats-data/planning_review/sql/planning_review_base_insert.sql b/bin/migrate-oats-data/planning_review/sql/planning_review_base_insert.sql new file mode 100644 index 0000000000..d28d167953 --- /dev/null +++ b/bin/migrate-oats-data/planning_review/sql/planning_review_base_insert.sql @@ -0,0 +1,116 @@ +-- Step 1: get local gov application name & match to uuid +WITH oats_gov AS ( + SELECT oaap.alr_application_id AS application_id, + oo.organization_name AS oats_gov_name + FROM oats.oats_alr_application_parties oaap + JOIN oats.oats_person_organizations opo ON oaap.person_organization_id = opo.person_organization_id + JOIN oats.oats_organizations oo ON opo.organization_id = oo.organization_id + WHERE oo.organization_type_cd IN ('MUNI', 'FN', 'RD', 'RM') +), +alcs_gov AS ( + SELECT oats_gov.application_id AS application_id, + alg.uuid AS gov_uuid + FROM oats_gov + JOIN alcs.local_government alg on ( + CASE + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Gabriola Island' THEN 'Islands Trust Gabriola Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Galiano Island' THEN 'Islands Trust Galiano Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Gambier Island' THEN 'Islands Trust Gambier Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Hornby Island' THEN 'Islands Trust Hornby Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Lasqueti Island' THEN 'Islands Trust Lasqueti Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Mayne Island' THEN 'Islands Trust Mayne Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Pender Island' THEN 'Islands Trust Pender Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Quadra Island' THEN 'Islands Trust Quadra Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Salt Spring Island' THEN 'Islands Trust Salt Spring Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Saturna Island' THEN 'Islands Trust Saturna Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Sidney Island' THEN 'Islands Trust Sidney Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust- Comox Strathcona' THEN 'Islands Trust Comox Strathcona (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Comox-Strathcona (Historical)' THEN 'Comox-Strathcona Regional District (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust- Nanaimo' THEN 'Islands Trust Nanaimo (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust-Capital' THEN 'Islands Trust Capital (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust-Powell River' THEN 'Islands Trust Powell River (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust-Sunshine Coast' THEN 'Islands Trust Sunshine Coast (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Bowen Island' THEN 'Bowen Island (Island Municipality)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Denman Island' THEN 'Islands Trust Denman Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust - Cowichan Valley' THEN 'Islands Trust Cowichan Valley (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Northern Rockies' THEN 'Northern Rockies (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Sliammon%' THEN 'Tla''amin Nation' + WHEN oats_gov.oats_gov_name LIKE 'Thompson Nicola%' THEN 'Thompson Nicola Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Cariboo%' THEN 'Cariboo Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Fraser Valley%' THEN 'Fraser Valley Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Columbia Shuswap%' THEN 'Columbia Shuswap Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Central Okanagan%' THEN 'Central Okanagan Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Squamish Lillooet%' THEN 'Squamish Lillooet Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Alberni-Clayoquot%' THEN 'Alberni-Clayoquot Regional District' + WHEN oats_gov.oats_gov_name LIKE 'qathet%' THEN 'qathet Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Peace River%' THEN 'Peace River Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Okanagan Similkameen%' THEN 'Okanagan Similkameen Regional District' + WHEN oats_gov.oats_gov_name LIKE 'East Kootenay%' THEN 'East Kootenay Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Bulkley-Nechako%' THEN 'Bulkley-Nechako Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Sunshine Coast%' THEN 'Sunshine Coast Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Nanaimo%' THEN 'Nanaimo Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Kitimat Stikine%' THEN 'Kitimat Stikine Regional District' + WHEN oats_gov.oats_gov_name LIKE 'North Okanagan%' THEN 'North Okanagan Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Fraser Fort George%' THEN 'Fraser Fort George Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Cowichan Valley%' THEN 'Cowichan Valley Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Kootenay Boundary%' THEN 'Kootenay Boundary Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Comox Valley%' THEN 'Comox Valley Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Central Kootenay%' THEN 'Central Kootenay Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Capital%' THEN 'Capital Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Metro Vancouver%' THEN 'Metro Vancouver Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Central Coast%' THEN 'Central Coast Regional District' + WHEN oats_gov.oats_gov_name LIKE 'North Coast%' THEN 'North Coast Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Strathcona%' THEN 'Strathcona Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Mount Waddington%' THEN 'Mount Waddington Regional District' + ELSE oats_gov.oats_gov_name + END + ) = alg."name" +), +-- Step 2: Perform a lookup to retrieve the region code for each application ID +panel_lookup AS ( + SELECT DISTINCT oaap.alr_application_id AS application_id, + CASE + WHEN oo2.parent_organization_id IS NULL THEN oo2.organization_name + WHEN oo3.parent_organization_id IS NULL THEN oo3.organization_name + ELSE 'NONE' + END AS panel_region + FROM oats.oats_alr_application_parties oaap + JOIN oats.oats_person_organizations opo ON oaap.person_organization_id = opo.person_organization_id + JOIN oats.oats_organizations oo ON opo.organization_id = oo.organization_id + LEFT JOIN oats.oats_organizations oo2 ON oo.parent_organization_id = oo2.organization_id + LEFT JOIN oats.oats_organizations oo3 ON oo2.parent_organization_id = oo3.organization_id + WHERE oo2.organization_type_cd = 'PANEL' + OR oo3.organization_type_cd = 'PANEL' +), +-- Step 3: Perform lookup to retrieve type code +application_type_lookup AS ( + SELECT oaac.alr_application_id AS application_id, + oacc."description" AS "description", + oaac.alr_change_code AS code + FROM oats.oats_alr_appl_components AS oaac + JOIN oats.oats_alr_change_codes oacc ON oaac.alr_change_code = oacc.alr_change_code + LEFT JOIN oats.alcs_etl_application_exclude aee ON oaac.alr_appl_component_id = aee.component_id + WHERE aee.component_id IS NULL +) -- Step 5: Insert new records into the alcs_applications table +SELECT oa.alr_application_id::text AS file_number, + atl.code AS type_code, + 'Unknown' as applicant, + ar.code AS region_code, + alcs_gov.gov_uuid AS local_government_uuid, + 'oats_etl', + CASE + WHEN oa.proposal_background_desc IS NOT NULL + AND length(oa.proposal_background_desc) > 10 THEN oa.proposal_summary_desc || '. Background: ' || oa.proposal_background_desc + ELSE oa.proposal_summary_desc + END AS "summary", + oa.staff_comment_observations, + oa.submitted_to_alc_date +FROM oats.oats_alr_applications AS oa + JOIN oats.alcs_etl_srw_duplicate AS ae ON oa.alr_application_id = ae.application_id + AND ae.duplicated IS false + LEFT JOIN panel_lookup ON oa.alr_application_id = panel_lookup.application_id + LEFT JOIN application_type_lookup AS atl ON oa.alr_application_id = atl.application_id + LEFT JOIN alcs.application_region ar ON panel_lookup.panel_region = ar."label" + LEFT JOIN alcs_gov ON oa.alr_application_id = alcs_gov.application_id +WHERE atl.code = 'SRW' + AND oa.application_class_code IN ('LOA', 'BLK', 'SCH', 'NAN') -- filter SRW only \ No newline at end of file From a2a2845896c0add4be067bf1f48a0126a2bab56d Mon Sep 17 00:00:00 2001 From: Tristan Slater <1631008+trslater@users.noreply.github.com> Date: Mon, 18 Mar 2024 12:01:41 -0700 Subject: [PATCH 011/153] Display correct org text for individual owners Show 'Not Applicable' instead of 'No Data' when for individual owners. --- .../parcel/parcel.component.html | 30 +++++++------------ .../parcel/parcel.component.scss | 10 ++++++- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/parcel/parcel.component.html b/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/parcel/parcel.component.html index 0fcb8e5cdf..210a9a81f7 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/parcel/parcel.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/parcel/parcel.component.html @@ -76,37 +76,27 @@
Government Parcel Contact
{{ parcel.owners[0].firstName }}
-
- Last Name -
+
Last Name
{{ parcel.owners[0].lastName }}
-
- Ministry or Department -
+
Ministry or Department
{{ parcel.owners[0].organizationName }}
-
- Phone -
+
Phone
{{ parcel.owners[0].phoneNumber }}
-
- Email -
+
Email
{{ parcel.owners[0].email }}
-
- Crown Type -
+
Crown Type
{{ parcel.owners[0].crownLandOwnerType === 'provincial' ? 'Provincial Crown' : '' }} {{ parcel.owners[0].crownLandOwnerType === 'federal' ? 'Federal Crown' : '' }} @@ -115,10 +105,7 @@
Government Parcel Contact
-
+
Land Owner(s)
Organization
Phone
@@ -128,7 +115,10 @@
Government Parcel Contact
{{ owner.displayName }}
{{ owner.organizationName }} - +
+
Not Applicable
+
No Data
+
{{ owner.phoneNumber }}
{{ owner.email }}
diff --git a/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/parcel/parcel.component.scss b/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/parcel/parcel.component.scss index 018792dbb2..b7e99ddc90 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/parcel/parcel.component.scss +++ b/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/parcel/parcel.component.scss @@ -1,3 +1,5 @@ +@use '../../../../../../styles/colors'; + .owner-information { display: grid; grid-template-columns: 1fr 1fr 1fr 1fr 1fr; @@ -17,4 +19,10 @@ .crown-land { text-transform: capitalize; -} \ No newline at end of file +} + +.no-data-text { + color: colors.$grey; + text-align: left; + padding-top: 0; +} From de419a5843b513682e52509f06f11df995e44291 Mon Sep 17 00:00:00 2001 From: Tristan Slater <1631008+trslater@users.noreply.github.com> Date: Mon, 18 Mar 2024 12:22:04 -0700 Subject: [PATCH 012/153] Show area-wide fill question in ALCS detail --- .../pfrs-details/pfrs-details.component.html | 17 +++++++++++------ .../pofo-details/pofo-details.component.html | 9 +++++++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/pfrs-details/pfrs-details.component.html b/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/pfrs-details/pfrs-details.component.html index c66db1e684..bb551ee630 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/pfrs-details/pfrs-details.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/pfrs-details/pfrs-details.component.html @@ -19,9 +19,7 @@
{{ _noiSubmission.fillProjectDuration }}
- +
Removal of Soil Project Duration
@@ -29,9 +27,7 @@
{{ _noiSubmission.soilProjectDuration }}
- +
@@ -142,6 +138,15 @@ {{ file.fileName }}
+ +
Is your proposal for area-wide filling?
+
+ + {{ _noiSubmission.soilIsAreaWideFilling ? 'Yes' : 'No' }} + + +
+
Cross Sections
diff --git a/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/pofo-details/pofo-details.component.html b/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/pofo-details/pofo-details.component.html index 6ae69fbe16..306f2f8083 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/pofo-details/pofo-details.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/pofo-details/pofo-details.component.html @@ -80,6 +80,15 @@ {{ file.fileName }}
+ +
Is your proposal for area-wide filling?
+
+ + {{ _noiSubmission.soilIsAreaWideFilling ? 'Yes' : 'No' }} + + +
+
Cross Sections
+
Describe the type of soil proposed to be removed.
+
+ {{ _noiSubmission.soilTypeRemoved }} +
+ +
Describe the type, origin and quality of fill proposed to be placed.
+
+ {{ _noiSubmission.soilFillTypeToPlace }} +
+
Placement of Fill Project Duration
@@ -122,16 +132,6 @@
-
Describe the type, origin and quality of fill proposed to be placed.
-
- {{ _noiSubmission.soilFillTypeToPlace }} -
- -
Describe the type of soil proposed to be removed.
-
- {{ _noiSubmission.soilTypeRemoved }} -
-
Proposal Map / Site Plan
+
Is your proposal for aggregate extraction or placer mining?
+
+ + {{ _noiSubmission.soilIsExtractionOrMining ? 'Yes' : 'No' }} + +
+
Cross Sections
-
Is your proposal for aggregate extraction or placer mining?
-
- - {{ _noiSubmission.soilIsExtractionOrMining ? 'Yes' : 'No' }} - -
-
Have you submitted a Notice of Work to the Ministry of Energy, Mines and Low Carbon Innovation (EMLI)?
From 76fa19a9d4bbd8afcca0a72d0c5dfa09a85a20c6 Mon Sep 17 00:00:00 2001 From: Tristan Slater <1631008+trslater@users.noreply.github.com> Date: Mon, 18 Mar 2024 16:13:09 -0700 Subject: [PATCH 014/153] Add m^2 to ALCS applicant structures table --- .../additional-information.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/additional-information/additional-information.component.html b/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/additional-information/additional-information.component.html index abc15b9f7c..b4b70bd909 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/additional-information/additional-information.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/additional-information/additional-information.component.html @@ -22,7 +22,7 @@
- {{ structure.area }} + {{ structure.area }} m2
From 3019feb6dd50269b7243a89d97cd420f3785a6ee Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Mon, 18 Mar 2024 16:25:31 -0700 Subject: [PATCH 015/153] Add Planning Review to Search, Delete Covenants --- .../board-management-dialog.component.ts | 2 +- .../features/board/board.component.spec.ts | 37 +--- .../src/app/features/board/board.component.ts | 33 +--- .../src/app/features/board/board.module.ts | 4 - .../covenant/covenant-dialog.component.html | 108 ---------- .../covenant-dialog.component.spec.ts | 118 ----------- .../covenant/covenant-dialog.component.ts | 84 -------- .../create-covenant-dialog.component.html | 95 --------- .../create-covenant-dialog.component.scss | 7 - .../create-covenant-dialog.component.spec.ts | 54 ----- .../create-covenant-dialog.component.ts | 92 --------- .../home/assigned/assigned.component.html | 4 +- .../home/assigned/assigned.component.spec.ts | 1 - .../home/assigned/assigned.component.ts | 33 +--- .../subtask-table.component.html | 6 - .../subtask-table/subtask-table.component.ts | 5 +- .../home/subtask/subtask.component.html | 4 +- .../home/subtask/subtask.component.ts | 11 +- .../file-type-filter-drop-down.component.ts | 4 +- ...notice-of-intent-search-table.component.ts | 4 +- ...anning-review-search-table.component.html} | 23 ++- ...anning-review-search-table.component.scss} | 0 ...ing-review-search-table.component.spec.ts} | 12 +- ...planning-review-search-table.component.ts} | 50 ++--- .../app/features/search/search.component.html | 10 +- .../app/features/search/search.component.ts | 26 +-- .../src/app/features/search/search.module.ts | 4 +- .../src/app/services/board/board.dto.ts | 4 +- .../src/app/services/covenant/covenant.dto.ts | 19 -- .../covenant/covenant.service.spec.ts | 105 ---------- .../app/services/covenant/covenant.service.ts | 39 ---- .../src/app/services/home/home.service.ts | 2 - .../file-type-data-source.service.spec.ts | 4 +- .../file-type-data-source.service.ts | 39 +++- .../src/app/services/search/search.dto.ts | 19 +- .../services/search/search.service.spec.ts | 20 +- .../src/app/services/search/search.service.ts | 32 +-- .../application-type-pill.constants.ts | 10 - .../src/app/shared/card/card.component.ts | 1 - .../apps/alcs/src/alcs/admin/admin.module.ts | 2 - .../unarchive-card.service.spec.ts | 9 - .../unarchive-card/unarchive-card.service.ts | 23 --- services/apps/alcs/src/alcs/alcs.module.ts | 3 - .../src/alcs/board/board.controller.spec.ts | 7 - .../alcs/src/alcs/board/board.controller.ts | 12 +- .../apps/alcs/src/alcs/board/board.module.ts | 2 - .../alcs/covenant/covenant.controller.spec.ts | 73 ------- .../src/alcs/covenant/covenant.controller.ts | 43 ---- .../alcs/src/alcs/covenant/covenant.dto.ts | 44 ----- .../alcs/src/alcs/covenant/covenant.entity.ts | 53 ----- .../alcs/src/alcs/covenant/covenant.module.ts | 22 --- .../alcs/covenant/covenant.service.spec.ts | 133 ------------- .../src/alcs/covenant/covenant.service.ts | 152 -------------- .../src/alcs/home/home.controller.spec.ts | 36 ---- .../alcs/src/alcs/home/home.controller.ts | 34 ---- .../apps/alcs/src/alcs/home/home.module.ts | 2 - ...ing-review-advanced-search.service.spec.ts | 106 ++++++++++ ...planning-review-advanced-search.service.ts | 187 ++++++++++++++++++ .../planning-review-search-view.entity.ts | 103 ++++++++++ .../src/alcs/search/search.controller.spec.ts | 64 +++--- .../alcs/src/alcs/search/search.controller.ts | 106 ++++++---- .../apps/alcs/src/alcs/search/search.dto.ts | 12 +- .../alcs/src/alcs/search/search.module.ts | 6 +- .../src/alcs/search/search.service.spec.ts | 26 ++- .../alcs/src/alcs/search/search.service.ts | 19 +- .../automapper/covenant.automapper.profile.ts | 18 -- .../src/file-number/file-number.module.ts | 6 +- .../file-number/file-number.service.spec.ts | 30 +-- .../src/file-number/file-number.service.ts | 16 +- .../1710799245145-add_pr_search_view.ts | 28 +++ .../1710800257994-delete_v1_covenant.ts | 14 ++ 71 files changed, 747 insertions(+), 1769 deletions(-) delete mode 100644 alcs-frontend/src/app/features/board/dialogs/covenant/covenant-dialog.component.html delete mode 100644 alcs-frontend/src/app/features/board/dialogs/covenant/covenant-dialog.component.spec.ts delete mode 100644 alcs-frontend/src/app/features/board/dialogs/covenant/covenant-dialog.component.ts delete mode 100644 alcs-frontend/src/app/features/board/dialogs/covenant/create/create-covenant-dialog.component.html delete mode 100644 alcs-frontend/src/app/features/board/dialogs/covenant/create/create-covenant-dialog.component.scss delete mode 100644 alcs-frontend/src/app/features/board/dialogs/covenant/create/create-covenant-dialog.component.spec.ts delete mode 100644 alcs-frontend/src/app/features/board/dialogs/covenant/create/create-covenant-dialog.component.ts rename alcs-frontend/src/app/features/search/{non-application-search-table/non-application-search-table.component.html => planning-review-search-table/planning-review-search-table.component.html} (77%) rename alcs-frontend/src/app/features/search/{non-application-search-table/non-application-search-table.component.scss => planning-review-search-table/planning-review-search-table.component.scss} (100%) rename alcs-frontend/src/app/features/search/{non-application-search-table/non-application-search-table.component.spec.ts => planning-review-search-table/planning-review-search-table.component.spec.ts} (66%) rename alcs-frontend/src/app/features/search/{non-application-search-table/non-application-search-table.component.ts => planning-review-search-table/planning-review-search-table.component.ts} (55%) delete mode 100644 alcs-frontend/src/app/services/covenant/covenant.dto.ts delete mode 100644 alcs-frontend/src/app/services/covenant/covenant.service.spec.ts delete mode 100644 alcs-frontend/src/app/services/covenant/covenant.service.ts delete mode 100644 services/apps/alcs/src/alcs/covenant/covenant.controller.spec.ts delete mode 100644 services/apps/alcs/src/alcs/covenant/covenant.controller.ts delete mode 100644 services/apps/alcs/src/alcs/covenant/covenant.dto.ts delete mode 100644 services/apps/alcs/src/alcs/covenant/covenant.entity.ts delete mode 100644 services/apps/alcs/src/alcs/covenant/covenant.module.ts delete mode 100644 services/apps/alcs/src/alcs/covenant/covenant.service.spec.ts delete mode 100644 services/apps/alcs/src/alcs/covenant/covenant.service.ts create mode 100644 services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.spec.ts create mode 100644 services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.ts create mode 100644 services/apps/alcs/src/alcs/search/planning-review/planning-review-search-view.entity.ts delete mode 100644 services/apps/alcs/src/common/automapper/covenant.automapper.profile.ts create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1710799245145-add_pr_search_view.ts create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1710800257994-delete_v1_covenant.ts diff --git a/alcs-frontend/src/app/features/admin/board-management/board-management-dialog/board-management-dialog.component.ts b/alcs-frontend/src/app/features/admin/board-management/board-management-dialog/board-management-dialog.component.ts index b84363054a..388c5d1892 100644 --- a/alcs-frontend/src/app/features/admin/board-management/board-management-dialog/board-management-dialog.component.ts +++ b/alcs-frontend/src/app/features/admin/board-management/board-management-dialog/board-management-dialog.component.ts @@ -11,7 +11,7 @@ import { CardStatusService } from '../../../../services/card/card-status/card-st import { CardType } from '../../../../shared/card/card.component'; import { BaseCodeDto } from '../../../../shared/dto/base.dto'; -const DISABLED_CREATE_CARD_TYPES = [CardType.APP, CardType.COV, CardType.NOI, CardType.NOTIFICATION]; +const DISABLED_CREATE_CARD_TYPES = [CardType.APP, CardType.NOI, CardType.NOTIFICATION]; @Component({ selector: 'app-decision-condition-types-dialog', 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 4c1c21ad08..a6a4f748ca 100644 --- a/alcs-frontend/src/app/features/board/board.component.spec.ts +++ b/alcs-frontend/src/app/features/board/board.component.spec.ts @@ -17,15 +17,12 @@ import { BoardDto } from '../../services/board/board.dto'; import { BoardService, BoardWithFavourite } from '../../services/board/board.service'; import { CardDto } from '../../services/card/card.dto'; import { CardService } from '../../services/card/card.service'; -import { CovenantDto } from '../../services/covenant/covenant.dto'; -import { CovenantService } from '../../services/covenant/covenant.service'; import { NoticeOfIntentModificationService } from '../../services/notice-of-intent/notice-of-intent-modification/notice-of-intent-modification.service'; 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 { 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 { PlanningReferralDto } from '../../services/planning-review/planning-review.dto'; import { ToastService } from '../../services/toast/toast.service'; import { CardType } from '../../shared/card/card.component'; import { BoardComponent } from './board.component'; @@ -43,7 +40,6 @@ describe('BoardComponent', () => { let reconsiderationService: DeepMocked; let planningReferralService: DeepMocked; let modificationService: DeepMocked; - let covenantService: DeepMocked; let titleService: DeepMocked; let noticeOfIntentService: DeepMocked<NoticeOfIntentService>; let noticeOfIntentModificationService: DeepMocked<NoticeOfIntentModificationService>; @@ -95,7 +91,6 @@ describe('BoardComponent', () => { boardService.fetchBoardWithCards.mockResolvedValue({ board: mockDetailBoard, applications: [], - covenants: [], modifications: [], planningReferrals: [], reconsiderations: [], @@ -112,7 +107,6 @@ describe('BoardComponent', () => { reconsiderationService = createMock(); planningReferralService = createMock(); modificationService = createMock(); - covenantService = createMock(); titleService = createMock(); noticeOfIntentService = createMock(); noticeOfIntentModificationService = createMock(); @@ -170,10 +164,6 @@ describe('BoardComponent', () => { provide: ApplicationModificationService, useValue: modificationService, }, - { - provide: CovenantService, - useValue: covenantService, - }, { provide: NoticeOfIntentService, useValue: noticeOfIntentService, @@ -222,7 +212,6 @@ describe('BoardComponent', () => { boardService.fetchBoardWithCards.mockResolvedValue({ board: mockDetailBoard, applications: [mockApplication], - covenants: [], modifications: [], planningReferrals: [], reconsiderations: [], @@ -243,7 +232,6 @@ describe('BoardComponent', () => { boardService.fetchBoardWithCards.mockResolvedValue({ board: mockDetailBoard, applications: [], - covenants: [], modifications: [], planningReferrals: [], reconsiderations: [mockRecon], @@ -286,7 +274,6 @@ describe('BoardComponent', () => { boardService.fetchBoardWithCards.mockResolvedValue({ board: mockDetailBoard, applications: [mockApplication, highPriorityApplication, highActiveDays], - covenants: [], modifications: [], planningReferrals: [], reconsiderations: [], @@ -353,22 +340,6 @@ describe('BoardComponent', () => { expect(dialog.open).toHaveBeenCalledTimes(1); }); - it('should load covenant and open dialog when url is set', async () => { - covenantService.fetchByCardUuid.mockResolvedValue({} as CovenantDto); - - queryParamMapEmitter.next( - new Map([ - ['card', 'app-id'], - ['type', CardType.COV], - ]), - ); - - await sleep(1); - - expect(covenantService.fetchByCardUuid).toHaveBeenCalledTimes(1); - expect(dialog.open).toHaveBeenCalledTimes(1); - }); - it('should load notification and open dialog when url is set', async () => { notificationService.fetchByCardUuid.mockResolvedValue({} as NotificationDto); @@ -386,18 +357,18 @@ describe('BoardComponent', () => { }); it('should show an error toast if fetching data fails', async () => { - covenantService.fetchByCardUuid.mockRejectedValue({}); + notificationService.fetchByCardUuid.mockRejectedValue({}); queryParamMapEmitter.next( new Map([ ['card', 'app-id'], - ['type', CardType.COV], + ['type', CardType.NOTIFICATION], ]), ); await sleep(1); - expect(covenantService.fetchByCardUuid).toHaveBeenCalledTimes(1); + expect(notificationService.fetchByCardUuid).toHaveBeenCalledTimes(1); expect(dialog.open).toHaveBeenCalledTimes(0); expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); }); diff --git a/alcs-frontend/src/app/features/board/board.component.ts b/alcs-frontend/src/app/features/board/board.component.ts index 2b536d9300..2d2f0e271c 100644 --- a/alcs-frontend/src/app/features/board/board.component.ts +++ b/alcs-frontend/src/app/features/board/board.component.ts @@ -15,8 +15,6 @@ import { ApplicationService } from '../../services/application/application.servi import { CardsDto } from '../../services/board/board.dto'; import { BoardService, BoardWithFavourite } from '../../services/board/board.service'; import { CardService } from '../../services/card/card.service'; -import { CovenantDto } from '../../services/covenant/covenant.dto'; -import { CovenantService } from '../../services/covenant/covenant.service'; import { NoticeOfIntentModificationDto } from '../../services/notice-of-intent/notice-of-intent-modification/notice-of-intent-modification.dto'; import { NoticeOfIntentModificationService } from '../../services/notice-of-intent/notice-of-intent-modification/notice-of-intent-modification.service'; import { NoticeOfIntentDto } from '../../services/notice-of-intent/notice-of-intent.dto'; @@ -27,7 +25,6 @@ import { PlanningReferralService } from '../../services/planning-review/planning import { PlanningReferralDto } from '../../services/planning-review/planning-review.dto'; import { ToastService } from '../../services/toast/toast.service'; import { - COVENANT_TYPE_LABEL, MODIFICATION_TYPE_LABEL, RECON_TYPE_LABEL, RETROACTIVE_TYPE_LABEL, @@ -37,7 +34,6 @@ 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 { CovenantDialogComponent } from './dialogs/covenant/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 { NoticeOfIntentDialogComponent } from './dialogs/notice-of-intent/notice-of-intent-dialog.component'; @@ -121,7 +117,6 @@ export class BoardComponent implements OnInit, OnDestroy { private reconsiderationService: ApplicationReconsiderationService, private planningReferralService: PlanningReferralService, private modificationService: ApplicationModificationService, - private covenantService: CovenantService, private noticeOfIntentService: NoticeOfIntentService, private noiModificationService: NoticeOfIntentModificationService, private notificationService: NotificationService, @@ -189,7 +184,6 @@ export class BoardComponent implements OnInit, OnDestroy { case CardType.RECON: case CardType.PLAN: case CardType.MODI: - case CardType.COV: case CardType.NOI: case CardType.NOI_MODI: case CardType.NOTIFICATION: @@ -249,7 +243,6 @@ export class BoardComponent implements OnInit, OnDestroy { const mappedRecons = response.reconsiderations.map(this.mapReconsiderationDtoToCard.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), @@ -265,7 +258,7 @@ export class BoardComponent implements OnInit, OnDestroy { this.cards = [ ...[...mappedNoticeOfIntents, ...mappedNoticeOfIntentModifications].sort(vettingSort), ...[...mappedApps, ...mappedRecons, ...mappedModifications].sort(vettingSort), - ...[...mappedPlanningReferrals, ...mappedCovenants].sort(vettingSort), + ...[...mappedPlanningReferrals].sort(vettingSort), ...mappedNotifications, ]; } else if (boardCode === BOARD_TYPE_CODES.NOI) { @@ -290,7 +283,6 @@ export class BoardComponent implements OnInit, OnDestroy { ...mappedRecons, ...mappedModifications, ...mappedPlanningReferrals, - ...mappedCovenants, ...mappedNotifications, ].sort(noiSort); } else { @@ -305,7 +297,6 @@ export class BoardComponent implements OnInit, OnDestroy { ...mappedModifications.filter((r) => r.highPriority).sort((a, b) => a.dateReceived - b.dateReceived), ...mappedRecons.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 ...mappedNoticeOfIntents @@ -318,7 +309,6 @@ export class BoardComponent implements OnInit, OnDestroy { ...mappedModifications.filter((r) => !r.highPriority).sort((a, b) => a.dateReceived - b.dateReceived), ...mappedRecons.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), ); this.cards = sorted; @@ -400,23 +390,6 @@ export class BoardComponent implements OnInit, OnDestroy { }; } - private mapCovenantToCard(covenant: CovenantDto): CardData { - return { - status: covenant.card.status.code, - typeLabel: 'Non-Application', - title: `${covenant.fileNumber} (${covenant.applicant})`, - titleTooltip: covenant.applicant, - assignee: covenant.card.assignee, - id: covenant.card.uuid, - labels: [COVENANT_TYPE_LABEL], - cardType: CardType.COV, - paused: false, - highPriority: covenant.card.highPriority, - cardUuid: covenant.card.uuid, - dateReceived: covenant.card.createdAt, - }; - } - private mapNoticeOfIntentToCard(noticeOfIntent: NoticeOfIntentDto): CardData { return { status: noticeOfIntent.card.status.code, @@ -524,10 +497,6 @@ export class BoardComponent implements OnInit, OnDestroy { const modification = await this.modificationService.fetchByCardUuid(card.uuid); this.openDialog(AppModificationDialogComponent, modification); break; - case CardType.COV: - const covenant = await this.covenantService.fetchByCardUuid(card.uuid); - this.openDialog(CovenantDialogComponent, covenant); - break; case CardType.NOI: const notceOfIntent = await this.noticeOfIntentService.fetchByCardUuid(card.uuid); this.openDialog(NoticeOfIntentDialogComponent, notceOfIntent); diff --git a/alcs-frontend/src/app/features/board/board.module.ts b/alcs-frontend/src/app/features/board/board.module.ts index 514088bcb9..9627822ea3 100644 --- a/alcs-frontend/src/app/features/board/board.module.ts +++ b/alcs-frontend/src/app/features/board/board.module.ts @@ -17,8 +17,6 @@ import { AppModificationDialogComponent } from './dialogs/app-modification/app-m 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'; @@ -56,8 +54,6 @@ const routes: Routes = [ PlanningReviewDialogComponent, CreateAppModificationDialogComponent, AppModificationDialogComponent, - CovenantDialogComponent, - CreateCovenantDialogComponent, NoticeOfIntentDialogComponent, CreateNoticeOfIntentDialogComponent, CreateNoiModificationDialogComponent, diff --git a/alcs-frontend/src/app/features/board/dialogs/covenant/covenant-dialog.component.html b/alcs-frontend/src/app/features/board/dialogs/covenant/covenant-dialog.component.html deleted file mode 100644 index 388c1e5061..0000000000 --- a/alcs-frontend/src/app/features/board/dialogs/covenant/covenant-dialog.component.html +++ /dev/null @@ -1,108 +0,0 @@ -<div mat-dialog-title> - <div class="close"> - <h6 class="card-type-label">Non-Application</h6> - <button mat-icon-button [mat-dialog-close]="isDirty"> - <mat-icon>close</mat-icon> - </button> - </div> - <div class="header-row"> - <div class="left"> - <h3 class="card-title"> - <span class="margin-right">{{ cardTitle }}</span> - <app-application-type-pill [type]="covenantType"></app-application-type-pill> - </h3> - </div> - </div> - <div> - <span class="region">{{ covenant.localGovernment.name }} - {{ covenant.region.label }} Region</span> - </div> - <div class="header-row"> - <div class="left"></div> - <div class="right"> - <button matTooltip="Move Board" [matMenuTriggerFor]="moveMenu" mat-icon-button> - <mat-icon>move_down</mat-icon> - </button> - <mat-menu class="move-board-menu" xPosition="before" #moveMenu="matMenu"> - <button (click)="onBoardSelected(board)" *ngFor="let board of allowedBoards" mat-menu-item> - <div class="board-menu-item"> - <span class="favourite-board-icon-container" - ><mat-icon *ngIf="board.isFavourite" class="favourite-board-icon">star</mat-icon> - </span> - <span>{{ board.title }}</span> - <span *ngIf="card && card.boardCode === board.code"><mat-icon class="selected-board-icon">check</mat-icon></span> - </div> - </button> - </mat-menu> - <button - *ngIf="canArchive" - matTooltip="Archive Card" - class="toggle-priority" - (click)="onArchiveCard()" - mat-icon-button - > - <mat-icon>archive</mat-icon> - </button> - <button - *ngIf='card' - [matTooltip]="card.highPriority ? 'Remove Priority' : 'Add Priority'" - class="toggle-priority" - (click)="onTogglePriority()" - mat-icon-button - > - <div - class="priority" - [ngClass]="{ - 'filled-priority': card.highPriority, - 'empty-priority': !card.highPriority - }" - ></div> - </button> - </div> - </div> -</div> -<mat-dialog-content> - <div class="select-container"> - <ng-select - class="card-type" - appearance="outline" - [items]="boardStatuses" - placeholder="Workflow Stage" - bindLabel="label" - bindValue="statusCode" - [clearable]="false" - [(ngModel)]="selectedApplicationStatus" - (change)="onStatusSelected($event)" - > - <ng-template ng-option-tmp let-item="item"> - <span [innerHTML]="item.label"> </span> - </ng-template> - <ng-template ng-label-tmp let-item="item"> - <span [innerHTML]="item.label"> </span> - </ng-template> - </ng-select> - <ng-select - class="card-assignee" - appearance="outline" - [items]="$users | async" - placeholder="Assigned Planner" - bindLabel="prettyName" - bindValue="prettyName" - [(ngModel)]="selectedAssigneeName" - [searchFn]="filterAssigneeList" - (change)="onAssigneeSelected($event)" - > - <ng-template ng-option-tmp let-item="item" let-search="searchTerm"> - <div class="assignee-card-body"> - <p [ngOptionHighlight]="search" class="assignee-card-name">{{ item.prettyName }}</p> - <p class="assignee-card-email" [ngOptionHighlight]="search">{{ item.email }}</p> - </div> - </ng-template> - </ng-select> - </div> - <div *ngIf="card" class="subtasks-wrapper"> - <app-subtasks [cardUuid]="card.uuid"></app-subtasks> - </div> - <div *ngIf="card" class="card-comments-wrapper"> - <app-comments [cardUuid]="card.uuid" [notificationTitle]="cardTitle"></app-comments> - </div> -</mat-dialog-content> diff --git a/alcs-frontend/src/app/features/board/dialogs/covenant/covenant-dialog.component.spec.ts b/alcs-frontend/src/app/features/board/dialogs/covenant/covenant-dialog.component.spec.ts deleted file mode 100644 index b8e02d8169..0000000000 --- a/alcs-frontend/src/app/features/board/dialogs/covenant/covenant-dialog.component.spec.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { EventEmitter, NO_ERRORS_SCHEMA } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { FormsModule } from '@angular/forms'; -import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { MatMenuModule } from '@angular/material/menu'; -import { MatSnackBarModule } from '@angular/material/snack-bar'; -import { RouterTestingModule } from '@angular/router/testing'; -import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { NgSelectModule } from '@ng-select/ng-select'; -import { BehaviorSubject } from 'rxjs'; -import { AuthenticationService, ICurrentUser } from '../../../../services/authentication/authentication.service'; -import { BoardService, BoardWithFavourite } from '../../../../services/board/board.service'; -import { CardDto } from '../../../../services/card/card.dto'; -import { CardService } from '../../../../services/card/card.service'; -import { CovenantDto } from '../../../../services/covenant/covenant.dto'; -import { CovenantService } from '../../../../services/covenant/covenant.service'; -import { ToastService } from '../../../../services/toast/toast.service'; -import { AssigneeDto } from '../../../../services/user/user.dto'; -import { UserService } from '../../../../services/user/user.service'; -import { ConfirmationDialogService } from '../../../../shared/confirmation-dialog/confirmation-dialog.service'; -import { CovenantDialogComponent } from './covenant-dialog.component'; - -describe('CovenantDialogComponent', () => { - let component: CovenantDialogComponent; - let fixture: ComponentFixture<CovenantDialogComponent>; - let mockUserService: DeepMocked<UserService>; - let mockBoardService: DeepMocked<BoardService>; - let authenticationService: DeepMocked<AuthenticationService>; - - const mockCovenantDto: CovenantDto = { - applicant: 'fake-type', - region: { - code: 'region-code', - label: 'region', - description: 'WHY', - }, - localGovernment: { - name: 'local-gov', - uuid: 'uuid', - preferredRegionCode: 'CODE', - isFirstNation: false, - }, - fileNumber: 'file-number', - card: { - status: { - code: 'FAKE_STATUS', - }, - boardCode: 'FAKE_BOARD', - } as CardDto, - }; - - beforeEach(async () => { - const mockDialogRef = { - close: jest.fn(), - afterClosed: jest.fn(), - subscribe: jest.fn(), - backdropClick: () => new EventEmitter(), - }; - mockUserService = createMock(); - mockUserService.$assignableUsers = new BehaviorSubject<AssigneeDto[]>([]); - - mockBoardService = createMock(); - mockBoardService.$boards = new BehaviorSubject<BoardWithFavourite[]>([]); - - authenticationService = createMock(); - authenticationService.$currentUser = new BehaviorSubject<ICurrentUser | undefined>(undefined); - - await TestBed.configureTestingModule({ - declarations: [CovenantDialogComponent], - providers: [ - { - provide: MAT_DIALOG_DATA, - useValue: mockCovenantDto, - }, - { - provide: UserService, - useValue: mockUserService, - }, - { - provide: CardService, - useValue: {}, - }, - { - provide: CovenantService, - useValue: {}, - }, - { - provide: BoardService, - useValue: mockBoardService, - }, - { - provide: ToastService, - useValue: {}, - }, - { - provide: ConfirmationDialogService, - useValue: {}, - }, - { - provide: AuthenticationService, - useValue: authenticationService, - }, - { provide: MatDialogRef, useValue: mockDialogRef }, - ], - imports: [MatDialogModule, MatSnackBarModule, FormsModule, MatMenuModule, RouterTestingModule, NgSelectModule], - schemas: [NO_ERRORS_SCHEMA], - }).compileComponents(); - - fixture = TestBed.createComponent(CovenantDialogComponent); - component = fixture.componentInstance; - component.data = mockCovenantDto; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/alcs-frontend/src/app/features/board/dialogs/covenant/covenant-dialog.component.ts b/alcs-frontend/src/app/features/board/dialogs/covenant/covenant-dialog.component.ts deleted file mode 100644 index 02bc3ba6dd..0000000000 --- a/alcs-frontend/src/app/features/board/dialogs/covenant/covenant-dialog.component.ts +++ /dev/null @@ -1,84 +0,0 @@ -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 { CovenantDto } from '../../../../services/covenant/covenant.dto'; -import { CovenantService } from '../../../../services/covenant/covenant.service'; -import { ToastService } from '../../../../services/toast/toast.service'; -import { UserService } from '../../../../services/user/user.service'; -import { COVENANT_TYPE_LABEL } from '../../../../shared/application-type-pill/application-type-pill.constants'; -import { ConfirmationDialogService } from '../../../../shared/confirmation-dialog/confirmation-dialog.service'; -import { CardDialogComponent } from '../card-dialog/card-dialog.component'; - -@Component({ - selector: 'app-covenant-dialog', - templateUrl: './covenant-dialog.component.html', - styleUrls: ['../card-dialog/card-dialog.component.scss'], -}) -export class CovenantDialogComponent extends CardDialogComponent implements OnInit { - selectedRegion?: string; - title?: string; - covenantType = COVENANT_TYPE_LABEL; - cardTitle = ''; - - covenant: CovenantDto = this.data; - - constructor( - @Inject(MAT_DIALOG_DATA) public data: CovenantDto, - private dialogRef: MatDialogRef<CovenantDialogComponent>, - private covenantService: CovenantService, - private router: Router, - boardService: BoardService, - userService: UserService, - authService: AuthenticationService, - toastService: ToastService, - confirmationDialogService: ConfirmationDialogService, - cardService: CardService - ) { - super(authService, dialogRef, cardService, confirmationDialogService, toastService, userService, boardService); - } - - override ngOnInit(): void { - super.ngOnInit(); - this.populateData(this.data); - - this.title = `${this.covenant.fileNumber} (${this.covenant.applicant})`; - } - - populateData(covenant: CovenantDto) { - this.covenant = covenant; - super.populateCardData(covenant.card); - this.selectedRegion = covenant.region.code; - this.userService.fetchAssignableUsers(); - this.cardTitle = `${covenant.fileNumber} (${covenant.applicant})`; - } - - private async reloadCovenant() { - const covenant = await this.covenantService.fetchByCardUuid(this.covenant.card.uuid); - if (covenant) { - this.populateData(covenant); - } - } - - async onBoardSelected(board: BoardWithFavourite) { - this.selectedBoard = board.code; - try { - await this.boardService.changeBoard(this.covenant.card.uuid, board.code); - const loadedBoard = await this.boardService.fetchBoardDetail(board.code); - if (loadedBoard) { - this.boardStatuses = loadedBoard.statuses; - } - - this.isDirty = true; - const toast = this.toastService.showSuccessToast(`Covenant moved to ${board.title}`, 'Go to Board'); - toast.onAction().subscribe(() => { - this.router.navigate(['/board', board.code]); - }); - await this.reloadCovenant(); - } catch (e) { - this.toastService.showErrorToast('Failed to move to new board'); - } - } -} diff --git a/alcs-frontend/src/app/features/board/dialogs/covenant/create/create-covenant-dialog.component.html b/alcs-frontend/src/app/features/board/dialogs/covenant/create/create-covenant-dialog.component.html deleted file mode 100644 index 6353f8d23b..0000000000 --- a/alcs-frontend/src/app/features/board/dialogs/covenant/create/create-covenant-dialog.component.html +++ /dev/null @@ -1,95 +0,0 @@ -<div mat-dialog-title> - <h2 class="card-title">Create Covenant</h2> -</div> -<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>Applicant Name</mat-label> - <input - matInput - id="applicant" - formControlName="applicant" - type="text" - [class.valid]=" - createForm.get('applicant')!.valid && - (createForm.get('applicant')!.dirty || createForm.get('applicant')!.touched) - " - [class.invalid]=" - createForm.get('applicant')!.invalid && - (createForm.get('applicant')!.dirty || createForm.get('applicant')!.touched) - " - /> - <mat-error - class="text-danger" - *ngIf="createForm.get('applicant')!.touched && createForm.get('applicant')!.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 *" - bindLabel="name" - bindValue="uuid" - [clearable]="false" - formControlName="localGovernment" - (change)="onSelectGovernment($event)" - > - <ng-template ng-option-tmp let-item="item" let-search="searchTerm"> - <div [ngOptionHighlight]="search">{{ item.name }}</div> - </ng-template> - </ng-select> - </div> - <div> - <ng-select - appearance="outline" - class="card-region" - [items]="regions" - appendTo="body" - placeholder="Region *" - bindLabel="label" - bindValue="code" - [clearable]="false" - formControlName="region" - > - </ng-select> - </div> - </div> - </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 - </button> - </div> - </mat-dialog-actions> -</form> diff --git a/alcs-frontend/src/app/features/board/dialogs/covenant/create/create-covenant-dialog.component.scss b/alcs-frontend/src/app/features/board/dialogs/covenant/create/create-covenant-dialog.component.scss deleted file mode 100644 index e47b56d77e..0000000000 --- a/alcs-frontend/src/app/features/board/dialogs/covenant/create/create-covenant-dialog.component.scss +++ /dev/null @@ -1,7 +0,0 @@ -.two-item-row { - display: grid; - grid-template-columns: 1fr 1fr; - grid-column-gap: 24px; - grid-row-gap: 24px; - margin-bottom: 24px; -} diff --git a/alcs-frontend/src/app/features/board/dialogs/covenant/create/create-covenant-dialog.component.spec.ts b/alcs-frontend/src/app/features/board/dialogs/covenant/create/create-covenant-dialog.component.spec.ts deleted file mode 100644 index cee123b34e..0000000000 --- a/alcs-frontend/src/app/features/board/dialogs/covenant/create/create-covenant-dialog.component.spec.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; -import { MatDividerModule } from '@angular/material/divider'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatInputModule } from '@angular/material/input'; -import { MatSelectModule } from '@angular/material/select'; -import { MatSnackBarModule } from '@angular/material/snack-bar'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; - -import { CreateCovenantDialogComponent } from './create-covenant-dialog.component'; - -describe('CreateCovenantDialogComponent', () => { - let component: CreateCovenantDialogComponent; - let fixture: ComponentFixture<CreateCovenantDialogComponent>; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [CreateCovenantDialogComponent], - imports: [ - MatDialogModule, - HttpClientTestingModule, - MatFormFieldModule, - MatDividerModule, - MatInputModule, - MatSelectModule, - BrowserAnimationsModule, - MatSnackBarModule, - ], - providers: [ - { provide: MatDialogRef, useValue: {} }, - { provide: MAT_DIALOG_DATA, useValue: {} }, - ], - schemas: [NO_ERRORS_SCHEMA], - }).compileComponents(); - - fixture = TestBed.createComponent(CreateCovenantDialogComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - it('should render the form fields', () => { - fixture.detectChanges(); - - const compiled = fixture.debugElement.nativeElement; - expect(compiled.querySelector('#applicant')).toBeTruthy(); - expect(compiled.querySelector('#fileNumber')).toBeTruthy(); - }); -}); diff --git a/alcs-frontend/src/app/features/board/dialogs/covenant/create/create-covenant-dialog.component.ts b/alcs-frontend/src/app/features/board/dialogs/covenant/create/create-covenant-dialog.component.ts deleted file mode 100644 index 9bd27a7e9a..0000000000 --- a/alcs-frontend/src/app/features/board/dialogs/covenant/create/create-covenant-dialog.component.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -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 { CreateCovenantDto } from '../../../../../services/covenant/covenant.dto'; -import { CovenantService } from '../../../../../services/covenant/covenant.service'; -import { ToastService } from '../../../../../services/toast/toast.service'; - -@Component({ - selector: 'app-create-covenant-dialog', - templateUrl: './create-covenant-dialog.component.html', - styleUrls: ['./create-covenant-dialog.component.scss'], -}) -export class CreateCovenantDialogComponent implements OnInit, OnDestroy { - $destroy = new Subject<void>(); - regions: ApplicationRegionDto[] = []; - localGovernments: ApplicationLocalGovernmentDto[] = []; - isLoading: boolean = false; - currentBoardCode: string = ''; - - fileNumberControl = new FormControl<string | any>('', [Validators.required]); - regionControl = new FormControl<string | null>(null, [Validators.required]); - localGovernmentControl = new FormControl<string | null>(null, [Validators.required]); - applicantControl = new FormControl<string | any>('', [Validators.required]); - - createForm = new FormGroup({ - fileNumber: this.fileNumberControl, - region: this.regionControl, - localGovernment: this.localGovernmentControl, - applicant: this.applicantControl, - }); - - constructor( - @Inject(MAT_DIALOG_DATA) public data: any, - private dialogRef: MatDialogRef<CreateCovenantDialogComponent>, - private covenantService: CovenantService, - private cardService: CardService, - private applicationService: ApplicationService, - private localGovernmentService: ApplicationLocalGovernmentService, - private toastService: ToastService - ) {} - - ngOnInit(): void { - this.currentBoardCode = this.data.currentBoardCode; - this.cardService.fetchCodes(); - - this.localGovernmentService.list().then((res) => { - this.localGovernments = res; - }); - - this.applicationService.$applicationRegions.pipe(takeUntil(this.$destroy)).subscribe((regions) => { - this.regions = regions; - }); - } - - async onSubmit() { - try { - this.isLoading = true; - const formValues = this.createForm.getRawValue(); - const planningReview: CreateCovenantDto = { - fileNumber: formValues.fileNumber!.trim(), - regionCode: formValues.region!, - localGovernmentUuid: formValues.localGovernment!, - applicant: formValues.applicant!.trim(), - boardCode: this.currentBoardCode, - }; - - await this.covenantService.create(planningReview); - - this.dialogRef.close(true); - this.toastService.showSuccessToast('Covenant card created'); - } finally { - this.isLoading = false; - } - } - - onSelectGovernment(value: ApplicationLocalGovernmentDto) { - this.createForm.patchValue({ - region: value.preferredRegionCode, - }); - } - - ngOnDestroy(): void { - this.$destroy.next(); - this.$destroy.complete(); - } -} diff --git a/alcs-frontend/src/app/features/home/assigned/assigned.component.html b/alcs-frontend/src/app/features/home/assigned/assigned.component.html index 1141dee85e..8d84cdbcc7 100644 --- a/alcs-frontend/src/app/features/home/assigned/assigned.component.html +++ b/alcs-frontend/src/app/features/home/assigned/assigned.component.html @@ -11,9 +11,9 @@ <h4>Cards Assigned to Me: {{ totalFiles }}</h4> <app-assigned-table [assignedFiles]="applications"></app-assigned-table> </section> - <section *ngIf="nonApplications.length"> + <section *ngIf="planningReferrals.length"> <div class="subheading2">Non-Applications</div> - <app-assigned-table [assignedFiles]="nonApplications"></app-assigned-table> + <app-assigned-table [assignedFiles]="planningReferrals"></app-assigned-table> </section> <section *ngIf="notifications.length"> 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 2d24740773..445abfb5ea 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 @@ -36,7 +36,6 @@ describe('AssignedComponent', () => { mockHomeService.fetchAssignedToMe.mockResolvedValue({ applications: [], - covenants: [], modifications: [], noticeOfIntentModifications: [], planningReferrals: [], 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 ad2e3fd15b..94192a8516 100644 --- a/alcs-frontend/src/app/features/home/assigned/assigned.component.ts +++ b/alcs-frontend/src/app/features/home/assigned/assigned.component.ts @@ -3,14 +3,12 @@ import { ApplicationModificationDto } from '../../../services/application/applic import { ApplicationReconsiderationDto } from '../../../services/application/application-reconsideration/application-reconsideration.dto'; import { ApplicationDto } from '../../../services/application/application.dto'; import { ApplicationService } from '../../../services/application/application.service'; -import { CovenantDto } from '../../../services/covenant/covenant.dto'; 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 { PlanningReferralDto, PlanningReviewDto } from '../../../services/planning-review/planning-review.dto'; +import { PlanningReferralDto } from '../../../services/planning-review/planning-review.dto'; import { - COVENANT_TYPE_LABEL, MODIFICATION_TYPE_LABEL, NOTIFICATION_LABEL, PLANNING_TYPE_LABEL, @@ -27,7 +25,7 @@ import { AssignedToMeFile } from './assigned-table/assigned-table.component'; export class AssignedComponent implements OnInit { noticeOfIntents: AssignedToMeFile[] = []; applications: AssignedToMeFile[] = []; - nonApplications: AssignedToMeFile[] = []; + planningReferrals: AssignedToMeFile[] = []; notifications: AssignedToMeFile[] = []; totalFiles = 0; @@ -47,7 +45,6 @@ export class AssignedComponent implements OnInit { reconsiderations, planningReferrals, modifications, - covenants, noticeOfIntents, noticeOfIntentModifications, notifications, @@ -98,23 +95,15 @@ export class AssignedComponent implements OnInit { .sort((a, b) => a.date! - b.date!), ]; - this.nonApplications = [ + this.planningReferrals = [ ...planningReferrals .filter((r) => r.card.highPriority) .map((r) => this.mapPlanning(r)) .sort((a, b) => a.date! - b.date!), - ...covenants - .filter((r) => r.card.highPriority) - .map((r) => this.mapCovenant(r)) - .sort((a, b) => a.date! - b.date!), ...planningReferrals .filter((r) => !r.card.highPriority) .map((r) => this.mapPlanning(r)) .sort((a, b) => a.date! - b.date!), - ...covenants - .filter((r) => !r.card.highPriority) - .map((r) => this.mapCovenant(r)) - .sort((a, b) => a.date! - b.date!), ]; this.notifications = [ @@ -129,18 +118,10 @@ export class AssignedComponent implements OnInit { ]; this.totalFiles = - this.applications.length + this.nonApplications.length + this.noticeOfIntents.length + this.notifications.length; - } - - private mapCovenant(c: CovenantDto): AssignedToMeFile { - return { - title: `${c.fileNumber} (${c.applicant})`, - type: c.card.type, - date: c.card.createdAt, - card: c.card, - highPriority: c.card.highPriority, - labels: [COVENANT_TYPE_LABEL], - }; + this.applications.length + + this.planningReferrals.length + + this.noticeOfIntents.length + + this.notifications.length; } private mapPlanning(p: PlanningReferralDto): AssignedToMeFile { diff --git a/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.html b/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.html index 50a08fb9af..3c6e515460 100644 --- a/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.html +++ b/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.html @@ -30,12 +30,6 @@ [type]="RECON_TYPE_LABEL" > </app-application-type-pill> - <app-application-type-pill - [useShortLabel]="true" - *ngIf="element.parentType === 'covenant'" - [type]="COVENANT_TYPE_LABEL" - > - </app-application-type-pill> <app-application-type-pill [useShortLabel]="true" *ngIf="element.parentType === 'planning-review'" diff --git a/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.ts b/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.ts index 783aea7f8d..349bb6b7db 100644 --- a/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.ts +++ b/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.ts @@ -5,7 +5,6 @@ import { CardSubtaskService } from '../../../../services/card/card-subtask/card- import { AssigneeDto, UserDto } from '../../../../services/user/user.dto'; import { NgSelectComponent } from '@ng-select/ng-select'; import { - COVENANT_TYPE_LABEL, MODIFICATION_TYPE_LABEL, NOTIFICATION_LABEL, PLANNING_TYPE_LABEL, @@ -22,10 +21,8 @@ export class SubtaskTableComponent { @Input() subtasks: HomepageSubtaskDto[] = []; @Input() users: AssigneeDto[] = []; - MODIFICATION_TYPE_LABEL = MODIFICATION_TYPE_LABEL; PLANNING_TYPE_LABEL = PLANNING_TYPE_LABEL; - COVENANT_TYPE_LABEL = COVENANT_TYPE_LABEL; RECON_TYPE_LABEL = RECON_TYPE_LABEL; NOTIFICATION_LABEL = NOTIFICATION_LABEL; @@ -43,7 +40,7 @@ export class SubtaskTableComponent { const columns = ['highPriority', 'title', 'type', 'activeDays', 'stage', 'assignee', 'action']; // Check if any file has type 'NOTI' - const hasNoti = this.subtasks.some(task => task.parentType === 'notification'); + const hasNoti = this.subtasks.some((task) => task.parentType === 'notification'); // If 'NOTI' type exists, remove 'activeDays' column if (hasNoti) { const index = columns.indexOf('activeDays'); diff --git a/alcs-frontend/src/app/features/home/subtask/subtask.component.html b/alcs-frontend/src/app/features/home/subtask/subtask.component.html index 5d9519df0f..388b546fb2 100644 --- a/alcs-frontend/src/app/features/home/subtask/subtask.component.html +++ b/alcs-frontend/src/app/features/home/subtask/subtask.component.html @@ -9,9 +9,9 @@ <h4>{{ subtaskLabel }} Subtasks: {{ totalSubtaskCount }}</h4> <div class="subheading2">Applications</div> <app-subtask-table [subtasks]="applicationSubtasks" [users]="users"></app-subtask-table> </section> - <section *ngIf="nonApplicationSubtasks.length && showAppAndNonApp"> + <section *ngIf="planningReviewSubtasks.length && showAppAndNonApp"> <div class="subheading2">Non-Applications</div> - <app-subtask-table [subtasks]="nonApplicationSubtasks" [users]="users"></app-subtask-table> + <app-subtask-table [subtasks]="planningReviewSubtasks" [users]="users"></app-subtask-table> </section> <section *ngIf="notificationSubtasks.length"> <div class="subheading2">Notifications</div> diff --git a/alcs-frontend/src/app/features/home/subtask/subtask.component.ts b/alcs-frontend/src/app/features/home/subtask/subtask.component.ts index b1a0e64514..f01b49e517 100644 --- a/alcs-frontend/src/app/features/home/subtask/subtask.component.ts +++ b/alcs-frontend/src/app/features/home/subtask/subtask.component.ts @@ -23,7 +23,7 @@ export class SubtaskComponent implements OnInit, OnDestroy { public users: AssigneeDto[] = []; applicationSubtasks: HomepageSubtaskDto[] = []; noticeOfIntentSubtasks: HomepageSubtaskDto[] = []; - nonApplicationSubtasks: HomepageSubtaskDto[] = []; + planningReviewSubtasks: HomepageSubtaskDto[] = []; notificationSubtasks: HomepageSubtaskDto[] = []; showNoi = true; @@ -57,7 +57,6 @@ export class SubtaskComponent implements OnInit, OnDestroy { const reconsiderations = allSubtasks.filter((s) => s.card.type === CardType.RECON); const planningReviews = allSubtasks.filter((s) => s.card.type === CardType.PLAN); const modifications = allSubtasks.filter((s) => s.card.type === CardType.MODI); - const covenants = allSubtasks.filter((s) => s.card.type === CardType.COV); const nois = allSubtasks.filter((s) => s.card.type === CardType.NOI); const noiModifications = allSubtasks.filter((s) => s.card.type === CardType.NOI_MODI); const notifications = allSubtasks.filter((s) => s.card.type === CardType.NOTIFICATION); @@ -78,11 +77,9 @@ export class SubtaskComponent implements OnInit, OnDestroy { ...noiModifications.filter((r) => !r.card.highPriority).sort((a, b) => a.createdAt! - b.createdAt!), ]; - this.nonApplicationSubtasks = [ + this.planningReviewSubtasks = [ ...planningReviews.filter((r) => r.card.highPriority).sort((a, b) => a.createdAt! - b.createdAt!), - ...covenants.filter((r) => r.card.highPriority).sort((a, b) => a.createdAt! - b.createdAt!), ...planningReviews.filter((r) => !r.card.highPriority).sort((a, b) => a.createdAt! - b.createdAt!), - ...covenants.filter((r) => !r.card.highPriority).sort((a, b) => a.createdAt! - b.createdAt!), ]; this.notificationSubtasks = [ @@ -96,14 +93,14 @@ export class SubtaskComponent implements OnInit, OnDestroy { if (this.showAppAndNonApp) { this.totalSubtaskCount = - this.applicationSubtasks.length + this.nonApplicationSubtasks.length + this.notificationSubtasks.length; + this.applicationSubtasks.length + this.planningReviewSubtasks.length + this.notificationSubtasks.length; } if (this.showAppAndNonApp && this.showNoi) { this.totalSubtaskCount = this.applicationSubtasks.length + this.noticeOfIntentSubtasks.length + - this.nonApplicationSubtasks.length + + this.planningReviewSubtasks.length + this.notificationSubtasks.length; } } diff --git a/alcs-frontend/src/app/features/search/file-type-filter-drop-down/file-type-filter-drop-down.component.ts b/alcs-frontend/src/app/features/search/file-type-filter-drop-down/file-type-filter-drop-down.component.ts index c4a1e67b54..abae549fbe 100644 --- a/alcs-frontend/src/app/features/search/file-type-filter-drop-down/file-type-filter-drop-down.component.ts +++ b/alcs-frontend/src/app/features/search/file-type-filter-drop-down/file-type-filter-drop-down.component.ts @@ -162,7 +162,7 @@ export class FileTypeFilterDropDownComponent implements AfterViewInit { this.fileTypeChange.emit( this.checklistSelection.selected .filter((selectedItem) => selectedItem.item.value) - .map((selectedItem) => selectedItem.item.value!) + .map((selectedItem) => selectedItem.item.value!), ); } @@ -173,7 +173,7 @@ export class FileTypeFilterDropDownComponent implements AfterViewInit { } ngAfterViewInit(): void { - this.treeControl.expandAll(); + //this.treeControl.expandAll(); } clear() { diff --git a/alcs-frontend/src/app/features/search/notice-of-intent-search-table/notice-of-intent-search-table.component.ts b/alcs-frontend/src/app/features/search/notice-of-intent-search-table/notice-of-intent-search-table.component.ts index ca4080a0ba..b32404ce25 100644 --- a/alcs-frontend/src/app/features/search/notice-of-intent-search-table/notice-of-intent-search-table.component.ts +++ b/alcs-frontend/src/app/features/search/notice-of-intent-search-table/notice-of-intent-search-table.component.ts @@ -84,8 +84,8 @@ export class NoticeOfIntentSearchTableComponent { window.open(url, '_blank'); } - private mapNoticeOfIntent(applications: NoticeOfIntentSearchResultDto[]): SearchResult[] { - return applications.map((e) => { + private mapNoticeOfIntent(noticesOfIntent: NoticeOfIntentSearchResultDto[]): SearchResult[] { + return noticesOfIntent.map((e) => { const status = this.statuses.find((st) => st.code === e.status); return { diff --git a/alcs-frontend/src/app/features/search/non-application-search-table/non-application-search-table.component.html b/alcs-frontend/src/app/features/search/planning-review-search-table/planning-review-search-table.component.html similarity index 77% rename from alcs-frontend/src/app/features/search/non-application-search-table/non-application-search-table.component.html rename to alcs-frontend/src/app/features/search/planning-review-search-table/planning-review-search-table.component.html index 776be54397..3534373d59 100644 --- a/alcs-frontend/src/app/features/search/non-application-search-table/non-application-search-table.component.html +++ b/alcs-frontend/src/app/features/search/planning-review-search-table/planning-review-search-table.component.html @@ -20,23 +20,14 @@ <th mat-header-cell *matHeaderCellDef mat-sort-header>Type</th> <td mat-cell *matCellDef="let element"> - <app-application-type-pill - [useShortLabel]="true" - *ngIf="element.class === 'PLAN'" - [type]="PLANNING_TYPE_LABEL" - /> - <app-application-type-pill - [useShortLabel]="true" - *ngIf="element.class === 'COV'" - [type]="COVENANT_TYPE_LABEL" - /> + <app-application-type-pill [useShortLabel]="true" [type]="element.type" /> </td> </ng-container> <ng-container matColumnDef="applicant"> - <th mat-header-cell *matHeaderCellDef mat-sort-header>Owner Name</th> + <th mat-header-cell *matHeaderCellDef mat-sort-header>Document Name</th> <td mat-cell *matCellDef="let element"> - {{ element.applicant | emptyColumn }} + {{ element.documentName | emptyColumn }} </td> </ng-container> @@ -47,6 +38,14 @@ </td> </ng-container> + <ng-container matColumnDef="status"> + <th mat-header-cell *matHeaderCellDef mat-sort-header>Status</th> + <td mat-cell *matCellDef="let element"> + <app-application-type-pill *ngIf="element.open" [type]="OPEN_TYPE" /> + <app-application-type-pill *ngIf="!element.open" [type]="CLOSED_TYPE" /> + </td> + </ng-container> + <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> <tr (click)="onSelectRecord(row)" mat-row *matRowDef="let row; columns: displayedColumns"></tr> diff --git a/alcs-frontend/src/app/features/search/non-application-search-table/non-application-search-table.component.scss b/alcs-frontend/src/app/features/search/planning-review-search-table/planning-review-search-table.component.scss similarity index 100% rename from alcs-frontend/src/app/features/search/non-application-search-table/non-application-search-table.component.scss rename to alcs-frontend/src/app/features/search/planning-review-search-table/planning-review-search-table.component.scss diff --git a/alcs-frontend/src/app/features/search/non-application-search-table/non-application-search-table.component.spec.ts b/alcs-frontend/src/app/features/search/planning-review-search-table/planning-review-search-table.component.spec.ts similarity index 66% rename from alcs-frontend/src/app/features/search/non-application-search-table/non-application-search-table.component.spec.ts rename to alcs-frontend/src/app/features/search/planning-review-search-table/planning-review-search-table.component.spec.ts index b23aefd68b..2ecad7c5d6 100644 --- a/alcs-frontend/src/app/features/search/non-application-search-table/non-application-search-table.component.spec.ts +++ b/alcs-frontend/src/app/features/search/planning-review-search-table/planning-review-search-table.component.spec.ts @@ -2,18 +2,18 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { NonApplicationSearchTableComponent } from './non-application-search-table.component'; +import { PlanningReviewSearchTableComponent } from './planning-review-search-table.component'; describe('NonApplicationSearchTableComponent', () => { - let component: NonApplicationSearchTableComponent; - let fixture: ComponentFixture<NonApplicationSearchTableComponent>; + let component: PlanningReviewSearchTableComponent; + let fixture: ComponentFixture<PlanningReviewSearchTableComponent>; let mockRouter: DeepMocked<Router>; beforeEach(async () => { mockRouter = createMock(); - + await TestBed.configureTestingModule({ - declarations: [NonApplicationSearchTableComponent], + declarations: [PlanningReviewSearchTableComponent], providers: [ { provide: Router, @@ -22,7 +22,7 @@ describe('NonApplicationSearchTableComponent', () => { ], }).compileComponents(); - fixture = TestBed.createComponent(NonApplicationSearchTableComponent); + fixture = TestBed.createComponent(PlanningReviewSearchTableComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/alcs-frontend/src/app/features/search/non-application-search-table/non-application-search-table.component.ts b/alcs-frontend/src/app/features/search/planning-review-search-table/planning-review-search-table.component.ts similarity index 55% rename from alcs-frontend/src/app/features/search/non-application-search-table/non-application-search-table.component.ts rename to alcs-frontend/src/app/features/search/planning-review-search-table/planning-review-search-table.component.ts index 8212c4f7a7..c4f2c20c15 100644 --- a/alcs-frontend/src/app/features/search/non-application-search-table/non-application-search-table.component.ts +++ b/alcs-frontend/src/app/features/search/planning-review-search-table/planning-review-search-table.component.ts @@ -1,12 +1,9 @@ -import { Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; import { PageEvent } from '@angular/material/paginator'; import { Sort, SortDirection } from '@angular/material/sort'; import { Router } from '@angular/router'; -import { NonApplicationSearchResultDto } from '../../../services/search/search.dto'; -import { - COVENANT_TYPE_LABEL, - PLANNING_TYPE_LABEL, -} from '../../../shared/application-type-pill/application-type-pill.constants'; +import { PlanningReviewSearchResultDto } from '../../../services/search/search.dto'; +import { CLOSED_PR_LABEL, OPEN_PR_LABEL } from '../../../shared/application-type-pill/application-type-pill.constants'; import { TableChange } from '../search.interface'; interface SearchResult { @@ -19,25 +16,28 @@ interface SearchResult { } @Component({ - selector: 'app-non-application-search-table', - templateUrl: './non-application-search-table.component.html', - styleUrls: ['./non-application-search-table.component.scss'], + selector: 'app-planning-review-search-table', + templateUrl: './planning-review-search-table.component.html', + styleUrls: ['./planning-review-search-table.component.scss'], }) -export class NonApplicationSearchTableComponent { - _nonApplications: NonApplicationSearchResultDto[] = []; - @Input() set nonApplications(nonApplications: NonApplicationSearchResultDto[]) { - this._nonApplications = nonApplications; +export class PlanningReviewSearchTableComponent { + _planningReviews: PlanningReviewSearchResultDto[] = []; + @Input() set planningReviews(planningReviews: PlanningReviewSearchResultDto[]) { + this._planningReviews = planningReviews; this.isLoading = false; - this.dataSource = this.mapNonApplications(nonApplications); + this.dataSource = planningReviews; } + OPEN_TYPE = OPEN_PR_LABEL; + CLOSED_TYPE = CLOSED_PR_LABEL; + @Input() totalCount: number | undefined; @Input() pageIndex: number = 0; @Output() tableChange = new EventEmitter<TableChange>(); - displayedColumns = ['fileId', 'type', 'applicant', 'government']; - dataSource: NonApplicationSearchResultDto[] = []; + displayedColumns = ['fileId', 'type', 'applicant', 'government', 'status']; + dataSource: PlanningReviewSearchResultDto[] = []; itemsPerPage = 20; total = 0; @@ -45,9 +45,6 @@ export class NonApplicationSearchTableComponent { sortField = 'fileId'; isLoading = false; - COVENANT_TYPE_LABEL = COVENANT_TYPE_LABEL; - PLANNING_TYPE_LABEL = PLANNING_TYPE_LABEL; - constructor(private router: Router) {} onTableChange() { @@ -84,19 +81,4 @@ export class NonApplicationSearchTableComponent { window.open(url, '_blank'); } - - private mapNonApplications(nonApplications: NonApplicationSearchResultDto[]): NonApplicationSearchResultDto[] { - return nonApplications.map((e) => { - return { - fileNumber: e.fileNumber, - type: e.type, - applicant: e.applicant, - boardCode: e.boardCode, - localGovernmentName: e.localGovernmentName, - referenceId: e.referenceId, - board: e.boardCode, - class: e.class, - }; - }); - } } diff --git a/alcs-frontend/src/app/features/search/search.component.html b/alcs-frontend/src/app/features/search/search.component.html index bc6f0f2305..9ad377c6a6 100644 --- a/alcs-frontend/src/app/features/search/search.component.html +++ b/alcs-frontend/src/app/features/search/search.component.html @@ -280,15 +280,15 @@ <h2 class="search-title">Search Results:</h2> </mat-tab> <mat-tab> - <ng-template mat-tab-label> Non-Applications: {{ nonApplicationsTotal }} </ng-template> - <app-non-application-search-table + <ng-template mat-tab-label> Planning Reviews: {{ planningReviewsTotal }} </ng-template> + <app-planning-review-search-table *ngIf="!isLoading" - [nonApplications]="nonApplications" - [totalCount]="nonApplicationsTotal" + [planningReviews]="planningReviews" + [totalCount]="planningReviewsTotal" [pageIndex]="pageIndex" (tableChange)="onTableChange($event)" > - </app-non-application-search-table> + </app-planning-review-search-table> <div class="center"> <mat-spinner class="spinner" *ngIf="isLoading"></mat-spinner> </div> diff --git a/alcs-frontend/src/app/features/search/search.component.ts b/alcs-frontend/src/app/features/search/search.component.ts index 7bea90aeec..cdd19c4e42 100644 --- a/alcs-frontend/src/app/features/search/search.component.ts +++ b/alcs-frontend/src/app/features/search/search.component.ts @@ -20,9 +20,9 @@ import { NotificationSubmissionStatusDto } from '../../services/notification/not import { AdvancedSearchResponseDto, ApplicationSearchResultDto, - NonApplicationSearchResultDto, NoticeOfIntentSearchResultDto, NotificationSearchResultDto, + PlanningReviewSearchResultDto, SearchRequestDto, } from '../../services/search/search.dto'; import { SearchService } from '../../services/search/search.service'; @@ -53,8 +53,8 @@ export class SearchComponent implements OnInit, OnDestroy { noticeOfIntents: NoticeOfIntentSearchResultDto[] = []; noticeOfIntentTotal = 0; - nonApplications: NonApplicationSearchResultDto[] = []; - nonApplicationsTotal = 0; + planningReviews: PlanningReviewSearchResultDto[] = []; + planningReviewsTotal = 0; notifications: NotificationSearchResultDto[] = []; notificationTotal = 0; @@ -300,12 +300,12 @@ export class SearchComponent implements OnInit, OnDestroy { this.noticeOfIntentTotal = result?.total ?? 0; } - async onNonApplicationSearch() { + async onPlanningReviewSearch() { const searchParams = this.getSearchParams(); - const result = await this.searchService.advancedSearchNonApplicationsFetch(searchParams); + const result = await this.searchService.advancedSearchPlanningReviewsFetch(searchParams); - this.nonApplications = result?.data ?? []; - this.nonApplicationsTotal = result?.total ?? 0; + this.planningReviews = result?.data ?? []; + this.planningReviewsTotal = result?.total ?? 0; } async onNotificationSearch() { @@ -330,7 +330,7 @@ export class SearchComponent implements OnInit, OnDestroy { await this.onNoticeOfIntentSearch(); break; case 'NONAPP': - await this.onNonApplicationSearch(); + await this.onPlanningReviewSearch(); break; case 'NOTI': await this.onNotificationSearch(); @@ -364,12 +364,12 @@ export class SearchComponent implements OnInit, OnDestroy { searchResult = { applications: [], noticeOfIntents: [], - nonApplications: [], + planningReviews: [], notifications: [], totalApplications: 0, totalNoticeOfIntents: 0, - totalNonApplications: 0, totalNotifications: 0, + totalPlanningReviews: 0, }; } @@ -379,8 +379,8 @@ export class SearchComponent implements OnInit, OnDestroy { this.noticeOfIntentTotal = searchResult.totalNoticeOfIntents; this.noticeOfIntents = searchResult.noticeOfIntents; - this.nonApplicationsTotal = searchResult.totalNonApplications; - this.nonApplications = searchResult.nonApplications; + this.planningReviewsTotal = searchResult.totalPlanningReviews; + this.planningReviews = searchResult.planningReviews; this.notifications = searchResult.notifications; this.notificationTotal = searchResult.totalNotifications; @@ -391,7 +391,7 @@ export class SearchComponent implements OnInit, OnDestroy { const searchCounts = [ this.applicationTotal, this.noticeOfIntentTotal, - this.nonApplicationsTotal, + this.planningReviewsTotal, this.notificationTotal, ]; diff --git a/alcs-frontend/src/app/features/search/search.module.ts b/alcs-frontend/src/app/features/search/search.module.ts index fce2699aa6..e26045094e 100644 --- a/alcs-frontend/src/app/features/search/search.module.ts +++ b/alcs-frontend/src/app/features/search/search.module.ts @@ -7,7 +7,7 @@ import { RouterModule, Routes } from '@angular/router'; import { SharedModule } from '../../shared/shared.module'; import { ApplicationSearchTableComponent } from './application-search-table/application-search-table.component'; import { FileTypeFilterDropDownComponent } from './file-type-filter-drop-down/file-type-filter-drop-down.component'; -import { NonApplicationSearchTableComponent } from './non-application-search-table/non-application-search-table.component'; +import { PlanningReviewSearchTableComponent } from './planning-review-search-table/planning-review-search-table.component'; import { NoticeOfIntentSearchTableComponent } from './notice-of-intent-search-table/notice-of-intent-search-table.component'; import { NotificationSearchTableComponent } from './notification-search-table/notification-search-table.component'; import { SearchComponent } from './search.component'; @@ -24,7 +24,7 @@ const routes: Routes = [ SearchComponent, ApplicationSearchTableComponent, NoticeOfIntentSearchTableComponent, - NonApplicationSearchTableComponent, + PlanningReviewSearchTableComponent, NotificationSearchTableComponent, FileTypeFilterDropDownComponent, ], diff --git a/alcs-frontend/src/app/services/board/board.dto.ts b/alcs-frontend/src/app/services/board/board.dto.ts index 5d44e29719..36eeadaed2 100644 --- a/alcs-frontend/src/app/services/board/board.dto.ts +++ b/alcs-frontend/src/app/services/board/board.dto.ts @@ -2,11 +2,10 @@ import { CardType } from '../../shared/card/card.component'; import { ApplicationModificationDto } from '../application/application-modification/application-modification.dto'; import { ApplicationReconsiderationDto } from '../application/application-reconsideration/application-reconsideration.dto'; import { ApplicationDto } from '../application/application.dto'; -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 { PlanningReferralDto, PlanningReviewDto } from '../planning-review/planning-review.dto'; +import { PlanningReferralDto } from '../planning-review/planning-review.dto'; export interface MinimalBoardDto { code: string; @@ -32,7 +31,6 @@ export interface CardsDto { reconsiderations: ApplicationReconsiderationDto[]; planningReferrals: PlanningReferralDto[]; modifications: ApplicationModificationDto[]; - covenants: CovenantDto[]; noticeOfIntents: NoticeOfIntentDto[]; noiModifications: NoticeOfIntentModificationDto[]; notifications: NotificationDto[]; diff --git a/alcs-frontend/src/app/services/covenant/covenant.dto.ts b/alcs-frontend/src/app/services/covenant/covenant.dto.ts deleted file mode 100644 index da13893386..0000000000 --- a/alcs-frontend/src/app/services/covenant/covenant.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -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 CreateCovenantDto { - fileNumber: string; - localGovernmentUuid: string; - regionCode: string; - applicant: string; - boardCode: string; -} - -export interface CovenantDto { - fileNumber: string; - card: CardDto; - localGovernment: ApplicationLocalGovernmentDto; - region: ApplicationRegionDto; - applicant: string; -} diff --git a/alcs-frontend/src/app/services/covenant/covenant.service.spec.ts b/alcs-frontend/src/app/services/covenant/covenant.service.spec.ts deleted file mode 100644 index e820143cbe..0000000000 --- a/alcs-frontend/src/app/services/covenant/covenant.service.spec.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { HttpClient } from '@angular/common/http'; -import { TestBed } from '@angular/core/testing'; -import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { firstValueFrom, of, throwError } from 'rxjs'; -import { ToastService } from '../toast/toast.service'; - -import { CovenantService } from './covenant.service'; - -describe('CovenantService', () => { - let service: CovenantService; - let httpClient: DeepMocked<HttpClient>; - let toastService: DeepMocked<ToastService>; - - beforeEach(() => { - httpClient = createMock(); - toastService = createMock(); - - TestBed.configureTestingModule({ - providers: [ - { - provide: HttpClient, - useValue: httpClient, - }, - { - provide: ToastService, - useValue: toastService, - }, - ], - }); - service = TestBed.inject(CovenantService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); - - it('should create covenants', async () => { - httpClient.post.mockReturnValue( - of({ - fileNumber: '1', - }) - ); - - const res = await service.create({ - applicant: '', - boardCode: '', - fileNumber: '', - localGovernmentUuid: '', - regionCode: '', - }); - - expect(httpClient.post).toHaveBeenCalledTimes(1); - expect(res.fileNumber).toEqual('1'); - }); - - it('should show an error toast message if create fails', async () => { - httpClient.post.mockReturnValue( - throwError(() => { - new Error(''); - }) - ); - - try { - await service.create({ - applicant: '', - boardCode: '', - fileNumber: '', - localGovernmentUuid: '', - regionCode: '', - }); - } catch (e) { - //OM NOM NOM - } - - expect(httpClient.post).toHaveBeenCalledTimes(1); - expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); - }); - - it('should fetch by card', async () => { - httpClient.get.mockReturnValue( - of({ - fileNumber: '1', - }) - ); - - const res = await service.fetchByCardUuid('1'); - - expect(httpClient.get).toHaveBeenCalledTimes(1); - expect(res).toBeDefined(); - expect(res!.fileNumber).toEqual('1'); - }); - - it('should show an error toast message if fetch by card fails', async () => { - httpClient.get.mockReturnValue( - throwError(() => { - new Error(''); - }) - ); - - await service.fetchByCardUuid('1'); - - expect(httpClient.get).toHaveBeenCalledTimes(1); - expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); - }); -}); diff --git a/alcs-frontend/src/app/services/covenant/covenant.service.ts b/alcs-frontend/src/app/services/covenant/covenant.service.ts deleted file mode 100644 index 569b9467e7..0000000000 --- a/alcs-frontend/src/app/services/covenant/covenant.service.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { HttpClient, HttpErrorResponse } 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 { CovenantDto, CreateCovenantDto } from './covenant.dto'; - -@Injectable({ - providedIn: 'root', -}) -export class CovenantService { - private url = `${environment.apiUrl}/covenant`; - - constructor(private http: HttpClient, private toastService: ToastService) {} - - async create(covenant: CreateCovenantDto) { - try { - return await firstValueFrom(this.http.post<CovenantDto>(`${this.url}`, covenant)); - } catch (e) { - console.error(e); - if (e instanceof HttpErrorResponse && e.status === 400) { - this.toastService.showErrorToast(`Covenant/Application/NOI with File ID ${covenant.fileNumber} already exists`); - } else { - this.toastService.showErrorToast('Failed to create Covenant'); - } - throw e; - } - } - - async fetchByCardUuid(id: string) { - try { - return await firstValueFrom(this.http.get<CovenantDto>(`${this.url}/card/${id}`)); - } catch (e) { - console.error(e); - this.toastService.showErrorToast('Failed to fetch covenant'); - } - return; - } -} diff --git a/alcs-frontend/src/app/services/home/home.service.ts b/alcs-frontend/src/app/services/home/home.service.ts index 0a0c3384bc..dccd47e755 100644 --- a/alcs-frontend/src/app/services/home/home.service.ts +++ b/alcs-frontend/src/app/services/home/home.service.ts @@ -6,7 +6,6 @@ import { ApplicationModificationDto } from '../application/application-modificat import { ApplicationReconsiderationDto } from '../application/application-reconsideration/application-reconsideration.dto'; import { ApplicationDto } from '../application/application.dto'; import { CARD_SUBTASK_TYPE, HomepageSubtaskDto } from '../card/card-subtask/card-subtask.dto'; -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'; @@ -25,7 +24,6 @@ export class HomeService { reconsiderations: ApplicationReconsiderationDto[]; planningReferrals: PlanningReferralDto[]; modifications: ApplicationModificationDto[]; - covenants: CovenantDto[]; noticeOfIntents: NoticeOfIntentDto[]; noticeOfIntentModifications: NoticeOfIntentModificationDto[]; notifications: NotificationDto[]; diff --git a/alcs-frontend/src/app/services/search/file-type/file-type-data-source.service.spec.ts b/alcs-frontend/src/app/services/search/file-type/file-type-data-source.service.spec.ts index b691389f45..663703714c 100644 --- a/alcs-frontend/src/app/services/search/file-type/file-type-data-source.service.spec.ts +++ b/alcs-frontend/src/app/services/search/file-type/file-type-data-source.service.spec.ts @@ -19,11 +19,11 @@ describe('FileTypeDataSourceService', () => { }); it('should filter data', () => { - service.filter('Covenants'); + service.filter('Utility/Energy Planning'); expect(service.data.length).toBe(1); const node: TreeNode = service.data[0]; - expect(node.item.label).toEqual('Non-Application'); + expect(node.item.label).toEqual('Planning Reviews'); }); it('should reset data when filtering with empty text', () => { diff --git a/alcs-frontend/src/app/services/search/file-type/file-type-data-source.service.ts b/alcs-frontend/src/app/services/search/file-type/file-type-data-source.service.ts index cf66283046..de4c3c2b67 100644 --- a/alcs-frontend/src/app/services/search/file-type/file-type-data-source.service.ts +++ b/alcs-frontend/src/app/services/search/file-type/file-type-data-source.service.ts @@ -22,7 +22,7 @@ export interface FlatTreeNode { const TREE_DATA: TreeNode[] = [ { - item: { label: 'Application', value: null }, + item: { label: 'Applications', value: null }, children: [ { item: { label: 'Exclusion', value: 'EXCL' } }, { item: { label: 'Inclusion', value: 'INCL' } }, @@ -53,23 +53,48 @@ const TREE_DATA: TreeNode[] = [ ], }, { - item: { label: 'Notice of Intent', value: 'NOI' }, + item: { label: 'Notices of Intent', value: 'NOI' }, }, { - item: { label: 'Non-Application', value: null }, - + item: { label: 'Planning Reviews', value: 'PLAN' }, children: [ { - item: { label: 'Covenants', value: 'COV' }, + item: { label: 'Agricultural Area Plan', value: 'AAPP' }, + }, + { + item: { label: 'ALR Boundary', value: 'ALRB' }, + }, + { + item: { label: 'L/FNG Boundary Adjustment', value: 'BAPP' }, + }, + { + item: { label: 'Crown Land Use Plan', value: 'CLUP' }, + }, + { + item: { label: 'Misc Studies and Projects', value: 'MISC' }, + }, + { + item: { label: 'Official Community Plan', value: 'OCPP' }, + }, + { + item: { label: 'Parks Planning', value: 'PARK' }, + }, + { + item: { label: 'Regional Growth Strategy', value: 'RGSP' }, }, { - item: { label: 'Planning Review', value: 'PLAN' }, + item: { label: 'Transportation Plan', value: 'TPPP' }, + }, + { + item: { label: 'Utility/Energy Planning', value: 'UEPP' }, + }, + { + item: { label: 'Zoning Bylaw', value: 'ZBPP' }, }, ], }, { item: { label: 'Notifications', value: null }, - children: [ { item: { label: 'SRW', value: 'SRW' }, diff --git a/alcs-frontend/src/app/services/search/search.dto.ts b/alcs-frontend/src/app/services/search/search.dto.ts index f5290e7de6..ba2514e168 100644 --- a/alcs-frontend/src/app/services/search/search.dto.ts +++ b/alcs-frontend/src/app/services/search/search.dto.ts @@ -17,24 +17,24 @@ export interface ApplicationSearchResultDto { export interface NoticeOfIntentSearchResultDto extends ApplicationSearchResultDto {} export interface NotificationSearchResultDto extends ApplicationSearchResultDto {} -export interface NonApplicationSearchResultDto { +export interface PlanningReviewSearchResultDto { type: string | null; - applicant: string | null; + documentName: string | null; referenceId: string | null; localGovernmentName: string | null; fileNumber: string; - boardCode: string | null; - class: 'PLAN' | 'COV'; + class: 'PLAN'; + open: boolean; } export interface AdvancedSearchResponseDto { applications: ApplicationSearchResultDto[]; noticeOfIntents: NoticeOfIntentSearchResultDto[]; - nonApplications: NonApplicationSearchResultDto[]; + planningReviews: PlanningReviewSearchResultDto[]; notifications: NotificationSearchResultDto[]; totalApplications: number; totalNoticeOfIntents: number; - totalNonApplications: number; + totalPlanningReviews: number; totalNotifications: number; } @@ -68,13 +68,6 @@ export interface SearchRequestDto extends PagingRequestDto { fileTypes: string[]; } -export interface NonApplicationsSearchRequestDto extends PagingRequestDto { - fileNumber?: string; - governmentName?: string; - regionCode?: string; - name?: string; -} - export interface SearchResultDto { fileNumber: string; type: string; diff --git a/alcs-frontend/src/app/services/search/search.service.spec.ts b/alcs-frontend/src/app/services/search/search.service.spec.ts index 12f94c593d..4207669a49 100644 --- a/alcs-frontend/src/app/services/search/search.service.spec.ts +++ b/alcs-frontend/src/app/services/search/search.service.spec.ts @@ -49,6 +49,8 @@ describe('SearchService', () => { totalNoticeOfIntents: 0, nonApplications: [], totalNonApplications: 0, + planningReviews: [], + totalPlanningReviews: 0, }; const mockSearchRequestDto = { @@ -96,7 +98,7 @@ describe('SearchService', () => { mockHttpClient.get.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); const res = await service.fetch('1'); @@ -117,15 +119,15 @@ describe('SearchService', () => { expect(res?.applications).toEqual([]); expect(res?.totalNoticeOfIntents).toEqual(0); expect(res?.noticeOfIntents).toEqual([]); - expect(res?.totalNonApplications).toEqual(0); - expect(res?.nonApplications).toEqual([]); + expect(res?.totalPlanningReviews).toEqual(0); + expect(res?.planningReviews).toEqual([]); }); it('should show an error toast message if search fails', async () => { mockHttpClient.post.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); const res = await service.advancedSearchFetch(mockSearchRequestDto); @@ -150,7 +152,7 @@ describe('SearchService', () => { mockHttpClient.post.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); const res = await service.advancedSearchApplicationsFetch(mockSearchRequestDto); @@ -175,7 +177,7 @@ describe('SearchService', () => { mockHttpClient.post.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); const res = await service.advancedSearchNoticeOfIntentsFetch(mockSearchRequestDto); @@ -188,7 +190,7 @@ describe('SearchService', () => { it('should fetch Non Applications advanced search results by AdvancedSearchRequestDto', async () => { mockHttpClient.post.mockReturnValue(of(mockAdvancedSearchEntityResult)); - const res = await service.advancedSearchNonApplicationsFetch(mockSearchRequestDto); + const res = await service.advancedSearchPlanningReviewsFetch(mockSearchRequestDto); expect(mockHttpClient.post).toHaveBeenCalledTimes(1); expect(res).toBeDefined(); @@ -200,10 +202,10 @@ describe('SearchService', () => { mockHttpClient.post.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); - const res = await service.advancedSearchNonApplicationsFetch(mockSearchRequestDto); + const res = await service.advancedSearchPlanningReviewsFetch(mockSearchRequestDto); expect(mockHttpClient.post).toHaveBeenCalledTimes(1); expect(res).toBeUndefined(); diff --git a/alcs-frontend/src/app/services/search/search.service.ts b/alcs-frontend/src/app/services/search/search.service.ts index 9e269f60cb..2c4e03c40e 100644 --- a/alcs-frontend/src/app/services/search/search.service.ts +++ b/alcs-frontend/src/app/services/search/search.service.ts @@ -7,8 +7,7 @@ import { AdvancedSearchEntityResponseDto, AdvancedSearchResponseDto, ApplicationSearchResultDto, - NonApplicationSearchResultDto, - NonApplicationsSearchRequestDto, + PlanningReviewSearchResultDto, NoticeOfIntentSearchResultDto, NotificationSearchResultDto, SearchRequestDto, @@ -21,7 +20,10 @@ import { export class SearchService { private baseUrl = `${environment.apiUrl}/search`; - constructor(private http: HttpClient, private toastService: ToastService) {} + constructor( + private http: HttpClient, + private toastService: ToastService, + ) {} async advancedSearchFetch(searchDto: SearchRequestDto) { try { @@ -48,8 +50,8 @@ export class SearchService { return await firstValueFrom( this.http.post<AdvancedSearchEntityResponseDto<ApplicationSearchResultDto>>( `${this.baseUrl}/advanced/application`, - searchDto - ) + searchDto, + ), ); } catch (e) { console.error(e); @@ -63,8 +65,8 @@ export class SearchService { return await firstValueFrom( this.http.post<AdvancedSearchEntityResponseDto<NoticeOfIntentSearchResultDto>>( `${this.baseUrl}/advanced/notice-of-intent`, - searchDto - ) + searchDto, + ), ); } catch (e) { console.error(e); @@ -73,13 +75,13 @@ export class SearchService { } } - async advancedSearchNonApplicationsFetch(searchDto: NonApplicationsSearchRequestDto) { + async advancedSearchPlanningReviewsFetch(searchDto: SearchRequestDto) { try { return await firstValueFrom( - this.http.post<AdvancedSearchEntityResponseDto<NonApplicationSearchResultDto>>( - `${this.baseUrl}/advanced/non-applications`, - searchDto - ) + this.http.post<AdvancedSearchEntityResponseDto<PlanningReviewSearchResultDto>>( + `${this.baseUrl}/advanced/planning-reviews`, + searchDto, + ), ); } catch (e) { console.error(e); @@ -88,13 +90,13 @@ export class SearchService { } } - async advancedSearchNotificationsFetch(searchDto: NonApplicationsSearchRequestDto) { + async advancedSearchNotificationsFetch(searchDto: SearchRequestDto) { try { return await firstValueFrom( this.http.post<AdvancedSearchEntityResponseDto<NotificationSearchResultDto>>( `${this.baseUrl}/advanced/notifications`, - searchDto - ) + searchDto, + ), ); } catch (e) { console.error(e); diff --git a/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.constants.ts b/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.constants.ts index f6cff1d23a..ca3ab5298f 100644 --- a/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.constants.ts +++ b/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.constants.ts @@ -26,16 +26,6 @@ export const PLANNING_TYPE_LABEL = { textColor: '#000', }; -export const COVENANT_TYPE_LABEL = { - label: 'Covenant', - code: 'COV', - shortLabel: 'COV', - backgroundColor: '#fff', - borderColor: '#228820', - description: 'Covenant', - textColor: '#000', -}; - export const RETROACTIVE_TYPE_LABEL = { label: 'Retroactive', code: 'RETRO', diff --git a/alcs-frontend/src/app/shared/card/card.component.ts b/alcs-frontend/src/app/shared/card/card.component.ts index 940f4349b2..94dfc96b41 100644 --- a/alcs-frontend/src/app/shared/card/card.component.ts +++ b/alcs-frontend/src/app/shared/card/card.component.ts @@ -38,7 +38,6 @@ export enum CardType { RECON = 'RECON', PLAN = 'PLAN', MODI = 'MODI', - COV = 'COV', NOI = 'NOI', NOI_MODI = 'NOIM', NOTIFICATION = 'NOTI', diff --git a/services/apps/alcs/src/alcs/admin/admin.module.ts b/services/apps/alcs/src/alcs/admin/admin.module.ts index ee8fb0b2d6..9912d54711 100644 --- a/services/apps/alcs/src/alcs/admin/admin.module.ts +++ b/services/apps/alcs/src/alcs/admin/admin.module.ts @@ -6,7 +6,6 @@ import { ApplicationDecisionMakerCode } from '../application-decision/applicatio import { ApplicationModule } from '../application/application.module'; import { BoardModule } from '../board/board.module'; import { CardModule } from '../card/card.module'; -import { CovenantModule } from '../covenant/covenant.module'; import { ApplicationCeoCriterionCode } from '../application-decision/application-ceo-criterion/application-ceo-criterion.entity'; import { ApplicationDecisionModule } from '../application-decision/application-decision.module'; import { NoticeOfIntentDecisionModule } from '../notice-of-intent-decision/notice-of-intent-decision.module'; @@ -48,7 +47,6 @@ import { UnarchiveCardService } from './unarchive-card/unarchive-card.service'; NoticeOfIntentDecisionModule, forwardRef(() => ApplicationDecisionModule), forwardRef(() => PlanningReviewModule), - forwardRef(() => CovenantModule), NoticeOfIntentModule, NoticeOfIntentDecisionModule, CardModule, 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 f376177820..6c0b46682e 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 @@ -5,7 +5,6 @@ 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'; @@ -19,7 +18,6 @@ describe('UnarchiveCardService', () => { let mockReconsiderationService: DeepMocked<ApplicationReconsiderationService>; let mockPlanningReferralService: DeepMocked<PlanningReferralService>; let mockModificationService: DeepMocked<ApplicationModificationService>; - let mockCovenantService: DeepMocked<CovenantService>; let mockNOIService: DeepMocked<NoticeOfIntentService>; let mockNOIModificationService: DeepMocked<NoticeOfIntentModificationService>; let mockNotificationService: DeepMocked<NoticeOfIntentService>; @@ -29,7 +27,6 @@ describe('UnarchiveCardService', () => { mockReconsiderationService = createMock(); mockPlanningReferralService = createMock(); mockModificationService = createMock(); - mockCovenantService = createMock(); mockNOIService = createMock(); mockNOIModificationService = createMock(); mockNotificationService = createMock(); @@ -58,10 +55,6 @@ describe('UnarchiveCardService', () => { provide: ApplicationModificationService, useValue: mockModificationService, }, - { - provide: CovenantService, - useValue: mockCovenantService, - }, { provide: NoticeOfIntentService, useValue: mockNOIService, @@ -89,7 +82,6 @@ describe('UnarchiveCardService', () => { mockReconsiderationService.getDeletedCards.mockResolvedValue([]); mockPlanningReferralService.getDeletedCards.mockResolvedValue([]); mockModificationService.getDeletedCards.mockResolvedValue([]); - mockCovenantService.getDeletedCards.mockResolvedValue([]); mockNOIService.getDeletedCards.mockResolvedValue([]); mockNOIModificationService.getDeletedCards.mockResolvedValue([]); mockNotificationService.getDeletedCards.mockResolvedValue([]); @@ -102,7 +94,6 @@ describe('UnarchiveCardService', () => { 1, ); expect(mockModificationService.getDeletedCards).toHaveBeenCalledTimes(1); - expect(mockCovenantService.getDeletedCards).toHaveBeenCalledTimes(1); expect(mockNOIService.getDeletedCards).toHaveBeenCalledTimes(1); expect(mockNOIModificationService.getDeletedCards).toHaveBeenCalledTimes(1); expect(mockNotificationService.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 84f3a15589..6ddc22a5c3 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 @@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common'; 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'; @@ -14,7 +13,6 @@ export class UnarchiveCardService { private applicationService: ApplicationService, private reconsiderationService: ApplicationReconsiderationService, private modificationService: ApplicationModificationService, - private covenantService: CovenantService, private noticeOfIntentService: NoticeOfIntentService, private noticeOfIntentModificationService: NoticeOfIntentModificationService, private notificationService: NotificationService, @@ -41,33 +39,12 @@ export class UnarchiveCardService { await this.fetchAndMapRecons(fileId, result); await this.fetchAndMapPlanningReferrals(fileId, result); await this.fetchAndMapModifications(fileId, result); - await this.fetchAndMapCovenants(fileId, result); await this.fetchAndMapNOIs(fileId, result); await this.fetchAndMapNotifications(fileId, result); return result; } - private async fetchAndMapCovenants( - fileId: string, - result: { - cardUuid: string; - type: string; - status: string; - createdAt: number; - }[], - ) { - const covenants = await this.covenantService.getDeletedCards(fileId); - for (const covenant of covenants) { - result.push({ - cardUuid: covenant.cardUuid, - createdAt: covenant.auditCreatedAt.getTime(), - type: 'Covenant', - status: covenant.card!.status.label, - }); - } - } - private async fetchAndMapModifications( fileId: string, result: { diff --git a/services/apps/alcs/src/alcs/alcs.module.ts b/services/apps/alcs/src/alcs/alcs.module.ts index 2038251041..dcb906f3e4 100644 --- a/services/apps/alcs/src/alcs/alcs.module.ts +++ b/services/apps/alcs/src/alcs/alcs.module.ts @@ -10,7 +10,6 @@ import { CardModule } from './card/card.module'; import { CodeModule } from './code/code.module'; import { CommentModule } from './comment/comment.module'; import { CommissionerModule } from './commissioner/commissioner.module'; -import { CovenantModule } from './covenant/covenant.module'; import { HomeModule } from './home/home.module'; import { ImportModule } from './import/import.module'; import { LocalGovernmentModule } from './local-government/local-government.module'; @@ -37,7 +36,6 @@ import { StaffJournalModule } from './staff-journal/staff-journal.module'; CodeModule, PlanningReviewModule, PlanningReviewDecisionModule, - CovenantModule, CommissionerModule, ApplicationDecisionModule, AdminModule, @@ -60,7 +58,6 @@ import { StaffJournalModule } from './staff-journal/staff-journal.module'; { path: 'alcs', module: CodeModule }, { path: 'alcs', module: PlanningReviewModule }, { path: 'alcs', module: PlanningReviewDecisionModule }, - { path: 'alcs', module: CovenantModule }, { path: 'alcs', module: CommissionerModule }, { path: 'alcs', module: ApplicationDecisionModule }, { path: 'alcs', module: AdminModule }, 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 c302ca0b67..ebc8fdb7b1 100644 --- a/services/apps/alcs/src/alcs/board/board.controller.spec.ts +++ b/services/apps/alcs/src/alcs/board/board.controller.spec.ts @@ -11,12 +11,10 @@ import { ApplicationService } from '../application/application.service'; 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'; import { Board } from './board.entity'; @@ -31,7 +29,6 @@ describe('BoardController', () => { let modificationService: DeepMocked<ApplicationModificationService>; let cardService: DeepMocked<CardService>; let planningReferralService: DeepMocked<PlanningReferralService>; - let covenantService: DeepMocked<CovenantService>; let noticeOfIntentService: DeepMocked<NoticeOfIntentService>; let noiModificationService: DeepMocked<NoticeOfIntentModificationService>; let notificationService: DeepMocked<NotificationService>; @@ -44,7 +41,6 @@ describe('BoardController', () => { modificationService = createMock(); planningReferralService = createMock(); cardService = createMock(); - covenantService = createMock(); noticeOfIntentService = createMock(); noiModificationService = createMock(); notificationService = createMock(); @@ -65,8 +61,6 @@ describe('BoardController', () => { planningReferralService.mapToDtos.mockResolvedValue([]); modificationService.getByBoard.mockResolvedValue([]); modificationService.mapToDtos.mockResolvedValue([]); - covenantService.getByBoard.mockResolvedValue([]); - covenantService.mapToDtos.mockResolvedValue([]); noticeOfIntentService.getByBoard.mockResolvedValue([]); noticeOfIntentService.mapToDtos.mockResolvedValue([]); noiModificationService.getByBoard.mockResolvedValue([]); @@ -96,7 +90,6 @@ describe('BoardController', () => { provide: PlanningReferralService, useValue: planningReferralService, }, - { provide: CovenantService, useValue: covenantService }, { provide: NoticeOfIntentService, useValue: noticeOfIntentService, diff --git a/services/apps/alcs/src/alcs/board/board.controller.ts b/services/apps/alcs/src/alcs/board/board.controller.ts index fe97ea2d39..b31db99d36 100644 --- a/services/apps/alcs/src/alcs/board/board.controller.ts +++ b/services/apps/alcs/src/alcs/board/board.controller.ts @@ -1,8 +1,8 @@ import { ServiceValidationException } from '@app/common/exceptions/base.exception'; -import { Mapper } from 'automapper-core'; -import { InjectMapper } from 'automapper-nestjs'; import { Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common'; import { ApiOAuth2 } from '@nestjs/swagger'; +import { Mapper } from 'automapper-core'; +import { InjectMapper } from 'automapper-nestjs'; import * as config from 'config'; import { ANY_AUTH_ROLE, @@ -16,12 +16,10 @@ import { ApplicationService } from '../application/application.service'; import { CARD_TYPE } from '../card/card-type/card-type.entity'; import { CardCreateDto } from '../card/card.dto'; 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 { BoardDto, MinimalBoardDto } from './board.dto'; import { Board } from './board.entity'; import { BoardService } from './board.service'; @@ -38,7 +36,6 @@ export class BoardController { private planningReferralService: PlanningReferralService, private appModificationService: ApplicationModificationService, private noiModificationService: NoticeOfIntentModificationService, - private covenantService: CovenantService, private noticeOfIntentService: NoticeOfIntentService, private notificationService: NotificationService, @InjectMapper() private autoMapper: Mapper, @@ -82,10 +79,6 @@ export class BoardController { ? await this.appModificationService.getByBoard(board.uuid) : []; - const covenants = allowedCodes.includes(CARD_TYPE.COV) - ? await this.covenantService.getByBoard(board.uuid) - : []; - const noticeOfIntents = allowedCodes.includes(CARD_TYPE.NOI) ? await this.noticeOfIntentService.getByBoard(board.uuid) : []; @@ -109,7 +102,6 @@ export class BoardController { planningReferrals: await this.planningReferralService.mapToDtos(planningReferrals), modifications: await this.appModificationService.mapToDtos(modifications), - covenants: await this.covenantService.mapToDtos(covenants), noticeOfIntents: await this.noticeOfIntentService.mapToDtos(noticeOfIntents), noiModifications: diff --git a/services/apps/alcs/src/alcs/board/board.module.ts b/services/apps/alcs/src/alcs/board/board.module.ts index 49928311f8..f4567db02d 100644 --- a/services/apps/alcs/src/alcs/board/board.module.ts +++ b/services/apps/alcs/src/alcs/board/board.module.ts @@ -3,7 +3,6 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { BoardAutomapperProfile } from '../../common/automapper/board.automapper.profile'; import { ApplicationModule } from '../application/application.module'; import { CardModule } from '../card/card.module'; -import { CovenantModule } from '../covenant/covenant.module'; import { ApplicationDecisionModule } from '../application-decision/application-decision.module'; import { NoticeOfIntentDecisionModule } from '../notice-of-intent-decision/notice-of-intent-decision.module'; import { NoticeOfIntentModule } from '../notice-of-intent/notice-of-intent.module'; @@ -21,7 +20,6 @@ import { BoardService } from './board.service'; CardModule, forwardRef(() => ApplicationDecisionModule), PlanningReviewModule, - forwardRef(() => CovenantModule), forwardRef(() => NoticeOfIntentModule), NoticeOfIntentDecisionModule, NotificationModule, diff --git a/services/apps/alcs/src/alcs/covenant/covenant.controller.spec.ts b/services/apps/alcs/src/alcs/covenant/covenant.controller.spec.ts deleted file mode 100644 index 122b7b8e03..0000000000 --- a/services/apps/alcs/src/alcs/covenant/covenant.controller.spec.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; -import { Test, TestingModule } from '@nestjs/testing'; -import { ClsService } from 'nestjs-cls'; -import { Board } from '../board/board.entity'; -import { BoardService } from '../board/board.service'; -import { mockKeyCloakProviders } from '../../../test/mocks/mockTypes'; -import { CovenantController } from './covenant.controller'; -import { Covenant } from './covenant.entity'; -import { CovenantService } from './covenant.service'; - -describe('CovenantController', () => { - let controller: CovenantController; - let mockService: DeepMocked<CovenantService>; - let mockBoardService: DeepMocked<BoardService>; - - beforeEach(async () => { - mockService = createMock<CovenantService>(); - mockBoardService = createMock<BoardService>(); - - const module: TestingModule = await Test.createTestingModule({ - controllers: [CovenantController], - providers: [ - { - provide: CovenantService, - useValue: mockService, - }, - { - provide: BoardService, - useValue: mockBoardService, - }, - { - provide: ClsService, - useValue: {}, - }, - ...mockKeyCloakProviders, - ], - }).compile(); - - controller = module.get<CovenantController>(CovenantController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); - - it('should call board service then main service for create', async () => { - mockBoardService.getOneOrFail.mockResolvedValue({} as Board); - mockService.create.mockResolvedValue({} as Covenant); - mockService.mapToDtos.mockResolvedValue([]); - - await controller.create({ - applicant: 'fake-applicant', - localGovernmentUuid: 'local-gov-uuid', - fileNumber: 'file-number', - regionCode: 'region-code', - boardCode: 'fake', - }); - - 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 Covenant); - mockService.mapToDtos.mockResolvedValue([]); - - await controller.getByCard('uuid'); - - expect(mockService.getByCardUuid).toHaveBeenCalledTimes(1); - expect(mockService.mapToDtos).toHaveBeenCalledTimes(1); - }); -}); diff --git a/services/apps/alcs/src/alcs/covenant/covenant.controller.ts b/services/apps/alcs/src/alcs/covenant/covenant.controller.ts deleted file mode 100644 index 2102dbab20..0000000000 --- a/services/apps/alcs/src/alcs/covenant/covenant.controller.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common'; -import { ApiOAuth2 } from '@nestjs/swagger'; -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 { CreateCovenantDto } from './covenant.dto'; -import { CovenantService } from './covenant.service'; - -@ApiOAuth2(config.get<string[]>('KEYCLOAK.SCOPES')) -@UseGuards(RolesGuard) -@Controller('covenant') -export class CovenantController { - constructor( - private covenantService: CovenantService, - private boardService: BoardService, - ) {} - - @Post() - @UserRoles(...ROLES_ALLOWED_BOARDS) - async create(@Body() createCovenantDto: CreateCovenantDto) { - const board = await this.boardService.getOneOrFail({ - code: createCovenantDto.boardCode, - }); - - const createdCovenant = await this.covenantService.create( - createCovenantDto, - board, - ); - - const mapped = this.covenantService.mapToDtos([createdCovenant]); - return mapped[0]; - } - - @Get('/card/:uuid') - @UserRoles(...ROLES_ALLOWED_BOARDS) - async getByCard(@Param('uuid') cardUuid: string) { - const covenant = await this.covenantService.getByCardUuid(cardUuid); - const mapped = await this.covenantService.mapToDtos([covenant]); - return mapped[0]; - } -} diff --git a/services/apps/alcs/src/alcs/covenant/covenant.dto.ts b/services/apps/alcs/src/alcs/covenant/covenant.dto.ts deleted file mode 100644 index a2ff6f7272..0000000000 --- a/services/apps/alcs/src/alcs/covenant/covenant.dto.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { AutoMap } from 'automapper-classes'; -import { IsNotEmpty, IsString } from 'class-validator'; -import { LocalGovernmentDto } from '../local-government/local-government.dto'; -import { CardDto } from '../card/card.dto'; -import { ApplicationRegionDto } from '../code/application-code/application-region/application-region.dto'; - -export class CreateCovenantDto { - @IsString() - @IsNotEmpty() - fileNumber: string; - - @IsString() - @IsNotEmpty() - applicant: string; - - @IsString() - @IsNotEmpty() - localGovernmentUuid: string; - - @IsString() - @IsNotEmpty() - regionCode: string; - - @IsString() - @IsNotEmpty() - boardCode: string; -} - -export class CovenantDto { - @AutoMap() - fileNumber: string; - - @AutoMap() - applicant: string; - - @AutoMap() - card: CardDto; - - @AutoMap() - localGovernment: LocalGovernmentDto; - - @AutoMap() - region: ApplicationRegionDto; -} diff --git a/services/apps/alcs/src/alcs/covenant/covenant.entity.ts b/services/apps/alcs/src/alcs/covenant/covenant.entity.ts deleted file mode 100644 index 4ac7f0e1c6..0000000000 --- a/services/apps/alcs/src/alcs/covenant/covenant.entity.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Type } from 'class-transformer'; -import { - Column, - Entity, - Index, - JoinColumn, - ManyToOne, - OneToOne, -} from 'typeorm'; -import { Base } from '../../common/entities/base.entity'; -import { Card } from '../card/card.entity'; -import { ApplicationRegion } from '../code/application-code/application-region/application-region.entity'; -import { LocalGovernment } from '../local-government/local-government.entity'; - -@Entity() -export class Covenant extends Base { - constructor(data?: Partial<Covenant>) { - super(); - if (data) { - Object.assign(this, data); - } - } - - @Index() - @Column({ unique: true }) - fileNumber: string; - - @Column() - applicant: string; - - @Column({ type: 'uuid' }) - cardUuid: string; - - @OneToOne(() => Card, { cascade: true }) - @JoinColumn() - @Type(() => Card) - card: Card; - - @ManyToOne(() => LocalGovernment) - localGovernment: LocalGovernment; - - @Index() - @Column({ - type: 'uuid', - }) - localGovernmentUuid: string; - - @ManyToOne(() => ApplicationRegion) - region: ApplicationRegion; - - @Column() - regionCode: string; -} diff --git a/services/apps/alcs/src/alcs/covenant/covenant.module.ts b/services/apps/alcs/src/alcs/covenant/covenant.module.ts deleted file mode 100644 index 89f6722d79..0000000000 --- a/services/apps/alcs/src/alcs/covenant/covenant.module.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { forwardRef, Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { CovenantProfile } from '../../common/automapper/covenant.automapper.profile'; -import { FileNumberModule } from '../../file-number/file-number.module'; -import { BoardModule } from '../board/board.module'; -import { CardModule } from '../card/card.module'; -import { CovenantController } from './covenant.controller'; -import { Covenant } from './covenant.entity'; -import { CovenantService } from './covenant.service'; - -@Module({ - imports: [ - TypeOrmModule.forFeature([Covenant]), - forwardRef(() => BoardModule), - CardModule, - FileNumberModule, - ], - providers: [CovenantService, CovenantProfile], - controllers: [CovenantController], - exports: [CovenantService], -}) -export class CovenantModule {} diff --git a/services/apps/alcs/src/alcs/covenant/covenant.service.spec.ts b/services/apps/alcs/src/alcs/covenant/covenant.service.spec.ts deleted file mode 100644 index a35f61d3d6..0000000000 --- a/services/apps/alcs/src/alcs/covenant/covenant.service.spec.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { classes } from 'automapper-classes'; -import { AutomapperModule } from 'automapper-nestjs'; -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 { ApplicationService } from '../application/application.service'; -import { Board } from '../board/board.entity'; -import { Card } from '../card/card.entity'; -import { CardService } from '../card/card.service'; -import { Covenant } from './covenant.entity'; -import { CovenantService } from './covenant.service'; - -describe('CovenantService', () => { - let service: CovenantService; - let mockRepository: DeepMocked<Repository<Covenant>>; - let mockCardService: DeepMocked<CardService>; - let mockApplicationService: DeepMocked<ApplicationService>; - let mockFileNumberService: DeepMocked<FileNumberService>; - - beforeEach(async () => { - mockCardService = createMock(); - mockApplicationService = createMock(); - mockRepository = createMock(); - mockFileNumberService = createMock(); - - const module: TestingModule = await Test.createTestingModule({ - imports: [ - AutomapperModule.forRoot({ - strategyInitializer: classes(), - }), - ], - providers: [ - { - provide: getRepositoryToken(Covenant), - useValue: mockRepository, - }, - { - provide: CardService, - useValue: mockCardService, - }, - { - provide: FileNumberService, - useValue: mockFileNumberService, - }, - CovenantService, - ], - }).compile(); - - mockApplicationService.get.mockResolvedValue(null); - - service = module.get<CovenantService>(CovenantService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); - - it('should load the type code and call the repo to save when creating', async () => { - const mockCard = {} as Card; - const fakeBoard = {} as Board; - - mockFileNumberService.checkValidFileNumber.mockResolvedValue(true); - mockRepository.findOne.mockResolvedValueOnce({} as Covenant); - mockRepository.save.mockResolvedValue({} as Covenant); - mockCardService.create.mockResolvedValue(mockCard); - - const res = await service.create( - { - applicant: 'fake-applicant', - fileNumber: '1512311', - localGovernmentUuid: 'fake-uuid', - regionCode: 'region-code', - boardCode: 'fake', - }, - fakeBoard, - ); - - expect(mockFileNumberService.checkValidFileNumber).toHaveBeenCalledTimes(1); - expect(mockRepository.findOne).toHaveBeenCalledTimes(1); - expect(mockCardService.create).toHaveBeenCalledTimes(1); - expect(mockRepository.save).toHaveBeenCalledTimes(1); - expect(mockRepository.save.mock.calls[0][0].card).toBe(mockCard); - }); - - it('should call through to the repo for get by card', async () => { - mockRepository.findOne.mockResolvedValue({} as Covenant); - 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 covenant 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('fake'); - - expect(mockRepository.find).toHaveBeenCalledTimes(1); - }); - - it('should call through to the repo for getby', async () => { - const mockFilter = { - uuid: '5', - }; - mockRepository.find.mockResolvedValue([]); - await service.getBy(mockFilter); - - expect(mockRepository.find).toHaveBeenCalledTimes(1); - expect(mockRepository.find.mock.calls[0][0]!.where).toEqual(mockFilter); - }); - - it('should 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/covenant/covenant.service.ts b/services/apps/alcs/src/alcs/covenant/covenant.service.ts deleted file mode 100644 index 6c19acf1e0..0000000000 --- a/services/apps/alcs/src/alcs/covenant/covenant.service.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { ServiceNotFoundException } from '@app/common/exceptions/base.exception'; -import { Mapper } from 'automapper-core'; -import { InjectMapper } from 'automapper-nestjs'; -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { - FindOptionsRelations, - FindOptionsWhere, - IsNull, - Not, - Repository, -} from 'typeorm'; -import { FileNumberService } from '../../file-number/file-number.service'; -import { Board } from '../board/board.entity'; -import { CARD_TYPE } from '../card/card-type/card-type.entity'; -import { CardService } from '../card/card.service'; -import { CovenantDto, CreateCovenantDto } from './covenant.dto'; -import { Covenant } from './covenant.entity'; - -@Injectable() -export class CovenantService { - private CARD_RELATIONS = { - board: true, - type: true, - status: true, - assignee: true, - }; - private DEFAULT_RELATIONS: FindOptionsRelations<Covenant> = { - card: this.CARD_RELATIONS, - localGovernment: true, - region: true, - }; - - constructor( - private cardService: CardService, - @InjectRepository(Covenant) - private repository: Repository<Covenant>, - @InjectMapper() private mapper: Mapper, - private fileNumberService: FileNumberService, - ) {} - - async create(data: CreateCovenantDto, board: Board) { - await this.fileNumberService.checkValidFileNumber(data.fileNumber); - - const covenant = new Covenant({ - localGovernmentUuid: data.localGovernmentUuid, - fileNumber: data.fileNumber, - regionCode: data.regionCode, - applicant: data.applicant, - }); - - covenant.card = await this.cardService.create(CARD_TYPE.COV, board, false); - const savedCovenant = await this.repository.save(covenant); - - return this.getOrFail(savedCovenant.uuid); - } - - async getOrFail(uuid: string) { - const covenant = await this.get(uuid); - if (!covenant) { - throw new ServiceNotFoundException( - `Failed to find covenant with uuid ${uuid}`, - ); - } - - return covenant; - } - - mapToDtos(covenants: Covenant[]) { - return this.mapper.mapArrayAsync(covenants, Covenant, CovenantDto); - } - - async getByCardUuid(cardUuid: string) { - const covenant = await this.repository.findOne({ - where: { cardUuid }, - relations: this.DEFAULT_RELATIONS, - }); - - if (!covenant) { - throw new ServiceNotFoundException( - `Failed to find covenant with card uuid ${cardUuid}`, - ); - } - - return covenant; - } - - getBy(findOptions: FindOptionsWhere<Covenant>) { - return this.repository.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({ - where: { - uuid, - }, - relations: this.DEFAULT_RELATIONS, - }); - } - - async getByBoard(boardUuid: string) { - return this.repository.find({ - where: { card: { boardUuid } }, - relations: { - ...this.DEFAULT_RELATIONS, - card: { - ...this.CARD_RELATIONS, - board: false, - }, - }, - }); - } - - 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 }, - }, - }, - }); - } -} 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 478b071bbf..b4ad6e82a3 100644 --- a/services/apps/alcs/src/alcs/home/home.controller.spec.ts +++ b/services/apps/alcs/src/alcs/home/home.controller.spec.ts @@ -14,7 +14,6 @@ import { mockKeyCloakProviders } from '../../../test/mocks/mockTypes'; import { ApplicationSubtaskProfile } from '../../common/automapper/application-subtask.automapper.profile'; import { ApplicationProfile } from '../../common/automapper/application.automapper.profile'; import { CardProfile } from '../../common/automapper/card.automapper.profile'; -import { CovenantProfile } from '../../common/automapper/covenant.automapper.profile'; import { NotificationProfile } from '../../common/automapper/notification.automapper.profile'; import { UserProfile } from '../../common/automapper/user.automapper.profile'; import { ApplicationModificationService } from '../application-decision/application-modification/application-modification.service'; @@ -25,8 +24,6 @@ import { CARD_STATUS } from '../card/card-status/card-status.entity'; import { CARD_SUBTASK_TYPE } from '../card/card-subtask/card-subtask.dto'; import { CardSubtaskService } from '../card/card-subtask/card-subtask.service'; import { CodeService } from '../code/code.service'; -import { Covenant } from '../covenant/covenant.entity'; -import { CovenantService } from '../covenant/covenant.service'; import { NoticeOfIntentModification } from '../notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.entity'; import { NoticeOfIntentModificationService } from '../notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.service'; import { NoticeOfIntent } from '../notice-of-intent/notice-of-intent.entity'; @@ -41,7 +38,6 @@ describe('HomeController', () => { let mockApplicationSubtaskService: DeepMocked<CardSubtaskService>; let mockApplicationReconsiderationService: DeepMocked<ApplicationReconsiderationService>; let mockApplicationModificationService: DeepMocked<ApplicationModificationService>; - let mockCovenantService: DeepMocked<CovenantService>; let mockApplicationTimeTrackingService: DeepMocked<ApplicationTimeTrackingService>; let mockNoticeOfIntentService: DeepMocked<NoticeOfIntentService>; let mockNoticeOfIntentModificationService: DeepMocked<NoticeOfIntentModificationService>; @@ -53,7 +49,6 @@ describe('HomeController', () => { mockApplicationReconsiderationService = createMock(); mockApplicationTimeTrackingService = createMock(); mockApplicationModificationService = createMock(); - mockCovenantService = createMock(); mockNoticeOfIntentService = createMock(); mockNoticeOfIntentModificationService = createMock(); mockNotificationService = createMock(); @@ -94,10 +89,6 @@ describe('HomeController', () => { provide: ApplicationTimeTrackingService, useValue: mockApplicationTimeTrackingService, }, - { - provide: CovenantService, - useValue: mockCovenantService, - }, { provide: NoticeOfIntentService, useValue: mockNoticeOfIntentService, @@ -112,7 +103,6 @@ describe('HomeController', () => { }, ApplicationProfile, ApplicationSubtaskProfile, - CovenantProfile, UserProfile, CardProfile, NotificationProfile, @@ -128,8 +118,6 @@ describe('HomeController', () => { mockApplicationReconsiderationService.mapToDtos.mockResolvedValue([]); mockApplicationModificationService.getBy.mockResolvedValue([]); mockApplicationModificationService.mapToDtos.mockResolvedValue([]); - mockCovenantService.getBy.mockResolvedValue([]); - mockCovenantService.mapToDtos.mockResolvedValue([]); mockNoticeOfIntentService.getBy.mockResolvedValue([]); mockNoticeOfIntentService.mapToDtos.mockResolvedValue([]); mockNoticeOfIntentModificationService.getBy.mockResolvedValue([]); @@ -152,7 +140,6 @@ describe('HomeController', () => { [], ); mockApplicationService.getWithIncompleteSubtaskByType.mockResolvedValue([]); - mockCovenantService.getWithIncompleteSubtaskByType.mockResolvedValue([]); mockNoticeOfIntentService.getWithIncompleteSubtaskByType.mockResolvedValue( [], ); @@ -324,29 +311,6 @@ describe('HomeController', () => { expect(res[0].paused).toBeFalsy(); }); - it('should call Covenant Service and map it', async () => { - const mockCovenant = { - applicant: 'fake-applicant', - fileNumber: 'fileNumber', - card: initCardMockEntity('222'), - } as Covenant; - mockCovenantService.getWithIncompleteSubtaskByType.mockResolvedValue([ - mockCovenant, - ]); - - const res = await controller.getIncompleteSubtasksByType( - CARD_SUBTASK_TYPE.GIS, - ); - - expect(res.length).toEqual(1); - expect( - mockCovenantService.getWithIncompleteSubtaskByType, - ).toHaveBeenCalledTimes(1); - - expect(res[0].title).toContain(mockCovenant.fileNumber); - expect(res[0].title).toContain(mockCovenant.applicant); - }); - it('should call NOI Service and map it', async () => { const activeDays = 5; const mockNoi = new NoticeOfIntent({ diff --git a/services/apps/alcs/src/alcs/home/home.controller.ts b/services/apps/alcs/src/alcs/home/home.controller.ts index 1de8cee4de..1ceafe44c5 100644 --- a/services/apps/alcs/src/alcs/home/home.controller.ts +++ b/services/apps/alcs/src/alcs/home/home.controller.ts @@ -27,9 +27,6 @@ import { } from '../card/card-subtask/card-subtask.dto'; import { CardDto } from '../card/card.dto'; import { Card } from '../card/card.entity'; -import { CovenantDto } from '../covenant/covenant.dto'; -import { Covenant } from '../covenant/covenant.entity'; -import { CovenantService } from '../covenant/covenant.service'; import { NoticeOfIntentModificationDto } from '../notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.dto'; import { NoticeOfIntentModification } from '../notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.entity'; import { NoticeOfIntentModificationService } from '../notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.service'; @@ -57,7 +54,6 @@ export class HomeController { private timeService: ApplicationTimeTrackingService, private reconsiderationService: ApplicationReconsiderationService, private modificationService: ApplicationModificationService, - private covenantService: CovenantService, private noticeOfIntentService: NoticeOfIntentService, private noticeOfIntentModificationService: NoticeOfIntentModificationService, private notificationService: NotificationService, @@ -72,7 +68,6 @@ export class HomeController { reconsiderations: ApplicationReconsiderationDto[]; planningReferrals: PlanningReviewDto[]; modifications: ApplicationModificationDto[]; - covenants: CovenantDto[]; notifications: NotificationDto[]; }> { const userId = req.user.entity.uuid; @@ -97,8 +92,6 @@ export class HomeController { const modifications = await this.modificationService.getBy(assignedFindOptions); - const covenants = await this.covenantService.getBy(assignedFindOptions); - const noticeOfIntents = await this.noticeOfIntentService.getBy(assignedFindOptions); @@ -120,7 +113,6 @@ export class HomeController { await this.reconsiderationService.mapToDtos(reconsiderations), planningReferrals: [], modifications: await this.modificationService.mapToDtos(modifications), - covenants: await this.covenantService.mapToDtos(covenants), notifications: await this.notificationService.mapToDtos(notifications), }; } else { @@ -131,7 +123,6 @@ export class HomeController { reconsiderations: [], planningReferrals: [], modifications: [], - covenants: [], notifications: [], }; } @@ -170,10 +161,6 @@ export class HomeController { modificationsWithSubtasks, ); - const covenantWithSubtasks = - await this.covenantService.getWithIncompleteSubtaskByType(subtaskType); - const covenantReviewSubtasks = this.mapCovenantToDtos(covenantWithSubtasks); - const noiSubtasks = await this.noticeOfIntentService.getWithIncompleteSubtaskByType( subtaskType, @@ -203,7 +190,6 @@ export class HomeController { ...applicationSubtasks, ...reconSubtasks, ...modificationSubtasks, - ...covenantReviewSubtasks, ...noiModificationsSubtasks, ...notificationSubtasks, ]; @@ -286,26 +272,6 @@ export class HomeController { return result; } - private mapCovenantToDtos(covenants: Covenant[]) { - const result: HomepageSubtaskDTO[] = []; - for (const covenant of covenants) { - for (const subtask of covenant.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(covenant.card, Card, CardDto), - completedAt: subtask.completedAt?.getTime(), - paused: false, - title: `${covenant.fileNumber} (${covenant.applicant})`, - parentType: PARENT_TYPE.COVENANT, - }); - } - } - return result; - } - private async mapNoticeOfIntentToDtos(noticeOfIntents: NoticeOfIntent[]) { const uuids = noticeOfIntents.map((noi) => noi.uuid); const timeMap = await this.noticeOfIntentService.getTimes(uuids); diff --git a/services/apps/alcs/src/alcs/home/home.module.ts b/services/apps/alcs/src/alcs/home/home.module.ts index fcdb0fe77a..01ac60fd28 100644 --- a/services/apps/alcs/src/alcs/home/home.module.ts +++ b/services/apps/alcs/src/alcs/home/home.module.ts @@ -3,7 +3,6 @@ import { ApplicationSubtaskProfile } from '../../common/automapper/application-s import { UserModule } from '../../user/user.module'; import { ApplicationDecisionModule } from '../application-decision/application-decision.module'; import { ApplicationModule } from '../application/application.module'; -import { CovenantModule } from '../covenant/covenant.module'; import { NoticeOfIntentDecisionModule } from '../notice-of-intent-decision/notice-of-intent-decision.module'; import { NoticeOfIntentModule } from '../notice-of-intent/notice-of-intent.module'; import { NotificationModule } from '../notification/notification.module'; @@ -15,7 +14,6 @@ import { HomeController } from './home.controller'; ApplicationModule, UserModule, PlanningReviewModule, - CovenantModule, ApplicationDecisionModule, NoticeOfIntentModule, NoticeOfIntentDecisionModule, diff --git a/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.spec.ts b/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.spec.ts new file mode 100644 index 0000000000..b1dbf5dfef --- /dev/null +++ b/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.spec.ts @@ -0,0 +1,106 @@ +import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; +import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { LocalGovernment } from '../../local-government/local-government.entity'; +import { SearchRequestDto } from '../search.dto'; +import { PlanningReviewAdvancedSearchService } from './planning-review-advanced-search.service'; +import { PlanningReviewSearchView } from './planning-review-search-view.entity'; + +describe('PlanningReviewAdvancedSearchService', () => { + let service: PlanningReviewAdvancedSearchService; + let mockPRSearchView: DeepMocked<Repository<PlanningReviewSearchView>>; + let mockLocalGovernmentRepository: DeepMocked<Repository<LocalGovernment>>; + + const mockSearchDto: SearchRequestDto = { + fileNumber: '123', + portalStatusCode: 'A', + governmentName: 'B', + regionCode: 'C', + name: 'D', + pid: 'E', + civicAddress: 'F', + dateSubmittedFrom: new Date('2020-10-10').getTime(), + dateSubmittedTo: new Date('2021-10-10').getTime(), + dateDecidedFrom: new Date('2020-11-10').getTime(), + dateDecidedTo: new Date('2021-11-10').getTime(), + resolutionNumber: 123, + resolutionYear: 2021, + fileTypes: ['type1', 'type2'], + page: 1, + pageSize: 10, + sortField: 'ownerName', + sortDirection: 'ASC', + }; + + let mockQuery: any = {}; + + beforeEach(async () => { + mockPRSearchView = createMock(); + mockLocalGovernmentRepository = createMock(); + + mockQuery = { + getManyAndCount: jest.fn().mockResolvedValue([[], 0]), + orderBy: jest.fn().mockReturnThis(), + offset: jest.fn().mockReturnThis(), + limit: jest.fn().mockReturnThis(), + innerJoinAndMapOne: jest.fn().mockReturnThis(), + groupBy: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + andWhere: jest.fn().mockReturnThis(), + setParameters: jest.fn().mockReturnThis(), + leftJoin: jest.fn().mockReturnThis(), + withDeleted: jest.fn().mockReturnThis(), + }; + + const module: TestingModule = await Test.createTestingModule({ + providers: [ + PlanningReviewAdvancedSearchService, + { + provide: getRepositoryToken(PlanningReviewSearchView), + useValue: mockPRSearchView, + }, + { + provide: getRepositoryToken(LocalGovernment), + useValue: mockLocalGovernmentRepository, + }, + ], + }).compile(); + + service = module.get<PlanningReviewAdvancedSearchService>( + PlanningReviewAdvancedSearchService, + ); + + mockLocalGovernmentRepository.findOneByOrFail.mockResolvedValue( + new LocalGovernment(), + ); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + it('should successfully build a query using all search parameters defined', async () => { + mockPRSearchView.createQueryBuilder.mockReturnValue(mockQuery as any); + + const result = await service.search(mockSearchDto); + + expect(result).toEqual({ data: [], total: 0 }); + expect(mockPRSearchView.createQueryBuilder).toBeCalledTimes(1); + expect(mockQuery.andWhere).toBeCalledTimes(8); + }); + + it('should call compileSearchQuery method correctly', async () => { + const compileSearchQuerySpy = jest + .spyOn(service as any, 'compileSearchQuery') + .mockResolvedValue(mockQuery); + + const result = await service.search(mockSearchDto); + + expect(result).toEqual({ data: [], total: 0 }); + expect(compileSearchQuerySpy).toBeCalledWith(mockSearchDto); + expect(mockQuery.orderBy).toHaveBeenCalledTimes(1); + expect(mockQuery.offset).toHaveBeenCalledTimes(1); + expect(mockQuery.limit).toHaveBeenCalledTimes(1); + }); +}); diff --git a/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.ts b/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.ts new file mode 100644 index 0000000000..9ff0975e8d --- /dev/null +++ b/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.ts @@ -0,0 +1,187 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository, SelectQueryBuilder } from 'typeorm'; +import { + getNextDayToPacific, + getStartOfDayToPacific, +} from '../../../utils/pacific-date-time-helper'; +import { formatStringToPostgresSearchStringArrayWithWildCard } from '../../../utils/search-helper'; +import { LocalGovernment } from '../../local-government/local-government.entity'; +import { PlanningReviewDecision } from '../../planning-review/planning-review-decision/planning-review-decision.entity'; +import { AdvancedSearchResultDto, SearchRequestDto } from '../search.dto'; +import { PlanningReviewSearchView } from './planning-review-search-view.entity'; + +@Injectable() +export class PlanningReviewAdvancedSearchService { + constructor( + @InjectRepository(PlanningReviewSearchView) + private planningReviewSearchRepo: Repository<PlanningReviewSearchView>, + @InjectRepository(LocalGovernment) + private governmentRepository: Repository<LocalGovernment>, + ) {} + + async search( + searchDto: SearchRequestDto, + ): Promise<AdvancedSearchResultDto<PlanningReviewSearchView[]>> { + 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 'fileId': + return '"planningReviewSearch"."file_number"'; + + case 'ownerName': + return '"planningReviewSearch"."document_name"'; + + case 'type': + return '"planningReviewSearch"."planning_review_type_code"'; + + case 'government': + return '"planningReviewSearch"."local_government_name"'; + + default: + case 'dateSubmitted': + return '"planningReviewSearch"."date_submitted_to_alc"'; + } + } + + private async compileSearchQuery(searchDto: SearchRequestDto) { + let query = this.planningReviewSearchRepo.createQueryBuilder( + 'planningReviewSearch', + ); + + if (searchDto.fileNumber) { + query = query + .andWhere('planningReviewSearch.file_number = :fileNumber') + .setParameters({ fileNumber: searchDto.fileNumber ?? null }); + } + + if (searchDto.governmentName) { + const government = await this.governmentRepository.findOneByOrFail({ + name: searchDto.governmentName, + }); + + query = query.andWhere( + 'planningReviewSearch.local_government_uuid = :local_government_uuid', + { + local_government_uuid: government.uuid, + }, + ); + } + + if (searchDto.regionCode) { + query = query.andWhere( + 'planningReviewSearch.region_code = :region_code', + { + region_code: searchDto.regionCode, + }, + ); + } + + query = this.compileSearchByNameQuery(searchDto, query); + query = this.compileDecisionSearchQuery(searchDto, query); + query = this.compileDateRangeSearchQuery(searchDto, query); + + return query; + } + + private compileDateRangeSearchQuery( + searchDto: SearchRequestDto, + query: SelectQueryBuilder<PlanningReviewSearchView>, + ) { + if (searchDto.dateSubmittedFrom) { + query = query.andWhere( + 'planningReviewSearch.date_submitted_to_alc >= :date_submitted_from_alc', + { + date_submitted_from_alc: getStartOfDayToPacific( + searchDto.dateSubmittedFrom, + ).toISOString(), + }, + ); + } + + if (searchDto.dateSubmittedTo) { + query = query.andWhere( + 'planningReviewSearch.date_submitted_to_alc < :date_submitted_to_alc', + { + date_submitted_to_alc: getNextDayToPacific( + searchDto.dateSubmittedTo, + ).toISOString(), + }, + ); + } + + return query; + } + + private compileDecisionSearchQuery( + searchDto: SearchRequestDto, + query: SelectQueryBuilder<PlanningReviewSearchView>, + ) { + if ( + searchDto.resolutionNumber !== undefined || + searchDto.resolutionYear !== undefined + ) { + query = this.joinDecision(query); + + if (searchDto.resolutionNumber !== undefined) { + query = query.andWhere( + 'decision.resolution_number = :resolution_number AND decision.is_draft = false', + { + resolution_number: searchDto.resolutionNumber, + }, + ); + } + + if (searchDto.resolutionYear !== undefined) { + query = query.andWhere('decision.resolution_year = :resolution_year', { + resolution_year: searchDto.resolutionYear, + }); + } + } + return query; + } + + private joinDecision(query: any) { + query = query.leftJoin( + PlanningReviewDecision, + 'decision', + 'decision.planning_review_uuid = "planningReviewSearch"."uuid" AND decision.is_draft = false', + ); + return query; + } + + private compileSearchByNameQuery( + searchDto: SearchRequestDto, + query: SelectQueryBuilder<PlanningReviewSearchView>, + ) { + if (searchDto.name) { + const formattedSearchString = + formatStringToPostgresSearchStringArrayWithWildCard(searchDto.name!); + + query = query.andWhere('planningReviewSearch.document_name LIKE :name', { + name: formattedSearchString, + }); + } + return query; + } +} diff --git a/services/apps/alcs/src/alcs/search/planning-review/planning-review-search-view.entity.ts b/services/apps/alcs/src/alcs/search/planning-review/planning-review-search-view.entity.ts new file mode 100644 index 0000000000..680e94f335 --- /dev/null +++ b/services/apps/alcs/src/alcs/search/planning-review/planning-review-search-view.entity.ts @@ -0,0 +1,103 @@ +import { DataSource, PrimaryColumn, ViewColumn, ViewEntity } from 'typeorm'; +import { LocalGovernment } from '../../local-government/local-government.entity'; +import { PlanningReferral } from '../../planning-review/planning-referral/planning-referral.entity'; +import { PlanningReviewType } from '../../planning-review/planning-review-type.entity'; +import { PlanningReview } from '../../planning-review/planning-review.entity'; + +@ViewEntity({ + expression: (datasource: DataSource) => + datasource + .createQueryBuilder() + .select('planning_review.uuid', 'uuid') + .addSelect('planning_review.file_number', 'file_number') + .addSelect('planning_review.open', 'open') + .addSelect('planning_review.document_name', 'document_name') + .addSelect('planning_referral.submission_date', 'date_submitted_to_alc') + .addSelect( + 'planning_review.local_government_uuid', + 'local_government_uuid', + ) + .addSelect('localGovernment.name', 'local_government_name') + .addSelect('planning_review.type_code', 'planning_review_type_code') + .addSelect('planning_review.region_code', 'region_code') + .from(PlanningReview, 'planning_review') + .innerJoinAndSelect( + PlanningReviewType, + 'planningReviewType', + 'planning_review.type_code = planningReviewType.code', + ) + .leftJoin( + LocalGovernment, + 'localGovernment', + 'planning_review.local_government_uuid = localGovernment.uuid', + ) + .leftJoinAndMapOne( + 'planning_review.planning_referral', + (qb) => { + return qb + .subQuery() + .select('planning_referral.uuid', 'planning_referral_uuid') + .addSelect('planning_referral.submission_date', 'submission_date') + .addSelect( + 'planning_referral.planning_review_uuid', + 'planning_review_uuid', + ) + .from(PlanningReferral, 'planning_referral') + .innerJoin( + (qb) => { + return qb + .subQuery() + .select('child.planning_review_uuid', 'planning_review_uuid') + .addSelect('MIN(child.audit_created_at)', 'audit_created_at') + .from(PlanningReferral, 'child') + .groupBy('child.planning_review_uuid'); + }, + 'child', + 'planning_referral.audit_created_at = child.audit_created_at AND planning_referral.planning_review_uuid = child.planning_review_uuid', + ); + }, + 'planning_referral', + 'planning_review.uuid = planning_referral.planning_review_uuid', + ), +}) +export class PlanningReviewSearchView { + @ViewColumn() + @PrimaryColumn() + uuid: string; + + @ViewColumn() + regionCode?: string; + + @ViewColumn() + open: boolean; + + @ViewColumn() + fileNumber: string; + + @ViewColumn() + documentName: string; + + @ViewColumn() + localGovernmentUuid?: string; + + @ViewColumn() + localGovernmentName?: string; + + @ViewColumn() + dateSubmittedToAlc: number; + + @ViewColumn() + planningReviewType_code: string; + + @ViewColumn({ name: 'planningReviewType_short_label' }) + planningReviewType_short_label: string; + + @ViewColumn({ name: 'planningReviewType_background_color' }) + planningReviewType_background_color: string; + + @ViewColumn({ name: 'planningReviewType_text_color' }) + planningReviewType_text_color: string; + + @ViewColumn({ name: 'planningReviewType_label' }) + planningReviewType_label: string; +} 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 41f513ec68..1a12d72b65 100644 --- a/services/apps/alcs/src/alcs/search/search.controller.spec.ts +++ b/services/apps/alcs/src/alcs/search/search.controller.spec.ts @@ -7,15 +7,14 @@ import { ClsService } from 'nestjs-cls'; import { DataSource, QueryRunner, Repository } from 'typeorm'; import { mockKeyCloakProviders } from '../../../test/mocks/mockTypes'; import { Application } from '../application/application.entity'; -import { Board } from '../board/board.entity'; -import { Card } from '../card/card.entity'; import { ApplicationType } from '../code/application-code/application-type/application-type.entity'; -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 { NoticeOfIntentAdvancedSearchService } from './notice-of-intent/notice-of-intent-advanced-search.service'; import { NotificationAdvancedSearchService } from './notification/notification-advanced-search.service'; +import { PlanningReviewAdvancedSearchService } from './planning-review/planning-review-advanced-search.service'; import { SearchController } from './search.controller'; import { SearchRequestDto } from './search.dto'; import { SearchService } from './search.service'; @@ -26,6 +25,7 @@ describe('SearchController', () => { let mockNoticeOfIntentAdvancedSearchService: DeepMocked<NoticeOfIntentAdvancedSearchService>; let mockApplicationAdvancedSearchService: DeepMocked<ApplicationAdvancedSearchService>; let mockNotificationAdvancedSearchService: DeepMocked<NotificationAdvancedSearchService>; + let mockPlanningReviewAdvancedSearchService: DeepMocked<PlanningReviewAdvancedSearchService>; let mockDataSource: DeepMocked<DataSource>; let mockQueryRunner: DeepMocked<QueryRunner>; let mockAppTypeRepo: DeepMocked<Repository<ApplicationType>>; @@ -35,6 +35,7 @@ describe('SearchController', () => { mockNoticeOfIntentAdvancedSearchService = createMock(); mockApplicationAdvancedSearchService = createMock(); mockNotificationAdvancedSearchService = createMock(); + mockPlanningReviewAdvancedSearchService = createMock(); mockDataSource = createMock(); mockAppTypeRepo = createMock(); @@ -61,6 +62,10 @@ describe('SearchController', () => { provide: NotificationAdvancedSearchService, useValue: mockNotificationAdvancedSearchService, }, + { + provide: PlanningReviewAdvancedSearchService, + useValue: mockPlanningReviewAdvancedSearchService, + }, { provide: DataSource, useValue: mockDataSource, @@ -87,15 +92,7 @@ describe('SearchController', () => { mockSearchService.getApplication.mockResolvedValue(new Application()); mockSearchService.getNoi.mockResolvedValue(new NoticeOfIntent()); mockSearchService.getNotification.mockResolvedValue(new Notification()); - mockSearchService.getCovenant.mockResolvedValue( - new Covenant({ - card: { - board: { - code: 'fake_board', - } as Board, - } as Card, - }), - ); + mockSearchService.getPlanningReview.mockResolvedValue(new PlanningReview()); mockNoticeOfIntentAdvancedSearchService.searchNoticeOfIntents.mockResolvedValue( { @@ -113,6 +110,11 @@ describe('SearchController', () => { data: [], total: 0, }); + + mockPlanningReviewAdvancedSearchService.search.mockResolvedValue({ + data: [], + total: 0, + }); }); it('should be defined', () => { @@ -124,13 +126,17 @@ describe('SearchController', () => { const result = await controller.search(searchString); expect(mockSearchService.getApplication).toBeCalledTimes(1); - expect(mockSearchService.getApplication).toBeCalledWith(searchString); + expect(mockSearchService.getApplication).toHaveBeenCalledWith(searchString); expect(mockSearchService.getNoi).toBeCalledTimes(1); - expect(mockSearchService.getNoi).toBeCalledWith(searchString); - expect(mockSearchService.getCovenant).toBeCalledTimes(1); - expect(mockSearchService.getCovenant).toBeCalledWith(searchString); + expect(mockSearchService.getNoi).toHaveBeenCalledWith(searchString); + expect(mockSearchService.getPlanningReview).toHaveBeenCalledTimes(1); + expect(mockSearchService.getPlanningReview).toHaveBeenCalledWith( + searchString, + ); expect(mockSearchService.getNotification).toHaveBeenCalledTimes(1); - expect(mockSearchService.getNotification).toBeCalledWith(searchString); + expect(mockSearchService.getNotification).toHaveBeenCalledWith( + searchString, + ); expect(result).toBeDefined(); expect(result.length).toBe(4); }); @@ -151,19 +157,19 @@ describe('SearchController', () => { expect( mockApplicationAdvancedSearchService.searchApplications, - ).toBeCalledTimes(1); + ).toHaveBeenCalledTimes(1); expect( mockApplicationAdvancedSearchService.searchApplications, - ).toBeCalledWith(mockSearchRequestDto, {}); + ).toHaveBeenCalledWith(mockSearchRequestDto, {}); expect(result.applications).toBeDefined(); expect(result.totalApplications).toBe(0); expect( mockNoticeOfIntentAdvancedSearchService.searchNoticeOfIntents, - ).toBeCalledTimes(1); + ).toHaveBeenCalledTimes(1); expect( mockNoticeOfIntentAdvancedSearchService.searchNoticeOfIntents, - ).toBeCalledWith(mockSearchRequestDto); + ).toHaveBeenCalledWith(mockSearchRequestDto); expect(result.noticeOfIntents).toBeDefined(); expect(result.totalNoticeOfIntents).toBe(0); }); @@ -184,10 +190,10 @@ describe('SearchController', () => { expect(mockDataSource.createQueryRunner).toHaveBeenCalledTimes(1); expect( mockApplicationAdvancedSearchService.searchApplications, - ).toBeCalledTimes(1); + ).toHaveBeenCalledTimes(1); expect( mockApplicationAdvancedSearchService.searchApplications, - ).toBeCalledWith(mockSearchRequestDto, {}); + ).toHaveBeenCalledWith(mockSearchRequestDto, {}); expect(result.data).toBeDefined(); expect(result.total).toBe(0); expect(mockQueryRunner.release).toHaveBeenCalledTimes(1); @@ -208,10 +214,10 @@ describe('SearchController', () => { expect( mockNoticeOfIntentAdvancedSearchService.searchNoticeOfIntents, - ).toBeCalledTimes(1); + ).toHaveBeenCalledTimes(1); expect( mockNoticeOfIntentAdvancedSearchService.searchNoticeOfIntents, - ).toBeCalledWith(mockSearchRequestDto); + ).toHaveBeenCalledWith(mockSearchRequestDto); expect(result.data).toBeDefined(); expect(result.total).toBe(0); }); @@ -232,10 +238,10 @@ describe('SearchController', () => { expect(mockDataSource.createQueryRunner).toHaveBeenCalledTimes(1); expect( mockApplicationAdvancedSearchService.searchApplications, - ).toBeCalledTimes(1); + ).toHaveBeenCalledTimes(1); expect( mockApplicationAdvancedSearchService.searchApplications, - ).toBeCalledWith(mockSearchRequestDto, {}); + ).toHaveBeenCalledWith(mockSearchRequestDto, {}); expect(result.applications).toBeDefined(); expect(result.totalApplications).toBe(0); expect(mockQueryRunner.release).toHaveBeenCalledTimes(1); @@ -256,10 +262,10 @@ describe('SearchController', () => { expect( mockNoticeOfIntentAdvancedSearchService.searchNoticeOfIntents, - ).toBeCalledTimes(1); + ).toHaveBeenCalledTimes(1); expect( mockNoticeOfIntentAdvancedSearchService.searchNoticeOfIntents, - ).toBeCalledWith(mockSearchRequestDto); + ).toHaveBeenCalledWith(mockSearchRequestDto); expect(result.noticeOfIntents).toBeDefined(); expect(result.totalNoticeOfIntents).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 1d2616f5ca..3c2d4b888b 100644 --- a/services/apps/alcs/src/alcs/search/search.controller.ts +++ b/services/apps/alcs/src/alcs/search/search.controller.ts @@ -14,7 +14,6 @@ import { Application } from '../application/application.entity'; 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'; -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'; @@ -24,12 +23,15 @@ import { NoticeOfIntentAdvancedSearchService } from './notice-of-intent/notice-o import { NoticeOfIntentSubmissionSearchView } from './notice-of-intent/notice-of-intent-search-view.entity'; import { NotificationAdvancedSearchService } from './notification/notification-advanced-search.service'; import { NotificationSubmissionSearchView } from './notification/notification-search-view.entity'; +import { PlanningReviewAdvancedSearchService } from './planning-review/planning-review-advanced-search.service'; +import { PlanningReviewSearchView } from './planning-review/planning-review-search-view.entity'; import { AdvancedSearchResponseDto, AdvancedSearchResultDto, ApplicationSearchResultDto, NoticeOfIntentSearchResultDto, NotificationSearchResultDto, + PlanningReviewSearchResultDto, SearchRequestDto, SearchResultDto, } from './search.dto'; @@ -45,6 +47,7 @@ export class SearchController { private noticeOfIntentSearchService: NoticeOfIntentAdvancedSearchService, private applicationSearchService: ApplicationAdvancedSearchService, private notificationSearchService: NotificationAdvancedSearchService, + private planningReviewSearchService: PlanningReviewAdvancedSearchService, @InjectRepository(ApplicationType) private appTypeRepo: Repository<ApplicationType>, @InjectDataSource() @@ -56,8 +59,9 @@ export class SearchController { async search(@Param('searchTerm') searchTerm) { const application = await this.searchService.getApplication(searchTerm); const noi = await this.searchService.getNoi(searchTerm); - const covenant = await this.searchService.getCovenant(searchTerm); const notification = await this.searchService.getNotification(searchTerm); + const planningReview = + await this.searchService.getPlanningReview(searchTerm); const result: SearchResultDto[] = []; @@ -65,8 +69,7 @@ export class SearchController { result, application, noi, - null, //TODO - covenant, + planningReview, notification, ); @@ -78,7 +81,6 @@ export class SearchController { application: Application | null, noi: NoticeOfIntent | null, planningReview: PlanningReview | null, - covenant: Covenant | null, notification: Notification | null, ) { if (application) { @@ -93,10 +95,6 @@ export class SearchController { result.push(this.mapPlanningReviewToSearchResult(planningReview)); } - if (covenant) { - result.push(this.mapCovenantToSearchResult(covenant)); - } - if (notification) { result.push(this.mapNotificationToSearchResult(notification)); } @@ -108,7 +106,7 @@ export class SearchController { const { searchApplications, searchNoi, - searchNonApplications, + searchPlanningReviews, searchNotifications, } = this.getEntitiesTypeToSearch(searchDto); @@ -142,10 +140,18 @@ export class SearchController { notifications = await this.notificationSearchService.search(searchDto); } + let planningReviews: AdvancedSearchResultDto< + PlanningReviewSearchView[] + > | null = null; + if (searchPlanningReviews) { + planningReviews = + await this.planningReviewSearchService.search(searchDto); + } + return await this.mapAdvancedSearchResults( applicationSearchResult, noticeOfIntentSearchService, - null, + planningReviews, notifications, ); } finally { @@ -228,7 +234,7 @@ export class SearchController { private getEntitiesTypeToSearch(searchDto: SearchRequestDto) { let searchApplications = true; - let nonApplicationTypeSpecified = false; + let planningReviewTypeSpecified = false; let noiTypeSpecified = false; let notificationTypeSpecified = false; if (searchDto.fileTypes.length > 0) { @@ -245,24 +251,17 @@ export class SearchController { notificationTypeSpecified = searchDto.fileTypes.includes('SRW'); - nonApplicationTypeSpecified = searchDto.fileTypes.some((searchType) => - ['COV', 'PLAN'].includes(searchType), + planningReviewTypeSpecified = searchDto.fileTypes.some((searchType) => + ['PLAN'].includes(searchType), ); } const searchNoi = searchDto.fileTypes.length > 0 ? noiTypeSpecified : true; - const searchNonApplications = - (searchDto.fileTypes.length > 0 ? nonApplicationTypeSpecified : true) && - !searchDto.dateDecidedFrom && - !searchDto.dateDecidedTo && - !searchDto.dateSubmittedFrom && - !searchDto.dateDecidedTo && - !searchDto.resolutionNumber && - !searchDto.resolutionYear && + const searchPlanningReviews = + (searchDto.fileTypes.length > 0 ? planningReviewTypeSpecified : true) && !searchDto.portalStatusCode && !searchDto.pid && - !isStringSetAndNotEmpty(searchDto.legacyId) && !isStringSetAndNotEmpty(searchDto.civicAddress); const searchNotifications = @@ -276,7 +275,7 @@ export class SearchController { return { searchApplications, searchNoi, - searchNonApplications, + searchPlanningReviews, searchNotifications, }; } @@ -288,7 +287,7 @@ export class SearchController { noticeOfIntents: AdvancedSearchResultDto< NoticeOfIntentSubmissionSearchView[] > | null, - nonApplications: null, + planningReviews: AdvancedSearchResultDto<PlanningReviewSearchView[]> | null, notifications: AdvancedSearchResultDto< NotificationSubmissionSearchView[] > | null, @@ -328,12 +327,27 @@ export class SearchController { ); } + const mappedPlanningReviews: PlanningReviewSearchResultDto[] = []; + if ( + planningReviews && + planningReviews.data && + planningReviews.data.length > 0 + ) { + mappedPlanningReviews.push( + ...planningReviews.data.map((planningReview) => + this.mapPlanningReviewToAdvancedSearchResult(planningReview), + ), + ); + } + response.applications = mappedApplications; response.totalApplications = applications?.total ?? 0; response.noticeOfIntents = mappedNoticeOfIntents; response.totalNoticeOfIntents = noticeOfIntents?.total ?? 0; response.notifications = mappedNotifications; response.totalNotifications = notifications?.total ?? 0; + response.planningReviews = mappedPlanningReviews; + response.totalPlanningReviews = planningReviews?.total ?? 0; return response; } @@ -370,23 +384,12 @@ export class SearchController { private mapPlanningReviewToSearchResult( planning: PlanningReview, ): SearchResultDto { - //TODO return { - fileNumber: '', - localGovernmentName: undefined, - referenceId: '', - type: '', - }; - } - - private mapCovenantToSearchResult(covenant: Covenant): SearchResultDto { - return { - type: CARD_TYPE.COV, - referenceId: covenant.cardUuid, - localGovernmentName: covenant.localGovernment?.name, - applicant: covenant.applicant, - fileNumber: covenant.fileNumber, - boardCode: covenant.card.board.code, + type: CARD_TYPE.PLAN, + referenceId: planning.fileNumber, + localGovernmentName: planning.localGovernment?.name, + applicant: planning.documentName, + fileNumber: planning.fileNumber, }; } @@ -459,4 +462,25 @@ export class SearchController { status: notification.status.status_type_code, }; } + + private mapPlanningReviewToAdvancedSearchResult( + planningReview: PlanningReviewSearchView, + ): PlanningReviewSearchResultDto { + return { + documentName: planningReview.documentName, + referenceId: planningReview.fileNumber, + fileNumber: planningReview.fileNumber, + open: planningReview.open, + type: { + code: planningReview.planningReviewType_code, + label: planningReview.planningReviewType_label, + backgroundColor: planningReview.planningReviewType_background_color, + textColor: planningReview.planningReviewType_text_color, + description: '', + shortLabel: planningReview.planningReviewType_short_label, + }, + localGovernmentName: planningReview.localGovernmentName ?? null, + class: 'PLAN', + }; + } } diff --git a/services/apps/alcs/src/alcs/search/search.dto.ts b/services/apps/alcs/src/alcs/search/search.dto.ts index 78b918f28f..2b9c69583f 100644 --- a/services/apps/alcs/src/alcs/search/search.dto.ts +++ b/services/apps/alcs/src/alcs/search/search.dto.ts @@ -7,6 +7,7 @@ import { MinLength, } from 'class-validator'; import { ApplicationTypeDto } from '../code/application-code/application-type/application-type.dto'; +import { PlanningReviewTypeDto } from '../planning-review/planning-review.dto'; export class SearchResultDto { type: string; @@ -44,14 +45,14 @@ export class NoticeOfIntentSearchResultDto { class: SearchEntityClass; } -export class NonApplicationSearchResultDto { - type: string | null; - applicant: string | null; +export class PlanningReviewSearchResultDto { + type: PlanningReviewTypeDto | null; + documentName: string | null; referenceId: string | null; localGovernmentName: string | null; fileNumber: string; - boardCode: string | null; class: SearchEntityClass; + open: boolean; } export class NotificationSearchResultDto { @@ -70,9 +71,10 @@ export class AdvancedSearchResponseDto { applications: ApplicationSearchResultDto[]; noticeOfIntents: NoticeOfIntentSearchResultDto[]; notifications: NotificationSearchResultDto[]; + planningReviews: PlanningReviewSearchResultDto[]; totalApplications: number; totalNoticeOfIntents: number; - totalNonApplications: number; + totalPlanningReviews: number; totalNotifications: number; } diff --git a/services/apps/alcs/src/alcs/search/search.module.ts b/services/apps/alcs/src/alcs/search/search.module.ts index 6fbb75f13e..a3897bfb64 100644 --- a/services/apps/alcs/src/alcs/search/search.module.ts +++ b/services/apps/alcs/src/alcs/search/search.module.ts @@ -3,7 +3,6 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { ApplicationProfile } from '../../common/automapper/application.automapper.profile'; import { Application } from '../application/application.entity'; 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'; import { NoticeOfIntent } from '../notice-of-intent/notice-of-intent.entity'; import { Notification } from '../notification/notification.entity'; @@ -14,6 +13,8 @@ import { NoticeOfIntentAdvancedSearchService } from './notice-of-intent/notice-o import { NoticeOfIntentSubmissionSearchView } from './notice-of-intent/notice-of-intent-search-view.entity'; import { NotificationAdvancedSearchService } from './notification/notification-advanced-search.service'; import { NotificationSubmissionSearchView } from './notification/notification-search-view.entity'; +import { PlanningReviewAdvancedSearchService } from './planning-review/planning-review-advanced-search.service'; +import { PlanningReviewSearchView } from './planning-review/planning-review-search-view.entity'; import { SearchController } from './search.controller'; import { SearchService } from './search.service'; @@ -24,12 +25,12 @@ import { SearchService } from './search.service'; ApplicationType, NoticeOfIntent, PlanningReview, - Covenant, Notification, LocalGovernment, ApplicationSubmissionSearchView, NoticeOfIntentSubmissionSearchView, NotificationSubmissionSearchView, + PlanningReviewSearchView, ]), ], providers: [ @@ -38,6 +39,7 @@ import { SearchService } from './search.service'; ApplicationAdvancedSearchService, NoticeOfIntentAdvancedSearchService, NotificationAdvancedSearchService, + PlanningReviewAdvancedSearchService, ], 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 a01f160f12..0ee1ff6bdb 100644 --- a/services/apps/alcs/src/alcs/search/search.service.spec.ts +++ b/services/apps/alcs/src/alcs/search/search.service.spec.ts @@ -3,10 +3,10 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Application } from '../application/application.entity'; -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'; @@ -14,7 +14,7 @@ describe('SearchService', () => { let service: SearchService; let mockApplicationRepository: DeepMocked<Repository<Application>>; let mockNoiRepository: DeepMocked<Repository<NoticeOfIntent>>; - let mockCovenantRepository: DeepMocked<Repository<Covenant>>; + let mockPlanningReviewRepository: DeepMocked<Repository<PlanningReview>>; let mockApplicationSubmissionSearchView: DeepMocked< Repository<ApplicationSubmissionSearchView> >; @@ -26,7 +26,7 @@ describe('SearchService', () => { beforeEach(async () => { mockApplicationRepository = createMock(); mockNoiRepository = createMock(); - mockCovenantRepository = createMock(); + mockPlanningReviewRepository = createMock(); mockApplicationSubmissionSearchView = createMock(); mockLocalGovernment = createMock(); mockNotificationRepository = createMock(); @@ -43,8 +43,8 @@ describe('SearchService', () => { useValue: mockNoiRepository, }, { - provide: getRepositoryToken(Covenant), - useValue: mockCovenantRepository, + provide: getRepositoryToken(PlanningReview), + useValue: mockPlanningReviewRepository, }, { provide: getRepositoryToken(Notification), @@ -105,21 +105,19 @@ describe('SearchService', () => { expect(result).toBeDefined(); }); - it('should call repository to get covenant', async () => { - mockCovenantRepository.findOne.mockResolvedValue(new Covenant()); + it('should call repository to get planning review', async () => { + mockPlanningReviewRepository.findOne.mockResolvedValue( + new PlanningReview(), + ); - const result = await service.getCovenant('fake'); + const result = await service.getPlanningReview('fake'); - expect(mockCovenantRepository.findOne).toBeCalledTimes(1); - expect(mockCovenantRepository.findOne).toBeCalledWith({ + expect(mockPlanningReviewRepository.findOne).toBeCalledTimes(1); + expect(mockPlanningReviewRepository.findOne).toBeCalledWith({ where: { fileNumber: fakeFileNumber, - card: { archived: false }, }, relations: { - card: { - board: true, - }, localGovernment: true, }, }); diff --git a/services/apps/alcs/src/alcs/search/search.service.ts b/services/apps/alcs/src/alcs/search/search.service.ts index 1351386f20..c53df14ad1 100644 --- a/services/apps/alcs/src/alcs/search/search.service.ts +++ b/services/apps/alcs/src/alcs/search/search.service.ts @@ -2,10 +2,8 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; 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 = { @@ -22,10 +20,10 @@ export class SearchService { private applicationRepository: Repository<Application>, @InjectRepository(NoticeOfIntent) private noiRepository: Repository<NoticeOfIntent>, - @InjectRepository(Covenant) - private covenantRepository: Repository<Covenant>, @InjectRepository(Notification) private notificationRepository: Repository<Notification>, + @InjectRepository(PlanningReview) + private planningReviewRepository: Repository<PlanningReview>, ) {} async getApplication(fileNumber: string) { @@ -53,22 +51,23 @@ export class SearchService { }); } - async getCovenant(fileNumber: string) { - return await this.covenantRepository.findOne({ + async getNotification(fileNumber: string) { + return await this.notificationRepository.findOne({ where: { fileNumber, - card: { archived: false }, }, relations: CARD_RELATIONSHIP, }); } - async getNotification(fileNumber: string) { - return await this.notificationRepository.findOne({ + async getPlanningReview(fileNumber: string) { + return await this.planningReviewRepository.findOne({ where: { fileNumber, }, - relations: CARD_RELATIONSHIP, + relations: { + localGovernment: true, + }, }); } } diff --git a/services/apps/alcs/src/common/automapper/covenant.automapper.profile.ts b/services/apps/alcs/src/common/automapper/covenant.automapper.profile.ts deleted file mode 100644 index 585c1ada3f..0000000000 --- a/services/apps/alcs/src/common/automapper/covenant.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 { CovenantDto } from '../../alcs/covenant/covenant.dto'; -import { Covenant } from '../../alcs/covenant/covenant.entity'; - -@Injectable() -export class CovenantProfile extends AutomapperProfile { - constructor(@InjectMapper() mapper: Mapper) { - super(mapper); - } - - override get profile() { - return (mapper) => { - createMap(mapper, Covenant, CovenantDto); - }; - } -} diff --git a/services/apps/alcs/src/file-number/file-number.module.ts b/services/apps/alcs/src/file-number/file-number.module.ts index 2a923ff808..5d542aa0c1 100644 --- a/services/apps/alcs/src/file-number/file-number.module.ts +++ b/services/apps/alcs/src/file-number/file-number.module.ts @@ -1,12 +1,14 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Application } from '../alcs/application/application.entity'; -import { Covenant } from '../alcs/covenant/covenant.entity'; import { NoticeOfIntent } from '../alcs/notice-of-intent/notice-of-intent.entity'; +import { PlanningReview } from '../alcs/planning-review/planning-review.entity'; import { FileNumberService } from './file-number.service'; @Module({ - imports: [TypeOrmModule.forFeature([Covenant, Application, NoticeOfIntent])], + imports: [ + TypeOrmModule.forFeature([PlanningReview, Application, NoticeOfIntent]), + ], providers: [FileNumberService], exports: [FileNumberService], }) diff --git a/services/apps/alcs/src/file-number/file-number.service.spec.ts b/services/apps/alcs/src/file-number/file-number.service.spec.ts index b108979436..62b05bb08a 100644 --- a/services/apps/alcs/src/file-number/file-number.service.spec.ts +++ b/services/apps/alcs/src/file-number/file-number.service.spec.ts @@ -4,19 +4,19 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Application } from '../alcs/application/application.entity'; -import { Covenant } from '../alcs/covenant/covenant.entity'; import { NoticeOfIntent } from '../alcs/notice-of-intent/notice-of-intent.entity'; +import { PlanningReview } from '../alcs/planning-review/planning-review.entity'; import { FileNumberService } from './file-number.service'; describe('FileNumberService', () => { let service: FileNumberService; let mockAppRepo: DeepMocked<Repository<Application>>; - let mockCovenantRepo: DeepMocked<Repository<Covenant>>; + let mockPlanningReviewRepo: DeepMocked<Repository<PlanningReview>>; let mockNOIRepo: DeepMocked<Repository<NoticeOfIntent>>; beforeEach(async () => { mockAppRepo = createMock(); - mockCovenantRepo = createMock(); + mockPlanningReviewRepo = createMock(); mockNOIRepo = createMock(); const module: TestingModule = await Test.createTestingModule({ @@ -27,8 +27,8 @@ describe('FileNumberService', () => { useValue: mockAppRepo, }, { - provide: getRepositoryToken(Covenant), - useValue: mockCovenantRepo, + provide: getRepositoryToken(PlanningReview), + useValue: mockPlanningReviewRepo, }, { provide: getRepositoryToken(NoticeOfIntent), @@ -45,9 +45,9 @@ describe('FileNumberService', () => { }); it('should check all three repos for existence', async () => { - mockAppRepo.exist.mockResolvedValue(false); - mockCovenantRepo.exist.mockResolvedValue(false); - mockNOIRepo.exist.mockResolvedValue(false); + mockAppRepo.exists.mockResolvedValue(false); + mockPlanningReviewRepo.exists.mockResolvedValue(false); + mockNOIRepo.exists.mockResolvedValue(false); const res = await service.checkValidFileNumber(''); @@ -55,20 +55,20 @@ describe('FileNumberService', () => { }); it('should throw an exception if application exists', async () => { - mockAppRepo.exist.mockResolvedValue(true); - mockCovenantRepo.exist.mockResolvedValue(false); - mockNOIRepo.exist.mockResolvedValue(false); + mockAppRepo.exists.mockResolvedValue(true); + mockPlanningReviewRepo.exists.mockResolvedValue(false); + mockNOIRepo.exists.mockResolvedValue(false); const promise = service.checkValidFileNumber('5'); await expect(promise).rejects.toMatchObject( new ServiceValidationException( - `Application/Covenant/NOI already exists with File ID 5`, + `Application/Planning Review/NOI already exists with File ID 5`, ), ); - expect(mockAppRepo.exist).toHaveBeenCalledTimes(1); - expect(mockCovenantRepo.exist).toHaveBeenCalledTimes(1); - expect(mockNOIRepo.exist).toHaveBeenCalledTimes(1); + expect(mockAppRepo.exists).toHaveBeenCalledTimes(1); + expect(mockPlanningReviewRepo.exists).toHaveBeenCalledTimes(1); + expect(mockNOIRepo.exists).toHaveBeenCalledTimes(1); }); it('should generate and return new fileNumber', async () => { diff --git a/services/apps/alcs/src/file-number/file-number.service.ts b/services/apps/alcs/src/file-number/file-number.service.ts index 03f3b1580e..a54e4c4427 100644 --- a/services/apps/alcs/src/file-number/file-number.service.ts +++ b/services/apps/alcs/src/file-number/file-number.service.ts @@ -3,8 +3,8 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Application } from '../alcs/application/application.entity'; -import { Covenant } from '../alcs/covenant/covenant.entity'; import { NoticeOfIntent } from '../alcs/notice-of-intent/notice-of-intent.entity'; +import { PlanningReview } from '../alcs/planning-review/planning-review.entity'; import { FILE_NUMBER_SEQUENCE } from './file-number.constants'; @Injectable() @@ -12,33 +12,33 @@ export class FileNumberService { constructor( @InjectRepository(Application) private applicationRepo: Repository<Application>, - @InjectRepository(Covenant) - private covenantRepo: Repository<Covenant>, @InjectRepository(NoticeOfIntent) private noticeOfIntentRepo: Repository<NoticeOfIntent>, + @InjectRepository(PlanningReview) + private planningReviewRepo: Repository<PlanningReview>, ) {} async checkValidFileNumber(fileNumber: string) { - const applicationExists = await this.applicationRepo.exist({ + const applicationExists = await this.applicationRepo.exists({ where: { fileNumber, }, }); - const covenantExists = await this.covenantRepo.exist({ + const noticeOfIntentExists = await this.noticeOfIntentRepo.exists({ where: { fileNumber, }, }); - const noticeOfIntentExists = await this.noticeOfIntentRepo.exist({ + const planningReviewExists = await this.planningReviewRepo.exists({ where: { fileNumber, }, }); - if (applicationExists || covenantExists || noticeOfIntentExists) { + if (applicationExists || planningReviewExists || noticeOfIntentExists) { throw new ServiceValidationException( - `Application/Covenant/NOI already exists with File ID ${fileNumber}`, + `Application/Planning Review/NOI already exists with File ID ${fileNumber}`, ); } return true; diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1710799245145-add_pr_search_view.ts b/services/apps/alcs/src/providers/typeorm/migrations/1710799245145-add_pr_search_view.ts new file mode 100644 index 0000000000..b9ea59be99 --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1710799245145-add_pr_search_view.ts @@ -0,0 +1,28 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddPrSearchView1710799245145 implements MigrationInterface { + name = 'AddPrSearchView1710799245145'; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `CREATE VIEW "alcs"."planning_review_search_view" AS SELECT "planning_review"."uuid" AS "uuid", "planning_review"."open" AS "open", "planningReviewType"."audit_deleted_date_at" AS "planningReviewType_audit_deleted_date_at", "planningReviewType"."audit_created_at" AS "planningReviewType_audit_created_at", "planningReviewType"."audit_updated_at" AS "planningReviewType_audit_updated_at", "planningReviewType"."audit_created_by" AS "planningReviewType_audit_created_by", "planningReviewType"."audit_updated_by" AS "planningReviewType_audit_updated_by", "planningReviewType"."label" AS "planningReviewType_label", "planningReviewType"."code" AS "planningReviewType_code", "planningReviewType"."description" AS "planningReviewType_description", "planningReviewType"."short_label" AS "planningReviewType_short_label", "planningReviewType"."background_color" AS "planningReviewType_background_color", "planningReviewType"."text_color" AS "planningReviewType_text_color", "planningReviewType"."html_description" AS "planningReviewType_html_description", "localGovernment"."name" AS "local_government_name", "planning_referral".*, "planning_review"."file_number" AS "file_number", "planning_review"."document_name" AS "document_name", planning_referral.submission_date AS "date_submitted_to_alc", "planning_review"."local_government_uuid" AS "local_government_uuid", "planning_review"."type_code" AS "planning_review_type_code", "planning_review"."region_code" AS "region_code" FROM "alcs"."planning_review" "planning_review" INNER JOIN "alcs"."planning_review_type" "planningReviewType" ON "planning_review"."type_code" = "planningReviewType"."code" AND "planningReviewType"."audit_deleted_date_at" IS NULL LEFT JOIN "alcs"."local_government" "localGovernment" ON "planning_review"."local_government_uuid" = "localGovernment"."uuid" AND "localGovernment"."audit_deleted_date_at" IS NULL LEFT JOIN (SELECT "planning_referral"."uuid" AS "planning_referral_uuid", "planning_referral"."submission_date" AS "submission_date", "planning_referral"."planning_review_uuid" AS "planning_review_uuid" FROM "alcs"."planning_referral" "planning_referral" INNER JOIN (SELECT "child"."planning_review_uuid" AS "planning_review_uuid", MIN("child"."audit_created_at") AS "audit_created_at" FROM "alcs"."planning_referral" "child" WHERE "child"."audit_deleted_date_at" IS NULL GROUP BY "child"."planning_review_uuid") "child" ON "planning_referral"."audit_created_at" = child.audit_created_at AND "planning_referral"."planning_review_uuid" = child.planning_review_uuid WHERE "planning_referral"."audit_deleted_date_at" IS NULL) "planning_referral" ON "planning_review"."uuid" = planning_referral.planning_review_uuid WHERE "planning_review"."audit_deleted_date_at" IS NULL`, + ); + await queryRunner.query( + `INSERT INTO "alcs"."typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES (DEFAULT, $1, DEFAULT, $2, $3, $4)`, + [ + 'alcs', + 'VIEW', + 'planning_review_search_view', + 'SELECT "planning_review"."uuid" AS "uuid", "planning_review"."open" AS "open", "planningReviewType"."audit_deleted_date_at" AS "planningReviewType_audit_deleted_date_at", "planningReviewType"."audit_created_at" AS "planningReviewType_audit_created_at", "planningReviewType"."audit_updated_at" AS "planningReviewType_audit_updated_at", "planningReviewType"."audit_created_by" AS "planningReviewType_audit_created_by", "planningReviewType"."audit_updated_by" AS "planningReviewType_audit_updated_by", "planningReviewType"."label" AS "planningReviewType_label", "planningReviewType"."code" AS "planningReviewType_code", "planningReviewType"."description" AS "planningReviewType_description", "planningReviewType"."short_label" AS "planningReviewType_short_label", "planningReviewType"."background_color" AS "planningReviewType_background_color", "planningReviewType"."text_color" AS "planningReviewType_text_color", "planningReviewType"."html_description" AS "planningReviewType_html_description", "localGovernment"."name" AS "local_government_name", "planning_referral".*, "planning_review"."file_number" AS "file_number", "planning_review"."document_name" AS "document_name", planning_referral.submission_date AS "date_submitted_to_alc", "planning_review"."local_government_uuid" AS "local_government_uuid", "planning_review"."type_code" AS "planning_review_type_code", "planning_review"."region_code" AS "region_code" FROM "alcs"."planning_review" "planning_review" INNER JOIN "alcs"."planning_review_type" "planningReviewType" ON "planning_review"."type_code" = "planningReviewType"."code" AND "planningReviewType"."audit_deleted_date_at" IS NULL LEFT JOIN "alcs"."local_government" "localGovernment" ON "planning_review"."local_government_uuid" = "localGovernment"."uuid" AND "localGovernment"."audit_deleted_date_at" IS NULL LEFT JOIN (SELECT "planning_referral"."uuid" AS "planning_referral_uuid", "planning_referral"."submission_date" AS "submission_date", "planning_referral"."planning_review_uuid" AS "planning_review_uuid" FROM "alcs"."planning_referral" "planning_referral" INNER JOIN (SELECT "child"."planning_review_uuid" AS "planning_review_uuid", MIN("child"."audit_created_at") AS "audit_created_at" FROM "alcs"."planning_referral" "child" WHERE "child"."audit_deleted_date_at" IS NULL GROUP BY "child"."planning_review_uuid") "child" ON "planning_referral"."audit_created_at" = child.audit_created_at AND "planning_referral"."planning_review_uuid" = child.planning_review_uuid WHERE "planning_referral"."audit_deleted_date_at" IS NULL) "planning_referral" ON "planning_review"."uuid" = planning_referral.planning_review_uuid WHERE "planning_review"."audit_deleted_date_at" IS NULL', + ], + ); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `DELETE FROM "alcs"."typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, + ['VIEW', 'planning_review_search_view', 'alcs'], + ); + await queryRunner.query(`DROP VIEW "alcs"."planning_review_search_view"`); + } +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1710800257994-delete_v1_covenant.ts b/services/apps/alcs/src/providers/typeorm/migrations/1710800257994-delete_v1_covenant.ts new file mode 100644 index 0000000000..222cb57922 --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1710800257994-delete_v1_covenant.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class DeleteV1Covenant1710800257994 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + DROP TABLE "alcs"."covenant"; + `); + await queryRunner.query(` + UPDATE "alcs"."card" SET "audit_deleted_date_at" = NOW(), "archived" = TRUE WHERE "type_code" = 'COVE' + `); + } + + public async down(): Promise<void> {} +} From 9423cd72241959a5be73eed51b431d3417f25f6c Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Mon, 18 Mar 2024 16:41:38 -0700 Subject: [PATCH 016/153] Fixes for PRs * Change menu order * Keep columns evenly sized * Add missing confirmation dialog * Update Decision Document visibility --- .../decision-documents.component.html | 5 +++-- .../decision-documents.component.ts | 6 ++---- .../planning-review.component.ts | 12 ++++++------ .../referrals/referral.component.scss | 2 +- .../referrals/referral.component.ts | 18 ++++++++++++------ 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/alcs-frontend/src/app/features/planning-review/decision/decision-documents/decision-documents.component.html b/alcs-frontend/src/app/features/planning-review/decision/decision-documents/decision-documents.component.html index 4d76fc9688..4454c01830 100644 --- a/alcs-frontend/src/app/features/planning-review/decision/decision-documents/decision-documents.component.html +++ b/alcs-frontend/src/app/features/planning-review/decision/decision-documents/decision-documents.component.html @@ -36,10 +36,11 @@ <h3>Documents</h3> <ng-container matColumnDef="visibilityFlags"> <th mat-header-cell *matHeaderCellDef> Visibility - <div class="subheading">* = Pending</div> </th> <td mat-cell *matCellDef="let element"> - <span matTooltip="Commissioner">C<span *ngIf="!areDocumentsReleased">*</span></span> + <span matTooltip="Not Visible to A, G, C, or P"> + <mat-icon>visibility_off</mat-icon> + </span> </td> </ng-container> diff --git a/alcs-frontend/src/app/features/planning-review/decision/decision-documents/decision-documents.component.ts b/alcs-frontend/src/app/features/planning-review/decision/decision-documents/decision-documents.component.ts index 72be901bc2..1de8f1a863 100644 --- a/alcs-frontend/src/app/features/planning-review/decision/decision-documents/decision-documents.component.ts +++ b/alcs-frontend/src/app/features/planning-review/decision/decision-documents/decision-documents.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, OnChanges, OnDestroy, Output, SimpleChanges, ViewChild } from '@angular/core'; +import { Component, EventEmitter, Input, OnChanges, OnDestroy, Output, ViewChild } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { MatSort } from '@angular/material/sort'; import { MatTableDataSource } from '@angular/material/table'; @@ -28,7 +28,6 @@ export class DecisionDocumentsComponent implements OnDestroy, OnChanges { displayedColumns: string[] = ['type', 'fileName', 'source', 'visibilityFlags', 'uploadedAt', 'actions']; private fileId = ''; - areDocumentsReleased = false; @ViewChild(MatSort) sort!: MatSort; dataSource: MatTableDataSource<PlanningReviewDecisionDocumentDto> = @@ -103,8 +102,7 @@ export class DecisionDocumentsComponent implements OnDestroy, OnChanges { this.$destroy.complete(); } - ngOnChanges(changes: SimpleChanges): void { - this.areDocumentsReleased = true; + ngOnChanges(): void { if (this.decision) { this.dataSource = new MatTableDataSource(this.decision.documents); } diff --git a/alcs-frontend/src/app/features/planning-review/planning-review.component.ts b/alcs-frontend/src/app/features/planning-review/planning-review.component.ts index eba0051892..5e12f6f237 100644 --- a/alcs-frontend/src/app/features/planning-review/planning-review.component.ts +++ b/alcs-frontend/src/app/features/planning-review/planning-review.component.ts @@ -21,12 +21,6 @@ export const childRoutes = [ icon: 'edit_note', component: ReferralComponent, }, - { - path: 'documents', - menuTitle: 'Documents', - icon: 'description', - component: DocumentsComponent, - }, { path: 'decision', menuTitle: 'Decisions', @@ -35,6 +29,12 @@ export const childRoutes = [ portalOnly: false, children: decisionChildRoutes, }, + { + path: 'documents', + menuTitle: 'Documents', + icon: 'description', + component: DocumentsComponent, + }, ]; @Component({ diff --git a/alcs-frontend/src/app/features/planning-review/referrals/referral.component.scss b/alcs-frontend/src/app/features/planning-review/referrals/referral.component.scss index bc32538cb7..6f59a1e36f 100644 --- a/alcs-frontend/src/app/features/planning-review/referrals/referral.component.scss +++ b/alcs-frontend/src/app/features/planning-review/referrals/referral.component.scss @@ -15,7 +15,7 @@ section { .two-columns { display: grid; - grid-template-columns: 1fr 1fr; + grid-template-columns: calc(50% - 12px) calc(50% - 12px); grid-column-gap: 24px; grid-row-gap: 24px; diff --git a/alcs-frontend/src/app/features/planning-review/referrals/referral.component.ts b/alcs-frontend/src/app/features/planning-review/referrals/referral.component.ts index 3c5cdc0234..3f3d11af5e 100644 --- a/alcs-frontend/src/app/features/planning-review/referrals/referral.component.ts +++ b/alcs-frontend/src/app/features/planning-review/referrals/referral.component.ts @@ -1,4 +1,3 @@ -import { Dialog } from '@angular/cdk/dialog'; import { Component, OnDestroy, OnInit } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { Subject, takeUntil } from 'rxjs'; @@ -9,7 +8,7 @@ import { PlanningReviewDto, UpdatePlanningReferralDto, } from '../../../services/planning-review/planning-review.dto'; -import { PlanningReviewService } from '../../../services/planning-review/planning-review.service'; +import { ConfirmationDialogService } from '../../../shared/confirmation-dialog/confirmation-dialog.service'; import { CreatePlanningReferralDialogComponent } from './create/create-planning-referral-dialog.component'; @Component({ @@ -28,6 +27,7 @@ export class ReferralComponent implements OnInit, OnDestroy { constructor( private planningReviewDetailService: PlanningReviewDetailService, private planningReferralService: PlanningReferralService, + private confirmationDialogService: ConfirmationDialogService, private dialog: MatDialog, ) {} @@ -76,9 +76,15 @@ export class ReferralComponent implements OnInit, OnDestroy { } async onDelete(uuid: string) { - if (this.planningReview) { - await this.planningReferralService.delete(uuid); - this.planningReviewDetailService.loadReview(this.planningReview.fileNumber); - } + this.confirmationDialogService + .openDialog({ + body: 'This action will delete the referral, the kanban card, and the ALC Response. Do you want to continue?', + }) + .subscribe(async (onConfirm) => { + if (onConfirm && this.planningReview) { + await this.planningReferralService.delete(uuid); + await this.planningReviewDetailService.loadReview(this.planningReview.fileNumber); + } + }); } } From d19af42a0b2f8a94c1ccd381fedb383f749ed066 Mon Sep 17 00:00:00 2001 From: Liam Stoddard <lstodd@protonmail.com> Date: Tue, 19 Mar 2024 10:56:51 -0700 Subject: [PATCH 017/153] set survey to true if file present --- .../menu/post_launch_commands/documents.py | 3 + .../menu/post_launch_commands/import_all.py | 5 +- .../srw/post_launch/srw_migration.py | 9 +- .../srw_survey_plan_update.sql | 10 ++ .../srw_survey_plan_update_count.sql | 17 +++ .../srw/submission/__init__.py | 1 + .../srw/submission/srw_survey_update.py | 113 ++++++++++++++++++ 7 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 bin/migrate-oats-data/srw/sql/submission/proposal_fields/srw_survey_plan_update.sql create mode 100644 bin/migrate-oats-data/srw/sql/submission/proposal_fields/srw_survey_plan_update_count.sql create mode 100644 bin/migrate-oats-data/srw/submission/srw_survey_update.py diff --git a/bin/migrate-oats-data/menu/post_launch_commands/documents.py b/bin/migrate-oats-data/menu/post_launch_commands/documents.py index b130239efb..d3daba0c60 100644 --- a/bin/migrate-oats-data/menu/post_launch_commands/documents.py +++ b/bin/migrate-oats-data/menu/post_launch_commands/documents.py @@ -1,4 +1,5 @@ from documents.post_launch import import_documents, clean_documents +from srw.post_launch.srw_migration import srw_survey_plan_update def document_import(console, args): @@ -12,6 +13,8 @@ def document_import(console, args): console.log(f"Processing documents import in batch size = {import_batch_size}") import_documents(batch_size=import_batch_size) + # update SRW submissions based on data imported to alcs + srw_survey_plan_update(batch_size=import_batch_size) def document_clean(console): diff --git a/bin/migrate-oats-data/menu/post_launch_commands/import_all.py b/bin/migrate-oats-data/menu/post_launch_commands/import_all.py index a0de0fffd6..f060b78416 100644 --- a/bin/migrate-oats-data/menu/post_launch_commands/import_all.py +++ b/bin/migrate-oats-data/menu/post_launch_commands/import_all.py @@ -2,7 +2,7 @@ from noi.post_launch import ( process_notice_of_intent, ) -from srw.post_launch.srw_migration import process_srw +from srw.post_launch.srw_migration import process_srw, srw_survey_plan_update from documents.post_launch.migrate_documents import import_documents @@ -26,4 +26,7 @@ def import_all(console, args): console.log("Processing Documents") import_documents(batch_size=import_batch_size) + console.log("Process SRW Updates") + srw_survey_plan_update(batch_size=import_batch_size) + console.log("Done") diff --git a/bin/migrate-oats-data/srw/post_launch/srw_migration.py b/bin/migrate-oats-data/srw/post_launch/srw_migration.py index 6e000d2ece..6ea59e1b0b 100644 --- a/bin/migrate-oats-data/srw/post_launch/srw_migration.py +++ b/bin/migrate-oats-data/srw/post_launch/srw_migration.py @@ -1,7 +1,10 @@ from ..srw_base import init_srw_base, clean_initial_srw from ..srw_base_update import update_srw_base_fields from ..submission.srw_submission_init import init_srw_submissions, clean_srw_submissions -from ..submission.srw_proposal_fields import process_alcs_srw_proposal_fields +from ..submission.srw_proposal_fields import ( + process_alcs_srw_proposal_fields, +) +from ..submission.srw_survey_update import srw_survey_plan_update from ..submission.parcel.srw_parcel_init import init_srw_parcels, clean_parcels from ..submission.transferee.srw_init_transferee import ( init_srw_parcel_transferee, @@ -51,6 +54,10 @@ def _process_srw_submission_statuses(batch_size): process_alcs_srw_submitted_to_alc_status(batch_size) +def update_srw_survey_plan(batch_size): + srw_survey_plan_update(batch_size) + + def clean_srw(): clean_srw_staff_journal() clean_transferees() diff --git a/bin/migrate-oats-data/srw/sql/submission/proposal_fields/srw_survey_plan_update.sql b/bin/migrate-oats-data/srw/sql/submission/proposal_fields/srw_survey_plan_update.sql new file mode 100644 index 0000000000..b6aa63decf --- /dev/null +++ b/bin/migrate-oats-data/srw/sql/submission/proposal_fields/srw_survey_plan_update.sql @@ -0,0 +1,10 @@ +SELECT DISTINCT + ON (ns.file_number::INTEGER) nd.type_code, + ns.has_survey_plan, + nd.oats_application_id::INTEGER +FROM + alcs.notification_submission ns + JOIN alcs.notification_document nd ON ns.file_number = nd.oats_application_id::TEXT +WHERE + nd.type_code IN ('SRWP', 'SURV') + AND ns.has_survey_plan IS False \ No newline at end of file diff --git a/bin/migrate-oats-data/srw/sql/submission/proposal_fields/srw_survey_plan_update_count.sql b/bin/migrate-oats-data/srw/sql/submission/proposal_fields/srw_survey_plan_update_count.sql new file mode 100644 index 0000000000..0c93a118e1 --- /dev/null +++ b/bin/migrate-oats-data/srw/sql/submission/proposal_fields/srw_survey_plan_update_count.sql @@ -0,0 +1,17 @@ +WITH + update_count AS ( + SELECT DISTINCT + ON (ns.file_number) nd.type_code, + ns.has_survey_plan, + nd.oats_application_id + FROM + alcs.notification_submission ns + JOIN alcs.notification_document nd ON ns.file_number = nd.oats_application_id::TEXT + WHERE + nd.type_code IN ('SRWP', 'SURV') + AND ns.has_survey_plan IS False + ) +SELECT + COUNT(*) +FROM + update_count; \ No newline at end of file diff --git a/bin/migrate-oats-data/srw/submission/__init__.py b/bin/migrate-oats-data/srw/submission/__init__.py index e68657c6d2..8383076216 100644 --- a/bin/migrate-oats-data/srw/submission/__init__.py +++ b/bin/migrate-oats-data/srw/submission/__init__.py @@ -1,3 +1,4 @@ from .srw_submission_init import init_srw_submissions, clean_srw_submissions from .srw_proposal_fields import process_alcs_srw_proposal_fields from .statuses import * +from .srw_survey_update import srw_survey_plan_update diff --git a/bin/migrate-oats-data/srw/submission/srw_survey_update.py b/bin/migrate-oats-data/srw/submission/srw_survey_update.py new file mode 100644 index 0000000000..66c19046c8 --- /dev/null +++ b/bin/migrate-oats-data/srw/submission/srw_survey_update.py @@ -0,0 +1,113 @@ +from common import BATCH_UPLOAD_SIZE, setup_and_get_logger, DEFAULT_ETL_USER_UUID +from db import inject_conn_pool +from psycopg2.extras import RealDictCursor, execute_batch + +etl_name = "process_alcs_srw_survey_update" +logger = setup_and_get_logger(etl_name) + + +@inject_conn_pool +def srw_survey_plan_update(conn=None, batch_size=BATCH_UPLOAD_SIZE): + """ + This function is responsible for updating of the notification_submission in ALCS: has_survey_plan will be set to true if document types of SURV or SRWP are present in alcs notification_documents. + + Args: + conn (psycopg2.extensions.connection): PostgreSQL database connection. Provided by the decorator. + batch_size (int): The number of items to process at once. Defaults to BATCH_UPLOAD_SIZE. + """ + + logger.info(f"Start {etl_name}") + with conn.cursor(cursor_factory=RealDictCursor) as cursor: + with open( + "srw/sql/submission/proposal_fields/srw_survey_plan_update_count.sql", + "r", + encoding="utf-8", + ) as sql_file: + count_query = sql_file.read() + cursor.execute(count_query) + count_total = dict(cursor.fetchone())["count"] + logger.info(f"Total SRW data to update: {count_total}") + + failed_inserts_count = 0 + successful_updates_count = 0 + last_application_id = 0 + + with open( + "srw/sql/submission/proposal_fields/srw_survey_plan_update.sql", + "r", + encoding="utf-8", + ) as sql_file: + application_sql = sql_file.read() + while True: + cursor.execute( + f""" + {application_sql} + AND ns.file_number::INTEGER > {last_application_id} ORDER BY ns.file_number::INTEGER; + """ + ) + + rows = cursor.fetchmany(batch_size) + + if not rows: + break + try: + records_to_be_updated_count = len(rows) + + _update_records(conn, batch_size, cursor, rows) + + successful_updates_count = ( + successful_updates_count + records_to_be_updated_count + ) + last_application_id = dict(rows[-1])["oats_application_id"] + + logger.debug( + f"retrieved/updated items count: {records_to_be_updated_count}; total successfully updated SRWs so far {successful_updates_count}; last updated alr_application_id: {last_application_id}" + ) + except Exception as err: + # this is NOT going to be caused by actual data update failure. This code is only executed when the code error appears or connection to DB is lost + logger.exception(err) + conn.rollback() + failed_inserts_count = count_total - successful_updates_count + last_application_id = last_application_id + 1 + + logger.info( + f"Finished {etl_name}: total amount of successful updates {successful_updates_count}, total failed updates {failed_inserts_count}" + ) + + +def _update_records(conn, batch_size, cursor, rows): + parsed_data_list = _prepare_oats_data(rows) + + if len(parsed_data_list) > 0: + execute_batch( + cursor, + _update_query, + parsed_data_list, + page_size=batch_size, + ) + + conn.commit() + + +_update_query = """ + UPDATE + alcs.notification_submission + SET + has_survey_plan = %(has_survey_plan)s + WHERE + alcs.notification_submission.file_number = %(file_number)s::TEXT +""" + + +def _prepare_oats_data(row_data_list): + data_list = [] + for row in row_data_list: + data_list.append(_map_fields(dict(row))) + return data_list + + +def _map_fields(data): + return { + "has_survey_plan": True, + "file_number": data["oats_application_id"], + } From 7ef334b66cfba5d72611adef6a29c43bfd73d5cd Mon Sep 17 00:00:00 2001 From: Liam Stoddard <lstodd@protonmail.com> Date: Tue, 19 Mar 2024 11:18:03 -0700 Subject: [PATCH 018/153] update mapping of ownership --- bin/migrate-oats-data/srw/submission/parcel/srw_parcel_init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/migrate-oats-data/srw/submission/parcel/srw_parcel_init.py b/bin/migrate-oats-data/srw/submission/parcel/srw_parcel_init.py index 2e3e31aa6a..d32a887630 100644 --- a/bin/migrate-oats-data/srw/submission/parcel/srw_parcel_init.py +++ b/bin/migrate-oats-data/srw/submission/parcel/srw_parcel_init.py @@ -114,7 +114,7 @@ def _map_data(row, insert_index): def _map_ownership_type_code(data): return ( OatsToAlcsOwnershipType.CROWN.value - if data["pin"] + if data.get("pin") or not data.get("pid") else OatsToAlcsOwnershipType.FEE.value ) From ed9163ae7b1e3b34717c362b9c907a3749881207 Mon Sep 17 00:00:00 2001 From: Liam Stoddard <lstodd@protonmail.com> Date: Tue, 19 Mar 2024 11:39:43 -0700 Subject: [PATCH 019/153] add mapping from oats --- .../srw/sql/submission/parcel/srw_parcels_insert.sql | 5 +++-- .../srw/submission/parcel/srw_parcel_init.py | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/bin/migrate-oats-data/srw/sql/submission/parcel/srw_parcels_insert.sql b/bin/migrate-oats-data/srw/sql/submission/parcel/srw_parcels_insert.sql index ed5aab5494..7d75f02424 100644 --- a/bin/migrate-oats-data/srw/sql/submission/parcel/srw_parcels_insert.sql +++ b/bin/migrate-oats-data/srw/sql/submission/parcel/srw_parcels_insert.sql @@ -7,7 +7,7 @@ WITH parcels_to_insert AS ( AND nos.type_code = 'SRW' ), grouped_oats_property_interests_ids AS ( - SELECT subject_property_id + SELECT subject_property_id, MAX(property_owner_type_code) AS property_owner_type_code FROM oats.oats_property_interests opi GROUP BY opi.subject_property_id ) @@ -18,7 +18,8 @@ SELECT uuid AS notification_submission_uuid, op.pid, op.pin, osp.subject_property_id, - op.property_id + op.property_id, + gopi.property_owner_type_code FROM parcels_to_insert pti JOIN oats.oats_subject_properties osp ON osp.subject_property_id = pti.subject_property_id JOIN oats.oats_properties op ON op.property_id = osp.property_id diff --git a/bin/migrate-oats-data/srw/submission/parcel/srw_parcel_init.py b/bin/migrate-oats-data/srw/submission/parcel/srw_parcel_init.py index d32a887630..38dd361f59 100644 --- a/bin/migrate-oats-data/srw/submission/parcel/srw_parcel_init.py +++ b/bin/migrate-oats-data/srw/submission/parcel/srw_parcel_init.py @@ -112,6 +112,12 @@ def _map_data(row, insert_index): def _map_ownership_type_code(data): + if data.get("property_owner_type_code"): + ownership_type_code = data["property_owner_type_code"] + if ownership_type_code == OatsToAlcsOwnershipType.FEE.name: + return OatsToAlcsOwnershipType.FEE.value + if ownership_type_code == OatsToAlcsOwnershipType.CROWN.name: + return OatsToAlcsOwnershipType.CROWN.value return ( OatsToAlcsOwnershipType.CROWN.value if data.get("pin") or not data.get("pid") From 757be3fdc11ca48b3c61ae4e0646d07e6deac4d3 Mon Sep 17 00:00:00 2001 From: Liam Stoddard <lstodd@protonmail.com> Date: Tue, 19 Mar 2024 11:45:52 -0700 Subject: [PATCH 020/153] add comment for clarity --- bin/migrate-oats-data/menu/post_launch_commands/import_all.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/migrate-oats-data/menu/post_launch_commands/import_all.py b/bin/migrate-oats-data/menu/post_launch_commands/import_all.py index f060b78416..691e924f0c 100644 --- a/bin/migrate-oats-data/menu/post_launch_commands/import_all.py +++ b/bin/migrate-oats-data/menu/post_launch_commands/import_all.py @@ -27,6 +27,7 @@ def import_all(console, args): import_documents(batch_size=import_batch_size) console.log("Process SRW Updates") + # These are updates that need to happen after the SRW document import srw_survey_plan_update(batch_size=import_batch_size) console.log("Done") From 6718c998f3f222d94b3111d35aef3d1601cd1234 Mon Sep 17 00:00:00 2001 From: Liam Stoddard <lstodd@protonmail.com> Date: Tue, 19 Mar 2024 12:05:29 -0700 Subject: [PATCH 021/153] add SRW document description --- .../alcs_documents_to_notification_documents.py | 7 +++++-- .../sql/alcs_documents_to_notification_documents.sql | 6 ++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_notification_documents.py b/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_notification_documents.py index 609b979906..715c9496bc 100644 --- a/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_notification_documents.py +++ b/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_notification_documents.py @@ -96,7 +96,8 @@ def _compile_insert_query(number_of_rows_to_insert): oats_application_id, audit_created_by, survey_plan_number, - control_number + control_number, + description ) VALUES{documents_to_insert} ON CONFLICT (oats_document_id, oats_application_id) DO UPDATE SET @@ -106,7 +107,8 @@ def _compile_insert_query(number_of_rows_to_insert): visibility_flags = EXCLUDED.visibility_flags, audit_created_by = EXCLUDED.audit_created_by, survey_plan_number = EXCLUDED.survey_plan_number, - control_number = EXCLUDED.control_number; + control_number = EXCLUDED.control_number, + description = EXCLUDED.description; """ @@ -130,6 +132,7 @@ def _map_data(row): "audit_created_by": OATS_ETL_USER, "plan_number": row["plan_no"], "control_number": row["control_no"], + "description": row["description"], } diff --git a/bin/migrate-oats-data/documents/post_launch/sql/alcs_documents_to_notification_documents.sql b/bin/migrate-oats-data/documents/post_launch/sql/alcs_documents_to_notification_documents.sql index 126d746295..6398f0c817 100644 --- a/bin/migrate-oats-data/documents/post_launch/sql/alcs_documents_to_notification_documents.sql +++ b/bin/migrate-oats-data/documents/post_launch/sql/alcs_documents_to_notification_documents.sql @@ -7,7 +7,8 @@ with oats_documents_to_map as ( od.document_id as oats_document_id, od.alr_application_id as oats_application_id, oaa.plan_no, - oaa.control_no + oaa.control_no, + od."description" from oats.oats_documents od join alcs."document" d on d.oats_document_id = od.document_id::text join alcs.document_code adc on adc.oats_code = od.document_code @@ -29,5 +30,6 @@ select otm.notification_uuid, oats_document_id, oats_application_id, plan_no, - control_no + control_no, + otm."description" from oats_documents_to_map otm \ No newline at end of file From b0d1e505b7d06aa79209dada73130aa8b33c7b66 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Tue, 19 Mar 2024 13:37:08 -0700 Subject: [PATCH 022/153] Code Review Feedback --- .../file-type-filter-drop-down.component.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/alcs-frontend/src/app/features/search/file-type-filter-drop-down/file-type-filter-drop-down.component.ts b/alcs-frontend/src/app/features/search/file-type-filter-drop-down/file-type-filter-drop-down.component.ts index abae549fbe..ba58148b79 100644 --- a/alcs-frontend/src/app/features/search/file-type-filter-drop-down/file-type-filter-drop-down.component.ts +++ b/alcs-frontend/src/app/features/search/file-type-filter-drop-down/file-type-filter-drop-down.component.ts @@ -14,7 +14,7 @@ import { templateUrl: './file-type-filter-drop-down.component.html', styleUrls: ['./file-type-filter-drop-down.component.scss'], }) -export class FileTypeFilterDropDownComponent implements AfterViewInit { +export class FileTypeFilterDropDownComponent { hasItemsSelected = false; /** Map from flat node to nested node. This helps us finding the nested node to be modified */ flatNodeMap = new Map<FlatTreeNode, TreeNode>(); @@ -172,10 +172,6 @@ export class FileTypeFilterDropDownComponent implements AfterViewInit { this.hasItemsSelected = false; } - ngAfterViewInit(): void { - //this.treeControl.expandAll(); - } - clear() { this.checklistSelection.selected.forEach((selectedItem) => this.checklistSelection.deselect(selectedItem)); this.onChange(); From c8624444d09217e55129922ef133bfeb8ac8c9a1 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Tue, 19 Mar 2024 15:07:08 -0700 Subject: [PATCH 023/153] PR Home page fixes * Actually fetch subtasks for home page * Load card and board so we can redirect properly * Update home-page from non-Applications to Planning Referrals * Remove max height on card dialog content so it scrolls nicely --- .../home/assigned/assigned.component.html | 2 +- .../subtask-table.component.html | 6 -- .../subtask-table/subtask-table.component.ts | 1 - .../home/subtask/subtask.component.html | 4 +- alcs-frontend/src/styles.scss | 16 ++--- .../card/card-subtask/card-subtask.dto.ts | 3 +- .../src/alcs/home/home.controller.spec.ts | 13 ++++ .../alcs/src/alcs/home/home.controller.ts | 64 ++++++++++--------- .../planning-referral.service.ts | 45 ++++++++++++- .../planning-review.service.ts | 7 +- 10 files changed, 110 insertions(+), 51 deletions(-) diff --git a/alcs-frontend/src/app/features/home/assigned/assigned.component.html b/alcs-frontend/src/app/features/home/assigned/assigned.component.html index 8d84cdbcc7..093529bc02 100644 --- a/alcs-frontend/src/app/features/home/assigned/assigned.component.html +++ b/alcs-frontend/src/app/features/home/assigned/assigned.component.html @@ -12,7 +12,7 @@ <h4>Cards Assigned to Me: {{ totalFiles }}</h4> </section> <section *ngIf="planningReferrals.length"> - <div class="subheading2">Non-Applications</div> + <div class="subheading2">Planning Referrals</div> <app-assigned-table [assignedFiles]="planningReferrals"></app-assigned-table> </section> diff --git a/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.html b/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.html index 3c6e515460..768a2af105 100644 --- a/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.html +++ b/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.html @@ -30,12 +30,6 @@ [type]="RECON_TYPE_LABEL" > </app-application-type-pill> - <app-application-type-pill - [useShortLabel]="true" - *ngIf="element.parentType === 'planning-review'" - [type]="PLANNING_TYPE_LABEL" - > - </app-application-type-pill> <app-application-type-pill [useShortLabel]="true" *ngIf="element.parentType === 'notification'" diff --git a/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.ts b/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.ts index 349bb6b7db..bdd6dae485 100644 --- a/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.ts +++ b/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.ts @@ -22,7 +22,6 @@ export class SubtaskTableComponent { @Input() users: AssigneeDto[] = []; MODIFICATION_TYPE_LABEL = MODIFICATION_TYPE_LABEL; - PLANNING_TYPE_LABEL = PLANNING_TYPE_LABEL; RECON_TYPE_LABEL = RECON_TYPE_LABEL; NOTIFICATION_LABEL = NOTIFICATION_LABEL; diff --git a/alcs-frontend/src/app/features/home/subtask/subtask.component.html b/alcs-frontend/src/app/features/home/subtask/subtask.component.html index 388b546fb2..f30b734b54 100644 --- a/alcs-frontend/src/app/features/home/subtask/subtask.component.html +++ b/alcs-frontend/src/app/features/home/subtask/subtask.component.html @@ -2,7 +2,7 @@ <h4>{{ subtaskLabel }} Subtasks: {{ totalSubtaskCount }}</h4> <section *ngIf="noticeOfIntentSubtasks.length && showNoi"> - <div class="subheading2">Notice of Intent</div> + <div class="subheading2">Notices of Intent</div> <app-subtask-table [subtasks]="noticeOfIntentSubtasks" [users]="users"></app-subtask-table> </section> <section *ngIf="applicationSubtasks.length && showAppAndNonApp"> @@ -10,7 +10,7 @@ <h4>{{ subtaskLabel }} Subtasks: {{ totalSubtaskCount }}</h4> <app-subtask-table [subtasks]="applicationSubtasks" [users]="users"></app-subtask-table> </section> <section *ngIf="planningReviewSubtasks.length && showAppAndNonApp"> - <div class="subheading2">Non-Applications</div> + <div class="subheading2">Planning Reviews</div> <app-subtask-table [subtasks]="planningReviewSubtasks" [users]="users"></app-subtask-table> </section> <section *ngIf="notificationSubtasks.length"> diff --git a/alcs-frontend/src/styles.scss b/alcs-frontend/src/styles.scss index c865196401..6c3be8512c 100644 --- a/alcs-frontend/src/styles.scss +++ b/alcs-frontend/src/styles.scss @@ -132,13 +132,13 @@ $alcs-warn: mat.define-palette($mat-custom-warn); // Create the theme object. A theme consists of configurations for individual // theming systems such as "color" or "typography". $alcs-theme: mat.define-light-theme( - ( - color: ( - primary: $alcs-primary, - accent: $alcs-accent, - warn: $alcs-warn, - ), - ) + ( + color: ( + primary: $alcs-primary, + accent: $alcs-accent, + warn: $alcs-warn, + ), + ) ); // Include theme styles for core and each component used in your app. @@ -176,7 +176,7 @@ body { mat-dialog-content, div[mat-dialog-content] { flex-grow: 1; - max-height: 80vh; + max-height: unset; } } } diff --git a/services/apps/alcs/src/alcs/card/card-subtask/card-subtask.dto.ts b/services/apps/alcs/src/alcs/card/card-subtask/card-subtask.dto.ts index de1449b8a4..4f8ec51eb1 100644 --- a/services/apps/alcs/src/alcs/card/card-subtask/card-subtask.dto.ts +++ b/services/apps/alcs/src/alcs/card/card-subtask/card-subtask.dto.ts @@ -2,6 +2,7 @@ import { AutoMap } from 'automapper-classes'; import { IsOptional, IsUUID } from 'class-validator'; import { ApplicationTypeDto } from '../../code/application-code/application-type/application-type.dto'; import { AssigneeDto } from '../../../user/user.dto'; +import { PlanningReviewTypeDto } from '../../planning-review/planning-review.dto'; import { CardDto } from '../card.dto'; export enum PARENT_TYPE { @@ -58,7 +59,7 @@ export class CardSubtaskDto { export class HomepageSubtaskDTO extends CardSubtaskDto { card: CardDto; title: string; - appType?: ApplicationTypeDto; + appType?: PlanningReviewTypeDto; parentType: PARENT_TYPE; activeDays?: number; paused: boolean; 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 b4ad6e82a3..c5369c4b15 100644 --- a/services/apps/alcs/src/alcs/home/home.controller.spec.ts +++ b/services/apps/alcs/src/alcs/home/home.controller.spec.ts @@ -30,6 +30,8 @@ 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 { PlanningReferral } from '../planning-review/planning-referral/planning-referral.entity'; +import { PlanningReferralService } from '../planning-review/planning-referral/planning-referral.service'; import { HomeController } from './home.controller'; describe('HomeController', () => { @@ -42,6 +44,7 @@ describe('HomeController', () => { let mockNoticeOfIntentService: DeepMocked<NoticeOfIntentService>; let mockNoticeOfIntentModificationService: DeepMocked<NoticeOfIntentModificationService>; let mockNotificationService: DeepMocked<NotificationService>; + let mockPlanningReferralService: DeepMocked<PlanningReferralService>; beforeEach(async () => { mockApplicationService = createMock(); @@ -52,6 +55,7 @@ describe('HomeController', () => { mockNoticeOfIntentService = createMock(); mockNoticeOfIntentModificationService = createMock(); mockNotificationService = createMock(); + mockPlanningReferralService = createMock(); const module: TestingModule = await Test.createTestingModule({ imports: [ @@ -101,6 +105,10 @@ describe('HomeController', () => { provide: NotificationService, useValue: mockNotificationService, }, + { + provide: PlanningReferralService, + useValue: mockPlanningReferralService, + }, ApplicationProfile, ApplicationSubtaskProfile, UserProfile, @@ -124,6 +132,8 @@ describe('HomeController', () => { mockNoticeOfIntentModificationService.mapToDtos.mockResolvedValue([]); mockNotificationService.getBy.mockResolvedValue([]); mockNotificationService.mapToDtos.mockResolvedValue([]); + mockPlanningReferralService.getBy.mockResolvedValue([]); + mockPlanningReferralService.mapToDtos.mockResolvedValue([]); mockNoticeOfIntentService.getTimes.mockResolvedValue(new Map()); mockApplicationTimeTrackingService.fetchActiveTimes.mockResolvedValue( @@ -149,6 +159,9 @@ describe('HomeController', () => { mockNotificationService.getWithIncompleteSubtaskByType.mockResolvedValue( [], ); + mockPlanningReferralService.getWithIncompleteSubtaskByType.mockResolvedValue( + [], + ); }); it('should be defined', () => { diff --git a/services/apps/alcs/src/alcs/home/home.controller.ts b/services/apps/alcs/src/alcs/home/home.controller.ts index 1ceafe44c5..9b2397130a 100644 --- a/services/apps/alcs/src/alcs/home/home.controller.ts +++ b/services/apps/alcs/src/alcs/home/home.controller.ts @@ -36,8 +36,9 @@ import { NoticeOfIntentService } from '../notice-of-intent/notice-of-intent.serv 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 { PlanningReferral } from '../planning-review/planning-referral/planning-referral.entity'; +import { PlanningReferralService } from '../planning-review/planning-referral/planning-referral.service'; +import { PlanningReferralDto } from '../planning-review/planning-review.dto'; const HIDDEN_CARD_STATUSES = [ CARD_STATUS.CANCELLED, @@ -57,6 +58,7 @@ export class HomeController { private noticeOfIntentService: NoticeOfIntentService, private noticeOfIntentModificationService: NoticeOfIntentModificationService, private notificationService: NotificationService, + private planningReferralService: PlanningReferralService, ) {} @Get('/assigned') @@ -66,7 +68,7 @@ export class HomeController { noticeOfIntentModifications: NoticeOfIntentModificationDto[]; applications: ApplicationDto[]; reconsiderations: ApplicationReconsiderationDto[]; - planningReferrals: PlanningReviewDto[]; + planningReferrals: PlanningReferralDto[]; modifications: ApplicationModificationDto[]; notifications: NotificationDto[]; }> { @@ -86,8 +88,8 @@ export class HomeController { const reconsiderations = await this.reconsiderationService.getBy(assignedFindOptions); - // const planningReviews = - // await this.planningReviewService.getBy(assignedFindOptions); + const planningReviews = + await this.planningReferralService.getBy(assignedFindOptions); const modifications = await this.modificationService.getBy(assignedFindOptions); @@ -111,7 +113,8 @@ export class HomeController { applications: await this.applicationService.mapToDtos(applications), reconsiderations: await this.reconsiderationService.mapToDtos(reconsiderations), - planningReferrals: [], + planningReferrals: + await this.planningReferralService.mapToDtos(planningReviews), modifications: await this.modificationService.mapToDtos(modifications), notifications: await this.notificationService.mapToDtos(notifications), }; @@ -145,13 +148,13 @@ export class HomeController { ); const reconSubtasks = this.mapReconToDto(reconsiderationWithSubtasks); - // const planningReviewsWithSubtasks = - // await this.planningReviewService.getWithIncompleteSubtaskByType( - // subtaskType, - // ); - // const planningReviewSubtasks = this.mapPlanningReviewsToDtos( - // planningReviewsWithSubtasks, - // ); + const planningReferralsWithSubtasks = + await this.planningReferralService.getWithIncompleteSubtaskByType( + subtaskType, + ); + const planningReferralSubtasks = this.mapPlanningReferralsToDtos( + planningReferralsWithSubtasks, + ); const modificationsWithSubtasks = await this.modificationService.getWithIncompleteSubtaskByType( @@ -191,6 +194,7 @@ export class HomeController { ...reconSubtasks, ...modificationSubtasks, ...noiModificationsSubtasks, + ...planningReferralSubtasks, ...notificationSubtasks, ]; } @@ -251,24 +255,24 @@ export class HomeController { return result; } - private mapPlanningReviewsToDtos(planingReviews: PlanningReview[]) { + private mapPlanningReferralsToDtos(planningReferrals: PlanningReferral[]) { const result: HomepageSubtaskDTO[] = []; - // 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, - // }); - // } - // } + for (const planningReferral of planningReferrals) { + for (const subtask of planningReferral.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(planningReferral.card, Card, CardDto), + completedAt: subtask.completedAt?.getTime(), + paused: false, + title: `${planningReferral.planningReview.fileNumber} (${planningReferral.planningReview.documentName})`, + parentType: PARENT_TYPE.PLANNING_REVIEW, + appType: planningReferral.planningReview.type, + }); + } + } return result; } 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 index da903ebb1c..39faeca8d7 100644 --- 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 @@ -2,10 +2,18 @@ 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 { + FindOptions, + FindOptionsRelations, + FindOptionsWhere, + IsNull, + Not, + Repository, +} from 'typeorm'; import { formatIncomingDate } from '../../../utils/incoming-date.formatter'; import { filterUndefined } from '../../../utils/undefined'; import { Board } from '../../board/board.entity'; +import { CARD_SUBTASK_TYPE } from '../../card/card-subtask/card-subtask.dto'; import { CARD_TYPE } from '../../card/card-type/card-type.entity'; import { CardService } from '../../card/card.service'; import { @@ -33,6 +41,7 @@ export class PlanningReferralService { type: true, status: true, board: true, + assignee: true, }, planningReview: { localGovernment: true, @@ -150,4 +159,38 @@ export class PlanningReferralService { await this.referralRepository.softRemove(existingReferral); } + + async getBy(assignedFindOptions: FindOptionsWhere<PlanningReferral>) { + return this.referralRepository.find({ + where: assignedFindOptions, + relations: this.DEFAULT_RELATIONS, + }); + } + + async getWithIncompleteSubtaskByType(subtaskType: CARD_SUBTASK_TYPE) { + return this.referralRepository.find({ + where: { + card: { + subtasks: { + completedAt: IsNull(), + type: { + code: subtaskType, + }, + }, + }, + }, + relations: { + planningReview: { + type: true, + localGovernment: true, + }, + card: { + status: true, + board: true, + type: true, + subtasks: { type: true, assignee: 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 109fde16f2..4f93794003 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 @@ -101,7 +101,12 @@ export class PlanningReviewService { }, relations: { ...this.DEFAULT_RELATIONS, - referrals: true, + referrals: { + card: { + board: true, + type: true, + }, + }, }, order: { referrals: { From 0eb64c391b82616b1be83c6d08b4912e3bb7e1a0 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Tue, 19 Mar 2024 13:35:20 -0700 Subject: [PATCH 024/153] Add Review Page for Planning Reviews * Add meetings * Add evidentiary record * Update visibility flags for PR documents --- .../src/app/features/board/board.component.ts | 1 + .../planning-review-dialog.component.spec.ts | 1 + .../documents/documents.component.html | 23 +-- .../documents/documents.component.ts | 4 +- .../header/header.component.spec.ts | 1 + .../planning-review.component.ts | 7 + .../planning-review/planning-review.module.ts | 13 +- .../edit-meeting-dialog.component.html | 34 +++++ .../edit-meeting-dialog.component.scss | 6 + .../edit-meeting-dialog.component.spec.ts | 44 ++++++ .../edit-meeting-dialog.component.ts | 65 ++++++++ .../evidentiary-record.component.html | 64 ++++++++ .../evidentiary-record.component.scss | 46 ++++++ .../evidentiary-record.component.spec.ts | 31 ++++ .../evidentiary-record.component.ts | 56 +++++++ .../review/review.component.html | 12 ++ .../review/review.component.scss | 11 ++ .../review/review.component.spec.ts | 38 +++++ .../review/review.component.ts | 23 +++ .../review/schedule/schedule.html | 44 ++++++ .../review/schedule/schedule.scss | 52 +++++++ .../review/schedule/schedule.spec.ts | 57 +++++++ .../review/schedule/schedule.ts | 100 +++++++++++++ .../planning-review-meeting.dto.ts | 19 +++ .../planning-review-meeting.service.ts | 77 ++++++++++ .../planning-review/planning-review.dto.ts | 2 + .../planning-referral.service.ts | 14 +- .../planning-review-meeting-type.entity.ts | 14 ++ ...planning-review-meeting.controller.spec.ts | 126 ++++++++++++++++ .../planning-review-meeting.controller.ts | 90 +++++++++++ .../planning-review-meeting.dto.ts | 36 +++++ .../planning-review-meeting.entity.ts | 36 +++++ .../planning-review-meeting.service.spec.ts | 141 ++++++++++++++++++ .../planning-review-meeting.service.ts | 98 ++++++++++++ .../planning-review/planning-review.dto.ts | 4 + .../planning-review/planning-review.entity.ts | 8 + .../planning-review/planning-review.module.ts | 8 + .../planning-review.service.ts | 1 + .../planning-review.automapper.profile.ts | 22 +++ .../1710869779332-add_pr_meetings.ts | 43 ++++++ .../1710870743180-seed_pr_meeting_types.ts | 18 +++ 41 files changed, 1462 insertions(+), 28 deletions(-) create mode 100644 alcs-frontend/src/app/features/planning-review/review/edit-meeting-dialog/edit-meeting-dialog.component.html create mode 100644 alcs-frontend/src/app/features/planning-review/review/edit-meeting-dialog/edit-meeting-dialog.component.scss create mode 100644 alcs-frontend/src/app/features/planning-review/review/edit-meeting-dialog/edit-meeting-dialog.component.spec.ts create mode 100644 alcs-frontend/src/app/features/planning-review/review/edit-meeting-dialog/edit-meeting-dialog.component.ts create mode 100644 alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.html create mode 100644 alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.scss create mode 100644 alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.spec.ts create mode 100644 alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.ts create mode 100644 alcs-frontend/src/app/features/planning-review/review/review.component.html create mode 100644 alcs-frontend/src/app/features/planning-review/review/review.component.scss create mode 100644 alcs-frontend/src/app/features/planning-review/review/review.component.spec.ts create mode 100644 alcs-frontend/src/app/features/planning-review/review/review.component.ts create mode 100644 alcs-frontend/src/app/features/planning-review/review/schedule/schedule.html create mode 100644 alcs-frontend/src/app/features/planning-review/review/schedule/schedule.scss create mode 100644 alcs-frontend/src/app/features/planning-review/review/schedule/schedule.spec.ts create mode 100644 alcs-frontend/src/app/features/planning-review/review/schedule/schedule.ts create mode 100644 alcs-frontend/src/app/services/planning-review/planning-review-meeting/planning-review-meeting.dto.ts create mode 100644 alcs-frontend/src/app/services/planning-review/planning-review-meeting/planning-review-meeting.service.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting-type.entity.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.controller.spec.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.controller.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.dto.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.entity.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.service.spec.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.service.ts create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1710869779332-add_pr_meetings.ts create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1710870743180-seed_pr_meeting_types.ts diff --git a/alcs-frontend/src/app/features/board/board.component.ts b/alcs-frontend/src/app/features/board/board.component.ts index 2d2f0e271c..a5280ce82c 100644 --- a/alcs-frontend/src/app/features/board/board.component.ts +++ b/alcs-frontend/src/app/features/board/board.component.ts @@ -386,6 +386,7 @@ export class BoardComponent implements OnInit, OnDestroy { cardUuid: referral.card.uuid, dateReceived: referral.card.createdAt, dueDate: referral.dueDate ? new Date(referral.dueDate) : undefined, + decisionMeetings: referral.planningReview.meetings, showDueDate: true, }; } 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 cfa0e51be6..3ba75a9cca 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 @@ -43,6 +43,7 @@ describe('PlanningReviewDialogComponent', () => { isFirstNation: false, }, fileNumber: 'file-number', + meetings: [], }; const mockReferralDto: PlanningReferralDto = { diff --git a/alcs-frontend/src/app/features/planning-review/documents/documents.component.html b/alcs-frontend/src/app/features/planning-review/documents/documents.component.html index 6a55826bb8..6180359943 100644 --- a/alcs-frontend/src/app/features/planning-review/documents/documents.component.html +++ b/alcs-frontend/src/app/features/planning-review/documents/documents.component.html @@ -28,29 +28,8 @@ <h3>Documents</h3> <div class="subheading">* = Pending</div> </th> <td mat-cell *matCellDef="let element"> - <ng-container *ngIf="element.visibilityFlags.includes('A')"> - <span matTooltip="Applicant">A<span *ngIf="hiddenFromPortal">*</span></span> - <ng-container - *ngIf=" - element.visibilityFlags.includes('C') || - element.visibilityFlags.includes('G') || - element.visibilityFlags.includes('P') - " - >, - </ng-container> - </ng-container> - <ng-container *ngIf="element.visibilityFlags.includes('C') && !element.visibilityFlags.includes('A')"> + <ng-container *ngIf="element.visibilityFlags.includes('C')"> <span matTooltip="Commissioner">C<span *ngIf="!hasBeenSetForDiscussion">*</span></span> - <ng-container *ngIf="element.visibilityFlags.includes('G') || element.visibilityFlags.includes('P')" - >, - </ng-container> - </ng-container> - <ng-container *ngIf="element.visibilityFlags.includes('G')"> - <span matTooltip="L/FNG">G<span *ngIf="hiddenFromPortal">*</span></span> - <ng-container *ngIf="element.visibilityFlags.includes('P')">, </ng-container> - </ng-container> - <ng-container *ngIf="element.visibilityFlags.includes('P')"> - <span matTooltip="Public">P<span *ngIf="hiddenFromPortal || !hasBeenReceived">*</span></span> </ng-container> </td> </ng-container> diff --git a/alcs-frontend/src/app/features/planning-review/documents/documents.component.ts b/alcs-frontend/src/app/features/planning-review/documents/documents.component.ts index be582c6df5..87b4a31d79 100644 --- a/alcs-frontend/src/app/features/planning-review/documents/documents.component.ts +++ b/alcs-frontend/src/app/features/planning-review/documents/documents.component.ts @@ -21,10 +21,7 @@ export class DocumentsComponent implements OnInit { private fileId = ''; DOCUMENT_SYSTEM = DOCUMENT_SYSTEM; - - hasBeenReceived = false; hasBeenSetForDiscussion = false; - hiddenFromPortal = false; @ViewChild(MatSort) sort!: MatSort; dataSource: MatTableDataSource<PlanningReviewDocumentDto> = new MatTableDataSource<PlanningReviewDocumentDto>(); @@ -42,6 +39,7 @@ export class DocumentsComponent implements OnInit { if (planningReview) { this.fileId = planningReview.fileNumber; this.loadDocuments(planningReview.fileNumber); + this.hasBeenSetForDiscussion = planningReview.meetings.length > 0; } }); } diff --git a/alcs-frontend/src/app/features/planning-review/header/header.component.spec.ts b/alcs-frontend/src/app/features/planning-review/header/header.component.spec.ts index 89016c03ba..f8970f5495 100644 --- a/alcs-frontend/src/app/features/planning-review/header/header.component.spec.ts +++ b/alcs-frontend/src/app/features/planning-review/header/header.component.spec.ts @@ -45,6 +45,7 @@ describe('HeaderComponent', () => { textColor: '', }, uuid: '', + meetings: [], }; fixture.detectChanges(); diff --git a/alcs-frontend/src/app/features/planning-review/planning-review.component.ts b/alcs-frontend/src/app/features/planning-review/planning-review.component.ts index 5e12f6f237..da8a8af4b6 100644 --- a/alcs-frontend/src/app/features/planning-review/planning-review.component.ts +++ b/alcs-frontend/src/app/features/planning-review/planning-review.component.ts @@ -7,6 +7,7 @@ import { decisionChildRoutes, DecisionModule } from './decision/decision.module' import { DocumentsComponent } from './documents/documents.component'; import { OverviewComponent } from './overview/overview.component'; import { ReferralComponent } from './referrals/referral.component'; +import { ReviewComponent } from './review/review.component'; export const childRoutes = [ { @@ -21,6 +22,12 @@ export const childRoutes = [ icon: 'edit_note', component: ReferralComponent, }, + { + path: 'review', + menuTitle: 'Review', + icon: 'rate_review', + component: ReviewComponent, + }, { path: 'decision', menuTitle: 'Decisions', diff --git a/alcs-frontend/src/app/features/planning-review/planning-review.module.ts b/alcs-frontend/src/app/features/planning-review/planning-review.module.ts index 2590d6c981..a1b478b956 100644 --- a/alcs-frontend/src/app/features/planning-review/planning-review.module.ts +++ b/alcs-frontend/src/app/features/planning-review/planning-review.module.ts @@ -1,5 +1,6 @@ -import { NgModule } from '@angular/core'; +import { CdkDrag, CdkDropList } from '@angular/cdk/drag-drop'; import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { PlanningReviewDetailService } from '../../services/planning-review/planning-review-detail.service'; import { SharedModule } from '../../shared/shared.module'; @@ -10,6 +11,10 @@ import { OverviewComponent } from './overview/overview.component'; import { childRoutes, PlanningReviewComponent } from './planning-review.component'; import { CreatePlanningReferralDialogComponent } from './referrals/create/create-planning-referral-dialog.component'; import { ReferralComponent } from './referrals/referral.component'; +import { EditMeetingDialogComponent } from './review/edit-meeting-dialog/edit-meeting-dialog.component'; +import { EvidentiaryRecordComponent } from './review/evidentiary-record/evidentiary-record.component'; +import { ReviewComponent } from './review/review.component'; +import { ScheduleComponent } from './review/schedule/schedule'; const routes: Routes = [ { @@ -29,7 +34,11 @@ const routes: Routes = [ DocumentUploadDialogComponent, ReferralComponent, CreatePlanningReferralDialogComponent, + EvidentiaryRecordComponent, + ReviewComponent, + ScheduleComponent, + EditMeetingDialogComponent, ], - imports: [CommonModule, SharedModule, RouterModule.forChild(routes)], + imports: [CommonModule, SharedModule, RouterModule.forChild(routes), CdkDropList, CdkDrag], }) export class PlanningReviewModule {} diff --git a/alcs-frontend/src/app/features/planning-review/review/edit-meeting-dialog/edit-meeting-dialog.component.html b/alcs-frontend/src/app/features/planning-review/review/edit-meeting-dialog/edit-meeting-dialog.component.html new file mode 100644 index 0000000000..b812f7d957 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/review/edit-meeting-dialog/edit-meeting-dialog.component.html @@ -0,0 +1,34 @@ +<div mat-dialog-title> + <h2 class="card-title">{{ data.existingMeeting ? 'Edit Scheduled Date' : 'Add Date to Schedule' }}</h2> +</div> +<form (ngSubmit)="onSubmit()" #meetingForm="ngForm"> + <mat-dialog-content class="form"> + <mat-form-field appearance="outline"> + <mat-label>Date</mat-label> + <input + matInput + (click)="datePicker.open()" + [matDatepicker]="datePicker" + [(ngModel)]="selectedDate" + name="date" + required + /> + <mat-datepicker-toggle matSuffix [for]="datePicker"></mat-datepicker-toggle> + <mat-datepicker #datePicker type="date"></mat-datepicker> + </mat-form-field> + <mat-form-field appearance="outline"> + <mat-label>Type</mat-label> + <mat-select [required]="true" name="type" [(ngModel)]="selectedType"> + <mat-option *ngFor="let type of types" [value]="type.code">{{ type.label }}</mat-option> + </mat-select> + </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">Close</button> + <button [loading]="isLoading" mat-flat-button color="primary" type="submit" [disabled]="!meetingForm.form.valid"> + Save + </button> + </div> + </mat-dialog-actions> +</form> diff --git a/alcs-frontend/src/app/features/planning-review/review/edit-meeting-dialog/edit-meeting-dialog.component.scss b/alcs-frontend/src/app/features/planning-review/review/edit-meeting-dialog/edit-meeting-dialog.component.scss new file mode 100644 index 0000000000..9666af8c0a --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/review/edit-meeting-dialog/edit-meeting-dialog.component.scss @@ -0,0 +1,6 @@ +.form { + width: 100%; + display: grid; + grid-template-columns: calc(50% - 12px) calc(50% - 12px); + grid-column-gap: 24px; +} diff --git a/alcs-frontend/src/app/features/planning-review/review/edit-meeting-dialog/edit-meeting-dialog.component.spec.ts b/alcs-frontend/src/app/features/planning-review/review/edit-meeting-dialog/edit-meeting-dialog.component.spec.ts new file mode 100644 index 0000000000..6d89d5067d --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/review/edit-meeting-dialog/edit-meeting-dialog.component.spec.ts @@ -0,0 +1,44 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; +import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { PlanningReviewMeetingService } from '../../../../services/planning-review/planning-review-meeting/planning-review-meeting.service'; +import { MomentPipe } from '../../../../shared/pipes/moment.pipe'; + +import { EditMeetingDialogComponent } from './edit-meeting-dialog.component'; + +describe('EditMeetingDialogComponent', () => { + let component: EditMeetingDialogComponent; + let fixture: ComponentFixture<EditMeetingDialogComponent>; + let mockPlanningReviewMeetingService: DeepMocked<PlanningReviewMeetingService>; + + beforeEach(async () => { + mockPlanningReviewMeetingService = createMock(); + mockPlanningReviewMeetingService.fetchTypes.mockResolvedValue([]); + + await TestBed.configureTestingModule({ + declarations: [EditMeetingDialogComponent, MomentPipe], + providers: [ + { + provide: PlanningReviewMeetingService, + useValue: mockPlanningReviewMeetingService, + }, + { provide: MAT_DIALOG_DATA, useValue: {} }, + { provide: MatDialogRef, useValue: {} }, + ], + imports: [HttpClientTestingModule, MatDialogModule, MatSnackBarModule, FormsModule], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(EditMeetingDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alcs-frontend/src/app/features/planning-review/review/edit-meeting-dialog/edit-meeting-dialog.component.ts b/alcs-frontend/src/app/features/planning-review/review/edit-meeting-dialog/edit-meeting-dialog.component.ts new file mode 100644 index 0000000000..f456224eaa --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/review/edit-meeting-dialog/edit-meeting-dialog.component.ts @@ -0,0 +1,65 @@ +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { ApplicationDecisionMeetingService } from '../../../../services/application/application-decision-meeting/application-decision-meeting.service'; +import { ApplicationDetailService } from '../../../../services/application/application-detail.service'; +import { PlanningReviewDetailService } from '../../../../services/planning-review/planning-review-detail.service'; +import { + PlanningReviewMeetingDto, + PlanningReviewMeetingTypeDto, + UpdatePlanningReviewMeetingDto, +} from '../../../../services/planning-review/planning-review-meeting/planning-review-meeting.dto'; +import { PlanningReviewMeetingService } from '../../../../services/planning-review/planning-review-meeting/planning-review-meeting.service'; +import { ToastService } from '../../../../services/toast/toast.service'; +import { formatDateForApi } from '../../../../shared/utils/api-date-formatter'; + +@Component({ + selector: 'app-planning-review-meeting-dialog', + templateUrl: './edit-meeting-dialog.component.html', + styleUrls: ['./edit-meeting-dialog.component.scss'], +}) +export class EditMeetingDialogComponent { + isLoading = false; + types: PlanningReviewMeetingTypeDto[] = []; + selectedDate: Date | undefined; + selectedType: string | undefined; + + constructor( + @Inject(MAT_DIALOG_DATA) + public data: { + planningReviewUuid: string; + existingMeeting?: PlanningReviewMeetingDto; + }, + private dialogRef: MatDialogRef<EditMeetingDialogComponent>, + private planningReviewMeetingService: PlanningReviewMeetingService, + ) { + this.loadTypes(); + + if (data.existingMeeting) { + this.selectedType = data.existingMeeting.type.code; + this.selectedDate = new Date(data.existingMeeting.date); + } + } + + async onSubmit() { + const updateDto: UpdatePlanningReviewMeetingDto = { + date: formatDateForApi(this.selectedDate!), + typeCode: this.selectedType!, + }; + if (this.data.existingMeeting) { + await this.planningReviewMeetingService.update(this.data.existingMeeting.uuid, updateDto); + } else { + await this.planningReviewMeetingService.create({ + planningReviewUuid: this.data.planningReviewUuid, + ...updateDto, + }); + } + this.dialogRef.close(true); + } + + private async loadTypes() { + const types = await this.planningReviewMeetingService.fetchTypes(); + if (types) { + this.types = types; + } + } +} diff --git a/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.html b/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.html new file mode 100644 index 0000000000..c695d1e2c1 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.html @@ -0,0 +1,64 @@ +<div class="table-header"> + <div> + <h4>{{ tableTitle }}</h4> + </div> +</div> + +<table + cdkDropList + [cdkDropListDisabled]='!sortable' + (cdkDropListDropped)="onRowDropped($event)" + mat-table + [dataSource]="dataSource" + class="mat-elevation-z3 width-100" +> + <ng-container matColumnDef="type"> + <th mat-header-cell *matHeaderCellDef>Type</th> + <td mat-cell *matCellDef="let element">{{ element.type?.label }}</td> + </ng-container> + + <ng-container matColumnDef="fileName"> + <th mat-header-cell *matHeaderCellDef>File Name</th> + <td mat-cell *matCellDef="let element"> + <a (click)="onOpen(element.uuid, element.fileName)">{{ element.fileName }}</a> + </td> + </ng-container> + + <ng-container matColumnDef="source"> + <th mat-header-cell *matHeaderCellDef>Source</th> + <td mat-cell *matCellDef="let element">{{ element.source }}</td> + </ng-container> + + <ng-container matColumnDef="uploadedAt"> + <th mat-header-cell *matHeaderCellDef>Upload Date</th> + <td mat-cell *matCellDef="let element">{{ element.uploadedAt | momentFormat }}</td> + </ng-container> + + <ng-container matColumnDef="action"> + <th mat-header-cell *matHeaderCellDef>Action</th> + <td mat-cell *matCellDef="let element"> + <button title="Download" mat-icon-button class="action-btn" (click)="onDownload(element.uuid, element.fileName)"> + <mat-icon>download</mat-icon> + </button> + </td> + </ng-container> + + <ng-container matColumnDef="sorting"> + <th mat-header-cell *matHeaderCellDef></th> + <td class='drag-cell' mat-cell *matCellDef="let element"> + <mat-icon>drag_indicator</mat-icon> + </td> + </ng-container> + + <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> + <tr + style="background-color: #fff" + mat-row + cdkDrag + cdkDragLockAxis="y" + *matRowDef="let row; columns: displayedColumns" + ></tr> + <tr class="mat-row" *matNoDataRow> + <td class="text-center" colspan="4">No files</td> + </tr> +</table> diff --git a/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.scss b/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.scss new file mode 100644 index 0000000000..117b2949ea --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.scss @@ -0,0 +1,46 @@ +@use '../../../../../styles/colors'; + +.action-btn.delete { + color: colors.$error-color; +} + +.table-header { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; +} + +.width-100 { + width: 100%; +} + +.mat-column-action { + width: 8%; + text-align: right; +} + +a { + font-size: 14px !important; + cursor: pointer; +} + +:host::ng-deep { + .mat-spinner circle { + stroke: #fff; + } +} + +.drag-cell { + cursor: grab; + + &:active { + cursor: grabbing; + } +} + +.cdk-drag-placeholder, .cdk-drag-preview { + background-color: colors.$grey-light !important; +} diff --git a/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.spec.ts b/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.spec.ts new file mode 100644 index 0000000000..82cae94ac5 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.spec.ts @@ -0,0 +1,31 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { PlanningReviewDocumentService } from '../../../../services/planning-review/planning-review-document/planning-review-document.service'; + +import { EvidentiaryRecordComponent } from './evidentiary-record.component'; + +describe('EvidentiaryRecordComponent', () => { + let component: EvidentiaryRecordComponent; + let fixture: ComponentFixture<EvidentiaryRecordComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + providers: [ + { + provide: PlanningReviewDocumentService, + useValue: {}, + }, + ], + declarations: [EvidentiaryRecordComponent], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(EvidentiaryRecordComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.ts b/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.ts new file mode 100644 index 0000000000..04e7588ff2 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.ts @@ -0,0 +1,56 @@ +import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; +import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { MatTableDataSource } from '@angular/material/table'; +import { PlanningReviewDocumentDto } from '../../../../services/planning-review/planning-review-document/planning-review-document.dto'; +import { PlanningReviewDocumentService } from '../../../../services/planning-review/planning-review-document/planning-review-document.service'; + +@Component({ + selector: 'app-evidentiary-record[tableTitle][fileNumber][visibilityFlags]', + templateUrl: './evidentiary-record.component.html', + styleUrls: ['./evidentiary-record.component.scss'], +}) +export class EvidentiaryRecordComponent implements OnChanges { + @Input() tableTitle = ''; + @Input() fileNumber: string = ''; + @Input() visibilityFlags: string[] = []; + @Input() sortable = false; + + displayedColumns: string[] = ['type', 'fileName', 'source', 'uploadedAt', 'action', 'sorting']; + documents: PlanningReviewDocumentDto[] = []; + dataSource = new MatTableDataSource<PlanningReviewDocumentDto>([]); + + constructor(private planningReviewDocumentService: PlanningReviewDocumentService) {} + + ngOnChanges(): void { + this.loadDocuments(); + if (!this.sortable) { + this.displayedColumns = ['type', 'fileName', 'source', 'uploadedAt', 'action']; + } + } + + async loadDocuments() { + if (this.fileNumber) { + this.documents = await this.planningReviewDocumentService.listByVisibility(this.fileNumber, this.visibilityFlags); + this.documents.sort((a, b) => (a.evidentiaryRecordSorting ?? 9999) - (b.evidentiaryRecordSorting ?? 9999)); + this.dataSource.data = this.documents; + } + } + + async onDownload(uuid: string, fileName: string) { + await this.planningReviewDocumentService.download(uuid, fileName, false); + } + + async onOpen(uuid: string, fileName: string) { + await this.planningReviewDocumentService.download(uuid, fileName); + } + + async onRowDropped(event: CdkDragDrop<PlanningReviewDocumentDto, any>) { + moveItemInArray(this.documents, event.previousIndex, event.currentIndex); + const order = this.documents.map((doc, index) => ({ + uuid: doc.uuid, + order: index, + })); + this.dataSource.data = this.documents; + this.planningReviewDocumentService.updateSort(order); + } +} diff --git a/alcs-frontend/src/app/features/planning-review/review/review.component.html b/alcs-frontend/src/app/features/planning-review/review/review.component.html new file mode 100644 index 0000000000..4b3365ecf6 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/review/review.component.html @@ -0,0 +1,12 @@ +<h3>Review</h3> +<section> + <app-planning-review-schedule> </app-planning-review-schedule> +</section> +<section> + <app-evidentiary-record + [fileNumber]="fileNumber" + [visibilityFlags]="['C']" + [sortable]="true" + tableTitle="Evidentiary Record" + ></app-evidentiary-record> +</section> diff --git a/alcs-frontend/src/app/features/planning-review/review/review.component.scss b/alcs-frontend/src/app/features/planning-review/review/review.component.scss new file mode 100644 index 0000000000..4506d79fb1 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/review/review.component.scss @@ -0,0 +1,11 @@ +section { + margin-bottom: 64px; +} + +.notification-date { + margin: 32px 0; +} + +.subheading1 { + margin-bottom: 8px !important; +} diff --git a/alcs-frontend/src/app/features/planning-review/review/review.component.spec.ts b/alcs-frontend/src/app/features/planning-review/review/review.component.spec.ts new file mode 100644 index 0000000000..4ab6b24e67 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/review/review.component.spec.ts @@ -0,0 +1,38 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { BehaviorSubject } from 'rxjs'; +import { PlanningReviewDetailService } from '../../../services/planning-review/planning-review-detail.service'; +import { PlanningReviewDetailedDto } from '../../../services/planning-review/planning-review.dto'; + +import { ReviewComponent } from './review.component'; + +describe('ReviewComponent', () => { + let component: ReviewComponent; + let fixture: ComponentFixture<ReviewComponent>; + let mockPRDetailService: DeepMocked<PlanningReviewDetailService>; + + beforeEach(async () => { + mockPRDetailService = createMock(); + mockPRDetailService.$planningReview = new BehaviorSubject<PlanningReviewDetailedDto | undefined>(undefined); + + await TestBed.configureTestingModule({ + providers: [ + { + provide: PlanningReviewDetailService, + useValue: mockPRDetailService, + }, + ], + declarations: [ReviewComponent], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(ReviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alcs-frontend/src/app/features/planning-review/review/review.component.ts b/alcs-frontend/src/app/features/planning-review/review/review.component.ts new file mode 100644 index 0000000000..fdac49c5b1 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/review/review.component.ts @@ -0,0 +1,23 @@ +import { Component, OnInit } from '@angular/core'; +import { PlanningReviewDetailService } from '../../../services/planning-review/planning-review-detail.service'; +import { DOCUMENT_TYPE } from '../../../shared/document/document.dto'; + +@Component({ + selector: 'app-review', + templateUrl: './review.component.html', + styleUrls: ['./review.component.scss'], +}) +export class ReviewComponent implements OnInit { + fileNumber: string = ''; + DOCUMENT_TYPE = DOCUMENT_TYPE; + + constructor(private planningReviewDetailService: PlanningReviewDetailService) {} + + ngOnInit(): void { + this.planningReviewDetailService.$planningReview.subscribe((planningReview) => { + if (planningReview) { + this.fileNumber = planningReview.fileNumber; + } + }); + } +} diff --git a/alcs-frontend/src/app/features/planning-review/review/schedule/schedule.html b/alcs-frontend/src/app/features/planning-review/review/schedule/schedule.html new file mode 100644 index 0000000000..375476d220 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/review/schedule/schedule.html @@ -0,0 +1,44 @@ +<br /> + +<div class="flex-container"> + <div class="col-40"> + <div class="label-wrapper"> + <h4>Schedule</h4> + </div> + </div> + <div class="pull-right"> + <button mat-flat-button color="primary" class="pull-right" (click)="onCreate()">+ Date</button> + </div> +</div> + +<br /> + +<table mat-table [dataSource]="meetings" class="mat-elevation-z3 width-100"> + <ng-container matColumnDef="date"> + <th mat-header-cell *matHeaderCellDef>Date</th> + <td mat-cell *matCellDef="let element">{{ element.date | momentFormat }}</td> + </ng-container> + + <ng-container matColumnDef="type"> + <th mat-header-cell *matHeaderCellDef>Type</th> + <td mat-cell *matCellDef="let element">{{ element.type.label }}</td> + </ng-container> + + <ng-container matColumnDef="action"> + <th mat-header-cell *matHeaderCellDef>Action</th> + <td mat-cell *matCellDef="let element"> + <button mat-icon-button class="action-btn" (click)="onEdit(element)"> + <mat-icon>edit</mat-icon> + </button> + <button mat-icon-button class="action-btn delete" (click)="onDelete(element.uuid)"> + <mat-icon>delete</mat-icon> + </button> + </td> + </ng-container> + + <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> + <tr mat-row *matRowDef="let row; columns: displayedColumns"></tr> + <tr class="mat-row" *matNoDataRow> + <td class="text-center" colspan="3">No data</td> + </tr> +</table> diff --git a/alcs-frontend/src/app/features/planning-review/review/schedule/schedule.scss b/alcs-frontend/src/app/features/planning-review/review/schedule/schedule.scss new file mode 100644 index 0000000000..c4b9b5fafb --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/review/schedule/schedule.scss @@ -0,0 +1,52 @@ +@use '../../../../../styles/colors'; + +.action-btn.edit { + color: colors.$error-color; +} + +.action-btn.delete { + color: colors.$error-color; +} + +:host::ng-deep { + .mat-mdc-icon-button .mat-mdc-button-touch-target { + width: 47px; + } +} + +.flex-container { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + + .col-40 { + width: 40%; + } + + .pull-right { + float: right; + } +} + +.width-100 { + width: 100%; +} + +.mat-column-action { + width: 21%; + text-align: right; +} + +.icon { + margin-top: 4px; + height: 20px; + width: 20px; + font-size: 20px; +} + +.label-wrapper { + display: flex; + gap: 8px; +} diff --git a/alcs-frontend/src/app/features/planning-review/review/schedule/schedule.spec.ts b/alcs-frontend/src/app/features/planning-review/review/schedule/schedule.spec.ts new file mode 100644 index 0000000000..1bf3a89dfa --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/review/schedule/schedule.spec.ts @@ -0,0 +1,57 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { BehaviorSubject } from 'rxjs'; +import { ApplicationDetailService } from '../../../../services/application/application-detail.service'; +import { PlanningReviewDetailService } from '../../../../services/planning-review/planning-review-detail.service'; +import { PlanningReviewMeetingService } from '../../../../services/planning-review/planning-review-meeting/planning-review-meeting.service'; +import { PlanningReviewDetailedDto } from '../../../../services/planning-review/planning-review.dto'; +import { ConfirmationDialogService } from '../../../../shared/confirmation-dialog/confirmation-dialog.service'; + +import { ScheduleComponent } from './schedule'; + +describe('ScheduleComponent', () => { + let component: ScheduleComponent; + let fixture: ComponentFixture<ScheduleComponent>; + let mockPRDetailService: DeepMocked<PlanningReviewDetailService>; + + beforeEach(async () => { + mockPRDetailService = createMock(); + mockPRDetailService.$planningReview = new BehaviorSubject<PlanningReviewDetailedDto | undefined>(undefined); + + await TestBed.configureTestingModule({ + declarations: [ScheduleComponent], + providers: [ + { + provide: ConfirmationDialogService, + useValue: {}, + }, + { + provide: PlanningReviewMeetingService, + useValue: {}, + }, + { + provide: PlanningReviewDetailService, + useValue: mockPRDetailService, + }, + { + provide: MatDialog, + useValue: {}, + }, + ], + imports: [HttpClientTestingModule, MatSnackBarModule], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(ScheduleComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alcs-frontend/src/app/features/planning-review/review/schedule/schedule.ts b/alcs-frontend/src/app/features/planning-review/review/schedule/schedule.ts new file mode 100644 index 0000000000..f01a5681c7 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/review/schedule/schedule.ts @@ -0,0 +1,100 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { Subject, takeUntil } from 'rxjs'; +import { PlanningReviewDetailService } from '../../../../services/planning-review/planning-review-detail.service'; +import { PlanningReviewMeetingDto } from '../../../../services/planning-review/planning-review-meeting/planning-review-meeting.dto'; +import { PlanningReviewMeetingService } from '../../../../services/planning-review/planning-review-meeting/planning-review-meeting.service'; +import { ConfirmationDialogService } from '../../../../shared/confirmation-dialog/confirmation-dialog.service'; +import { EditMeetingDialogComponent } from '../edit-meeting-dialog/edit-meeting-dialog.component'; + +@Component({ + selector: 'app-planning-review-schedule', + templateUrl: './schedule.html', + styleUrls: ['./schedule.scss'], +}) +export class ScheduleComponent implements OnInit, OnDestroy { + $destroy = new Subject<void>(); + + displayedColumns: string[] = ['date', 'type', 'action']; + meetings: PlanningReviewMeetingDto[] = []; + planningReviewUuid: string | undefined; + + constructor( + public dialog: MatDialog, + private decisionMeetingService: PlanningReviewMeetingService, + private confirmationDialogService: ConfirmationDialogService, + private planningReviewDetailService: PlanningReviewDetailService, + ) {} + + ngOnInit(): void { + this.planningReviewDetailService.$planningReview.pipe(takeUntil(this.$destroy)).subscribe((planningReview) => { + if (planningReview) { + this.planningReviewUuid = planningReview.uuid; + this.loadMeetings(); + } + }); + } + + async onCreate() { + this.dialog + .open(EditMeetingDialogComponent, { + minWidth: '600px', + maxWidth: '800px', + width: '70%', + data: { + planningReviewUuid: this.planningReviewUuid, + }, + }) + .beforeClosed() + .subscribe((didCreate) => { + if (didCreate) { + this.loadMeetings(); + } + }); + } + + async onEdit(meeting: PlanningReviewMeetingDto) { + this.dialog + .open(EditMeetingDialogComponent, { + minWidth: '600px', + maxWidth: '800px', + width: '70%', + data: { + planningReviewUuid: this.planningReviewUuid, + existingMeeting: meeting, + }, + }) + .beforeClosed() + .subscribe((didEdit) => { + if (didEdit) { + this.loadMeetings(); + } + }); + } + + async onDelete(uuid: string) { + this.confirmationDialogService + .openDialog({ + body: 'Are you sure you want to delete this meeting', + }) + .subscribe((didConfirm) => { + if (didConfirm) { + this.decisionMeetingService.delete(uuid).then(() => { + this.loadMeetings(); + }); + } + }); + } + + ngOnDestroy(): void { + this.$destroy.next(); + this.$destroy.complete(); + } + + private async loadMeetings() { + if (this.planningReviewUuid) { + const meetings = await this.decisionMeetingService.listByPlanningReview(this.planningReviewUuid); + this.meetings = meetings ?? []; + } + } +} diff --git a/alcs-frontend/src/app/services/planning-review/planning-review-meeting/planning-review-meeting.dto.ts b/alcs-frontend/src/app/services/planning-review/planning-review-meeting/planning-review-meeting.dto.ts new file mode 100644 index 0000000000..5ebc764baa --- /dev/null +++ b/alcs-frontend/src/app/services/planning-review/planning-review-meeting/planning-review-meeting.dto.ts @@ -0,0 +1,19 @@ +import { BaseCodeDto } from '../../../shared/dto/base.dto'; + +export interface PlanningReviewMeetingTypeDto extends BaseCodeDto {} + +export interface PlanningReviewMeetingDto { + uuid: string; + type: PlanningReviewMeetingTypeDto; + date: number; + planningReviewUuid: string; +} + +export interface UpdatePlanningReviewMeetingDto { + typeCode: string; + date: number; +} + +export interface CreatePlanningReviewMeetingDto extends UpdatePlanningReviewMeetingDto { + planningReviewUuid: string; +} diff --git a/alcs-frontend/src/app/services/planning-review/planning-review-meeting/planning-review-meeting.service.ts b/alcs-frontend/src/app/services/planning-review/planning-review-meeting/planning-review-meeting.service.ts new file mode 100644 index 0000000000..8a34b04313 --- /dev/null +++ b/alcs-frontend/src/app/services/planning-review/planning-review-meeting/planning-review-meeting.service.ts @@ -0,0 +1,77 @@ +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 { + CreatePlanningReviewMeetingDto, + PlanningReviewMeetingDto, + PlanningReviewMeetingTypeDto, + UpdatePlanningReviewMeetingDto, +} from './planning-review-meeting.dto'; + +@Injectable({ + providedIn: 'root', +}) +export class PlanningReviewMeetingService { + private url = `${environment.apiUrl}/planning-review-meeting`; + + constructor( + private http: HttpClient, + private toastService: ToastService, + ) {} + + async listByPlanningReview(planningReviewUuid: string) { + try { + return await firstValueFrom( + this.http.get<PlanningReviewMeetingDto[]>(`${this.url}/planning-review/${planningReviewUuid}`), + ); + } catch (err) { + console.error(err); + this.toastService.showErrorToast('Failed to fetch planning review meetings'); + } + return; + } + + async create(meeting: CreatePlanningReviewMeetingDto) { + try { + const res = await firstValueFrom(this.http.post<PlanningReviewMeetingDto>(`${this.url}`, meeting)); + this.toastService.showSuccessToast('Planning Review meeting created'); + return res; + } catch (err) { + console.error(err); + this.toastService.showErrorToast('Failed to create planning review meeting'); + } + return; + } + + async fetchTypes() { + try { + return await firstValueFrom(this.http.get<PlanningReviewMeetingTypeDto[]>(`${this.url}/types`)); + } catch (err) { + console.error(err); + this.toastService.showErrorToast('Failed to fetch planning review meeting types'); + } + return; + } + + async update(uuid: string, updateDto: UpdatePlanningReviewMeetingDto) { + try { + return await firstValueFrom(this.http.patch<PlanningReviewMeetingDto>(`${this.url}/${uuid}`, updateDto)); + } catch (err) { + console.error(err); + this.toastService.showErrorToast('Failed to update planning review meeting'); + } + return; + } + + async delete(uuid: string) { + try { + return await firstValueFrom(this.http.delete<{ success: boolean }>(`${this.url}/${uuid}`)); + } catch (err) { + console.error(err); + this.toastService.showErrorToast('Failed to delete planning review meeting'); + } + 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 b29e725e77..1cbf6d2c66 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 @@ -2,6 +2,7 @@ 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'; +import { PlanningReviewMeetingDto } from './planning-review-meeting/planning-review-meeting.dto'; export interface CreatePlanningReviewDto { description: string; @@ -23,6 +24,7 @@ export interface PlanningReviewDto { region: ApplicationRegionDto; type: PlanningReviewTypeDto; documentName: string; + meetings: PlanningReviewMeetingDto[]; } export interface PlanningReviewDetailedDto extends PlanningReviewDto { 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 index 39faeca8d7..fbd38d9205 100644 --- 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 @@ -57,7 +57,19 @@ export class PlanningReferralService { boardUuid, }, }, - relations: this.DEFAULT_RELATIONS, + relations: { + card: { + type: true, + status: true, + board: true, + }, + planningReview: { + localGovernment: true, + region: true, + type: true, + meetings: true, + }, + }, }); } diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting-type.entity.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting-type.entity.ts new file mode 100644 index 0000000000..f075e92d62 --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting-type.entity.ts @@ -0,0 +1,14 @@ +import { Entity } from 'typeorm'; +import { BaseCodeEntity } from '../../../common/entities/base.code.entity'; + +@Entity({ + comment: 'Meetings Types for Planning Review Meetings', +}) +export class PlanningReviewMeetingType extends BaseCodeEntity { + constructor(data?: Partial<PlanningReviewMeetingType>) { + super(); + if (data) { + Object.assign(this, data); + } + } +} diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.controller.spec.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.controller.spec.ts new file mode 100644 index 0000000000..e79a433381 --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.controller.spec.ts @@ -0,0 +1,126 @@ +import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; +import { Test } from '@nestjs/testing'; +import { classes } from 'automapper-classes'; +import { AutomapperModule } from 'automapper-nestjs'; +import { PlanningReviewProfile } from '../../../common/automapper/planning-review.automapper.profile'; +import { PlanningReviewMeetingType } from './planning-review-meeting-type.entity'; +import { PlanningReviewMeetingController } from './planning-review-meeting.controller'; +import { + CreatePlanningReviewMeetingDto, + UpdatePlanningReviewMeetingDto, +} from './planning-review-meeting.dto'; +import { PlanningReviewMeeting } from './planning-review-meeting.entity'; +import { PlanningReviewMeetingService } from './planning-review-meeting.service'; + +describe('PlanningReviewMeetingController', () => { + let controller: PlanningReviewMeetingController; + let mockPlanningReviewMeetingService: DeepMocked<PlanningReviewMeetingService>; + + beforeEach(async () => { + mockPlanningReviewMeetingService = createMock(); + + const module = await Test.createTestingModule({ + imports: [ + AutomapperModule.forRoot({ + strategyInitializer: classes(), + }), + ], + controllers: [PlanningReviewMeetingController], + providers: [ + PlanningReviewProfile, + { + provide: PlanningReviewMeetingService, + useValue: mockPlanningReviewMeetingService, + }, + ], + }).compile(); + + controller = module.get(PlanningReviewMeetingController); + }); + + it('should call through to service for getByPlanningReview', async () => { + mockPlanningReviewMeetingService.getByPlanningReview.mockResolvedValue([]); + + await controller.findAllByPlanningReview('uuid'); + + expect( + mockPlanningReviewMeetingService.getByPlanningReview, + ).toHaveBeenCalledTimes(1); + expect( + mockPlanningReviewMeetingService.getByPlanningReview, + ).toHaveBeenCalledWith('uuid'); + }); + + it('should call through to service for findOne', async () => { + mockPlanningReviewMeetingService.getByUuid.mockResolvedValue( + new PlanningReviewMeeting({ + date: new Date(), + }), + ); + + await controller.findOne('uuid'); + + expect(mockPlanningReviewMeetingService.getByUuid).toHaveBeenCalledTimes(1); + expect(mockPlanningReviewMeetingService.getByUuid).toHaveBeenCalledWith( + 'uuid', + ); + }); + + it('should call through to service for listTypes', async () => { + mockPlanningReviewMeetingService.listTypes.mockResolvedValue([ + new PlanningReviewMeetingType(), + ]); + + const res = await controller.listTypes(); + + expect(mockPlanningReviewMeetingService.listTypes).toHaveBeenCalledTimes(1); + expect(res.length).toEqual(1); + }); + + it('should call through to service for create', async () => { + mockPlanningReviewMeetingService.create.mockResolvedValue( + new PlanningReviewMeeting({ + date: new Date(), + }), + ); + const createDto: CreatePlanningReviewMeetingDto = { + date: 0, + planningReviewUuid: '', + typeCode: '', + }; + + await controller.create(createDto); + + expect(mockPlanningReviewMeetingService.create).toHaveBeenCalledTimes(1); + expect(mockPlanningReviewMeetingService.create).toHaveBeenCalledWith( + createDto, + ); + }); + + it('should call through to service for update', async () => { + mockPlanningReviewMeetingService.update.mockResolvedValue(); + const updateDto: UpdatePlanningReviewMeetingDto = { + date: 5, + typeCode: 'new-type', + }; + + await controller.update('uuid', updateDto); + + expect(mockPlanningReviewMeetingService.update).toHaveBeenCalledTimes(1); + expect(mockPlanningReviewMeetingService.update).toHaveBeenCalledWith( + 'uuid', + updateDto, + ); + }); + + it('should call through to service for remove', async () => { + mockPlanningReviewMeetingService.remove.mockResolvedValue(); + + await controller.remove('uuid'); + + expect(mockPlanningReviewMeetingService.remove).toHaveBeenCalledTimes(1); + expect(mockPlanningReviewMeetingService.remove).toHaveBeenCalledWith( + 'uuid', + ); + }); +}); diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.controller.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.controller.ts new file mode 100644 index 0000000000..b5a5169711 --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.controller.ts @@ -0,0 +1,90 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + Patch, + Post, +} from '@nestjs/common'; +import { Mapper } from 'automapper-core'; +import { InjectMapper } from 'automapper-nestjs'; +import { PlanningReviewMeetingType } from './planning-review-meeting-type.entity'; +import { + CreatePlanningReviewMeetingDto, + PlanningReviewMeetingDto, + PlanningReviewMeetingTypeDto, + UpdatePlanningReviewMeetingDto, +} from './planning-review-meeting.dto'; +import { PlanningReviewMeeting } from './planning-review-meeting.entity'; +import { PlanningReviewMeetingService } from './planning-review-meeting.service'; + +@Controller('planning-review-meeting') +export class PlanningReviewMeetingController { + constructor( + private readonly service: PlanningReviewMeetingService, + @InjectMapper() private mapper: Mapper, + ) {} + + @Get('/planning-review/:uuid') + async findAllByPlanningReview( + @Param(':uuid') uuid: string, + ): Promise<PlanningReviewMeetingDto[]> { + const meetings = await this.service.getByPlanningReview(uuid); + return this.mapper.mapArray( + meetings, + PlanningReviewMeeting, + PlanningReviewMeetingDto, + ); + } + + @Get(':uuid') + async findOne( + @Param('uuid') uuid: string, + ): Promise<PlanningReviewMeetingDto> { + const meeting = await this.service.getByUuid(uuid); + return this.mapper.map( + meeting, + PlanningReviewMeeting, + PlanningReviewMeetingDto, + ); + } + + @Get('types') + async listTypes(): Promise<PlanningReviewMeetingTypeDto[]> { + const types = await this.service.listTypes(); + return this.mapper.mapArray( + types, + PlanningReviewMeetingType, + PlanningReviewMeetingTypeDto, + ); + } + + @Post() + async create( + @Body() createDto: CreatePlanningReviewMeetingDto, + ): Promise<PlanningReviewMeetingDto> { + const newMeeting = await this.service.create(createDto); + return this.mapper.map( + newMeeting, + PlanningReviewMeeting, + PlanningReviewMeetingDto, + ); + } + + @Patch(':uuid') + async update( + @Param('uuid') id: string, + @Body() updateDto: UpdatePlanningReviewMeetingDto, + ) { + await this.service.update(id, updateDto); + return { + success: true, + }; + } + + @Delete(':uuid') + remove(@Param('uuid') id: string): Promise<void> { + return this.service.remove(id); + } +} diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.dto.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.dto.ts new file mode 100644 index 0000000000..ac87548680 --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.dto.ts @@ -0,0 +1,36 @@ +import { AutoMap } from 'automapper-classes'; +import { IsNotEmpty, IsNumber, IsString, IsUUID } from 'class-validator'; +import { Column } from 'typeorm'; +import { BaseCodeDto } from '../../../common/dtos/base.dto'; + +export class PlanningReviewMeetingTypeDto extends BaseCodeDto {} + +export class PlanningReviewMeetingDto { + @AutoMap() + uuid: string; + + @AutoMap(() => PlanningReviewMeetingTypeDto) + type: PlanningReviewMeetingTypeDto; + + date: number; + + @AutoMap() + @Column({ type: 'uuid' }) + planningReviewUuid: string; +} + +export class UpdatePlanningReviewMeetingDto { + @IsString() + @IsNotEmpty() + typeCode: string; + + @IsNumber() + @IsNotEmpty() + date: number; +} + +export class CreatePlanningReviewMeetingDto extends UpdatePlanningReviewMeetingDto { + @IsUUID() + @IsNotEmpty() + planningReviewUuid: string; +} diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.entity.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.entity.ts new file mode 100644 index 0000000000..7287141c9d --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.entity.ts @@ -0,0 +1,36 @@ +import { AutoMap } from 'automapper-classes'; +import { Column, Entity, ManyToOne } from 'typeorm'; +import { Base } from '../../../common/entities/base.entity'; +import { PlanningReview } from '../planning-review.entity'; +import { PlanningReviewMeetingType } from './planning-review-meeting-type.entity'; + +@Entity({ + comment: 'Meeting schedule for Planning Reviews', +}) +export class PlanningReviewMeeting extends Base { + constructor(data?: Partial<PlanningReviewMeeting>) { + super(); + if (data) { + Object.assign(this, data); + } + } + + @AutoMap(() => PlanningReviewMeetingType) + @ManyToOne(() => PlanningReviewMeetingType, { nullable: false }) + type: PlanningReviewMeetingType; + + @Column() + typeCode: string; + + @Column({ + type: 'timestamptz', + comment: 'Date of the meeting', + }) + date: Date; + + @ManyToOne(() => PlanningReview) + planningReview: PlanningReview; + + @Column({ type: 'uuid' }) + planningReviewUuid: string; +} diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.service.spec.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.service.spec.ts new file mode 100644 index 0000000000..e41dc992ac --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.service.spec.ts @@ -0,0 +1,141 @@ +import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; +import { Test } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { PlanningReview } from '../planning-review.entity'; +import { PlanningReviewService } from '../planning-review.service'; +import { PlanningReviewMeetingType } from './planning-review-meeting-type.entity'; +import { UpdatePlanningReviewMeetingDto } from './planning-review-meeting.dto'; +import { PlanningReviewMeeting } from './planning-review-meeting.entity'; +import { PlanningReviewMeetingService } from './planning-review-meeting.service'; + +describe('PlanningReviewMeetingService', () => { + let service: PlanningReviewMeetingService; + let mockPlanningReviewMeetingRepository: DeepMocked< + Repository<PlanningReviewMeeting> + >; + let mockPlanningReviewMeetingTypeRepository: DeepMocked< + Repository<PlanningReviewMeetingType> + >; + let mockPlanningReviewService: DeepMocked<PlanningReviewService>; + + beforeEach(async () => { + mockPlanningReviewMeetingRepository = createMock(); + mockPlanningReviewMeetingTypeRepository = createMock(); + mockPlanningReviewService = createMock(); + + const module = await Test.createTestingModule({ + providers: [ + PlanningReviewMeetingService, + { + provide: getRepositoryToken(PlanningReviewMeeting), + useValue: mockPlanningReviewMeetingRepository, + }, + { + provide: getRepositoryToken(PlanningReviewMeetingType), + useValue: mockPlanningReviewMeetingTypeRepository, + }, + { + provide: PlanningReviewService, + useValue: mockPlanningReviewService, + }, + ], + }).compile(); + + service = module.get(PlanningReviewMeetingService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + it('should call find on the type repo for list types', async () => { + mockPlanningReviewMeetingTypeRepository.find.mockResolvedValue([ + new PlanningReviewMeetingType(), + ]); + + const res = await service.listTypes(); + + expect(res.length).toEqual(1); + expect(mockPlanningReviewMeetingTypeRepository.find).toHaveBeenCalledTimes( + 1, + ); + }); + + it('should call find on the repo for get by planning review', async () => { + mockPlanningReviewMeetingRepository.find.mockResolvedValue([ + new PlanningReviewMeeting(), + ]); + + const res = await service.getByPlanningReview('uuid'); + + expect(res.length).toEqual(1); + expect(mockPlanningReviewMeetingRepository.find).toHaveBeenCalledTimes(1); + }); + + it('should call findOne on the repo for get by uuid', async () => { + mockPlanningReviewMeetingRepository.findOne.mockResolvedValue( + new PlanningReviewMeeting(), + ); + + const res = await service.getByUuid('uuid'); + + expect(res).toBeDefined(); + expect(mockPlanningReviewMeetingRepository.findOne).toHaveBeenCalledTimes( + 1, + ); + }); + + it('should load the type and parent then save for create', async () => { + mockPlanningReviewService.getOrFail.mockResolvedValue(new PlanningReview()); + mockPlanningReviewMeetingTypeRepository.findOneOrFail.mockResolvedValue( + new PlanningReviewMeetingType(), + ); + + mockPlanningReviewMeetingRepository.save.mockResolvedValue( + new PlanningReviewMeeting(), + ); + + const res = await service.create({ + date: 0, + planningReviewUuid: '', + typeCode: '', + }); + + expect(res).toBeDefined(); + expect(mockPlanningReviewService.getOrFail).toHaveBeenCalledTimes(1); + expect( + mockPlanningReviewMeetingTypeRepository.findOneOrFail, + ).toHaveBeenCalledTimes(1); + expect(mockPlanningReviewMeetingRepository.save).toHaveBeenCalledTimes(1); + }); + + it('should load the type then save for update', async () => { + const mockResult = new PlanningReviewMeeting(); + const mockUpdate: UpdatePlanningReviewMeetingDto = { + date: 15, + typeCode: 'NEW_TYPE', + }; + const mockType = new PlanningReviewMeetingType({ + code: 'NEW_TYPE', + }); + + mockPlanningReviewMeetingTypeRepository.findOneOrFail.mockResolvedValue( + mockType, + ); + + mockPlanningReviewMeetingRepository.findOneOrFail.mockResolvedValue( + mockResult, + ); + mockPlanningReviewMeetingRepository.save.mockResolvedValue(mockResult); + + await service.update('uuid', mockUpdate); + + expect( + mockPlanningReviewMeetingTypeRepository.findOneOrFail, + ).toHaveBeenCalledTimes(1); + expect(mockPlanningReviewMeetingRepository.save).toHaveBeenCalledTimes(1); + expect(mockResult.date).toEqual(new Date(mockUpdate.date)); + expect(mockResult.type).toEqual(mockType); + }); +}); diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.service.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.service.ts new file mode 100644 index 0000000000..a38c5a4142 --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.service.ts @@ -0,0 +1,98 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { filterUndefined } from '../../../utils/undefined'; +import { PlanningReviewService } from '../planning-review.service'; +import { PlanningReviewMeetingType } from './planning-review-meeting-type.entity'; +import { + CreatePlanningReviewMeetingDto, + UpdatePlanningReviewMeetingDto, +} from './planning-review-meeting.dto'; +import { PlanningReviewMeeting } from './planning-review-meeting.entity'; + +@Injectable() +export class PlanningReviewMeetingService { + constructor( + private planningReviewService: PlanningReviewService, + @InjectRepository(PlanningReviewMeeting) + private meetingRepository: Repository<PlanningReviewMeeting>, + @InjectRepository(PlanningReviewMeetingType) + private meetingTypeRepository: Repository<PlanningReviewMeetingType>, + ) {} + + async listTypes() { + return this.meetingTypeRepository.find(); + } + + getByPlanningReview(uuid: string) { + return this.meetingRepository.find({ + where: { + planningReviewUuid: uuid, + }, + relations: { + type: true, + }, + }); + } + + getByUuid(uuid: string) { + return this.meetingRepository.findOne({ + where: { + uuid, + }, + relations: { + type: true, + }, + }); + } + + async create(createDto: CreatePlanningReviewMeetingDto) { + const parentPlanningReview = await this.planningReviewService.getOrFail( + createDto.planningReviewUuid, + ); + + const type = await this.meetingTypeRepository.findOneOrFail({ + where: { + code: createDto.typeCode, + }, + }); + + const newMeeting = new PlanningReviewMeeting({ + planningReview: parentPlanningReview, + date: new Date(createDto.date), + type, + }); + + return await this.meetingRepository.save(newMeeting); + } + + async remove(uuid: string) { + const meeting = await this.meetingRepository.findOneOrFail({ + where: { + uuid, + }, + }); + + await this.meetingRepository.remove(meeting); + } + + async update(uuid: string, updateDto: UpdatePlanningReviewMeetingDto) { + const existingMeeting = await this.meetingRepository.findOneOrFail({ + where: { + uuid, + }, + }); + + existingMeeting.type = await this.meetingTypeRepository.findOneOrFail({ + where: { + code: updateDto.typeCode, + }, + }); + existingMeeting.date = filterUndefined( + new Date(updateDto.date), + existingMeeting.date, + ); + + await this.meetingRepository.save(existingMeeting); + } +} 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 998ec30f65..be665ac4b1 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 @@ -11,6 +11,7 @@ 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'; +import { PlanningReviewMeetingDto } from './planning-review-meeting/planning-review-meeting.dto'; export class PlanningReviewTypeDto extends BaseCodeDto { @AutoMap() @@ -84,6 +85,9 @@ export class PlanningReviewDto { @AutoMap(() => ApplicationRegionDto) region: ApplicationRegionDto; + @AutoMap(() => [PlanningReviewMeetingDto]) + meetings: PlanningReviewMeetingDto[]; + @AutoMap(() => PlanningReviewTypeDto) type: PlanningReviewTypeDto; } 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 22a9e6c358..3fedcd97e6 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,6 +5,7 @@ import { User } from '../../user/user.entity'; import { ApplicationRegion } from '../code/application-code/application-region/application-region.entity'; import { LocalGovernment } from '../local-government/local-government.entity'; import { PlanningReferral } from './planning-referral/planning-referral.entity'; +import { PlanningReviewMeeting } from './planning-review-meeting/planning-review-meeting.entity'; import { PlanningReviewType } from './planning-review-type.entity'; @Entity({ @@ -56,6 +57,13 @@ export class PlanningReview extends Base { @OneToMany(() => PlanningReferral, (referral) => referral.planningReview) referrals: PlanningReferral[]; + @AutoMap(() => [PlanningReviewMeeting]) + @OneToMany( + () => PlanningReviewMeeting, + (reviewMeeting) => reviewMeeting.planningReview, + ) + meetings: PlanningReviewMeeting[]; + @Column() typeCode: string; 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 9b81109c95..6dac6b21ea 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 @@ -13,6 +13,10 @@ import { PlanningReferralService } from './planning-referral/planning-referral.s import { PlanningReviewDocumentController } from './planning-review-document/planning-review-document.controller'; import { PlanningReviewDocument } from './planning-review-document/planning-review-document.entity'; import { PlanningReviewDocumentService } from './planning-review-document/planning-review-document.service'; +import { PlanningReviewMeetingType } from './planning-review-meeting/planning-review-meeting-type.entity'; +import { PlanningReviewMeetingController } from './planning-review-meeting/planning-review-meeting.controller'; +import { PlanningReviewMeeting } from './planning-review-meeting/planning-review-meeting.entity'; +import { PlanningReviewMeetingService } from './planning-review-meeting/planning-review-meeting.service'; import { PlanningReviewType } from './planning-review-type.entity'; import { PlanningReviewController } from './planning-review.controller'; import { PlanningReview } from './planning-review.entity'; @@ -25,6 +29,8 @@ import { PlanningReviewService } from './planning-review.service'; PlanningReferral, PlanningReviewType, PlanningReviewDocument, + PlanningReviewMeeting, + PlanningReviewMeetingType, DocumentCode, ]), forwardRef(() => BoardModule), @@ -37,12 +43,14 @@ import { PlanningReviewService } from './planning-review.service'; PlanningReviewController, PlanningReferralController, PlanningReviewDocumentController, + PlanningReviewMeetingController, ], providers: [ PlanningReviewService, PlanningReviewProfile, PlanningReferralService, PlanningReviewDocumentService, + PlanningReviewMeetingService, ], exports: [PlanningReviewService, PlanningReferralService], }) 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 4f93794003..912e6527c7 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 @@ -107,6 +107,7 @@ export class PlanningReviewService { type: true, }, }, + meetings: true, }, order: { referrals: { diff --git a/services/apps/alcs/src/common/automapper/planning-review.automapper.profile.ts b/services/apps/alcs/src/common/automapper/planning-review.automapper.profile.ts index b64c5ef743..f8589fb8d8 100644 --- a/services/apps/alcs/src/common/automapper/planning-review.automapper.profile.ts +++ b/services/apps/alcs/src/common/automapper/planning-review.automapper.profile.ts @@ -4,6 +4,12 @@ import { AutomapperProfile, InjectMapper } from 'automapper-nestjs'; import { PlanningReferral } from '../../alcs/planning-review/planning-referral/planning-referral.entity'; import { PlanningReviewDocumentDto } from '../../alcs/planning-review/planning-review-document/planning-review-document.dto'; import { PlanningReviewDocument } from '../../alcs/planning-review/planning-review-document/planning-review-document.entity'; +import { PlanningReviewMeetingType } from '../../alcs/planning-review/planning-review-meeting/planning-review-meeting-type.entity'; +import { + PlanningReviewMeetingDto, + PlanningReviewMeetingTypeDto, +} from '../../alcs/planning-review/planning-review-meeting/planning-review-meeting.dto'; +import { PlanningReviewMeeting } from '../../alcs/planning-review/planning-review-meeting/planning-review-meeting.entity'; import { PlanningReviewType } from '../../alcs/planning-review/planning-review-type.entity'; import { PlanningReferralDto, @@ -82,6 +88,22 @@ export class PlanningReviewProfile extends AutomapperProfile { ), ); createMap(mapper, DocumentCode, DocumentTypeDto); + + createMap( + mapper, + PlanningReviewMeetingType, + PlanningReviewMeetingTypeDto, + ); + + createMap( + mapper, + PlanningReviewMeeting, + PlanningReviewMeetingDto, + forMember( + (dto) => dto.date, + mapFrom((entity) => entity.date.getTime()), + ), + ); }; } } diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1710869779332-add_pr_meetings.ts b/services/apps/alcs/src/providers/typeorm/migrations/1710869779332-add_pr_meetings.ts new file mode 100644 index 0000000000..4891d8a016 --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1710869779332-add_pr_meetings.ts @@ -0,0 +1,43 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddPrMeetings1710869779332 implements MigrationInterface { + name = 'AddPrMeetings1710869779332'; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `CREATE TABLE "alcs"."planning_review_meeting_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, CONSTRAINT "UQ_192f56174d61ea85f55f7c94d1b" UNIQUE ("description"), CONSTRAINT "PK_3cb364ba2e88e35a13fdb9cdbd0" PRIMARY KEY ("code"))`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."planning_review_meeting_type" IS 'Meetings Types for Planning Review Meetings'`, + ); + await queryRunner.query( + `CREATE TABLE "alcs"."planning_review_meeting" ("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(), "type_code" text NOT NULL, "date" TIMESTAMP WITH TIME ZONE NOT NULL, "planning_review_uuid" uuid NOT NULL, CONSTRAINT "PK_7c2ce6697f1c82b92d303d95ecd" PRIMARY KEY ("uuid")); COMMENT ON COLUMN "alcs"."planning_review_meeting"."date" IS 'Date of the meeting'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."planning_review_meeting" IS 'Meeting schedule for Planning Reviews'`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_meeting" ADD CONSTRAINT "FK_3cb364ba2e88e35a13fdb9cdbd0" FOREIGN KEY ("type_code") REFERENCES "alcs"."planning_review_meeting_type"("code") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_meeting" ADD CONSTRAINT "FK_9ffabcba2f2b7059bbb49ecb5e5" FOREIGN KEY ("planning_review_uuid") REFERENCES "alcs"."planning_review"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_meeting" DROP CONSTRAINT "FK_9ffabcba2f2b7059bbb49ecb5e5"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_meeting" DROP CONSTRAINT "FK_3cb364ba2e88e35a13fdb9cdbd0"`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."planning_review_meeting" IS NULL`, + ); + await queryRunner.query(`DROP TABLE "alcs"."planning_review_meeting"`); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."planning_review_meeting_type" IS NULL`, + ); + await queryRunner.query(`DROP TABLE "alcs"."planning_review_meeting_type"`); + } +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1710870743180-seed_pr_meeting_types.ts b/services/apps/alcs/src/providers/typeorm/migrations/1710870743180-seed_pr_meeting_types.ts new file mode 100644 index 0000000000..aa08739d95 --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1710870743180-seed_pr_meeting_types.ts @@ -0,0 +1,18 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class SeedPrMeetingTypes1710870743180 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + INSERT INTO "alcs"."planning_review_meeting_type" + ("audit_deleted_date_at", "audit_created_at", "audit_updated_at", "audit_created_by", "audit_updated_by", "label", "code", "description") VALUES + (NULL, NOW(), NULL, 'migration-seed', NULL, 'Discussion', 'DISC', 'Discussion'), + (NULL, NOW(), NULL, 'migration-seed', NULL, 'Site Visit', 'SITE', 'Site Visit'), + (NULL, NOW(), NULL, 'migration-seed', NULL, 'L/FNG Presentation', 'LFNG', 'L/FNG Presentation'), + (NULL, NOW(), NULL, 'migration-seed', NULL, 'L/FNG Presentation & Discussion', 'LFNP', 'L/FNG Presentation & Discussion'); + `); + } + + public async down(): Promise<void> { + //Not needed for seeds + } +} From 26c5c9bc1945f4ca5e6e6fc3e52391e09cb76d71 Mon Sep 17 00:00:00 2001 From: Tristan Slater <1631008+trslater@users.noreply.github.com> Date: Wed, 20 Mar 2024 10:38:28 -0700 Subject: [PATCH 025/153] Only update intake email manually - Add `updateOnSave` option to `inline-text` component - Set `updateOnSave` to false for intake email --- .../app/features/notification/intake/intake.component.html | 7 ++++++- .../app/features/notification/intake/intake.component.ts | 1 + .../inline-editors/inline-text/inline-text.component.ts | 7 +++++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/alcs-frontend/src/app/features/notification/intake/intake.component.html b/alcs-frontend/src/app/features/notification/intake/intake.component.html index 192bcc2d02..f76c2a9342 100644 --- a/alcs-frontend/src/app/features/notification/intake/intake.component.html +++ b/alcs-frontend/src/app/features/notification/intake/intake.component.html @@ -44,6 +44,11 @@ <h3>ALC Intake</h3> </div> <div *ngIf="contactEmail"> <div class="subheading2">Primary Contact Email</div> - <app-inline-text [value]="contactEmail" [required]="true" (save)="updateSubmissionEmail($event)"></app-inline-text> + <app-inline-text + [updateOnSave]="false" + [value]="contactEmail" + [required]="true" + (save)="updateSubmissionEmail($event)" + ></app-inline-text> </div> </div> diff --git a/alcs-frontend/src/app/features/notification/intake/intake.component.ts b/alcs-frontend/src/app/features/notification/intake/intake.component.ts index 08530013e6..820635fb5a 100644 --- a/alcs-frontend/src/app/features/notification/intake/intake.component.ts +++ b/alcs-frontend/src/app/features/notification/intake/intake.component.ts @@ -77,6 +77,7 @@ export class IntakeComponent implements OnInit { const update = await this.notificationSubmissionService.setContactEmail(email, notification.fileNumber); if (update) { this.toastService.showSuccessToast('Notification updated'); + this.contactEmail = email; } } } diff --git a/alcs-frontend/src/app/shared/inline-editors/inline-text/inline-text.component.ts b/alcs-frontend/src/app/shared/inline-editors/inline-text/inline-text.component.ts index 5b18d7bb92..28c1d39a2c 100644 --- a/alcs-frontend/src/app/shared/inline-editors/inline-text/inline-text.component.ts +++ b/alcs-frontend/src/app/shared/inline-editors/inline-text/inline-text.component.ts @@ -15,6 +15,7 @@ import { styleUrls: ['./inline-text.component.scss'], }) export class InlineTextComponent implements AfterContentChecked { + @Input() updateOnSave: boolean = true; @Input() value?: string | undefined; @Input() placeholder: string = 'Enter a value'; @Input() required = false; @@ -41,7 +42,10 @@ export class InlineTextComponent implements AfterContentChecked { confirmEdit() { if (this.pendingValue !== this.value) { this.save.emit(this.pendingValue?.toString() ?? null); - this.value = this.pendingValue; + + if (this.updateOnSave) { + this.value = this.pendingValue; + } } this.isEditing = false; @@ -49,6 +53,5 @@ export class InlineTextComponent implements AfterContentChecked { cancelEdit() { this.isEditing = false; - this.pendingValue = this.value; } } From 71dde695da30cec37517b64e4f431d8a45c76c5e Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Wed, 20 Mar 2024 12:54:43 -0700 Subject: [PATCH 026/153] More PR Card Fixes * Load assignee so it shows the avatar circle * Only add due date padding when its not the only child * Use actual PR type instead of fixed type --- .../app/features/home/assigned/assigned.component.ts | 9 ++++----- .../subtask/subtask-table/subtask-table.component.ts | 1 - .../application-type-pill.constants.ts | 10 ---------- alcs-frontend/src/app/shared/card/card.component.scss | 2 +- .../planning-referral/planning-referral.service.ts | 1 + 5 files changed, 6 insertions(+), 17 deletions(-) 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 94192a8516..5cf145b80b 100644 --- a/alcs-frontend/src/app/features/home/assigned/assigned.component.ts +++ b/alcs-frontend/src/app/features/home/assigned/assigned.component.ts @@ -11,7 +11,6 @@ import { PlanningReferralDto } from '../../../services/planning-review/planning- import { MODIFICATION_TYPE_LABEL, NOTIFICATION_LABEL, - PLANNING_TYPE_LABEL, RECON_TYPE_LABEL, RETROACTIVE_TYPE_LABEL, } from '../../../shared/application-type-pill/application-type-pill.constants'; @@ -98,11 +97,11 @@ export class AssignedComponent implements OnInit { this.planningReferrals = [ ...planningReferrals .filter((r) => r.card.highPriority) - .map((r) => this.mapPlanning(r)) + .map((r) => this.mapPlanningReferral(r)) .sort((a, b) => a.date! - b.date!), ...planningReferrals .filter((r) => !r.card.highPriority) - .map((r) => this.mapPlanning(r)) + .map((r) => this.mapPlanningReferral(r)) .sort((a, b) => a.date! - b.date!), ]; @@ -124,14 +123,14 @@ export class AssignedComponent implements OnInit { this.notifications.length; } - private mapPlanning(p: PlanningReferralDto): AssignedToMeFile { + private mapPlanningReferral(p: PlanningReferralDto): AssignedToMeFile { return { title: `${p.planningReview.fileNumber} (${p.planningReview.documentName})`, type: p.card.type, date: p.card.createdAt, card: p.card, highPriority: p.card.highPriority, - labels: [PLANNING_TYPE_LABEL], + labels: [p.planningReview.type], }; } diff --git a/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.ts b/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.ts index bdd6dae485..29e14ebe10 100644 --- a/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.ts +++ b/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.ts @@ -7,7 +7,6 @@ import { NgSelectComponent } from '@ng-select/ng-select'; import { MODIFICATION_TYPE_LABEL, NOTIFICATION_LABEL, - PLANNING_TYPE_LABEL, RECON_TYPE_LABEL, } from '../../../../shared/application-type-pill/application-type-pill.constants'; import { CardType } from '../../../../shared/card/card.component'; diff --git a/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.constants.ts b/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.constants.ts index ca3ab5298f..f07be97a73 100644 --- a/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.constants.ts +++ b/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.constants.ts @@ -16,16 +16,6 @@ export const MODIFICATION_TYPE_LABEL: ApplicationPill = { textColor: '#000', }; -export const PLANNING_TYPE_LABEL = { - label: 'Planning Review', - code: 'PLAN', - shortLabel: 'PLAN', - backgroundColor: '#fff', - borderColor: '#b2ff59', - description: 'Planning Review', - textColor: '#000', -}; - export const RETROACTIVE_TYPE_LABEL = { label: 'Retroactive', code: 'RETRO', diff --git a/alcs-frontend/src/app/shared/card/card.component.scss b/alcs-frontend/src/app/shared/card/card.component.scss index def7b0182d..0a55622d4a 100644 --- a/alcs-frontend/src/app/shared/card/card.component.scss +++ b/alcs-frontend/src/app/shared/card/card.component.scss @@ -86,7 +86,7 @@ $date-ht: 32px; } } -.due-date { +.due-date:not(:only-child) { margin-left: 12px; } 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 index fbd38d9205..2740e406f5 100644 --- 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 @@ -62,6 +62,7 @@ export class PlanningReferralService { type: true, status: true, board: true, + assignee: true, }, planningReview: { localGovernment: true, From 831b7c05d989f47868aea0b3946ccb5281fba2c2 Mon Sep 17 00:00:00 2001 From: Tristan Slater <1631008+trslater@users.noreply.github.com> Date: Wed, 20 Mar 2024 15:47:21 -0700 Subject: [PATCH 027/153] Fix `Yes` used instead of `true` - This fixes farm use description not showing at all - Also cleaned up spaces --- .../noi-pfrs-submission-template.docx | Bin 50016 -> 50062 bytes .../noi-pofo-submission-template.docx | Bin 50170 -> 53475 bytes .../noi-roso-submission-template.docx | Bin 49556 -> 49605 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/services/templates/pdf/noi-submissions/noi-pfrs-submission-template.docx b/services/templates/pdf/noi-submissions/noi-pfrs-submission-template.docx index dfc73105db17913450919089293f5dfc9490b601..984dcfcfa664cc1449415160490de2fe33f14df7 100644 GIT binary patch delta 14907 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 MDWJG}|FiG^0EP$5M*si- delta 14885 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 diff --git a/services/templates/pdf/noi-submissions/noi-pofo-submission-template.docx b/services/templates/pdf/noi-submissions/noi-pofo-submission-template.docx index 7eea8c74356da5acd03cebc8b8bfe0594576983a..9e1679dfd8bb34d8f6853449fc73b75a3720a108 100644 GIT binary patch delta 22989 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 aL5cFI6B66;0wtmkGTOm}5k2_3!~Xz2+fVfX delta 19582 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 I%WlMf09b6xGynhq diff --git a/services/templates/pdf/noi-submissions/noi-roso-submission-template.docx b/services/templates/pdf/noi-submissions/noi-roso-submission-template.docx index 1a8ed80d1177409f941ba9cfbb79d2ad52beeca3..80fdbea8ac447767eeacc56c28e86b5495577528 100644 GIT binary patch delta 14608 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�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 GzW)OmvKkry delta 14559 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 From bdcac1e6d58c3d18bb131ef5a5f811ad7e9e28a4 Mon Sep 17 00:00:00 2001 From: Tristan Slater <1631008+trslater@users.noreply.github.com> Date: Wed, 20 Mar 2024 16:00:04 -0700 Subject: [PATCH 028/153] Fix residential use description not showing for accessory buildings --- ...enerate-noi-submission-document.service.ts | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/services/apps/alcs/src/portal/pdf-generation/generate-noi-submission-document.service.ts b/services/apps/alcs/src/portal/pdf-generation/generate-noi-submission-document.service.ts index ff176c8f92..df7560ef0b 100644 --- a/services/apps/alcs/src/portal/pdf-generation/generate-noi-submission-document.service.ts +++ b/services/apps/alcs/src/portal/pdf-generation/generate-noi-submission-document.service.ts @@ -313,21 +313,30 @@ export class GenerateNoiSubmissionDocumentService { (document) => document.type?.code === DOCUMENT_TYPE.BUILDING_PLAN, ); const hasFarmStructure = submission?.soilProposedStructures.some( - (structure) => structure.type === 'Farm Structure' + (structure) => structure.type === 'Farm Structure', ); pdfData = { ...pdfData, isSoilAgriParcelActivityVisible: hasFarmStructure, isSoilStructureFarmUseReasonVisible: hasFarmStructure, - isSoilStructureResidentialUseReasonVisible: !!submission?.soilProposedStructures.some( - (structure) => structure.type === "Residential - Principal Residence" || structure.type === "Residential - Additional Residence" - ), - isSoilStructureResidentialAccessoryUseReasonVisible: !!submission?.soilProposedStructures.some( - (structure) => structure.type === "Residential - Accessory Structure" - ), - isSoilOtherStructureUseReasonVisible: !!submission?.soilProposedStructures.some( - (structure) => structure.type === "Other Structure" - ), + isSoilStructureResidentialUseReasonVisible: + !!submission?.soilProposedStructures.some( + (structure) => + structure.type && + [ + 'Residential - Accessory Structure', + 'Residential - Additional Residence', + 'Residential - Principal Residence', + ].includes(structure.type), + ), + isSoilStructureResidentialAccessoryUseReasonVisible: + !!submission?.soilProposedStructures.some( + (structure) => structure.type === 'Residential - Accessory Structure', + ), + isSoilOtherStructureUseReasonVisible: + !!submission?.soilProposedStructures.some( + (structure) => structure.type === 'Other Structure', + ), fillProjectDuration: submission.fillProjectDuration, soilIsFollowUp: formatBooleanToYesNoString(submission.soilIsFollowUp), soilFollowUpIDs: submission.soilFollowUpIDs, From d1f40ea9fd24551d5ccc4422b1afdaa04bb8d410 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Wed, 20 Mar 2024 16:09:09 -0700 Subject: [PATCH 029/153] More PR Fixes --- .../document-upload-dialog.component.ts | 3 ++- .../planning-review/overview/overview.component.html | 4 ++-- .../application-type-pill/application-type-pill.constants.ts | 4 ++-- .../inline-dropdown/inline-dropdown.component.scss | 5 +++++ 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.ts b/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.ts index 779cdab8a1..923a358a34 100644 --- a/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.ts +++ b/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.ts @@ -95,6 +95,7 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { visibilityFlags.push('C'); } + const file = this.pendingFile; const dto: UpdateDocumentDto = { fileName: this.name.value!, source: this.source.value as DOCUMENT_SOURCE, @@ -102,9 +103,9 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { visibilityFlags, parcelUuid: this.parcelId.value ?? undefined, ownerUuid: this.ownerId.value ?? undefined, + file, }; - const file = this.pendingFile; this.isSaving = true; if (this.data.existingDocument) { await this.planningReviewDocumentService.update(this.data.existingDocument.uuid, dto); diff --git a/alcs-frontend/src/app/features/planning-review/overview/overview.component.html b/alcs-frontend/src/app/features/planning-review/overview/overview.component.html index c534794c8c..7908db8884 100644 --- a/alcs-frontend/src/app/features/planning-review/overview/overview.component.html +++ b/alcs-frontend/src/app/features/planning-review/overview/overview.component.html @@ -6,13 +6,13 @@ <h3>Overview</h3> </section> <section *ngIf="planningReview"> <h5>Planning Review Type</h5> - <div> + <div style="width: 50%"> <app-inline-dropdown (save)="onSaveType($event)" [options]="types" [value]="planningReview.type.code" /> </div> </section> <section *ngIf="planningReview"> <h5>Status</h5> - <div> + <div style="width: 50%"> <app-inline-button-toggle (save)="onSaveStatus($event)" [selectedValue]="planningReview.open ? 'Open' : 'Closed'" diff --git a/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.constants.ts b/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.constants.ts index f07be97a73..d8dc694408 100644 --- a/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.constants.ts +++ b/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.constants.ts @@ -89,7 +89,7 @@ export const OPEN_PR_LABEL = { export const CLOSED_PR_LABEL = { label: 'Closed', shortLabel: 'Closed', - backgroundColor: '#C6242A', - borderColor: '#C6242A', + backgroundColor: '#DD8C8F', + borderColor: '#DD8C8F', textColor: '#313132', }; diff --git a/alcs-frontend/src/app/shared/inline-editors/inline-dropdown/inline-dropdown.component.scss b/alcs-frontend/src/app/shared/inline-editors/inline-dropdown/inline-dropdown.component.scss index 6487e5e781..d39565d0a9 100644 --- a/alcs-frontend/src/app/shared/inline-editors/inline-dropdown/inline-dropdown.component.scss +++ b/alcs-frontend/src/app/shared/inline-editors/inline-dropdown/inline-dropdown.component.scss @@ -6,6 +6,11 @@ .editing { display: inline-block; + width: 100%; + + .mat-mdc-form-field { + width: 100%; + } } .editing.hidden { From 91aff4f87b96174860fa206ad4a0e0c21216b96a Mon Sep 17 00:00:00 2001 From: mhuseinov <61513701+mhuseinov@users.noreply.github.com> Date: Thu, 21 Mar 2024 08:27:29 -0700 Subject: [PATCH 030/153] Feature/alcs 1444 inquiry API (#1523) inquiry API: DB structure data seed service with unit tests document service with unit tests notification fix card type used for notification update notification error messages to refer to notification instead of notice of intent NOTE: Controllers will be provided in subsequent MRs --- services/apps/alcs/src/alcs/alcs.module.ts | 9 +- .../alcs/card/card-type/card-type.entity.ts | 1 + .../inquiry-document.controller.spec.ts | 167 ++++++++++ .../inquiry-document.controller.ts | 151 +++++++++ .../inquiry-document/inquiry-document.dto.ts | 20 ++ .../inquiry-document.entity.ts | 50 +++ .../inquiry-document.service.spec.ts | 266 +++++++++++++++ .../inquiry-document.service.ts | 234 ++++++++++++++ .../inquiry-parcel.controller.spec.ts | 18 ++ .../inquiry-parcel.controller.ts | 6 + .../inquiry-parcel/inquiry-parcel.dto.ts | 50 +++ .../inquiry-parcel/inquiry-parcel.entity.ts | 46 +++ .../inquiry-parcel.service.spec.ts | 18 ++ .../inquiry-parcel/inquiry-parcel.service.ts | 6 + .../src/alcs/inquiry/inquiry-type.entity.ts | 29 ++ .../alcs/inquiry/inquiry.controller.spec.ts | 18 ++ .../src/alcs/inquiry/inquiry.controller.ts | 6 + .../apps/alcs/src/alcs/inquiry/inquiry.dto.ts | 174 ++++++++++ .../alcs/src/alcs/inquiry/inquiry.entity.ts | 102 ++++++ .../alcs/src/alcs/inquiry/inquiry.module.ts | 45 +++ .../src/alcs/inquiry/inquiry.service.spec.ts | 251 +++++++++++++++ .../alcs/src/alcs/inquiry/inquiry.service.ts | 302 ++++++++++++++++++ .../notification-document.entity.ts | 14 +- .../notification/notification.service.spec.ts | 4 +- .../alcs/notification/notification.service.ts | 10 +- .../staff-journal/staff-journal.entity.ts | 8 + .../automapper/inquiry.automapper.profile.ts | 72 +++++ .../migrations/1710371089218-inquiry.ts | 105 ++++++ .../1710373199878-inquiry_staff_journal.ts | 29 ++ .../migrations/1710437289952-file_number.ts | 23 ++ ...1-added_comments_and_types_to_inquiries.ts | 79 +++++ 31 files changed, 2301 insertions(+), 12 deletions(-) create mode 100644 services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.controller.spec.ts create mode 100644 services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.controller.ts create mode 100644 services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.dto.ts create mode 100644 services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.entity.ts create mode 100644 services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.service.spec.ts create mode 100644 services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.service.ts create mode 100644 services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.controller.spec.ts create mode 100644 services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.controller.ts create mode 100644 services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.dto.ts create mode 100644 services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.entity.ts create mode 100644 services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.service.spec.ts create mode 100644 services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.service.ts create mode 100644 services/apps/alcs/src/alcs/inquiry/inquiry-type.entity.ts create mode 100644 services/apps/alcs/src/alcs/inquiry/inquiry.controller.spec.ts create mode 100644 services/apps/alcs/src/alcs/inquiry/inquiry.controller.ts create mode 100644 services/apps/alcs/src/alcs/inquiry/inquiry.dto.ts create mode 100644 services/apps/alcs/src/alcs/inquiry/inquiry.entity.ts create mode 100644 services/apps/alcs/src/alcs/inquiry/inquiry.module.ts create mode 100644 services/apps/alcs/src/alcs/inquiry/inquiry.service.spec.ts create mode 100644 services/apps/alcs/src/alcs/inquiry/inquiry.service.ts create mode 100644 services/apps/alcs/src/common/automapper/inquiry.automapper.profile.ts create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1710371089218-inquiry.ts create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1710373199878-inquiry_staff_journal.ts create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1710437289952-file_number.ts create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1710791560891-added_comments_and_types_to_inquiries.ts diff --git a/services/apps/alcs/src/alcs/alcs.module.ts b/services/apps/alcs/src/alcs/alcs.module.ts index dcb906f3e4..9b32d89ab2 100644 --- a/services/apps/alcs/src/alcs/alcs.module.ts +++ b/services/apps/alcs/src/alcs/alcs.module.ts @@ -1,8 +1,8 @@ import { Module } from '@nestjs/common'; import { RouterModule } from '@nestjs/core'; -import { ApplicationSubmissionStatusModule } from './application/application-submission-status/application-submission-status.module'; import { AdminModule } from './admin/admin.module'; import { ApplicationDecisionModule } from './application-decision/application-decision.module'; +import { ApplicationSubmissionStatusModule } from './application/application-submission-status/application-submission-status.module'; import { ApplicationTimelineModule } from './application/application-timeline/application-timeline.module'; import { ApplicationModule } from './application/application.module'; import { BoardModule } from './board/board.module'; @@ -12,12 +12,13 @@ import { CommentModule } from './comment/comment.module'; import { CommissionerModule } from './commissioner/commissioner.module'; import { HomeModule } from './home/home.module'; import { ImportModule } from './import/import.module'; +import { InquiryModule } from './inquiry/inquiry.module'; import { LocalGovernmentModule } from './local-government/local-government.module'; +import { MessageModule } from './message/message.module'; import { NoticeOfIntentDecisionModule } from './notice-of-intent-decision/notice-of-intent-decision.module'; -import { NoticeOfIntentTimelineModule } from './notice-of-intent/notice-of-intent-timeline/notice-of-intent-timeline.module'; import { NoticeOfIntentSubmissionStatusModule } from './notice-of-intent/notice-of-intent-submission-status/notice-of-intent-submission-status.module'; +import { NoticeOfIntentTimelineModule } from './notice-of-intent/notice-of-intent-timeline/notice-of-intent-timeline.module'; import { NoticeOfIntentModule } from './notice-of-intent/notice-of-intent.module'; -import { MessageModule } from './message/message.module'; import { NotificationSubmissionStatusModule } from './notification/notification-submission-status/notification-submission-status.module'; import { NotificationTimelineModule } from './notification/notification-timeline/notification-timeline.module'; import { NotificationModule } from './notification/notification.module'; @@ -50,6 +51,7 @@ import { StaffJournalModule } from './staff-journal/staff-journal.module'; LocalGovernmentModule, NotificationModule, NotificationTimelineModule, + InquiryModule, RouterModule.register([ { path: 'alcs', module: ApplicationModule }, { path: 'alcs', module: CommentModule }, @@ -75,6 +77,7 @@ import { StaffJournalModule } from './staff-journal/staff-journal.module'; { path: 'alcs', module: NotificationModule }, { path: 'alcs', module: NotificationSubmissionStatusModule }, { path: 'alcs', module: NotificationTimelineModule }, + { path: 'alcs', module: InquiryModule }, ]), ], controllers: [], 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..6ea64d16aa 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 @@ -11,6 +11,7 @@ export enum CARD_TYPE { NOI = 'NOI', NOI_MODI = 'NOIM', NOTIFICATION = 'NOTI', + INQUIRY = 'INCR', } @Entity() diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.controller.spec.ts b/services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.controller.spec.ts new file mode 100644 index 0000000000..5a41262826 --- /dev/null +++ b/services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.controller.spec.ts @@ -0,0 +1,167 @@ +import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; +import { BadRequestException } from '@nestjs/common'; +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 { InquiryProfile } from '../../../common/automapper/inquiry.automapper.profile'; +import { DOCUMENT_TYPE } from '../../../document/document-code.entity'; +import { DOCUMENT_SOURCE } from '../../../document/document.dto'; +import { Document } from '../../../document/document.entity'; +import { User } from '../../../user/user.entity'; +import { CodeService } from '../../code/code.service'; +import { InquiryDocumentController } from './inquiry-document.controller'; +import { InquiryDocument } from './inquiry-document.entity'; +import { InquiryDocumentService } from './inquiry-document.service'; + +describe('InquiryDocumentController', () => { + let controller: InquiryDocumentController; + let inquiryDocumentService: DeepMocked<InquiryDocumentService>; + + const mockDocument = new InquiryDocument({ + document: new Document({ + mimeType: 'mimeType', + uploadedBy: new User(), + uploadedAt: new Date(), + }), + }); + + beforeEach(async () => { + inquiryDocumentService = createMock(); + + const module: TestingModule = await Test.createTestingModule({ + imports: [ + AutomapperModule.forRoot({ + strategyInitializer: classes(), + }), + ], + controllers: [InquiryDocumentController], + providers: [ + { + provide: CodeService, + useValue: {}, + }, + InquiryProfile, + { + provide: InquiryDocumentService, + useValue: inquiryDocumentService, + }, + { + provide: ClsService, + useValue: {}, + }, + ...mockKeyCloakProviders, + ], + }).compile(); + controller = module.get<InquiryDocumentController>( + InquiryDocumentController, + ); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); + + it('should return the attached document', async () => { + const mockFile = {}; + const mockUser = {}; + + inquiryDocumentService.attachDocument.mockResolvedValue(mockDocument); + + const res = await controller.attachDocument('fileNumber', { + isMultipart: () => true, + body: { + documentType: { + value: DOCUMENT_TYPE.CERTIFICATE_OF_TITLE, + }, + fileName: { + value: 'file', + }, + source: { + value: DOCUMENT_SOURCE.APPLICANT, + }, + visibilityFlags: { + value: '', + }, + file: mockFile, + }, + user: { + entity: mockUser, + }, + }); + + expect(res.mimeType).toEqual(mockDocument.document.mimeType); + + expect(inquiryDocumentService.attachDocument).toHaveBeenCalledTimes(1); + const callData = inquiryDocumentService.attachDocument.mock.calls[0][0]; + expect(callData.fileName).toEqual('file'); + expect(callData.file).toEqual(mockFile); + expect(callData.user).toEqual(mockUser); + }); + + it('should throw an exception if request is not the right type', async () => { + const mockFile = {}; + const mockUser = {}; + + inquiryDocumentService.attachDocument.mockResolvedValue(mockDocument); + + await expect( + controller.attachDocument('fileNumber', { + isMultipart: () => false, + file: () => mockFile, + user: { + entity: mockUser, + }, + }), + ).rejects.toMatchObject( + new BadRequestException('Request is not multipart'), + ); + }); + + it('should list documents', async () => { + inquiryDocumentService.list.mockResolvedValue([mockDocument]); + + const res = await controller.listDocuments('fake-number'); + + expect(res[0].mimeType).toEqual(mockDocument.document.mimeType); + }); + + it('should call through to delete documents', async () => { + inquiryDocumentService.delete.mockResolvedValue(mockDocument); + inquiryDocumentService.get.mockResolvedValue(mockDocument); + + await controller.delete('fake-uuid'); + + expect(inquiryDocumentService.get).toHaveBeenCalledTimes(1); + expect(inquiryDocumentService.delete).toHaveBeenCalledTimes(1); + }); + + it('should call through for open', async () => { + const fakeUrl = 'fake-url'; + inquiryDocumentService.getInlineUrl.mockResolvedValue(fakeUrl); + inquiryDocumentService.get.mockResolvedValue(mockDocument); + + const res = await controller.open('fake-uuid'); + + expect(res.url).toEqual(fakeUrl); + }); + + it('should call through for download', async () => { + const fakeUrl = 'fake-url'; + inquiryDocumentService.getDownloadUrl.mockResolvedValue(fakeUrl); + inquiryDocumentService.get.mockResolvedValue(mockDocument); + + const res = await controller.download('fake-uuid'); + + expect(res.url).toEqual(fakeUrl); + }); + + it('should call through for list types', async () => { + inquiryDocumentService.fetchTypes.mockResolvedValue([]); + + await controller.listTypes(); + + expect(inquiryDocumentService.fetchTypes).toHaveBeenCalledTimes(1); + }); +}); diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.controller.ts b/services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.controller.ts new file mode 100644 index 0000000000..7801aa4c3c --- /dev/null +++ b/services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.controller.ts @@ -0,0 +1,151 @@ +import { + BadRequestException, + Controller, + Delete, + Get, + Param, + Post, + 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 { ANY_AUTH_ROLE } from '../../../common/authorization/roles'; +import { RolesGuard } from '../../../common/authorization/roles-guard.service'; +import { UserRoles } from '../../../common/authorization/roles.decorator'; +import { + DOCUMENT_TYPE, + DocumentCode, +} from '../../../document/document-code.entity'; +import { + DOCUMENT_SOURCE, + DOCUMENT_SYSTEM, + DocumentTypeDto, +} from '../../../document/document.dto'; +import { InquiryDocumentDto } from './inquiry-document.dto'; +import { InquiryDocument } from './inquiry-document.entity'; +import { InquiryDocumentService } from './inquiry-document.service'; + +@ApiOAuth2(config.get<string[]>('KEYCLOAK.SCOPES')) +@UseGuards(RolesGuard) +@Controller('inquiry-document') +export class InquiryDocumentController { + constructor( + private inquiryDocumentService: InquiryDocumentService, + @InjectMapper() private mapper: Mapper, + ) {} + + @Get('/inquiry/:fileNumber') + @UserRoles(...ANY_AUTH_ROLE) + async listAll( + @Param('fileNumber') fileNumber: string, + ): Promise<InquiryDocumentDto[]> { + const documents = await this.inquiryDocumentService.list(fileNumber); + return this.mapper.mapArray(documents, InquiryDocument, InquiryDocumentDto); + } + + @Post('/inquiry/:fileNumber') + @UserRoles(...ANY_AUTH_ROLE) + async attachDocument( + @Param('fileNumber') fileNumber: string, + @Req() req, + ): Promise<InquiryDocumentDto> { + if (!req.isMultipart()) { + throw new BadRequestException('Request is not multipart'); + } + + const savedDocument = await this.saveUploadedFile(req, fileNumber); + + return this.mapper.map(savedDocument, InquiryDocument, InquiryDocumentDto); + } + + @Post('/:uuid') + @UserRoles(...ANY_AUTH_ROLE) + async updateDocument( + @Param('uuid') documentUuid: string, + @Req() req, + ): Promise<InquiryDocumentDto> { + if (!req.isMultipart()) { + throw new BadRequestException('Request is not multipart'); + } + + const documentType = req.body.documentType.value as DOCUMENT_TYPE; + const file = req.body.file; + const fileName = req.body.fileName.value as string; + const documentSource = req.body.source.value as DOCUMENT_SOURCE; + + const savedDocument = await this.inquiryDocumentService.update({ + uuid: documentUuid, + fileName, + file, + documentType: documentType as DOCUMENT_TYPE, + source: documentSource, + user: req.user.entity, + }); + + return this.mapper.map(savedDocument, InquiryDocument, InquiryDocumentDto); + } + + @Get('/inquiry/:fileNumber/inquiryDocuments') + @UserRoles(...ANY_AUTH_ROLE) + async listDocuments( + @Param('fileNumber') fileNumber: string, + ): Promise<InquiryDocumentDto[]> { + const documents = await this.inquiryDocumentService.list(fileNumber); + return this.mapper.mapArray(documents, InquiryDocument, InquiryDocumentDto); + } + + @Get('/types') + @UserRoles(...ANY_AUTH_ROLE) + async listTypes() { + const types = await this.inquiryDocumentService.fetchTypes(); + return this.mapper.mapArray(types, DocumentCode, DocumentTypeDto); + } + + @Get('/:uuid/open') + @UserRoles(...ANY_AUTH_ROLE) + async open(@Param('uuid') fileUuid: string) { + const document = await this.inquiryDocumentService.get(fileUuid); + const url = await this.inquiryDocumentService.getInlineUrl(document); + return { + url, + }; + } + + @Get('/:uuid/download') + @UserRoles(...ANY_AUTH_ROLE) + async download(@Param('uuid') fileUuid: string) { + const document = await this.inquiryDocumentService.get(fileUuid); + const url = await this.inquiryDocumentService.getDownloadUrl(document); + return { + url, + }; + } + + @Delete('/:uuid') + @UserRoles(...ANY_AUTH_ROLE) + async delete(@Param('uuid') fileUuid: string) { + const document = await this.inquiryDocumentService.get(fileUuid); + await this.inquiryDocumentService.delete(document); + return {}; + } + + private async saveUploadedFile(req, fileNumber: string) { + const documentType = req.body.documentType.value as DOCUMENT_TYPE; + const file = req.body.file; + const fileName = req.body.fileName.value as string; + const documentSource = req.body.source.value as DOCUMENT_SOURCE; + + return await this.inquiryDocumentService.attachDocument({ + fileNumber, + fileName, + file, + user: req.user.entity, + documentType: documentType as DOCUMENT_TYPE, + source: documentSource, + system: DOCUMENT_SYSTEM.ALCS, + }); + } +} diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.dto.ts b/services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.dto.ts new file mode 100644 index 0000000000..07d815c275 --- /dev/null +++ b/services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.dto.ts @@ -0,0 +1,20 @@ +import { AutoMap } from 'automapper-classes'; +import { DocumentTypeDto } from '../../../document/document.dto'; + +export class InquiryDocumentDto { + @AutoMap() + uuid: string; + + @AutoMap(() => DocumentTypeDto) + type?: DocumentTypeDto; + + //Document Fields + documentUuid: string; + fileName: string; + fileSize?: number; + source: string; + system: string; + mimeType: string; + uploadedBy: string; + uploadedAt: number; +} diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.entity.ts b/services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.entity.ts new file mode 100644 index 0000000000..1557cd927a --- /dev/null +++ b/services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.entity.ts @@ -0,0 +1,50 @@ +import { AutoMap } from 'automapper-classes'; +import { + BaseEntity, + Column, + Entity, + Index, + JoinColumn, + ManyToOne, + OneToOne, + PrimaryGeneratedColumn, +} from 'typeorm'; +import { DocumentCode } from '../../../document/document-code.entity'; +import { Document } from '../../../document/document.entity'; +import { Inquiry } from '../inquiry.entity'; + +@Entity({ + comment: 'Stores inquiry documents', +}) +export class InquiryDocument extends BaseEntity { + constructor(data?: Partial<InquiryDocument>) { + super(); + if (data) { + Object.assign(this, data); + } + } + + @AutoMap() + @PrimaryGeneratedColumn('uuid') + uuid: string; + + @ManyToOne(() => DocumentCode) + type?: DocumentCode; + + @Column({ nullable: true }) + typeCode?: string | null; + + @ManyToOne(() => Inquiry, { nullable: false }) + inquiry: Inquiry; + + @Column() + @Index() + inquiryUuid: string; + + @Column({ nullable: true, type: 'uuid' }) + documentUuid?: string | null; + + @OneToOne(() => Document) + @JoinColumn() + document: Document; +} diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.service.spec.ts b/services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.service.spec.ts new file mode 100644 index 0000000000..179d0e8948 --- /dev/null +++ b/services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.service.spec.ts @@ -0,0 +1,266 @@ +import { ServiceNotFoundException } from '@app/common/exceptions/base.exception'; +import { MultipartFile } from '@fastify/multipart'; +import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; +import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { + DOCUMENT_TYPE, + DocumentCode, +} from '../../../document/document-code.entity'; +import { + DOCUMENT_SOURCE, + DOCUMENT_SYSTEM, +} from '../../../document/document.dto'; +import { Document } from '../../../document/document.entity'; +import { DocumentService } from '../../../document/document.service'; +import { User } from '../../../user/user.entity'; +import { UserService } from '../../../user/user.service'; +import { Inquiry } from '../inquiry.entity'; +import { InquiryService } from '../inquiry.service'; +import { InquiryDocument } from './inquiry-document.entity'; +import { InquiryDocumentService } from './inquiry-document.service'; + +describe('InquiryDocumentService', () => { + let service: InquiryDocumentService; + let mockDocumentService: DeepMocked<DocumentService>; + let mockInquiryService: DeepMocked<InquiryService>; + let mockRepository: DeepMocked<Repository<InquiryDocument>>; + let mockTypeRepository: DeepMocked<Repository<DocumentCode>>; + + let mockInquiry; + const fileNumber = '12345'; + + beforeEach(async () => { + mockDocumentService = createMock(); + mockInquiryService = createMock(); + mockRepository = createMock(); + mockTypeRepository = createMock(); + + mockInquiry = new Inquiry(); + mockInquiryService.getByFileNumber.mockResolvedValue(mockInquiry); + mockDocumentService.create.mockResolvedValue({} as Document); + + const module: TestingModule = await Test.createTestingModule({ + providers: [ + InquiryDocumentService, + { + provide: DocumentService, + useValue: mockDocumentService, + }, + { + provide: InquiryService, + useValue: mockInquiryService, + }, + { + provide: getRepositoryToken(DocumentCode), + useValue: mockTypeRepository, + }, + { + provide: getRepositoryToken(InquiryDocument), + useValue: mockRepository, + }, + { + provide: UserService, + useValue: createMock<UserService>(), + }, + ], + }).compile(); + + service = module.get<InquiryDocumentService>(InquiryDocumentService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + it('should create a document in the happy path', async () => { + const mockUser = new User(); + const mockFile = {}; + const mockSavedDocument = {}; + + mockRepository.save.mockResolvedValue(mockSavedDocument as InquiryDocument); + + const res = await service.attachDocument({ + fileNumber, + file: mockFile as MultipartFile, + user: mockUser, + documentType: DOCUMENT_TYPE.DECISION_DOCUMENT, + fileName: '', + source: DOCUMENT_SOURCE.APPLICANT, + system: DOCUMENT_SYSTEM.PORTAL, + }); + + expect(mockInquiryService.getByFileNumber).toHaveBeenCalledTimes(1); + expect(mockDocumentService.create).toHaveBeenCalledTimes(1); + expect(mockDocumentService.create.mock.calls[0][0]).toBe('inquiry/12345'); + expect(mockDocumentService.create.mock.calls[0][2]).toBe(mockFile); + expect(mockDocumentService.create.mock.calls[0][3]).toBe(mockUser); + + expect(mockRepository.save).toHaveBeenCalledTimes(1); + expect(mockRepository.save.mock.calls[0][0].inquiry).toBe(mockInquiry); + + expect(res).toBe(mockSavedDocument); + }); + + it('should delete document and inquiry document when deleting', async () => { + const mockDocument = {}; + const mockIncDocument = { + uuid: '1', + document: mockDocument, + } as InquiryDocument; + + mockDocumentService.softRemove.mockResolvedValue(); + mockRepository.remove.mockResolvedValue({} as any); + + await service.delete(mockIncDocument); + + expect(mockDocumentService.softRemove).toHaveBeenCalledTimes(1); + expect(mockDocumentService.softRemove.mock.calls[0][0]).toBe(mockDocument); + + expect(mockRepository.remove).toHaveBeenCalledTimes(1); + expect(mockRepository.remove.mock.calls[0][0]).toBe(mockIncDocument); + }); + + it('should call through for get', async () => { + const mockDocument = {}; + const mockIncDocument = { + uuid: '1', + document: mockDocument, + } as InquiryDocument; + + mockDocumentService.softRemove.mockResolvedValue(); + mockRepository.findOne.mockResolvedValue(mockIncDocument); + + const res = await service.get('fake-uuid'); + expect(res).toBe(mockIncDocument); + }); + + it("should throw an exception when getting a document that doesn't exist", async () => { + const mockDocument = {}; + const mockIncDocument = { + uuid: '1', + document: mockDocument, + } as InquiryDocument; + + mockDocumentService.softRemove.mockResolvedValue(); + mockRepository.findOne.mockResolvedValue(null); + + await expect(service.get(mockIncDocument.uuid)).rejects.toMatchObject( + new ServiceNotFoundException( + `Failed to find document ${mockIncDocument.uuid}`, + ), + ); + }); + + it('should call through for list', async () => { + const mockDocument = {}; + const mockIncDocument = { + uuid: '1', + document: mockDocument, + } as InquiryDocument; + mockRepository.find.mockResolvedValue([mockIncDocument]); + + const res = await service.list(fileNumber); + + expect(mockRepository.find).toHaveBeenCalledTimes(1); + expect(res[0]).toBe(mockIncDocument); + }); + + it('should call through for download', async () => { + const mockDocument = {}; + const mockIncDocument = { + uuid: '1', + document: mockDocument, + } as InquiryDocument; + + const fakeUrl = 'mock-url'; + mockDocumentService.getDownloadUrl.mockResolvedValue(fakeUrl); + + const res = await service.getInlineUrl(mockIncDocument); + + expect(mockDocumentService.getDownloadUrl).toHaveBeenCalledTimes(1); + expect(res).toEqual(fakeUrl); + }); + + it('should call delete for each document loaded', async () => { + const mockIncDocument = new InquiryDocument({ + uuid: '1', + document: new Document({ + source: DOCUMENT_SOURCE.APPLICANT, + }), + }); + const mockLgDocument = new InquiryDocument({ + uuid: '2', + document: new Document({ + source: DOCUMENT_SOURCE.LFNG, + }), + }); + + mockRepository.find.mockResolvedValue([mockIncDocument, mockLgDocument]); + mockDocumentService.softRemove.mockResolvedValue(); + mockRepository.remove.mockResolvedValue({} as any); + + await service.deleteByType(DOCUMENT_TYPE.STAFF_REPORT, ''); + + expect(mockRepository.find).toHaveBeenCalledTimes(1); + expect(mockDocumentService.softRemove).toHaveBeenCalledTimes(2); + }); + + it('should call through for fetchTypes', async () => { + mockTypeRepository.find.mockResolvedValue([]); + + const res = await service.fetchTypes(); + + expect(mockTypeRepository.find).toHaveBeenCalledTimes(1); + expect(res).toBeDefined(); + }); + + it('should create a record for external documents', async () => { + mockRepository.save.mockResolvedValue(new InquiryDocument()); + mockInquiryService.getUuid.mockResolvedValueOnce('app-uuid'); + mockRepository.findOne.mockResolvedValue(new InquiryDocument()); + + const res = await service.attachExternalDocument('', { + type: DOCUMENT_TYPE.CERTIFICATE_OF_TITLE, + documentUuid: 'fake-uuid', + }); + + expect(mockInquiryService.getUuid).toHaveBeenCalledTimes(1); + expect(mockRepository.save).toHaveBeenCalledTimes(1); + expect(mockRepository.save.mock.calls[0][0].inquiryUuid).toEqual( + 'app-uuid', + ); + expect(mockRepository.save.mock.calls[0][0].typeCode).toEqual( + DOCUMENT_TYPE.CERTIFICATE_OF_TITLE, + ); + expect(mockRepository.findOne).toHaveBeenCalledTimes(1); + expect(res).toBeDefined(); + }); + + it('should delete the existing file and create a new when updating', async () => { + mockRepository.findOne.mockResolvedValue( + new InquiryDocument({ + document: new Document(), + }), + ); + mockInquiryService.getFileNumber.mockResolvedValue('inc-uuid'); + mockRepository.save.mockResolvedValue(new InquiryDocument()); + mockDocumentService.create.mockResolvedValue(new Document()); + mockDocumentService.softRemove.mockResolvedValue(); + + await service.update({ + source: DOCUMENT_SOURCE.APPLICANT, + fileName: 'fileName', + user: new User(), + file: {} as File, + uuid: '', + documentType: DOCUMENT_TYPE.DECISION_DOCUMENT, + }); + + expect(mockRepository.findOne).toHaveBeenCalledTimes(1); + expect(mockInquiryService.getFileNumber).toHaveBeenCalledTimes(1); + expect(mockDocumentService.create).toHaveBeenCalledTimes(1); + expect(mockRepository.save).toHaveBeenCalledTimes(1); + }); +}); diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.service.ts b/services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.service.ts new file mode 100644 index 0000000000..83da9f1d67 --- /dev/null +++ b/services/apps/alcs/src/alcs/inquiry/inquiry-document/inquiry-document.service.ts @@ -0,0 +1,234 @@ +import { MultipartFile } from '@fastify/multipart'; +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { FindOptionsRelations, FindOptionsWhere, Repository } from 'typeorm'; +import { + DOCUMENT_TYPE, + DocumentCode, +} from '../../../document/document-code.entity'; +import { + DOCUMENT_SOURCE, + DOCUMENT_SYSTEM, +} from '../../../document/document.dto'; +import { DocumentService } from '../../../document/document.service'; +import { User } from '../../../user/user.entity'; +import { InquiryService } from '../inquiry.service'; +import { InquiryDocument } from './inquiry-document.entity'; + +@Injectable() +export class InquiryDocumentService { + private DEFAULT_RELATIONS: FindOptionsRelations<InquiryDocument> = { + document: true, + type: true, + }; + + constructor( + private documentService: DocumentService, + private inquiryService: InquiryService, + @InjectRepository(InquiryDocument) + private inquiryDocumentRepository: Repository<InquiryDocument>, + @InjectRepository(DocumentCode) + private documentCodeRepository: Repository<DocumentCode>, + ) {} + + async attachDocument({ + fileNumber, + fileName, + file, + documentType, + user, + system, + source = DOCUMENT_SOURCE.ALC, + }: { + fileNumber: string; + fileName: string; + file: MultipartFile; + user: User; + documentType: DOCUMENT_TYPE; + source?: DOCUMENT_SOURCE; + system: DOCUMENT_SYSTEM; + }) { + const inquiry = await this.inquiryService.getByFileNumber(fileNumber); + const document = await this.documentService.create( + `inquiry/${fileNumber}`, + fileName, + file, + user, + source, + system, + ); + const incDocument = new InquiryDocument({ + typeCode: documentType, + inquiry, + document, + }); + + return this.inquiryDocumentRepository.save(incDocument); + } + + async attachDocumentAsBuffer({ + fileNumber, + fileName, + file, + mimeType, + fileSize, + documentType, + user, + system, + source = DOCUMENT_SOURCE.ALC, + }: { + fileNumber: string; + fileName: string; + file: Buffer; + mimeType: string; + fileSize: number; + user: User; + documentType: DOCUMENT_TYPE; + source?: DOCUMENT_SOURCE; + system: DOCUMENT_SYSTEM; + }) { + const inquiry = await this.inquiryService.getByFileNumber(fileNumber); + const document = await this.documentService.createFromBuffer( + `inquiry/${fileNumber}`, + fileName, + file, + mimeType, + fileSize, + user, + source, + system, + ); + const appDocument = new InquiryDocument({ + typeCode: documentType, + inquiry, + document, + }); + + return await this.inquiryDocumentRepository.save(appDocument); + } + + async get(uuid: string) { + const document = await this.inquiryDocumentRepository.findOne({ + where: { + uuid: uuid, + }, + relations: this.DEFAULT_RELATIONS, + }); + if (!document) { + throw new NotFoundException(`Failed to find document ${uuid}`); + } + return document; + } + + async delete(document: InquiryDocument) { + await this.inquiryDocumentRepository.remove(document); + await this.documentService.softRemove(document.document); + return document; + } + + async list(fileNumber: string) { + const where: FindOptionsWhere<InquiryDocument> = { + inquiry: { + fileNumber, + }, + }; + return this.inquiryDocumentRepository.find({ + where, + order: { + document: { + uploadedAt: 'DESC', + }, + }, + relations: this.DEFAULT_RELATIONS, + }); + } + + async getInlineUrl(document: InquiryDocument) { + return this.documentService.getDownloadUrl(document.document, true); + } + + async getDownloadUrl(document: InquiryDocument) { + return this.documentService.getDownloadUrl(document.document); + } + + async attachExternalDocument( + fileNumber: string, + data: { + type?: DOCUMENT_TYPE; + documentUuid: string; + }, + ) { + const inquiryUuid = await this.inquiryService.getUuid(fileNumber); + const document = new InquiryDocument({ + inquiryUuid, + typeCode: data.type, + documentUuid: data.documentUuid, + }); + + const savedDocument = await this.inquiryDocumentRepository.save(document); + return this.get(savedDocument.uuid); + } + + async deleteByType(documentType: DOCUMENT_TYPE, inquiryUuid: string) { + const documents = await this.inquiryDocumentRepository.find({ + where: { + inquiryUuid, + typeCode: documentType, + }, + relations: { + document: true, + }, + }); + for (const document of documents) { + await this.documentService.softRemove(document.document); + await this.inquiryDocumentRepository.remove(document); + } + + return; + } + + async fetchTypes() { + return await this.documentCodeRepository.find(); + } + + async update({ + uuid, + documentType, + file, + fileName, + source, + user, + }: { + uuid: string; + file?: any; + fileName: string; + documentType: DOCUMENT_TYPE; + source: DOCUMENT_SOURCE; + user: User; + }) { + const inquiryDocument = await this.get(uuid); + + if (file) { + const fileNumber = await this.inquiryService.getFileNumber( + inquiryDocument.inquiryUuid, + ); + await this.documentService.softRemove(inquiryDocument.document); + inquiryDocument.document = await this.documentService.create( + `inquiry/${fileNumber}`, + fileName, + file, + user, + source, + inquiryDocument.document.system as DOCUMENT_SYSTEM, + ); + } else { + await this.documentService.update(inquiryDocument.document, { + fileName, + source, + }); + } + inquiryDocument.type = undefined; + inquiryDocument.typeCode = documentType; + return await this.inquiryDocumentRepository.save(inquiryDocument); + } +} diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.controller.spec.ts b/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.controller.spec.ts new file mode 100644 index 0000000000..f466738fc7 --- /dev/null +++ b/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { InquiryParcelController } from './inquiry-parcel.controller'; + +describe('InquiryParcelController', () => { + let controller: InquiryParcelController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [InquiryParcelController], + }).compile(); + + controller = module.get<InquiryParcelController>(InquiryParcelController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.controller.ts b/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.controller.ts new file mode 100644 index 0000000000..878e3cd553 --- /dev/null +++ b/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.controller.ts @@ -0,0 +1,6 @@ +import { Controller } from '@nestjs/common'; + +@Controller('inquiry-parcel') +export class InquiryParcelController { + // TODO will be implemented in other ticket +} diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.dto.ts b/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.dto.ts new file mode 100644 index 0000000000..b66de650f2 --- /dev/null +++ b/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.dto.ts @@ -0,0 +1,50 @@ +import { AutoMap } from 'automapper-classes'; +import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; + +export class InquiryParcelDto { + @AutoMap() + uuid: string; + + @AutoMap() + inquiryUuid: string; + + @AutoMap(() => String) + pid?: string | null; + + @AutoMap(() => String) + pin?: string | null; + + @AutoMap() + civicAddress: string; +} + +export class InquiryParcelCreateDto { + @IsNotEmpty() + @IsString() + civicAddress: string; + + @IsOptional() + @IsString() + pid?: string | null; + + @IsOptional() + @IsString() + pin?: string | null; +} + +export class InquiryParcelUpdateDto { + @IsString() + uuid: string; + + @IsString() + @IsOptional() + pid?: string | null; + + @IsString() + @IsOptional() + pin?: string | null; + + @IsString() + @IsNotEmpty() + civicAddress: string; +} diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.entity.ts b/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.entity.ts new file mode 100644 index 0000000000..66d117fe9a --- /dev/null +++ b/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.entity.ts @@ -0,0 +1,46 @@ +import { AutoMap } from 'automapper-classes'; +import { Column, Entity, ManyToOne } from 'typeorm'; +import { Base } from '../../../common/entities/base.entity'; +import { Inquiry } from '../inquiry.entity'; + +@Entity({ comment: 'Parcels associated with the inquiries' }) +export class InquiryParcel extends Base { + constructor(data?: Partial<InquiryParcel>) { + super(); + if (data) { + Object.assign(this, data); + } + } + + @AutoMap(() => String) + @Column({ + type: 'text', + comment: + 'The Parcels pid entered by the user or populated from third-party data', + nullable: true, + }) + pid?: string | null; + + @AutoMap(() => String) + @Column({ + type: 'text', + comment: + 'The Parcels pin entered by the user or populated from third-party data', + nullable: true, + }) + pin?: string | null; + + @AutoMap(() => String) + @Column({ + comment: 'The standard address for the parcel', + }) + civicAddress: string; + + @AutoMap() + @ManyToOne(() => Inquiry) + inquiry: Inquiry; + + @AutoMap() + @Column() + inquiryUuid: string; +} diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.service.spec.ts b/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.service.spec.ts new file mode 100644 index 0000000000..abe4f4baee --- /dev/null +++ b/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { InquiryParcelService } from './inquiry-parcel.service'; + +describe('InquiryParcelService', () => { + let service: InquiryParcelService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [InquiryParcelService], + }).compile(); + + service = module.get<InquiryParcelService>(InquiryParcelService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.service.ts b/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.service.ts new file mode 100644 index 0000000000..c998cc3d38 --- /dev/null +++ b/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.service.ts @@ -0,0 +1,6 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class InquiryParcelService { + // TODO will be implemented in other ticket +} diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry-type.entity.ts b/services/apps/alcs/src/alcs/inquiry/inquiry-type.entity.ts new file mode 100644 index 0000000000..d8536ae030 --- /dev/null +++ b/services/apps/alcs/src/alcs/inquiry/inquiry-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({ comment: 'Code table for possible inquiry types' }) +export class InquiryType extends BaseCodeEntity { + constructor(data?: Partial<InquiryType>) { + 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/inquiry/inquiry.controller.spec.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.controller.spec.ts new file mode 100644 index 0000000000..8d55460dc1 --- /dev/null +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { InquiryController } from './inquiry.controller'; + +describe('InquiryController', () => { + let controller: InquiryController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [InquiryController], + }).compile(); + + controller = module.get<InquiryController>(InquiryController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.controller.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.controller.ts new file mode 100644 index 0000000000..1fc2d5dcd1 --- /dev/null +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.controller.ts @@ -0,0 +1,6 @@ +import { Controller } from '@nestjs/common'; + +@Controller('inquiry') +export class InquiryController { + // TODO will be implemented in other ticket +} diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.dto.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.dto.ts new file mode 100644 index 0000000000..fe9a7478e7 --- /dev/null +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.dto.ts @@ -0,0 +1,174 @@ +import { AutoMap } from 'automapper-classes'; +import { Type } from 'class-transformer'; +import { IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; +import { BaseCodeDto } from '../../common/dtos/base.dto'; +import { ApplicationRegionDto } from '../code/application-code/application-region/application-region.dto'; +import { LocalGovernmentDto } from '../local-government/local-government.dto'; +import { InquiryParcelCreateDto } from './inquiry-parcel/inquiry-parcel.dto'; + +export class InquiryTypeDto extends BaseCodeDto { + @AutoMap() + shortLabel: string; + + @AutoMap() + backgroundColor: string; + + @AutoMap() + textColor: string; +} + +export class CreateInquiryDto { + @IsString() + @IsNotEmpty() + summary: string; + + @IsNumber() + @IsNotEmpty() + submittedToAlcDate: number; + + @IsString() + @IsNotEmpty() + localGovernmentUuid: string; + + @IsString() + @IsNotEmpty() + typeCode: string; + + @IsString() + @IsNotEmpty() + regionCode: string; + + @IsString() + @IsOptional() + inquirerFirstName?: string; + + @IsString() + @IsOptional() + inquirerLastName?: string; + + @IsString() + @IsOptional() + inquirerOrganization?: string; + + @IsString() + @IsOptional() + inquirerPhone?: string; + + @IsString() + @IsOptional() + inquirerEmail?: string; + + @IsOptional() + @Type(() => InquiryParcelCreateDto) + parcels?: InquiryParcelCreateDto[]; +} + +export class InquiryDto { + @AutoMap() + uuid: string; + + @AutoMap() + summary: string; + + @AutoMap() + dateSubmittedToAlc: number; + + @AutoMap() + localGovernmentUuid: string; + + @AutoMap() + typeCode: string; + + @AutoMap() + regionCode: string; + + @AutoMap() + inquirerFirstName?: string; + + @AutoMap() + inquirerLastName?: string; + + @AutoMap() + inquirerOrganization?: string; + + @AutoMap() + inquirerPhone?: string; + + @AutoMap() + inquirerEmail?: string; + + @Type(() => InquiryParcelCreateDto) + parcels?: InquiryParcelCreateDto[]; + + @AutoMap(() => LocalGovernmentDto) + localGovernment: LocalGovernmentDto; + + @AutoMap(() => ApplicationRegionDto) + region: ApplicationRegionDto; +} + +export class UpdateInquiryDto { + @IsString() + @IsNotEmpty() + uuid: string; + + @IsString() + @IsNotEmpty() + summary: string; + + @IsNumber() + @IsNotEmpty() + dateSubmittedToAlc: number; + + @IsString() + @IsNotEmpty() + typeCode: string; + + @IsString() + @IsOptional() + inquirerFirstName?: string; + + @IsString() + @IsOptional() + inquirerLastName?: string; + + @IsString() + @IsOptional() + inquirerOrganization?: string; + + @IsString() + @IsOptional() + inquirerPhone?: string; + + @IsString() + @IsOptional() + inquirerEmail?: string; + + @IsOptional() + @Type(() => InquiryParcelCreateDto) + parcels?: InquiryParcelCreateDto[]; +} + +export class CreateInquiryServiceDto { + summary: string; + + dateSubmittedToAlc: Date; + + localGovernmentUuid: string; + + typeCode: string; + + regionCode: string; + + inquirerFirstName?: string; + + inquirerLastName?: string; + + inquirerOrganization?: string; + + inquirerPhone?: string; + + inquirerEmail?: string; + + parcels?: InquiryParcelCreateDto[]; +} diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.entity.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.entity.ts new file mode 100644 index 0000000000..ca9c2755a3 --- /dev/null +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.entity.ts @@ -0,0 +1,102 @@ +import { AutoMap } from 'automapper-classes'; +import { Type } from 'class-transformer'; +import { + Column, + Entity, + Index, + JoinColumn, + ManyToOne, + OneToMany, + OneToOne, +} from 'typeorm'; +import { Base } from '../../common/entities/base.entity'; +import { FILE_NUMBER_SEQUENCE } from '../../file-number/file-number.constants'; +import { User } from '../../user/user.entity'; +import { Card } from '../card/card.entity'; +import { ApplicationRegion } from '../code/application-code/application-region/application-region.entity'; +import { LocalGovernment } from '../local-government/local-government.entity'; +import { InquiryParcel } from './inquiry-parcel/inquiry-parcel.entity'; +import { InquiryType } from './inquiry-type.entity'; + +@Entity({ + comment: + 'Inquiries from the public or other agencies that require a response from the ALC.', +}) +export class Inquiry extends Base { + constructor(data?: Partial<Inquiry>) { + super(); + if (data) { + Object.assign(this, data); + } + } + + @Column({ + unique: true, + default: () => `NEXTVAL('${FILE_NUMBER_SEQUENCE}')`, + }) + fileNumber: string; + + @Column({ type: 'text' }) + summary: string; + + @Column({ type: 'timestamptz' }) + dateSubmittedToAlc: Date; + + @Column({ type: 'varchar', nullable: true }) + inquirerFirstName?: string | null; + + @Column({ type: 'text', nullable: true }) + inquirerLastName?: string | null; + + @Column({ type: 'text', nullable: true }) + inquirerOrganization?: string | null; + + @Column({ type: 'text', nullable: true }) + inquirerPhone?: string | null; + + @Column({ type: 'text', nullable: true }) + inquirerEmail?: string | null; + + @Column({ default: true }) + open: boolean; + + @ManyToOne(() => User) + closedBy: User; + + @Column({ type: 'timestamptz', nullable: true }) + closedDate: Date | null; + + @Index() + @Column({ + type: 'uuid', + }) + localGovernmentUuid: string; + + @ManyToOne(() => LocalGovernment) + localGovernment: LocalGovernment; + + @Column() + regionCode: string; + + @ManyToOne(() => ApplicationRegion) + region: ApplicationRegion; + + @Column() + typeCode: string; + + @AutoMap(() => InquiryType) + @ManyToOne(() => InquiryType, { nullable: false }) + type: InquiryType; + + @Column({ type: 'uuid', nullable: true }) + cardUuid: string | null; + + @OneToOne(() => Card, { cascade: true }) + @JoinColumn() + @Type(() => Card) + card: Card; + + @AutoMap(() => InquiryParcel) + @OneToMany(() => InquiryParcel, (incParcel) => incParcel.inquiry) + parcels: InquiryParcel[]; +} diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.module.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.module.ts new file mode 100644 index 0000000000..79fb457fd9 --- /dev/null +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.module.ts @@ -0,0 +1,45 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { InquiryProfile } from '../../common/automapper/inquiry.automapper.profile'; +import { DocumentModule } from '../../document/document.module'; +import { FileNumberModule } from '../../file-number/file-number.module'; +import { CardModule } from '../card/card.module'; +import { InquiryDocumentController } from './inquiry-document/inquiry-document.controller'; +import { InquiryDocument } from './inquiry-document/inquiry-document.entity'; +import { InquiryDocumentService } from './inquiry-document/inquiry-document.service'; +import { InquiryParcelController } from './inquiry-parcel/inquiry-parcel.controller'; +import { InquiryParcel } from './inquiry-parcel/inquiry-parcel.entity'; +import { InquiryParcelService } from './inquiry-parcel/inquiry-parcel.service'; +import { InquiryType } from './inquiry-type.entity'; +import { InquiryController } from './inquiry.controller'; +import { Inquiry } from './inquiry.entity'; +import { InquiryService } from './inquiry.service'; +import { DocumentCode } from '../../document/document-code.entity'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ + Inquiry, + InquiryType, + InquiryDocument, + InquiryParcel, + DocumentCode, + ]), + CardModule, + FileNumberModule, + DocumentModule, + ], + providers: [ + InquiryService, + InquiryParcelService, + InquiryDocumentService, + InquiryProfile, + ], + controllers: [ + InquiryController, + InquiryParcelController, + InquiryDocumentController, + ], + exports: [InquiryProfile, InquiryService], +}) +export class InquiryModule {} diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.service.spec.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.service.spec.ts new file mode 100644 index 0000000000..a39205c3b7 --- /dev/null +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.service.spec.ts @@ -0,0 +1,251 @@ +import { DeepMocked, createMock } 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 { ServiceNotFoundException } from '../../../../../libs/common/src/exceptions/base.exception'; +import { InquiryProfile } from '../../common/automapper/inquiry.automapper.profile'; +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 { InquiryParcel } from './inquiry-parcel/inquiry-parcel.entity'; +import { InquiryType } from './inquiry-type.entity'; +import { UpdateInquiryDto } from './inquiry.dto'; +import { Inquiry } from './inquiry.entity'; +import { InquiryService } from './inquiry.service'; + +describe('InquiryService', () => { + let service: InquiryService; + let mockCardService: DeepMocked<CardService>; + let mockRepository: DeepMocked<Repository<Inquiry>>; + let mockTypeRepository: DeepMocked<Repository<InquiryType>>; + let mockFileNumberService: DeepMocked<FileNumberService>; + + beforeEach(async () => { + mockCardService = createMock(); + mockRepository = createMock(); + mockTypeRepository = createMock(); + mockFileNumberService = createMock(); + + const module: TestingModule = await Test.createTestingModule({ + imports: [ + AutomapperModule.forRoot({ + strategyInitializer: classes(), + }), + ], + providers: [ + InquiryService, + InquiryProfile, + { + provide: CardService, + useValue: mockCardService, + }, + { + provide: getRepositoryToken(Inquiry), + useValue: mockRepository, + }, + { + provide: getRepositoryToken(InquiryType), + useValue: mockTypeRepository, + }, + { + provide: FileNumberService, + useValue: mockFileNumberService, + }, + ], + }).compile(); + + service = module.get<InquiryService>(InquiryService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + it('should load the type code and call the repo to save when creating', async () => { + const mockCard = new Card(); + const fakeBoard = new Board(); + + const payload = { + dateSubmittedToAlc: new Date(0), + summary: 'fake_s', + fileNumber: 'fake_num', + localGovernmentUuid: 'fake_gov', + typeCode: 'fake_type', + regionCode: 'fake_reg', + inquirerFirstName: 'fake_first', + inquirerLastName: 'fake_last', + inquirerOrganization: 'fake_org', + inquirerPhone: 'fake_phone', + inquirerEmail: 'fake_email', + parcels: [ + { + civicAddress: 'fake_civic', + pid: 'fake_pid', + pin: 'fake_pin', + }, + ], + }; + + mockRepository.findOne.mockResolvedValue( + new Inquiry({ + ...payload, + parcels: [{ ...payload.parcels[0] } as InquiryParcel], + }), + ); + mockRepository.save.mockResolvedValue(new Inquiry()); + mockCardService.create.mockResolvedValue(mockCard); + mockFileNumberService.generateNextFileNumber.mockResolvedValue('fake_num'); + mockTypeRepository.findOneOrFail.mockResolvedValue(new InquiryType()); + + const res = await service.create(payload, fakeBoard); + + expect(mockFileNumberService.generateNextFileNumber).toHaveBeenCalledTimes( + 1, + ); + expect(mockRepository.findOne).toHaveBeenCalledTimes(1); + expect(mockCardService.create).toHaveBeenCalledTimes(1); + expect(mockRepository.save).toHaveBeenCalledTimes(1); + expect(mockRepository.save.mock.calls[0][0].card).toBe(mockCard); + expect(mockTypeRepository.findOneOrFail).toHaveBeenCalledTimes(1); + expect(res).toEqual({ ...payload, fileNumber: 'fake_num' }); + }); + + it('should call through to the repo for get by card', async () => { + mockRepository.findOne.mockResolvedValue(new Inquiry()); + 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 inquiry 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('fake'); + + expect(mockRepository.find).toHaveBeenCalledTimes(1); + }); + + it('should call through to the repo for getBy', async () => { + const mockFilter = { + uuid: '5', + }; + mockRepository.find.mockResolvedValue([]); + await service.getBy(mockFilter); + + expect(mockRepository.find).toHaveBeenCalledTimes(1); + expect(mockRepository.find.mock.calls[0][0]!.where).toEqual(mockFilter); + }); + + it('should call throw an exception when getOrFailByUuid fails', async () => { + mockRepository.findOne.mockResolvedValue(null); + const promise = service.getOrFailByUuid('uuid'); + + await expect(promise).rejects.toMatchObject( + new ServiceNotFoundException(`Failed to find inquiry with uuid uuid`), + ); + + expect(mockRepository.findOne).toHaveBeenCalledTimes(1); + }); + + it('should call through to the repo for getByFileNumber', async () => { + mockRepository.findOneOrFail.mockResolvedValue(new Inquiry()); + await service.getByFileNumber('file'); + + expect(mockRepository.findOneOrFail).toHaveBeenCalledTimes(1); + }); + + it('should call through to the repo for getFileNumber', async () => { + mockRepository.findOneOrFail.mockResolvedValue( + new Inquiry({ + fileNumber: 'fileNumber', + }), + ); + const res = await service.getFileNumber('file'); + + expect(mockRepository.findOneOrFail).toHaveBeenCalledTimes(1); + expect(res).toEqual('fileNumber'); + }); + + it('should call through to the repo for getUuid', async () => { + mockRepository.findOneOrFail.mockResolvedValue( + new Inquiry({ + uuid: 'uuid', + }), + ); + const res = await service.getUuid('file'); + + expect(mockRepository.findOneOrFail).toHaveBeenCalledTimes(1); + expect(res).toEqual('uuid'); + }); + + it('should set values and call save for update', async () => { + const mockCard = new Card(); + const fakeFileNumber = 'fake_num'; + + const payload: UpdateInquiryDto = { + uuid: 'fake', + dateSubmittedToAlc: 0, + summary: 'fake_s', + typeCode: 'fake_type', + inquirerFirstName: 'fake_first', + inquirerLastName: 'fake_last', + inquirerOrganization: 'fake_org', + inquirerPhone: 'fake_phone', + inquirerEmail: 'fake_email', + }; + + const mockInquiry = new Inquiry({ + ...payload, + dateSubmittedToAlc: new Date(0), + fileNumber: fakeFileNumber, + localGovernmentUuid: 'fake_gov', + regionCode: 'fake_reg', + parcels: [], + }); + + mockRepository.findOneOrFail.mockResolvedValue(mockInquiry); + mockRepository.save.mockResolvedValue(new Inquiry()); + mockCardService.create.mockResolvedValue(mockCard); + mockFileNumberService.checkValidFileNumber.mockResolvedValue(true); + mockTypeRepository.findOneByOrFail.mockResolvedValue(new InquiryType()); + + const res = await service.update('fake_num', payload); + + expect(mockRepository.save).toHaveBeenCalledTimes(1); + expect(mockRepository.findOneOrFail).toHaveBeenCalledTimes(2); + expect(mockTypeRepository.findOneByOrFail).toHaveBeenCalledTimes(1); + expect(res).toEqual(mockInquiry); + }); + + 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); + }); + + it('should call through to the repo for searchByFileNumber', async () => { + mockRepository.find.mockResolvedValue([new Inquiry()]); + const res = await service.searchByFileNumber('file'); + + expect(mockRepository.find).toHaveBeenCalledTimes(1); + expect(res.length).toEqual(1); + }); +}); diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.service.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.service.ts new file mode 100644 index 0000000000..a6d03e653d --- /dev/null +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.service.ts @@ -0,0 +1,302 @@ +import { ServiceNotFoundException } from '@app/common/exceptions/base.exception'; +import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Mapper } from 'automapper-core'; +import { InjectMapper } from 'automapper-nestjs'; +import { + FindOptionsRelations, + FindOptionsWhere, + IsNull, + Like, + Not, + Repository, +} from 'typeorm'; +import { FileNumberService } from '../../file-number/file-number.service'; +import { formatIncomingDate } from '../../utils/incoming-date.formatter'; +import { filterUndefined } from '../../utils/undefined'; +import { Board } from '../board/board.entity'; +import { CARD_TYPE } from '../card/card-type/card-type.entity'; +import { CardService } from '../card/card.service'; +import { InquiryParcel } from './inquiry-parcel/inquiry-parcel.entity'; +import { InquiryType } from './inquiry-type.entity'; +import { + CreateInquiryServiceDto, + InquiryDto, + UpdateInquiryDto, +} from './inquiry.dto'; +import { Inquiry } from './inquiry.entity'; + +@Injectable() +export class InquiryService { + private logger = new Logger(InquiryService.name); + + private CARD_RELATIONS = { + board: true, + type: true, + status: true, + assignee: true, + }; + + private DEFAULT_RELATIONS: FindOptionsRelations<Inquiry> = { + card: this.CARD_RELATIONS, + localGovernment: true, + region: true, + type: true, + }; + + constructor( + private cardService: CardService, + @InjectRepository(Inquiry) + private repository: Repository<Inquiry>, + @InjectRepository(InquiryType) + private typeRepository: Repository<InquiryType>, + @InjectMapper() private mapper: Mapper, + private fileNumberService: FileNumberService, + ) {} + + async create(createDto: CreateInquiryServiceDto, board?: Board) { + const fileNumber = await this.fileNumberService.generateNextFileNumber(); + + const type = await this.typeRepository.findOneOrFail({ + where: { + code: createDto.typeCode, + }, + }); + + const inquiry = new Inquiry({ + fileNumber: fileNumber, + summary: createDto.summary, + dateSubmittedToAlc: createDto.dateSubmittedToAlc, + inquirerFirstName: createDto.inquirerFirstName, + inquirerLastName: createDto.inquirerLastName, + inquirerOrganization: createDto.inquirerOrganization, + inquirerPhone: createDto.inquirerPhone, + inquirerEmail: createDto.inquirerEmail, + localGovernmentUuid: createDto.localGovernmentUuid, + regionCode: createDto.regionCode, + type, + }); + + if (board) { + inquiry.card = await this.cardService.create( + CARD_TYPE.INQUIRY, + board, + false, + ); + } + + if (createDto.parcels) { + inquiry.parcels = createDto.parcels.map( + (parcel) => + new InquiryParcel({ + pid: parcel.pid, + pin: parcel.pin, + civicAddress: parcel.civicAddress, + }), + ); + } + + const savedInquiry = await this.repository.save(inquiry); + return await this.getOrFailByUuid(savedInquiry.uuid); + } + + async getOrFailByUuid(uuid: string) { + const inquiry = await this.get(uuid); + if (!inquiry) { + throw new ServiceNotFoundException( + `Failed to find inquiry with uuid ${uuid}`, + ); + } + + return inquiry; + } + + async mapToDtos(inquiries: Inquiry[]) { + return this.mapper.mapArray(inquiries, Inquiry, InquiryDto); + } + + async getByCardUuid(cardUuid: string) { + const inquiry = await this.repository.findOne({ + where: { cardUuid }, + relations: this.DEFAULT_RELATIONS, + }); + + if (!inquiry) { + throw new ServiceNotFoundException( + `Failed to find inquiry with card uuid ${cardUuid}`, + ); + } + + return inquiry; + } + + getBy(findOptions: FindOptionsWhere<Inquiry>) { + return this.repository.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({ + where: { + uuid, + }, + relations: { + ...this.DEFAULT_RELATIONS, + card: { ...this.CARD_RELATIONS, board: false }, + }, + }); + } + + async getByBoard(boardUuid: string) { + return this.repository.find({ + where: { card: { boardUuid } }, + relations: this.DEFAULT_RELATIONS, + }); + } + + 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 getByFileNumber(fileNumber: string) { + return this.repository.findOneOrFail({ + where: { fileNumber }, + relations: this.DEFAULT_RELATIONS, + }); + } + + async update(fileNumber: string, updateDto: UpdateInquiryDto) { + const inquiry = await this.getByFileNumber(fileNumber); + + inquiry.summary = filterUndefined(updateDto.summary, inquiry.summary); + + if (updateDto.typeCode) { + this.typeRepository.findOneByOrFail({ + code: updateDto.typeCode, + }); + inquiry.typeCode = filterUndefined(updateDto.typeCode, inquiry.typeCode); + } + + inquiry.dateSubmittedToAlc = filterUndefined( + formatIncomingDate(updateDto.dateSubmittedToAlc), + inquiry.dateSubmittedToAlc, + ); + + inquiry.inquirerFirstName = filterUndefined( + updateDto.inquirerFirstName, + inquiry.inquirerFirstName, + ); + + inquiry.inquirerLastName = filterUndefined( + updateDto.inquirerLastName, + inquiry.inquirerLastName, + ); + + inquiry.inquirerOrganization = filterUndefined( + updateDto.inquirerOrganization, + inquiry.inquirerOrganization, + ); + + inquiry.inquirerPhone = filterUndefined( + updateDto.inquirerPhone, + inquiry.inquirerPhone, + ); + + inquiry.inquirerEmail = filterUndefined( + updateDto.inquirerEmail, + inquiry.inquirerEmail, + ); + + // TODO complete open and closed + // inquiry.open = filterUndefined(updateDto.open, inquiry.open); + + // if (updateDto.closedDate) { + // inquiry.closedDate = filterUndefined( + // formatIncomingDate(updateDto.closedDate), + // inquiry.closedDate, + // ); + // inquiry.open = false; + // } + + await this.repository.save(inquiry); + + return this.getByFileNumber(inquiry.fileNumber); + } + + async listTypes() { + return this.typeRepository.find(); + } + + async searchByFileNumber(fileNumber: string) { + return this.repository.find({ + where: { + fileNumber: Like(`${fileNumber}%`), + }, + order: { + fileNumber: 'ASC', + }, + relations: { + region: true, + localGovernment: true, + }, + }); + } + + async getFileNumber(uuid: string) { + const inquiry = await this.repository.findOneOrFail({ + where: { + uuid, + }, + select: { + fileNumber: true, + }, + }); + return inquiry.fileNumber; + } + + async getUuid(fileNumber: string) { + const inquiry = await this.repository.findOneOrFail({ + where: { + fileNumber, + }, + select: { + uuid: true, + }, + }); + return inquiry.uuid; + } +} diff --git a/services/apps/alcs/src/alcs/notification/notification-document/notification-document.entity.ts b/services/apps/alcs/src/alcs/notification/notification-document/notification-document.entity.ts index 98edea46a3..b7c2713f7b 100644 --- a/services/apps/alcs/src/alcs/notification/notification-document/notification-document.entity.ts +++ b/services/apps/alcs/src/alcs/notification/notification-document/notification-document.entity.ts @@ -56,10 +56,20 @@ export class NotificationDocument extends BaseEntity { @Column({ nullable: true, type: 'uuid' }) documentUuid?: string | null; - @Column({ type: 'text', nullable: true }) + @Column({ + type: 'text', + nullable: true, + comment: + 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.alr_application to alcs.notification_document.', + }) oatsApplicationId?: string | null; - @Column({ type: 'text', nullable: true }) + @Column({ + type: 'text', + nullable: true, + comment: + 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.documents/alcs.documents to alcs.notification_document.', + }) oatsDocumentId?: string | null; @AutoMap(() => [String]) diff --git a/services/apps/alcs/src/alcs/notification/notification.service.spec.ts b/services/apps/alcs/src/alcs/notification/notification.service.spec.ts index 5b05300e6b..9cb71ada4b 100644 --- a/services/apps/alcs/src/alcs/notification/notification.service.spec.ts +++ b/services/apps/alcs/src/alcs/notification/notification.service.spec.ts @@ -146,7 +146,7 @@ describe('NotificationService', () => { const promise = service.getByCardUuid(cardUuid); await expect(promise).rejects.toMatchObject( - new Error(`Failed to find notice of intent with card uuid ${cardUuid}`), + new Error(`Failed to find notification with card uuid ${cardUuid}`), ); expect(mockRepository.findOne).toHaveBeenCalledTimes(1); @@ -176,7 +176,7 @@ describe('NotificationService', () => { await expect(promise).rejects.toMatchObject( new ServiceNotFoundException( - `Failed to find notice of intent with uuid uuid`, + `Failed to find notification with uuid uuid`, ), ); diff --git a/services/apps/alcs/src/alcs/notification/notification.service.ts b/services/apps/alcs/src/alcs/notification/notification.service.ts index e07ffe3716..4ff8c026ce 100644 --- a/services/apps/alcs/src/alcs/notification/notification.service.ts +++ b/services/apps/alcs/src/alcs/notification/notification.service.ts @@ -2,10 +2,10 @@ import { ServiceNotFoundException, ServiceValidationException, } from '@app/common/exceptions/base.exception'; -import { Mapper } from 'automapper-core'; -import { InjectMapper } from 'automapper-nestjs'; import { Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { Mapper } from 'automapper-core'; +import { InjectMapper } from 'automapper-nestjs'; import { FindOptionsRelations, FindOptionsWhere, @@ -90,7 +90,7 @@ export class NotificationService { if (board) { notification.card = await this.cardService.create( - CARD_TYPE.NOI, + CARD_TYPE.NOTIFICATION, board, false, ); @@ -108,7 +108,7 @@ export class NotificationService { const notification = await this.get(uuid); if (!notification) { throw new ServiceNotFoundException( - `Failed to find notice of intent with uuid ${uuid}`, + `Failed to find notification with uuid ${uuid}`, ); } @@ -127,7 +127,7 @@ export class NotificationService { if (!notification) { throw new ServiceNotFoundException( - `Failed to find notice of intent with card uuid ${cardUuid}`, + `Failed to find notification with card uuid ${cardUuid}`, ); } diff --git a/services/apps/alcs/src/alcs/staff-journal/staff-journal.entity.ts b/services/apps/alcs/src/alcs/staff-journal/staff-journal.entity.ts index d9726c024a..de2401b84e 100644 --- a/services/apps/alcs/src/alcs/staff-journal/staff-journal.entity.ts +++ b/services/apps/alcs/src/alcs/staff-journal/staff-journal.entity.ts @@ -3,6 +3,7 @@ import { Column, CreateDateColumn, Entity, Index, ManyToOne } from 'typeorm'; import { Base } from '../../common/entities/base.entity'; import { User } from '../../user/user.entity'; import { Application } from '../application/application.entity'; +import { Inquiry } from '../inquiry/inquiry.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'; @@ -56,4 +57,11 @@ export class StaffJournal extends Base { @Column({ nullable: true }) @Index() planningReviewUuid: string; + + @ManyToOne(() => Inquiry) + inquiry: Inquiry | null; + + @Column({ nullable: true }) + @Index() + inquiryUuid: string; } diff --git a/services/apps/alcs/src/common/automapper/inquiry.automapper.profile.ts b/services/apps/alcs/src/common/automapper/inquiry.automapper.profile.ts new file mode 100644 index 0000000000..76996d0a54 --- /dev/null +++ b/services/apps/alcs/src/common/automapper/inquiry.automapper.profile.ts @@ -0,0 +1,72 @@ +import { Injectable } from '@nestjs/common'; +import { createMap, forMember, mapFrom, Mapper } from 'automapper-core'; +import { AutomapperProfile, InjectMapper } from 'automapper-nestjs'; +import { InquiryDocumentDto } from '../../alcs/inquiry/inquiry-document/inquiry-document.dto'; +import { InquiryDocument } from '../../alcs/inquiry/inquiry-document/inquiry-document.entity'; +import { InquiryType } from '../../alcs/inquiry/inquiry-type.entity'; +import { InquiryDto, InquiryTypeDto } from '../../alcs/inquiry/inquiry.dto'; +import { Inquiry } from '../../alcs/inquiry/inquiry.entity'; +import { DocumentCode } from '../../document/document-code.entity'; +import { DocumentTypeDto } from '../../document/document.dto'; + +@Injectable() +export class InquiryProfile extends AutomapperProfile { + constructor(@InjectMapper() mapper: Mapper) { + super(mapper); + } + + override get profile() { + return (mapper) => { + createMap(mapper, InquiryType, InquiryTypeDto); + + createMap( + mapper, + Inquiry, + InquiryDto, + forMember( + (a) => a.dateSubmittedToAlc, + mapFrom((ad) => ad.dateSubmittedToAlc?.getTime()), + ), + ); + + createMap( + mapper, + InquiryDocument, + InquiryDocumentDto, + forMember( + (a) => a.mimeType, + mapFrom((ad) => ad.document.mimeType), + ), + forMember( + (a) => a.fileName, + mapFrom((ad) => ad.document.fileName), + ), + forMember( + (a) => a.fileSize, + mapFrom((ad) => ad.document.fileSize), + ), + forMember( + (a) => a.uploadedBy, + mapFrom((ad) => ad.document.uploadedBy?.name), + ), + forMember( + (a) => a.uploadedAt, + mapFrom((ad) => ad.document.uploadedAt.getTime()), + ), + forMember( + (a) => a.documentUuid, + mapFrom((ad) => ad.document.uuid), + ), + forMember( + (a) => a.source, + mapFrom((ad) => ad.document.source), + ), + forMember( + (a) => a.system, + mapFrom((ad) => ad.document.system), + ), + ); + createMap(mapper, DocumentCode, DocumentTypeDto); + }; + } +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1710371089218-inquiry.ts b/services/apps/alcs/src/providers/typeorm/migrations/1710371089218-inquiry.ts new file mode 100644 index 0000000000..c80451511d --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1710371089218-inquiry.ts @@ -0,0 +1,105 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class Inquiry1710371089218 implements MigrationInterface { + name = 'Inquiry1710371089218'; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `CREATE TABLE "alcs"."inquiry_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_9caab940baead9499eae4fd0ed8" UNIQUE ("description"), CONSTRAINT "PK_1a0ca48142efa882f687fbed1a4" PRIMARY KEY ("code"))`, + ); + await queryRunner.query( + `CREATE TABLE "alcs"."inquiry" ("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(), "summary" text NOT NULL, "date_submitted_to_alc" TIMESTAMP WITH TIME ZONE NOT NULL, "inquirer_first_name" character varying, "inquirer_last_name" text, "inquirer_organization" text, "inquirer_phone" text, "inquirer_email" text, "open" boolean NOT NULL DEFAULT true, "closed_date" TIMESTAMP WITH TIME ZONE, "local_government_uuid" uuid NOT NULL, "region_code" text NOT NULL, "type_code" text NOT NULL, "card_uuid" uuid, "closed_by_uuid" uuid, CONSTRAINT "REL_217c73ca560363e2391d59ef75" UNIQUE ("card_uuid"), CONSTRAINT "PK_6063787da196f8f53f968ace0d0" PRIMARY KEY ("uuid"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_3b17d9cc412828dd0f1b416481" ON "alcs"."inquiry" ("local_government_uuid") `, + ); + await queryRunner.query( + `CREATE TABLE "alcs"."inquiry_parcel" ("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(), "pid" text, "pin" text, "civic_address" character varying NOT NULL, "inquiry_uuid" uuid NOT NULL, CONSTRAINT "PK_2e38295de61ab5e9f2f5b222c37" PRIMARY KEY ("uuid")); COMMENT ON COLUMN "alcs"."inquiry_parcel"."pid" IS 'The Parcels pid entered by the user or populated from third-party data'; COMMENT ON COLUMN "alcs"."inquiry_parcel"."pin" IS 'The Parcels pin entered by the user or populated from third-party data'; COMMENT ON COLUMN "alcs"."inquiry_parcel"."civic_address" IS 'The standard address for the parcel'`, + ); + await queryRunner.query( + `CREATE TABLE "alcs"."inquiry_document" ("uuid" uuid NOT NULL DEFAULT gen_random_uuid(), "type_code" text, "inquiry_uuid" uuid NOT NULL, "document_uuid" uuid, CONSTRAINT "REL_fbb716772d7eff5b5fb454bb26" UNIQUE ("document_uuid"), CONSTRAINT "PK_e3fda23cbde427b27e20d70fb5f" PRIMARY KEY ("uuid"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_1ee43ebfd0d1cb4148331c2ac9" ON "alcs"."inquiry_document" ("inquiry_uuid") `, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."inquiry_document" IS 'Stores inquiry documents'`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "alcs"."notification_document"."oats_application_id" IS 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.alr_application to alcs.notification_document.'`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry" ADD CONSTRAINT "FK_3fcfda215d023cf892bf0b3048a" FOREIGN KEY ("closed_by_uuid") REFERENCES "alcs"."user"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry" ADD CONSTRAINT "FK_3b17d9cc412828dd0f1b4164818" FOREIGN KEY ("local_government_uuid") REFERENCES "alcs"."local_government"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry" ADD CONSTRAINT "FK_40858ba1a310330fdea4d56a85c" FOREIGN KEY ("region_code") REFERENCES "alcs"."application_region"("code") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry" ADD CONSTRAINT "FK_1a0ca48142efa882f687fbed1a4" FOREIGN KEY ("type_code") REFERENCES "alcs"."inquiry_type"("code") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry" ADD CONSTRAINT "FK_217c73ca560363e2391d59ef75d" FOREIGN KEY ("card_uuid") REFERENCES "alcs"."card"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry_parcel" ADD CONSTRAINT "FK_593a8419d704330268b92565f3a" FOREIGN KEY ("inquiry_uuid") REFERENCES "alcs"."inquiry"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry_document" ADD CONSTRAINT "FK_57359f6ad774ef7a18ebed83d13" FOREIGN KEY ("type_code") REFERENCES "alcs"."document_code"("code") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry_document" ADD CONSTRAINT "FK_1ee43ebfd0d1cb4148331c2ac9d" FOREIGN KEY ("inquiry_uuid") REFERENCES "alcs"."inquiry"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry_document" ADD CONSTRAINT "FK_fbb716772d7eff5b5fb454bb267" FOREIGN KEY ("document_uuid") REFERENCES "alcs"."document"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry_document" DROP CONSTRAINT "FK_fbb716772d7eff5b5fb454bb267"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry_document" DROP CONSTRAINT "FK_1ee43ebfd0d1cb4148331c2ac9d"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry_document" DROP CONSTRAINT "FK_57359f6ad774ef7a18ebed83d13"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry_parcel" DROP CONSTRAINT "FK_593a8419d704330268b92565f3a"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry" DROP CONSTRAINT "FK_217c73ca560363e2391d59ef75d"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry" DROP CONSTRAINT "FK_1a0ca48142efa882f687fbed1a4"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry" DROP CONSTRAINT "FK_40858ba1a310330fdea4d56a85c"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry" DROP CONSTRAINT "FK_3b17d9cc412828dd0f1b4164818"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry" DROP CONSTRAINT "FK_3fcfda215d023cf892bf0b3048a"`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "alcs"."notification_document"."oats_application_id" IS 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.documents/alcs.documents to alcs.notification_document.'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."inquiry_document" IS NULL`, + ); + await queryRunner.query( + `DROP INDEX "alcs"."IDX_1ee43ebfd0d1cb4148331c2ac9"`, + ); + await queryRunner.query(`DROP TABLE "alcs"."inquiry_document"`); + await queryRunner.query(`DROP TABLE "alcs"."inquiry_parcel"`); + await queryRunner.query( + `DROP INDEX "alcs"."IDX_3b17d9cc412828dd0f1b416481"`, + ); + await queryRunner.query(`DROP TABLE "alcs"."inquiry"`); + await queryRunner.query(`DROP TABLE "alcs"."inquiry_type"`); + } +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1710373199878-inquiry_staff_journal.ts b/services/apps/alcs/src/providers/typeorm/migrations/1710373199878-inquiry_staff_journal.ts new file mode 100644 index 0000000000..3de409dbb6 --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1710373199878-inquiry_staff_journal.ts @@ -0,0 +1,29 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class InquiryStaffJournal1710373199878 implements MigrationInterface { + name = 'InquiryStaffJournal1710373199878'; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "alcs"."staff_journal" ADD "inquiry_uuid" uuid`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_4b9a68b9c2f91567e84a604423" ON "alcs"."staff_journal" ("inquiry_uuid") `, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."staff_journal" ADD CONSTRAINT "FK_4b9a68b9c2f91567e84a6044235" FOREIGN KEY ("inquiry_uuid") REFERENCES "alcs"."inquiry"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "alcs"."staff_journal" DROP CONSTRAINT "FK_4b9a68b9c2f91567e84a6044235"`, + ); + await queryRunner.query( + `DROP INDEX "alcs"."IDX_4b9a68b9c2f91567e84a604423"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."staff_journal" DROP COLUMN "inquiry_uuid"`, + ); + } +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1710437289952-file_number.ts b/services/apps/alcs/src/providers/typeorm/migrations/1710437289952-file_number.ts new file mode 100644 index 0000000000..b1976472b6 --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1710437289952-file_number.ts @@ -0,0 +1,23 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class FileNumber1710437289952 implements MigrationInterface { + name = 'FileNumber1710437289952'; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry" ADD "file_number" character varying NOT NULL DEFAULT NEXTVAL('alcs.alcs_file_number_seq')`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry" ADD CONSTRAINT "UQ_419b155603f3444977c64c6dd72" UNIQUE ("file_number")`, + ); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry" DROP CONSTRAINT "UQ_419b155603f3444977c64c6dd72"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."inquiry" DROP COLUMN "file_number"`, + ); + } +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1710791560891-added_comments_and_types_to_inquiries.ts b/services/apps/alcs/src/providers/typeorm/migrations/1710791560891-added_comments_and_types_to_inquiries.ts new file mode 100644 index 0000000000..19c6f6871d --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1710791560891-added_comments_and_types_to_inquiries.ts @@ -0,0 +1,79 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddedCommentsAndTypesToInquiries1710791560891 + implements MigrationInterface +{ + name = 'AddedCommentsAndTypesToInquiries1710791560891'; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `COMMENT ON TABLE "alcs"."inquiry_parcel" IS 'Parcels associated with the inquiries'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."inquiry_type" IS 'Code table for possible inquiry types'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."inquiry" IS 'Inquiries from the public or other agencies that require a response from the ALC.'`, + ); + + await queryRunner.query( + ` + INSERT INTO alcs.inquiry_type (audit_created_by,"label",code,description,short_label,background_color,text_color,html_description) VALUES + ('migration_seed','General Correspondence','GENC','Inquiries or correspondence that aren’t property specific or don’t fit other inquiry types','GEN','#0B5656','#FFFFFF','Inquiries or correspondence that aren’t property specific or don’t fit other inquiry types'), + ('migration_seed','Inquiry for Investigation','INVN','Requests to confirm if a use is permitted or not, generally property specific','INV','#8E7F04','#FFFFFF','Requests to confirm if a use is permitted or not, generally property specific'), + ('migration_seed','Subdivision by Approving Officer','SAOF','Submission of Subdivision Plans approved by an Approving Officer under Section 3 of the ALR General Regulation.','SAO','#B07213','#FFFFFF','Submission of Subdivision Plans approved by an Approving Officer under Section 3 of the ALR General Regulation.'), + ('migration_seed','Referral','REFR','A request for ALC comment on a Ministry referral or L/FNG property specific bylaw or other planning document request that is not a Planning Review – often based on an ALC Application approval.','REF','#1D3251','#FFFFFF','A request for ALC comment on a Ministry referral or L/FNG property specific bylaw or other planning document request that is not a Planning Review – often based on an ALC Application approval.'), + ('migration_seed','Parcel Under 2 Acres','P2AC','Requests to confirm if a parcel meets ALCA Section 23(1) exception requirements.','P2A','#752053','#FFFFFF','Requests to confirm if a parcel meets ALCA Section 23(1) exception requirements.'), + ('migration_seed','ALR Boundary Definition','ABDF','Requests to confirm the legal ALR boundary. E.g. Is the ALR mapping correct or letters confirming ALC interests are unaffected for uses/subdivisions on the non-ALR portion of partial ALR properties.','ABD','#49701E','#FFFFFF','Requests to confirm the legal ALR boundary. E.g. Is the ALR mapping correct or letters confirming ALC interests are unaffected for uses/subdivisions on the non-ALR portion of partial ALR properties.'); + `, + ); + + await queryRunner.query(` + INSERT INTO alcs.board (uuid,audit_created_by,code,title,show_on_schedule) VALUES + ('c24234e9-748c-48db-9a0f-88e447473c8e', 'migration_seed','incr','Inquiries',false); + `); + + await queryRunner.query(` + INSERT INTO alcs.card_status (audit_created_by,"label",code,description) VALUES + ('migration_seed','Closed','CLSD','TBD CLSD'), + ('migration_seed','Drafting Response','DRRE','TBD DRRE'), + ('migration_seed','Incoming','INCM','TBD INCM'), + ('migration_seed','Waiting on Inquirer','WAIN','TBD WAIN'), + ('migration_seed','More Research Needed','MRNE','TBD MRNE'), + ('migration_seed','Waiting on ALC Review','WARE','TBD WARE'), + ('migration_seed','ALC Review Complete','ALRC','TBD ALRC'); + `); + + await queryRunner.query(` + INSERT INTO alcs.board_status (audit_created_by,"order",board_uuid,status_code) VALUES + ('migration_seed',0,'c24234e9-748c-48db-9a0f-88e447473c8e','INCM'), + ('migration_seed',1,'c24234e9-748c-48db-9a0f-88e447473c8e','WAIN'), + ('migration_seed',2,'c24234e9-748c-48db-9a0f-88e447473c8e','MRNE'), + ('migration_seed',3,'c24234e9-748c-48db-9a0f-88e447473c8e','WARE'), + ('migration_seed',4,'c24234e9-748c-48db-9a0f-88e447473c8e','ALRC'), + ('migration_seed',5,'c24234e9-748c-48db-9a0f-88e447473c8e','DRRE'), + ('migration_seed',6,'c24234e9-748c-48db-9a0f-88e447473c8e','CLSD'); + `); + + await queryRunner.query(` + INSERT INTO alcs.card_type (audit_created_by,"label",code,description,portal_html_description) VALUES + ('migration_seed','Inquiry','INQR','Card type for inquiries',''); + `); + + await queryRunner.query(` + INSERT INTO alcs.board_create_card_types_card_type (board_uuid,card_type_code) VALUES + ('c24234e9-748c-48db-9a0f-88e447473c8e','INQR'); + `); + + await queryRunner.query(` + INSERT INTO alcs.board_allowed_card_types_card_type (board_uuid,card_type_code) VALUES + ('c24234e9-748c-48db-9a0f-88e447473c8e','INQR'); + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(`COMMENT ON TABLE "alcs"."inquiry" IS NULL`); + await queryRunner.query(`COMMENT ON TABLE "alcs"."inquiry_type" IS NULL`); + await queryRunner.query(`COMMENT ON TABLE "alcs"."inquiry_parcel" IS NULL`); + } +} From 2b9483612b79a3516e07365e4ff8d621501ad594 Mon Sep 17 00:00:00 2001 From: Tristan Slater <1631008+trslater@users.noreply.github.com> Date: Thu, 21 Mar 2024 09:13:13 -0700 Subject: [PATCH 031/153] Fix `draftMode` not set `true` for NARU --- .../alcs-edit-submission/alcs-edit-submission.component.html | 1 + 1 file changed, 1 insertion(+) diff --git a/portal-frontend/src/app/features/applications/alcs-edit-submission/alcs-edit-submission.component.html b/portal-frontend/src/app/features/applications/alcs-edit-submission/alcs-edit-submission.component.html index 8dd0f56693..579016d641 100644 --- a/portal-frontend/src/app/features/applications/alcs-edit-submission/alcs-edit-submission.component.html +++ b/portal-frontend/src/app/features/applications/alcs-edit-submission/alcs-edit-submission.component.html @@ -135,6 +135,7 @@ <h6 *ngIf="applicationSubmission"> [$applicationSubmission]="$applicationSubmission" [$applicationDocuments]="$applicationDocuments" [showErrors]="showValidationErrors" + [draftMode]="true" (navigateToStep)="switchStep($event)" (exit)="onExit()" ></app-naru-proposal> From 539ede0def02c1fefb8a10d19896290f50743a61 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Wed, 20 Mar 2024 11:33:03 -0700 Subject: [PATCH 032/153] Delete V1 Decisions + Add PR Meetings to Commissioner Controller * Move Application Decision Meetings to Application Module, they do not reference decisions but are used in other Application services * Move the controller to a new meeting module that will serve both App and PR meetings * Clean up old V1 Decision Code --- .../decision-meeting-dialog.component.ts | 8 +- .../decision-meeting.component.ts | 4 +- .../commissioner-application.component.html} | 0 .../commissioner-application.component.scss} | 0 ...ommissioner-application.component.spec.ts} | 14 +- .../commissioner-application.component.ts} | 20 +- .../commissioner/commissioner.module.ts | 16 +- ...ommissioner-planning-review.component.html | 13 + ...ommissioner-planning-review.component.scss | 18 + ...issioner-planning-review.component.spec.ts | 40 ++ .../commissioner-planning-review.component.ts | 45 ++ .../header/header.component.ts | 17 +- .../planning-review.component.html | 2 +- .../planning-review/planning-review.module.ts | 1 + ...plication-decision-meeting.service.spec.ts | 12 +- .../application-decision-meeting.service.ts | 11 +- .../application-modification.service.spec.ts | 22 +- .../application-modification.service.ts | 11 +- .../application-reconsideration.service.ts | 13 +- .../services/commissioner/commissioner.dto.ts | 11 + .../commissioner/commissioner.service.ts | 8 +- .../decision-meeting/decision-meeting.dto.ts | 2 + .../decision-meeting.service.ts | 7 +- .../meeting-overview.component.html | 34 +- .../meeting-overview.component.scss | 14 +- .../meeting-overview.component.spec.ts | 2 + .../meeting-overview.component.ts | 69 ++- .../application-ceo-criterion.controller.ts | 2 +- .../application-ceo-criterion.service.ts | 2 +- services/apps/alcs/src/alcs/alcs.module.ts | 3 + .../application-decision-v1.module.ts | 82 --- ...application-decision-v1.controller.spec.ts | 329 ------------ .../application-decision-v1.controller.ts | 238 --------- .../application-decision-v1.service.spec.ts | 479 ----------------- .../application-decision-v1.service.ts | 501 ------------------ .../application-decision.dto.ts | 175 ------ .../ceo-criterion/ceo-criterion.dto.ts | 9 - .../application-decision-v2.module.ts | 15 +- .../application-decision.module.ts | 6 +- .../application-modification.dto.ts | 6 +- .../application-modification.service.spec.ts | 6 +- .../application-modification.service.ts | 8 +- .../application-reconsideration.dto.ts | 6 +- ...pplication-reconsideration.service.spec.ts | 6 +- .../application-reconsideration.service.ts | 4 +- .../application-decision-meeting.entity.ts | 4 +- ...plication-decision-meeting.service.spec.ts | 10 +- .../application-decision-meeting.service.ts | 13 +- .../application-timeline.service.spec.ts | 4 +- .../application-timeline.service.ts | 2 +- .../src/alcs/application/application.dto.ts | 6 +- .../alcs/application/application.entity.ts | 2 +- .../alcs/application/application.module.ts | 5 + .../commissioner.controller.spec.ts | 8 + .../commissioner/commissioner.controller.ts | 28 +- .../src/alcs/commissioner/commissioner.dto.ts | 25 + .../alcs/commissioner/commissioner.module.ts | 5 +- .../decision-meeting.controller.spec.ts} | 61 ++- .../decision-meeting.controller.ts} | 111 ++-- .../decision-meeting.dto.ts} | 5 +- .../alcs/src/alcs/meetings/meeting.module.ts | 13 + .../planning-referral.service.ts | 13 +- .../planning-review-meeting.service.ts | 15 + .../planning-review/planning-review.module.ts | 6 +- .../planning-review.service.ts | 7 +- ...lication-decision-v1.automapper.profile.ts | 205 ------- .../application.automapper.profile.ts | 16 +- .../commissioner.automapper.profile.ts | 9 +- .../modification.automapper.profile.ts | 14 +- .../reconsideration.automapper.profile.ts | 6 +- .../application/application-decision.dto.ts | 4 +- .../notice-of-intent-decision.dto.ts | 2 +- services/apps/alcs/test/mocks/mockEntities.ts | 2 +- 73 files changed, 612 insertions(+), 2280 deletions(-) rename alcs-frontend/src/app/features/commissioner/{commissioner.component.html => application/commissioner-application.component.html} (100%) rename alcs-frontend/src/app/features/commissioner/{commissioner.component.scss => application/commissioner-application.component.scss} (100%) rename alcs-frontend/src/app/features/commissioner/{commissioner.component.spec.ts => application/commissioner-application.component.spec.ts} (64%) rename alcs-frontend/src/app/features/commissioner/{commissioner.component.ts => application/commissioner-application.component.ts} (60%) create mode 100644 alcs-frontend/src/app/features/commissioner/planning-review/commissioner-planning-review.component.html create mode 100644 alcs-frontend/src/app/features/commissioner/planning-review/commissioner-planning-review.component.scss create mode 100644 alcs-frontend/src/app/features/commissioner/planning-review/commissioner-planning-review.component.spec.ts create mode 100644 alcs-frontend/src/app/features/commissioner/planning-review/commissioner-planning-review.component.ts delete mode 100644 services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision-v1.module.ts delete mode 100644 services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision/application-decision-v1.controller.spec.ts delete mode 100644 services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision/application-decision-v1.controller.ts delete mode 100644 services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision/application-decision-v1.service.spec.ts delete mode 100644 services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision/application-decision-v1.service.ts delete mode 100644 services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision/application-decision.dto.ts delete mode 100644 services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision/ceo-criterion/ceo-criterion.dto.ts rename services/apps/alcs/src/alcs/{application-decision/application-decision-v1 => application}/application-decision-meeting/application-decision-meeting.entity.ts (78%) rename services/apps/alcs/src/alcs/{application-decision/application-decision-v1 => application}/application-decision-meeting/application-decision-meeting.service.spec.ts (92%) rename services/apps/alcs/src/alcs/{application-decision/application-decision-v1 => application}/application-decision-meeting/application-decision-meeting.service.ts (90%) rename services/apps/alcs/src/alcs/{application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.controller.spec.ts => meetings/decision-meeting.controller.spec.ts} (72%) rename services/apps/alcs/src/alcs/{application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.controller.ts => meetings/decision-meeting.controller.ts} (57%) rename services/apps/alcs/src/alcs/{application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.dto.ts => meetings/decision-meeting.dto.ts} (78%) create mode 100644 services/apps/alcs/src/alcs/meetings/meeting.module.ts delete mode 100644 services/apps/alcs/src/common/automapper/application-decision-v1.automapper.profile.ts diff --git a/alcs-frontend/src/app/features/application/review/decision-meeting-dialog/decision-meeting-dialog.component.ts b/alcs-frontend/src/app/features/application/review/decision-meeting-dialog/decision-meeting-dialog.component.ts index 6cb029cf99..afcbcab8a0 100644 --- a/alcs-frontend/src/app/features/application/review/decision-meeting-dialog/decision-meeting-dialog.component.ts +++ b/alcs-frontend/src/app/features/application/review/decision-meeting-dialog/decision-meeting-dialog.component.ts @@ -5,7 +5,11 @@ import { ApplicationDetailService } from '../../../../services/application/appli import { ToastService } from '../../../../services/toast/toast.service'; export class ApplicationDecisionMeetingForm { - constructor(public fileNumber: string, public date: Date, public uuid: string | undefined = undefined) {} + constructor( + public fileNumber: string, + public date: Date, + public uuid: string | undefined = undefined, + ) {} } @Component({ @@ -22,7 +26,7 @@ export class DecisionMeetingDialogComponent { private dialogRef: MatDialogRef<DecisionMeetingDialogComponent>, private decisionMeetingService: ApplicationDecisionMeetingService, private applicationDetailService: ApplicationDetailService, - private toastService: ToastService + private toastService: ToastService, ) { if (data.uuid) { this.model = { diff --git a/alcs-frontend/src/app/features/application/review/decision-meeting/decision-meeting.component.ts b/alcs-frontend/src/app/features/application/review/decision-meeting/decision-meeting.component.ts index be4a61b852..aacfabb304 100644 --- a/alcs-frontend/src/app/features/application/review/decision-meeting/decision-meeting.component.ts +++ b/alcs-frontend/src/app/features/application/review/decision-meeting/decision-meeting.component.ts @@ -29,12 +29,12 @@ export class DecisionMeetingComponent implements OnInit { private decisionMeetingService: ApplicationDecisionMeetingService, private confirmationDialogService: ConfirmationDialogService, private applicationDetailService: ApplicationDetailService, - private toastService: ToastService + private toastService: ToastService, ) {} ngOnInit(): void { this.decisionMeetingService.$decisionMeetings.subscribe( - (meetings) => (this.decisionMeetings = meetings.sort((a, b) => (a.date >= b.date ? -1 : 1))) + (meetings) => (this.decisionMeetings = meetings.sort((a, b) => (a.date >= b.date ? -1 : 1))), ); } diff --git a/alcs-frontend/src/app/features/commissioner/commissioner.component.html b/alcs-frontend/src/app/features/commissioner/application/commissioner-application.component.html similarity index 100% rename from alcs-frontend/src/app/features/commissioner/commissioner.component.html rename to alcs-frontend/src/app/features/commissioner/application/commissioner-application.component.html diff --git a/alcs-frontend/src/app/features/commissioner/commissioner.component.scss b/alcs-frontend/src/app/features/commissioner/application/commissioner-application.component.scss similarity index 100% rename from alcs-frontend/src/app/features/commissioner/commissioner.component.scss rename to alcs-frontend/src/app/features/commissioner/application/commissioner-application.component.scss diff --git a/alcs-frontend/src/app/features/commissioner/commissioner.component.spec.ts b/alcs-frontend/src/app/features/commissioner/application/commissioner-application.component.spec.ts similarity index 64% rename from alcs-frontend/src/app/features/commissioner/commissioner.component.spec.ts rename to alcs-frontend/src/app/features/commissioner/application/commissioner-application.component.spec.ts index bcf121aca4..19d1ea86bd 100644 --- a/alcs-frontend/src/app/features/commissioner/commissioner.component.spec.ts +++ b/alcs-frontend/src/app/features/commissioner/application/commissioner-application.component.spec.ts @@ -2,13 +2,13 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { CommissionerService } from '../../services/commissioner/commissioner.service'; +import { CommissionerService } from '../../../services/commissioner/commissioner.service'; -import { CommissionerComponent } from './commissioner.component'; +import { CommissionerApplicationComponent } from './commissioner-application.component'; -describe('CommissionerComponent', () => { - let component: CommissionerComponent; - let fixture: ComponentFixture<CommissionerComponent>; +describe('CommissionerApplicationComponent', () => { + let component: CommissionerApplicationComponent; + let fixture: ComponentFixture<CommissionerApplicationComponent>; let mockCommissionerService: DeepMocked<CommissionerService>; beforeEach(async () => { @@ -16,7 +16,7 @@ describe('CommissionerComponent', () => { await TestBed.configureTestingModule({ imports: [RouterTestingModule], - declarations: [CommissionerComponent], + declarations: [CommissionerApplicationComponent], providers: [ { provide: CommissionerService, @@ -26,7 +26,7 @@ describe('CommissionerComponent', () => { schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); - fixture = TestBed.createComponent(CommissionerComponent); + fixture = TestBed.createComponent(CommissionerApplicationComponent); component = fixture.componentInstance; mockCommissionerService.fetchApplication.mockResolvedValue({} as any); diff --git a/alcs-frontend/src/app/features/commissioner/commissioner.component.ts b/alcs-frontend/src/app/features/commissioner/application/commissioner-application.component.ts similarity index 60% rename from alcs-frontend/src/app/features/commissioner/commissioner.component.ts rename to alcs-frontend/src/app/features/commissioner/application/commissioner-application.component.ts index 60fb01ea2e..8673a068f7 100644 --- a/alcs-frontend/src/app/features/commissioner/commissioner.component.ts +++ b/alcs-frontend/src/app/features/commissioner/application/commissioner-application.component.ts @@ -2,17 +2,17 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { Title } from '@angular/platform-browser'; import { ActivatedRoute } from '@angular/router'; import { Subject, takeUntil } from 'rxjs'; -import { DOCUMENT_TYPE } from '../../shared/document/document.dto'; -import { environment } from '../../../environments/environment'; -import { CommissionerApplicationDto } from '../../services/commissioner/commissioner.dto'; -import { CommissionerService } from '../../services/commissioner/commissioner.service'; +import { DOCUMENT_TYPE } from '../../../shared/document/document.dto'; +import { environment } from '../../../../environments/environment'; +import { CommissionerApplicationDto } from '../../../services/commissioner/commissioner.dto'; +import { CommissionerService } from '../../../services/commissioner/commissioner.service'; @Component({ - selector: 'app-commissioner', - templateUrl: './commissioner.component.html', - styleUrls: ['./commissioner.component.scss'], + selector: 'app-commissioner-application', + templateUrl: './commissioner-application.component.html', + styleUrls: ['./commissioner-application.component.scss'], }) -export class CommissionerComponent implements OnInit, OnDestroy { +export class CommissionerApplicationComponent implements OnInit, OnDestroy { destroy = new Subject<void>(); DOCUMENT_TYPE = DOCUMENT_TYPE; application: CommissionerApplicationDto | undefined; @@ -21,7 +21,7 @@ export class CommissionerComponent implements OnInit, OnDestroy { constructor( private commissionerService: CommissionerService, private route: ActivatedRoute, - private titleService: Title + private titleService: Title, ) {} ngOnInit(): void { @@ -30,7 +30,7 @@ export class CommissionerComponent implements OnInit, OnDestroy { this.fileNumber = fileNumber; this.application = await this.commissionerService.fetchApplication(fileNumber); this.titleService.setTitle( - `${environment.siteName} | ${this.application.fileNumber} (${this.application.applicant})` + `${environment.siteName} | ${this.application.fileNumber} (${this.application.applicant})`, ); }); } diff --git a/alcs-frontend/src/app/features/commissioner/commissioner.module.ts b/alcs-frontend/src/app/features/commissioner/commissioner.module.ts index 4655fb24d9..eb94ab63d7 100644 --- a/alcs-frontend/src/app/features/commissioner/commissioner.module.ts +++ b/alcs-frontend/src/app/features/commissioner/commissioner.module.ts @@ -2,17 +2,23 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { SharedModule } from '../../shared/shared.module'; -import { CommissionerComponent } from './commissioner.component'; +import { PlanningReviewModule } from '../planning-review/planning-review.module'; +import { CommissionerApplicationComponent } from './application/commissioner-application.component'; +import { CommissionerPlanningReviewComponent } from './planning-review/commissioner-planning-review.component'; const routes: Routes = [ { - path: ':fileNumber', - component: CommissionerComponent, + path: 'application/:fileNumber', + component: CommissionerApplicationComponent, + }, + { + path: 'planning-review/:fileNumber', + component: CommissionerPlanningReviewComponent, }, ]; @NgModule({ - declarations: [CommissionerComponent], - imports: [CommonModule, SharedModule, RouterModule.forChild(routes)], + declarations: [CommissionerApplicationComponent, CommissionerPlanningReviewComponent], + imports: [CommonModule, SharedModule, PlanningReviewModule, RouterModule.forChild(routes)], }) export class CommissionerModule {} diff --git a/alcs-frontend/src/app/features/commissioner/planning-review/commissioner-planning-review.component.html b/alcs-frontend/src/app/features/commissioner/planning-review/commissioner-planning-review.component.html new file mode 100644 index 0000000000..d906beeaff --- /dev/null +++ b/alcs-frontend/src/app/features/commissioner/planning-review/commissioner-planning-review.component.html @@ -0,0 +1,13 @@ +<div class="layout"> + <div class="application"> + <app-planning-review-header *ngIf="planningReview" [planningReview]="planningReview"></app-planning-review-header> + <section class="content"> + <app-evidentiary-record + *ngIf="fileNumber" + [fileNumber]="fileNumber" + [visibilityFlags]="['C']" + tableTitle="Evidentiary Record" + ></app-evidentiary-record> + </section> + </div> +</div> diff --git a/alcs-frontend/src/app/features/commissioner/planning-review/commissioner-planning-review.component.scss b/alcs-frontend/src/app/features/commissioner/planning-review/commissioner-planning-review.component.scss new file mode 100644 index 0000000000..f9c70863b8 --- /dev/null +++ b/alcs-frontend/src/app/features/commissioner/planning-review/commissioner-planning-review.component.scss @@ -0,0 +1,18 @@ +.layout { + display: flex; + flex-direction: row; + height: 100%; + width: 100%; + justify-content: center; +} + +.application { + width: 100%; + display: flex; + flex-direction: column; +} + +.content { + margin-top: 36px; + padding: 0 80px; +} diff --git a/alcs-frontend/src/app/features/commissioner/planning-review/commissioner-planning-review.component.spec.ts b/alcs-frontend/src/app/features/commissioner/planning-review/commissioner-planning-review.component.spec.ts new file mode 100644 index 0000000000..03d82b1409 --- /dev/null +++ b/alcs-frontend/src/app/features/commissioner/planning-review/commissioner-planning-review.component.spec.ts @@ -0,0 +1,40 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { CommissionerService } from '../../../services/commissioner/commissioner.service'; + +import { CommissionerPlanningReviewComponent } from './commissioner-planning-review.component'; + +describe('CommissionerPlanningReviewComponent', () => { + let component: CommissionerPlanningReviewComponent; + let fixture: ComponentFixture<CommissionerPlanningReviewComponent>; + let mockCommissionerService: DeepMocked<CommissionerService>; + + beforeEach(async () => { + mockCommissionerService = createMock(); + + await TestBed.configureTestingModule({ + imports: [RouterTestingModule], + declarations: [CommissionerPlanningReviewComponent], + providers: [ + { + provide: CommissionerService, + useValue: mockCommissionerService, + }, + ], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(CommissionerPlanningReviewComponent); + component = fixture.componentInstance; + + mockCommissionerService.fetchApplication.mockResolvedValue({} as any); + + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alcs-frontend/src/app/features/commissioner/planning-review/commissioner-planning-review.component.ts b/alcs-frontend/src/app/features/commissioner/planning-review/commissioner-planning-review.component.ts new file mode 100644 index 0000000000..8d01c89a22 --- /dev/null +++ b/alcs-frontend/src/app/features/commissioner/planning-review/commissioner-planning-review.component.ts @@ -0,0 +1,45 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Title } from '@angular/platform-browser'; +import { ActivatedRoute } from '@angular/router'; +import { Subject, takeUntil } from 'rxjs'; +import { DOCUMENT_TYPE } from '../../../shared/document/document.dto'; +import { environment } from '../../../../environments/environment'; +import { + CommissionerApplicationDto, + CommissionerPlanningReviewDto, +} from '../../../services/commissioner/commissioner.dto'; +import { CommissionerService } from '../../../services/commissioner/commissioner.service'; + +@Component({ + selector: 'app-commissioner-planning-review', + templateUrl: './commissioner-planning-review.component.html', + styleUrls: ['./commissioner-planning-review.component.scss'], +}) +export class CommissionerPlanningReviewComponent implements OnInit, OnDestroy { + destroy = new Subject<void>(); + DOCUMENT_TYPE = DOCUMENT_TYPE; + planningReview: CommissionerPlanningReviewDto | undefined; + fileNumber: string | undefined; + + constructor( + private commissionerService: CommissionerService, + private route: ActivatedRoute, + private titleService: Title, + ) {} + + ngOnInit(): void { + this.route.params.pipe(takeUntil(this.destroy)).subscribe(async (routeParams) => { + const { fileNumber } = routeParams; + this.fileNumber = fileNumber; + this.planningReview = await this.commissionerService.fetchPlanningReview(fileNumber); + this.titleService.setTitle( + `${environment.siteName} | ${this.planningReview.fileNumber} (${this.planningReview.documentName})`, + ); + }); + } + + ngOnDestroy(): void { + this.destroy.next(); + this.destroy.complete(); + } +} diff --git a/alcs-frontend/src/app/features/planning-review/header/header.component.ts b/alcs-frontend/src/app/features/planning-review/header/header.component.ts index ae0807ddbe..23fb9f97d7 100644 --- a/alcs-frontend/src/app/features/planning-review/header/header.component.ts +++ b/alcs-frontend/src/app/features/planning-review/header/header.component.ts @@ -2,18 +2,19 @@ import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; import { Router } from '@angular/router'; import { Subject } from 'rxjs'; import { CardDto } from '../../../services/card/card.dto'; +import { CommissionerPlanningReviewDto } from '../../../services/commissioner/commissioner.dto'; import { PlanningReviewDetailedDto } from '../../../services/planning-review/planning-review.dto'; import { CLOSED_PR_LABEL, OPEN_PR_LABEL } from '../../../shared/application-type-pill/application-type-pill.constants'; @Component({ - selector: 'app-header[planningReview]', + selector: 'app-planning-review-header[planningReview]', templateUrl: './header.component.html', styleUrls: ['./header.component.scss'], }) export class HeaderComponent implements OnChanges { destroy = new Subject<void>(); - @Input() planningReview!: PlanningReviewDetailedDto; + @Input() planningReview!: PlanningReviewDetailedDto | CommissionerPlanningReviewDto; linkedCards: (CardDto & { displayName: string })[] = []; statusPill = OPEN_PR_LABEL; @@ -28,11 +29,13 @@ export class HeaderComponent implements OnChanges { } async setupLinkedCards() { - for (const [index, referral] of this.planningReview.referrals.entries()) { - this.linkedCards.push({ - ...referral.card, - displayName: `Referral ${index}`, - }); + if ('referrals' in this.planningReview) { + for (const [index, referral] of this.planningReview.referrals.entries()) { + this.linkedCards.push({ + ...referral.card, + displayName: `Referral ${index}`, + }); + } } } diff --git a/alcs-frontend/src/app/features/planning-review/planning-review.component.html b/alcs-frontend/src/app/features/planning-review/planning-review.component.html index dbc7fd31ce..78bc8090ae 100644 --- a/alcs-frontend/src/app/features/planning-review/planning-review.component.html +++ b/alcs-frontend/src/app/features/planning-review/planning-review.component.html @@ -1,6 +1,6 @@ <div class="layout"> <div class="application"> - <app-header *ngIf="planningReview" [planningReview]="planningReview"></app-header> + <app-planning-review-header *ngIf="planningReview" [planningReview]="planningReview"></app-planning-review-header> <div class="content"> <div class="nav"> <div *ngFor="let route of childRoutes" class="nav-link"> diff --git a/alcs-frontend/src/app/features/planning-review/planning-review.module.ts b/alcs-frontend/src/app/features/planning-review/planning-review.module.ts index a1b478b956..8cfc1b0c47 100644 --- a/alcs-frontend/src/app/features/planning-review/planning-review.module.ts +++ b/alcs-frontend/src/app/features/planning-review/planning-review.module.ts @@ -40,5 +40,6 @@ const routes: Routes = [ EditMeetingDialogComponent, ], imports: [CommonModule, SharedModule, RouterModule.forChild(routes), CdkDropList, CdkDrag], + exports: [HeaderComponent, EvidentiaryRecordComponent], }) export class PlanningReviewModule {} diff --git a/alcs-frontend/src/app/services/application/application-decision-meeting/application-decision-meeting.service.spec.ts b/alcs-frontend/src/app/services/application/application-decision-meeting/application-decision-meeting.service.spec.ts index 6da13d1201..5ea8e83196 100644 --- a/alcs-frontend/src/app/services/application/application-decision-meeting/application-decision-meeting.service.spec.ts +++ b/alcs-frontend/src/app/services/application/application-decision-meeting/application-decision-meeting.service.spec.ts @@ -6,7 +6,7 @@ import { ToastService } from '../../toast/toast.service'; import { ApplicationDecisionMeetingService } from './application-decision-meeting.service'; -describe('ApplicationDecisionMeetingService', () => { +describe('DecisionMeetingService', () => { let service: ApplicationDecisionMeetingService; let httpClient: DeepMocked<HttpClient>; let toastService: DeepMocked<ToastService>; @@ -53,7 +53,7 @@ describe('ApplicationDecisionMeetingService', () => { httpClient.get.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); await service.fetch('1'); @@ -78,7 +78,7 @@ describe('ApplicationDecisionMeetingService', () => { httpClient.patch.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); await service.update({ applicationFileNumber: '', date: new Date(), uuid: '1' }); @@ -103,7 +103,7 @@ describe('ApplicationDecisionMeetingService', () => { httpClient.post.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); await service.create({ applicationFileNumber: '', date: new Date() }); @@ -128,7 +128,7 @@ describe('ApplicationDecisionMeetingService', () => { { uuid: '1', }, - ]) + ]), ); await service.delete('1'); @@ -141,7 +141,7 @@ describe('ApplicationDecisionMeetingService', () => { httpClient.delete.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); await service.delete('1'); diff --git a/alcs-frontend/src/app/services/application/application-decision-meeting/application-decision-meeting.service.ts b/alcs-frontend/src/app/services/application/application-decision-meeting/application-decision-meeting.service.ts index e5583b6003..ce590c2bfc 100644 --- a/alcs-frontend/src/app/services/application/application-decision-meeting/application-decision-meeting.service.ts +++ b/alcs-frontend/src/app/services/application/application-decision-meeting/application-decision-meeting.service.ts @@ -11,9 +11,12 @@ import { ApplicationDecisionMeetingDto, CreateApplicationDecisionMeetingDto } fr }) export class ApplicationDecisionMeetingService { $decisionMeetings = new BehaviorSubject<ApplicationDecisionMeetingDto[]>([]); - private url = `${environment.apiUrl}/application-decision-meeting`; + private url = `${environment.apiUrl}/decision-meeting`; - constructor(private http: HttpClient, private toastService: ToastService) {} + constructor( + private http: HttpClient, + private toastService: ToastService, + ) {} async fetch(fileNumber: string) { this.clearDecisionMeeting(); @@ -34,7 +37,7 @@ export class ApplicationDecisionMeetingService { this.http.patch<ApplicationDecisionMeetingDto>(this.url, { ...decisionMeeting, date: formatDateForApi(decisionMeeting.date), - }) + }), ); await this.fetch(decisionMeeting.applicationFileNumber); } catch (e) { @@ -48,7 +51,7 @@ export class ApplicationDecisionMeetingService { this.http.post<ApplicationDecisionMeetingDto>(this.url, { ...decisionMeeting, date: formatDateForApi(decisionMeeting.date), - }) + }), ); return await this.fetch(decisionMeeting.applicationFileNumber); } catch (e) { diff --git a/alcs-frontend/src/app/services/application/application-modification/application-modification.service.spec.ts b/alcs-frontend/src/app/services/application/application-modification/application-modification.service.spec.ts index 05b04bf5f5..97d1b89040 100644 --- a/alcs-frontend/src/app/services/application/application-modification/application-modification.service.spec.ts +++ b/alcs-frontend/src/app/services/application/application-modification/application-modification.service.spec.ts @@ -6,7 +6,7 @@ import { ToastService } from '../../toast/toast.service'; import { ApplicationModificationService } from './application-modification.service'; -describe('ApplicationReconsiderationService', () => { +describe('ApplicationModificationService', () => { let service: ApplicationModificationService; let httpClient: DeepMocked<HttpClient>; let toastService: DeepMocked<ToastService>; @@ -49,7 +49,7 @@ describe('ApplicationReconsiderationService', () => { uuid: '2', submittedDate: new Date(4000), }, - ]) + ]), ); await service.fetchByApplication('1'); @@ -66,7 +66,7 @@ describe('ApplicationReconsiderationService', () => { httpClient.get.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); await service.fetchByApplication('1'); @@ -82,7 +82,7 @@ describe('ApplicationReconsiderationService', () => { of({ uuid: '1', submittedDate: new Date(3000), - }) + }), ); const res = await service.fetchByCardUuid('1'); @@ -96,7 +96,7 @@ describe('ApplicationReconsiderationService', () => { httpClient.get.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); const res = await service.fetchByCardUuid('1'); @@ -111,7 +111,7 @@ describe('ApplicationReconsiderationService', () => { of({ uuid: '1', submittedDate: new Date(3000), - }) + }), ); const res = await service.update('1', {}); @@ -125,7 +125,7 @@ describe('ApplicationReconsiderationService', () => { httpClient.patch.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); const res = await service.update('1', {}); @@ -140,7 +140,7 @@ describe('ApplicationReconsiderationService', () => { of({ uuid: '1', submittedDate: new Date(3000), - }) + }), ); const res = await service.create({ @@ -165,7 +165,7 @@ describe('ApplicationReconsiderationService', () => { httpClient.post.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); const res = await service.create({ @@ -191,7 +191,7 @@ describe('ApplicationReconsiderationService', () => { of({ uuid: '1', submittedDate: new Date(3000), - }) + }), ); await service.delete(''); @@ -203,7 +203,7 @@ describe('ApplicationReconsiderationService', () => { httpClient.delete.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); await service.delete(''); diff --git a/alcs-frontend/src/app/services/application/application-modification/application-modification.service.ts b/alcs-frontend/src/app/services/application/application-modification/application-modification.service.ts index 07edecc587..94d971765c 100644 --- a/alcs-frontend/src/app/services/application/application-modification/application-modification.service.ts +++ b/alcs-frontend/src/app/services/application/application-modification/application-modification.service.ts @@ -16,16 +16,19 @@ import { export class ApplicationModificationService { $modifications = new BehaviorSubject<ApplicationModificationDto[]>([]); - private url = `${environment.apiUrl}/application-modification`; + private url = `${environment.apiUrl}/v2/application-modification`; - constructor(private http: HttpClient, private toastService: ToastService) {} + constructor( + private http: HttpClient, + private toastService: ToastService, + ) {} async fetchByApplication(applicationFileNumber: string) { try { this.clearModifications(); const modifications = await firstValueFrom( - this.http.get<ApplicationModificationDto[]>(`${this.url}/application/${applicationFileNumber}`) + this.http.get<ApplicationModificationDto[]>(`${this.url}/application/${applicationFileNumber}`), ); modifications.sort((a, b) => b.submittedDate - a.submittedDate); this.$modifications.next(modifications); @@ -62,7 +65,7 @@ export class ApplicationModificationService { this.http.post<ApplicationModificationDto>(this.url, { ...createDto, submittedDate: formatDateForApi(createDto.submittedDate), - }) + }), ); } catch (err) { console.error(err); diff --git a/alcs-frontend/src/app/services/application/application-reconsideration/application-reconsideration.service.ts b/alcs-frontend/src/app/services/application/application-reconsideration/application-reconsideration.service.ts index 21db6ce332..dfc90bc3c8 100644 --- a/alcs-frontend/src/app/services/application/application-reconsideration/application-reconsideration.service.ts +++ b/alcs-frontend/src/app/services/application/application-reconsideration/application-reconsideration.service.ts @@ -18,16 +18,19 @@ export class ApplicationReconsiderationService { $reconsiderations = new BehaviorSubject<ApplicationReconsiderationDto[]>([]); $codes = new BehaviorSubject<BaseCodeDto[]>([]); - private url = `${environment.apiUrl}/application-reconsideration`; + private url = `${environment.apiUrl}/v2/application-reconsideration`; - constructor(private http: HttpClient, private toastService: ToastService) {} + constructor( + private http: HttpClient, + private toastService: ToastService, + ) {} async fetchByApplication(applicationFileNumber: string) { try { this.clearReconsiderations(); const reconsiderations = await firstValueFrom( - this.http.get<ApplicationReconsiderationDto[]>(`${this.url}/application/${applicationFileNumber}`) + this.http.get<ApplicationReconsiderationDto[]>(`${this.url}/application/${applicationFileNumber}`), ); reconsiderations.sort((a, b) => b.submittedDate - a.submittedDate); this.$reconsiderations.next(reconsiderations); @@ -50,7 +53,7 @@ export class ApplicationReconsiderationService { async update(reconsiderationUuid: string, reconsideration: UpdateApplicationReconsiderationDto) { try { return await firstValueFrom( - this.http.patch<ApplicationReconsiderationDto>(`${this.url}/${reconsiderationUuid}`, reconsideration) + this.http.patch<ApplicationReconsiderationDto>(`${this.url}/${reconsiderationUuid}`, reconsideration), ); } catch (err) { console.error(err); @@ -65,7 +68,7 @@ export class ApplicationReconsiderationService { this.http.post<ApplicationReconsiderationDto>(this.url, { ...reconsideration, submittedDate: formatDateForApi(reconsideration.submittedDate), - }) + }), ); } catch (err) { console.error(err); diff --git a/alcs-frontend/src/app/services/commissioner/commissioner.dto.ts b/alcs-frontend/src/app/services/commissioner/commissioner.dto.ts index dd94cc4646..604ba26fc5 100644 --- a/alcs-frontend/src/app/services/commissioner/commissioner.dto.ts +++ b/alcs-frontend/src/app/services/commissioner/commissioner.dto.ts @@ -1,5 +1,6 @@ import { ApplicationRegionDto, ApplicationTypeDto } from '../application/application-code.dto'; import { ApplicationLocalGovernmentDto } from '../application/application-local-government/application-local-government.dto'; +import { PlanningReviewTypeDto } from '../planning-review/planning-review.dto'; export interface CommissionerApplicationDto { fileNumber: string; @@ -14,3 +15,13 @@ export interface CommissionerApplicationDto { hasModifications: boolean; legacyId?: string; } + +export interface CommissionerPlanningReviewDto { + fileNumber: string; + documentName: string; + open: boolean; + type: PlanningReviewTypeDto; + region: ApplicationRegionDto; + localGovernment: ApplicationLocalGovernmentDto; + legacyId?: string; +} diff --git a/alcs-frontend/src/app/services/commissioner/commissioner.service.ts b/alcs-frontend/src/app/services/commissioner/commissioner.service.ts index ffbdac5d63..35d028d8b4 100644 --- a/alcs-frontend/src/app/services/commissioner/commissioner.service.ts +++ b/alcs-frontend/src/app/services/commissioner/commissioner.service.ts @@ -2,7 +2,7 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { firstValueFrom } from 'rxjs'; import { environment } from '../../../environments/environment'; -import { CommissionerApplicationDto } from './commissioner.dto'; +import { CommissionerApplicationDto, CommissionerPlanningReviewDto } from './commissioner.dto'; @Injectable({ providedIn: 'root', @@ -15,4 +15,10 @@ export class CommissionerService { async fetchApplication(fileNumber: string): Promise<CommissionerApplicationDto> { return firstValueFrom(this.http.get<CommissionerApplicationDto>(`${this.baseUrl}/${fileNumber}`)); } + + async fetchPlanningReview(fileNumber: string): Promise<CommissionerPlanningReviewDto> { + return firstValueFrom( + this.http.get<CommissionerPlanningReviewDto>(`${this.baseUrl}/planning-review/${fileNumber}`), + ); + } } diff --git a/alcs-frontend/src/app/services/decision-meeting/decision-meeting.dto.ts b/alcs-frontend/src/app/services/decision-meeting/decision-meeting.dto.ts index 2b5577eb19..8ec4c659c1 100644 --- a/alcs-frontend/src/app/services/decision-meeting/decision-meeting.dto.ts +++ b/alcs-frontend/src/app/services/decision-meeting/decision-meeting.dto.ts @@ -1,3 +1,4 @@ +import { CardType } from '../../shared/card/card.component'; import { AssigneeDto } from '../user/user.dto'; export type UpcomingMeetingDto = { @@ -6,6 +7,7 @@ export type UpcomingMeetingDto = { applicant: string; boardCode: string; assignee: AssigneeDto; + type: CardType; }; export type UpcomingMeetingBoardMapDto = Record<string, UpcomingMeetingDto[]>; diff --git a/alcs-frontend/src/app/services/decision-meeting/decision-meeting.service.ts b/alcs-frontend/src/app/services/decision-meeting/decision-meeting.service.ts index f38e3169fb..c4c41319b4 100644 --- a/alcs-frontend/src/app/services/decision-meeting/decision-meeting.service.ts +++ b/alcs-frontend/src/app/services/decision-meeting/decision-meeting.service.ts @@ -9,9 +9,12 @@ import { UpcomingMeetingBoardMapDto } from './decision-meeting.dto'; providedIn: 'root', }) export class DecisionMeetingService { - private url = `${environment.apiUrl}/application-decision-meeting`; + private url = `${environment.apiUrl}/decision-meeting`; - constructor(private http: HttpClient, private toastService: ToastService) {} + constructor( + private http: HttpClient, + private toastService: ToastService, + ) {} async fetch() { try { diff --git a/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.html b/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.html index 27871afac8..59b466ad78 100644 --- a/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.html +++ b/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.html @@ -62,8 +62,8 @@ <h5 *ngIf="!boardMeeting.pastMeetings.length" class="no-meetings">No Meetings</h <h5>{{ pastMeeting.meetingDate | momentFormat : customDateFormat }}</h5> </mat-panel-title> </mat-expansion-panel-header> - <ng-container *ngFor="let application of pastMeeting.applications"> - <ng-template *ngTemplateOutlet="applicationPanel; context: { $implicit: application }"></ng-template> + <ng-container *ngFor="let meeting of pastMeeting.meetings"> + <ng-template *ngTemplateOutlet="meetingPanel; context: { $implicit: meeting }"></ng-template> </ng-container> </mat-expansion-panel> </div> @@ -85,8 +85,8 @@ <h5 *ngIf="!boardMeeting.nextMeeting" class="no-meetings">No Meetings</h5> <h5>{{ boardMeeting.nextMeeting!.meetingDate | momentFormat : customDateFormat }}</h5> </mat-panel-title> </mat-expansion-panel-header> - <ng-container *ngFor="let application of boardMeeting.nextMeeting!.applications"> - <ng-template *ngTemplateOutlet="applicationPanel; context: { $implicit: application }"></ng-template> + <ng-container *ngFor="let meeting of boardMeeting.nextMeeting!.meetings"> + <ng-template *ngTemplateOutlet="meetingPanel; context: { $implicit: meeting }"></ng-template> </ng-container> </mat-expansion-panel> </div> @@ -108,8 +108,8 @@ <h5 *ngIf="!boardMeeting.upcomingMeetings.length" class="no-meetings">None Sched <h5>{{ upcomingMeeting.meetingDate | momentFormat : customDateFormat }}</h5> </mat-panel-title> </mat-expansion-panel-header> - <ng-container *ngFor="let application of upcomingMeeting.applications"> - <ng-template *ngTemplateOutlet="applicationPanel; context: { $implicit: application }"></ng-template> + <ng-container *ngFor="let meeting of upcomingMeeting.meetings"> + <ng-template *ngTemplateOutlet="meetingPanel; context: { $implicit: meeting }"></ng-template> </ng-container> </mat-expansion-panel> </div> @@ -117,29 +117,29 @@ <h5>{{ upcomingMeeting.meetingDate | momentFormat : customDateFormat }}</h5> </mat-expansion-panel> </div> -<ng-template #applicationPanel let-application> +<ng-template #meetingPanel let-meeting> <mat-card - (click)="openApplication(application.fileNumber)" - class="application-card" + (click)="openMeetings(meeting.fileNumber, meeting.type)" + class="meeting-card" [ngClass]="{ - 'application-highlighted': application.isHighlighted + 'meeting-highlighted': meeting.isHighlighted }" > <div class="split"> <div class="ellipsis" - [id]="application.fileNumber" + [id]="meeting.fileNumber" matTooltipClass="no-wrap-tooltip" - [matTooltip]="application.fileNumber + ' (' + application.applicant + ')'" - [matTooltipDisabled]="!isEllipsisActive(application.fileNumber)" + [matTooltip]="meeting.fileNumber + ' (' + meeting.applicant + ')'" + [matTooltipDisabled]="!isEllipsisActive(meeting.fileNumber)" > - {{ application.fileNumber }} ({{ application.applicant }}) + {{ meeting.fileNumber }} ({{ meeting.applicant }}) </div> <div> <app-avatar-circle - *ngIf="application.assignee" - [initials]="application.assignee.initials" - [name]="application.assignee.prettyName" + *ngIf="meeting.assignee" + [initials]="meeting.assignee.initials" + [name]="meeting.assignee.prettyName" ></app-avatar-circle> </div> </div> diff --git a/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.scss b/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.scss index 8dd662352c..ab0574a2a1 100644 --- a/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.scss +++ b/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.scss @@ -150,7 +150,7 @@ mat-panel-description { } } - .application-card:hover { + .meeting-card:hover { border-color: colors.$secondary-color; } } @@ -180,7 +180,7 @@ mat-panel-description { } } - .application-card:hover { + .meeting-card:hover { border-color: colors.$primary-color; } } @@ -210,7 +210,7 @@ mat-panel-description { } } - .application-card:hover { + .meeting-card:hover { border-color: colors.$accent-color; } } @@ -245,7 +245,7 @@ mat-panel-description { } } -.application-card { +.meeting-card { margin-bottom: 16px; padding: 8px 16px; background-color: colors.$white; @@ -260,18 +260,18 @@ mat-panel-description { margin-bottom: unset; } - &.application-highlighted { + &.meeting-highlighted { border: 5px solid rgba(colors.$primary-color, 0.5); animation: border-pulsate 2s infinite; } } -.application-header { +.meeting-header { box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25); border-radius: 4px; } -.application-file { +.meeting-file { display: flex; align-items: center; justify-content: space-between; diff --git a/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.spec.ts b/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.spec.ts index ddf39f1ceb..c82e79d317 100644 --- a/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.spec.ts +++ b/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.spec.ts @@ -9,6 +9,7 @@ import { DecisionMeetingService } from '../../services/decision-meeting/decision import { ToastService } from '../../services/toast/toast.service'; import { AssigneeDto, UserDto } from '../../services/user/user.dto'; import { UserService } from '../../services/user/user.service'; +import { CardType } from '../card/card.component'; import { MeetingOverviewComponent } from './meeting-overview.component'; @@ -77,6 +78,7 @@ describe('MeetingOverviewComponent', () => { assignee: {} as AssigneeDto, fileNumber: '', meetingDate: 0, + type: CardType.APP, }, ], }); diff --git a/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.ts b/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.ts index 5d3e8c93eb..c1c090f26a 100644 --- a/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.ts +++ b/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.ts @@ -1,18 +1,17 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import * as moment from 'moment'; import { Subject, takeUntil } from 'rxjs'; -import { BOARD_TYPE_CODES } from '../../features/board/board.component'; -import { ApplicationDocumentService } from '../../services/application/application-document/application-document.service'; import { ROLES } from '../../services/authentication/authentication.service'; import { BoardService, BoardWithFavourite } from '../../services/board/board.service'; import { UpcomingMeetingBoardMapDto, UpcomingMeetingDto } from '../../services/decision-meeting/decision-meeting.dto'; import { DecisionMeetingService } from '../../services/decision-meeting/decision-meeting.service'; import { ToastService } from '../../services/toast/toast.service'; import { UserService } from '../../services/user/user.service'; +import { CardType } from '../card/card.component'; -type MeetingWithApplications = { +type MeetingCollection = { meetingDate: number; - applications: (UpcomingMeetingDto & { isHighlighted: boolean })[]; + meetings: (UpcomingMeetingDto & { isHighlighted: boolean })[]; isExpanded: boolean; }; @@ -20,9 +19,9 @@ type BoardWithDecisionMeetings = { boardTitle: string; boardCode: string; isFavourite: boolean; - pastMeetings: MeetingWithApplications[]; - upcomingMeetings: MeetingWithApplications[]; - nextMeeting: MeetingWithApplications | undefined; + pastMeetings: MeetingCollection[]; + upcomingMeetings: MeetingCollection[]; + nextMeeting: MeetingCollection | undefined; isExpanded: boolean; }; @@ -44,9 +43,8 @@ export class MeetingOverviewComponent implements OnInit, OnDestroy { constructor( private meetingService: DecisionMeetingService, private boardService: BoardService, - private applicationDocumentService: ApplicationDocumentService, private toastService: ToastService, - private userService: UserService + private userService: UserService, ) {} ngOnInit(): void { @@ -83,11 +81,11 @@ export class MeetingOverviewComponent implements OnInit, OnDestroy { this.viewData = this.boards .filter((board) => board.showOnSchedule) .map((board): BoardWithDecisionMeetings => { - let upcomingMeetings: MeetingWithApplications[] = []; - let pastMeetings: MeetingWithApplications[] = []; - const applications = this.meetings![board.code]; - if (applications) { - this.sortApplications(applications, pastMeetings, upcomingMeetings); + let upcomingMeetings: MeetingCollection[] = []; + let pastMeetings: MeetingCollection[] = []; + const meetings = this.meetings![board.code]; + if (meetings) { + this.sortMeetings(meetings, pastMeetings, upcomingMeetings); } upcomingMeetings.sort((a, b) => a.meetingDate - b.meetingDate); @@ -110,35 +108,35 @@ export class MeetingOverviewComponent implements OnInit, OnDestroy { } } - private sortApplications( - applications: UpcomingMeetingDto[], - pastMeetings: MeetingWithApplications[], - upcomingMeetings: MeetingWithApplications[] + private sortMeetings( + meetings: UpcomingMeetingDto[], + pastMeetings: MeetingCollection[], + upcomingMeetings: MeetingCollection[], ) { - applications.forEach((app) => { + meetings.forEach((app) => { const yesterday = moment.utc().startOf('day').add(-1, 'day'); if (yesterday.isAfter(app.meetingDate)) { - this.mapApplicationsIntoMeetings(pastMeetings, app); + this.sortMeetingsIntoCollections(pastMeetings, app); } else { - this.mapApplicationsIntoMeetings(upcomingMeetings, app); + this.sortMeetingsIntoCollections(upcomingMeetings, app); } }); } - private mapApplicationsIntoMeetings(pastMeetings: MeetingWithApplications[], app: UpcomingMeetingDto) { - const meeting = pastMeetings.find((meeting) => meeting.meetingDate === app.meetingDate); + private sortMeetingsIntoCollections(pastMeetings: MeetingCollection[], meetings: UpcomingMeetingDto) { + const meeting = pastMeetings.find((meeting) => meeting.meetingDate === meetings.meetingDate); if (meeting) { - meeting.applications.push({ - ...app, + meeting.meetings.push({ + ...meetings, isHighlighted: false, }); } else { pastMeetings.push({ - meetingDate: app.meetingDate, - applications: [ + meetingDate: meetings.meetingDate, + meetings: [ { - ...app, + ...meetings, isHighlighted: false, }, ], @@ -184,9 +182,9 @@ export class MeetingOverviewComponent implements OnInit, OnDestroy { } } - private findAndExpand(meeting: MeetingWithApplications, board: BoardWithDecisionMeetings) { + private findAndExpand(meeting: MeetingCollection, board: BoardWithDecisionMeetings) { meeting.isExpanded = false; - meeting.applications = meeting.applications.map((application) => { + meeting.meetings = meeting.meetings.map((application) => { application.isHighlighted = false; if (application.fileNumber === this.searchText) { meeting.isExpanded = true; @@ -214,7 +212,7 @@ export class MeetingOverviewComponent implements OnInit, OnDestroy { clearHighlight() { this.viewData = this.viewData.map((board) => { board.pastMeetings = board.pastMeetings.map((meeting) => { - meeting.applications = meeting.applications.map((application) => { + meeting.meetings = meeting.meetings.map((application) => { application.isHighlighted = false; return application; }); @@ -222,14 +220,14 @@ export class MeetingOverviewComponent implements OnInit, OnDestroy { }); if (board.nextMeeting) { - board.nextMeeting.applications.map((application) => { + board.nextMeeting.meetings.map((application) => { application.isHighlighted = false; return application; }); } board.upcomingMeetings = board.upcomingMeetings.map((meeting) => { - meeting.applications = meeting.applications.map((application) => { + meeting.meetings = meeting.meetings.map((application) => { application.isHighlighted = false; return application; }); @@ -245,9 +243,10 @@ export class MeetingOverviewComponent implements OnInit, OnDestroy { return el ? el.offsetWidth < el.scrollWidth : false; } - openApplication(fileNumber: string) { + openMeetings(fileNumber: string, type: CardType) { this.clearHighlight(); - const url = this.isCommissioner ? `/commissioner/${fileNumber}` : `/application/${fileNumber}/review`; + const target = type === CardType.PLAN ? 'planning-review' : 'application'; + const url = this.isCommissioner ? `/commissioner/${target}/${fileNumber}` : `/${target}/${fileNumber}/review`; window.open(url, '_blank'); } } diff --git a/services/apps/alcs/src/alcs/admin/application-ceo-criterion/application-ceo-criterion.controller.ts b/services/apps/alcs/src/alcs/admin/application-ceo-criterion/application-ceo-criterion.controller.ts index 2a7982c7ae..fd3058a6d1 100644 --- a/services/apps/alcs/src/alcs/admin/application-ceo-criterion/application-ceo-criterion.controller.ts +++ b/services/apps/alcs/src/alcs/admin/application-ceo-criterion/application-ceo-criterion.controller.ts @@ -13,7 +13,7 @@ import * as config from 'config'; import { AUTH_ROLE } from '../../../common/authorization/roles'; import { RolesGuard } from '../../../common/authorization/roles-guard.service'; import { UserRoles } from '../../../common/authorization/roles.decorator'; -import { CeoCriterionCodeDto } from '../../application-decision/application-decision-v1/application-decision/ceo-criterion/ceo-criterion.dto'; +import { CeoCriterionCodeDto } from '../../application-decision/application-decision-v2/application-decision/ceo-criterion/ceo-criterion.dto'; import { ApplicationCeoCriterionService } from './application-ceo-criterion.service'; @Controller('ceo-criterion') diff --git a/services/apps/alcs/src/alcs/admin/application-ceo-criterion/application-ceo-criterion.service.ts b/services/apps/alcs/src/alcs/admin/application-ceo-criterion/application-ceo-criterion.service.ts index a0176a19a4..0b46cc2358 100644 --- a/services/apps/alcs/src/alcs/admin/application-ceo-criterion/application-ceo-criterion.service.ts +++ b/services/apps/alcs/src/alcs/admin/application-ceo-criterion/application-ceo-criterion.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { ApplicationCeoCriterionCode } from '../../application-decision/application-ceo-criterion/application-ceo-criterion.entity'; -import { CeoCriterionCodeDto } from '../../application-decision/application-decision-v1/application-decision/ceo-criterion/ceo-criterion.dto'; +import { CeoCriterionCodeDto } from '../../application-decision/application-decision-v2/application-decision/ceo-criterion/ceo-criterion.dto'; @Injectable() export class ApplicationCeoCriterionService { diff --git a/services/apps/alcs/src/alcs/alcs.module.ts b/services/apps/alcs/src/alcs/alcs.module.ts index 9b32d89ab2..0f4a85c904 100644 --- a/services/apps/alcs/src/alcs/alcs.module.ts +++ b/services/apps/alcs/src/alcs/alcs.module.ts @@ -15,6 +15,7 @@ import { ImportModule } from './import/import.module'; import { InquiryModule } from './inquiry/inquiry.module'; import { LocalGovernmentModule } from './local-government/local-government.module'; import { MessageModule } from './message/message.module'; +import { MeetingModule } from './meetings/meeting.module'; import { NoticeOfIntentDecisionModule } from './notice-of-intent-decision/notice-of-intent-decision.module'; import { NoticeOfIntentSubmissionStatusModule } from './notice-of-intent/notice-of-intent-submission-status/notice-of-intent-submission-status.module'; import { NoticeOfIntentTimelineModule } from './notice-of-intent/notice-of-intent-timeline/notice-of-intent-timeline.module'; @@ -52,6 +53,7 @@ import { StaffJournalModule } from './staff-journal/staff-journal.module'; NotificationModule, NotificationTimelineModule, InquiryModule, + MeetingModule, RouterModule.register([ { path: 'alcs', module: ApplicationModule }, { path: 'alcs', module: CommentModule }, @@ -78,6 +80,7 @@ import { StaffJournalModule } from './staff-journal/staff-journal.module'; { path: 'alcs', module: NotificationSubmissionStatusModule }, { path: 'alcs', module: NotificationTimelineModule }, { path: 'alcs', module: InquiryModule }, + { path: 'alcs', module: MeetingModule }, ]), ], controllers: [], diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision-v1.module.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision-v1.module.ts deleted file mode 100644 index a45749dad1..0000000000 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision-v1.module.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { forwardRef, Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { ApplicationSubmissionStatusModule } from '../../application/application-submission-status/application-submission-status.module'; -import { ApplicationSubmissionStatusType } from '../../application/application-submission-status/submission-status-type.entity'; -import { ApplicationSubmissionToSubmissionStatus } from '../../application/application-submission-status/submission-status.entity'; -import { ApplicationDecisionProfile } from '../../../common/automapper/application-decision-v1.automapper.profile'; -import { ModificationProfile } from '../../../common/automapper/modification.automapper.profile'; -import { ReconsiderationProfile } from '../../../common/automapper/reconsideration.automapper.profile'; -import { DocumentModule } from '../../../document/document.module'; -import { ApplicationSubmission } from '../../../portal/application-submission/application-submission.entity'; -import { ApplicationModule } from '../../application/application.module'; -import { BoardModule } from '../../board/board.module'; -import { CardModule } from '../../card/card.module'; -import { ApplicationCeoCriterionCode } from '../application-ceo-criterion/application-ceo-criterion.entity'; -import { ApplicationDecisionDocument } from '../application-decision-document/application-decision-document.entity'; -import { ApplicationDecisionMakerCode } from '../application-decision-maker/application-decision-maker.entity'; -import { ApplicationDecisionChairReviewOutcomeType } from '../application-decision-outcome-type/application-decision-outcome-type.entity'; -import { ApplicationDecisionOutcomeCode } from '../application-decision-outcome.entity'; -import { ApplicationDecision } from '../application-decision.entity'; -import { ApplicationModificationOutcomeType } from '../application-modification/application-modification-outcome-type/application-modification-outcome-type.entity'; -import { ApplicationModificationController } from '../application-modification/application-modification.controller'; -import { ApplicationModification } from '../application-modification/application-modification.entity'; -import { ApplicationModificationService } from '../application-modification/application-modification.service'; -import { ApplicationReconsiderationController } from '../application-reconsideration/application-reconsideration.controller'; -import { ApplicationReconsideration } from '../application-reconsideration/application-reconsideration.entity'; -import { ApplicationReconsiderationService } from '../application-reconsideration/application-reconsideration.service'; -import { ApplicationReconsiderationOutcomeType } from '../application-reconsideration/reconsideration-outcome-type/application-reconsideration-outcome-type.entity'; -import { ApplicationReconsiderationType } from '../application-reconsideration/reconsideration-type/application-reconsideration-type.entity'; -import { ApplicationDecisionMeetingController } from './application-decision-meeting/application-decision-meeting.controller'; -import { ApplicationDecisionMeeting } from './application-decision-meeting/application-decision-meeting.entity'; -import { ApplicationDecisionMeetingService } from './application-decision-meeting/application-decision-meeting.service'; -import { ApplicationDecisionV1Controller } from './application-decision/application-decision-v1.controller'; -import { ApplicationDecisionV1Service } from './application-decision/application-decision-v1.service'; - -@Module({ - imports: [ - TypeOrmModule.forFeature([ - ApplicationDecisionOutcomeCode, - ApplicationDecisionMakerCode, - ApplicationCeoCriterionCode, - ApplicationDecision, - ApplicationDecisionDocument, - ApplicationModification, - ApplicationReconsideration, - ApplicationReconsiderationType, - ApplicationDecisionMeeting, - ApplicationDecisionChairReviewOutcomeType, - ApplicationReconsiderationOutcomeType, - ApplicationModificationOutcomeType, - ApplicationSubmissionToSubmissionStatus, - ApplicationSubmission, - ApplicationSubmissionStatusType, - ]), - forwardRef(() => BoardModule), - forwardRef(() => ApplicationModule), - CardModule, - DocumentModule, - ApplicationSubmissionStatusModule, - ], - providers: [ - ApplicationModificationService, - ModificationProfile, - ApplicationDecisionV1Service, - ApplicationDecisionProfile, - ApplicationReconsiderationService, - ReconsiderationProfile, - ApplicationDecisionMeetingService, - ], - controllers: [ - ApplicationModificationController, - ApplicationDecisionV1Controller, - ApplicationReconsiderationController, - ApplicationDecisionMeetingController, - ], - exports: [ - ApplicationModificationService, - ApplicationDecisionV1Service, - ApplicationDecisionMeetingService, - ApplicationReconsiderationService, - ], -}) -export class ApplicationDecisionV1Module {} diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision/application-decision-v1.controller.spec.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision/application-decision-v1.controller.spec.ts deleted file mode 100644 index ed29924310..0000000000 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision/application-decision-v1.controller.spec.ts +++ /dev/null @@ -1,329 +0,0 @@ -import { classes } from 'automapper-classes'; -import { AutomapperModule } from 'automapper-nestjs'; -import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; -import { BadRequestException } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { ClsService } from 'nestjs-cls'; -import { - initApplicationDecisionMock, - initApplicationMockEntity, -} from '../../../../../test/mocks/mockEntities'; -import { mockKeyCloakProviders } from '../../../../../test/mocks/mockTypes'; -import { ApplicationDecisionProfile } from '../../../../common/automapper/application-decision-v1.automapper.profile'; -import { ApplicationProfile } from '../../../../common/automapper/application.automapper.profile'; -import { UserProfile } from '../../../../common/automapper/user.automapper.profile'; -import { ApplicationService } from '../../../application/application.service'; -import { CodeService } from '../../../code/code.service'; -import { ApplicationDecisionOutcomeCode } from '../../application-decision-outcome.entity'; -import { ApplicationModification } from '../../application-modification/application-modification.entity'; -import { ApplicationModificationService } from '../../application-modification/application-modification.service'; -import { ApplicationReconsideration } from '../../application-reconsideration/application-reconsideration.entity'; -import { ApplicationReconsiderationService } from '../../application-reconsideration/application-reconsideration.service'; -import { ApplicationDecisionV1Controller } from './application-decision-v1.controller'; -import { ApplicationDecisionV1Service } from './application-decision-v1.service'; -import { - CreateApplicationDecisionDto, - UpdateApplicationDecisionDto, -} from './application-decision.dto'; - -describe('ApplicationDecisionV1Controller', () => { - let controller: ApplicationDecisionV1Controller; - let mockDecisionService: DeepMocked<ApplicationDecisionV1Service>; - let mockApplicationService: DeepMocked<ApplicationService>; - let mockCodeService: DeepMocked<CodeService>; - let mockModificationService: DeepMocked<ApplicationModificationService>; - let mockReconService: DeepMocked<ApplicationReconsiderationService>; - - let mockApplication; - let mockDecision; - - beforeEach(async () => { - mockDecisionService = createMock(); - mockApplicationService = createMock(); - mockCodeService = createMock(); - mockModificationService = createMock(); - mockReconService = createMock(); - - mockApplication = initApplicationMockEntity(); - mockDecision = initApplicationDecisionMock(mockApplication); - - const module: TestingModule = await Test.createTestingModule({ - imports: [ - AutomapperModule.forRoot({ - strategyInitializer: classes(), - }), - ], - controllers: [ApplicationDecisionV1Controller], - providers: [ - ApplicationProfile, - ApplicationDecisionProfile, - UserProfile, - { - provide: ApplicationDecisionV1Service, - useValue: mockDecisionService, - }, - { - provide: ApplicationService, - useValue: mockApplicationService, - }, - { - provide: CodeService, - useValue: mockCodeService, - }, - { - provide: ApplicationModificationService, - useValue: mockModificationService, - }, - { - provide: ApplicationReconsiderationService, - useValue: mockReconService, - }, - { - provide: ClsService, - useValue: {}, - }, - ...mockKeyCloakProviders, - ], - }).compile(); - - controller = module.get<ApplicationDecisionV1Controller>( - ApplicationDecisionV1Controller, - ); - - mockDecisionService.fetchCodes.mockResolvedValue({ - outcomes: [ - { - code: 'decision-code', - label: 'decision-label', - } as ApplicationDecisionOutcomeCode, - ], - ceoCriterion: [], - decisionMakers: [], - }); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); - - it('should get all for application', async () => { - mockDecisionService.getByAppFileNumber.mockResolvedValue([mockDecision]); - - const result = await controller.getByFileNumber('fake-number'); - - expect(mockDecisionService.getByAppFileNumber).toBeCalledTimes(1); - expect(result[0].uuid).toStrictEqual(mockDecision.uuid); - }); - - it('should get a specific decision', async () => { - mockDecisionService.get.mockResolvedValue(mockDecision); - const result = await controller.get('fake-uuid'); - - expect(mockDecisionService.get).toBeCalledTimes(1); - expect(result.uuid).toStrictEqual(mockDecision.uuid); - }); - - it('should call through for deletion', async () => { - mockDecisionService.delete.mockResolvedValue({} as any); - - await controller.delete('fake-uuid'); - - expect(mockDecisionService.delete).toBeCalledTimes(1); - expect(mockDecisionService.delete).toBeCalledWith('fake-uuid'); - }); - - it('should create the decision if application exists', async () => { - mockApplicationService.getOrFail.mockResolvedValue(mockApplication); - mockDecisionService.create.mockResolvedValue(mockDecision); - - const decisionToCreate = { - date: new Date(2022, 2, 2, 2, 2, 2, 2).valueOf(), - applicationFileNumber: mockApplication.fileNumber, - outcomeCode: 'outcome', - } as CreateApplicationDecisionDto; - - await controller.create(decisionToCreate); - - expect(mockDecisionService.create).toBeCalledTimes(1); - expect(mockDecisionService.create).toBeCalledWith( - { - applicationFileNumber: mockApplication.fileNumber, - outcomeCode: 'outcome', - date: decisionToCreate.date, - }, - mockApplication, - undefined, - undefined, - ); - }); - - it('should load the linked modification for create', async () => { - mockApplicationService.getOrFail.mockResolvedValue(mockApplication); - mockDecisionService.create.mockResolvedValue(mockDecision); - const mockModification = {} as ApplicationModification; - mockModificationService.getByUuid.mockResolvedValue(mockModification); - - const decisionToCreate = { - date: new Date(2022, 2, 2, 2, 2, 2, 2).valueOf(), - applicationFileNumber: mockApplication.fileNumber, - outcomeCode: 'outcome', - modifiesUuid: 'fake-modification', - } as CreateApplicationDecisionDto; - - await controller.create(decisionToCreate); - - expect(mockModificationService.getByUuid).toBeCalledTimes(1); - expect(mockDecisionService.create).toBeCalledTimes(1); - expect(mockDecisionService.create).toBeCalledWith( - { - applicationFileNumber: mockApplication.fileNumber, - outcomeCode: 'outcome', - date: decisionToCreate.date, - modifiesUuid: 'fake-modification', - }, - mockApplication, - mockModification, - undefined, - ); - }); - - it('should load the linked reconsideration for create', async () => { - mockApplicationService.getOrFail.mockResolvedValue(mockApplication); - mockDecisionService.create.mockResolvedValue(mockDecision); - const mockReconsideration = {} as ApplicationReconsideration; - mockReconService.getByUuid.mockResolvedValue(mockReconsideration); - - const decisionToCreate = { - date: new Date(2022, 2, 2, 2, 2, 2, 2).valueOf(), - applicationFileNumber: mockApplication.fileNumber, - outcomeCode: 'outcome', - reconsidersUuid: 'fake-recon', - } as CreateApplicationDecisionDto; - - await controller.create(decisionToCreate); - - expect(mockReconService.getByUuid).toBeCalledTimes(1); - expect(mockDecisionService.create).toBeCalledTimes(1); - expect(mockDecisionService.create).toBeCalledWith( - { - applicationFileNumber: mockApplication.fileNumber, - outcomeCode: 'outcome', - date: decisionToCreate.date, - reconsidersUuid: 'fake-recon', - }, - mockApplication, - undefined, - mockReconsideration, - ); - }); - - it('should throw an exception when trying to create with both modification and recon', async () => { - const decisionToCreate = { - date: new Date(2022, 2, 2, 2, 2, 2, 2).valueOf(), - applicationFileNumber: mockApplication.fileNumber, - outcomeCode: 'outcome', - reconsidersUuid: 'fake-recon', - modifiesUuid: 'fake-modification', - } as CreateApplicationDecisionDto; - - const promise = controller.create(decisionToCreate); - await expect(promise).rejects.toMatchObject( - new BadRequestException( - 'Cannot create a Decision linked to both a modification and a reconsideration', - ), - ); - }); - - it('should throw an exception when trying to update with both modification and recon', async () => { - const updateDto = { - date: new Date(2022, 2, 2, 2, 2, 2, 2).valueOf(), - applicationFileNumber: mockApplication.fileNumber, - outcomeCode: 'outcome', - reconsidersUuid: 'fake-recon', - modifiesUuid: 'fake-modification', - } as UpdateApplicationDecisionDto; - - const promise = controller.update('uuid', updateDto); - await expect(promise).rejects.toMatchObject( - new BadRequestException( - 'Cannot create a Decision linked to both a modification and a reconsideration', - ), - ); - }); - - it('should update the decision', async () => { - mockDecisionService.update.mockResolvedValue(mockDecision); - const updates = { - outcome: 'New Outcome', - date: new Date(2022, 2, 2, 2, 2, 2, 2).valueOf(), - } as UpdateApplicationDecisionDto; - - await controller.update('fake-uuid', updates); - - expect(mockDecisionService.update).toBeCalledTimes(1); - expect(mockDecisionService.update).toBeCalledWith( - 'fake-uuid', - { - outcome: 'New Outcome', - date: updates.date, - }, - undefined, - undefined, - ); - }); - - it('should call through for attaching the document', async () => { - mockDecisionService.attachDocument.mockResolvedValue({} as any); - await controller.attachDocument('fake-uuid', { - isMultipart: () => true, - body: { - file: {}, - }, - user: { - entity: {}, - }, - }); - - expect(mockDecisionService.attachDocument).toBeCalledTimes(1); - }); - - it('should throw an exception if there is no file for file upload', async () => { - mockDecisionService.attachDocument.mockResolvedValue({} as any); - const promise = controller.attachDocument('fake-uuid', { - file: () => ({}), - isMultipart: () => false, - user: { - entity: {}, - }, - }); - - await expect(promise).rejects.toMatchObject( - new Error('Request is not multipart'), - ); - }); - - it('should call through for getting download url', async () => { - const fakeUrl = 'fake-url'; - mockDecisionService.getDownloadUrl.mockResolvedValue(fakeUrl); - const res = await controller.getDownloadUrl('fake-uuid', 'document-uuid'); - - expect(mockDecisionService.getDownloadUrl).toBeCalledTimes(1); - expect(res.url).toEqual(fakeUrl); - }); - - it('should call through for getting open url', async () => { - const fakeUrl = 'fake-url'; - mockDecisionService.getDownloadUrl.mockResolvedValue(fakeUrl); - const res = await controller.getOpenUrl('fake-uuid', 'document-uuid'); - - expect(mockDecisionService.getDownloadUrl).toBeCalledTimes(1); - expect(res.url).toEqual(fakeUrl); - }); - - it('should call through for document deletion', async () => { - mockDecisionService.deleteDocument.mockResolvedValue({} as any); - await controller.deleteDocument('fake-uuid', 'document-uuid'); - - expect(mockDecisionService.deleteDocument).toBeCalledTimes(1); - }); -}); diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision/application-decision-v1.controller.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision/application-decision-v1.controller.ts deleted file mode 100644 index 93fcaa467f..0000000000 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision/application-decision-v1.controller.ts +++ /dev/null @@ -1,238 +0,0 @@ -import { Mapper } from 'automapper-core'; -import { InjectMapper } from 'automapper-nestjs'; -import { - BadRequestException, - Body, - Controller, - Delete, - Get, - Param, - Patch, - Post, - Req, - UseGuards, -} from '@nestjs/common'; -import { ApiOAuth2 } from '@nestjs/swagger'; -import * as config from 'config'; -import { ANY_AUTH_ROLE } from '../../../../common/authorization/roles'; -import { RolesGuard } from '../../../../common/authorization/roles-guard.service'; -import { UserRoles } from '../../../../common/authorization/roles.decorator'; -import { ApplicationService } from '../../../application/application.service'; -import { ApplicationDecisionOutcomeCode } from '../../application-decision-outcome.entity'; -import { ApplicationDecision } from '../../application-decision.entity'; -import { ApplicationModificationService } from '../../application-modification/application-modification.service'; -import { ApplicationReconsiderationService } from '../../application-reconsideration/application-reconsideration.service'; -import { ApplicationCeoCriterionCode } from '../../application-ceo-criterion/application-ceo-criterion.entity'; -import { ApplicationDecisionMakerCode } from '../../application-decision-maker/application-decision-maker.entity'; -import { ApplicationDecisionV1Service } from './application-decision-v1.service'; -import { - ApplicationDecisionDto, - CreateApplicationDecisionDto, - DecisionOutcomeCodeDto, - UpdateApplicationDecisionDto, -} from './application-decision.dto'; -import { CeoCriterionCodeDto } from './ceo-criterion/ceo-criterion.dto'; -import { ApplicationDecisionMakerCodeDto } from '../../application-decision-maker/decision-maker.dto'; - -@ApiOAuth2(config.get<string[]>('KEYCLOAK.SCOPES')) -@Controller('application-decision') -@UseGuards(RolesGuard) -export class ApplicationDecisionV1Controller { - constructor( - private appDecisionService: ApplicationDecisionV1Service, - private applicationService: ApplicationService, - private modificationService: ApplicationModificationService, - private reconsiderationService: ApplicationReconsiderationService, - @InjectMapper() private mapper: Mapper, - ) {} - - @Get('/application/:fileNumber') - @UserRoles(...ANY_AUTH_ROLE) - async getByFileNumber( - @Param('fileNumber') fileNumber, - ): Promise<ApplicationDecisionDto[]> { - const decisions = - await this.appDecisionService.getByAppFileNumber(fileNumber); - return await this.mapper.mapArrayAsync( - decisions, - ApplicationDecision, - ApplicationDecisionDto, - ); - } - - @Get('/codes') - @UserRoles(...ANY_AUTH_ROLE) - async getCodes() { - const codes = await this.appDecisionService.fetchCodes(); - return { - outcomes: await this.mapper.mapArrayAsync( - codes.outcomes, - ApplicationDecisionOutcomeCode, - DecisionOutcomeCodeDto, - ), - decisionMakers: await this.mapper.mapArrayAsync( - codes.decisionMakers, - ApplicationDecisionMakerCode, - ApplicationDecisionMakerCodeDto, - ), - ceoCriterion: await this.mapper.mapArrayAsync( - codes.ceoCriterion, - ApplicationCeoCriterionCode, - CeoCriterionCodeDto, - ), - }; - } - - @Get('/:uuid') - @UserRoles(...ANY_AUTH_ROLE) - async get(@Param('uuid') uuid: string): Promise<ApplicationDecisionDto> { - const meeting = await this.appDecisionService.get(uuid); - return this.mapper.mapAsync( - meeting, - ApplicationDecision, - ApplicationDecisionDto, - ); - } - - @Post() - @UserRoles(...ANY_AUTH_ROLE) - async create( - @Body() createDto: CreateApplicationDecisionDto, - ): Promise<ApplicationDecisionDto> { - if (createDto.modifiesUuid && createDto.reconsidersUuid) { - throw new BadRequestException( - 'Cannot create a Decision linked to both a modification and a reconsideration', - ); - } - - const application = await this.applicationService.getOrFail( - createDto.applicationFileNumber, - ); - - const modification = createDto.modifiesUuid - ? await this.modificationService.getByUuid(createDto.modifiesUuid) - : undefined; - - const reconsiders = createDto.reconsidersUuid - ? await this.reconsiderationService.getByUuid(createDto.reconsidersUuid) - : undefined; - - const newDecision = await this.appDecisionService.create( - createDto, - application, - modification, - reconsiders, - ); - - return this.mapper.mapAsync( - newDecision, - ApplicationDecision, - ApplicationDecisionDto, - ); - } - - @Patch('/:uuid') - @UserRoles(...ANY_AUTH_ROLE) - async update( - @Param('uuid') uuid: string, - @Body() updateDto: UpdateApplicationDecisionDto, - ): Promise<ApplicationDecisionDto> { - if (updateDto.modifiesUuid && updateDto.reconsidersUuid) { - throw new BadRequestException( - 'Cannot create a Decision linked to both a modification and a reconsideration', - ); - } - - let modifies; - if (updateDto.modifiesUuid) { - modifies = await this.modificationService.getByUuid( - updateDto.modifiesUuid, - ); - } else if (updateDto.modifiesUuid === null) { - modifies = null; - } - - let reconsiders; - if (updateDto.reconsidersUuid) { - reconsiders = await this.reconsiderationService.getByUuid( - updateDto.reconsidersUuid, - ); - } else if (updateDto.reconsidersUuid === null) { - reconsiders = null; - } - - const updatedMeeting = await this.appDecisionService.update( - uuid, - updateDto, - modifies, - reconsiders, - ); - return this.mapper.mapAsync( - updatedMeeting, - ApplicationDecision, - ApplicationDecisionDto, - ); - } - - @Delete('/:uuid') - @UserRoles(...ANY_AUTH_ROLE) - async delete(@Param('uuid') uuid: string) { - return await this.appDecisionService.delete(uuid); - } - - @Post('/:uuid/file') - @UserRoles(...ANY_AUTH_ROLE) - async attachDocument(@Param('uuid') decisionUuid: string, @Req() req) { - if (!req.isMultipart()) { - throw new BadRequestException('Request is not multipart'); - } - - const file = req.body.file; - await this.appDecisionService.attachDocument( - decisionUuid, - file, - req.user.entity, - ); - return { - uploaded: true, - }; - } - - @Get('/:uuid/file/:fileUuid/download') - @UserRoles(...ANY_AUTH_ROLE) - async getDownloadUrl( - @Param('uuid') decisionUuid: string, - @Param('fileUuid') documentUuid: string, - ) { - const downloadUrl = - await this.appDecisionService.getDownloadUrl(documentUuid); - return { - url: downloadUrl, - }; - } - - @Get('/:uuid/file/:fileUuid/open') - @UserRoles(...ANY_AUTH_ROLE) - async getOpenUrl( - @Param('uuid') decisionUuid: string, - @Param('fileUuid') documentUuid: string, - ) { - const downloadUrl = await this.appDecisionService.getDownloadUrl( - documentUuid, - true, - ); - return { - url: downloadUrl, - }; - } - - @Delete('/:uuid/file/:fileUuid') - @UserRoles(...ANY_AUTH_ROLE) - async deleteDocument( - @Param('uuid') decisionUuid: string, - @Param('fileUuid') documentUuid: string, - ) { - await this.appDecisionService.deleteDocument(documentUuid); - return {}; - } -} diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision/application-decision-v1.service.spec.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision/application-decision-v1.service.spec.ts deleted file mode 100644 index f39d2f0a42..0000000000 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision/application-decision-v1.service.spec.ts +++ /dev/null @@ -1,479 +0,0 @@ -import { - ServiceNotFoundException, - ServiceValidationException, -} from '@app/common/exceptions/base.exception'; -import { classes } from 'automapper-classes'; -import { AutomapperModule } from 'automapper-nestjs'; -import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; -import { Test, TestingModule } from '@nestjs/testing'; -import { getRepositoryToken } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { - initApplicationDecisionMock, - initApplicationMockEntity, -} from '../../../../../test/mocks/mockEntities'; -import { ApplicationSubmissionStatusService } from '../../../application/application-submission-status/application-submission-status.service'; -import { SUBMISSION_STATUS } from '../../../application/application-submission-status/submission-status.dto'; -import { DocumentService } from '../../../../document/document.service'; -import { ApplicationService } from '../../../application/application.service'; -import { ApplicationCeoCriterionCode } from '../../application-ceo-criterion/application-ceo-criterion.entity'; -import { ApplicationDecisionDocument } from '../../application-decision-document/application-decision-document.entity'; -import { ApplicationDecisionMakerCode } from '../../application-decision-maker/application-decision-maker.entity'; -import { ApplicationDecisionOutcomeCode } from '../../application-decision-outcome.entity'; -import { ApplicationDecision } from '../../application-decision.entity'; -import { ApplicationDecisionV1Service } from './application-decision-v1.service'; -import { - CreateApplicationDecisionDto, - UpdateApplicationDecisionDto, -} from './application-decision.dto'; - -describe('ApplicationDecisionV1Service', () => { - let service: ApplicationDecisionV1Service; - let mockDecisionRepository: DeepMocked<Repository<ApplicationDecision>>; - let mockDecisionDocumentRepository: DeepMocked< - Repository<ApplicationDecisionDocument> - >; - let mockDecisionOutcomeRepository: DeepMocked< - Repository<ApplicationDecisionOutcomeCode> - >; - let mockDecisionMakerCodeRepository: DeepMocked< - Repository<ApplicationDecisionMakerCode> - >; - let mockCeoCriterionCodeRepository: DeepMocked< - Repository<ApplicationCeoCriterionCode> - >; - let mockApplicationService: DeepMocked<ApplicationService>; - let mockDocumentService: DeepMocked<DocumentService>; - let mockApplicationSubmissionStatusService: DeepMocked<ApplicationSubmissionStatusService>; - - let mockApplication; - let mockDecision; - - beforeEach(async () => { - mockApplicationService = createMock<ApplicationService>(); - mockDocumentService = createMock<DocumentService>(); - mockDecisionRepository = createMock<Repository<ApplicationDecision>>(); - mockDecisionDocumentRepository = - createMock<Repository<ApplicationDecisionDocument>>(); - mockDecisionOutcomeRepository = - createMock<Repository<ApplicationDecisionOutcomeCode>>(); - mockDecisionMakerCodeRepository = - createMock<Repository<ApplicationDecisionMakerCode>>(); - mockCeoCriterionCodeRepository = - createMock<Repository<ApplicationCeoCriterionCode>>(); - mockApplicationSubmissionStatusService = createMock(); - - const module: TestingModule = await Test.createTestingModule({ - imports: [ - AutomapperModule.forRoot({ - strategyInitializer: classes(), - }), - ], - providers: [ - ApplicationDecisionV1Service, - { - provide: getRepositoryToken(ApplicationDecision), - useValue: mockDecisionRepository, - }, - { - provide: getRepositoryToken(ApplicationDecisionDocument), - useValue: mockDecisionDocumentRepository, - }, - { - provide: getRepositoryToken(ApplicationDecisionMakerCode), - useValue: mockDecisionMakerCodeRepository, - }, - { - provide: getRepositoryToken(ApplicationCeoCriterionCode), - useValue: mockCeoCriterionCodeRepository, - }, - { - provide: getRepositoryToken(ApplicationDecisionOutcomeCode), - useValue: mockDecisionOutcomeRepository, - }, - { - provide: ApplicationService, - useValue: mockApplicationService, - }, - { - provide: DocumentService, - useValue: mockDocumentService, - }, - { - provide: ApplicationSubmissionStatusService, - useValue: mockApplicationSubmissionStatusService, - }, - ], - }).compile(); - - service = module.get<ApplicationDecisionV1Service>( - ApplicationDecisionV1Service, - ); - - mockApplication = initApplicationMockEntity(); - mockDecision = initApplicationDecisionMock(mockApplication); - - mockDecisionRepository.find.mockResolvedValue([mockDecision]); - mockDecisionRepository.findOne.mockResolvedValue(mockDecision); - mockDecisionRepository.save.mockResolvedValue(mockDecision); - - mockDecisionDocumentRepository.find.mockResolvedValue([]); - - mockApplicationService.getOrFail.mockResolvedValue(mockApplication); - mockApplicationService.update.mockResolvedValue({} as any); - mockApplicationService.updateByUuid.mockResolvedValue({} as any); - - mockDecisionOutcomeRepository.find.mockResolvedValue([]); - mockDecisionOutcomeRepository.findOneOrFail.mockResolvedValue({} as any); - - mockDecisionMakerCodeRepository.find.mockResolvedValue([]); - mockCeoCriterionCodeRepository.find.mockResolvedValue([]); - - mockApplicationSubmissionStatusService.setStatusDateByFileNumber.mockResolvedValue( - {} as any, - ); - }); - - describe('ApplicationDecisionService Core Tests', () => { - it('should be defined', () => { - expect(service).toBeDefined(); - }); - - it('should get decisions by application', async () => { - const result = await service.getByAppFileNumber( - mockApplication.fileNumber, - ); - - expect(result).toStrictEqual([mockDecision]); - }); - - it('should return decisions by uuid', async () => { - const result = await service.get(mockDecision.uuid); - - expect(result).toStrictEqual(mockDecision); - }); - - it('should delete decision with uuid and update application and submission status', async () => { - mockDecisionRepository.softRemove.mockResolvedValue({} as any); - mockDecisionRepository.findOne.mockResolvedValue({ - ...mockDecision, - reconsiders: 'reconsider-uuid', - modifies: 'modified-uuid', - }); - mockDecisionRepository.find.mockResolvedValue([]); - - await service.delete(mockDecision.uuid); - - expect(mockDecisionRepository.save.mock.calls[0][0].modifies).toBeNull(); - expect( - mockDecisionRepository.save.mock.calls[0][0].reconsiders, - ).toBeNull(); - expect(mockDecisionRepository.softRemove).toBeCalledTimes(1); - expect(mockApplicationService.update).toHaveBeenCalledTimes(1); - expect(mockApplicationService.update).toHaveBeenCalledWith( - mockApplication, - { - decisionDate: null, - }, - ); - expect( - mockApplicationSubmissionStatusService.setStatusDateByFileNumber, - ).toBeCalledTimes(1); - expect( - mockApplicationSubmissionStatusService.setStatusDateByFileNumber, - ).toBeCalledWith( - mockApplication.fileNumber, - SUBMISSION_STATUS.ALC_DECISION, - null, - ); - }); - - it('should create a decision and update the application if this was the first decision', async () => { - mockDecisionRepository.find.mockResolvedValue([]); - mockDecisionRepository.findOne.mockResolvedValueOnce(null); - - const decisionDate = new Date(2022, 2, 2, 2, 2, 2, 2); - const decisionToCreate = { - date: decisionDate.getTime(), - applicationFileNumber: 'file-number', - outcomeCode: 'Outcome', - } as CreateApplicationDecisionDto; - - await service.create( - decisionToCreate, - mockApplication, - undefined, - undefined, - ); - - expect(mockDecisionRepository.save).toBeCalledTimes(1); - expect(mockApplicationService.update).toHaveBeenCalledTimes(1); - expect(mockApplicationService.update).toHaveBeenCalledWith( - mockApplication, - { - decisionDate, - }, - ); - }); - - it('should fail create a decision and update application if the resolution number is already in use', async () => { - mockDecisionRepository.findOne.mockResolvedValue( - {} as ApplicationDecision, - ); - - const decisionDate = new Date(2022, 2, 2, 2, 2, 2, 2); - const decisionToCreate = { - resolutionNumber: 1, - resolutionYear: 1, - date: decisionDate.getTime(), - applicationFileNumber: 'file-number', - outcomeCode: 'Outcome', - } as CreateApplicationDecisionDto; - - await expect( - service.create(decisionToCreate, mockApplication, undefined, undefined), - ).rejects.toMatchObject( - new ServiceValidationException( - `Resolution number #${decisionToCreate.resolutionNumber}/${decisionToCreate.resolutionYear} is already in use`, - ), - ); - - expect(mockDecisionRepository.save).toBeCalledTimes(0); - expect(mockApplicationService.update).toHaveBeenCalledTimes(0); - }); - - it('should create a decision and NOT update the application if this was the second decision', async () => { - mockDecisionRepository.findOne.mockResolvedValueOnce(null); - - const decisionDate = new Date(2022, 2, 2, 2, 2, 2, 2); - const decisionToCreate = { - date: decisionDate.getTime(), - applicationFileNumber: 'file-number', - outcomeCode: 'Outcome', - } as CreateApplicationDecisionDto; - - await service.create( - decisionToCreate, - mockApplication, - undefined, - undefined, - ); - - expect(mockDecisionRepository.save).toBeCalledTimes(1); - expect(mockApplicationService.update).not.toHaveBeenCalled(); - }); - - it('should update the decision and update the application and submission status if it was the only decision', async () => { - const decisionDate = new Date(2022, 3, 3, 3, 3, 3, 3); - const decisionUpdate: UpdateApplicationDecisionDto = { - date: decisionDate.getTime(), - outcomeCode: 'New Outcome', - }; - - await service.update( - mockDecision.uuid, - decisionUpdate, - undefined, - undefined, - ); - - expect(mockDecisionRepository.findOne).toBeCalledTimes(2); - expect(mockDecisionRepository.save).toBeCalledTimes(1); - expect(mockApplicationService.updateByUuid).toHaveBeenCalledTimes(1); - expect(mockApplicationService.updateByUuid).toHaveBeenCalledWith( - mockApplication.uuid, - { - decisionDate, - }, - ); - expect( - mockApplicationSubmissionStatusService.setStatusDateByFileNumber, - ).toBeCalledTimes(1); - expect( - mockApplicationSubmissionStatusService.setStatusDateByFileNumber, - ).toBeCalledWith( - mockApplication.fileNumber, - SUBMISSION_STATUS.ALC_DECISION, - decisionDate, - ); - }); - - it('should not update the application if this was not the first decision', async () => { - const secondDecision = initApplicationDecisionMock(mockApplication); - secondDecision.uuid = 'second-uuid'; - mockDecisionRepository.find.mockResolvedValue([ - secondDecision, - mockDecision, - ]); - mockDecisionRepository.findOne.mockResolvedValue(secondDecision); - - const decisionDate = new Date(2022, 3, 3, 3, 3, 3, 3); - const decisionUpdate: UpdateApplicationDecisionDto = { - date: decisionDate.getTime(), - outcomeCode: 'New Outcome', - }; - - await service.update( - mockDecision.uuid, - decisionUpdate, - undefined, - undefined, - ); - - expect(mockDecisionRepository.findOne).toBeCalledTimes(2); - expect(mockDecisionRepository.save).toBeCalledTimes(1); - expect(mockApplicationService.update).not.toHaveBeenCalled(); - }); - - it('should fail on update if the decision is not found', async () => { - const nonExistantUuid = 'bad-uuid'; - mockDecisionRepository.findOne.mockResolvedValue(null); - const decisionUpdate: UpdateApplicationDecisionDto = { - date: new Date(2022, 2, 2, 2, 2, 2, 2).getTime(), - outcomeCode: 'New Outcome', - }; - const promise = service.update( - nonExistantUuid, - decisionUpdate, - undefined, - undefined, - ); - - await expect(promise).rejects.toMatchObject( - new ServiceNotFoundException( - `Decision with UUID ${nonExistantUuid} not found`, - ), - ); - expect(mockDecisionRepository.save).toBeCalledTimes(0); - }); - - it('should fail on update if trying to set ceo criterion but decision maker is not CEO', async () => { - const uuid = 'uuid'; - const decisionUpdate: UpdateApplicationDecisionDto = { - ceoCriterionCode: 'fake-code', - }; - - const promise = service.update( - uuid, - decisionUpdate, - undefined, - undefined, - ); - - await expect(promise).rejects.toMatchObject( - new ServiceValidationException( - `Cannot set ceo criterion code unless ceo the decision maker`, - ), - ); - expect(mockDecisionRepository.save).toBeCalledTimes(0); - }); - - it('should allow updating the ceo criterion and decision maker to CEO', async () => { - const uuid = 'uuid'; - const decisionUpdate: UpdateApplicationDecisionDto = { - ceoCriterionCode: 'fake-code', - decisionMakerCode: 'CEOP', - }; - - await service.update(uuid, decisionUpdate, undefined, undefined); - - expect(mockDecisionRepository.findOne).toBeCalledTimes(2); - expect(mockDecisionRepository.save).toBeCalledTimes(1); - }); - - it('should fail on update if trying to set time extension when ceo criterion is not correct', async () => { - const uuid = 'uuid'; - const decisionUpdate: UpdateApplicationDecisionDto = { - isTimeExtension: true, - }; - - const promise = service.update( - uuid, - decisionUpdate, - undefined, - undefined, - ); - - await expect(promise).rejects.toMatchObject( - new ServiceValidationException( - `Cannot set time extension unless ceo criterion is modification`, - ), - ); - expect(mockDecisionRepository.save).toBeCalledTimes(0); - }); - - it('should call through for get code', async () => { - await service.fetchCodes(); - expect(mockDecisionOutcomeRepository.find).toHaveBeenCalledTimes(1); - }); - }); - - describe('ApplicationDecisionService File Tests', () => { - let mockDocument; - beforeEach(() => { - mockDecisionDocumentRepository.findOne.mockResolvedValue(mockDocument); - mockDecisionDocumentRepository.save.mockResolvedValue(mockDocument); - - mockDocument = { - uuid: 'fake-uuid', - decisionUuid: 'decision-uuid', - } as ApplicationDecisionDocument; - }); - - it('should call the repository for attaching a file', async () => { - mockDocumentService.create.mockResolvedValue({} as any); - - await service.attachDocument('uuid', {} as any, {} as any); - expect(mockDecisionDocumentRepository.save).toHaveBeenCalledTimes(1); - expect(mockDocumentService.create).toHaveBeenCalledTimes(1); - }); - - it('should throw an exception when attaching a document to a non-existent decision', async () => { - mockDecisionRepository.findOne.mockResolvedValue(null); - await expect( - service.attachDocument('uuid', {} as any, {} as any), - ).rejects.toMatchObject( - new ServiceNotFoundException(`Decision with UUID uuid not found`), - ); - expect(mockDocumentService.create).not.toHaveBeenCalled(); - }); - - it('should call the repository to delete documents', async () => { - mockDecisionDocumentRepository.softRemove.mockResolvedValue({} as any); - - await service.deleteDocument('fake-uuid'); - expect(mockDecisionDocumentRepository.softRemove).toHaveBeenCalledTimes( - 1, - ); - }); - - it('should throw an exception when document not found for deletion', async () => { - mockDecisionDocumentRepository.findOne.mockResolvedValue(null); - await expect(service.deleteDocument('fake-uuid')).rejects.toMatchObject( - new ServiceNotFoundException( - `Failed to find document with uuid fake-uuid`, - ), - ); - expect(mockDocumentService.softRemove).not.toHaveBeenCalled(); - }); - - it('should call through to document service for download', async () => { - const downloadUrl = 'download-url'; - mockDocumentService.getDownloadUrl.mockResolvedValue(downloadUrl); - - const res = await service.getDownloadUrl('fake-uuid'); - - expect(mockDocumentService.getDownloadUrl).toHaveBeenCalledTimes(1); - expect(res).toEqual(downloadUrl); - }); - - it('should throw an exception when document not found for download', async () => { - mockDecisionDocumentRepository.findOne.mockResolvedValue(null); - await expect(service.getDownloadUrl('fake-uuid')).rejects.toMatchObject( - new ServiceNotFoundException( - `Failed to find document with uuid fake-uuid`, - ), - ); - }); - }); -}); diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision/application-decision-v1.service.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision/application-decision-v1.service.ts deleted file mode 100644 index c6541c5925..0000000000 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision/application-decision-v1.service.ts +++ /dev/null @@ -1,501 +0,0 @@ -import { - ServiceNotFoundException, - ServiceValidationException, -} from '@app/common/exceptions/base.exception'; -import { MultipartFile } from '@fastify/multipart'; -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { In, IsNull, Repository } from 'typeorm'; -import { ApplicationSubmissionStatusService } from '../../../application/application-submission-status/application-submission-status.service'; -import { SUBMISSION_STATUS } from '../../../application/application-submission-status/submission-status.dto'; -import { - DOCUMENT_SOURCE, - DOCUMENT_SYSTEM, -} from '../../../../document/document.dto'; -import { DocumentService } from '../../../../document/document.service'; -import { User } from '../../../../user/user.entity'; -import { formatIncomingDate } from '../../../../utils/incoming-date.formatter'; -import { Application } from '../../../application/application.entity'; -import { ApplicationService } from '../../../application/application.service'; -import { ApplicationCeoCriterionCode } from '../../application-ceo-criterion/application-ceo-criterion.entity'; -import { ApplicationDecisionDocument } from '../../application-decision-document/application-decision-document.entity'; -import { ApplicationDecisionMakerCode } from '../../application-decision-maker/application-decision-maker.entity'; -import { ApplicationDecisionOutcomeCode } from '../../application-decision-outcome.entity'; -import { ApplicationDecision } from '../../application-decision.entity'; -import { ApplicationModification } from '../../application-modification/application-modification.entity'; -import { ApplicationReconsideration } from '../../application-reconsideration/application-reconsideration.entity'; -import { - CreateApplicationDecisionDto, - UpdateApplicationDecisionDto, -} from './application-decision.dto'; - -@Injectable() -export class ApplicationDecisionV1Service { - constructor( - @InjectRepository(ApplicationDecision) - private appDecisionRepository: Repository<ApplicationDecision>, - @InjectRepository(ApplicationDecisionDocument) - private decisionDocumentRepository: Repository<ApplicationDecisionDocument>, - @InjectRepository(ApplicationDecisionOutcomeCode) - private decisionOutcomeRepository: Repository<ApplicationDecisionOutcomeCode>, - @InjectRepository(ApplicationDecisionMakerCode) - private decisionMakerRepository: Repository<ApplicationDecisionMakerCode>, - @InjectRepository(ApplicationCeoCriterionCode) - private ceoCriterionRepository: Repository<ApplicationCeoCriterionCode>, - private applicationService: ApplicationService, - private documentService: DocumentService, - private applicationSubmissionStatusService: ApplicationSubmissionStatusService, - ) {} - - async getByAppFileNumber(number: string) { - const application = await this.applicationService.getOrFail(number); - - const decisions = await this.appDecisionRepository.find({ - where: { - applicationUuid: application.uuid, - }, - order: { - date: 'DESC', - }, - relations: { - outcome: true, - decisionMaker: true, - ceoCriterion: true, - modifies: { - modifiesDecisions: true, - }, - reconsiders: { - reconsidersDecisions: true, - }, - chairReviewOutcome: true, - }, - }); - - // do not place modifiedBy into query above, it will kill performance - const decisionsWithModifiedBy = await this.appDecisionRepository.find({ - where: { - applicationUuid: application.uuid, - }, - relations: { - modifiedBy: { - resultingDecision: true, - reviewOutcome: true, - }, - }, - }); - - // do not place reconsideredBy into query above, it will kill performance - const decisionsWithReconsideredBy = await this.appDecisionRepository.find({ - where: { - applicationUuid: application.uuid, - }, - relations: { - reconsideredBy: { - resultingDecision: true, - reviewOutcome: true, - }, - }, - }); - - for (const decision of decisions) { - decision.reconsideredBy = - decisionsWithReconsideredBy.find((r) => r.uuid === decision.uuid) - ?.reconsideredBy || []; - - decision.modifiedBy = - decisionsWithModifiedBy.find((r) => r.uuid === decision.uuid) - ?.modifiedBy || []; - } - - //Query Documents separately as when added to the above joins caused performance issues - for (const decision of decisions) { - decision.documents = await this.decisionDocumentRepository.find({ - where: { - decisionUuid: decision.uuid, - document: { - auditDeletedDateAt: IsNull(), - }, - }, - relations: { - document: true, - }, - }); - } - return decisions; - } - - async get(uuid) { - const decision = await this.appDecisionRepository.findOne({ - where: { - uuid, - }, - relations: { - outcome: true, - decisionMaker: true, - ceoCriterion: true, - documents: { - document: true, - }, - }, - }); - - if (!decision) { - throw new ServiceNotFoundException( - `Failed to load decision with uuid ${uuid}`, - ); - } - - decision.documents = decision.documents.filter( - (document) => !!document.document, - ); - return decision; - } - - async update( - uuid: string, - updateDto: UpdateApplicationDecisionDto, - modifies: ApplicationModification | undefined | null, - reconsiders: ApplicationReconsideration | undefined | null, - ) { - const existingDecision: Partial<ApplicationDecision> = await this.getOrFail( - uuid, - ); - - await this.validateDecisionChanges(updateDto); - - // resolution number is int64 in postgres, which means it is a string in JS - if ( - updateDto.resolutionNumber && - updateDto.resolutionYear && - (existingDecision.resolutionNumber !== updateDto.resolutionNumber || - existingDecision.resolutionYear !== updateDto.resolutionYear) - ) { - await this.validateResolutionNumber( - updateDto.resolutionNumber, - updateDto.resolutionYear, - ); - } - - existingDecision.decisionMakerCode = updateDto.decisionMakerCode; - existingDecision.ceoCriterionCode = updateDto.ceoCriterionCode; - existingDecision.isTimeExtension = updateDto.isTimeExtension; - existingDecision.isOther = updateDto.isOther; - existingDecision.auditDate = formatIncomingDate(updateDto.auditDate); - existingDecision.chairReviewDate = formatIncomingDate( - updateDto.chairReviewDate, - ); - existingDecision.chairReviewOutcomeCode = updateDto.chairReviewOutcomeCode; - existingDecision.modifies = modifies; - existingDecision.reconsiders = reconsiders; - existingDecision.resolutionNumber = updateDto.resolutionNumber; - existingDecision.resolutionYear = updateDto.resolutionYear; - - if (updateDto.outcomeCode) { - existingDecision.outcome = await this.getOutcomeByCode( - updateDto.outcomeCode, - ); - } - - if (updateDto.chairReviewRequired !== undefined) { - existingDecision.chairReviewRequired = updateDto.chairReviewRequired; - if (!updateDto.chairReviewRequired) { - existingDecision.chairReviewDate = null; - existingDecision.chairReviewOutcomeCode = null; - } - } - - let dateHasChanged = false; - if (updateDto.date && existingDecision.date !== new Date(updateDto.date)) { - dateHasChanged = true; - existingDecision.date = new Date(updateDto.date); - } - - const updatedDecision = await this.appDecisionRepository.save( - existingDecision, - ); - - //If we are updating the date, we need to check if it's the first decision and if so update the application decisionDate - if (dateHasChanged) { - const existingDecisions = await this.getByAppFileNumber( - existingDecision.application!.fileNumber, - ); - - const decisionIndex = existingDecisions.findIndex( - (dec) => dec.uuid === existingDecision.uuid, - ); - - if (decisionIndex === existingDecisions.length - 1) { - await this.applicationService.updateByUuid( - existingDecision.applicationUuid!, - { - decisionDate: updatedDecision.date, - }, - ); - - await this.applicationSubmissionStatusService.setStatusDateByFileNumber( - existingDecision.application!.fileNumber, - SUBMISSION_STATUS.ALC_DECISION, - updatedDecision.date, - ); - } - } - - return this.get(existingDecision.uuid); - } - - private async getOrFail(uuid: string) { - const existingDecision = await this.appDecisionRepository.findOne({ - where: { - uuid, - }, - relations: { - application: true, - }, - }); - - if (!existingDecision) { - throw new ServiceNotFoundException( - `Decision with UUID ${uuid} not found`, - ); - } - return existingDecision; - } - - private async validateDecisionChanges( - updateData: UpdateApplicationDecisionDto, - ) { - if ( - updateData.ceoCriterionCode && - updateData.decisionMakerCode !== 'CEOP' - ) { - throw new ServiceValidationException( - 'Cannot set ceo criterion code unless ceo the decision maker', - ); - } - - if ( - updateData.ceoCriterionCode !== 'MODI' && - (updateData.isTimeExtension === true || - updateData.isTimeExtension === false) - ) { - throw new ServiceValidationException( - 'Cannot set time extension unless ceo criterion is modification', - ); - } - } - - async create( - createDto: CreateApplicationDecisionDto, - application: Application, - modifies: ApplicationModification | undefined | null, - reconsiders: ApplicationReconsideration | undefined | null, - ) { - const decision = new ApplicationDecision({ - outcome: await this.getOutcomeByCode(createDto.outcomeCode), - date: new Date(createDto.date), - resolutionNumber: createDto.resolutionNumber, - resolutionYear: createDto.resolutionYear, - chairReviewRequired: createDto.chairReviewRequired, - auditDate: createDto.auditDate - ? new Date(createDto.auditDate) - : undefined, - chairReviewDate: createDto.chairReviewDate - ? new Date(createDto.chairReviewDate) - : undefined, - chairReviewOutcomeCode: createDto.chairReviewOutcomeCode, - ceoCriterionCode: createDto.ceoCriterionCode, - decisionMakerCode: createDto.decisionMakerCode, - isTimeExtension: createDto.isTimeExtension, - isOther: createDto.isOther, - application, - modifies, - reconsiders, - }); - - await this.validateDecisionChanges(createDto); - - await this.validateResolutionNumber( - createDto.resolutionNumber, - createDto.resolutionYear, - ); - - const existingDecisions = await this.getByAppFileNumber( - application.fileNumber, - ); - if (existingDecisions.length === 0) { - await this.applicationService.update(application, { - decisionDate: decision.date, - }); - } - - const savedDecision = await this.appDecisionRepository.save(decision); - - return this.get(savedDecision.uuid); - } - - private async validateResolutionNumber(number, year) { - const existingDecision = await this.appDecisionRepository.findOne({ - where: { - resolutionNumber: number, - resolutionYear: year, - }, - withDeleted: true, - }); - - if (existingDecision) { - throw new ServiceValidationException( - `Resolution number #${number}/${year} is already in use`, - ); - } - } - - async delete(uuid) { - const applicationDecision = await this.appDecisionRepository.findOne({ - where: { uuid }, - relations: { - outcome: true, - documents: { - document: true, - }, - application: true, - }, - }); - - if (!applicationDecision) { - throw new ServiceNotFoundException( - `Failed to find decision with uuid ${uuid}`, - ); - } - - for (const document of applicationDecision.documents) { - await this.documentService.softRemove(document.document); - } - - //Clear potential links - applicationDecision.reconsiders = null; - applicationDecision.modifies = null; - await this.appDecisionRepository.save(applicationDecision); - - await this.appDecisionRepository.softRemove([applicationDecision]); - - const existingDecisions = await this.getByAppFileNumber( - applicationDecision.application.fileNumber, - ); - if (existingDecisions.length === 0) { - await this.applicationService.update(applicationDecision.application, { - decisionDate: null, - }); - await this.applicationSubmissionStatusService.setStatusDateByFileNumber( - applicationDecision.application.fileNumber, - SUBMISSION_STATUS.ALC_DECISION, - null, - ); - } else { - await this.applicationService.update(applicationDecision.application, { - decisionDate: existingDecisions[existingDecisions.length - 1].date, - }); - await this.applicationSubmissionStatusService.setStatusDateByFileNumber( - applicationDecision.application.fileNumber, - SUBMISSION_STATUS.ALC_DECISION, - existingDecisions[existingDecisions.length - 1].date, - ); - } - } - - async attachDocument(decisionUuid: string, file: MultipartFile, user: User) { - const decision = await this.getOrFail(decisionUuid); - const document = await this.documentService.create( - `decision/${decision.uuid}`, - file.filename, - file, - user, - DOCUMENT_SOURCE.ALC, - DOCUMENT_SYSTEM.ALCS, - ); - const appDocument = new ApplicationDecisionDocument({ - decision, - document, - }); - - return this.decisionDocumentRepository.save(appDocument); - } - - async deleteDocument(decisionDocumentUuid: string) { - const decisionDocument = await this.getDecisionDocumentOrFail( - decisionDocumentUuid, - ); - - await this.decisionDocumentRepository.softRemove(decisionDocument); - return decisionDocument; - } - - async getDownloadUrl(decisionDocumentUuid: string, openInline = false) { - const decisionDocument = await this.getDecisionDocumentOrFail( - decisionDocumentUuid, - ); - - return this.documentService.getDownloadUrl( - decisionDocument.document, - openInline, - ); - } - - private async getDecisionDocumentOrFail(decisionDocumentUuid: string) { - const decisionDocument = await this.decisionDocumentRepository.findOne({ - where: { - uuid: decisionDocumentUuid, - }, - relations: { - document: true, - }, - }); - - if (!decisionDocument) { - throw new ServiceNotFoundException( - `Failed to find document with uuid ${decisionDocumentUuid}`, - ); - } - return decisionDocument; - } - - getOutcomeByCode(code: string) { - return this.decisionOutcomeRepository.findOneOrFail({ - where: { - code, - }, - }); - } - - async fetchCodes() { - const values = await Promise.all([ - this.decisionOutcomeRepository.find(), - this.decisionMakerRepository.find({ - order: { - label: 'ASC', - }, - where: { - isActive: true, - }, - }), - this.ceoCriterionRepository.find({ - order: { - number: 'ASC', - }, - }), - ]); - - return { - outcomes: values[0], - decisionMakers: values[1], - ceoCriterion: values[2], - }; - } - - getMany(modifiesDecisionUuids: string[]) { - return this.appDecisionRepository.find({ - where: { - uuid: In(modifiesDecisionUuids), - }, - }); - } -} diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision/application-decision.dto.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision/application-decision.dto.ts deleted file mode 100644 index 2deb7f96e6..0000000000 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision/application-decision.dto.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { AutoMap } from 'automapper-classes'; -import { - IsBoolean, - IsNumber, - IsOptional, - IsString, - IsUUID, -} from 'class-validator'; -import { BaseCodeDto } from '../../../../common/dtos/base.dto'; -import { CeoCriterionCodeDto } from './ceo-criterion/ceo-criterion.dto'; -import { ApplicationDecisionMakerCodeDto } from '../../application-decision-maker/decision-maker.dto'; - -export class UpdateApplicationDecisionDto { - @IsNumber() - @IsOptional() - resolutionNumber?: number; - - @IsNumber() - @IsOptional() - resolutionYear?: number; - - @IsNumber() - @IsOptional() - date?: number; - - @IsString() - @IsOptional() - outcomeCode?: string; - - @IsNumber() - @IsOptional() - auditDate?: number | null; - - @IsBoolean() - @IsOptional() - chairReviewRequired?: boolean; - - @IsNumber() - @IsOptional() - chairReviewDate?: number | null; - - @IsString() - @IsOptional() - decisionMakerCode?: string | null; - - @IsString() - @IsOptional() - ceoCriterionCode?: string | null; - - @IsBoolean() - @IsOptional() - isTimeExtension?: boolean | null; - - @IsBoolean() - @IsOptional() - isOther?: boolean | null; - - @IsString() - @IsOptional() - chairReviewOutcomeCode?: string | null; - - @IsUUID() - @IsOptional() - modifiesUuid?: string | null; - - @IsUUID() - @IsOptional() - reconsidersUuid?: string | null; -} - -export class CreateApplicationDecisionDto extends UpdateApplicationDecisionDto { - @IsString() - applicationFileNumber; - - @IsNumber() - date: number; - - @IsString() - outcomeCode: string; - - @IsNumber() - resolutionNumber: number; - - @IsNumber() - resolutionYear: number; - - @IsBoolean() - chairReviewRequired: boolean; - - @IsUUID() - @IsOptional() - modifiesUuid?: string; - - @IsUUID() - @IsOptional() - reconsidersUuid?: string; -} - -export class DecisionOutcomeCodeDto extends BaseCodeDto { - @AutoMap() - isFirstDecision: boolean; -} - -export class ChairReviewOutcomeCodeDto extends BaseCodeDto {} - -export class ApplicationDecisionDto { - @AutoMap() - uuid: string; - - @AutoMap() - applicationFileNumber; - - @AutoMap() - date?: number; - - @AutoMap() - outcome: DecisionOutcomeCodeDto; - - @AutoMap() - resolutionNumber: string; - - @AutoMap() - resolutionYear: number; - - @AutoMap() - chairReviewRequired: boolean; - - @AutoMap() - auditDate?: number | null; - - @AutoMap() - chairReviewDate?: number | null; - - @AutoMap() - chairReviewOutcome?: ChairReviewOutcomeCodeDto | null; - - @AutoMap() - documents: DecisionDocumentDto[]; - - @AutoMap() - decisionMaker?: ApplicationDecisionMakerCodeDto | null; - - @AutoMap() - ceoCriterion?: CeoCriterionCodeDto | null; - - @AutoMap(() => Boolean) - isTimeExtension?: boolean | null; - - @AutoMap(() => Boolean) - isOther?: boolean | null; - - @AutoMap(() => Boolean) - isDraft: boolean; - - reconsiders?: LinkedResolutionDto; - modifies?: LinkedResolutionDto; - reconsideredBy?: LinkedResolutionDto[]; - modifiedBy?: LinkedResolutionDto[]; -} - -export class LinkedResolutionDto { - uuid: string; - linkedResolutions: string[]; -} - -export class DecisionDocumentDto { - @AutoMap() - uuid: string; - - fileName: string; - fileSize: number; - mimeType: string; - uploadedBy: string; - uploadedAt: number; -} diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision/ceo-criterion/ceo-criterion.dto.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision/ceo-criterion/ceo-criterion.dto.ts deleted file mode 100644 index 8e6b398522..0000000000 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision/ceo-criterion/ceo-criterion.dto.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { AutoMap } from 'automapper-classes'; -import { IsNumber } from 'class-validator'; -import { BaseCodeDto } from '../../../../../common/dtos/base.dto'; - -export class CeoCriterionCodeDto extends BaseCodeDto { - @AutoMap() - @IsNumber() - number: number; -} diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-v2/application-decision-v2.module.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-v2/application-decision-v2.module.ts index 98a4799f97..168020a1ce 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-v2/application-decision-v2.module.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-decision-v2/application-decision-v2.module.ts @@ -31,11 +31,12 @@ import { ApplicationDecisionDocument } from '../application-decision-document/ap import { ApplicationDecisionMakerCode } from '../application-decision-maker/application-decision-maker.entity'; import { ApplicationDecisionChairReviewOutcomeType } from '../application-decision-outcome-type/application-decision-outcome-type.entity'; import { ApplicationDecisionOutcomeCode } from '../application-decision-outcome.entity'; -import { ApplicationDecisionV1Service } from '../application-decision-v1/application-decision/application-decision-v1.service'; import { ApplicationDecision } from '../application-decision.entity'; import { ApplicationModificationOutcomeType } from '../application-modification/application-modification-outcome-type/application-modification-outcome-type.entity'; +import { ApplicationModificationController } from '../application-modification/application-modification.controller'; import { ApplicationModification } from '../application-modification/application-modification.entity'; import { ApplicationModificationService } from '../application-modification/application-modification.service'; +import { ApplicationReconsiderationController } from '../application-reconsideration/application-reconsideration.controller'; import { ApplicationReconsideration } from '../application-reconsideration/application-reconsideration.entity'; import { ApplicationReconsiderationService } from '../application-reconsideration/application-reconsideration.service'; import { ApplicationReconsiderationOutcomeType } from '../application-reconsideration/reconsideration-outcome-type/application-reconsideration-outcome-type.entity'; @@ -78,15 +79,11 @@ import { ApplicationDecisionComponentService } from './application-decision/comp forwardRef(() => ApplicationModule), CardModule, DocumentModule, - ApplicationDecisionV2Module, ApplicationSubmissionStatusModule, ], providers: [ ApplicationModificationService, ApplicationReconsiderationService, - // This is required because it is referenced in ApplicationModificationService and ApplicationReconsiderationService. - // However it must not be used anywhere in the reconsideration v2 controller directly. - ApplicationDecisionV1Service, ModificationProfile, ReconsiderationProfile, ApplicationDecisionV2Service, @@ -104,7 +101,13 @@ import { ApplicationDecisionComponentService } from './application-decision/comp ApplicationDecisionComponentLotController, ApplicationConditionToComponentLotController, ApplicationBoundaryAmendmentController, + ApplicationReconsiderationController, + ApplicationModificationController, + ], + exports: [ + ApplicationDecisionV2Service, + ApplicationModificationService, + ApplicationReconsiderationService, ], - exports: [ApplicationDecisionV2Service], }) export class ApplicationDecisionV2Module {} diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision.module.ts b/services/apps/alcs/src/alcs/application-decision/application-decision.module.ts index 4fd01b6ed1..e68645c078 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision.module.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-decision.module.ts @@ -1,17 +1,15 @@ import { Module } from '@nestjs/common'; import { RouterModule } from '@nestjs/core'; -import { ApplicationDecisionV1Module } from './application-decision-v1/application-decision-v1.module'; import { ApplicationDecisionV2Module } from './application-decision-v2/application-decision-v2.module'; @Module({ imports: [ - ApplicationDecisionV1Module, ApplicationDecisionV2Module, RouterModule.register([ - { path: 'alcs', module: ApplicationDecisionV1Module }, { path: 'alcs/v2', module: ApplicationDecisionV2Module }, ]), ], - exports: [ApplicationDecisionV1Module, ApplicationDecisionV2Module], + providers: [], + exports: [ApplicationDecisionV2Module], }) export class ApplicationDecisionModule {} diff --git a/services/apps/alcs/src/alcs/application-decision/application-modification/application-modification.dto.ts b/services/apps/alcs/src/alcs/application-decision/application-modification/application-modification.dto.ts index dd9db30855..548de1f689 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-modification/application-modification.dto.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-modification/application-modification.dto.ts @@ -14,8 +14,8 @@ import { LocalGovernmentDto } from '../../local-government/local-government.dto' import { CardDto } from '../../card/card.dto'; import { ApplicationRegionDto } from '../../code/application-code/application-region/application-region.dto'; import { ApplicationTypeDto } from '../../code/application-code/application-type/application-type.dto'; -import { ApplicationDecisionMeetingDto } from '../application-decision-v1/application-decision-meeting/application-decision-meeting.dto'; -import { ApplicationDecisionDto } from '../application-decision-v1/application-decision/application-decision.dto'; +import { DecisionMeetingDto } from '../../meetings/decision-meeting.dto'; +import { ApplicationDecisionDto } from '../application-decision-v2/application-decision/application-decision.dto'; export class ApplicationModificationOutcomeCodeDto extends BaseCodeDto {} @@ -89,7 +89,7 @@ export class ApplicationForModificationDto { applicant: string; region: ApplicationRegionDto; localGovernment: LocalGovernmentDto; - decisionMeetings: ApplicationDecisionMeetingDto[]; + decisionMeetings: DecisionMeetingDto[]; } export class ApplicationModificationDto { diff --git a/services/apps/alcs/src/alcs/application-decision/application-modification/application-modification.service.spec.ts b/services/apps/alcs/src/alcs/application-decision/application-modification/application-modification.service.spec.ts index bbd87ae758..921a8a5c7e 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-modification/application-modification.service.spec.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-modification/application-modification.service.spec.ts @@ -15,7 +15,7 @@ import { ApplicationService } from '../../application/application.service'; import { Board } from '../../board/board.entity'; import { Card } from '../../card/card.entity'; import { CardService } from '../../card/card.service'; -import { ApplicationDecisionV1Service } from '../application-decision-v1/application-decision/application-decision-v1.service'; +import { ApplicationDecisionV2Service } from '../application-decision-v2/application-decision/application-decision-v2.service'; import { ApplicationDecision } from '../application-decision.entity'; import { ApplicationModificationCreateDto, @@ -29,7 +29,7 @@ describe('ApplicationModificationService', () => { let service: ApplicationModificationService; let applicationServiceMock: DeepMocked<ApplicationService>; let cardServiceMock: DeepMocked<CardService>; - let decisionServiceMock: DeepMocked<ApplicationDecisionV1Service>; + let decisionServiceMock: DeepMocked<ApplicationDecisionV2Service>; let mockModification; let mockModificationCreateDto; @@ -90,7 +90,7 @@ describe('ApplicationModificationService', () => { useValue: cardServiceMock, }, { - provide: ApplicationDecisionV1Service, + provide: ApplicationDecisionV2Service, useValue: decisionServiceMock, }, { diff --git a/services/apps/alcs/src/alcs/application-decision/application-modification/application-modification.service.ts b/services/apps/alcs/src/alcs/application-decision/application-modification/application-modification.service.ts index 505901956e..3715a053fa 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-modification/application-modification.service.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-modification/application-modification.service.ts @@ -16,7 +16,7 @@ import { Board } from '../../board/board.entity'; import { CARD_TYPE } from '../../card/card-type/card-type.entity'; import { Card } from '../../card/card.entity'; import { CardService } from '../../card/card.service'; -import { ApplicationDecisionV1Service } from '../application-decision-v1/application-decision/application-decision-v1.service'; +import { ApplicationDecisionV2Service } from '../application-decision-v2/application-decision/application-decision-v2.service'; import { ApplicationModificationCreateDto, ApplicationModificationDto, @@ -31,7 +31,7 @@ export class ApplicationModificationService { private modificationRepository: Repository<ApplicationModification>, @InjectMapper() private mapper: Mapper, private applicationService: ApplicationService, - private applicationDecisionV1Service: ApplicationDecisionV1Service, + private applicationDecisionV2Service: ApplicationDecisionV2Service, private cardService: CardService, ) {} @@ -131,7 +131,7 @@ export class ApplicationModificationService { ); modification.application = await this.getOrCreateApplication(createDto); modification.modifiesDecisions = - await this.applicationDecisionV1Service.getMany( + await this.applicationDecisionV2Service.getMany( createDto.modifiesDecisionUuids, ); @@ -185,7 +185,7 @@ export class ApplicationModificationService { if (updateDto.modifiesDecisionUuids) { modification.modifiesDecisions = - await this.applicationDecisionV1Service.getMany( + await this.applicationDecisionV2Service.getMany( updateDto.modifiesDecisionUuids, ); } diff --git a/services/apps/alcs/src/alcs/application-decision/application-reconsideration/application-reconsideration.dto.ts b/services/apps/alcs/src/alcs/application-decision/application-reconsideration/application-reconsideration.dto.ts index 04b5e2bc64..d537b44d7d 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-reconsideration/application-reconsideration.dto.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-reconsideration/application-reconsideration.dto.ts @@ -13,8 +13,8 @@ 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 { ApplicationTypeDto } from '../../code/application-code/application-type/application-type.dto'; -import { ApplicationDecisionMeetingDto } from '../application-decision-v1/application-decision-meeting/application-decision-meeting.dto'; -import { ApplicationDecisionDto } from '../application-decision-v1/application-decision/application-decision.dto'; +import { DecisionMeetingDto } from '../../meetings/decision-meeting.dto'; +import { ApplicationDecisionDto } from '../application-decision-v2/application-decision/application-decision.dto'; export class ReconsiderationTypeDto extends BaseCodeDto {} @@ -127,7 +127,7 @@ export class ApplicationForReconsiderationDto { source: string; region: ApplicationRegionDto; localGovernment: string; - decisionMeetings: ApplicationDecisionMeetingDto[]; + decisionMeetings: DecisionMeetingDto[]; } export class ApplicationReconsiderationWithoutApplicationDto { diff --git a/services/apps/alcs/src/alcs/application-decision/application-reconsideration/application-reconsideration.service.spec.ts b/services/apps/alcs/src/alcs/application-decision/application-reconsideration/application-reconsideration.service.spec.ts index 1826ab37ff..1b0d19806a 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-reconsideration/application-reconsideration.service.spec.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-reconsideration/application-reconsideration.service.spec.ts @@ -16,7 +16,7 @@ import { Board } from '../../board/board.entity'; import { Card } from '../../card/card.entity'; import { CardService } from '../../card/card.service'; import { CodeService } from '../../code/code.service'; -import { ApplicationDecisionV1Service } from '../application-decision-v1/application-decision/application-decision-v1.service'; +import { ApplicationDecisionV2Service } from '../application-decision-v2/application-decision/application-decision-v2.service'; import { ApplicationDecision } from '../application-decision.entity'; import { ApplicationReconsiderationCreateDto, @@ -37,7 +37,7 @@ describe('ReconsiderationService', () => { let codeServiceMock: DeepMocked<CodeService>; let applicationServiceMock: DeepMocked<ApplicationService>; let cardServiceMock: DeepMocked<CardService>; - let decisionServiceMock: DeepMocked<ApplicationDecisionV1Service>; + let decisionServiceMock: DeepMocked<ApplicationDecisionV2Service>; let mockReconsideration; let mockReconsiderationCreateDto; @@ -110,7 +110,7 @@ describe('ReconsiderationService', () => { useValue: cardServiceMock, }, { - provide: ApplicationDecisionV1Service, + provide: ApplicationDecisionV2Service, useValue: decisionServiceMock, }, { diff --git a/services/apps/alcs/src/alcs/application-decision/application-reconsideration/application-reconsideration.service.ts b/services/apps/alcs/src/alcs/application-decision/application-reconsideration/application-reconsideration.service.ts index f48fd5c7d8..94e0d50025 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-reconsideration/application-reconsideration.service.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-reconsideration/application-reconsideration.service.ts @@ -15,7 +15,7 @@ import { ApplicationService } from '../../application/application.service'; import { Board } from '../../board/board.entity'; import { CARD_TYPE } from '../../card/card-type/card-type.entity'; import { CardService } from '../../card/card.service'; -import { ApplicationDecisionV1Service } from '../application-decision-v1/application-decision/application-decision-v1.service'; +import { ApplicationDecisionV2Service } from '../application-decision-v2/application-decision/application-decision-v2.service'; import { ApplicationReconsiderationCreateDto, ApplicationReconsiderationDto, @@ -40,7 +40,7 @@ export class ApplicationReconsiderationService { private reconsiderationTypeRepository: Repository<ApplicationReconsiderationType>, private applicationService: ApplicationService, private cardService: CardService, - private applicationDecisionService: ApplicationDecisionV1Service, + private applicationDecisionService: ApplicationDecisionV2Service, ) {} private DEFAULT_CARD_RELATIONS = { diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.entity.ts b/services/apps/alcs/src/alcs/application/application-decision-meeting/application-decision-meeting.entity.ts similarity index 78% rename from services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.entity.ts rename to services/apps/alcs/src/alcs/application/application-decision-meeting/application-decision-meeting.entity.ts index 0544fe63e9..5c00e9bdb4 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.entity.ts +++ b/services/apps/alcs/src/alcs/application/application-decision-meeting/application-decision-meeting.entity.ts @@ -1,7 +1,7 @@ import { AutoMap } from 'automapper-classes'; import { Column, Entity, Index, ManyToOne } from 'typeorm'; -import { Base } from '../../../../common/entities/base.entity'; -import { Application } from '../../../application/application.entity'; +import { Base } from '../../../common/entities/base.entity'; +import { Application } from '../application.entity'; @Entity() export class ApplicationDecisionMeeting extends Base { diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.service.spec.ts b/services/apps/alcs/src/alcs/application/application-decision-meeting/application-decision-meeting.service.spec.ts similarity index 92% rename from services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.service.spec.ts rename to services/apps/alcs/src/alcs/application/application-decision-meeting/application-decision-meeting.service.spec.ts index f272c8e8ae..30cb06f555 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.service.spec.ts +++ b/services/apps/alcs/src/alcs/application/application-decision-meeting/application-decision-meeting.service.spec.ts @@ -6,11 +6,11 @@ import { Repository } from 'typeorm'; import { initApplicationDecisionMeetingMock, initApplicationMockEntity, -} from '../../../../../test/mocks/mockEntities'; -import { ApplicationSubmissionStatusService } from '../../../application/application-submission-status/application-submission-status.service'; -import { SUBMISSION_STATUS } from '../../../application/application-submission-status/submission-status.dto'; -import { ApplicationSubmissionToSubmissionStatus } from '../../../application/application-submission-status/submission-status.entity'; -import { ApplicationService } from '../../../application/application.service'; +} from '../../../../test/mocks/mockEntities'; +import { ApplicationSubmissionStatusService } from '../application-submission-status/application-submission-status.service'; +import { SUBMISSION_STATUS } from '../application-submission-status/submission-status.dto'; +import { ApplicationSubmissionToSubmissionStatus } from '../application-submission-status/submission-status.entity'; +import { ApplicationService } from '../application.service'; import { ApplicationDecisionMeeting } from './application-decision-meeting.entity'; import { ApplicationDecisionMeetingService } from './application-decision-meeting.service'; diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.service.ts b/services/apps/alcs/src/alcs/application/application-decision-meeting/application-decision-meeting.service.ts similarity index 90% rename from services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.service.ts rename to services/apps/alcs/src/alcs/application/application-decision-meeting/application-decision-meeting.service.ts index 59906bd274..59d660199a 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.service.ts +++ b/services/apps/alcs/src/alcs/application/application-decision-meeting/application-decision-meeting.service.ts @@ -2,10 +2,10 @@ import { ServiceNotFoundException } from '@app/common/exceptions/base.exception' import { Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; -import { ApplicationSubmissionStatusService } from '../../../application/application-submission-status/application-submission-status.service'; -import { SUBMISSION_STATUS } from '../../../application/application-submission-status/submission-status.dto'; -import { ApplicationService } from '../../../application/application.service'; -import { CARD_STATUS } from '../../../card/card-status/card-status.entity'; +import { ApplicationSubmissionStatusService } from '../application-submission-status/application-submission-status.service'; +import { SUBMISSION_STATUS } from '../application-submission-status/submission-status.dto'; +import { ApplicationService } from '../application.service'; +import { CARD_STATUS } from '../../card/card-status/card-status.entity'; import { ApplicationDecisionMeeting } from './application-decision-meeting.entity'; @Injectable() @@ -61,9 +61,8 @@ export class ApplicationDecisionMeetingService { decisionMeeting, ); - const meeting = await this.appDecisionMeetingRepository.save( - updatedMeeting, - ); + const meeting = + await this.appDecisionMeetingRepository.save(updatedMeeting); await this.updateSubmissionStatus(meeting); diff --git a/services/apps/alcs/src/alcs/application/application-timeline/application-timeline.service.spec.ts b/services/apps/alcs/src/alcs/application/application-timeline/application-timeline.service.spec.ts index 0a457ef5cf..936f329c1e 100644 --- a/services/apps/alcs/src/alcs/application/application-timeline/application-timeline.service.spec.ts +++ b/services/apps/alcs/src/alcs/application/application-timeline/application-timeline.service.spec.ts @@ -2,8 +2,8 @@ import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; -import { ApplicationDecisionMeeting } from '../../application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.entity'; -import { ApplicationDecisionMeetingService } from '../../application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.service'; +import { ApplicationDecisionMeeting } from '../application-decision-meeting/application-decision-meeting.entity'; +import { ApplicationDecisionMeetingService } from '../application-decision-meeting/application-decision-meeting.service'; import { ApplicationDecision } from '../../application-decision/application-decision.entity'; import { ApplicationModificationOutcomeType } from '../../application-decision/application-modification/application-modification-outcome-type/application-modification-outcome-type.entity'; import { ApplicationModification } from '../../application-decision/application-modification/application-modification.entity'; diff --git a/services/apps/alcs/src/alcs/application/application-timeline/application-timeline.service.ts b/services/apps/alcs/src/alcs/application/application-timeline/application-timeline.service.ts index 4fee3385f1..3d4ac19448 100644 --- a/services/apps/alcs/src/alcs/application/application-timeline/application-timeline.service.ts +++ b/services/apps/alcs/src/alcs/application/application-timeline/application-timeline.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; -import { ApplicationDecisionMeetingService } from '../../application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.service'; +import { ApplicationDecisionMeetingService } from '../application-decision-meeting/application-decision-meeting.service'; import { ApplicationDecision } from '../../application-decision/application-decision.entity'; import { ApplicationModification } from '../../application-decision/application-modification/application-modification.entity'; import { ApplicationReconsideration } from '../../application-decision/application-reconsideration/application-reconsideration.entity'; diff --git a/services/apps/alcs/src/alcs/application/application.dto.ts b/services/apps/alcs/src/alcs/application/application.dto.ts index 15480d869d..28b5828857 100644 --- a/services/apps/alcs/src/alcs/application/application.dto.ts +++ b/services/apps/alcs/src/alcs/application/application.dto.ts @@ -10,7 +10,7 @@ import { } from 'class-validator'; import { ApplicationOwnerDto } from '../../portal/application-submission/application-owner/application-owner.dto'; import { ApplicationSubmissionDetailedDto } from '../../portal/application-submission/application-submission.dto'; -import { ApplicationDecisionMeetingDto } from '../application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.dto'; +import { DecisionMeetingDto } from '../meetings/decision-meeting.dto'; import { CardDto } from '../card/card.dto'; import { ApplicationRegionDto } from '../code/application-code/application-region/application-region.dto'; import { ApplicationTypeDto } from '../code/application-code/application-type/application-type.dto'; @@ -222,8 +222,8 @@ export class ApplicationDto { @AutoMap(() => LocalGovernmentDto) localGovernment: LocalGovernmentDto; - @AutoMap(() => ApplicationDecisionMeetingDto) - decisionMeetings: ApplicationDecisionMeetingDto[]; + @AutoMap(() => DecisionMeetingDto) + decisionMeetings: DecisionMeetingDto[]; @AutoMap() @Type(() => CardDto) diff --git a/services/apps/alcs/src/alcs/application/application.entity.ts b/services/apps/alcs/src/alcs/application/application.entity.ts index 90773d2f4a..80ff2d87ea 100644 --- a/services/apps/alcs/src/alcs/application/application.entity.ts +++ b/services/apps/alcs/src/alcs/application/application.entity.ts @@ -14,7 +14,7 @@ import { Base } from '../../common/entities/base.entity'; import { FILE_NUMBER_SEQUENCE } from '../../file-number/file-number.constants'; import { ApplicationSubmissionReview } from '../../portal/application-submission-review/application-submission-review.entity'; import { ColumnNumericTransformer } from '../../utils/column-numeric-transform'; -import { ApplicationDecisionMeeting } from '../application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.entity'; +import { ApplicationDecisionMeeting } from './application-decision-meeting/application-decision-meeting.entity'; import { ApplicationReconsideration } from '../application-decision/application-reconsideration/application-reconsideration.entity'; import { Card } from '../card/card.entity'; import { ApplicationRegion } from '../code/application-code/application-region/application-region.entity'; diff --git a/services/apps/alcs/src/alcs/application/application.module.ts b/services/apps/alcs/src/alcs/application/application.module.ts index 4498e03076..4b86c19889 100644 --- a/services/apps/alcs/src/alcs/application/application.module.ts +++ b/services/apps/alcs/src/alcs/application/application.module.ts @@ -1,6 +1,8 @@ import { forwardRef, Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { CovenantTransferee } from '../../portal/application-submission/covenant-transferee/covenant-transferee.entity'; +import { ApplicationDecisionMeeting } from './application-decision-meeting/application-decision-meeting.entity'; +import { ApplicationDecisionMeetingService } from './application-decision-meeting/application-decision-meeting.service'; import { ApplicationSubmissionStatusModule } from './application-submission-status/application-submission-status.module'; import { ApplicationSubmissionStatusType } from './application-submission-status/submission-status-type.entity'; import { ApplicationSubmissionToSubmissionStatus } from './application-submission-status/submission-status.entity'; @@ -50,6 +52,7 @@ import { ApplicationService } from './application.service'; ApplicationType, ApplicationPaused, ApplicationMeeting, + ApplicationDecisionMeeting, ApplicationDocument, DocumentCode, ApplicationParcel, @@ -83,6 +86,7 @@ import { ApplicationService } from './application.service'; LocalGovernmentService, ApplicationSubmissionService, ApplicationSubmissionReviewService, + ApplicationDecisionMeetingService, ApplicationSubmissionProfile, ], controllers: [ @@ -103,6 +107,7 @@ import { ApplicationService } from './application.service'; ApplicationPausedService, LocalGovernmentService, ApplicationDocumentService, + ApplicationDecisionMeetingService, ], }) export class ApplicationModule {} diff --git a/services/apps/alcs/src/alcs/commissioner/commissioner.controller.spec.ts b/services/apps/alcs/src/alcs/commissioner/commissioner.controller.spec.ts index aedd5145b2..1846d68b7a 100644 --- a/services/apps/alcs/src/alcs/commissioner/commissioner.controller.spec.ts +++ b/services/apps/alcs/src/alcs/commissioner/commissioner.controller.spec.ts @@ -12,6 +12,8 @@ import { ApplicationService } from '../application/application.service'; import { CommissionerProfile } from '../../common/automapper/commissioner.automapper.profile'; import { ApplicationModificationService } from '../application-decision/application-modification/application-modification.service'; import { ApplicationReconsiderationService } from '../application-decision/application-reconsideration/application-reconsideration.service'; +import { PlanningReview } from '../planning-review/planning-review.entity'; +import { PlanningReviewService } from '../planning-review/planning-review.service'; import { CommissionerController } from './commissioner.controller'; describe('CommissionerController', () => { @@ -20,6 +22,7 @@ describe('CommissionerController', () => { let mockReconsiderationService: DeepMocked<ApplicationReconsiderationService>; let mockModificationService: DeepMocked<ApplicationModificationService>; let mockTrackingService: DeepMocked<TrackingService>; + let mockPlanningReviewService: DeepMocked<PlanningReview>; let mockRequest; const fileNumber = 'fake-file'; @@ -29,6 +32,7 @@ describe('CommissionerController', () => { mockReconsiderationService = createMock(); mockModificationService = createMock(); mockTrackingService = createMock(); + mockPlanningReviewService = createMock(); const module: TestingModule = await Test.createTestingModule({ imports: [ @@ -54,6 +58,10 @@ describe('CommissionerController', () => { provide: TrackingService, useValue: mockTrackingService, }, + { + provide: PlanningReviewService, + useValue: mockPlanningReviewService, + }, { provide: ClsService, useValue: {}, diff --git a/services/apps/alcs/src/alcs/commissioner/commissioner.controller.ts b/services/apps/alcs/src/alcs/commissioner/commissioner.controller.ts index a77785180d..0cbb0338af 100644 --- a/services/apps/alcs/src/alcs/commissioner/commissioner.controller.ts +++ b/services/apps/alcs/src/alcs/commissioner/commissioner.controller.ts @@ -11,7 +11,12 @@ import { RolesGuard } from '../../common/authorization/roles-guard.service'; import { UserRoles } from '../../common/authorization/roles.decorator'; import { ApplicationModificationService } from '../application-decision/application-modification/application-modification.service'; import { ApplicationReconsiderationService } from '../application-decision/application-reconsideration/application-reconsideration.service'; -import { CommissionerApplicationDto } from './commissioner.dto'; +import { PlanningReviewDto } from '../planning-review/planning-review.dto'; +import { PlanningReviewService } from '../planning-review/planning-review.service'; +import { + CommissionerApplicationDto, + CommissionerPlanningReviewDto, +} from './commissioner.dto'; @Controller('commissioner') @ApiOAuth2(config.get<string[]>('KEYCLOAK.SCOPES')) @@ -19,6 +24,7 @@ import { CommissionerApplicationDto } from './commissioner.dto'; export class CommissionerController { constructor( private applicationService: ApplicationService, + private planningReviewService: PlanningReviewService, private modificationService: ApplicationModificationService, private reconsiderationService: ApplicationReconsiderationService, private trackingService: TrackingService, @@ -58,4 +64,24 @@ export class CommissionerController { hasRecons: recons.length > 0, }; } + + @Get('/planning-review/:fileNumber') + @UserRoles(AUTH_ROLE.COMMISSIONER) + async getPlanningReview( + @Param('fileNumber') fileNumber, + @Req() req, + ): Promise<CommissionerPlanningReviewDto> { + const application = + await this.planningReviewService.getByFileNumber(fileNumber); + const firstMap = await this.planningReviewService.mapToDtos([application]); + const finalMap = await this.mapper.mapArrayAsync( + firstMap, + PlanningReviewDto, + CommissionerPlanningReviewDto, + ); + + const mappedRecords = finalMap[0]; + await this.trackingService.trackView(req.user.entity, fileNumber); + return mappedRecords; + } } diff --git a/services/apps/alcs/src/alcs/commissioner/commissioner.dto.ts b/services/apps/alcs/src/alcs/commissioner/commissioner.dto.ts index bc900846b4..d2be50f242 100644 --- a/services/apps/alcs/src/alcs/commissioner/commissioner.dto.ts +++ b/services/apps/alcs/src/alcs/commissioner/commissioner.dto.ts @@ -1,7 +1,9 @@ import { AutoMap } from 'automapper-classes'; +import { ApplicationReconsiderationDto } from '../application-decision/application-reconsideration/application-reconsideration.dto'; import { LocalGovernmentDto } from '../local-government/local-government.dto'; import { ApplicationRegionDto } from '../code/application-code/application-region/application-region.dto'; import { ApplicationTypeDto } from '../code/application-code/application-type/application-type.dto'; +import { PlanningReviewTypeDto } from '../planning-review/planning-review.dto'; export class CommissionerApplicationDto { @AutoMap() @@ -34,3 +36,26 @@ export class CommissionerApplicationDto { hasRecons: boolean; hasModifications: boolean; } + +export class CommissionerPlanningReviewDto { + @AutoMap() + fileNumber: string; + + @AutoMap() + documentName: string; + + @AutoMap() + open: boolean; + + @AutoMap(() => PlanningReviewTypeDto) + type: PlanningReviewTypeDto; + + @AutoMap(() => ApplicationRegionDto) + region: ApplicationRegionDto; + + @AutoMap(() => LocalGovernmentDto) + localGovernment: LocalGovernmentDto; + + @AutoMap() + legacyId?: string; +} diff --git a/services/apps/alcs/src/alcs/commissioner/commissioner.module.ts b/services/apps/alcs/src/alcs/commissioner/commissioner.module.ts index f364bb8b6e..f9aec79611 100644 --- a/services/apps/alcs/src/alcs/commissioner/commissioner.module.ts +++ b/services/apps/alcs/src/alcs/commissioner/commissioner.module.ts @@ -1,11 +1,12 @@ import { Module } from '@nestjs/common'; import { CommissionerProfile } from '../../common/automapper/commissioner.automapper.profile'; -import { ApplicationModule } from '../application/application.module'; import { ApplicationDecisionModule } from '../application-decision/application-decision.module'; +import { ApplicationModule } from '../application/application.module'; +import { PlanningReviewModule } from '../planning-review/planning-review.module'; import { CommissionerController } from './commissioner.controller'; @Module({ - imports: [ApplicationModule, ApplicationDecisionModule], + imports: [ApplicationModule, ApplicationDecisionModule, PlanningReviewModule], providers: [CommissionerProfile], controllers: [CommissionerController], }) diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.controller.spec.ts b/services/apps/alcs/src/alcs/meetings/decision-meeting.controller.spec.ts similarity index 72% rename from services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.controller.spec.ts rename to services/apps/alcs/src/alcs/meetings/decision-meeting.controller.spec.ts index 07737eba43..6eb1ecce2a 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.controller.spec.ts +++ b/services/apps/alcs/src/alcs/meetings/decision-meeting.controller.spec.ts @@ -1,33 +1,38 @@ -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 { initApplicationDecisionMeetingMock, initApplicationMockEntity, initApplicationReconsiderationMockEntity, -} from '../../../../../test/mocks/mockEntities'; -import { mockKeyCloakProviders } from '../../../../../test/mocks/mockTypes'; -import { ApplicationDecisionProfile } from '../../../../common/automapper/application-decision-v1.automapper.profile'; -import { UserProfile } from '../../../../common/automapper/user.automapper.profile'; -import { EmailService } from '../../../../providers/email/email.service'; -import { ApplicationService } from '../../../application/application.service'; -import { Board } from '../../../board/board.entity'; -import { ApplicationReconsiderationService } from '../../application-reconsideration/application-reconsideration.service'; -import { ApplicationDecisionMeetingController } from './application-decision-meeting.controller'; +} from '../../../test/mocks/mockEntities'; +import { mockKeyCloakProviders } from '../../../test/mocks/mockTypes'; +import { ApplicationDecisionProfile } from '../../common/automapper/application-decision-v2.automapper.profile'; +import { ApplicationProfile } from '../../common/automapper/application.automapper.profile'; +import { UserProfile } from '../../common/automapper/user.automapper.profile'; +import { EmailService } from '../../providers/email/email.service'; +import { ApplicationReconsiderationService } from '../application-decision/application-reconsideration/application-reconsideration.service'; +import { ApplicationDecisionMeetingService } from '../application/application-decision-meeting/application-decision-meeting.service'; +import { ApplicationService } from '../application/application.service'; +import { Board } from '../board/board.entity'; +import { PlanningReferralService } from '../planning-review/planning-referral/planning-referral.service'; +import { PlanningReviewMeetingService } from '../planning-review/planning-review-meeting/planning-review-meeting.service'; +import { DecisionMeetingController } from './decision-meeting.controller'; import { - ApplicationDecisionMeetingDto, CreateApplicationDecisionMeetingDto, -} from './application-decision-meeting.dto'; -import { ApplicationDecisionMeetingService } from './application-decision-meeting.service'; + DecisionMeetingDto, +} from './decision-meeting.dto'; -describe('ApplicationDecisionMeetingController', () => { - let controller: ApplicationDecisionMeetingController; +describe('DecisionMeetingController', () => { + let controller: DecisionMeetingController; let mockMeetingService: DeepMocked<ApplicationDecisionMeetingService>; let mockApplicationService: DeepMocked<ApplicationService>; let mockReconsiderationService: DeepMocked<ApplicationReconsiderationService>; let mockEmailService: DeepMocked<EmailService>; + let mockPlanningReferralService: DeepMocked<PlanningReferralService>; + let mockPlanningReviewMeetingService: DeepMocked<PlanningReviewMeetingService>; let mockApplication; let mockMeeting; @@ -35,7 +40,9 @@ describe('ApplicationDecisionMeetingController', () => { mockMeetingService = createMock(); mockApplicationService = createMock(); mockReconsiderationService = createMock(); - mockEmailService = createMock<EmailService>(); + mockPlanningReferralService = createMock(); + mockEmailService = createMock(); + mockPlanningReviewMeetingService = createMock(); const module: TestingModule = await Test.createTestingModule({ imports: [ @@ -43,9 +50,9 @@ describe('ApplicationDecisionMeetingController', () => { strategyInitializer: classes(), }), ], - controllers: [ApplicationDecisionMeetingController], + controllers: [DecisionMeetingController], providers: [ - ApplicationDecisionProfile, + ApplicationProfile, UserProfile, { provide: ApplicationDecisionMeetingService, @@ -63,6 +70,14 @@ describe('ApplicationDecisionMeetingController', () => { provide: EmailService, useValue: mockEmailService, }, + { + provide: PlanningReferralService, + useValue: mockPlanningReferralService, + }, + { + provide: PlanningReviewMeetingService, + useValue: mockPlanningReviewMeetingService, + }, { provide: ClsService, useValue: {}, @@ -71,8 +86,8 @@ describe('ApplicationDecisionMeetingController', () => { ], }).compile(); - controller = module.get<ApplicationDecisionMeetingController>( - ApplicationDecisionMeetingController, + controller = module.get<DecisionMeetingController>( + DecisionMeetingController, ); mockApplication = initApplicationMockEntity(); @@ -82,6 +97,8 @@ describe('ApplicationDecisionMeetingController', () => { mockMeetingService.get.mockResolvedValue(mockMeeting); mockMeetingService.getUpcomingReconsiderationMeetings.mockResolvedValue([]); mockMeetingService.getUpcomingApplicationMeetings.mockResolvedValue([]); + mockPlanningReviewMeetingService.getUpcomingMeetings.mockResolvedValue([]); + mockPlanningReferralService.getManyByPlanningReview.mockResolvedValue([]); }); it('should be defined', () => { @@ -133,7 +150,7 @@ describe('ApplicationDecisionMeetingController', () => { const meetingToUpdate = { uuid: mockMeeting.uuid, date: new Date(2022, 2, 2, 2, 2, 2, 2).valueOf(), - } as ApplicationDecisionMeetingDto; + } as DecisionMeetingDto; await controller.update(meetingToUpdate); diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.controller.ts b/services/apps/alcs/src/alcs/meetings/decision-meeting.controller.ts similarity index 57% rename from services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.controller.ts rename to services/apps/alcs/src/alcs/meetings/decision-meeting.controller.ts index 715bef31d4..e88280287f 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.controller.ts +++ b/services/apps/alcs/src/alcs/meetings/decision-meeting.controller.ts @@ -1,5 +1,3 @@ -import { Mapper } from 'automapper-core'; -import { InjectMapper } from 'automapper-nestjs'; import { Body, Controller, @@ -11,36 +9,40 @@ import { 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 { Any } from 'typeorm'; -import { ANY_AUTH_ROLE } from '../../../../common/authorization/roles'; -import { RolesGuard } from '../../../../common/authorization/roles-guard.service'; -import { UserRoles } from '../../../../common/authorization/roles.decorator'; -import { UserDto } from '../../../../user/user.dto'; -import { User } from '../../../../user/user.entity'; -import { formatIncomingDate } from '../../../../utils/incoming-date.formatter'; -import { ApplicationService } from '../../../application/application.service'; -import { ApplicationReconsiderationService } from '../../application-reconsideration/application-reconsideration.service'; +import { ANY_AUTH_ROLE } from '../../common/authorization/roles'; +import { RolesGuard } from '../../common/authorization/roles-guard.service'; +import { UserRoles } from '../../common/authorization/roles.decorator'; +import { UserDto } from '../../user/user.dto'; +import { User } from '../../user/user.entity'; +import { formatIncomingDate } from '../../utils/incoming-date.formatter'; +import { ApplicationReconsiderationService } from '../application-decision/application-reconsideration/application-reconsideration.service'; +import { ApplicationDecisionMeeting } from '../application/application-decision-meeting/application-decision-meeting.entity'; +import { ApplicationDecisionMeetingService } from '../application/application-decision-meeting/application-decision-meeting.service'; +import { ApplicationService } from '../application/application.service'; +import { CARD_TYPE } from '../card/card-type/card-type.entity'; +import { PlanningReferralService } from '../planning-review/planning-referral/planning-referral.service'; +import { PlanningReviewMeetingService } from '../planning-review/planning-review-meeting/planning-review-meeting.service'; import { - ApplicationDecisionMeetingDto, CreateApplicationDecisionMeetingDto, + DecisionMeetingDto, UpcomingMeetingBoardMapDto, UpcomingMeetingDto, -} from './application-decision-meeting.dto'; -import { ApplicationDecisionMeeting } from './application-decision-meeting.entity'; -import { ApplicationDecisionMeetingService } from './application-decision-meeting.service'; - -import { EmailService } from '../../../../providers/email/email.service'; +} from './decision-meeting.dto'; @ApiOAuth2(config.get<string[]>('KEYCLOAK.SCOPES')) -@Controller('application-decision-meeting') +@Controller('decision-meeting') @UseGuards(RolesGuard) -export class ApplicationDecisionMeetingController { +export class DecisionMeetingController { constructor( private appDecisionMeetingService: ApplicationDecisionMeetingService, private applicationService: ApplicationService, - private emailService: EmailService, private reconsiderationService: ApplicationReconsiderationService, + private planningReferralService: PlanningReferralService, + private planningReviewMeetingService: PlanningReviewMeetingService, @InjectMapper() private mapper: Mapper, ) {} @@ -49,9 +51,10 @@ export class ApplicationDecisionMeetingController { async getMeetings(): Promise<UpcomingMeetingBoardMapDto> { const mappedApps = await this.getMappedApplicationMeetings(); const mappedRecons = await this.getMappedReconsiderationMeetings(); + const mappedReviews = await this.getMappedPlanningReviewMeetings(); const boardCodeToApps: UpcomingMeetingBoardMapDto = {}; - [...mappedApps, ...mappedRecons].forEach((mappedApp) => { + [...mappedApps, ...mappedRecons, ...mappedReviews].forEach((mappedApp) => { const boardMeetings = boardCodeToApps[mappedApp.boardCode] || []; boardMeetings.push(mappedApp); boardCodeToApps[mappedApp.boardCode] = boardMeetings; @@ -64,26 +67,24 @@ export class ApplicationDecisionMeetingController { @UserRoles(...ANY_AUTH_ROLE) async getAllForApplication( @Param('fileNumber') fileNumber, - ): Promise<ApplicationDecisionMeetingDto[]> { + ): Promise<DecisionMeetingDto[]> { const meetings = await this.appDecisionMeetingService.getByAppFileNumber(fileNumber); return this.mapper.mapArrayAsync( meetings, ApplicationDecisionMeeting, - ApplicationDecisionMeetingDto, + DecisionMeetingDto, ); } @Get('/meeting/:uuid') @UserRoles(...ANY_AUTH_ROLE) - async get( - @Param('uuid') uuid: string, - ): Promise<ApplicationDecisionMeetingDto> { + async get(@Param('uuid') uuid: string): Promise<DecisionMeetingDto> { const meeting = await this.appDecisionMeetingService.get(uuid); return this.mapper.mapAsync( meeting, ApplicationDecisionMeeting, - ApplicationDecisionMeetingDto, + DecisionMeetingDto, ); } @@ -97,7 +98,7 @@ export class ApplicationDecisionMeetingController { @UserRoles(...ANY_AUTH_ROLE) async create( @Body() meeting: CreateApplicationDecisionMeetingDto, - ): Promise<ApplicationDecisionMeetingDto> { + ): Promise<DecisionMeetingDto> { const application = await this.applicationService.getOrFail( meeting.applicationFileNumber, ); @@ -110,26 +111,25 @@ export class ApplicationDecisionMeetingController { return this.mapper.map( newMeeting, ApplicationDecisionMeeting, - ApplicationDecisionMeetingDto, + DecisionMeetingDto, ); } @Patch() @UserRoles(...ANY_AUTH_ROLE) async update( - @Body() appDecMeeting: ApplicationDecisionMeetingDto, - ): Promise<ApplicationDecisionMeetingDto> { - const appDecEntity = this.mapper.map( - appDecMeeting, - ApplicationDecisionMeetingDto, - ApplicationDecisionMeeting, - ); + @Body() updateDto: DecisionMeetingDto, + ): Promise<DecisionMeetingDto> { + const appDecEntity = new ApplicationDecisionMeeting({ + uuid: updateDto.uuid, + date: formatIncomingDate(updateDto.date)!, + }); const updatedMeeting = await this.appDecisionMeetingService.createOrUpdate(appDecEntity); return this.mapper.map( updatedMeeting, ApplicationDecisionMeeting, - ApplicationDecisionMeetingDto, + DecisionMeetingDto, ); } @@ -149,6 +149,7 @@ export class ApplicationDecisionMeetingController { fileNumber: app.fileNumber, applicant: app.applicant, boardCode: app.card!.board.code, + type: CARD_TYPE.APP, assignee: this.mapper.map(app.card!.assignee, User, UserDto), }; }); @@ -170,8 +171,46 @@ export class ApplicationDecisionMeetingController { fileNumber: recon.application.fileNumber, applicant: recon.application.applicant, boardCode: recon.card!.board.code, + type: CARD_TYPE.APP, assignee: this.mapper.map(recon.card!.assignee, User, UserDto), }; }); } + + private async getMappedPlanningReviewMeetings() { + const upcomingMeetings = + await this.planningReviewMeetingService.getUpcomingMeetings(); + + const planningReviewIds = upcomingMeetings.map((a) => a.uuid); + const planningReferrals = + await this.planningReferralService.getManyByPlanningReview( + planningReviewIds, + ); + return planningReferrals.flatMap( + (planningReferral): UpcomingMeetingDto[] => { + const meetingDate = upcomingMeetings.find( + (meeting) => meeting.uuid === planningReferral.planningReview.uuid, + ); + + if (!meetingDate) { + return []; + } + + return [ + { + meetingDate: new Date(meetingDate.next_meeting).getTime(), + fileNumber: planningReferral.planningReview.fileNumber, + applicant: planningReferral.planningReview.documentName, + boardCode: planningReferral.card!.board.code, + type: CARD_TYPE.PLAN, + assignee: this.mapper.map( + planningReferral.card!.assignee, + User, + UserDto, + ), + }, + ]; + }, + ); + } } diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.dto.ts b/services/apps/alcs/src/alcs/meetings/decision-meeting.dto.ts similarity index 78% rename from services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.dto.ts rename to services/apps/alcs/src/alcs/meetings/decision-meeting.dto.ts index c28eeee381..a38e7b89aa 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.dto.ts +++ b/services/apps/alcs/src/alcs/meetings/decision-meeting.dto.ts @@ -1,6 +1,6 @@ import { AutoMap } from 'automapper-classes'; import { IsNumber, IsString } from 'class-validator'; -import { UserDto } from '../../../../user/user.dto'; +import { UserDto } from '../../user/user.dto'; export class CreateApplicationDecisionMeetingDto { @AutoMap() @@ -12,7 +12,7 @@ export class CreateApplicationDecisionMeetingDto { applicationFileNumber; } -export class ApplicationDecisionMeetingDto extends CreateApplicationDecisionMeetingDto { +export class DecisionMeetingDto extends CreateApplicationDecisionMeetingDto { @AutoMap() @IsString() uuid: string; @@ -23,6 +23,7 @@ export type UpcomingMeetingDto = { fileNumber: string; applicant: string; boardCode: string; + type: string; assignee: UserDto; }; diff --git a/services/apps/alcs/src/alcs/meetings/meeting.module.ts b/services/apps/alcs/src/alcs/meetings/meeting.module.ts new file mode 100644 index 0000000000..727cd7f1f9 --- /dev/null +++ b/services/apps/alcs/src/alcs/meetings/meeting.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { ApplicationDecisionModule } from '../application-decision/application-decision.module'; +import { ApplicationModule } from '../application/application.module'; +import { PlanningReviewModule } from '../planning-review/planning-review.module'; +import { DecisionMeetingController } from './decision-meeting.controller'; + +@Module({ + imports: [ApplicationModule, ApplicationDecisionModule, PlanningReviewModule], + providers: [], + controllers: [DecisionMeetingController], + exports: [], +}) +export class MeetingModule {} 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 index 2740e406f5..4773219e42 100644 --- 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 @@ -3,9 +3,9 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Mapper } from 'automapper-core'; import { InjectMapper } from 'automapper-nestjs'; import { - FindOptions, FindOptionsRelations, FindOptionsWhere, + In, IsNull, Not, Repository, @@ -206,4 +206,15 @@ export class PlanningReferralService { }, }); } + + async getManyByPlanningReview(planningReviewIds: string[]) { + return this.referralRepository.find({ + where: { + planningReview: { + uuid: In(planningReviewIds), + }, + }, + relations: this.DEFAULT_RELATIONS, + }); + } } diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.service.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.service.ts index a38c5a4142..82ebe6821c 100644 --- a/services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.service.ts +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-meeting/planning-review-meeting.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { filterUndefined } from '../../../utils/undefined'; +import { CARD_STATUS } from '../../card/card-status/card-status.entity'; import { PlanningReviewService } from '../planning-review.service'; import { PlanningReviewMeetingType } from './planning-review-meeting-type.entity'; import { @@ -95,4 +96,18 @@ export class PlanningReviewMeetingService { await this.meetingRepository.save(existingMeeting); } + + async getUpcomingMeetings(): Promise< + { uuid: string; next_meeting: string }[] + > { + return await this.meetingRepository + .createQueryBuilder('meeting') + .select('planningReview.uuid, MAX(meeting.date) as next_meeting') + .innerJoin('meeting.planningReview', 'planningReview') + .innerJoin('planningReview.referrals', 'referrals') + .innerJoin('referrals.card', 'card') + .where(`card.status_code != '${CARD_STATUS.DECISION_RELEASED}'`) + .groupBy('planningReview.uuid') + .getRawMany(); + } } 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 6dac6b21ea..73de96b549 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 @@ -52,6 +52,10 @@ import { PlanningReviewService } from './planning-review.service'; PlanningReviewDocumentService, PlanningReviewMeetingService, ], - exports: [PlanningReviewService, PlanningReferralService], + exports: [ + PlanningReviewService, + PlanningReferralService, + PlanningReviewMeetingService, + ], }) export class PlanningReviewModule {} 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 912e6527c7..f653bb14c0 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 @@ -3,7 +3,12 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Mapper } from 'automapper-core'; import { InjectMapper } from 'automapper-nestjs'; -import { FindOptionsRelations, FindOptionsWhere, Repository } from 'typeorm'; +import { + FindOptionsRelations, + FindOptionsWhere, + In, + Repository, +} from 'typeorm'; import { FileNumberService } from '../../file-number/file-number.service'; import { formatIncomingDate } from '../../utils/incoming-date.formatter'; import { filterUndefined } from '../../utils/undefined'; diff --git a/services/apps/alcs/src/common/automapper/application-decision-v1.automapper.profile.ts b/services/apps/alcs/src/common/automapper/application-decision-v1.automapper.profile.ts deleted file mode 100644 index 6cae24c7cf..0000000000 --- a/services/apps/alcs/src/common/automapper/application-decision-v1.automapper.profile.ts +++ /dev/null @@ -1,205 +0,0 @@ -import { createMap, forMember, mapFrom, Mapper } from 'automapper-core'; -import { AutomapperProfile, InjectMapper } from 'automapper-nestjs'; -import { Injectable } from '@nestjs/common'; -import { ApplicationCeoCriterionCode } from '../../alcs/application-decision/application-ceo-criterion/application-ceo-criterion.entity'; - -import { ApplicationDecisionOutcomeCode } from '../../alcs/application-decision/application-decision-outcome.entity'; -import { ApplicationDecision } from '../../alcs/application-decision/application-decision.entity'; -import { ApplicationDecisionDocument } from '../../alcs/application-decision/application-decision-document/application-decision-document.entity'; -import { ApplicationDecisionMakerCode } from '../../alcs/application-decision/application-decision-maker/application-decision-maker.entity'; -import { ApplicationDecisionChairReviewOutcomeType } from '../../alcs/application-decision/application-decision-outcome-type/application-decision-outcome-type.entity'; -import { ApplicationDecisionMeetingDto } from '../../alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.dto'; -import { ApplicationDecisionMeeting } from '../../alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.entity'; -import { - ApplicationDecisionDto, - ChairReviewOutcomeCodeDto, - DecisionDocumentDto, - DecisionOutcomeCodeDto, -} from '../../alcs/application-decision/application-decision-v1/application-decision/application-decision.dto'; -import { CeoCriterionCodeDto } from '../../alcs/application-decision/application-decision-v1/application-decision/ceo-criterion/ceo-criterion.dto'; -import { ApplicationDecisionMakerCodeDto } from '../../alcs/application-decision/application-decision-maker/decision-maker.dto'; - -@Injectable() -export class ApplicationDecisionProfile extends AutomapperProfile { - constructor(@InjectMapper() mapper: Mapper) { - super(mapper); - } - - override get profile() { - return (mapper) => { - createMap( - mapper, - ApplicationDecision, - ApplicationDecisionDto, - forMember( - (ad) => ad.documents, - mapFrom((a) => - this.mapper.mapArray( - a.documents || [], - ApplicationDecisionDocument, - DecisionDocumentDto, - ), - ), - ), - forMember( - (a) => a.reconsiders, - mapFrom((dec) => - dec.reconsiders - ? { - uuid: dec.reconsiders.uuid, - linkedResolutions: dec.reconsiders.reconsidersDecisions.map( - (decision) => - `#${decision.resolutionNumber}/${decision.resolutionYear}`, - ), - } - : undefined, - ), - ), - forMember( - (a) => a.modifies, - mapFrom((dec) => - dec.modifies - ? { - uuid: dec.modifies.uuid, - linkedResolutions: dec.modifies.modifiesDecisions.map( - (decision) => - `#${decision.resolutionNumber}/${decision.resolutionYear}`, - ), - } - : undefined, - ), - ), - forMember( - (a) => a.reconsideredBy, - mapFrom((dec) => - (dec.reconsideredBy || []) - .filter((reconsideration) => reconsideration.resultingDecision) - .map((reconsideration) => ({ - uuid: reconsideration.uuid, - linkedResolutions: [ - `#${reconsideration.resultingDecision!.resolutionNumber}/${ - reconsideration.resultingDecision!.resolutionYear - }`, - ], - })), - ), - ), - forMember( - (a) => a.modifiedBy, - mapFrom((dec) => - (dec.modifiedBy || []) - .filter((modification) => modification.resultingDecision) - .map((modification) => ({ - uuid: modification.uuid, - linkedResolutions: [ - `#${modification.resultingDecision!.resolutionNumber}/${ - modification.resultingDecision!.resolutionYear - }`, - ], - })), - ), - ), - forMember( - (ad) => ad.decisionMaker, - mapFrom((a) => - this.mapper.map( - a.decisionMaker, - ApplicationDecisionMakerCode, - ApplicationDecisionMakerCodeDto, - ), - ), - ), - forMember( - (ad) => ad.ceoCriterion, - mapFrom((a) => - this.mapper.map( - a.ceoCriterion, - ApplicationCeoCriterionCode, - CeoCriterionCodeDto, - ), - ), - ), - forMember( - (ad) => ad.chairReviewOutcome, - mapFrom((a) => - this.mapper.map( - a.chairReviewOutcome, - ApplicationDecisionChairReviewOutcomeType, - ChairReviewOutcomeCodeDto, - ), - ), - ), - forMember( - (ad) => ad.date, - mapFrom((a) => a.date?.getTime()), - ), - forMember( - (ad) => ad.auditDate, - mapFrom((a) => a.auditDate?.getTime()), - ), - forMember( - (ad) => ad.chairReviewDate, - mapFrom((a) => a.chairReviewDate?.getTime()), - ), - ); - - createMap(mapper, ApplicationDecisionOutcomeCode, DecisionOutcomeCodeDto); - createMap( - mapper, - ApplicationDecisionMakerCode, - ApplicationDecisionMakerCodeDto, - ); - createMap(mapper, ApplicationCeoCriterionCode, CeoCriterionCodeDto); - createMap( - mapper, - ApplicationDecisionChairReviewOutcomeType, - ChairReviewOutcomeCodeDto, - ); - - createMap( - mapper, - ApplicationDecisionMeeting, - ApplicationDecisionMeetingDto, - forMember( - (a) => a.date, - mapFrom((ad) => ad.date.getTime()), - ), - ); - createMap( - mapper, - ApplicationDecisionMeetingDto, - ApplicationDecisionMeeting, - forMember( - (a) => a.date, - mapFrom((ad) => new Date(ad.date)), - ), - ); - - createMap( - mapper, - ApplicationDecisionDocument, - DecisionDocumentDto, - forMember( - (a) => a.mimeType, - mapFrom((ad) => ad.document.mimeType), - ), - forMember( - (a) => a.fileName, - mapFrom((ad) => ad.document.fileName), - ), - forMember( - (a) => a.fileSize, - mapFrom((ad) => ad.document.fileSize), - ), - forMember( - (a) => a.uploadedBy, - mapFrom((ad) => ad.document.uploadedBy?.name), - ), - forMember( - (a) => a.uploadedAt, - mapFrom((ad) => ad.document.uploadedAt.getTime()), - ), - ); - }; - } -} diff --git a/services/apps/alcs/src/common/automapper/application.automapper.profile.ts b/services/apps/alcs/src/common/automapper/application.automapper.profile.ts index 15845d027c..e368e076a8 100644 --- a/services/apps/alcs/src/common/automapper/application.automapper.profile.ts +++ b/services/apps/alcs/src/common/automapper/application.automapper.profile.ts @@ -2,8 +2,8 @@ import { createMap, forMember, mapFrom, Mapper } from 'automapper-core'; import { AutomapperProfile, InjectMapper } from 'automapper-nestjs'; import { Injectable } from '@nestjs/common'; -import { ApplicationDecisionMeetingDto } from '../../alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.dto'; -import { ApplicationDecisionMeeting } from '../../alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.entity'; +import { DecisionMeetingDto } from '../../alcs/meetings/decision-meeting.dto'; +import { ApplicationDecisionMeeting } from '../../alcs/application/application-decision-meeting/application-decision-meeting.entity'; import { LocalGovernmentDto } from '../../alcs/local-government/local-government.dto'; import { LocalGovernment } from '../../alcs/local-government/local-government.entity'; import { ApplicationDocumentDto } from '../../alcs/application/application-document/application-document.dto'; @@ -99,7 +99,7 @@ export class ApplicationProfile extends AutomapperProfile { this.mapper.mapArray( a.decisionMeetings, ApplicationDecisionMeeting, - ApplicationDecisionMeetingDto, + DecisionMeetingDto, ), ), ), @@ -196,6 +196,16 @@ export class ApplicationProfile extends AutomapperProfile { createMap(mapper, ApplicationDto, Card); + createMap( + mapper, + ApplicationDecisionMeeting, + DecisionMeetingDto, + forMember( + (a) => a.date, + mapFrom((ad) => ad.date.getTime()), + ), + ); + createMap( mapper, StaffJournal, diff --git a/services/apps/alcs/src/common/automapper/commissioner.automapper.profile.ts b/services/apps/alcs/src/common/automapper/commissioner.automapper.profile.ts index 9c8a2efe92..46742fed87 100644 --- a/services/apps/alcs/src/common/automapper/commissioner.automapper.profile.ts +++ b/services/apps/alcs/src/common/automapper/commissioner.automapper.profile.ts @@ -1,8 +1,12 @@ +import { Injectable } from '@nestjs/common'; import { createMap, Mapper } from 'automapper-core'; import { AutomapperProfile, InjectMapper } from 'automapper-nestjs'; -import { Injectable } from '@nestjs/common'; import { ApplicationDto } from '../../alcs/application/application.dto'; -import { CommissionerApplicationDto } from '../../alcs/commissioner/commissioner.dto'; +import { + CommissionerApplicationDto, + CommissionerPlanningReviewDto, +} from '../../alcs/commissioner/commissioner.dto'; +import { PlanningReviewDto } from '../../alcs/planning-review/planning-review.dto'; @Injectable() export class CommissionerProfile extends AutomapperProfile { @@ -13,6 +17,7 @@ export class CommissionerProfile extends AutomapperProfile { override get profile() { return (mapper) => { createMap(mapper, ApplicationDto, CommissionerApplicationDto); + createMap(mapper, PlanningReviewDto, CommissionerPlanningReviewDto); }; } } diff --git a/services/apps/alcs/src/common/automapper/modification.automapper.profile.ts b/services/apps/alcs/src/common/automapper/modification.automapper.profile.ts index fcaddaf509..45c6cf2750 100644 --- a/services/apps/alcs/src/common/automapper/modification.automapper.profile.ts +++ b/services/apps/alcs/src/common/automapper/modification.automapper.profile.ts @@ -1,9 +1,8 @@ +import { Injectable } from '@nestjs/common'; import { createMap, forMember, mapFrom, Mapper } from 'automapper-core'; import { AutomapperProfile, InjectMapper } from 'automapper-nestjs'; -import { Injectable } from '@nestjs/common'; -import { ApplicationDecisionMeetingDto } from '../../alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.dto'; -import { ApplicationDecisionMeeting } from '../../alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.entity'; -import { ApplicationDecisionDto } from '../../alcs/application-decision/application-decision-v1/application-decision/application-decision.dto'; +import { ApplicationDecisionMeeting } from '../../alcs/application/application-decision-meeting/application-decision-meeting.entity'; +import { ApplicationDecisionDto } from '../../alcs/application-decision/application-decision-v2/application-decision/application-decision.dto'; import { ApplicationDecision } from '../../alcs/application-decision/application-decision.entity'; import { ApplicationModificationOutcomeType } from '../../alcs/application-decision/application-modification/application-modification-outcome-type/application-modification-outcome-type.entity'; import { @@ -12,11 +11,12 @@ import { ApplicationModificationOutcomeCodeDto, } from '../../alcs/application-decision/application-modification/application-modification.dto'; import { ApplicationModification } from '../../alcs/application-decision/application-modification/application-modification.entity'; -import { LocalGovernmentDto } from '../../alcs/local-government/local-government.dto'; -import { LocalGovernment } from '../../alcs/local-government/local-government.entity'; import { Application } from '../../alcs/application/application.entity'; import { CardDto } from '../../alcs/card/card.dto'; import { Card } from '../../alcs/card/card.entity'; +import { LocalGovernmentDto } from '../../alcs/local-government/local-government.dto'; +import { LocalGovernment } from '../../alcs/local-government/local-government.entity'; +import { DecisionMeetingDto } from '../../alcs/meetings/decision-meeting.dto'; @Injectable() export class ModificationProfile extends AutomapperProfile { @@ -52,7 +52,7 @@ export class ModificationProfile extends AutomapperProfile { this.mapper.mapArray( a.decisionMeetings, ApplicationDecisionMeeting, - ApplicationDecisionMeetingDto, + DecisionMeetingDto, ), ), ), diff --git a/services/apps/alcs/src/common/automapper/reconsideration.automapper.profile.ts b/services/apps/alcs/src/common/automapper/reconsideration.automapper.profile.ts index 05290d4070..25b9fb6928 100644 --- a/services/apps/alcs/src/common/automapper/reconsideration.automapper.profile.ts +++ b/services/apps/alcs/src/common/automapper/reconsideration.automapper.profile.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; import { createMap, forMember, mapFrom, Mapper } from 'automapper-core'; import { AutomapperProfile, InjectMapper } from 'automapper-nestjs'; -import { ApplicationDecisionMeetingDto } from '../../alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.dto'; -import { ApplicationDecisionMeeting } from '../../alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.entity'; +import { DecisionMeetingDto } from '../../alcs/meetings/decision-meeting.dto'; +import { ApplicationDecisionMeeting } from '../../alcs/application/application-decision-meeting/application-decision-meeting.entity'; import { ApplicationForReconsiderationDto, ApplicationReconsiderationCreateDto, @@ -50,7 +50,7 @@ export class ReconsiderationProfile extends AutomapperProfile { this.mapper.mapArray( a.decisionMeetings, ApplicationDecisionMeeting, - ApplicationDecisionMeetingDto, + DecisionMeetingDto, ), ), ), diff --git a/services/apps/alcs/src/portal/public/application/application-decision.dto.ts b/services/apps/alcs/src/portal/public/application/application-decision.dto.ts index 6393d1b17a..c4e0954a06 100644 --- a/services/apps/alcs/src/portal/public/application/application-decision.dto.ts +++ b/services/apps/alcs/src/portal/public/application/application-decision.dto.ts @@ -1,9 +1,9 @@ import { AutoMap } from 'automapper-classes'; import { + ApplicationDecisionOutcomeCodeDto, DecisionDocumentDto, LinkedResolutionDto, -} from '../../../alcs/application-decision/application-decision-v1/application-decision/application-decision.dto'; -import { ApplicationDecisionOutcomeCodeDto } from '../../../alcs/application-decision/application-decision-v2/application-decision/application-decision.dto'; +} from '../../../alcs/application-decision/application-decision-v2/application-decision/application-decision.dto'; export class ApplicationPortalDecisionDto { @AutoMap() diff --git a/services/apps/alcs/src/portal/public/notice-of-intent/notice-of-intent-decision.dto.ts b/services/apps/alcs/src/portal/public/notice-of-intent/notice-of-intent-decision.dto.ts index c8f7796c71..93495ce05a 100644 --- a/services/apps/alcs/src/portal/public/notice-of-intent/notice-of-intent-decision.dto.ts +++ b/services/apps/alcs/src/portal/public/notice-of-intent/notice-of-intent-decision.dto.ts @@ -1,6 +1,6 @@ import { AutoMap } from 'automapper-classes'; -import { LinkedResolutionDto } from '../../../alcs/application-decision/application-decision-v1/application-decision/application-decision.dto'; import { + LinkedResolutionDto, NoticeOfIntentDecisionDocumentDto, NoticeOfIntentDecisionOutcomeCodeDto, } from '../../../alcs/notice-of-intent-decision/notice-of-intent-decision.dto'; diff --git a/services/apps/alcs/test/mocks/mockEntities.ts b/services/apps/alcs/test/mocks/mockEntities.ts index 9de8fb094f..c7de433452 100644 --- a/services/apps/alcs/test/mocks/mockEntities.ts +++ b/services/apps/alcs/test/mocks/mockEntities.ts @@ -1,5 +1,5 @@ import { ApplicationDecisionOutcomeCode } from '../../src/alcs/application-decision/application-decision-outcome.entity'; -import { ApplicationDecisionMeeting } from '../../src/alcs/application-decision/application-decision-v1/application-decision-meeting/application-decision-meeting.entity'; +import { ApplicationDecisionMeeting } from '../../src/alcs/application/application-decision-meeting/application-decision-meeting.entity'; import { ApplicationDecision } from '../../src/alcs/application-decision/application-decision.entity'; import { ApplicationModificationOutcomeType } from '../../src/alcs/application-decision/application-modification/application-modification-outcome-type/application-modification-outcome-type.entity'; import { ApplicationModification } from '../../src/alcs/application-decision/application-modification/application-modification.entity'; From d214ac6237062b3edbaf3569bff329afc372578e Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Wed, 20 Mar 2024 15:46:10 -0700 Subject: [PATCH 033/153] Add Timeline to Planning Reviews --- .../overview/overview.component.html | 4 + .../overview/overview.component.spec.ts | 14 +- .../overview/overview.component.ts | 11 ++ .../planning-review-timeline.dto.ts | 7 + .../planning-review-timeline.service.spec.ts | 45 ++++++ .../planning-review-timeline.service.ts | 28 ++++ services/apps/alcs/src/alcs/alcs.module.ts | 3 + .../planning-review-decision.module.ts | 2 +- .../planning-review-decision.service.spec.ts | 11 -- .../planning-review-decision.service.ts | 30 ---- ...lanning-review-timeline.controller.spec.ts | 49 ++++++ .../planning-review-timeline.controller.ts | 24 +++ .../planning-review-timeline.module.ts | 12 ++ .../planning-review-timeline.service.spec.ts | 141 ++++++++++++++++++ .../planning-review-timeline.service.ts | 113 ++++++++++++++ 15 files changed, 445 insertions(+), 49 deletions(-) create mode 100644 alcs-frontend/src/app/services/planning-review/planning-review-timeline/planning-review-timeline.dto.ts create mode 100644 alcs-frontend/src/app/services/planning-review/planning-review-timeline/planning-review-timeline.service.spec.ts create mode 100644 alcs-frontend/src/app/services/planning-review/planning-review-timeline/planning-review-timeline.service.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.controller.spec.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.controller.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.module.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.service.spec.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.service.ts diff --git a/alcs-frontend/src/app/features/planning-review/overview/overview.component.html b/alcs-frontend/src/app/features/planning-review/overview/overview.component.html index 7908db8884..26f9c4f0e4 100644 --- a/alcs-frontend/src/app/features/planning-review/overview/overview.component.html +++ b/alcs-frontend/src/app/features/planning-review/overview/overview.component.html @@ -29,3 +29,7 @@ <h5>Status</h5> /> </div> </section> +<section> + <h5>Planning Review Event Timeline</h5> + <app-timeline [events]="events"></app-timeline> +</section> diff --git a/alcs-frontend/src/app/features/planning-review/overview/overview.component.spec.ts b/alcs-frontend/src/app/features/planning-review/overview/overview.component.spec.ts index 19932568db..7da0b708cc 100644 --- a/alcs-frontend/src/app/features/planning-review/overview/overview.component.spec.ts +++ b/alcs-frontend/src/app/features/planning-review/overview/overview.component.spec.ts @@ -1,28 +1,24 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MatDialog } from '@angular/material/dialog'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { BehaviorSubject } from 'rxjs'; -import { NoticeOfIntentDecisionService } from '../../../services/notice-of-intent/decision/notice-of-intent-decision.service'; -import { NoticeOfIntentDetailService } from '../../../services/notice-of-intent/notice-of-intent-detail.service'; -import { NoticeOfIntentTimelineService } from '../../../services/notice-of-intent/notice-of-intent-timeline/notice-of-intent-timeline.service'; -import { NoticeOfIntentDto } from '../../../services/notice-of-intent/notice-of-intent.dto'; import { PlanningReviewDetailService } from '../../../services/planning-review/planning-review-detail.service'; +import { PlanningReviewTimelineService } from '../../../services/planning-review/planning-review-timeline/planning-review-timeline.service'; import { PlanningReviewDetailedDto } from '../../../services/planning-review/planning-review.dto'; import { PlanningReviewService } from '../../../services/planning-review/planning-review.service'; -import { ConfirmationDialogService } from '../../../shared/confirmation-dialog/confirmation-dialog.service'; import { OverviewComponent } from './overview.component'; -import { NoticeOfIntentSubmissionStatusService } from '../../../services/notice-of-intent/notice-of-intent-submission-status/notice-of-intent-submission-status.service'; describe('OverviewComponent', () => { let component: OverviewComponent; let fixture: ComponentFixture<OverviewComponent>; let mockPRDetailService: DeepMocked<PlanningReviewDetailService>; let mockPRService: DeepMocked<PlanningReviewService>; + let mockTimelineService: DeepMocked<PlanningReviewTimelineService>; beforeEach(async () => { mockPRService = createMock(); + mockTimelineService = createMock(); mockPRDetailService = createMock(); mockPRDetailService.$planningReview = new BehaviorSubject<PlanningReviewDetailedDto | undefined>(undefined); @@ -36,6 +32,10 @@ describe('OverviewComponent', () => { provide: PlanningReviewService, useValue: mockPRService, }, + { + provide: PlanningReviewTimelineService, + useValue: mockTimelineService, + }, ], declarations: [OverviewComponent], schemas: [NO_ERRORS_SCHEMA], diff --git a/alcs-frontend/src/app/features/planning-review/overview/overview.component.ts b/alcs-frontend/src/app/features/planning-review/overview/overview.component.ts index 6afb4dcb09..4088c91601 100644 --- a/alcs-frontend/src/app/features/planning-review/overview/overview.component.ts +++ b/alcs-frontend/src/app/features/planning-review/overview/overview.component.ts @@ -1,6 +1,8 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { Subject, takeUntil } from 'rxjs'; import { PlanningReviewDetailService } from '../../../services/planning-review/planning-review-detail.service'; +import { TimelineEventDto } from '../../../services/planning-review/planning-review-timeline/planning-review-timeline.dto'; +import { PlanningReviewTimelineService } from '../../../services/planning-review/planning-review-timeline/planning-review-timeline.service'; import { PlanningReviewDto } from '../../../services/planning-review/planning-review.dto'; import { PlanningReviewService } from '../../../services/planning-review/planning-review.service'; @@ -13,15 +15,20 @@ export class OverviewComponent implements OnInit, OnDestroy { $destroy = new Subject<void>(); planningReview?: PlanningReviewDto; types: { label: string; value: string }[] = []; + events: TimelineEventDto[] = []; constructor( private planningReviewDetailService: PlanningReviewDetailService, private planningReviewService: PlanningReviewService, + private planningReviewTimelineService: PlanningReviewTimelineService, ) {} ngOnInit(): void { this.planningReviewDetailService.$planningReview.pipe(takeUntil(this.$destroy)).subscribe((review) => { this.planningReview = review; + if (review) { + this.loadEvents(review.fileNumber); + } }); this.loadTypes(); } @@ -56,4 +63,8 @@ export class OverviewComponent implements OnInit, OnDestroy { }); } } + + private async loadEvents(fileNumber: string) { + this.events = await this.planningReviewTimelineService.fetchByFileNumber(fileNumber); + } } diff --git a/alcs-frontend/src/app/services/planning-review/planning-review-timeline/planning-review-timeline.dto.ts b/alcs-frontend/src/app/services/planning-review/planning-review-timeline/planning-review-timeline.dto.ts new file mode 100644 index 0000000000..cc1519a556 --- /dev/null +++ b/alcs-frontend/src/app/services/planning-review/planning-review-timeline/planning-review-timeline.dto.ts @@ -0,0 +1,7 @@ +export interface TimelineEventDto { + htmlText: string; + startDate: number; + fulfilledDate: number | null; + isFulfilled: boolean; + link: string | null; +} diff --git a/alcs-frontend/src/app/services/planning-review/planning-review-timeline/planning-review-timeline.service.spec.ts b/alcs-frontend/src/app/services/planning-review/planning-review-timeline/planning-review-timeline.service.spec.ts new file mode 100644 index 0000000000..500a70f5e9 --- /dev/null +++ b/alcs-frontend/src/app/services/planning-review/planning-review-timeline/planning-review-timeline.service.spec.ts @@ -0,0 +1,45 @@ +import { HttpClient } from '@angular/common/http'; +import { TestBed } from '@angular/core/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { of } from 'rxjs'; +import { ToastService } from '../../toast/toast.service'; + +import { PlanningReviewTimelineService } from './planning-review-timeline.service'; + +describe('PlanningReviewTimelineService', () => { + let service: PlanningReviewTimelineService; + let httpClient: DeepMocked<HttpClient>; + let toastService: DeepMocked<ToastService>; + + beforeEach(() => { + httpClient = createMock(); + toastService = createMock(); + + TestBed.configureTestingModule({ + providers: [ + { + provide: HttpClient, + useValue: httpClient, + }, + { + provide: ToastService, + useValue: toastService, + }, + ], + }); + service = TestBed.inject(PlanningReviewTimelineService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should call get to fetch timeline events', async () => { + httpClient.get.mockReturnValue(of([])); + + const res = await service.fetchByFileNumber('1'); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(res.length).toEqual(0); + }); +}); diff --git a/alcs-frontend/src/app/services/planning-review/planning-review-timeline/planning-review-timeline.service.ts b/alcs-frontend/src/app/services/planning-review/planning-review-timeline/planning-review-timeline.service.ts new file mode 100644 index 0000000000..0d89e71834 --- /dev/null +++ b/alcs-frontend/src/app/services/planning-review/planning-review-timeline/planning-review-timeline.service.ts @@ -0,0 +1,28 @@ +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 { TimelineEventDto } from './planning-review-timeline.dto'; + +@Injectable({ + providedIn: 'root', +}) +export class PlanningReviewTimelineService { + private url = `${environment.apiUrl}/planning-review-timeline`; + + constructor( + private http: HttpClient, + private toastService: ToastService, + ) {} + + async fetchByFileNumber(fileNumber: string) { + try { + return await firstValueFrom(this.http.get<TimelineEventDto[]>(`${this.url}/${fileNumber}`)); + } catch (err) { + console.error(err); + this.toastService.showErrorToast('Failed to fetch timeline events'); + } + return []; + } +} diff --git a/services/apps/alcs/src/alcs/alcs.module.ts b/services/apps/alcs/src/alcs/alcs.module.ts index 0f4a85c904..d5bd663cda 100644 --- a/services/apps/alcs/src/alcs/alcs.module.ts +++ b/services/apps/alcs/src/alcs/alcs.module.ts @@ -24,6 +24,7 @@ import { NotificationSubmissionStatusModule } from './notification/notification- import { NotificationTimelineModule } from './notification/notification-timeline/notification-timeline.module'; import { NotificationModule } from './notification/notification.module'; import { PlanningReviewDecisionModule } from './planning-review/planning-review-decision/planning-review-decision.module'; +import { PlanningReviewTimelineModule } from './planning-review/planning-review-timeline/planning-review-timeline.module'; import { PlanningReviewModule } from './planning-review/planning-review.module'; import { SearchModule } from './search/search.module'; import { StaffJournalModule } from './staff-journal/staff-journal.module'; @@ -54,6 +55,7 @@ import { StaffJournalModule } from './staff-journal/staff-journal.module'; NotificationTimelineModule, InquiryModule, MeetingModule, + PlanningReviewTimelineModule, RouterModule.register([ { path: 'alcs', module: ApplicationModule }, { path: 'alcs', module: CommentModule }, @@ -81,6 +83,7 @@ import { StaffJournalModule } from './staff-journal/staff-journal.module'; { path: 'alcs', module: NotificationTimelineModule }, { path: 'alcs', module: InquiryModule }, { path: 'alcs', module: MeetingModule }, + { path: 'alcs', module: PlanningReviewTimelineModule }, ]), ], controllers: [], diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.module.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.module.ts index 10c147f67a..359c6abab3 100644 --- a/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.module.ts +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.module.ts @@ -21,6 +21,6 @@ import { PlanningReviewDecisionService } from './planning-review-decision.servic ], providers: [PlanningReviewDecisionService, PlanningReviewDecisionProfile], controllers: [PlanningReviewDecisionController], - exports: [], + exports: [PlanningReviewDecisionService], }) export class PlanningReviewDecisionModule {} diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.service.spec.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.service.spec.ts index bb442dcce8..76be872281 100644 --- a/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.service.spec.ts +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.service.spec.ts @@ -318,17 +318,6 @@ describe('PlanningReviewDecisionService', () => { ); }); - it('should call the repository to check if portal user can download document', async () => { - mockDecisionDocumentRepository.findOne.mockResolvedValue( - new PlanningReviewDecisionDocument(), - ); - mockDocumentService.getDownloadUrl.mockResolvedValue(''); - - await service.getDownloadForPortal('fake-uuid'); - expect(mockDecisionDocumentRepository.findOne).toHaveBeenCalledTimes(1); - expect(mockDocumentService.getDownloadUrl).toHaveBeenCalledTimes(1); - }); - it('should throw an exception when document not found for deletion', async () => { mockDecisionDocumentRepository.findOne.mockResolvedValue(null); await expect(service.deleteDocument('fake-uuid')).rejects.toMatchObject( diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.service.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.service.ts index 94c23884be..c9c12901f3 100644 --- a/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.service.ts +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.service.ts @@ -230,28 +230,6 @@ export class PlanningReviewDecisionService { ); } - async getDownloadForPortal(decisionDocumentUuid: string) { - const decisionDocument = await this.decisionDocumentRepository.findOne({ - where: { - decision: { - isDraft: false, - }, - uuid: decisionDocumentUuid, - }, - relations: { - document: true, - }, - }); - - if (decisionDocument) { - return this.documentService.getDownloadUrl( - decisionDocument.document, - true, // FIXME: Document does not open inline despite flag being true - ); - } - throw new ServiceNotFoundException('Failed to find document'); - } - getOutcomeByCode(code: string) { return this.decisionOutcomeRepository.findOneOrFail({ where: { @@ -268,14 +246,6 @@ export class PlanningReviewDecisionService { }; } - getMany(modifiesDecisionUuids: string[]) { - return this.planningReviewDecisionRepository.find({ - where: { - uuid: In(modifiesDecisionUuids), - }, - }); - } - async generateResolutionNumber(resolutionYear: number) { const result = await this.planningReviewDecisionRepository.query( `SELECT * FROM alcs.generate_next_resolution_number(${resolutionYear})`, diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.controller.spec.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.controller.spec.ts new file mode 100644 index 0000000000..6664f67b42 --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.controller.spec.ts @@ -0,0 +1,49 @@ +import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; +import { Test, TestingModule } from '@nestjs/testing'; +import { ClsService } from 'nestjs-cls'; +import { mockKeyCloakProviders } from '../../../../test/mocks/mockTypes'; +import { PlanningReviewTimelineController } from './planning-review-timeline.controller'; +import { PlanningReviewTimelineService } from './planning-review-timeline.service'; + +describe('PlanningReviewTimelineController', () => { + let controller: PlanningReviewTimelineController; + let mockTimelineService: DeepMocked<PlanningReviewTimelineService>; + + beforeEach(async () => { + mockTimelineService = createMock(); + + const module: TestingModule = await Test.createTestingModule({ + providers: [ + { + provide: PlanningReviewTimelineService, + useValue: mockTimelineService, + }, + { + provide: ClsService, + useValue: {}, + }, + ...mockKeyCloakProviders, + ], + controllers: [PlanningReviewTimelineController], + }).compile(); + + controller = module.get<PlanningReviewTimelineController>( + PlanningReviewTimelineController, + ); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); + + it('should call through to get timeline events', async () => { + mockTimelineService.getTimelineEvents.mockResolvedValue([]); + const res = await controller.fetchTimelineEvents('fileNumber'); + + expect(res).toBeDefined(); + expect(mockTimelineService.getTimelineEvents).toHaveBeenCalledTimes(1); + expect(mockTimelineService.getTimelineEvents).toHaveBeenCalledWith( + 'fileNumber', + ); + }); +}); diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.controller.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.controller.ts new file mode 100644 index 0000000000..595a2a5b07 --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.controller.ts @@ -0,0 +1,24 @@ +import { Controller, Get, Param, UseGuards } from '@nestjs/common'; +import { ApiOAuth2 } from '@nestjs/swagger'; +import * as config from 'config'; +import { ROLES_ALLOWED_APPLICATIONS } from '../../../common/authorization/roles'; +import { RolesGuard } from '../../../common/authorization/roles-guard.service'; +import { UserRoles } from '../../../common/authorization/roles.decorator'; +import { PlanningReviewTimelineService } from './planning-review-timeline.service'; + +@ApiOAuth2(config.get<string[]>('KEYCLOAK.SCOPES')) +@Controller('planning-review-timeline') +@UseGuards(RolesGuard) +export class PlanningReviewTimelineController { + constructor( + private planningReviewTimelineService: PlanningReviewTimelineService, + ) {} + + @Get('/:fileNumber') + @UserRoles(...ROLES_ALLOWED_APPLICATIONS) + async fetchTimelineEvents(@Param('fileNumber') fileNumber: string) { + return await this.planningReviewTimelineService.getTimelineEvents( + fileNumber, + ); + } +} diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.module.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.module.ts new file mode 100644 index 0000000000..e115697d27 --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { PlanningReviewDecisionModule } from '../planning-review-decision/planning-review-decision.module'; +import { PlanningReviewModule } from '../planning-review.module'; +import { PlanningReviewTimelineController } from './planning-review-timeline.controller'; +import { PlanningReviewTimelineService } from './planning-review-timeline.service'; + +@Module({ + imports: [PlanningReviewModule, PlanningReviewDecisionModule], + providers: [PlanningReviewTimelineService], + controllers: [PlanningReviewTimelineController], +}) +export class PlanningReviewTimelineModule {} diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.service.spec.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.service.spec.ts new file mode 100644 index 0000000000..58072de25d --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.service.spec.ts @@ -0,0 +1,141 @@ +import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; +import { Test, TestingModule } from '@nestjs/testing'; +import { PlanningReferral } from '../planning-referral/planning-referral.entity'; +import { PlanningReviewDecision } from '../planning-review-decision/planning-review-decision.entity'; +import { PlanningReviewDecisionService } from '../planning-review-decision/planning-review-decision.service'; +import { PlanningReviewMeetingType } from '../planning-review-meeting/planning-review-meeting-type.entity'; +import { PlanningReviewMeeting } from '../planning-review-meeting/planning-review-meeting.entity'; +import { PlanningReviewMeetingService } from '../planning-review-meeting/planning-review-meeting.service'; +import { PlanningReview } from '../planning-review.entity'; +import { PlanningReviewService } from '../planning-review.service'; +import { PlanningReviewTimelineService } from './planning-review-timeline.service'; + +describe('PlanningReviewTimelineService', () => { + let service: PlanningReviewTimelineService; + + let mockPlanningReviewDecisionService: DeepMocked<PlanningReviewDecisionService>; + let mockPlanningReviewService: DeepMocked<PlanningReviewService>; + let mockPlanningReviewMeetingService: DeepMocked<PlanningReviewMeetingService>; + + beforeEach(async () => { + mockPlanningReviewDecisionService = createMock(); + mockPlanningReviewService = createMock(); + mockPlanningReviewMeetingService = createMock(); + + const module: TestingModule = await Test.createTestingModule({ + providers: [ + { + provide: PlanningReviewDecisionService, + useValue: mockPlanningReviewDecisionService, + }, + { + provide: PlanningReviewService, + useValue: mockPlanningReviewService, + }, + { + provide: PlanningReviewMeetingService, + useValue: mockPlanningReviewMeetingService, + }, + PlanningReviewTimelineService, + ], + }).compile(); + + mockPlanningReviewService.getDetailedReview.mockResolvedValue( + new PlanningReview({ + referrals: [], + }), + ); + mockPlanningReviewDecisionService.getByFileNumber.mockResolvedValue([]); + mockPlanningReviewMeetingService.getByPlanningReview.mockResolvedValue([]); + + service = module.get<PlanningReviewTimelineService>( + PlanningReviewTimelineService, + ); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + it('should return nothing for empty PR', async () => { + const res = await service.getTimelineEvents('file-number'); + + expect(res).toBeDefined(); + }); + + it('should map Referral events in the correct order', async () => { + const sameDate = new Date(); + + mockPlanningReviewService.getDetailedReview.mockResolvedValue( + new PlanningReview({ + referrals: [ + new PlanningReferral({ + auditCreatedAt: sameDate, + submissionDate: sameDate, + responseDate: sameDate, + }), + new PlanningReferral({ + auditCreatedAt: sameDate, + submissionDate: sameDate, + responseDate: null, + }), + ], + }), + ); + + const res = await service.getTimelineEvents('file-number'); + + expect(res).toBeDefined(); + expect(res.length).toEqual(2); + expect(res[0].htmlText).toEqual('Referral #1'); + expect(res[0].isFulfilled).toBeTruthy(); + expect(res[1].htmlText).toEqual('Referral #2'); + expect(res[1].isFulfilled).toBeFalsy(); + }); + + it('should map Decision Events', async () => { + const sameDate = new Date(); + mockPlanningReviewDecisionService.getByFileNumber.mockResolvedValue([ + new PlanningReviewDecision({ + date: new Date(sameDate.getTime() + 1000), + }), + new PlanningReviewDecision({ + date: sameDate, + }), + ]); + + const res = await service.getTimelineEvents('file-number'); + + expect(res).toBeDefined(); + expect(res.length).toEqual(2); + expect(res[0].htmlText).toEqual('Decision #2'); + expect(res[1].htmlText).toEqual('Decision #1'); + }); + + it('should map Meeting Events', async () => { + const sameDate = new Date(); + mockPlanningReviewMeetingService.getByPlanningReview.mockResolvedValue([ + new PlanningReviewMeeting({ + date: new Date(sameDate.getTime() + 1000), + type: { + label: 'Other Meeting', + code: 'CATS', + } as PlanningReviewMeetingType, + }), + new PlanningReviewMeeting({ + date: sameDate, + type: { + label: 'Meeting', + code: 'CATS', + } as PlanningReviewMeetingType, + }), + ]); + + const res = await service.getTimelineEvents('file-number'); + + expect(res).toBeDefined(); + expect(res.length).toEqual(2); + expect(res[0].htmlText).toEqual('Scheduled Date - Other Meeting'); + expect(res[1].htmlText).toEqual('Scheduled Date - Meeting'); + }); +}); diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.service.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.service.ts new file mode 100644 index 0000000000..62faf708ae --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.service.ts @@ -0,0 +1,113 @@ +import { Injectable } from '@nestjs/common'; +import { CardSubtask } from '../../card/card-subtask/card-subtask.entity'; +import { PlanningReviewDecisionService } from '../planning-review-decision/planning-review-decision.service'; +import { PlanningReviewMeetingService } from '../planning-review-meeting/planning-review-meeting.service'; +import { PlanningReview } from '../planning-review.entity'; +import { PlanningReviewService } from '../planning-review.service'; + +export interface TimelineEvent { + htmlText: string; + startDate: number; + fulfilledDate: number | null; + isFulfilled: boolean; + link?: string | null; +} + +const SORTING_ORDER = { + //high comes first, 1 shows at bottom + DECISION: 3, + MEETING: 2, + REFERRAL: 1, +}; + +@Injectable() +export class PlanningReviewTimelineService { + constructor( + private planningReviewDecisionService: PlanningReviewDecisionService, + private planningReviewService: PlanningReviewService, + private planningReviewMeetingService: PlanningReviewMeetingService, //private planningReferralService: PlanningReferralService, + ) {} + + async getTimelineEvents(fileNumber: string) { + const events: TimelineEvent[] = []; + const planningReview = + await this.planningReviewService.getDetailedReview(fileNumber); + await this.addPlanningReferralEvents(planningReview, events); + await this.addDecisionEvents(planningReview, events); + await this.addMeetingEvents(planningReview, events); + + events.sort((a, b) => b.startDate - a.startDate); + return events; + } + + private mapSubtaskToEvent(subtask: CardSubtask): TimelineEvent { + return { + htmlText: `${subtask.type.label} Subtask`, + fulfilledDate: subtask.completedAt?.getTime() ?? null, + startDate: subtask.createdAt.getTime(), + isFulfilled: !!subtask.completedAt, + }; + } + + private async addDecisionEvents( + planningReview: PlanningReview, + events: TimelineEvent[], + ) { + const decisions = await this.planningReviewDecisionService.getByFileNumber( + planningReview.fileNumber, + ); + const sorted = decisions.filter((dec) => !dec.isDraft).sort(); + + for (const [index, decision] of sorted.entries()) { + events.push({ + htmlText: `Decision #${decisions.length - index}`, + startDate: decision.date!.getTime() + SORTING_ORDER.DECISION, + fulfilledDate: null, + isFulfilled: true, + }); + } + } + + private async addMeetingEvents( + planningReview: PlanningReview, + events: TimelineEvent[], + ) { + const meetings = + await this.planningReviewMeetingService.getByPlanningReview( + planningReview.uuid, + ); + + meetings.sort((a, b) => a.date.getTime() - b.date.getTime()); + const typeCount = new Map<string, number>(); + meetings.forEach((meeting) => { + const count = typeCount.get(meeting.type.code) || 0; + events.push({ + htmlText: `Scheduled Date - ${meeting.type.label}`, + startDate: meeting.date.getTime() + SORTING_ORDER.MEETING, + fulfilledDate: null, + isFulfilled: true, + }); + + typeCount.set(meeting.type.code, count + 1); + }); + } + + private async addPlanningReferralEvents( + planningReview: PlanningReview, + events: TimelineEvent[], + ) { + const referrals = planningReview.referrals; + referrals.sort( + (a, b) => a.auditCreatedAt.getTime() - b.auditCreatedAt.getTime(), + ); + for (const [index, referral] of planningReview.referrals.entries()) { + events.push({ + htmlText: `Referral #${index + 1}`, + startDate: referral.submissionDate.getTime() + SORTING_ORDER.REFERRAL, + fulfilledDate: referral.responseDate?.getTime() ?? null, + isFulfilled: !!referral.responseDate, + link: './referrals', + }); + } + } +} From 60a3eea8d4b4f93865877b5c614eb3d064647c6c Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Thu, 21 Mar 2024 10:29:48 -0700 Subject: [PATCH 034/153] Add Subtasks to PR Timelines --- .../planning-review-timeline.service.ts | 7 +++++++ .../src/alcs/planning-review/planning-review.service.ts | 3 +++ 2 files changed, 10 insertions(+) diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.service.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.service.ts index 62faf708ae..eb22d402b2 100644 --- a/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.service.ts +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.service.ts @@ -108,6 +108,13 @@ export class PlanningReviewTimelineService { isFulfilled: !!referral.responseDate, link: './referrals', }); + + if (referral.card) { + for (const subtask of referral.card.subtasks) { + const mappedEvent = this.mapSubtaskToEvent(subtask); + events.push(mappedEvent); + } + } } } } 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 f653bb14c0..8ad4e89a63 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 @@ -110,6 +110,9 @@ export class PlanningReviewService { card: { board: true, type: true, + subtasks: { + type: true, + }, }, }, meetings: true, From 0fd0bd6fc1623edb572c5b6f232eafa0822a0e0a Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Thu, 21 Mar 2024 10:39:31 -0700 Subject: [PATCH 035/153] PR search fixes * Update field description * Fix search by document name --- .../planning-review-search-table.component.ts | 6 +---- .../app/features/search/search.component.html | 2 +- .../app/features/search/search.component.ts | 24 ------------------- ...planning-review-advanced-search.service.ts | 9 ++++--- 4 files changed, 8 insertions(+), 33 deletions(-) diff --git a/alcs-frontend/src/app/features/search/planning-review-search-table/planning-review-search-table.component.ts b/alcs-frontend/src/app/features/search/planning-review-search-table/planning-review-search-table.component.ts index c4f2c20c15..1d2a9cb760 100644 --- a/alcs-frontend/src/app/features/search/planning-review-search-table/planning-review-search-table.component.ts +++ b/alcs-frontend/src/app/features/search/planning-review-search-table/planning-review-search-table.component.ts @@ -73,11 +73,7 @@ export class PlanningReviewSearchTableComponent { } onSelectRecord(record: SearchResult) { - const url = this.router.serializeUrl( - this.router.createUrlTree([`/board/${record.board}`], { - queryParams: { card: record.referenceId, type: record.class }, - }), - ); + const url = this.router.serializeUrl(this.router.createUrlTree([`/planning-review/${record.referenceId}`])); window.open(url, '_blank'); } diff --git a/alcs-frontend/src/app/features/search/search.component.html b/alcs-frontend/src/app/features/search/search.component.html index 9ad377c6a6..d0dc989bc4 100644 --- a/alcs-frontend/src/app/features/search/search.component.html +++ b/alcs-frontend/src/app/features/search/search.component.html @@ -21,7 +21,7 @@ <h6 class="subheading">Provide one or more of the following criteria:</h6> <input id="name" matInput formControlName="name" minlength="3" /> </mat-form-field> <div class="subtext"> - Search by Primary Contact, Parcel Owner, Organization, SRW Transferee, Ministry or Department + Search by Primary Contact, Parcel Owner, Organization, Ministry or Department, SRW Transferee, PR Document Name </div> <div *ngIf="nameControl.invalid && (nameControl.dirty || nameControl.touched)" class="field-error"> <mat-icon>warning</mat-icon> diff --git a/alcs-frontend/src/app/features/search/search.component.ts b/alcs-frontend/src/app/features/search/search.component.ts index cdd19c4e42..d4e97a5b4d 100644 --- a/alcs-frontend/src/app/features/search/search.component.ts +++ b/alcs-frontend/src/app/features/search/search.component.ts @@ -216,30 +216,6 @@ export class SearchComponent implements OnInit, OnDestroy { this.isSearchExpanded = !this.isSearchExpanded; } - onGovernmentChange($event: MatAutocompleteSelectedEvent) { - const localGovernmentName = $event.option.value; - if (localGovernmentName) { - const localGovernment = this.localGovernments.find((lg) => lg.name == localGovernmentName); - if (localGovernment) { - this.localGovernmentControl.setValue(localGovernment.name); - } - } - } - - onBlur() { - //Blur will fire before onGovernmentChange above, so use setTimeout to delay it - setTimeout(() => { - const localGovernmentName = this.localGovernmentControl.getRawValue(); - if (localGovernmentName) { - const localGovernment = this.localGovernments.find((lg) => lg.name == localGovernmentName); - if (!localGovernment) { - this.localGovernmentControl.setValue(null); - console.log('Clearing Local Government field'); - } - } - }, 500); - } - onReset() { this.searchForm.reset(); diff --git a/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.ts b/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.ts index 9ff0975e8d..332d38f954 100644 --- a/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.ts +++ b/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.ts @@ -178,9 +178,12 @@ export class PlanningReviewAdvancedSearchService { const formattedSearchString = formatStringToPostgresSearchStringArrayWithWildCard(searchDto.name!); - query = query.andWhere('planningReviewSearch.document_name LIKE :name', { - name: formattedSearchString, - }); + query = query.andWhere( + 'LOWER(planningReviewSearch.document_name) LIKE ANY (:names)', + { + names: formattedSearchString, + }, + ); } return query; } From c3e9aeacc1501789409d0a815cb1a0c1196f6152 Mon Sep 17 00:00:00 2001 From: Tristan Slater <1631008+trslater@users.noreply.github.com> Date: Thu, 21 Mar 2024 11:51:07 -0700 Subject: [PATCH 036/153] Add missing 'No Data' for app fields --- .../parcel/parcel.component.html | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/alcs-frontend/src/app/features/application/applicant-info/application-details/parcel/parcel.component.html b/alcs-frontend/src/app/features/application/applicant-info/application-details/parcel/parcel.component.html index dd798749da..951e4174f9 100644 --- a/alcs-frontend/src/app/features/application/applicant-info/application-details/parcel/parcel.component.html +++ b/alcs-frontend/src/app/features/application/applicant-info/application-details/parcel/parcel.component.html @@ -37,6 +37,7 @@ <h4 [id]="parcel.uuid">Parcel #{{ parcelInd + 1 }}: Parcel and Owner Information <div class="subheading2 grid-1">PIN (optional)</div> <div class="grid-double"> {{ parcel.pin }} + <app-no-data *ngIf="!parcel.pin"></app-no-data> </div> </ng-container> <ng-container *ngIf="parcel.ownershipTypeCode === PARCEL_OWNERSHIP_TYPES.FEE_SIMPLE"> @@ -68,6 +69,7 @@ <h4 [id]="parcel.uuid">Parcel #{{ parcelInd + 1 }}: Parcel and Owner Information <div *ngIf="parcel.certificateOfTitle"> <a (click)="openFile(parcel.certificateOfTitle)">{{ parcel.certificateOfTitle.fileName }}</a> </div> + <app-no-data *ngIf="!parcel.certificateOfTitle"></app-no-data> </div> <ng-container *ngIf="parcel.ownershipTypeCode === PARCEL_OWNERSHIP_TYPES.CROWN"> <h5>Government Parcel Contact</h5> @@ -77,37 +79,27 @@ <h5>Government Parcel Contact</h5> {{ parcel.owners[0].firstName }} <app-no-data *ngIf="!parcel.owners[0].firstName"></app-no-data> </div> - <div class="subheading2 grid-1"> - Last Name - </div> + <div class="subheading2 grid-1">Last Name</div> <div class="grid-double"> {{ parcel.owners[0].lastName }} <app-no-data *ngIf="!parcel.owners[0].lastName"></app-no-data> </div> - <div class="subheading2 grid-1"> - Ministry or Department - </div> + <div class="subheading2 grid-1">Ministry or Department</div> <div class="grid-double"> {{ parcel.owners[0].organizationName }} <app-no-data *ngIf="!parcel.owners[0].organizationName"></app-no-data> </div> - <div class="subheading2 grid-1"> - Phone - </div> + <div class="subheading2 grid-1">Phone</div> <div class="grid-double"> {{ parcel.owners[0].phoneNumber }} <app-no-data *ngIf="!parcel.owners[0].phoneNumber"></app-no-data> </div> - <div class="subheading2 grid-1"> - Email - </div> + <div class="subheading2 grid-1">Email</div> <div class="grid-double"> {{ parcel.owners[0].email }} <app-no-data *ngIf="!parcel.owners[0].email"></app-no-data> </div> - <div class="subheading2 grid-1"> - Crown Type - </div> + <div class="subheading2 grid-1">Crown Type</div> <div class="grid-double"> {{ parcel.owners[0].crownLandOwnerType === 'provincial' ? 'Provincial Crown' : '' }} {{ parcel.owners[0].crownLandOwnerType === 'federal' ? 'Federal Crown' : '' }} @@ -137,6 +129,7 @@ <h5>Government Parcel Contact</h5> <app-no-data *ngIf="!owner.corporateSummary"></app-no-data> </div> </ng-container> + <app-no-data *ngIf="parcel.owners.length === 0"></app-no-data> </div> </ng-container> </div> From 5077d44024dc9bd5a0a983dbc3b3b0cf0ce214ad Mon Sep 17 00:00:00 2001 From: Tristan Slater <1631008+trslater@users.noreply.github.com> Date: Thu, 21 Mar 2024 12:02:08 -0700 Subject: [PATCH 037/153] Add missing 'No Data' for NOI preview --- .../notice-of-intent-details/parcel/parcel.component.html | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/parcel/parcel.component.html b/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/parcel/parcel.component.html index 210a9a81f7..e3c8258402 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/parcel/parcel.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/parcel/parcel.component.html @@ -62,11 +62,12 @@ <h4 [id]="parcel.uuid">Parcel #{{ parcelInd + 1 }}: Parcel and Owner Information <app-no-data *ngIf="!parcel.crownLandOwnerType"></app-no-data> </div> </ng-container> - <div *ngIf="parcel.certificateOfTitle" class="subheading2 grid-1">Certificate Of Title</div> - <div *ngIf="parcel.certificateOfTitle" class="grid-double"> - <div> + <div class="subheading2 grid-1">Certificate Of Title</div> + <div class="grid-double"> + <div *ngIf="parcel.certificateOfTitle"> <a (click)="onOpenFile(parcel.certificateOfTitle)">{{ parcel.certificateOfTitle.fileName }}</a> </div> + <app-no-data *ngIf="!parcel.certificateOfTitle"></app-no-data> </div> <ng-container *ngIf="parcel.ownershipTypeCode === PARCEL_OWNERSHIP_TYPES.CROWN"> <h5>Government Parcel Contact</h5> @@ -129,6 +130,7 @@ <h5>Government Parcel Contact</h5> <app-no-data *ngIf="!owner.corporateSummary"></app-no-data> </div> </ng-container> + <app-no-data *ngIf="parcel.owners.length === 0"></app-no-data> </div> </ng-container> </div> From ffc1598fe5d308fad7d32ffc95a5fd4ba23a01eb Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Thu, 21 Mar 2024 12:06:35 -0700 Subject: [PATCH 038/153] Update Application applicant when publishing draft --- ...pplication-submission-draft.controller.spec.ts | 7 ------- .../application-submission-draft.controller.ts | 2 -- .../application-submission-draft.module.ts | 2 ++ .../application-submission-draft.service.spec.ts | 9 +++++++++ .../application-submission-draft.service.ts | 5 ++++- ...-of-intent-submission-draft.controller.spec.ts | 7 ------- ...otice-of-intent-submission-draft.controller.ts | 2 -- .../notice-of-intent-submission-draft.module.ts | 2 ++ ...ice-of-intent-submission-draft.service.spec.ts | 15 ++++++++++++--- .../notice-of-intent-submission-draft.service.ts | 7 +++++++ 10 files changed, 36 insertions(+), 22 deletions(-) diff --git a/services/apps/alcs/src/portal/application-submission-draft/application-submission-draft.controller.spec.ts b/services/apps/alcs/src/portal/application-submission-draft/application-submission-draft.controller.spec.ts index f659ed83a0..0e41d1a1fc 100644 --- a/services/apps/alcs/src/portal/application-submission-draft/application-submission-draft.controller.spec.ts +++ b/services/apps/alcs/src/portal/application-submission-draft/application-submission-draft.controller.spec.ts @@ -4,26 +4,19 @@ import { ClsService } from 'nestjs-cls'; import { mockKeyCloakProviders } from '../../../test/mocks/mockTypes'; import { User } from '../../user/user.entity'; import { ApplicationSubmission } from '../application-submission/application-submission.entity'; -import { ApplicationSubmissionService } from '../application-submission/application-submission.service'; import { ApplicationSubmissionDraftController } from './application-submission-draft.controller'; import { ApplicationSubmissionDraftService } from './application-submission-draft.service'; describe('ApplicationSubmissionDraftController', () => { let controller: ApplicationSubmissionDraftController; - let mockAppSubmissionService: DeepMocked<ApplicationSubmissionService>; let mockAppEditService: DeepMocked<ApplicationSubmissionDraftService>; beforeEach(async () => { - mockAppSubmissionService = createMock(); mockAppEditService = createMock(); const module: TestingModule = await Test.createTestingModule({ controllers: [ApplicationSubmissionDraftController], providers: [ - { - provide: ApplicationSubmissionService, - useValue: mockAppSubmissionService, - }, { provide: ApplicationSubmissionDraftService, useValue: mockAppEditService, diff --git a/services/apps/alcs/src/portal/application-submission-draft/application-submission-draft.controller.ts b/services/apps/alcs/src/portal/application-submission-draft/application-submission-draft.controller.ts index dddf19a8d2..1601c635bd 100644 --- a/services/apps/alcs/src/portal/application-submission-draft/application-submission-draft.controller.ts +++ b/services/apps/alcs/src/portal/application-submission-draft/application-submission-draft.controller.ts @@ -12,7 +12,6 @@ import * as config from 'config'; import { ROLES_ALLOWED_APPLICATIONS } from '../../common/authorization/roles'; import { RolesGuard } from '../../common/authorization/roles-guard.service'; import { UserRoles } from '../../common/authorization/roles.decorator'; -import { ApplicationSubmissionService } from '../application-submission/application-submission.service'; import { ApplicationSubmissionDraftService } from './application-submission-draft.service'; @ApiOAuth2(config.get<string[]>('KEYCLOAK.SCOPES')) @@ -20,7 +19,6 @@ import { ApplicationSubmissionDraftService } from './application-submission-draf @UseGuards(RolesGuard) export class ApplicationSubmissionDraftController { constructor( - private applicationSubmissionService: ApplicationSubmissionService, private applicationEditService: ApplicationSubmissionDraftService, ) {} diff --git a/services/apps/alcs/src/portal/application-submission-draft/application-submission-draft.module.ts b/services/apps/alcs/src/portal/application-submission-draft/application-submission-draft.module.ts index 1ff7684867..953671adfa 100644 --- a/services/apps/alcs/src/portal/application-submission-draft/application-submission-draft.module.ts +++ b/services/apps/alcs/src/portal/application-submission-draft/application-submission-draft.module.ts @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ApplicationSubmissionStatusModule } from '../../alcs/application/application-submission-status/application-submission-status.module'; import { ApplicationSubmissionStatusType } from '../../alcs/application/application-submission-status/submission-status-type.entity'; +import { ApplicationModule } from '../../alcs/application/application.module'; import { ParcelOwnershipType } from '../../common/entities/parcel-ownership-type/parcel-ownership-type.entity'; import { OwnerType } from '../../common/owner-type/owner-type.entity'; import { ApplicationOwner } from '../application-submission/application-owner/application-owner.entity'; @@ -22,6 +23,7 @@ import { ApplicationSubmissionDraftService } from './application-submission-draf ApplicationOwner, OwnerType, ]), + ApplicationModule, ApplicationSubmissionModule, PdfGenerationModule, ApplicationSubmissionStatusModule, diff --git a/services/apps/alcs/src/portal/application-submission-draft/application-submission-draft.service.spec.ts b/services/apps/alcs/src/portal/application-submission-draft/application-submission-draft.service.spec.ts index 14cfbde99a..e7f8960bea 100644 --- a/services/apps/alcs/src/portal/application-submission-draft/application-submission-draft.service.spec.ts +++ b/services/apps/alcs/src/portal/application-submission-draft/application-submission-draft.service.spec.ts @@ -4,6 +4,7 @@ import { getRepositoryToken } from '@nestjs/typeorm'; import { create } from 'handlebars'; import { Repository } from 'typeorm'; import { ApplicationSubmissionStatusService } from '../../alcs/application/application-submission-status/application-submission-status.service'; +import { ApplicationService } from '../../alcs/application/application.service'; import { User } from '../../user/user.entity'; import { ApplicationOwnerService } from '../application-submission/application-owner/application-owner.service'; import { ApplicationParcelService } from '../application-submission/application-parcel/application-parcel.service'; @@ -23,6 +24,7 @@ describe('ApplicationSubmissionDraftService', () => { let mockGenerateSubmissionDocumentService: DeepMocked<GenerateSubmissionDocumentService>; let mockApplicationSubmissionStatusService: DeepMocked<ApplicationSubmissionStatusService>; let mockTransfereeService: DeepMocked<CovenantTransfereeService>; + let mockApplicationService: DeepMocked<ApplicationService>; beforeEach(async () => { mockSubmissionRepo = createMock(); @@ -32,6 +34,7 @@ describe('ApplicationSubmissionDraftService', () => { mockGenerateSubmissionDocumentService = createMock(); mockApplicationSubmissionStatusService = createMock(); mockTransfereeService = createMock(); + mockApplicationService = createMock(); const module: TestingModule = await Test.createTestingModule({ providers: [ @@ -64,6 +67,10 @@ describe('ApplicationSubmissionDraftService', () => { provide: CovenantTransfereeService, useValue: mockTransfereeService, }, + { + provide: ApplicationService, + useValue: mockApplicationService, + }, ], }).compile(); @@ -166,6 +173,7 @@ describe('ApplicationSubmissionDraftService', () => { new CovenantTransferee(), ]); mockTransfereeService.delete.mockResolvedValue({} as any); + mockApplicationService.updateApplicant.mockResolvedValue(); await service.publish('fileNumber', new User()); @@ -185,6 +193,7 @@ describe('ApplicationSubmissionDraftService', () => { expect( mockGenerateSubmissionDocumentService.generateUpdate, ).toHaveBeenCalledTimes(1); + expect(mockApplicationService.updateApplicant).toHaveBeenCalledTimes(1); }); it('should call through for mapToDto', async () => { diff --git a/services/apps/alcs/src/portal/application-submission-draft/application-submission-draft.service.ts b/services/apps/alcs/src/portal/application-submission-draft/application-submission-draft.service.ts index 21c70ff800..63d848200c 100644 --- a/services/apps/alcs/src/portal/application-submission-draft/application-submission-draft.service.ts +++ b/services/apps/alcs/src/portal/application-submission-draft/application-submission-draft.service.ts @@ -3,13 +3,13 @@ import { BadRequestException, Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { ApplicationSubmissionStatusService } from '../../alcs/application/application-submission-status/application-submission-status.service'; +import { ApplicationService } from '../../alcs/application/application.service'; import { User } from '../../user/user.entity'; import { ApplicationOwnerService } from '../application-submission/application-owner/application-owner.service'; import { ApplicationParcelUpdateDto } from '../application-submission/application-parcel/application-parcel.dto'; import { ApplicationParcelService } from '../application-submission/application-parcel/application-parcel.service'; import { ApplicationSubmission } from '../application-submission/application-submission.entity'; import { ApplicationSubmissionService } from '../application-submission/application-submission.service'; -import { CovenantTransferee } from '../application-submission/covenant-transferee/covenant-transferee.entity'; import { CovenantTransfereeService } from '../application-submission/covenant-transferee/covenant-transferee.service'; import { APPLICATION_SUBMISSION_TYPES, @@ -29,6 +29,7 @@ export class ApplicationSubmissionDraftService { private generateSubmissionDocumentService: GenerateSubmissionDocumentService, private applicationSubmissionStatusService: ApplicationSubmissionStatusService, private covenantTransfereeService: CovenantTransfereeService, + private applicationService: ApplicationService, ) {} async getOrCreateDraft(fileNumber: string) { @@ -235,6 +236,8 @@ export class ApplicationSubmissionDraftService { fileNumber, user, ); + + await this.applicationService.updateApplicant(fileNumber, draft.applicant!); this.logger.debug(`Published Draft for file number ${fileNumber}`); } diff --git a/services/apps/alcs/src/portal/notice-of-intent-submission-draft/notice-of-intent-submission-draft.controller.spec.ts b/services/apps/alcs/src/portal/notice-of-intent-submission-draft/notice-of-intent-submission-draft.controller.spec.ts index 6d426c63d3..ec86dcef6f 100644 --- a/services/apps/alcs/src/portal/notice-of-intent-submission-draft/notice-of-intent-submission-draft.controller.spec.ts +++ b/services/apps/alcs/src/portal/notice-of-intent-submission-draft/notice-of-intent-submission-draft.controller.spec.ts @@ -4,18 +4,15 @@ import { ClsService } from 'nestjs-cls'; import { mockKeyCloakProviders } from '../../../test/mocks/mockTypes'; import { User } from '../../user/user.entity'; import { NoticeOfIntentSubmission } from '../notice-of-intent-submission/notice-of-intent-submission.entity'; -import { NoticeOfIntentSubmissionService } from '../notice-of-intent-submission/notice-of-intent-submission.service'; import { NoticeOfIntentSubmissionDraftController } from './notice-of-intent-submission-draft.controller'; import { NoticeOfIntentSubmissionDraftService } from './notice-of-intent-submission-draft.service'; describe('NoticeOfIntentSubmissionDraftController', () => { let controller: NoticeOfIntentSubmissionDraftController; - let mockNoiSubmissionService: DeepMocked<NoticeOfIntentSubmissionService>; let mockNoiDraftService: DeepMocked<NoticeOfIntentSubmissionDraftService>; let mockUser; beforeEach(async () => { - mockNoiSubmissionService = createMock(); mockNoiDraftService = createMock(); mockUser = new User({ @@ -25,10 +22,6 @@ describe('NoticeOfIntentSubmissionDraftController', () => { const module: TestingModule = await Test.createTestingModule({ controllers: [NoticeOfIntentSubmissionDraftController], providers: [ - { - provide: NoticeOfIntentSubmissionService, - useValue: mockNoiSubmissionService, - }, { provide: NoticeOfIntentSubmissionDraftService, useValue: mockNoiDraftService, diff --git a/services/apps/alcs/src/portal/notice-of-intent-submission-draft/notice-of-intent-submission-draft.controller.ts b/services/apps/alcs/src/portal/notice-of-intent-submission-draft/notice-of-intent-submission-draft.controller.ts index c69b19ea7b..4c0be2fae4 100644 --- a/services/apps/alcs/src/portal/notice-of-intent-submission-draft/notice-of-intent-submission-draft.controller.ts +++ b/services/apps/alcs/src/portal/notice-of-intent-submission-draft/notice-of-intent-submission-draft.controller.ts @@ -12,7 +12,6 @@ import * as config from 'config'; import { ROLES_ALLOWED_APPLICATIONS } from '../../common/authorization/roles'; import { RolesGuard } from '../../common/authorization/roles-guard.service'; import { UserRoles } from '../../common/authorization/roles.decorator'; -import { NoticeOfIntentSubmissionService } from '../notice-of-intent-submission/notice-of-intent-submission.service'; import { NoticeOfIntentSubmissionDraftService } from './notice-of-intent-submission-draft.service'; @ApiOAuth2(config.get<string[]>('KEYCLOAK.SCOPES')) @@ -20,7 +19,6 @@ import { NoticeOfIntentSubmissionDraftService } from './notice-of-intent-submiss @UseGuards(RolesGuard) export class NoticeOfIntentSubmissionDraftController { constructor( - private noticeOfIntentSubmissionService: NoticeOfIntentSubmissionService, private noticeOfIntentSubmissionDraftService: NoticeOfIntentSubmissionDraftService, ) {} diff --git a/services/apps/alcs/src/portal/notice-of-intent-submission-draft/notice-of-intent-submission-draft.module.ts b/services/apps/alcs/src/portal/notice-of-intent-submission-draft/notice-of-intent-submission-draft.module.ts index 860dab2355..4e9acccc6e 100644 --- a/services/apps/alcs/src/portal/notice-of-intent-submission-draft/notice-of-intent-submission-draft.module.ts +++ b/services/apps/alcs/src/portal/notice-of-intent-submission-draft/notice-of-intent-submission-draft.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { NoticeOfIntentSubmissionStatusModule } from '../../alcs/notice-of-intent/notice-of-intent-submission-status/notice-of-intent-submission-status.module'; +import { NoticeOfIntentModule } from '../../alcs/notice-of-intent/notice-of-intent.module'; import { NoticeOfIntentSubmission } from '../notice-of-intent-submission/notice-of-intent-submission.entity'; import { NoticeOfIntentSubmissionModule } from '../notice-of-intent-submission/notice-of-intent-submission.module'; import { PdfGenerationModule } from '../pdf-generation/pdf-generation.module'; @@ -13,6 +14,7 @@ import { NoticeOfIntentSubmissionDraftService } from './notice-of-intent-submiss NoticeOfIntentSubmissionModule, PdfGenerationModule, NoticeOfIntentSubmissionStatusModule, + NoticeOfIntentModule, ], providers: [NoticeOfIntentSubmissionDraftService], controllers: [NoticeOfIntentSubmissionDraftController], diff --git a/services/apps/alcs/src/portal/notice-of-intent-submission-draft/notice-of-intent-submission-draft.service.spec.ts b/services/apps/alcs/src/portal/notice-of-intent-submission-draft/notice-of-intent-submission-draft.service.spec.ts index 20240d2878..bbbbbea6e9 100644 --- a/services/apps/alcs/src/portal/notice-of-intent-submission-draft/notice-of-intent-submission-draft.service.spec.ts +++ b/services/apps/alcs/src/portal/notice-of-intent-submission-draft/notice-of-intent-submission-draft.service.spec.ts @@ -3,6 +3,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { NoticeOfIntentSubmissionStatusService } from '../../alcs/notice-of-intent/notice-of-intent-submission-status/notice-of-intent-submission-status.service'; +import { NoticeOfIntentService } from '../../alcs/notice-of-intent/notice-of-intent.service'; import { User } from '../../user/user.entity'; import { NoticeOfIntentOwnerService } from '../notice-of-intent-submission/notice-of-intent-owner/notice-of-intent-owner.service'; import { NoticeOfIntentParcelService } from '../notice-of-intent-submission/notice-of-intent-parcel/notice-of-intent-parcel.service'; @@ -19,6 +20,7 @@ describe('NoticeOfIntentSubmissionDraftService', () => { let mockAppOwnerService: DeepMocked<NoticeOfIntentOwnerService>; let mockGenerateSubmissionDocumentService: DeepMocked<GenerateNoiSubmissionDocumentService>; let mockNoiSubmissionStatusService: DeepMocked<NoticeOfIntentSubmissionStatusService>; + let mockNoiService: DeepMocked<NoticeOfIntentService>; let mockUser; @@ -29,6 +31,7 @@ describe('NoticeOfIntentSubmissionDraftService', () => { mockAppOwnerService = createMock(); mockGenerateSubmissionDocumentService = createMock(); mockNoiSubmissionStatusService = createMock(); + mockNoiService = createMock(); mockUser = new User({ clientRoles: [], @@ -61,6 +64,10 @@ describe('NoticeOfIntentSubmissionDraftService', () => { provide: NoticeOfIntentSubmissionStatusService, useValue: mockNoiSubmissionStatusService, }, + { + provide: NoticeOfIntentService, + useValue: mockNoiService, + }, ], }).compile(); @@ -147,6 +154,7 @@ describe('NoticeOfIntentSubmissionDraftService', () => { mockParcelService.deleteMany.mockResolvedValueOnce([]); mockGenerateSubmissionDocumentService.generateUpdate.mockResolvedValue(); mockNoiSubmissionStatusService.removeStatuses.mockResolvedValue({} as any); + mockNoiService.updateApplicant.mockResolvedValue(); await service.publish('fileNumber', new User()); @@ -159,9 +167,10 @@ describe('NoticeOfIntentSubmissionDraftService', () => { 1, ); expect(mockSubmissionRepo.save.mock.calls[0][0].isDraft).toEqual(false); - // expect( - // mockGenerateSubmissionDocumentService.generateUpdate, - // ).toHaveBeenCalledTimes(1); + expect( + mockGenerateSubmissionDocumentService.generateUpdate, + ).toHaveBeenCalledTimes(1); + expect(mockNoiService.updateApplicant).toHaveBeenCalledTimes(1); }); it('should call through for mapToDto', async () => { diff --git a/services/apps/alcs/src/portal/notice-of-intent-submission-draft/notice-of-intent-submission-draft.service.ts b/services/apps/alcs/src/portal/notice-of-intent-submission-draft/notice-of-intent-submission-draft.service.ts index 157cc03161..97b4cf0500 100644 --- a/services/apps/alcs/src/portal/notice-of-intent-submission-draft/notice-of-intent-submission-draft.service.ts +++ b/services/apps/alcs/src/portal/notice-of-intent-submission-draft/notice-of-intent-submission-draft.service.ts @@ -3,6 +3,7 @@ import { BadRequestException, Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { NoticeOfIntentSubmissionStatusService } from '../../alcs/notice-of-intent/notice-of-intent-submission-status/notice-of-intent-submission-status.service'; +import { NoticeOfIntentService } from '../../alcs/notice-of-intent/notice-of-intent.service'; import { User } from '../../user/user.entity'; import { NoticeOfIntentOwnerService } from '../notice-of-intent-submission/notice-of-intent-owner/notice-of-intent-owner.service'; import { NoticeOfIntentParcelUpdateDto } from '../notice-of-intent-submission/notice-of-intent-parcel/notice-of-intent-parcel.dto'; @@ -23,6 +24,7 @@ export class NoticeOfIntentSubmissionDraftService { private noticeOfIntentOwnerService: NoticeOfIntentOwnerService, private noticeOfIntentSubmissionStatusService: NoticeOfIntentSubmissionStatusService, private generateNoiSubmissionDocumentService: GenerateNoiSubmissionDocumentService, + private noticeOfIntentService: NoticeOfIntentService, ) {} async getOrCreateDraft(fileNumber: string, user: User) { @@ -224,6 +226,11 @@ export class NoticeOfIntentSubmissionDraftService { user, ); + await this.noticeOfIntentService.updateApplicant( + fileNumber, + draft.applicant!, + ); + this.logger.debug(`Published Draft for file number ${fileNumber}`); } From 46c649cae8bc779576166af1a87d35f8c88ef4dc Mon Sep 17 00:00:00 2001 From: Tristan Slater <1631008+trslater@users.noreply.github.com> Date: Thu, 21 Mar 2024 12:45:34 -0700 Subject: [PATCH 039/153] Fix useless scrollbar showing up --- .../application-details/naru-details/naru-details.component.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/alcs-frontend/src/app/features/application/applicant-info/application-details/naru-details/naru-details.component.scss b/alcs-frontend/src/app/features/application/applicant-info/application-details/naru-details/naru-details.component.scss index b2b6b90127..f53378423d 100644 --- a/alcs-frontend/src/app/features/application/applicant-info/application-details/naru-details/naru-details.component.scss +++ b/alcs-frontend/src/app/features/application/applicant-info/application-details/naru-details/naru-details.component.scss @@ -1,7 +1,6 @@ .soil-table { display: grid; grid-template-columns: max-content max-content; - overflow-x: auto; grid-column-gap: 36px; grid-row-gap: 12px; } From 8a6187e97668ae2c8544371ab7a9187060432901 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Thu, 21 Mar 2024 15:08:03 -0700 Subject: [PATCH 040/153] Allow user to delete OATS Documents --- .../documents/documents.component.html | 8 +++++-- .../documents/documents.component.html | 6 +++++- .../documents/documents.component.html | 21 +++++++++++++++---- .../documents/documents.component.html | 6 +++++- .../src/app/shared/document/document.dto.ts | 2 ++ 5 files changed, 35 insertions(+), 8 deletions(-) diff --git a/alcs-frontend/src/app/features/application/documents/documents.component.html b/alcs-frontend/src/app/features/application/documents/documents.component.html index 6a55826bb8..c0f78f79fc 100644 --- a/alcs-frontend/src/app/features/application/documents/documents.component.html +++ b/alcs-frontend/src/app/features/application/documents/documents.component.html @@ -47,7 +47,7 @@ <h3>Documents</h3> </ng-container> <ng-container *ngIf="element.visibilityFlags.includes('G')"> <span matTooltip="L/FNG">G<span *ngIf="hiddenFromPortal">*</span></span> - <ng-container *ngIf="element.visibilityFlags.includes('P')">, </ng-container> + <ng-container *ngIf="element.visibilityFlags.includes('P')">,</ng-container> </ng-container> <ng-container *ngIf="element.visibilityFlags.includes('P')"> <span matTooltip="Public">P<span *ngIf="hiddenFromPortal || !hasBeenReceived">*</span></span> @@ -69,7 +69,11 @@ <h3>Documents</h3> <button mat-icon-button (click)="onEditFile(element)"> <mat-icon>edit</mat-icon> </button> - <button *ngIf="element.system === DOCUMENT_SYSTEM.ALCS" mat-icon-button (click)="onDeleteFile(element)"> + <button + *ngIf="element.system === DOCUMENT_SYSTEM.ALCS || element.system === DOCUMENT_SYSTEM.OATS" + mat-icon-button + (click)="onDeleteFile(element)" + > <mat-icon color="warn">delete</mat-icon> </button> </td> diff --git a/alcs-frontend/src/app/features/notice-of-intent/documents/documents.component.html b/alcs-frontend/src/app/features/notice-of-intent/documents/documents.component.html index 6a365c8651..465b127163 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/documents/documents.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/documents/documents.component.html @@ -56,7 +56,11 @@ <h3>Documents</h3> <button mat-icon-button (click)="onEditFile(element)"> <mat-icon>edit</mat-icon> </button> - <button *ngIf="element.system === DOCUMENT_SYSTEM.ALCS" mat-icon-button (click)="onDeleteFile(element)"> + <button + *ngIf="element.system === DOCUMENT_SYSTEM.ALCS || element.system === DOCUMENT_SYSTEM.OATS" + mat-icon-button + (click)="onDeleteFile(element)" + > <mat-icon color="warn">delete</mat-icon> </button> </td> diff --git a/alcs-frontend/src/app/features/notification/documents/documents.component.html b/alcs-frontend/src/app/features/notification/documents/documents.component.html index 5d56cc6c58..5dea30e042 100644 --- a/alcs-frontend/src/app/features/notification/documents/documents.component.html +++ b/alcs-frontend/src/app/features/notification/documents/documents.component.html @@ -27,15 +27,24 @@ <h3>Documents</h3> <td mat-cell *matCellDef="let element"> <ng-container *ngIf="element.visibilityFlags.includes('A')"> <span matTooltip="Applicant">A</span> - <ng-container *ngIf="element.visibilityFlags.includes('C') || element.visibilityFlags.includes('G') || element.visibilityFlags.includes('P')">, </ng-container> + <ng-container + *ngIf=" + element.visibilityFlags.includes('C') || + element.visibilityFlags.includes('G') || + element.visibilityFlags.includes('P') + " + >, + </ng-container> </ng-container> <ng-container *ngIf="element.visibilityFlags.includes('C')"> <span matTooltip="Commissioner">C</span> - <ng-container *ngIf="element.visibilityFlags.includes('G') || element.visibilityFlags.includes('P')">, </ng-container> + <ng-container *ngIf="element.visibilityFlags.includes('G') || element.visibilityFlags.includes('P')" + >, + </ng-container> </ng-container> <ng-container *ngIf="element.visibilityFlags.includes('G')"> <span matTooltip="L/FNG">G</span> - <ng-container *ngIf="element.visibilityFlags.includes('P')">, </ng-container> + <ng-container *ngIf="element.visibilityFlags.includes('P')">,</ng-container> </ng-container> <ng-container *ngIf="element.visibilityFlags.includes('P')"> <span matTooltip="Public">P</span> @@ -57,7 +66,11 @@ <h3>Documents</h3> <button mat-icon-button (click)="onEditFile(element)"> <mat-icon>edit</mat-icon> </button> - <button *ngIf="element.system === DOCUMENT_SYSTEM.ALCS" mat-icon-button (click)="onDeleteFile(element)"> + <button + *ngIf="element.system === DOCUMENT_SYSTEM.ALCS || element.system === DOCUMENT_SYSTEM.OATS" + mat-icon-button + (click)="onDeleteFile(element)" + > <mat-icon color="warn">delete</mat-icon> </button> </td> diff --git a/alcs-frontend/src/app/features/planning-review/documents/documents.component.html b/alcs-frontend/src/app/features/planning-review/documents/documents.component.html index 6180359943..6d0956f57a 100644 --- a/alcs-frontend/src/app/features/planning-review/documents/documents.component.html +++ b/alcs-frontend/src/app/features/planning-review/documents/documents.component.html @@ -48,7 +48,11 @@ <h3>Documents</h3> <button mat-icon-button (click)="onEditFile(element)"> <mat-icon>edit</mat-icon> </button> - <button *ngIf="element.system === DOCUMENT_SYSTEM.ALCS" mat-icon-button (click)="onDeleteFile(element)"> + <button + *ngIf="element.system === DOCUMENT_SYSTEM.ALCS || element.system === DOCUMENT_SYSTEM.OATS" + mat-icon-button + (click)="onDeleteFile(element)" + > <mat-icon color="warn">delete</mat-icon> </button> </td> diff --git a/alcs-frontend/src/app/shared/document/document.dto.ts b/alcs-frontend/src/app/shared/document/document.dto.ts index ed87359c31..7dad0305fb 100644 --- a/alcs-frontend/src/app/shared/document/document.dto.ts +++ b/alcs-frontend/src/app/shared/document/document.dto.ts @@ -47,6 +47,8 @@ export enum DOCUMENT_SOURCE { export enum DOCUMENT_SYSTEM { ALCS = 'ALCS', PORTAL = 'Portal', + OATS = 'OATS', + OATS_P = 'OATS_P', } export interface DocumentTypeDto extends BaseCodeDto { From 5ed3d263f2d67662ea1a254ab7cc2f702e6d01cd Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Thu, 21 Mar 2024 15:40:31 -0700 Subject: [PATCH 041/153] Add expiry date to SUBD Decision Components --- .../decision-component/subd/subd.component.html | 5 +++++ .../decision-component/subd/subd.component.ts | 2 +- .../decision-component.component.html | 3 ++- .../decision-component.component.ts | 5 +++++ .../subd-input/subd-input.component.html | 17 ++--------------- .../application-decision-v2.dto.ts | 1 + 6 files changed, 16 insertions(+), 17 deletions(-) diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-component/subd/subd.component.html b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-component/subd/subd.component.html index 9078778b4d..2e303d5f5d 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-component/subd/subd.component.html +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-component/subd/subd.component.html @@ -1,4 +1,9 @@ <ng-content></ng-content> +<div> + <div class="subheading2">Expiry Date</div> + {{ component.expiryDate | date }} + <app-no-data *ngIf="component.expiryDate === null || component.expiryDate === undefined"></app-no-data> +</div> <div> <div class="subheading2">Total Number of Lots Approved</div> {{ component.lots?.length }} diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-component/subd/subd.component.ts b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-component/subd/subd.component.ts index ab5cdd4253..2bbb91540d 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-component/subd/subd.component.ts +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-component/subd/subd.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { ApplicationDecisionComponentLotService } from '../../../../../../services/application/decision/application-decision-v2/application-decision-component-lot/application-decision-component-lot.service'; import { ApplicationDecisionComponentDto, diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-components/decision-component/decision-component.component.html b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-components/decision-component/decision-component.component.html index 63d3ed6e30..58ff050c24 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-components/decision-component/decision-component.component.html +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-components/decision-component/decision-component.component.html @@ -11,7 +11,8 @@ <h5>{{ data.applicationDecisionComponentType?.label }}</h5> <div *ngIf=" data.applicationDecisionComponentTypeCode === COMPONENT_TYPE.TURP || - data.applicationDecisionComponentTypeCode === COMPONENT_TYPE.COVE + data.applicationDecisionComponentTypeCode === COMPONENT_TYPE.COVE || + data.applicationDecisionComponentTypeCode === COMPONENT_TYPE.SUBD " class="row-no-flex" > diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-components/decision-component/decision-component.component.ts b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-components/decision-component/decision-component.component.ts index 0c4463001c..e22be0e5df 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-components/decision-component/decision-component.component.ts +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-components/decision-component/decision-component.component.ts @@ -275,7 +275,11 @@ export class DecisionComponentComponent implements OnInit { private patchSubdFields() { this.form.addControl('subdApprovedLots', this.subdApprovedLots); + this.form.addControl('expiryDate', this.expiryDate); + const lots = this.data.lots?.sort((a, b) => a.index - b.index) ?? null; + + this.expiryDate.setValue(this.data.expiryDate ? new Date(this.data.expiryDate) : null); this.subdApprovedLots.setValue(lots); } @@ -352,6 +356,7 @@ export class DecisionComponentComponent implements OnInit { const update = this.subdApprovedLots.value?.map((e) => ({ ...e }) as ProposedDecisionLotDto); return { lots: update ?? undefined, + expiryDate: this.expiryDate.value ? formatDateForApi(this.expiryDate.value) : null, }; } diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-components/decision-component/subd-input/subd-input.component.html b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-components/decision-component/subd-input/subd-input.component.html index 2bac9ad60e..9c2e3ef34b 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-components/decision-component/subd-input/subd-input.component.html +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-components/decision-component/subd-input/subd-input.component.html @@ -1,18 +1,5 @@ <form [formGroup]="form" class="subd-component-form"> - <div class="row-no-flex"> - <mat-form-field appearance="outline"> - <mat-label>ALR Area Impacted (ha)</mat-label> - <input - matInput - min="0.01" - mask="separator.5" - thousandSeparator="," - separatorLimit="9999999999" - formControlName="alrArea" - required - /> - </mat-form-field> + <div> + <app-lots-table formControlName="subdApprovedLots"></app-lots-table> </div> - - <app-lots-table formControlName="subdApprovedLots"></app-lots-table> </form> diff --git a/alcs-frontend/src/app/services/application/decision/application-decision-v2/application-decision-v2.dto.ts b/alcs-frontend/src/app/services/application/decision/application-decision-v2/application-decision-v2.dto.ts index dd1c944915..3516c2cc01 100644 --- a/alcs-frontend/src/app/services/application/decision/application-decision-v2/application-decision-v2.dto.ts +++ b/alcs-frontend/src/app/services/application/decision/application-decision-v2/application-decision-v2.dto.ts @@ -165,6 +165,7 @@ export interface RosoDecisionComponentDto { export interface SubdDecisionComponentDto { // subdApprovedLots?: ProposedDecisionLotDto[]; lots?: ProposedDecisionLotDto[]; + expiryDate?: number | null; } export interface InclExclDecisionComponentDto { From 459dd917ce1b1330a27127d6e46236b2e2838856 Mon Sep 17 00:00:00 2001 From: Liam Stoddard <lstodd@protonmail.com> Date: Thu, 21 Mar 2024 16:39:31 -0700 Subject: [PATCH 042/153] planning review initial --- bin/migrate-oats-data/common/__init__.py | 4 +- .../common/alcs_planning_review_enum.py | 15 ++ .../oats_to_alcs_planning_review_type.py | 17 +++ .../menu/command_parser/__init__.py | 1 + .../planning_review_command_parser.py | 19 +++ bin/migrate-oats-data/menu/menu.py | 6 + .../menu/post_launch_commands/__init__.py | 1 + .../post_launch_commands/planning_reviews.py | 28 ++++ bin/migrate-oats-data/migrate.py | 6 + bin/migrate-oats-data/migrate_middle.py | 59 ++++++++ .../planning_review/__init__.py | 1 + .../migrate_planning_review.py | 14 ++ .../planning_review/planning_review_base.py | 103 +++++++++++++ .../planning_review_base_update.py | 140 ++++++++++++++++++ .../sql/planning_review_base_insert.sql | 53 +++---- .../sql/planning_review_base_insert_count.sql | 4 + .../sql/planning_review_base_update.sql | 8 + .../sql/planning_review_base_update_count.sql | 4 + 18 files changed, 446 insertions(+), 37 deletions(-) create mode 100644 bin/migrate-oats-data/common/alcs_planning_review_enum.py create mode 100644 bin/migrate-oats-data/common/oats_to_alcs_planning_review_type.py create mode 100644 bin/migrate-oats-data/menu/command_parser/planning_review_command_parser.py create mode 100644 bin/migrate-oats-data/menu/post_launch_commands/planning_reviews.py create mode 100644 bin/migrate-oats-data/migrate_middle.py create mode 100644 bin/migrate-oats-data/planning_review/__init__.py create mode 100644 bin/migrate-oats-data/planning_review/migrate_planning_review.py create mode 100644 bin/migrate-oats-data/planning_review/planning_review_base_update.py create mode 100644 bin/migrate-oats-data/planning_review/sql/planning_review_base_insert_count.sql create mode 100644 bin/migrate-oats-data/planning_review/sql/planning_review_base_update.sql create mode 100644 bin/migrate-oats-data/planning_review/sql/planning_review_base_update_count.sql diff --git a/bin/migrate-oats-data/common/__init__.py b/bin/migrate-oats-data/common/__init__.py index 4831296e43..d26e68ab07 100644 --- a/bin/migrate-oats-data/common/__init__.py +++ b/bin/migrate-oats-data/common/__init__.py @@ -12,4 +12,6 @@ from .oats_decision_enum import * from .oats_legislation_to_alcs_applicant_enum import * from .oats_to_alcs_nfu_type_enum import * -from .oats_to_alcs_document_source_code import * \ No newline at end of file +from .oats_to_alcs_document_source_code import * +from .oats_to_alcs_planning_review_type import * +from .alcs_planning_review_enum import * diff --git a/bin/migrate-oats-data/common/alcs_planning_review_enum.py b/bin/migrate-oats-data/common/alcs_planning_review_enum.py new file mode 100644 index 0000000000..790f47eb14 --- /dev/null +++ b/bin/migrate-oats-data/common/alcs_planning_review_enum.py @@ -0,0 +1,15 @@ +from enum import Enum + + +class AlcsPlanningReviewTypes(Enum): + AAPP = "Agricultural Area Plan" + MISC = "Misc Studies and Projects" + BAPP = "L/FNG Boundary Adjustment" + ALRB = "ALR Boundary" + RGSP = "Regional Growth Strategy" + CLUP = "Crown Land Use Plan" + OCPP = "Official Community Plan" + TPPP = "Transportation Plan" + UEPP = "Utility/Energy Planning" + ZBPP = "Zoning Bylaw" + PARK = "Parks Planning" diff --git a/bin/migrate-oats-data/common/oats_to_alcs_planning_review_type.py b/bin/migrate-oats-data/common/oats_to_alcs_planning_review_type.py new file mode 100644 index 0000000000..bbdcfeeffe --- /dev/null +++ b/bin/migrate-oats-data/common/oats_to_alcs_planning_review_type.py @@ -0,0 +1,17 @@ +from enum import Enum +from .alcs_planning_review_enum import AlcsPlanningReviewTypes + + +class OatsToAlcsPlanningReviewType(Enum): + OCPRV = "OCPP" + ZONERV = "ZBPP" + LGBOUND = "BAPP" + TRANSRV = "TPPP" + PARKS = "PARK" + REGGRO = "RGSP" + CROWN = "CLUP" + ALRBOUND = "ALRB" + MISC = "MISC" + UTIL = "UEPP" + AAP = "AAPP" + APC = "MISC" diff --git a/bin/migrate-oats-data/menu/command_parser/__init__.py b/bin/migrate-oats-data/menu/command_parser/__init__.py index 38126e29f6..d742042e93 100644 --- a/bin/migrate-oats-data/menu/command_parser/__init__.py +++ b/bin/migrate-oats-data/menu/command_parser/__init__.py @@ -3,3 +3,4 @@ from .document_command_parser import * from .user_command_parser import * from .srw_command_parser import * +from .planning_review_command_parser import * diff --git a/bin/migrate-oats-data/menu/command_parser/planning_review_command_parser.py b/bin/migrate-oats-data/menu/command_parser/planning_review_command_parser.py new file mode 100644 index 0000000000..d69313c28e --- /dev/null +++ b/bin/migrate-oats-data/menu/command_parser/planning_review_command_parser.py @@ -0,0 +1,19 @@ +def planning_review_import_command_parser(import_batch_size, subparsers): + srw_import_command = subparsers.add_parser( + "pr-import", + help=f"Import Planning Reviews with specified batch size: (default: {import_batch_size})", + ) + srw_import_command.add_argument( + "--batch-size", + type=int, + default=import_batch_size, + metavar="", + help=f"batch size (default: {import_batch_size})", + ) + + +def planning_review_clean_command_parser(subparsers): + subparsers.add_parser( + "pr-clean", + help="Clean Planning Reviews imported data:", + ) diff --git a/bin/migrate-oats-data/menu/menu.py b/bin/migrate-oats-data/menu/menu.py index 9d0a14964e..97030dfa53 100644 --- a/bin/migrate-oats-data/menu/menu.py +++ b/bin/migrate-oats-data/menu/menu.py @@ -22,6 +22,10 @@ srw_import_command_parser, srw_clean_command_parser, ) +from .command_parser.planning_review_command_parser import ( + planning_review_clean_command_parser, + planning_review_import_command_parser, +) import_batch_size = BATCH_UPLOAD_SIZE @@ -58,6 +62,8 @@ def setup_menu_args_parser(import_batch_size): user_clean_command_parser(subparsers) srw_import_command_parser(import_batch_size, subparsers) srw_clean_command_parser(subparsers) + planning_review_import_command_parser(import_batch_size, subparsers) + planning_review_clean_command_parser(subparsers) subparsers.add_parser("clean", help="Clean all imported data") subparsers.add_parser("obfuscate", help="Obfuscate PROD data") diff --git a/bin/migrate-oats-data/menu/post_launch_commands/__init__.py b/bin/migrate-oats-data/menu/post_launch_commands/__init__.py index 238f74542c..615326d2de 100644 --- a/bin/migrate-oats-data/menu/post_launch_commands/__init__.py +++ b/bin/migrate-oats-data/menu/post_launch_commands/__init__.py @@ -4,3 +4,4 @@ from .notice_of_intents import * from .srws import * from .documents import * +from .planning_reviews import * diff --git a/bin/migrate-oats-data/menu/post_launch_commands/planning_reviews.py b/bin/migrate-oats-data/menu/post_launch_commands/planning_reviews.py new file mode 100644 index 0000000000..69a7a5e46e --- /dev/null +++ b/bin/migrate-oats-data/menu/post_launch_commands/planning_reviews.py @@ -0,0 +1,28 @@ +from planning_review.migrate_planning_review import ( + process_planning_review, + clean_planning_review, +) +from common import setup_and_get_logger + +logger = setup_and_get_logger("plannng_review_import_post_launch") + + +def planning_review_import(console, args): + logger.debug("Beginning OATS -> ALCS Planning Review import process") + with console.status( + "[bold green]Planning Review import (notification table update in ALCS)...\n" + ) as status: + if args.batch_size: + import_batch_size = args.batch_size + + logger.debug( + f"Processing Planning Review import in batch size = {import_batch_size}" + ) + + process_planning_review(batch_size=import_batch_size) + + +def planning_review_clean(console): + logger.debug("Beginning OATS -> ALCS Planning Review import clean process") + with console.status("[bold green]SRW clean import...\n") as status: + clean_planning_review() diff --git a/bin/migrate-oats-data/migrate.py b/bin/migrate-oats-data/migrate.py index 0691d069ba..ae357e2640 100644 --- a/bin/migrate-oats-data/migrate.py +++ b/bin/migrate-oats-data/migrate.py @@ -16,6 +16,8 @@ srw_clean, document_import, document_clean, + planning_review_clean, + planning_review_import, ) from db import connection_pool from common import BATCH_UPLOAD_SIZE, setup_and_get_logger @@ -53,6 +55,10 @@ document_import(console, args) case "document-clean": document_clean(console) + case "pr-import": + planning_review_import(console, args) + case "pr-clean": + planning_review_clean(console) finally: if connection_pool: diff --git a/bin/migrate-oats-data/migrate_middle.py b/bin/migrate-oats-data/migrate_middle.py new file mode 100644 index 0000000000..0691d069ba --- /dev/null +++ b/bin/migrate-oats-data/migrate_middle.py @@ -0,0 +1,59 @@ +import logging +from rich.logging import RichHandler +from rich.traceback import install +from rich.console import Console +from logging.handlers import RotatingFileHandler + +from menu import setup_menu_args_parser +from menu.post_launch_commands import ( + import_all, + clean_all, + application_import, + application_clean, + notice_of_intent_import, + notice_of_intent_clean, + srw_import, + srw_clean, + document_import, + document_clean, +) +from db import connection_pool +from common import BATCH_UPLOAD_SIZE, setup_and_get_logger + + +import_batch_size = BATCH_UPLOAD_SIZE + + +if __name__ == "__main__": + args = setup_menu_args_parser(import_batch_size) + + logger = setup_and_get_logger("migrate") + console = Console() + + try: + # Call function corresponding to selected action using match statement + match args.command: + case "import": + import_all(console, args) + case "clean": + clean_all(console, args) + case "noi-import": + notice_of_intent_import(console, args) + case "noi-clean": + notice_of_intent_clean(console) + case "application-import": + application_import(console, args) + case "application-clean": + application_clean(console) + case "srw-import": + srw_import(console, args) + case "srw-clean": + srw_clean(console) + case "document-import": + document_import(console, args) + case "document-clean": + document_clean(console) + + finally: + if connection_pool: + connection_pool.closeall() diff --git a/bin/migrate-oats-data/planning_review/__init__.py b/bin/migrate-oats-data/planning_review/__init__.py new file mode 100644 index 0000000000..c5e9746af0 --- /dev/null +++ b/bin/migrate-oats-data/planning_review/__init__.py @@ -0,0 +1 @@ +from .migrate_planning_review import * \ No newline at end of file diff --git a/bin/migrate-oats-data/planning_review/migrate_planning_review.py b/bin/migrate-oats-data/planning_review/migrate_planning_review.py new file mode 100644 index 0000000000..fc695942c1 --- /dev/null +++ b/bin/migrate-oats-data/planning_review/migrate_planning_review.py @@ -0,0 +1,14 @@ +from .planning_review_base import ( + init_planning_review_base, + clean_initial_planning_review, +) +from .planning_review_base_update import update_planning_review_base_fields + + +def process_planning_review(batch_size): + init_planning_review_base(batch_size) + update_planning_review_base_fields(batch_size) + + +def clean_planning_review(): + clean_initial_planning_review() diff --git a/bin/migrate-oats-data/planning_review/planning_review_base.py b/bin/migrate-oats-data/planning_review/planning_review_base.py index e69de29bb2..1aea4f1a0f 100644 --- a/bin/migrate-oats-data/planning_review/planning_review_base.py +++ b/bin/migrate-oats-data/planning_review/planning_review_base.py @@ -0,0 +1,103 @@ +from db import inject_conn_pool +from common import ( + setup_and_get_logger, + BATCH_UPLOAD_SIZE, +) + +etl_name = "init_planning_review_base" +logger = setup_and_get_logger(etl_name) + + +@inject_conn_pool +def init_planning_review_base(conn=None, batch_size=BATCH_UPLOAD_SIZE): + """ + function uses a decorator pattern @inject_conn_pool to inject a database connection pool to the function. + It fetches the total count of Planning Reviews to import and prints it to the console. + Then, it fetches the PLanning reviews to insert in batches using planning review IDs / file_number, constructs an insert query, and processes them. + """ + logger.info(f"Start {etl_name}") + with conn.cursor() as cursor: + + with open( + "planning_review/sql/planning_review_base_insert_count.sql", + "r", + encoding="utf-8", + ) as sql_file: + count_query = sql_file.read() + cursor.execute(count_query) + count_total = cursor.fetchone()[0] + logger.info(f"Planning Reviews to insert: {count_total}") + + failed_inserts_count = 0 + successful_inserts_count = 0 + last_row_id = 0 + + with open( + "planning_review/sql/planning_review_base_insert.sql", "r", encoding="utf-8" + ) as sql_file: + application_sql = sql_file.read() + while True: + cursor.execute( + f"""{application_sql} + WHERE oa.planning_review_id > {last_row_id} ORDER by oa.planning_review_id; + """ + ) + + rows = cursor.fetchmany(batch_size) + if not rows: + break + try: + rows_to_be_inserted_count = len(rows) + + insert_query = _compile_insert_query(rows_to_be_inserted_count) + cursor.execute(insert_query, rows) + conn.commit() + + last_row_id = rows[-1][0] + successful_inserts_count = ( + successful_inserts_count + rows_to_be_inserted_count + ) + + logger.debug( + f"retrieved/inserted items count: {rows_to_be_inserted_count}; total successfully inserted/updated rows so far {successful_inserts_count}; last inserted application_id: {last_row_id}" + ) + except Exception as err: + logger.exception(err) + conn.rollback() + failed_inserts_count = count_total - successful_inserts_count + last_row_id = int(last_row_id) + 1 + + logger.info(f"Total amount of successful inserts: {successful_inserts_count}") + logger.info(f"Total failed inserts: {failed_inserts_count}") + + +@inject_conn_pool +def clean_initial_planning_review(conn=None): + logger.info("Start Planning Review cleaning") + with conn.cursor() as cursor: + cursor.execute( + "DELETE FROM alcs.planning_review a WHERE a.audit_created_by = 'oats_etl' and a.audit_updated_by is NULL" + ) + logger.info(f"Deleted items count = {cursor.rowcount}") + + conn.commit() + + +def _compile_insert_query(number_of_rows_to_insert): + """ + function takes the number of rows to insert and generates an SQL insert statement with upserts using the ON CONFLICT clause + """ + + applications_to_insert = ",".join(["%s"] * number_of_rows_to_insert) + + return f""" + INSERT INTO alcs.planning_review (file_number, region_code, type_code, local_government_uuid, document_name, audit_created_by) + VALUES{applications_to_insert} + ON CONFLICT (file_number) DO UPDATE SET + region_code = COALESCE(EXCLUDED.region_code, alcs.planning_review.region_code), + type_code = EXCLUDED.type_code, + local_government_uuid = COALESCE(EXCLUDED.local_government_uuid, alcs.planning_review.local_government_uuid), + document_name = EXCLUDED.document_name, + audit_created_by = EXCLUDED.audit_created_by + + """ diff --git a/bin/migrate-oats-data/planning_review/planning_review_base_update.py b/bin/migrate-oats-data/planning_review/planning_review_base_update.py new file mode 100644 index 0000000000..16eac2842e --- /dev/null +++ b/bin/migrate-oats-data/planning_review/planning_review_base_update.py @@ -0,0 +1,140 @@ +from common import ( + BATCH_UPLOAD_SIZE, + setup_and_get_logger, + add_timezone_and_keep_date_part, + OatsToAlcsPlanningReviewType, +) +from db import inject_conn_pool +from psycopg2.extras import RealDictCursor, execute_batch + +etl_name = "update_planning_review_base_fields" +logger = setup_and_get_logger(etl_name) + + +@inject_conn_pool +def update_planning_review_base_fields(conn=None, batch_size=BATCH_UPLOAD_SIZE): + """ + This function is responsible for populating date_submitted_to_alc in alcs.planning_review in ALCS. + + Args: + conn (psycopg2.extensions.connection): PostgreSQL database connection. Provided by the decorator. + batch_size (int): The number of items to process at once. Defaults to BATCH_UPLOAD_SIZE. + """ + + logger.info("Start update planning review base fields") + with conn.cursor(cursor_factory=RealDictCursor) as cursor: + with open( + "planning_review/sql/planning_review_base_update_count.sql", + "r", + encoding="utf-8", + ) as sql_file: + count_query = sql_file.read() + cursor.execute(count_query) + count_total = dict(cursor.fetchone())["count"] + logger.info(f"Total Planning Review data to update: {count_total}") + + failed_inserts = 0 + successful_updates_count = 0 + last_planning_review_id = 0 + + with open( + "planning_review/sql/planning_review_base_update.sql", + "r", + encoding="utf-8", + ) as sql_file: + query = sql_file.read() + while True: + cursor.execute( + f""" + {query} + WHERE ops.planning_review_id > {last_planning_review_id} ORDER BY ops.planning_review_id; + """ + ) + + rows = cursor.fetchmany(batch_size) + + if not rows: + break + try: + updated_data = _update_base_fields(conn, batch_size, cursor, rows) + + successful_updates_count = successful_updates_count + len( + updated_data + ) + last_planning_review_id = dict(updated_data[-1])[ + "planning_review_id" + ] + + logger.debug( + f"Retrieved/updated items count: {len(updated_data)}; total successfully updated planning_review so far {successful_updates_count}; last updated planning_review_id: {last_planning_review_id}" + ) + except Exception as err: + # this is NOT going to be caused by actual data update failure. This code is only executed when the code error appears or connection to DB is lost + logger.exception(err) + conn.rollback() + failed_inserts = count_total - successful_updates_count + last_planning_review_id = last_planning_review_id + 1 + + logger.info( + f"Finished {etl_name}: total amount of successful updates {successful_updates_count}, total failed updates {failed_inserts}" + ) + + +def _update_base_fields(conn, batch_size, cursor, rows): + parsed_data_list = _prepare_oats_planning_review_data(rows) + + execute_batch( + cursor, + _rx_items_query, + parsed_data_list, + page_size=batch_size, + ) + + conn.commit() + return parsed_data_list + + +_rx_items_query = """ + UPDATE alcs.planning_review + SET open = %(open_indicator)s, + type_code = %(alcs_planning_review_code)s, + legacy_id = %(legacy_number)s + WHERE alcs.planning_review.file_number = %(planning_review_id)s::text +""" + + +def _prepare_oats_planning_review_data(row_data_list): + mapped_data_list = [] + for row in row_data_list: + mapped_data_list.append( + { + "planning_review_id": row["planning_review_id"], + "open_indicator": _map_is_open(row), + "alcs_planning_review_code": _map_planning_code(row), + "legacy_number": row["legacy_planning_review_nbr"], + } + ) + + return mapped_data_list + + +def _map_is_open(data): + oats_val = data.get("open_ind") + if oats_val == "Y": + return True + elif oats_val == "N": + return False + else: + return True + + +def _map_planning_code(data): + oats_code = data.get("planning_review_code") + try: + return OatsToAlcsPlanningReviewType[oats_code].value + except KeyError: + file_number = data.get("planning_review_id") + logger.info( + f"Key Error for{file_number}, no match to {oats_code} in ALCS, Override to MISC" + ) + return "MISC" diff --git a/bin/migrate-oats-data/planning_review/sql/planning_review_base_insert.sql b/bin/migrate-oats-data/planning_review/sql/planning_review_base_insert.sql index d28d167953..ada73c7588 100644 --- a/bin/migrate-oats-data/planning_review/sql/planning_review_base_insert.sql +++ b/bin/migrate-oats-data/planning_review/sql/planning_review_base_insert.sql @@ -1,14 +1,14 @@ -- Step 1: get local gov application name & match to uuid WITH oats_gov AS ( - SELECT oaap.alr_application_id AS application_id, + SELECT oaap.planning_review_id AS review_id, oo.organization_name AS oats_gov_name - FROM oats.oats_alr_application_parties oaap + FROM oats.oats_planning_reviews oaap JOIN oats.oats_person_organizations opo ON oaap.person_organization_id = opo.person_organization_id JOIN oats.oats_organizations oo ON opo.organization_id = oo.organization_id WHERE oo.organization_type_cd IN ('MUNI', 'FN', 'RD', 'RM') ), alcs_gov AS ( - SELECT oats_gov.application_id AS application_id, + SELECT oats_gov.review_id AS review_id, alg.uuid AS gov_uuid FROM oats_gov JOIN alcs.local_government alg on ( @@ -62,55 +62,36 @@ alcs_gov AS ( WHEN oats_gov.oats_gov_name LIKE 'North Coast%' THEN 'North Coast Regional District' WHEN oats_gov.oats_gov_name LIKE 'Strathcona%' THEN 'Strathcona Regional District' WHEN oats_gov.oats_gov_name LIKE 'Mount Waddington%' THEN 'Mount Waddington Regional District' + WHEN oats_gov.oats_gov_name LIKE 'VILLAGE OF CHASE' THEN 'Village of Chase' + WHEN oats_gov.oats_gov_name LIKE 'Stikine Region' THEN'Kitimat Stikine Regional District' ELSE oats_gov.oats_gov_name END - ) = alg."name" + ) = TRIM(alg."name") ), -- Step 2: Perform a lookup to retrieve the region code for each application ID panel_lookup AS ( - SELECT DISTINCT oaap.alr_application_id AS application_id, + SELECT DISTINCT oaap.planning_review_id AS review_id, CASE WHEN oo2.parent_organization_id IS NULL THEN oo2.organization_name WHEN oo3.parent_organization_id IS NULL THEN oo3.organization_name ELSE 'NONE' END AS panel_region - FROM oats.oats_alr_application_parties oaap + FROM oats.oats_planning_reviews oaap JOIN oats.oats_person_organizations opo ON oaap.person_organization_id = opo.person_organization_id JOIN oats.oats_organizations oo ON opo.organization_id = oo.organization_id LEFT JOIN oats.oats_organizations oo2 ON oo.parent_organization_id = oo2.organization_id LEFT JOIN oats.oats_organizations oo3 ON oo2.parent_organization_id = oo3.organization_id WHERE oo2.organization_type_cd = 'PANEL' OR oo3.organization_type_cd = 'PANEL' -), --- Step 3: Perform lookup to retrieve type code -application_type_lookup AS ( - SELECT oaac.alr_application_id AS application_id, - oacc."description" AS "description", - oaac.alr_change_code AS code - FROM oats.oats_alr_appl_components AS oaac - JOIN oats.oats_alr_change_codes oacc ON oaac.alr_change_code = oacc.alr_change_code - LEFT JOIN oats.alcs_etl_application_exclude aee ON oaac.alr_appl_component_id = aee.component_id - WHERE aee.component_id IS NULL -) -- Step 5: Insert new records into the alcs_applications table -SELECT oa.alr_application_id::text AS file_number, - atl.code AS type_code, - 'Unknown' as applicant, +)--, +-- Step 3: Insert new records into the alcs_applications table +SELECT oa.planning_review_id::text AS file_number, ar.code AS region_code, + 'AAPP' AS type_code, alcs_gov.gov_uuid AS local_government_uuid, - 'oats_etl', - CASE - WHEN oa.proposal_background_desc IS NOT NULL - AND length(oa.proposal_background_desc) > 10 THEN oa.proposal_summary_desc || '. Background: ' || oa.proposal_background_desc - ELSE oa.proposal_summary_desc - END AS "summary", - oa.staff_comment_observations, - oa.submitted_to_alc_date -FROM oats.oats_alr_applications AS oa - JOIN oats.alcs_etl_srw_duplicate AS ae ON oa.alr_application_id = ae.application_id - AND ae.duplicated IS false - LEFT JOIN panel_lookup ON oa.alr_application_id = panel_lookup.application_id - LEFT JOIN application_type_lookup AS atl ON oa.alr_application_id = atl.application_id + oa.local_gov_document_name, + 'oats_etl' AS audit_created_by +FROM oats.oats_planning_reviews AS oa + LEFT JOIN panel_lookup ON oa.planning_review_id = panel_lookup.review_id LEFT JOIN alcs.application_region ar ON panel_lookup.panel_region = ar."label" - LEFT JOIN alcs_gov ON oa.alr_application_id = alcs_gov.application_id -WHERE atl.code = 'SRW' - AND oa.application_class_code IN ('LOA', 'BLK', 'SCH', 'NAN') -- filter SRW only \ No newline at end of file + LEFT JOIN alcs_gov ON oa.planning_review_id = alcs_gov.review_id \ No newline at end of file diff --git a/bin/migrate-oats-data/planning_review/sql/planning_review_base_insert_count.sql b/bin/migrate-oats-data/planning_review/sql/planning_review_base_insert_count.sql new file mode 100644 index 0000000000..3b4d437438 --- /dev/null +++ b/bin/migrate-oats-data/planning_review/sql/planning_review_base_insert_count.sql @@ -0,0 +1,4 @@ +SELECT + COUNT(*) +FROM + oats.oats_planning_reviews \ No newline at end of file diff --git a/bin/migrate-oats-data/planning_review/sql/planning_review_base_update.sql b/bin/migrate-oats-data/planning_review/sql/planning_review_base_update.sql new file mode 100644 index 0000000000..78792d0513 --- /dev/null +++ b/bin/migrate-oats-data/planning_review/sql/planning_review_base_update.sql @@ -0,0 +1,8 @@ +SELECT + ops.open_ind, + ops.planning_review_id, + ops.planning_review_code, + ops.legacy_planning_review_nbr +FROM + oats.oats_planning_reviews ops + JOIN alcs.planning_review apr ON ops.planning_review_id::TEXT = apr.file_number \ No newline at end of file diff --git a/bin/migrate-oats-data/planning_review/sql/planning_review_base_update_count.sql b/bin/migrate-oats-data/planning_review/sql/planning_review_base_update_count.sql new file mode 100644 index 0000000000..e162e43058 --- /dev/null +++ b/bin/migrate-oats-data/planning_review/sql/planning_review_base_update_count.sql @@ -0,0 +1,4 @@ +SELECT + COUNT(*) +FROM + alcs.planning_review \ No newline at end of file From 74ac1c3e2a38fa656257dca2b24e00a39c4ad19b Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Mon, 25 Mar 2024 09:30:42 -0700 Subject: [PATCH 043/153] Add Production Routes to API Guard * Production is set to have /api/ added to all routes breaking how the maintenance guard normally checks. --- services/apps/alcs/src/portal/guards/maintenance.guard.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/apps/alcs/src/portal/guards/maintenance.guard.ts b/services/apps/alcs/src/portal/guards/maintenance.guard.ts index 6c28b7b296..2a0ff61f25 100644 --- a/services/apps/alcs/src/portal/guards/maintenance.guard.ts +++ b/services/apps/alcs/src/portal/guards/maintenance.guard.ts @@ -30,7 +30,9 @@ export class MaintenanceGuard implements CanActivate { if ( req.routeOptions.url.startsWith('/portal') || - req.routeOptions.url.startsWith('/public') + req.routeOptions.url.startsWith('/public') || + req.routeOptions.url.startsWith('/api/portal') || + req.routeOptions.url.startsWith('/api/public') ) { const maintenanceMode = await this.configurationRepository.findOne({ where: { From 4eb4bc2034d618557195c41a1e2273dc01499b01 Mon Sep 17 00:00:00 2001 From: Liam Stoddard <lstodd@protonmail.com> Date: Mon, 25 Mar 2024 09:54:59 -0700 Subject: [PATCH 044/153] MR Feedback --- bin/migrate-oats-data/README.md | 6 ++++-- bin/migrate-oats-data/{migrate_initial.py => migrate_1.py} | 0 bin/migrate-oats-data/{migrate_middle.py => migrate_2.py} | 0 .../planning_review/planning_review_base.py | 4 ++-- .../planning_review/sql/planning_review_base_insert.sql | 4 ++-- 5 files changed, 8 insertions(+), 6 deletions(-) rename bin/migrate-oats-data/{migrate_initial.py => migrate_1.py} (100%) rename bin/migrate-oats-data/{migrate_middle.py => migrate_2.py} (100%) diff --git a/bin/migrate-oats-data/README.md b/bin/migrate-oats-data/README.md index 2fa80c2885..d0b7e41225 100644 --- a/bin/migrate-oats-data/README.md +++ b/bin/migrate-oats-data/README.md @@ -47,9 +47,11 @@ If you want to see a detailed description of the available arguments and options Pre-launch ETL command has been changed to: -`python migrate_initial.py [action]` +`python migrate_1.py [action]` -Post-launch ETL command now takes over migrate.py and is used as before +Post-launch ETL command is `migrate_2.py` and imports everything post launch inclusive of SRWs + +ETLs post SRWs take over migrate.py command and is used as before Commands for post-launch are stored in: diff --git a/bin/migrate-oats-data/migrate_initial.py b/bin/migrate-oats-data/migrate_1.py similarity index 100% rename from bin/migrate-oats-data/migrate_initial.py rename to bin/migrate-oats-data/migrate_1.py diff --git a/bin/migrate-oats-data/migrate_middle.py b/bin/migrate-oats-data/migrate_2.py similarity index 100% rename from bin/migrate-oats-data/migrate_middle.py rename to bin/migrate-oats-data/migrate_2.py diff --git a/bin/migrate-oats-data/planning_review/planning_review_base.py b/bin/migrate-oats-data/planning_review/planning_review_base.py index 1aea4f1a0f..70b35391da 100644 --- a/bin/migrate-oats-data/planning_review/planning_review_base.py +++ b/bin/migrate-oats-data/planning_review/planning_review_base.py @@ -35,10 +35,10 @@ def init_planning_review_base(conn=None, batch_size=BATCH_UPLOAD_SIZE): with open( "planning_review/sql/planning_review_base_insert.sql", "r", encoding="utf-8" ) as sql_file: - application_sql = sql_file.read() + query = sql_file.read() while True: cursor.execute( - f"""{application_sql} + f"""{query} WHERE oa.planning_review_id > {last_row_id} ORDER by oa.planning_review_id; """ ) diff --git a/bin/migrate-oats-data/planning_review/sql/planning_review_base_insert.sql b/bin/migrate-oats-data/planning_review/sql/planning_review_base_insert.sql index ada73c7588..7bd7429b94 100644 --- a/bin/migrate-oats-data/planning_review/sql/planning_review_base_insert.sql +++ b/bin/migrate-oats-data/planning_review/sql/planning_review_base_insert.sql @@ -68,7 +68,7 @@ alcs_gov AS ( END ) = TRIM(alg."name") ), --- Step 2: Perform a lookup to retrieve the region code for each application ID +-- Step 2: Perform a lookup to retrieve the region code for each planning_review_id panel_lookup AS ( SELECT DISTINCT oaap.planning_review_id AS review_id, CASE @@ -84,7 +84,7 @@ panel_lookup AS ( WHERE oo2.organization_type_cd = 'PANEL' OR oo3.organization_type_cd = 'PANEL' )--, --- Step 3: Insert new records into the alcs_applications table +-- Step 3: Insert new records into the alcs_planning_review table SELECT oa.planning_review_id::text AS file_number, ar.code AS region_code, 'AAPP' AS type_code, From afccd73413ac98ec3c8aa996de9dc1889bde1b7c Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Mon, 25 Mar 2024 10:31:25 -0700 Subject: [PATCH 045/153] Add minimum amount to soil and area fields for Decisions --- .../decision-component.component.ts | 20 ++++++++++--------- .../decision-component.component.ts | 20 ++++++++++--------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-components/decision-component/decision-component.component.ts b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-components/decision-component/decision-component.component.ts index e22be0e5df..f80aa2d470 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-components/decision-component/decision-component.component.ts +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-components/decision-component/decision-component.component.ts @@ -19,6 +19,8 @@ import { AG_CAP_OPTIONS, AG_CAP_SOURCE_OPTIONS } from '../../../../../../../shar import { formatDateForApi } from '../../../../../../../shared/utils/api-date-formatter'; import { SubdInputComponent } from './subd-input/subd-input.component'; +const MIN_SOIL_FIELDS = 0.01; + @Component({ selector: 'app-decision-component', templateUrl: './decision-component.component.html', @@ -47,20 +49,20 @@ export class DecisionComponentComponent implements OnInit { // pofo, pfrs fillTypeToPlace = new FormControl<string | null>(null, [Validators.required]); - volumeToPlace = new FormControl<number | null>(null, [Validators.required]); - areaToPlace = new FormControl<number | null>(null, [Validators.required]); - maximumDepthToPlace = new FormControl<number | null>(null, [Validators.required]); - averageDepthToPlace = new FormControl<number | null>(null, [Validators.required]); + volumeToPlace = new FormControl<number | null>(null, [Validators.required, Validators.min(MIN_SOIL_FIELDS)]); + areaToPlace = new FormControl<number | null>(null, [Validators.required, Validators.min(MIN_SOIL_FIELDS)]); + maximumDepthToPlace = new FormControl<number | null>(null, [Validators.required, Validators.min(MIN_SOIL_FIELDS)]); + averageDepthToPlace = new FormControl<number | null>(null, [Validators.required, Validators.min(MIN_SOIL_FIELDS)]); //pfrs endDate2 = new FormControl<Date | null>(null); // roso, pfrs soilTypeRemoved = new FormControl<string | null>(null, [Validators.required]); - volumeToRemove = new FormControl<number | null>(null, [Validators.required]); - areaToRemove = new FormControl<number | null>(null, [Validators.required]); - maximumDepthToRemove = new FormControl<number | null>(null, [Validators.required]); - averageDepthToRemove = new FormControl<number | null>(null, [Validators.required]); + volumeToRemove = new FormControl<number | null>(null, [Validators.required, Validators.min(MIN_SOIL_FIELDS)]); + areaToRemove = new FormControl<number | null>(null, [Validators.required, Validators.min(MIN_SOIL_FIELDS)]); + maximumDepthToRemove = new FormControl<number | null>(null, [Validators.required, Validators.min(MIN_SOIL_FIELDS)]); + averageDepthToRemove = new FormControl<number | null>(null, [Validators.required, Validators.min(MIN_SOIL_FIELDS)]); // naru naruSubtypeCode = new FormControl<string | null>(null, [Validators.required]); @@ -73,7 +75,7 @@ export class DecisionComponentComponent implements OnInit { applicantType = new FormControl<string | null>(null, [Validators.required]); // general - alrArea = new FormControl<number | null>(null, [Validators.required]); + alrArea = new FormControl<number | null>(null, [Validators.required, Validators.min(MIN_SOIL_FIELDS)]); agCap = new FormControl<string | null>(null, [Validators.required]); agCapSource = new FormControl<string | null>(null, [Validators.required]); agCapMap = new FormControl<string | null>(null); diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/decision-component.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/decision-component.component.ts index 59558a6b64..04bb05687d 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/decision-component.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/decision-component.component.ts @@ -13,6 +13,8 @@ import { ToastService } from '../../../../../../../services/toast/toast.service' import { AG_CAP_OPTIONS, AG_CAP_SOURCE_OPTIONS } from '../../../../../../../shared/dto/ag-cap.types.dto'; import { formatDateForApi } from '../../../../../../../shared/utils/api-date-formatter'; +const MIN_SOIL_FIELDS = 0.01; + @Component({ selector: 'app-decision-component', templateUrl: './decision-component.component.html', @@ -31,24 +33,24 @@ export class DecisionComponentComponent implements OnInit { // pofo, pfrs fillTypeToPlace = new FormControl<string | null>(null, [Validators.required]); - volumeToPlace = new FormControl<number | null>(null, [Validators.required]); - areaToPlace = new FormControl<number | null>(null, [Validators.required]); - maximumDepthToPlace = new FormControl<number | null>(null, [Validators.required]); - averageDepthToPlace = new FormControl<number | null>(null, [Validators.required]); + volumeToPlace = new FormControl<number | null>(null, [Validators.required, Validators.min(MIN_SOIL_FIELDS)]); + areaToPlace = new FormControl<number | null>(null, [Validators.required, Validators.min(MIN_SOIL_FIELDS)]); + maximumDepthToPlace = new FormControl<number | null>(null, [Validators.required, Validators.min(MIN_SOIL_FIELDS)]); + averageDepthToPlace = new FormControl<number | null>(null, [Validators.required, Validators.min(MIN_SOIL_FIELDS)]); // roso, pfrs soilTypeRemoved = new FormControl<string | null>(null, [Validators.required]); - volumeToRemove = new FormControl<number | null>(null, [Validators.required]); - areaToRemove = new FormControl<number | null>(null, [Validators.required]); - maximumDepthToRemove = new FormControl<number | null>(null, [Validators.required]); - averageDepthToRemove = new FormControl<number | null>(null, [Validators.required]); + volumeToRemove = new FormControl<number | null>(null, [Validators.required, Validators.min(MIN_SOIL_FIELDS)]); + areaToRemove = new FormControl<number | null>(null, [Validators.required, Validators.min(MIN_SOIL_FIELDS)]); + maximumDepthToRemove = new FormControl<number | null>(null, [Validators.required, Validators.min(MIN_SOIL_FIELDS)]); + averageDepthToRemove = new FormControl<number | null>(null, [Validators.required, Validators.min(MIN_SOIL_FIELDS)]); //pfrs endDate2 = new FormControl<Date | null>(null); // general endDate = new FormControl<Date | null>(null); - alrArea = new FormControl<number | null>(null, [Validators.required]); + alrArea = new FormControl<number | null>(null, [Validators.required, Validators.min(MIN_SOIL_FIELDS)]); agCap = new FormControl<string | null>(null, [Validators.required]); agCapSource = new FormControl<string | null>(null, [Validators.required]); agCapMap = new FormControl<string | null>(null); From f0c5fe549444baaa7fa96220b8ae431b858ff083 Mon Sep 17 00:00:00 2001 From: mhuseinov <61513701+mhuseinov@users.noreply.github.com> Date: Mon, 25 Mar 2024 16:17:27 -0700 Subject: [PATCH 046/153] Feature/alcs 1809 initial inquiry import Part 1 (#1544) menu setup for inquiry import import required fields for the base entity --- bin/migrate-oats-data/inquiry/__init__.py | 1 + bin/migrate-oats-data/inquiry/inquiry_base.py | 150 ++++++++++++++++++ .../inquiry/inquiry_migration.py | 9 ++ .../inquiry/sql/inquiry_base_insert.sql | 109 +++++++++++++ .../inquiry/sql/inquiry_base_insert_count.sql | 96 +++++++++++ .../menu/command_parser/__init__.py | 1 + .../command_parser/inquiry_command_parser.py | 19 +++ bin/migrate-oats-data/menu/menu.py | 6 + .../menu/post_launch_commands/__init__.py | 1 + .../menu/post_launch_commands/inquiries.py | 23 +++ bin/migrate-oats-data/migrate.py | 6 + 11 files changed, 421 insertions(+) create mode 100644 bin/migrate-oats-data/inquiry/__init__.py create mode 100644 bin/migrate-oats-data/inquiry/inquiry_base.py create mode 100644 bin/migrate-oats-data/inquiry/inquiry_migration.py create mode 100644 bin/migrate-oats-data/inquiry/sql/inquiry_base_insert.sql create mode 100644 bin/migrate-oats-data/inquiry/sql/inquiry_base_insert_count.sql create mode 100644 bin/migrate-oats-data/menu/command_parser/inquiry_command_parser.py create mode 100644 bin/migrate-oats-data/menu/post_launch_commands/inquiries.py diff --git a/bin/migrate-oats-data/inquiry/__init__.py b/bin/migrate-oats-data/inquiry/__init__.py new file mode 100644 index 0000000000..70e6815752 --- /dev/null +++ b/bin/migrate-oats-data/inquiry/__init__.py @@ -0,0 +1 @@ +from .inquiry_migration import process_inquiry, clean_inquiry diff --git a/bin/migrate-oats-data/inquiry/inquiry_base.py b/bin/migrate-oats-data/inquiry/inquiry_base.py new file mode 100644 index 0000000000..4a818b174b --- /dev/null +++ b/bin/migrate-oats-data/inquiry/inquiry_base.py @@ -0,0 +1,150 @@ +from common import ( + setup_and_get_logger, + BATCH_UPLOAD_SIZE, + OATS_ETL_USER, + DEFAULT_ETL_USER_UUID, + add_timezone_and_keep_date_part, +) +from db import inject_conn_pool +from datetime import datetime +from psycopg2.extras import RealDictCursor + +etl_name = "init_inquiries" +logger = setup_and_get_logger(etl_name) + + +@inject_conn_pool +def init_inquiries(conn=None, batch_size=BATCH_UPLOAD_SIZE): + logger.info(f"Start {etl_name}") + with conn.cursor(cursor_factory=RealDictCursor) as cursor: + with open( + "inquiry/sql/inquiry_base_insert_count.sql", + "r", + encoding="utf-8", + ) as sql_file: + count_query = sql_file.read() + cursor.execute(count_query) + count_total = dict(cursor.fetchone())["count"] + logger.info(f"Total inquiry data to insert: {count_total}") + + failed_inserts_count = 0 + successful_inserts_count = 0 + last_imported_id = 0 + + with open( + "inquiry/sql/inquiry_base_insert.sql", + "r", + encoding="utf-8", + ) as sql_file: + query = sql_file.read() + while True: + cursor.execute( + f"""{query} + AND oi.issue_id > {last_imported_id} ORDER BY oi.issue_id;""" + ) + + rows = cursor.fetchmany(batch_size) + + if not rows: + break + try: + records_to_be_inserted_count = len(rows) + + _insert_records(conn, cursor, rows) + + successful_inserts_count = ( + successful_inserts_count + records_to_be_inserted_count + ) + + last_record = dict(rows[-1]) + last_imported_id = last_record["issue_id"] + + logger.debug( + f"retrieved/updated items count: {records_to_be_inserted_count}; total successfully imported inquiry so far {successful_inserts_count}; last updated {last_imported_id}" + ) + except Exception as err: + logger.exception(err) + conn.rollback() + failed_inserts_count = count_total - successful_inserts_count + last_imported_id = last_imported_id + 1 + + logger.info( + f"Finished {etl_name}: total amount of successful inserts {successful_inserts_count}, total failed inserts {failed_inserts_count}" + ) + + +def _insert_records(conn, cursor, rows): + number_of_rows_to_insert = len(rows) + + if number_of_rows_to_insert > 0: + insert_query = _compile_insert_query(number_of_rows_to_insert) + rows_to_insert = _prepare_data_to_insert(rows) + cursor.execute(insert_query, rows_to_insert) + conn.commit() + + +def _compile_insert_query(number_of_rows_to_insert): + records_to_insert = ",".join(["%s"] * number_of_rows_to_insert) + return f""" + INSERT INTO alcs.inquiry( + file_number, + summary, + date_submitted_to_alc, + open, + local_government_uuid, + region_code, + type_code, + audit_created_by, + closed_by_uuid, + closed_date + ) + VALUES{records_to_insert} + ON CONFLICT (file_number) DO UPDATE SET + summary = COALESCE(EXCLUDED.summary, alcs.inquiry.summary), + date_submitted_to_alc = COALESCE(EXCLUDED.date_submitted_to_alc, alcs.inquiry.date_submitted_to_alc), + open = COALESCE(EXCLUDED.open, alcs.inquiry.open), + region_code = COALESCE(EXCLUDED.region_code, alcs.inquiry.region_code), + type_code = EXCLUDED.type_code, + local_government_uuid = COALESCE(EXCLUDED.local_government_uuid, alcs.inquiry.local_government_uuid), + audit_created_by = EXCLUDED.audit_created_by, + closed_by_uuid = COALESCE(EXCLUDED.closed_by_uuid, alcs.inquiry.closed_by_uuid), + closed_date = COALESCE(EXCLUDED.closed_date, alcs.inquiry.closed_date); + """ + + +def _prepare_data_to_insert(rows): + data_to_insert = [] + for row in rows: + mapped_row = _map_data(row) + data_to_insert.append(tuple(mapped_row.values())) + + return data_to_insert + + +def _map_data(row): + date_str = "0001-01-01 00:00:00.000 -0800" + + return { + "file_number": row["issue_id"], + "summary": row["description"], + "dateSubmittedToAlc": add_timezone_and_keep_date_part(row["received_date"]), + "open": False, + "local_government_uuid": row["gov_uuid"], + "region_code": row["region_code"], + "type_code": row["issue_type_code"], + "audit_created_by": OATS_ETL_USER, + "closed_by_uuid": DEFAULT_ETL_USER_UUID, + "closed_date": date_str, + } + + +@inject_conn_pool +def clean_inquiries(conn=None): + logger.info("Start inquiry cleaning") + with conn.cursor() as cursor: + cursor.execute( + f"DELETE FROM alcs.inquiry inq WHERE inq.audit_created_by = '{OATS_ETL_USER}' AND inq.audit_updated_by IS NULL" + ) + logger.info(f"Deleted items count = {cursor.rowcount}") + conn.commit() + logger.info("Done inquiry cleaning") diff --git a/bin/migrate-oats-data/inquiry/inquiry_migration.py b/bin/migrate-oats-data/inquiry/inquiry_migration.py new file mode 100644 index 0000000000..fd89b70a69 --- /dev/null +++ b/bin/migrate-oats-data/inquiry/inquiry_migration.py @@ -0,0 +1,9 @@ +from .inquiry_base import init_inquiries, clean_inquiries + + +def process_inquiry(batch_size): + init_inquiries(batch_size) + + +def clean_inquiry(): + clean_inquiries() diff --git a/bin/migrate-oats-data/inquiry/sql/inquiry_base_insert.sql b/bin/migrate-oats-data/inquiry/sql/inquiry_base_insert.sql new file mode 100644 index 0000000000..bca0acb0ce --- /dev/null +++ b/bin/migrate-oats-data/inquiry/sql/inquiry_base_insert.sql @@ -0,0 +1,109 @@ +-- Step 1: get local gov application name & match to uuid +WITH oats_gov AS ( + SELECT oi.issue_id, + TRIM(oo.organization_name) AS oats_gov_name + FROM oats.oats_issues oi + JOIN oats.oats_person_organizations opo ON opo.person_organization_id = oi.local_gov_pog_id + JOIN oats.oats_organizations oo ON opo.organization_id = oo.organization_id + WHERE oo.organization_type_cd IN ('MUNI', 'FN', 'RD', 'RM') + AND issu_type = 'INQ' +), +-- Step 2: trim gov name from white spaces +trimmed_alcs_gov AS ( + SELECT TRIM(lg."name") AS name, + lg.uuid + FROM alcs.local_government lg +), +alcs_gov AS ( + SELECT oats_gov.issue_id AS issue_id, + alg.uuid AS gov_uuid, + alg."name" AS gov_name + FROM oats_gov + JOIN trimmed_alcs_gov alg on ( + CASE + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Gabriola Island' THEN 'Islands Trust Gabriola Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Galiano Island' THEN 'Islands Trust Galiano Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Gambier Island' THEN 'Islands Trust Gambier Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Hornby Island' THEN 'Islands Trust Hornby Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Lasqueti Island' THEN 'Islands Trust Lasqueti Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Mayne Island' THEN 'Islands Trust Mayne Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Pender Island' THEN 'Islands Trust Pender Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Quadra Island' THEN 'Islands Trust Quadra Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Salt Spring Island' THEN 'Islands Trust Salt Spring Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Saturna Island' THEN 'Islands Trust Saturna Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Sidney Island' THEN 'Islands Trust Sidney Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust- Comox Strathcona' THEN 'Islands Trust Comox Strathcona (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Comox-Strathcona (Historical)' THEN 'Comox-Strathcona Regional District (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust- Nanaimo' THEN 'Islands Trust Nanaimo (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust-Capital' THEN 'Islands Trust Capital (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust-Powell River' THEN 'Islands Trust Powell River (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust-Sunshine Coast' THEN 'Islands Trust Sunshine Coast (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Bowen Island' THEN 'Bowen Island (Island Municipality)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Denman Island' THEN 'Islands Trust Denman Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust - Cowichan Valley' THEN 'Islands Trust Cowichan Valley (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Northern Rockies' THEN 'Northern Rockies (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Sliammon%' THEN 'Tla''amin Nation' + WHEN oats_gov.oats_gov_name LIKE 'Thompson Nicola%' THEN 'Thompson Nicola Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Cariboo%' THEN 'Cariboo Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Fraser Valley%' THEN 'Fraser Valley Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Columbia Shuswap%' THEN 'Columbia Shuswap Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Central Okanagan%' THEN 'Central Okanagan Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Squamish Lillooet%' THEN 'Squamish Lillooet Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Alberni-Clayoquot%' THEN 'Alberni-Clayoquot Regional District' + WHEN oats_gov.oats_gov_name LIKE 'qathet%' THEN 'qathet Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Peace River%' THEN 'Peace River Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Okanagan Similkameen%' THEN 'Okanagan Similkameen Regional District' + WHEN oats_gov.oats_gov_name LIKE 'East Kootenay%' THEN 'East Kootenay Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Bulkley-Nechako%' THEN 'Bulkley-Nechako Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Sunshine Coast%' THEN 'Sunshine Coast Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Nanaimo%' THEN 'Nanaimo Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Kitimat Stikine%' THEN 'Kitimat Stikine Regional District' + WHEN oats_gov.oats_gov_name LIKE 'North Okanagan%' THEN 'North Okanagan Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Fraser Fort George%' THEN 'Fraser Fort George Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Cowichan Valley%' THEN 'Cowichan Valley Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Kootenay Boundary%' THEN 'Kootenay Boundary Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Comox Valley%' THEN 'Comox Valley Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Central Kootenay%' THEN 'Central Kootenay Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Capital%' THEN 'Capital Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Metro Vancouver%' THEN 'Metro Vancouver Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Central Coast%' THEN 'Central Coast Regional District' + WHEN oats_gov.oats_gov_name LIKE 'North Coast%' THEN 'North Coast Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Strathcona%' THEN 'Strathcona Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Mount Waddington%' THEN 'Mount Waddington Regional District' + ELSE oats_gov.oats_gov_name + END + ) = alg."name" +), +-- Step 3: Perform a lookup to retrieve the region code for each issue ID +panel_lookup AS ( + SELECT DISTINCT oi.issue_id, + CASE + WHEN oo2.parent_organization_id IS NULL THEN oo2.organization_name + WHEN oo3.parent_organization_id IS NULL THEN oo3.organization_name + ELSE 'NONE' + END AS panel_region + FROM oats.oats_issues oi + JOIN oats.oats_person_organizations opo ON oi.local_gov_pog_id = opo.person_organization_id + JOIN oats.oats_organizations oo ON opo.organization_id = oo.organization_id + LEFT JOIN oats.oats_organizations oo2 ON oo.parent_organization_id = oo2.organization_id + LEFT JOIN oats.oats_organizations oo3 ON oo2.parent_organization_id = oo3.organization_id + WHERE oo2.organization_type_cd = 'PANEL' + OR oo3.organization_type_cd = 'PANEL' +) +SELECT oi.issue_id, + CASE + WHEN oi.inquiry_code = 'INQINV' THEN 'INVN' + WHEN oi.inquiry_code in ('GENERAL', 'AREAOI') THEN 'GENC' + WHEN oi.inquiry_code = 'COMPLAN' THEN 'REFR' + END AS issue_type_code, + panel_lookup.panel_region, + ar.code AS region_code, + alcs_gov.gov_uuid, + alcs_gov.gov_name, + oi.received_date, + oi."description" +FROM oats.oats_issues oi + JOIN panel_lookup ON panel_lookup.issue_id = oi.issue_id + JOIN alcs_gov ON alcs_gov.issue_id = oi.issue_id + LEFT JOIN alcs.application_region ar ON panel_lookup.panel_region = ar."label" +WHERE oi.issu_type = 'INQ' \ No newline at end of file diff --git a/bin/migrate-oats-data/inquiry/sql/inquiry_base_insert_count.sql b/bin/migrate-oats-data/inquiry/sql/inquiry_base_insert_count.sql new file mode 100644 index 0000000000..cdbc4cecbc --- /dev/null +++ b/bin/migrate-oats-data/inquiry/sql/inquiry_base_insert_count.sql @@ -0,0 +1,96 @@ +-- Step 1: get local gov application name & match to uuid +WITH oats_gov AS ( + SELECT oi.issue_id, + TRIM(oo.organization_name) AS oats_gov_name + FROM oats.oats_issues oi + JOIN oats.oats_person_organizations opo ON opo.person_organization_id = oi.local_gov_pog_id + JOIN oats.oats_organizations oo ON opo.organization_id = oo.organization_id + WHERE oo.organization_type_cd IN ('MUNI', 'FN', 'RD', 'RM') + AND issu_type = 'INQ' +), +trimed_alcs_gov AS ( + SELECT TRIM(lg."name") AS name, + lg.uuid + FROM alcs.local_government lg +), +alcs_gov AS ( + SELECT oats_gov.issue_id AS issue_id, + alg.uuid AS gov_uuid, + alg."name" AS gov_name + FROM oats_gov + JOIN trimed_alcs_gov alg on ( + CASE + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Gabriola Island' THEN 'Islands Trust Gabriola Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Galiano Island' THEN 'Islands Trust Galiano Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Gambier Island' THEN 'Islands Trust Gambier Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Hornby Island' THEN 'Islands Trust Hornby Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Lasqueti Island' THEN 'Islands Trust Lasqueti Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Mayne Island' THEN 'Islands Trust Mayne Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Pender Island' THEN 'Islands Trust Pender Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Quadra Island' THEN 'Islands Trust Quadra Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Salt Spring Island' THEN 'Islands Trust Salt Spring Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Saturna Island' THEN 'Islands Trust Saturna Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Sidney Island' THEN 'Islands Trust Sidney Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust- Comox Strathcona' THEN 'Islands Trust Comox Strathcona (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Comox-Strathcona (Historical)' THEN 'Comox-Strathcona Regional District (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust- Nanaimo' THEN 'Islands Trust Nanaimo (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust-Capital' THEN 'Islands Trust Capital (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust-Powell River' THEN 'Islands Trust Powell River (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust-Sunshine Coast' THEN 'Islands Trust Sunshine Coast (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Bowen Island' THEN 'Bowen Island (Island Municipality)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust Denman Island' THEN 'Islands Trust Denman Island (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Islands Trust - Cowichan Valley' THEN 'Islands Trust Cowichan Valley (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Northern Rockies' THEN 'Northern Rockies (Historical)' + WHEN oats_gov.oats_gov_name LIKE 'Sliammon%' THEN 'Tla''amin Nation' + WHEN oats_gov.oats_gov_name LIKE 'Thompson Nicola%' THEN 'Thompson Nicola Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Cariboo%' THEN 'Cariboo Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Fraser Valley%' THEN 'Fraser Valley Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Columbia Shuswap%' THEN 'Columbia Shuswap Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Central Okanagan%' THEN 'Central Okanagan Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Squamish Lillooet%' THEN 'Squamish Lillooet Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Alberni-Clayoquot%' THEN 'Alberni-Clayoquot Regional District' + WHEN oats_gov.oats_gov_name LIKE 'qathet%' THEN 'qathet Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Peace River%' THEN 'Peace River Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Okanagan Similkameen%' THEN 'Okanagan Similkameen Regional District' + WHEN oats_gov.oats_gov_name LIKE 'East Kootenay%' THEN 'East Kootenay Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Bulkley-Nechako%' THEN 'Bulkley-Nechako Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Sunshine Coast%' THEN 'Sunshine Coast Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Nanaimo%' THEN 'Nanaimo Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Kitimat Stikine%' THEN 'Kitimat Stikine Regional District' + WHEN oats_gov.oats_gov_name LIKE 'North Okanagan%' THEN 'North Okanagan Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Fraser Fort George%' THEN 'Fraser Fort George Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Cowichan Valley%' THEN 'Cowichan Valley Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Kootenay Boundary%' THEN 'Kootenay Boundary Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Comox Valley%' THEN 'Comox Valley Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Central Kootenay%' THEN 'Central Kootenay Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Capital%' THEN 'Capital Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Metro Vancouver%' THEN 'Metro Vancouver Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Central Coast%' THEN 'Central Coast Regional District' + WHEN oats_gov.oats_gov_name LIKE 'North Coast%' THEN 'North Coast Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Strathcona%' THEN 'Strathcona Regional District' + WHEN oats_gov.oats_gov_name LIKE 'Mount Waddington%' THEN 'Mount Waddington Regional District' + ELSE oats_gov.oats_gov_name + END + ) = alg."name" +), +-- Step 2: Perform a lookup to retrieve the region code for each issue ID +panel_lookup AS ( + SELECT DISTINCT oi.issue_id, + CASE + WHEN oo2.parent_organization_id IS NULL THEN oo2.organization_name + WHEN oo3.parent_organization_id IS NULL THEN oo3.organization_name + ELSE 'NONE' + END AS panel_region + FROM oats.oats_issues oi + JOIN oats.oats_person_organizations opo ON oi.local_gov_pog_id = opo.person_organization_id + JOIN oats.oats_organizations oo ON opo.organization_id = oo.organization_id + LEFT JOIN oats.oats_organizations oo2 ON oo.parent_organization_id = oo2.organization_id + LEFT JOIN oats.oats_organizations oo3 ON oo2.parent_organization_id = oo3.organization_id + WHERE oo2.organization_type_cd = 'PANEL' + OR oo3.organization_type_cd = 'PANEL' +) +SELECT count(*) +FROM oats.oats_issues oi + JOIN panel_lookup ON panel_lookup.issue_id = oi.issue_id + JOIN alcs_gov ON alcs_gov.issue_id = oi.issue_id +WHERE oi.issu_type = 'INQ'; \ No newline at end of file diff --git a/bin/migrate-oats-data/menu/command_parser/__init__.py b/bin/migrate-oats-data/menu/command_parser/__init__.py index d742042e93..da7e7ea6b2 100644 --- a/bin/migrate-oats-data/menu/command_parser/__init__.py +++ b/bin/migrate-oats-data/menu/command_parser/__init__.py @@ -4,3 +4,4 @@ from .user_command_parser import * from .srw_command_parser import * from .planning_review_command_parser import * +from .inquiry_command_parser import * diff --git a/bin/migrate-oats-data/menu/command_parser/inquiry_command_parser.py b/bin/migrate-oats-data/menu/command_parser/inquiry_command_parser.py new file mode 100644 index 0000000000..a170fe5eff --- /dev/null +++ b/bin/migrate-oats-data/menu/command_parser/inquiry_command_parser.py @@ -0,0 +1,19 @@ +def inquiry_import_command_parser(import_batch_size, subparsers): + inquiry_import_command = subparsers.add_parser( + "inquiry-import", + help=f"Import Inquiry with specified batch size: (default: {import_batch_size})", + ) + inquiry_import_command.add_argument( + "--batch-size", + type=int, + default=import_batch_size, + metavar="", + help=f"batch size (default: {import_batch_size})", + ) + + +def inquiry_clean_command_parser(subparsers): + subparsers.add_parser( + "inquiry-clean", + help="Clean Inquiry imported data:", + ) diff --git a/bin/migrate-oats-data/menu/menu.py b/bin/migrate-oats-data/menu/menu.py index 97030dfa53..53b44eb072 100644 --- a/bin/migrate-oats-data/menu/menu.py +++ b/bin/migrate-oats-data/menu/menu.py @@ -26,6 +26,10 @@ planning_review_clean_command_parser, planning_review_import_command_parser, ) +from .command_parser.inquiry_command_parser import ( + inquiry_import_command_parser, + inquiry_clean_command_parser, +) import_batch_size = BATCH_UPLOAD_SIZE @@ -64,6 +68,8 @@ def setup_menu_args_parser(import_batch_size): srw_clean_command_parser(subparsers) planning_review_import_command_parser(import_batch_size, subparsers) planning_review_clean_command_parser(subparsers) + inquiry_import_command_parser(import_batch_size, subparsers) + inquiry_clean_command_parser(subparsers) subparsers.add_parser("clean", help="Clean all imported data") subparsers.add_parser("obfuscate", help="Obfuscate PROD data") diff --git a/bin/migrate-oats-data/menu/post_launch_commands/__init__.py b/bin/migrate-oats-data/menu/post_launch_commands/__init__.py index 615326d2de..3a21dc1c07 100644 --- a/bin/migrate-oats-data/menu/post_launch_commands/__init__.py +++ b/bin/migrate-oats-data/menu/post_launch_commands/__init__.py @@ -5,3 +5,4 @@ from .srws import * from .documents import * from .planning_reviews import * +from .inquiries import * diff --git a/bin/migrate-oats-data/menu/post_launch_commands/inquiries.py b/bin/migrate-oats-data/menu/post_launch_commands/inquiries.py new file mode 100644 index 0000000000..32b6c79f97 --- /dev/null +++ b/bin/migrate-oats-data/menu/post_launch_commands/inquiries.py @@ -0,0 +1,23 @@ +from common import setup_and_get_logger +from inquiry.inquiry_migration import process_inquiry, clean_inquiry + +logger = setup_and_get_logger("inquiries_import_post_launch") + + +def inquiry_import(console, args): + logger.debug("Beginning OATS -> ALCS Inquiry import process") + with console.status( + "[bold green]Inquiry import (notification table update in ALCS)...\n" + ) as status: + if args.batch_size: + import_batch_size = args.batch_size + + logger.debug(f"Processing Inquiry import in batch size = {import_batch_size}") + + process_inquiry(batch_size=import_batch_size) + + +def inquiry_clean(console): + logger.debug("Beginning OATS -> ALCS Inquiry import clean process") + with console.status("[bold green]Inquiry clean import...\n") as status: + clean_inquiry() diff --git a/bin/migrate-oats-data/migrate.py b/bin/migrate-oats-data/migrate.py index ae357e2640..25f92cbd5a 100644 --- a/bin/migrate-oats-data/migrate.py +++ b/bin/migrate-oats-data/migrate.py @@ -18,6 +18,8 @@ document_clean, planning_review_clean, planning_review_import, + inquiry_import, + inquiry_clean, ) from db import connection_pool from common import BATCH_UPLOAD_SIZE, setup_and_get_logger @@ -59,6 +61,10 @@ planning_review_import(console, args) case "pr-clean": planning_review_clean(console) + case "inquiry-import": + inquiry_import(console, args) + case "inquiry-clean": + inquiry_clean(console) finally: if connection_pool: From 1e840462f8f756a8c0a2159eb6a56067da31d3c5 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Tue, 26 Mar 2024 09:30:26 -0700 Subject: [PATCH 047/153] Creation of Inquiries * Add front end dialogs for creating and viewing inquiries --- .../board-management-dialog.component.scss | 1 + .../board-management.component.ts | 4 +- .../features/board/board.component.spec.ts | 11 ++ .../src/app/features/board/board.component.ts | 41 +++- .../src/app/features/board/board.module.ts | 11 +- .../create-inquiry-dialog.component.html | 184 ++++++++++++++++++ .../create-inquiry-dialog.component.scss | 45 +++++ .../create-inquiry-dialog.component.spec.ts | 56 ++++++ .../create/create-inquiry-dialog.component.ts | 175 +++++++++++++++++ .../inquiry/inquiry-dialog.component.html | 123 ++++++++++++ .../inquiry/inquiry-dialog.component.spec.ts | 122 ++++++++++++ .../inquiry/inquiry-dialog.component.ts | 91 +++++++++ .../src/app/services/board/board.dto.ts | 2 + .../inquiry-parcel/inquiry-parcel.dto.ts | 20 ++ .../src/app/services/inquiry/inquiry.dto.ts | 74 +++++++ .../services/inquiry/inquiry.service.spec.ts | 103 ++++++++++ .../app/services/inquiry/inquiry.service.ts | 70 +++++++ .../src/app/shared/card/card.component.ts | 1 + .../src/alcs/board/board.controller.spec.ts | 9 + .../alcs/src/alcs/board/board.controller.ts | 7 + .../apps/alcs/src/alcs/board/board.dto.ts | 1 + .../apps/alcs/src/alcs/board/board.module.ts | 2 + .../alcs/card/card-type/card-type.entity.ts | 2 +- .../alcs/inquiry/inquiry.controller.spec.ts | 60 ++++++ .../src/alcs/inquiry/inquiry.controller.ts | 48 ++++- .../apps/alcs/src/alcs/inquiry/inquiry.dto.ts | 24 ++- .../alcs/src/alcs/inquiry/inquiry.entity.ts | 5 +- .../alcs/src/alcs/inquiry/inquiry.module.ts | 4 +- 28 files changed, 1278 insertions(+), 18 deletions(-) create mode 100644 alcs-frontend/src/app/features/board/dialogs/inquiry/create/create-inquiry-dialog.component.html create mode 100644 alcs-frontend/src/app/features/board/dialogs/inquiry/create/create-inquiry-dialog.component.scss create mode 100644 alcs-frontend/src/app/features/board/dialogs/inquiry/create/create-inquiry-dialog.component.spec.ts create mode 100644 alcs-frontend/src/app/features/board/dialogs/inquiry/create/create-inquiry-dialog.component.ts create mode 100644 alcs-frontend/src/app/features/board/dialogs/inquiry/inquiry-dialog.component.html create mode 100644 alcs-frontend/src/app/features/board/dialogs/inquiry/inquiry-dialog.component.spec.ts create mode 100644 alcs-frontend/src/app/features/board/dialogs/inquiry/inquiry-dialog.component.ts create mode 100644 alcs-frontend/src/app/services/inquiry/inquiry-parcel/inquiry-parcel.dto.ts create mode 100644 alcs-frontend/src/app/services/inquiry/inquiry.dto.ts create mode 100644 alcs-frontend/src/app/services/inquiry/inquiry.service.spec.ts create mode 100644 alcs-frontend/src/app/services/inquiry/inquiry.service.ts diff --git a/alcs-frontend/src/app/features/admin/board-management/board-management-dialog/board-management-dialog.component.scss b/alcs-frontend/src/app/features/admin/board-management/board-management-dialog/board-management-dialog.component.scss index d83912b82f..14c6f4efea 100644 --- a/alcs-frontend/src/app/features/admin/board-management/board-management-dialog/board-management-dialog.component.scss +++ b/alcs-frontend/src/app/features/admin/board-management/board-management-dialog/board-management-dialog.component.scss @@ -2,6 +2,7 @@ .dialog { padding: 24px; + max-height: 70vh; form { max-width: 100%; diff --git a/alcs-frontend/src/app/features/admin/board-management/board-management.component.ts b/alcs-frontend/src/app/features/admin/board-management/board-management.component.ts index a5ad222f95..4c12a19272 100644 --- a/alcs-frontend/src/app/features/admin/board-management/board-management.component.ts +++ b/alcs-frontend/src/app/features/admin/board-management/board-management.component.ts @@ -25,7 +25,7 @@ export class BoardManagementComponent implements OnInit, OnDestroy { private boardService: BoardService, public dialog: MatDialog, private confirmationDialogService: ConfirmationDialogService, - private adminBoardManagementService: AdminBoardManagementService + private adminBoardManagementService: AdminBoardManagementService, ) {} ngOnInit(): void { @@ -41,6 +41,7 @@ export class BoardManagementComponent implements OnInit, OnDestroy { minWidth: '800px', maxWidth: '1200px', width: '90%', + height: '80%', data: { cardTypes: this.cardTypes, }, @@ -57,6 +58,7 @@ export class BoardManagementComponent implements OnInit, OnDestroy { minWidth: '800px', maxWidth: '1200px', width: '90%', + height: '80%', data: { board: boardDto, cardTypes: this.cardTypes, 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 a6a4f748ca..b8b82cda48 100644 --- a/alcs-frontend/src/app/features/board/board.component.spec.ts +++ b/alcs-frontend/src/app/features/board/board.component.spec.ts @@ -17,6 +17,7 @@ import { BoardDto } from '../../services/board/board.dto'; import { BoardService, BoardWithFavourite } from '../../services/board/board.service'; import { CardDto } from '../../services/card/card.dto'; import { CardService } from '../../services/card/card.service'; +import { InquiryService } from '../../services/inquiry/inquiry.service'; import { NoticeOfIntentModificationService } from '../../services/notice-of-intent/notice-of-intent-modification/notice-of-intent-modification.service'; import { NoticeOfIntentService } from '../../services/notice-of-intent/notice-of-intent.service'; import { NotificationDto } from '../../services/notification/notification.dto'; @@ -44,6 +45,7 @@ describe('BoardComponent', () => { let noticeOfIntentService: DeepMocked<NoticeOfIntentService>; let noticeOfIntentModificationService: DeepMocked<NoticeOfIntentModificationService>; let notificationService: DeepMocked<NotificationService>; + let inquiryService: DeepMocked<InquiryService>; let boardEmitter = new BehaviorSubject<BoardWithFavourite[]>([]); @@ -97,6 +99,7 @@ describe('BoardComponent', () => { noticeOfIntents: [], noiModifications: [], notifications: [], + inquiries: [], }); dialog = createMock(); @@ -111,6 +114,7 @@ describe('BoardComponent', () => { noticeOfIntentService = createMock(); noticeOfIntentModificationService = createMock(); notificationService = createMock(); + inquiryService = createMock(); const params = { boardCode: 'boardCode', @@ -176,6 +180,10 @@ describe('BoardComponent', () => { provide: NotificationService, useValue: notificationService, }, + { + provide: InquiryService, + useValue: inquiryService, + }, { provide: Title, useValue: titleService, @@ -218,6 +226,7 @@ describe('BoardComponent', () => { noticeOfIntents: [], noiModifications: [], notifications: [], + inquiries: [], }); boardEmitter.next([mockBoard]); @@ -238,6 +247,7 @@ describe('BoardComponent', () => { noticeOfIntents: [], noiModifications: [], notifications: [], + inquiries: [], }); boardEmitter.next([mockBoard]); @@ -280,6 +290,7 @@ describe('BoardComponent', () => { noticeOfIntents: [], noiModifications: [], notifications: [], + inquiries: [], }); boardEmitter.next([mockBoard]); diff --git a/alcs-frontend/src/app/features/board/board.component.ts b/alcs-frontend/src/app/features/board/board.component.ts index a5280ce82c..0fd0d029fb 100644 --- a/alcs-frontend/src/app/features/board/board.component.ts +++ b/alcs-frontend/src/app/features/board/board.component.ts @@ -15,6 +15,8 @@ import { ApplicationService } from '../../services/application/application.servi import { CardsDto } from '../../services/board/board.dto'; import { BoardService, BoardWithFavourite } from '../../services/board/board.service'; import { CardService } from '../../services/card/card.service'; +import { InquiryDto } from '../../services/inquiry/inquiry.dto'; +import { InquiryService } from '../../services/inquiry/inquiry.service'; import { NoticeOfIntentModificationDto } from '../../services/notice-of-intent/notice-of-intent-modification/notice-of-intent-modification.dto'; import { NoticeOfIntentModificationService } from '../../services/notice-of-intent/notice-of-intent-modification/notice-of-intent-modification.service'; import { NoticeOfIntentDto } from '../../services/notice-of-intent/notice-of-intent.dto'; @@ -34,6 +36,8 @@ 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 { CreateInquiryDialogComponent } from './dialogs/inquiry/create/create-inquiry-dialog.component'; +import { InquiryDialogComponent } from './dialogs/inquiry/inquiry-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 { NoticeOfIntentDialogComponent } from './dialogs/notice-of-intent/notice-of-intent-dialog.component'; @@ -104,6 +108,13 @@ export class BoardComponent implements OnInit, OnDestroy { dialog: CreatePlanningReviewDialogComponent, }, ], + [ + CardType.INQUIRY, + { + label: 'Inquiry', + dialog: CreateInquiryDialogComponent, + }, + ], ]); constructor( @@ -120,6 +131,7 @@ export class BoardComponent implements OnInit, OnDestroy { private noticeOfIntentService: NoticeOfIntentService, private noiModificationService: NoticeOfIntentModificationService, private notificationService: NotificationService, + private inquiryService: InquiryService, private titleService: Title, ) {} @@ -187,6 +199,7 @@ export class BoardComponent implements OnInit, OnDestroy { case CardType.NOI: case CardType.NOI_MODI: case CardType.NOTIFICATION: + case CardType.INQUIRY: this.cardService .updateCard({ uuid: $event.id, @@ -248,6 +261,7 @@ export class BoardComponent implements OnInit, OnDestroy { this.mapNoticeOfIntentModificationToCard.bind(this), ); const mappedNotifications = response.notifications.map(this.mapNotificationToCard.bind(this)); + const mappedInquiries = response.inquiries.map(this.mapInquiryToCard.bind(this)); if (boardCode === BOARD_TYPE_CODES.VETT) { const vettingSort = (a: CardData, b: CardData) => { if (a.highPriority === b.highPriority) { @@ -259,7 +273,8 @@ export class BoardComponent implements OnInit, OnDestroy { ...[...mappedNoticeOfIntents, ...mappedNoticeOfIntentModifications].sort(vettingSort), ...[...mappedApps, ...mappedRecons, ...mappedModifications].sort(vettingSort), ...[...mappedPlanningReferrals].sort(vettingSort), - ...mappedNotifications, + ...[...mappedInquiries].sort(vettingSort), + ...[...mappedNotifications].sort(vettingSort), ]; } else if (boardCode === BOARD_TYPE_CODES.NOI) { const noiSort = (a: CardData, b: CardData) => { @@ -297,6 +312,7 @@ export class BoardComponent implements OnInit, OnDestroy { ...mappedModifications.filter((r) => r.highPriority).sort((a, b) => a.dateReceived - b.dateReceived), ...mappedRecons.filter((r) => r.highPriority).sort((a, b) => a.dateReceived - b.dateReceived), ...mappedPlanningReferrals.filter((r) => r.highPriority).sort((a, b) => a.dateReceived - b.dateReceived), + ...mappedInquiries.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 ...mappedNoticeOfIntents @@ -309,6 +325,7 @@ export class BoardComponent implements OnInit, OnDestroy { ...mappedModifications.filter((r) => !r.highPriority).sort((a, b) => a.dateReceived - b.dateReceived), ...mappedRecons.filter((r) => !r.highPriority).sort((a, b) => a.dateReceived - b.dateReceived), ...mappedPlanningReferrals.filter((r) => !r.highPriority).sort((a, b) => a.dateReceived - b.dateReceived), + ...mappedInquiries.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; @@ -454,6 +471,24 @@ export class BoardComponent implements OnInit, OnDestroy { }; } + private mapInquiryToCard(inquiry: InquiryDto): CardData { + return { + status: inquiry.card!.status.code, + typeLabel: 'Inquiry', + title: `${inquiry.fileNumber} (${inquiry.inquirerLastName ?? 'Unknown'})`, + titleTooltip: inquiry.inquirerLastName ?? 'Unknown', + assignee: inquiry.card!.assignee, + id: inquiry.card!.uuid, + labels: [inquiry.type], + cardType: CardType.INQUIRY, + paused: false, + highPriority: inquiry.card!.highPriority, + cardUuid: inquiry.card!.uuid, + dateReceived: inquiry.card!.createdAt, + cssClasses: ['inquiry'], + }; + } + private openDialog(component: ComponentType<any>, data: any) { const dialogRef = this.dialog.open(component, { minWidth: '600px', @@ -510,6 +545,10 @@ export class BoardComponent implements OnInit, OnDestroy { const notification = await this.notificationService.fetchByCardUuid(card.uuid); this.openDialog(NotificationDialogComponent, notification); break; + case CardType.INQUIRY: + const inquiry = await this.inquiryService.fetchByCardUuid(card.uuid); + this.openDialog(InquiryDialogComponent, inquiry); + break; default: console.error('Card type is not configured for a dialog'); this.toastService.showErrorToast('Failed to open card'); diff --git a/alcs-frontend/src/app/features/board/board.module.ts b/alcs-frontend/src/app/features/board/board.module.ts index 9627822ea3..5a93728b5f 100644 --- a/alcs-frontend/src/app/features/board/board.module.ts +++ b/alcs-frontend/src/app/features/board/board.module.ts @@ -1,9 +1,11 @@ import { DragDropModule } from '@angular/cdk/drag-drop'; -import { CommonModule } from '@angular/common'; +import { CommonModule, NgIf } from '@angular/common'; import { NgModule } from '@angular/core'; import { MatGridListModule } from '@angular/material/grid-list'; +import { MatInputModule } from '@angular/material/input'; import { RouterModule, Routes } from '@angular/router'; import { MentionModule } from 'angular-mentions'; +import { NgxMaskDirective } from 'ngx-mask'; import { CardComponent } from '../../shared/card/card.component'; import { CommentComponent } from '../../shared/commenting/comment.component'; import { CommentsComponent } from '../../shared/commenting/comments.component'; @@ -17,6 +19,8 @@ import { AppModificationDialogComponent } from './dialogs/app-modification/app-m 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 { CreateInquiryDialogComponent } from './dialogs/inquiry/create/create-inquiry-dialog.component'; +import { InquiryDialogComponent } from './dialogs/inquiry/inquiry-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'; @@ -59,6 +63,8 @@ const routes: Routes = [ CreateNoiModificationDialogComponent, NoiModificationDialogComponent, NotificationDialogComponent, + InquiryDialogComponent, + CreateInquiryDialogComponent, ], imports: [ CommonModule, @@ -67,6 +73,9 @@ const routes: Routes = [ MatGridListModule, RouterModule.forChild(routes), MentionModule, + NgIf, + MatInputModule, + NgxMaskDirective, ], }) export class BoardModule {} diff --git a/alcs-frontend/src/app/features/board/dialogs/inquiry/create/create-inquiry-dialog.component.html b/alcs-frontend/src/app/features/board/dialogs/inquiry/create/create-inquiry-dialog.component.html new file mode 100644 index 0000000000..91a92f3b09 --- /dev/null +++ b/alcs-frontend/src/app/features/board/dialogs/inquiry/create/create-inquiry-dialog.component.html @@ -0,0 +1,184 @@ +<div mat-dialog-title> + <h2 class="card-title">Create Inquiry</h2> +</div> +<ng-container *ngIf="currentStep === 0"> + <form class="content" [formGroup]="createForm"> + <h2 class="subtitle">Type</h2> + <div class="subheading2">Select an inquiry type to learn more</div> + <mat-radio-group id="type" class="type-selector" [formControl]="typeControl" (change)="onChange($event)"> + <mat-radio-button *ngFor="let type of types" [value]="type.code">{{ type.label }}</mat-radio-button> + </mat-radio-group> + <div id="warningBanner" class="warning-banner" *ngIf="selectedType"> + <div class="subheading2">{{ selectedType.label }}</div> + <div style="margin-top: 12px"> + {{ selectedType.description }} + </div> + </div> + <mat-dialog-actions align="end"> + <div class="button-container"> + <button mat-stroked-button color="primary" [mat-dialog-close]="false">Cancel</button> + <button [disabled]="!selectedType" mat-flat-button color="primary" type="button" (click)="onNextStep()"> + Next + </button> + </div> + </mat-dialog-actions> + </form> +</ng-container> +<ng-container *ngIf="currentStep === 1"> + <form class="content" [formGroup]="createForm" (ngSubmit)="onSubmit()"> + <mat-dialog-content> + <h2>Details</h2> + <div class="two-item-row"> + <mat-form-field class="full-width" 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> + <div> + <ng-select + appearance="outline" + class="card-local-government" + [items]="localGovernments" + appendTo="body" + placeholder="Local/First Nation Government*" + bindLabel="name" + bindValue="uuid" + [clearable]="false" + [formControl]="localGovernmentControl" + (change)="onSelectGovernment($event)" + > + <ng-template ng-option-tmp let-item="item" let-search="searchTerm"> + <div [ngOptionHighlight]="search">{{ item.name }}</div> + </ng-template> + </ng-select> + </div> + <div> + <ng-select + appearance="outline" + [items]="regions" + appendTo="body" + placeholder="Region*" + bindLabel="label" + bindValue="code" + [clearable]="false" + [formControl]="regionControl" + > + </ng-select> + </div> + + <mat-form-field class="full-width" appearance="outline"> + <mat-label>Summary</mat-label> + <input matInput [formControl]="summaryControl" required /> + </mat-form-field> + </div> + + <h2>Inquirer</h2> + + <div class="two-item-row"> + <mat-form-field appearance="outline"> + <mat-label>First Name</mat-label> + <input matInput [formControl]="firstName" /> + </mat-form-field> + + <mat-form-field appearance="outline"> + <mat-label>Last Name</mat-label> + <input matInput [formControl]="lastName" /> + </mat-form-field> + + <mat-form-field class="full-width" appearance="outline"> + <mat-label>Organization</mat-label> + <input matInput [formControl]="organization" /> + </mat-form-field> + + <mat-form-field appearance="outline"> + <mat-label>Phone</mat-label> + <input + mask="(000) 000-0000" + matInput + maxlength="14" + placeholder="(555) 555-5555" + [formControl]="phone" + /> + </mat-form-field> + + <mat-form-field appearance="outline"> + <mat-label>Email</mat-label> + <input matInput [formControl]="email" /> + </mat-form-field> + </div> + + <h2>Parcels</h2> + + <table mat-table [dataSource]="tableSource"> + <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> + <tr mat-row *matRowDef="let row; columns: displayedColumns"></tr> + + <ng-container matColumnDef="index"> + <th mat-header-cell *matHeaderCellDef>#</th> + <td mat-cell *matCellDef="let row; let i = index"> + {{ i + 1 }} + </td> + </ng-container> + + <ng-container matColumnDef="address"> + <th mat-header-cell *matHeaderCellDef>Civic Address*</th> + <td mat-cell *matCellDef="let row; let i = index"> + <mat-form-field appearance="outline"> + <input required [ngModelOptions]="{ standalone: true }" matInput [(ngModel)]="parcels[i].civicAddress" /> + </mat-form-field> + </td> + </ng-container> + + <ng-container matColumnDef="pid"> + <th mat-header-cell *matHeaderCellDef>PID</th> + <td mat-cell *matCellDef="let row; let i = index"> + <mat-form-field appearance="outline"> + <input mask="000-000-000" [ngModelOptions]="{ standalone: true }" matInput [(ngModel)]="parcels[i].pid" /> + </mat-form-field> + </td> + </ng-container> + + <ng-container matColumnDef="pin"> + <th mat-header-cell *matHeaderCellDef>PIN</th> + <td mat-cell *matCellDef="let row; let i = index"> + <mat-form-field appearance="outline"> + <input [ngModelOptions]="{ standalone: true }" matInput [(ngModel)]="parcels[i].pin" /> + </mat-form-field> + </td> + </ng-container> + + <ng-container matColumnDef="actions"> + <th mat-header-cell *matHeaderCellDef>Action</th> + <td mat-cell *matCellDef="let row; let i = index"> + <button type="button" class="edit-btn" mat-flat-button (click)="onRemoveParcel(i)"> + <mat-icon color="warn">delete</mat-icon> + </button> + </td> + </ng-container> + + <tr class="mat-row no-data" *matNoDataRow> + <td class="text-center" colspan="5">No Parcels</td> + </tr> + </table> + <button style="margin-top: 12px;" mat-stroked-button type="button" (click)="onAddParcel()" color="primary">+ Add Parcel</button> + </mat-dialog-content> + <mat-dialog-actions align="end"> + <div class="button-container"> + <button mat-stroked-button color="primary" (click)="onPreviousStep()">Back</button> + <button [loading]="isLoading" mat-flat-button color="primary" type="submit" + [disabled]="!createForm.valid || !areParcelsValid()"> + Create + </button> + </div> + </mat-dialog-actions> + </form> +</ng-container> diff --git a/alcs-frontend/src/app/features/board/dialogs/inquiry/create/create-inquiry-dialog.component.scss b/alcs-frontend/src/app/features/board/dialogs/inquiry/create/create-inquiry-dialog.component.scss new file mode 100644 index 0000000000..c23f4c5928 --- /dev/null +++ b/alcs-frontend/src/app/features/board/dialogs/inquiry/create/create-inquiry-dialog.component.scss @@ -0,0 +1,45 @@ +@use '../../../../../../styles/colors'; + +.content { + padding: 0 24px; +} + +.warning-banner { + background-color: colors.$secondary-color-light; + padding: 20px 28px; +} + +:host::ng-deep { + .mdc-label { + font-size: 16px; + } +} + +.subtitle { + margin-top: 24px !important; + margin-bottom: 24px !important; +} + +.two-item-row { + display: grid; + grid-template-columns: 1fr 1fr; + grid-column-gap: 24px; + grid-row-gap: 24px; + margin-bottom: 24px; +} + +.full-width { + grid-column: 1 /3; + width: 100%; +} + +.type-selector { + display: flex; + flex-direction: column; + margin: 12px 0; +} + +h2 { + color: colors.$black !important; + margin-bottom: 18px !important; +} diff --git a/alcs-frontend/src/app/features/board/dialogs/inquiry/create/create-inquiry-dialog.component.spec.ts b/alcs-frontend/src/app/features/board/dialogs/inquiry/create/create-inquiry-dialog.component.spec.ts new file mode 100644 index 0000000000..78e8b4433a --- /dev/null +++ b/alcs-frontend/src/app/features/board/dialogs/inquiry/create/create-inquiry-dialog.component.spec.ts @@ -0,0 +1,56 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatAutocompleteModule } from '@angular/material/autocomplete'; +import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { MatSelectModule } from '@angular/material/select'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { ActivatedRoute } from '@angular/router'; +import { CreateInquiryDialogComponent } from './create-inquiry-dialog.component'; + +describe('CreateInquiryDialogComponent', () => { + let component: CreateInquiryDialogComponent; + let fixture: ComponentFixture<CreateInquiryDialogComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [CreateInquiryDialogComponent], + imports: [ + MatDialogModule, + HttpClientTestingModule, + MatFormFieldModule, + MatDividerModule, + MatInputModule, + MatSelectModule, + BrowserAnimationsModule, + MatSnackBarModule, + MatAutocompleteModule, + ], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { provide: MAT_DIALOG_DATA, useValue: {} }, + { provide: ActivatedRoute, useValue: {} }, + ], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(CreateInquiryDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should render the form fields', () => { + fixture.detectChanges(); + + const compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('#type')).toBeTruthy(); + }); +}); diff --git a/alcs-frontend/src/app/features/board/dialogs/inquiry/create/create-inquiry-dialog.component.ts b/alcs-frontend/src/app/features/board/dialogs/inquiry/create/create-inquiry-dialog.component.ts new file mode 100644 index 0000000000..5cc5abb3bd --- /dev/null +++ b/alcs-frontend/src/app/features/board/dialogs/inquiry/create/create-inquiry-dialog.component.ts @@ -0,0 +1,175 @@ +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 { MatRadioChange } from '@angular/material/radio'; +import { MatTableDataSource } from '@angular/material/table'; +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 { InquiryParcelCreateDto } from '../../../../../services/inquiry/inquiry-parcel/inquiry-parcel.dto'; +import { CreateInquiryDto, InquiryTypeDto } from '../../../../../services/inquiry/inquiry.dto'; +import { InquiryService } from '../../../../../services/inquiry/inquiry.service'; + +@Component({ + selector: 'app-create-inquiry', + templateUrl: './create-inquiry-dialog.component.html', + styleUrls: ['./create-inquiry-dialog.component.scss'], +}) +export class CreateInquiryDialogComponent implements OnInit, OnDestroy { + $destroy = new Subject<void>(); + regions: ApplicationRegionDto[] = []; + localGovernments: ApplicationLocalGovernmentDto[] = []; + types: InquiryTypeDto[] = []; + isLoading = false; + currentStep = 0; + + regionControl = new FormControl<string | null>(null, [Validators.required]); + localGovernmentControl = new FormControl<string | null>(null, [Validators.required]); + typeControl = new FormControl<string | null>(null, [Validators.required]); + summaryControl = new FormControl<string | null>(null, [Validators.required]); + submissionDateControl = new FormControl<Moment | null>(null, [Validators.required]); + + firstName = new FormControl<string | null>(null, []); + lastName = new FormControl<string | null>(null, []); + organization = new FormControl<string | null>(null, []); + phone = new FormControl<string | null>(null, []); + email = new FormControl<string | null>(null, [Validators.email]); + + displayedColumns = ['index', 'address', 'pid', 'pin', 'actions']; + parcels: InquiryParcelCreateDto[] = []; + tableSource: MatTableDataSource<InquiryParcelCreateDto> = new MatTableDataSource(); + + createForm = new FormGroup({ + region: this.regionControl, + localGovernment: this.localGovernmentControl, + type: this.typeControl, + summary: this.summaryControl, + submissionDate: this.submissionDateControl, + firstName: this.firstName, + lastName: this.lastName, + organization: this.organization, + phone: this.phone, + email: this.email, + }); + selectedType: InquiryTypeDto | undefined; + + constructor( + @Inject(MAT_DIALOG_DATA) + public data: { + currentBoardCode: string; + }, + private dialogRef: MatDialogRef<CreateInquiryDialogComponent>, + private inquiryService: InquiryService, + private cardService: CardService, + private applicationService: ApplicationService, + private localGovernmentService: ApplicationLocalGovernmentService, + private router: Router, + private activatedRoute: ActivatedRoute, + ) {} + + ngOnInit(): void { + this.cardService.fetchCodes(); + + this.localGovernmentService.list().then((res) => { + this.localGovernments = res; + }); + + this.applicationService.$applicationRegions.pipe(takeUntil(this.$destroy)).subscribe((regions) => { + this.regions = regions; + }); + + this.loadTypes(); + } + + areParcelsValid() { + return this.parcels.reduce((previousValue, parcel) => { + const addressValid = !!parcel.civicAddress && parcel.civicAddress.length > 0; + const pidValid = parcel.pid ? parcel.pid.length === 9 : true; + return previousValue && pidValid && addressValid; + }, true); + } + + async onSubmit() { + console.log(this.parcels); + const form = this.createForm.valid; + try { + this.isLoading = true; + const formValues = this.createForm.getRawValue(); + const createDto: CreateInquiryDto = { + boardCode: this.data.currentBoardCode, + submittedToAlcDate: formValues.submissionDate!.valueOf(), + summary: formValues.summary!, + regionCode: formValues.region!, + localGovernmentUuid: formValues.localGovernment!, + typeCode: formValues.type!, + inquirerFirstName: formValues.firstName ?? undefined, + inquirerLastName: formValues.lastName ?? undefined, + inquirerOrganization: formValues.organization ?? undefined, + inquirerEmail: formValues.email ?? undefined, + inquirerPhone: formValues.phone ?? undefined, + parcels: this.parcels, + }; + + const res = await this.inquiryService.create(createDto); + this.dialogRef.close(true); + if (res) { + await this.router.navigate(this.activatedRoute.snapshot.url, { + queryParams: res.card?.uuid && res.card.type ? { card: res.card.uuid, type: res.card.type } : {}, + relativeTo: this.activatedRoute, + }); + } + } finally { + this.isLoading = false; + } + } + + onSelectGovernment(value: ApplicationLocalGovernmentDto) { + this.createForm.patchValue({ + region: value.preferredRegionCode, + }); + } + + ngOnDestroy(): void { + this.$destroy.next(); + this.$destroy.complete(); + } + + private async loadTypes() { + const types = await this.inquiryService.fetchTypes(); + if (types) { + this.types = types; + } + } + + onNextStep() { + this.currentStep = 1; + } + + onPreviousStep() { + this.currentStep = 0; + } + + onChange($event: MatRadioChange) { + const selectedType = this.types.find((type) => type.code === $event.value); + if (selectedType) { + this.selectedType = selectedType; + } + } + + onRemoveParcel(index: number) { + this.parcels.splice(index, 1); + this.tableSource = new MatTableDataSource(this.parcels); + } + + onAddParcel() { + this.parcels.push({ + civicAddress: '', + }); + this.tableSource = new MatTableDataSource(this.parcels); + } +} diff --git a/alcs-frontend/src/app/features/board/dialogs/inquiry/inquiry-dialog.component.html b/alcs-frontend/src/app/features/board/dialogs/inquiry/inquiry-dialog.component.html new file mode 100644 index 0000000000..2fff90b240 --- /dev/null +++ b/alcs-frontend/src/app/features/board/dialogs/inquiry/inquiry-dialog.component.html @@ -0,0 +1,123 @@ +<div mat-dialog-title> + <div class="close"> + <h6 class="card-type-label">Planning Review</h6> + <button mat-icon-button [mat-dialog-close]="isDirty"> + <mat-icon>close</mat-icon> + </button> + </div> + <div class="header-row"> + <div class="left"> + <h3 class="card-title center"> + <span class="margin-right">{{ cardTitle }}</span> + <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', inquiry.fileNumber]" + > + View Detail + </button> + </div> + </div> + <div> + <span class="region">{{ inquiry.localGovernment.name }} - {{ inquiry.region.label }} Region</span> + </div> + <div class="split"> + <div class="body-text"> + <app-application-type-pill *ngIf="inquiry.open" [type]="OPEN_TYPE"></app-application-type-pill> + <app-application-type-pill *ngIf="!inquiry.open" [type]="CLOSED_TYPE"></app-application-type-pill> + </div> + <div class="right"> + <button matTooltip="Move Board" [matMenuTriggerFor]="moveMenu" mat-icon-button> + <mat-icon>move_down</mat-icon> + </button> + <mat-menu class="move-board-menu" xPosition="before" #moveMenu="matMenu"> + <button (click)="onBoardSelected(board)" *ngFor="let board of allowedBoards" mat-menu-item> + <div class="board-menu-item"> + <span class="favourite-board-icon-container" + ><mat-icon *ngIf="board.isFavourite" class="favourite-board-icon">star</mat-icon> + </span> + <span>{{ board.title }}</span> + <span *ngIf="card && card.boardCode === board.code" + ><mat-icon class="selected-board-icon">check</mat-icon></span + > + </div> + </button> + </mat-menu> + <button + *ngIf="canArchive" + matTooltip="Archive Card" + class="toggle-priority" + (click)="onArchiveCard()" + mat-icon-button + > + <mat-icon>archive</mat-icon> + </button> + <button + *ngIf="card" + [matTooltip]="card.highPriority ? 'Remove Priority' : 'Add Priority'" + class="toggle-priority" + (click)="onTogglePriority()" + mat-icon-button + > + <div + class="priority" + [ngClass]="{ + 'filled-priority': card.highPriority, + 'empty-priority': !card.highPriority + }" + ></div> + </button> + </div> + </div> +</div> +<mat-dialog-content> + <div class="select-container"> + <ng-select + class="card-type" + appearance="outline" + [items]="boardStatuses" + placeholder="Workflow Stage" + bindLabel="label" + bindValue="statusCode" + [clearable]="false" + [(ngModel)]="selectedApplicationStatus" + (change)="onStatusSelected($event)" + > + <ng-template ng-option-tmp let-item="item"> + <span [innerHTML]="item.label"> </span> + </ng-template> + <ng-template ng-label-tmp let-item="item"> + <span [innerHTML]="item.label"> </span> + </ng-template> + </ng-select> + <ng-select + class="card-assignee" + appearance="outline" + [items]="$users | async" + placeholder="Assigned Planner" + bindLabel="prettyName" + bindValue="prettyName" + [(ngModel)]="selectedAssigneeName" + [searchFn]="filterAssigneeList" + (change)="onAssigneeSelected($event)" + > + <ng-template ng-option-tmp let-item="item" let-search="searchTerm"> + <div class="assignee-card-body"> + <p [ngOptionHighlight]="search" class="assignee-card-name">{{ item.prettyName }}</p> + <p class="assignee-card-email" [ngOptionHighlight]="search">{{ item.email }}</p> + </div> + </ng-template> + </ng-select> + </div> + <div *ngIf="card" class="subtasks-wrapper"> + <app-subtasks [cardUuid]="card.uuid"></app-subtasks> + </div> + <div *ngIf="card" class="card-comments-wrapper"> + <app-comments [cardUuid]="card.uuid" [notificationTitle]="cardTitle"></app-comments> + </div> +</mat-dialog-content> diff --git a/alcs-frontend/src/app/features/board/dialogs/inquiry/inquiry-dialog.component.spec.ts b/alcs-frontend/src/app/features/board/dialogs/inquiry/inquiry-dialog.component.spec.ts new file mode 100644 index 0000000000..0bf5738c67 --- /dev/null +++ b/alcs-frontend/src/app/features/board/dialogs/inquiry/inquiry-dialog.component.spec.ts @@ -0,0 +1,122 @@ +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 { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { NgSelectModule } from '@ng-select/ng-select'; +import { BehaviorSubject } from 'rxjs'; +import { BoardService, BoardWithFavourite } from '../../../../services/board/board.service'; +import { CardService } from '../../../../services/card/card.service'; +import { InquiryDto } from '../../../../services/inquiry/inquiry.dto'; +import { ToastService } from '../../../../services/toast/toast.service'; +import { AssigneeDto } from '../../../../services/user/user.dto'; +import { UserService } from '../../../../services/user/user.service'; +import { ConfirmationDialogService } from '../../../../shared/confirmation-dialog/confirmation-dialog.service'; +import { MomentPipe } from '../../../../shared/pipes/moment.pipe'; +import { InquiryDialogComponent } from './inquiry-dialog.component'; + +describe('InquiryDialogComponent', () => { + let component: InquiryDialogComponent; + let fixture: ComponentFixture<InquiryDialogComponent>; + let mockUserService: DeepMocked<UserService>; + let mockBoardService: DeepMocked<BoardService>; + + const mockInquiryDto: InquiryDto = { + uuid: '', + open: true, + type: {} as any, + region: { + code: 'region-code', + label: 'region', + description: 'WHY', + }, + card: { + status: {} as any, + assignee: {} as any, + } as any, + localGovernment: { + name: 'local-gov', + uuid: 'uuid', + preferredRegionCode: 'CODE', + isFirstNation: false, + }, + fileNumber: 'file-number', + dateSubmittedToAlc: 0, + summary: '', + localGovernmentUuid: '', + regionCode: '', + typeCode: '', + }; + + beforeEach(async () => { + const mockDialogRef = { + close: jest.fn(), + afterClosed: jest.fn(), + backdropClick: () => new EventEmitter(), + subscribe: jest.fn(), + }; + + mockUserService = createMock(); + mockUserService.$assignableUsers = new BehaviorSubject<AssigneeDto[]>([]); + + mockBoardService = createMock(); + mockBoardService.$boards = new BehaviorSubject<BoardWithFavourite[]>([]); + + await TestBed.configureTestingModule({ + declarations: [InquiryDialogComponent, MomentPipe], + providers: [ + { + provide: MAT_DIALOG_DATA, + useValue: { + statusDetails: { + code: 'fake', + }, + }, + }, + { + provide: UserService, + useValue: mockUserService, + }, + { + provide: CardService, + useValue: {}, + }, + { + provide: BoardService, + useValue: mockBoardService, + }, + { + provide: ToastService, + useValue: {}, + }, + { + provide: ConfirmationDialogService, + useValue: {}, + }, + { provide: MatDialogRef, useValue: mockDialogRef }, + { provide: ConfirmationDialogService, useValue: {} }, + ], + imports: [ + HttpClientTestingModule, + MatDialogModule, + MatSnackBarModule, + FormsModule, + MatMenuModule, + NgSelectModule, + ], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(InquiryDialogComponent); + component = fixture.componentInstance; + component.data = mockInquiryDto; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alcs-frontend/src/app/features/board/dialogs/inquiry/inquiry-dialog.component.ts b/alcs-frontend/src/app/features/board/dialogs/inquiry/inquiry-dialog.component.ts new file mode 100644 index 0000000000..ca21aaf368 --- /dev/null +++ b/alcs-frontend/src/app/features/board/dialogs/inquiry/inquiry-dialog.component.ts @@ -0,0 +1,91 @@ +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 { InquiryDto } from '../../../../services/inquiry/inquiry.dto'; +import { InquiryService } from '../../../../services/inquiry/inquiry.service'; +import { ToastService } from '../../../../services/toast/toast.service'; +import { UserService } from '../../../../services/user/user.service'; +import { ApplicationPill } from '../../../../shared/application-type-pill/application-type-pill.component'; +import { + CLOSED_PR_LABEL, + OPEN_PR_LABEL, +} from '../../../../shared/application-type-pill/application-type-pill.constants'; +import { ConfirmationDialogService } from '../../../../shared/confirmation-dialog/confirmation-dialog.service'; +import { CardDialogComponent } from '../card-dialog/card-dialog.component'; + +@Component({ + selector: 'app-inquiry-detail-dialog', + templateUrl: './inquiry-dialog.component.html', + styleUrls: ['../card-dialog/card-dialog.component.scss'], +}) +export class InquiryDialogComponent extends CardDialogComponent implements OnInit { + selectedRegion?: string; + title?: string; + planningType?: ApplicationPill; + cardTitle = ''; + OPEN_TYPE = OPEN_PR_LABEL; + CLOSED_TYPE = CLOSED_PR_LABEL; + + inquiry: InquiryDto = this.data; + + constructor( + @Inject(MAT_DIALOG_DATA) public data: InquiryDto, + dialogRef: MatDialogRef<InquiryDialogComponent>, + boardService: BoardService, + userService: UserService, + authService: AuthenticationService, + toastService: ToastService, + private inquiryService: InquiryService, + confirmationDialogService: ConfirmationDialogService, + cardService: CardService, + private router: Router, + ) { + super(authService, dialogRef, cardService, confirmationDialogService, toastService, userService, boardService); + } + + override ngOnInit(): void { + super.ngOnInit(); + + this.inquiry = this.data; + this.planningType = { + ...this.data.type, + borderColor: this.data.type.backgroundColor, + }; + this.populateCardData(this.data.card!); + + this.selectedRegion = this.data.region.code; + this.cardTitle = `${this.data.fileNumber} (${this.data.inquirerLastName ?? 'Unknown'})`; + + this.title = this.data.fileNumber; + } + + private async reload() { + const inquiry = await this.inquiryService.fetchByCardUuid(this.inquiry.card!.uuid); + if (inquiry && inquiry.card) { + await this.populateCardData(inquiry.card); + } + } + + async onBoardSelected(board: BoardWithFavourite) { + this.selectedBoard = board.code; + try { + await this.boardService.changeBoard(this.inquiry.card!.uuid, board.code); + const loadedBoard = await this.boardService.fetchBoardDetail(board.code); + if (loadedBoard) { + this.boardStatuses = loadedBoard.statuses; + } + + this.isDirty = true; + const toast = this.toastService.showSuccessToast(`Inquiry moved to ${board.title}`, 'Go to Board'); + toast.onAction().subscribe(() => { + this.router.navigate(['/board', board.code]); + }); + await this.reload(); + } catch (e) { + this.toastService.showErrorToast('Failed to move to new board'); + } + } +} diff --git a/alcs-frontend/src/app/services/board/board.dto.ts b/alcs-frontend/src/app/services/board/board.dto.ts index 36eeadaed2..09f2b9674e 100644 --- a/alcs-frontend/src/app/services/board/board.dto.ts +++ b/alcs-frontend/src/app/services/board/board.dto.ts @@ -2,6 +2,7 @@ import { CardType } from '../../shared/card/card.component'; import { ApplicationModificationDto } from '../application/application-modification/application-modification.dto'; import { ApplicationReconsiderationDto } from '../application/application-reconsideration/application-reconsideration.dto'; import { ApplicationDto } from '../application/application.dto'; +import { InquiryDto } from '../inquiry/inquiry.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'; @@ -34,4 +35,5 @@ export interface CardsDto { noticeOfIntents: NoticeOfIntentDto[]; noiModifications: NoticeOfIntentModificationDto[]; notifications: NotificationDto[]; + inquiries: InquiryDto[]; } diff --git a/alcs-frontend/src/app/services/inquiry/inquiry-parcel/inquiry-parcel.dto.ts b/alcs-frontend/src/app/services/inquiry/inquiry-parcel/inquiry-parcel.dto.ts new file mode 100644 index 0000000000..455528c5a9 --- /dev/null +++ b/alcs-frontend/src/app/services/inquiry/inquiry-parcel/inquiry-parcel.dto.ts @@ -0,0 +1,20 @@ +export interface InquiryParcelDto { + uuid: string; + inquiryUuid: string; + pid?: string | null; + pin?: string | null; + civicAddress: string; +} + +export interface InquiryParcelCreateDto { + civicAddress: string; + pid?: string | null; + pin?: string | null; +} + +export interface InquiryParcelUpdateDto { + uuid: string; + pid?: string | null; + pin?: string | null; + civicAddress: string; +} diff --git a/alcs-frontend/src/app/services/inquiry/inquiry.dto.ts b/alcs-frontend/src/app/services/inquiry/inquiry.dto.ts new file mode 100644 index 0000000000..22cf737e3f --- /dev/null +++ b/alcs-frontend/src/app/services/inquiry/inquiry.dto.ts @@ -0,0 +1,74 @@ +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'; +import { InquiryParcelCreateDto } from './inquiry-parcel/inquiry-parcel.dto'; + +export interface InquiryTypeDto extends BaseCodeDto { + shortLabel: string; + backgroundColor: string; + textColor: string; +} + +export interface CreateInquiryDto { + boardCode: string; + summary: string; + submittedToAlcDate: number; + localGovernmentUuid: string; + typeCode: string; + regionCode: string; + inquirerFirstName?: string; + inquirerLastName?: string; + inquirerOrganization?: string; + inquirerPhone?: string; + inquirerEmail?: string; + parcels?: InquiryParcelCreateDto[]; +} + +export interface InquiryDto { + uuid: string; + fileNumber: string; + summary: string; + open: boolean; + dateSubmittedToAlc: number; + localGovernmentUuid: string; + typeCode: string; + regionCode: string; + inquirerFirstName?: string; + inquirerLastName?: string; + inquirerOrganization?: string; + inquirerPhone?: string; + inquirerEmail?: string; + parcels?: InquiryParcelCreateDto[]; + localGovernment: ApplicationLocalGovernmentDto; + region: ApplicationRegionDto; + card?: CardDto; + type: InquiryTypeDto; +} + +export interface UpdateInquiryDto { + uuid: string; + summary: string; + dateSubmittedToAlc: number; + typeCode: string; + inquirerFirstName?: string; + inquirerLastName?: string; + inquirerOrganization?: string; + inquirerPhone?: string; + inquirerEmail?: string; + parcels?: InquiryParcelCreateDto[]; +} + +export interface CreateInquiryServiceDto { + summary: string; + dateSubmittedToAlc: Date; + localGovernmentUuid: string; + typeCode: string; + regionCode: string; + inquirerFirstName?: string; + inquirerLastName?: string; + inquirerOrganization?: string; + inquirerPhone?: string; + inquirerEmail?: string; + parcels?: InquiryParcelCreateDto[]; +} diff --git a/alcs-frontend/src/app/services/inquiry/inquiry.service.spec.ts b/alcs-frontend/src/app/services/inquiry/inquiry.service.spec.ts new file mode 100644 index 0000000000..b8d39c8b1c --- /dev/null +++ b/alcs-frontend/src/app/services/inquiry/inquiry.service.spec.ts @@ -0,0 +1,103 @@ +import { HttpClient } from '@angular/common/http'; +import { TestBed } from '@angular/core/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { of, throwError } from 'rxjs'; +import { ToastService } from '../toast/toast.service'; +import { InquiryService } from './inquiry.service'; + +describe('PlanningReviewService', () => { + let service: InquiryService; + let httpClient: DeepMocked<HttpClient>; + let toastService: DeepMocked<ToastService>; + + beforeEach(() => { + httpClient = createMock(); + toastService = createMock(); + + TestBed.configureTestingModule({ + providers: [ + { + provide: HttpClient, + useValue: httpClient, + }, + { + provide: ToastService, + useValue: toastService, + }, + ], + }); + service = TestBed.inject(InquiryService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should call post for create', async () => { + httpClient.post.mockReturnValue( + of({ + fileNumber: '1', + }), + ); + + await service.create({ + summary: '', + boardCode: '', + typeCode: '', + submittedToAlcDate: 0, + localGovernmentUuid: '', + regionCode: '', + }); + + expect(httpClient.post).toHaveBeenCalledTimes(1); + }); + + it('should show an error toast message if create fails', async () => { + httpClient.post.mockReturnValue( + throwError(() => { + new Error(''); + }), + ); + + const res = await service.create({ + summary: '', + boardCode: '', + typeCode: '', + submittedToAlcDate: 0, + localGovernmentUuid: '', + regionCode: '', + }); + + expect(httpClient.post).toHaveBeenCalledTimes(1); + expect(res).toBeUndefined(); + expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + + it('should fetch planning reviews by card', async () => { + httpClient.get.mockReturnValue( + of({ + fileNumber: '1', + }), + ); + + const res = await service.fetchByCardUuid('1'); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(res).toBeDefined(); + expect(res!.fileNumber).toEqual('1'); + }); + + it('should show an error toast message if fetch by card fails', async () => { + httpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }), + ); + + const res = await service.fetchByCardUuid('1'); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(res).toBeUndefined(); + expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); + }); +}); diff --git a/alcs-frontend/src/app/services/inquiry/inquiry.service.ts b/alcs-frontend/src/app/services/inquiry/inquiry.service.ts new file mode 100644 index 0000000000..5c6ace217d --- /dev/null +++ b/alcs-frontend/src/app/services/inquiry/inquiry.service.ts @@ -0,0 +1,70 @@ +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 { CreateInquiryDto, InquiryDto, InquiryTypeDto, UpdateInquiryDto } from './inquiry.dto'; + +@Injectable({ + providedIn: 'root', +}) +export class InquiryService { + private url = `${environment.apiUrl}/inquiry`; + + constructor( + private http: HttpClient, + private toastService: ToastService, + ) {} + + async create(meeting: CreateInquiryDto) { + try { + const res = await firstValueFrom(this.http.post<InquiryDto>(`${this.url}`, meeting)); + this.toastService.showSuccessToast('Inquiry created'); + return res; + } catch (err) { + console.error(err); + this.toastService.showErrorToast('Failed to create inquiry'); + } + return; + } + + async fetchByCardUuid(id: string) { + try { + return await firstValueFrom(this.http.get<InquiryDto>(`${this.url}/card/${id}`)); + } catch (err) { + console.error(err); + this.toastService.showErrorToast('Failed to fetch inquiry'); + } + return; + } + + async fetchTypes() { + try { + return await firstValueFrom(this.http.get<InquiryTypeDto[]>(`${this.url}/types`)); + } catch (err) { + console.error(err); + this.toastService.showErrorToast('Failed to fetch inquiry types'); + } + return; + } + + // async fetchDetailedByFileNumber(fileNumber: string) { + // try { + // return await firstValueFrom(this.http.get<PlanningReviewDetailedDto>(`${this.url}/${fileNumber}`)); + // } catch (err) { + // console.error(err); + // this.toastService.showErrorToast('Failed to fetch inquiry'); + // } + // return; + // } + + async update(fileNumber: string, updateDto: UpdateInquiryDto) { + try { + return await firstValueFrom(this.http.post<UpdateInquiryDto>(`${this.url}/${fileNumber}`, updateDto)); + } catch (err) { + console.error(err); + this.toastService.showErrorToast('Failed to update inquiry'); + } + return; + } +} diff --git a/alcs-frontend/src/app/shared/card/card.component.ts b/alcs-frontend/src/app/shared/card/card.component.ts index 94dfc96b41..75115073ee 100644 --- a/alcs-frontend/src/app/shared/card/card.component.ts +++ b/alcs-frontend/src/app/shared/card/card.component.ts @@ -41,6 +41,7 @@ export enum CardType { NOI = 'NOI', NOI_MODI = 'NOIM', NOTIFICATION = 'NOTI', + INQUIRY = 'INQR', } const lineHeight = 24; 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 ebc8fdb7b1..a249173413 100644 --- a/services/apps/alcs/src/alcs/board/board.controller.spec.ts +++ b/services/apps/alcs/src/alcs/board/board.controller.spec.ts @@ -11,6 +11,7 @@ import { ApplicationService } from '../application/application.service'; import { CARD_TYPE, CardType } from '../card/card-type/card-type.entity'; import { Card } from '../card/card.entity'; import { CardService } from '../card/card.service'; +import { InquiryService } from '../inquiry/inquiry.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'; @@ -32,6 +33,7 @@ describe('BoardController', () => { let noticeOfIntentService: DeepMocked<NoticeOfIntentService>; let noiModificationService: DeepMocked<NoticeOfIntentModificationService>; let notificationService: DeepMocked<NotificationService>; + let inquiryService: DeepMocked<InquiryService>; let mockBoard; beforeEach(async () => { @@ -44,6 +46,7 @@ describe('BoardController', () => { noticeOfIntentService = createMock(); noiModificationService = createMock(); notificationService = createMock(); + inquiryService = createMock(); mockBoard = new Board({ allowedCardTypes: [], @@ -67,6 +70,8 @@ describe('BoardController', () => { noiModificationService.mapToDtos.mockResolvedValue([]); notificationService.getByBoard.mockResolvedValue([]); notificationService.mapToDtos.mockResolvedValue([]); + inquiryService.getByBoard.mockResolvedValue([]); + inquiryService.mapToDtos.mockResolvedValue([]); const module: TestingModule = await Test.createTestingModule({ imports: [ @@ -102,6 +107,10 @@ describe('BoardController', () => { provide: NotificationService, useValue: notificationService, }, + { + provide: InquiryService, + useValue: inquiryService, + }, { provide: ClsService, useValue: {}, diff --git a/services/apps/alcs/src/alcs/board/board.controller.ts b/services/apps/alcs/src/alcs/board/board.controller.ts index b31db99d36..448b438cc0 100644 --- a/services/apps/alcs/src/alcs/board/board.controller.ts +++ b/services/apps/alcs/src/alcs/board/board.controller.ts @@ -16,6 +16,7 @@ import { ApplicationService } from '../application/application.service'; import { CARD_TYPE } from '../card/card-type/card-type.entity'; import { CardCreateDto } from '../card/card.dto'; import { CardService } from '../card/card.service'; +import { InquiryService } from '../inquiry/inquiry.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'; @@ -38,6 +39,7 @@ export class BoardController { private noiModificationService: NoticeOfIntentModificationService, private noticeOfIntentService: NoticeOfIntentService, private notificationService: NotificationService, + private inquiryService: InquiryService, @InjectMapper() private autoMapper: Mapper, ) {} @@ -95,6 +97,10 @@ export class BoardController { ? await this.notificationService.getByBoard(board.uuid) : []; + const inquiries = allowedCodes.includes(CARD_TYPE.INQUIRY) + ? await this.inquiryService.getByBoard(board.uuid) + : []; + return { board: await this.autoMapper.mapAsync(board, Board, BoardDto), applications: await this.applicationService.mapToDtos(applications), @@ -107,6 +113,7 @@ export class BoardController { noiModifications: await this.noiModificationService.mapToDtos(noiModifications), notifications: await this.notificationService.mapToDtos(notifications), + inquiries: await this.inquiryService.mapToDtos(inquiries), }; } diff --git a/services/apps/alcs/src/alcs/board/board.dto.ts b/services/apps/alcs/src/alcs/board/board.dto.ts index 4a281bc64e..50396bcd67 100644 --- a/services/apps/alcs/src/alcs/board/board.dto.ts +++ b/services/apps/alcs/src/alcs/board/board.dto.ts @@ -6,6 +6,7 @@ export enum BOARD_CODES { SOIL = 'soil', EXECUTIVE_COMMITTEE = 'exec', REGIONAL_PLANNING = 'rppp', + INQUIRY = 'incr', } export class MinimalBoardDto { diff --git a/services/apps/alcs/src/alcs/board/board.module.ts b/services/apps/alcs/src/alcs/board/board.module.ts index f4567db02d..2a452fab52 100644 --- a/services/apps/alcs/src/alcs/board/board.module.ts +++ b/services/apps/alcs/src/alcs/board/board.module.ts @@ -4,6 +4,7 @@ import { BoardAutomapperProfile } from '../../common/automapper/board.automapper import { ApplicationModule } from '../application/application.module'; import { CardModule } from '../card/card.module'; import { ApplicationDecisionModule } from '../application-decision/application-decision.module'; +import { InquiryModule } from '../inquiry/inquiry.module'; import { NoticeOfIntentDecisionModule } from '../notice-of-intent-decision/notice-of-intent-decision.module'; import { NoticeOfIntentModule } from '../notice-of-intent/notice-of-intent.module'; import { NotificationModule } from '../notification/notification.module'; @@ -23,6 +24,7 @@ import { BoardService } from './board.service'; forwardRef(() => NoticeOfIntentModule), NoticeOfIntentDecisionModule, NotificationModule, + forwardRef(() => InquiryModule), ], controllers: [BoardController], providers: [BoardService, BoardAutomapperProfile], 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 6ea64d16aa..464a826d54 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 @@ -11,7 +11,7 @@ export enum CARD_TYPE { NOI = 'NOI', NOI_MODI = 'NOIM', NOTIFICATION = 'NOTI', - INQUIRY = 'INCR', + INQUIRY = 'INQR', } @Entity() diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.controller.spec.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.controller.spec.ts index 8d55460dc1..64dc1cf36e 100644 --- a/services/apps/alcs/src/alcs/inquiry/inquiry.controller.spec.ts +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.controller.spec.ts @@ -1,12 +1,42 @@ +import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; import { Test, TestingModule } from '@nestjs/testing'; +import { classes } from 'automapper-classes'; +import { AutomapperModule } from 'automapper-nestjs'; +import { InquiryProfile } from '../../common/automapper/inquiry.automapper.profile'; +import { Board } from '../board/board.entity'; +import { BoardService } from '../board/board.service'; import { InquiryController } from './inquiry.controller'; +import { InquiryDto } from './inquiry.dto'; +import { Inquiry } from './inquiry.entity'; +import { InquiryService } from './inquiry.service'; describe('InquiryController', () => { let controller: InquiryController; + let mockInquiryService: DeepMocked<InquiryService>; + let mockBoardService: DeepMocked<BoardService>; beforeEach(async () => { + mockInquiryService = createMock(); + mockBoardService = createMock(); + const module: TestingModule = await Test.createTestingModule({ + imports: [ + AutomapperModule.forRoot({ + strategyInitializer: classes(), + }), + ], controllers: [InquiryController], + providers: [ + InquiryProfile, + { + provide: InquiryService, + useValue: mockInquiryService, + }, + { + provide: BoardService, + useValue: mockBoardService, + }, + ], }).compile(); controller = module.get<InquiryController>(InquiryController); @@ -15,4 +45,34 @@ describe('InquiryController', () => { it('should be defined', () => { expect(controller).toBeDefined(); }); + + it('should load the board and call create on the service for create', async () => { + mockBoardService.getOneOrFail.mockResolvedValue(new Board()); + mockInquiryService.create.mockResolvedValue(new Inquiry()); + + const res = await controller.create({ + boardCode: '', + localGovernmentUuid: '', + regionCode: '', + submittedToAlcDate: 0, + summary: '', + typeCode: '', + }); + + expect(res).toBeDefined(); + expect(mockBoardService.getOneOrFail).toHaveBeenCalledTimes(1); + expect(mockInquiryService.create).toHaveBeenCalledTimes(1); + }); + + it('should call through for get by card', async () => { + const mockUuid = 'uuid'; + mockInquiryService.getByCardUuid.mockResolvedValue(new Inquiry()); + mockInquiryService.mapToDtos.mockResolvedValue([new InquiryDto()]); + + const res = await controller.getByCard(mockUuid); + + expect(res).toBeDefined(); + expect(mockInquiryService.getByCardUuid).toHaveBeenCalledTimes(1); + expect(mockInquiryService.mapToDtos).toHaveBeenCalledTimes(1); + }); }); diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.controller.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.controller.ts index 1fc2d5dcd1..7f89faa17e 100644 --- a/services/apps/alcs/src/alcs/inquiry/inquiry.controller.ts +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.controller.ts @@ -1,6 +1,50 @@ -import { Controller } from '@nestjs/common'; +import { Body, Controller, Get, Param, Post } from '@nestjs/common'; +import { Mapper } from 'automapper-core'; +import { InjectMapper } from 'automapper-nestjs'; +import { ROLES_ALLOWED_BOARDS } from '../../common/authorization/roles'; +import { UserRoles } from '../../common/authorization/roles.decorator'; +import { formatIncomingDate } from '../../utils/incoming-date.formatter'; +import { BoardService } from '../board/board.service'; +import { InquiryType } from './inquiry-type.entity'; +import { CreateInquiryDto, InquiryDto, InquiryTypeDto } from './inquiry.dto'; +import { Inquiry } from './inquiry.entity'; +import { InquiryService } from './inquiry.service'; @Controller('inquiry') export class InquiryController { - // TODO will be implemented in other ticket + constructor( + private inquiryService: InquiryService, + private boardService: BoardService, + @InjectMapper() private mapper: Mapper, + ) {} + + @Get('types') + async getTypes() { + const types = await this.inquiryService.listTypes(); + return this.mapper.mapArray(types, InquiryType, InquiryTypeDto); + } + + @Post() + async create(@Body() createDto: CreateInquiryDto) { + const targetBoard = await this.boardService.getOneOrFail({ + code: createDto.boardCode, + }); + + const createdInquiry = await this.inquiryService.create( + { + ...createDto, + dateSubmittedToAlc: formatIncomingDate(createDto.submittedToAlcDate)!, + }, + targetBoard, + ); + return this.mapper.map(createdInquiry, Inquiry, InquiryDto); + } + + @Get('/card/:uuid') + @UserRoles(...ROLES_ALLOWED_BOARDS) + async getByCard(@Param('uuid') cardUuid: string) { + const notification = await this.inquiryService.getByCardUuid(cardUuid); + const mapped = await this.inquiryService.mapToDtos([notification]); + return mapped[0]; + } } diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.dto.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.dto.ts index fe9a7478e7..caeff2f111 100644 --- a/services/apps/alcs/src/alcs/inquiry/inquiry.dto.ts +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.dto.ts @@ -2,6 +2,7 @@ import { AutoMap } from 'automapper-classes'; import { Type } from 'class-transformer'; 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'; import { InquiryParcelCreateDto } from './inquiry-parcel/inquiry-parcel.dto'; @@ -18,6 +19,10 @@ export class InquiryTypeDto extends BaseCodeDto { } export class CreateInquiryDto { + @IsString() + @IsNotEmpty() + boardCode: string; + @IsString() @IsNotEmpty() summary: string; @@ -67,6 +72,9 @@ export class InquiryDto { @AutoMap() uuid: string; + @AutoMap() + fileNumber: string; + @AutoMap() summary: string; @@ -105,6 +113,12 @@ export class InquiryDto { @AutoMap(() => ApplicationRegionDto) region: ApplicationRegionDto; + + @AutoMap(() => CardDto) + card?: CardDto; + + @AutoMap(() => InquiryTypeDto) + type: InquiryTypeDto; } export class UpdateInquiryDto { @@ -151,24 +165,14 @@ export class UpdateInquiryDto { export class CreateInquiryServiceDto { summary: string; - dateSubmittedToAlc: Date; - localGovernmentUuid: string; - typeCode: string; - regionCode: string; - inquirerFirstName?: string; - inquirerLastName?: string; - inquirerOrganization?: string; - inquirerPhone?: string; - inquirerEmail?: string; - parcels?: InquiryParcelCreateDto[]; } diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.entity.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.entity.ts index ca9c2755a3..9ea5972941 100644 --- a/services/apps/alcs/src/alcs/inquiry/inquiry.entity.ts +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.entity.ts @@ -30,6 +30,7 @@ export class Inquiry extends Base { } } + @AutoMap() @Column({ unique: true, default: () => `NEXTVAL('${FILE_NUMBER_SEQUENCE}')`, @@ -97,6 +98,8 @@ export class Inquiry extends Base { card: Card; @AutoMap(() => InquiryParcel) - @OneToMany(() => InquiryParcel, (incParcel) => incParcel.inquiry) + @OneToMany(() => InquiryParcel, (incParcel) => incParcel.inquiry, { + cascade: true, + }) parcels: InquiryParcel[]; } diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.module.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.module.ts index 79fb457fd9..d3cf62ea89 100644 --- a/services/apps/alcs/src/alcs/inquiry/inquiry.module.ts +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.module.ts @@ -1,8 +1,9 @@ -import { Module } from '@nestjs/common'; +import { forwardRef, Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { InquiryProfile } from '../../common/automapper/inquiry.automapper.profile'; import { DocumentModule } from '../../document/document.module'; import { FileNumberModule } from '../../file-number/file-number.module'; +import { BoardModule } from '../board/board.module'; import { CardModule } from '../card/card.module'; import { InquiryDocumentController } from './inquiry-document/inquiry-document.controller'; import { InquiryDocument } from './inquiry-document/inquiry-document.entity'; @@ -26,6 +27,7 @@ import { DocumentCode } from '../../document/document-code.entity'; DocumentCode, ]), CardModule, + forwardRef(() => BoardModule), FileNumberModule, DocumentModule, ], From 957e5a2e9580495ac948e037998c4d25dc0ccbca Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Tue, 26 Mar 2024 11:03:00 -0700 Subject: [PATCH 048/153] PR Fixes * Hide open/closed on commissioner view * Use entire app / reload entire app for meetings to keep the documents page in sync for the pending flag * Add timeline event for closed and store data properly in service --- ...ommissioner-planning-review.component.html | 6 ++- .../commissioner-planning-review.component.ts | 7 +--- .../header/header.component.html | 9 ++++- .../header/header.component.ts | 1 + .../review/schedule/schedule.ts | 23 +++++------ .../planning-review-timeline.service.spec.ts | 23 ++++++++++- .../planning-review-timeline.service.ts | 14 ++++++- .../planning-review.controller.ts | 12 +++++- .../planning-review/planning-review.entity.ts | 2 +- .../planning-review.service.ts | 40 +++++++++++++------ 10 files changed, 99 insertions(+), 38 deletions(-) diff --git a/alcs-frontend/src/app/features/commissioner/planning-review/commissioner-planning-review.component.html b/alcs-frontend/src/app/features/commissioner/planning-review/commissioner-planning-review.component.html index d906beeaff..2311bdf333 100644 --- a/alcs-frontend/src/app/features/commissioner/planning-review/commissioner-planning-review.component.html +++ b/alcs-frontend/src/app/features/commissioner/planning-review/commissioner-planning-review.component.html @@ -1,6 +1,10 @@ <div class="layout"> <div class="application"> - <app-planning-review-header *ngIf="planningReview" [planningReview]="planningReview"></app-planning-review-header> + <app-planning-review-header + *ngIf="planningReview" + [planningReview]="planningReview" + [showStatus]="false" + ></app-planning-review-header> <section class="content"> <app-evidentiary-record *ngIf="fileNumber" diff --git a/alcs-frontend/src/app/features/commissioner/planning-review/commissioner-planning-review.component.ts b/alcs-frontend/src/app/features/commissioner/planning-review/commissioner-planning-review.component.ts index 8d01c89a22..bea5c2abe2 100644 --- a/alcs-frontend/src/app/features/commissioner/planning-review/commissioner-planning-review.component.ts +++ b/alcs-frontend/src/app/features/commissioner/planning-review/commissioner-planning-review.component.ts @@ -2,13 +2,10 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { Title } from '@angular/platform-browser'; import { ActivatedRoute } from '@angular/router'; import { Subject, takeUntil } from 'rxjs'; -import { DOCUMENT_TYPE } from '../../../shared/document/document.dto'; import { environment } from '../../../../environments/environment'; -import { - CommissionerApplicationDto, - CommissionerPlanningReviewDto, -} from '../../../services/commissioner/commissioner.dto'; +import { CommissionerPlanningReviewDto } from '../../../services/commissioner/commissioner.dto'; import { CommissionerService } from '../../../services/commissioner/commissioner.service'; +import { DOCUMENT_TYPE } from '../../../shared/document/document.dto'; @Component({ selector: 'app-commissioner-planning-review', diff --git a/alcs-frontend/src/app/features/planning-review/header/header.component.html b/alcs-frontend/src/app/features/planning-review/header/header.component.html index 8c46b06e83..ad734f2444 100644 --- a/alcs-frontend/src/app/features/planning-review/header/header.component.html +++ b/alcs-frontend/src/app/features/planning-review/header/header.component.html @@ -5,7 +5,10 @@ <div class="first-row"> <div class="title"> <h5>{{ planningReview.fileNumber }} ({{ planningReview.documentName }})</h5> - <app-application-legacy-id *ngIf="planningReview.legacyId" [legacyId]="planningReview.legacyId"></app-application-legacy-id> + <app-application-legacy-id + *ngIf="planningReview.legacyId" + [legacyId]="planningReview.legacyId" + ></app-application-legacy-id> <div class="labels"> <app-application-type-pill [type]="planningReview.type"></app-application-type-pill> </div> @@ -43,6 +46,8 @@ <h5>{{ planningReview.fileNumber }} ({{ planningReview.documentName }})</h5> <app-no-data *ngIf="!planningReview.localGovernment"></app-no-data> </div> </div> - <div class="status-wrapper"><app-application-submission-status-type-pill [type]="statusPill"></app-application-submission-status-type-pill></div> + <div *ngIf="showStatus" class="status-wrapper"> + <app-application-submission-status-type-pill [type]="statusPill"></app-application-submission-status-type-pill> + </div> </div> </div> diff --git a/alcs-frontend/src/app/features/planning-review/header/header.component.ts b/alcs-frontend/src/app/features/planning-review/header/header.component.ts index 23fb9f97d7..6af7d8a2a7 100644 --- a/alcs-frontend/src/app/features/planning-review/header/header.component.ts +++ b/alcs-frontend/src/app/features/planning-review/header/header.component.ts @@ -15,6 +15,7 @@ export class HeaderComponent implements OnChanges { destroy = new Subject<void>(); @Input() planningReview!: PlanningReviewDetailedDto | CommissionerPlanningReviewDto; + @Input() showStatus = true; linkedCards: (CardDto & { displayName: string })[] = []; statusPill = OPEN_PR_LABEL; diff --git a/alcs-frontend/src/app/features/planning-review/review/schedule/schedule.ts b/alcs-frontend/src/app/features/planning-review/review/schedule/schedule.ts index f01a5681c7..5c33c549ea 100644 --- a/alcs-frontend/src/app/features/planning-review/review/schedule/schedule.ts +++ b/alcs-frontend/src/app/features/planning-review/review/schedule/schedule.ts @@ -18,6 +18,7 @@ export class ScheduleComponent implements OnInit, OnDestroy { displayedColumns: string[] = ['date', 'type', 'action']; meetings: PlanningReviewMeetingDto[] = []; planningReviewUuid: string | undefined; + fileNumber: string | undefined; constructor( public dialog: MatDialog, @@ -30,7 +31,8 @@ export class ScheduleComponent implements OnInit, OnDestroy { this.planningReviewDetailService.$planningReview.pipe(takeUntil(this.$destroy)).subscribe((planningReview) => { if (planningReview) { this.planningReviewUuid = planningReview.uuid; - this.loadMeetings(); + this.fileNumber = planningReview.fileNumber; + this.meetings = planningReview.meetings; } }); } @@ -47,8 +49,8 @@ export class ScheduleComponent implements OnInit, OnDestroy { }) .beforeClosed() .subscribe((didCreate) => { - if (didCreate) { - this.loadMeetings(); + if (didCreate && this.fileNumber) { + this.planningReviewDetailService.loadReview(this.fileNumber); } }); } @@ -66,8 +68,8 @@ export class ScheduleComponent implements OnInit, OnDestroy { }) .beforeClosed() .subscribe((didEdit) => { - if (didEdit) { - this.loadMeetings(); + if (didEdit && this.fileNumber) { + this.planningReviewDetailService.loadReview(this.fileNumber); } }); } @@ -80,7 +82,9 @@ export class ScheduleComponent implements OnInit, OnDestroy { .subscribe((didConfirm) => { if (didConfirm) { this.decisionMeetingService.delete(uuid).then(() => { - this.loadMeetings(); + if (this.fileNumber) { + this.planningReviewDetailService.loadReview(this.fileNumber); + } }); } }); @@ -90,11 +94,4 @@ export class ScheduleComponent implements OnInit, OnDestroy { this.$destroy.next(); this.$destroy.complete(); } - - private async loadMeetings() { - if (this.planningReviewUuid) { - const meetings = await this.decisionMeetingService.listByPlanningReview(this.planningReviewUuid); - this.meetings = meetings ?? []; - } - } } diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.service.spec.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.service.spec.ts index 58072de25d..589fa5598e 100644 --- a/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.service.spec.ts +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.service.spec.ts @@ -1,5 +1,6 @@ import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; import { Test, TestingModule } from '@nestjs/testing'; +import { User } from '../../../user/user.entity'; import { PlanningReferral } from '../planning-referral/planning-referral.entity'; import { PlanningReviewDecision } from '../planning-review-decision/planning-review-decision.entity'; import { PlanningReviewDecisionService } from '../planning-review-decision/planning-review-decision.service'; @@ -42,6 +43,7 @@ describe('PlanningReviewTimelineService', () => { mockPlanningReviewService.getDetailedReview.mockResolvedValue( new PlanningReview({ + open: true, referrals: [], }), ); @@ -68,6 +70,7 @@ describe('PlanningReviewTimelineService', () => { mockPlanningReviewService.getDetailedReview.mockResolvedValue( new PlanningReview({ + open: true, referrals: [ new PlanningReferral({ auditCreatedAt: sameDate, @@ -87,7 +90,7 @@ describe('PlanningReviewTimelineService', () => { expect(res).toBeDefined(); expect(res.length).toEqual(2); - expect(res[0].htmlText).toEqual('Referral #1'); + expect(res[0].htmlText).toEqual('Referral #1 - <strong>Open</strong>'); expect(res[0].isFulfilled).toBeTruthy(); expect(res[1].htmlText).toEqual('Referral #2'); expect(res[1].isFulfilled).toBeFalsy(); @@ -138,4 +141,22 @@ describe('PlanningReviewTimelineService', () => { expect(res[0].htmlText).toEqual('Scheduled Date - Other Meeting'); expect(res[1].htmlText).toEqual('Scheduled Date - Meeting'); }); + + it('should add an event for closed', async () => { + const sameDate = new Date(); + mockPlanningReviewService.getDetailedReview.mockResolvedValue( + new PlanningReview({ + open: false, + closedBy: new User(), + closedDate: sameDate, + referrals: [], + }), + ); + + const res = await service.getTimelineEvents('file-number'); + + expect(res).toBeDefined(); + expect(res.length).toEqual(1); + expect(res[0].htmlText).toEqual('Closed'); + }); }); diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.service.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.service.ts index eb22d402b2..a6abf3208f 100644 --- a/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.service.ts +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-timeline/planning-review-timeline.service.ts @@ -36,6 +36,16 @@ export class PlanningReviewTimelineService { await this.addDecisionEvents(planningReview, events); await this.addMeetingEvents(planningReview, events); + if (!planningReview.open) { + events.push({ + htmlText: `Closed`, + startDate: + planningReview.closedDate!.getTime() + SORTING_ORDER.REFERRAL, + fulfilledDate: null, + isFulfilled: true, + }); + } + events.sort((a, b) => b.startDate - a.startDate); return events; } @@ -102,7 +112,9 @@ export class PlanningReviewTimelineService { ); for (const [index, referral] of planningReview.referrals.entries()) { events.push({ - htmlText: `Referral #${index + 1}`, + htmlText: `Referral #${index + 1}${ + index === 0 ? ' - <strong>Open</strong>' : '' + }`, startDate: referral.submissionDate.getTime() + SORTING_ORDER.REFERRAL, fulfilledDate: referral.responseDate?.getTime() ?? null, isFulfilled: !!referral.responseDate, 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 374073a846..5cedb7d6f2 100644 --- a/services/apps/alcs/src/alcs/planning-review/planning-review.controller.ts +++ b/services/apps/alcs/src/alcs/planning-review/planning-review.controller.ts @@ -1,4 +1,12 @@ -import { Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common'; +import { + Body, + Controller, + Get, + Param, + Post, + Req, + UseGuards, +} from '@nestjs/common'; import { ApiOAuth2 } from '@nestjs/swagger'; import { Mapper } from 'automapper-core'; import { InjectMapper } from 'automapper-nestjs'; @@ -77,10 +85,12 @@ export class PlanningReviewController { async updateByFileNumber( @Param('fileNumber') fileNumber: string, @Body() updateDto: UpdatePlanningReviewDto, + @Req() req, ) { const review = await this.planningReviewService.update( fileNumber, updateDto, + req.user.entity, ); return this.mapper.map(review, PlanningReview, PlanningReviewDetailedDto); 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 3fedcd97e6..7e307997a9 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 @@ -71,7 +71,7 @@ export class PlanningReview extends Base { open: boolean; @ManyToOne(() => User) - closedBy: User; + closedBy: User | null; @Column({ type: 'timestamptz', nullable: true }) closedDate: Date | null; 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 8ad4e89a63..1c8a1be914 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 @@ -3,13 +3,9 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Mapper } from 'automapper-core'; import { InjectMapper } from 'automapper-nestjs'; -import { - FindOptionsRelations, - FindOptionsWhere, - In, - Repository, -} from 'typeorm'; +import { FindOptionsRelations, Repository } from 'typeorm'; import { FileNumberService } from '../../file-number/file-number.service'; +import { User } from '../../user/user.entity'; import { formatIncomingDate } from '../../utils/incoming-date.formatter'; import { filterUndefined } from '../../utils/undefined'; import { Board } from '../board/board.entity'; @@ -115,7 +111,9 @@ export class PlanningReviewService { }, }, }, - meetings: true, + meetings: { + type: true, + }, }, order: { referrals: { @@ -142,20 +140,36 @@ export class PlanningReviewService { }); } - async update(fileNumber: string, updateDto: UpdatePlanningReviewDto) { - const existingApp = await this.reviewRepository.findOneOrFail({ + async update( + fileNumber: string, + updateDto: UpdatePlanningReviewDto, + user?: User, + ) { + const existingReview = await this.reviewRepository.findOneOrFail({ where: { fileNumber, }, }); - existingApp.open = filterUndefined(updateDto.open, existingApp.open); - existingApp.typeCode = filterUndefined( + if (!updateDto.open && existingReview.open) { + existingReview.closedDate = new Date(); + if (user) { + existingReview.closedBy = user; + } + } + + if (updateDto.open && !existingReview.open) { + existingReview.closedDate = null; + existingReview.closedBy = null; + } + + existingReview.open = filterUndefined(updateDto.open, existingReview.open); + existingReview.typeCode = filterUndefined( updateDto.typeCode, - existingApp.typeCode, + existingReview.typeCode, ); - await this.reviewRepository.save(existingApp); + await this.reviewRepository.save(existingReview); return this.getDetailedReview(fileNumber); } From 4ce8f5484155ffeee9c8ad703104db2c8458ce4c Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Tue, 26 Mar 2024 11:15:48 -0700 Subject: [PATCH 049/153] Attach pending files to update document requests * Previously would not send the new file and only change the name --- .../document-upload-dialog.component.ts | 3 ++- .../document-upload-dialog.component.ts | 5 +++-- .../document-upload-dialog.component.ts | 7 ++++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/alcs-frontend/src/app/features/application/documents/document-upload-dialog/document-upload-dialog.component.ts b/alcs-frontend/src/app/features/application/documents/document-upload-dialog/document-upload-dialog.component.ts index 655cb8ebd4..9bab97854e 100644 --- a/alcs-frontend/src/app/features/application/documents/document-upload-dialog/document-upload-dialog.component.ts +++ b/alcs-frontend/src/app/features/application/documents/document-upload-dialog/document-upload-dialog.component.ts @@ -116,6 +116,7 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { visibilityFlags.push('P'); } + const file = this.pendingFile; const dto: UpdateDocumentDto = { fileName: this.name.value!, source: this.source.value as DOCUMENT_SOURCE, @@ -123,9 +124,9 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { visibilityFlags, parcelUuid: this.parcelId.value ?? undefined, ownerUuid: this.ownerId.value ?? undefined, + file, }; - const file = this.pendingFile; this.isSaving = true; if (this.data.existingDocument) { await this.applicationDocumentService.update(this.data.existingDocument.uuid, dto); diff --git a/alcs-frontend/src/app/features/notice-of-intent/documents/document-upload-dialog/document-upload-dialog.component.ts b/alcs-frontend/src/app/features/notice-of-intent/documents/document-upload-dialog/document-upload-dialog.component.ts index 47f28fa407..4aac473798 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/documents/document-upload-dialog/document-upload-dialog.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/documents/document-upload-dialog/document-upload-dialog.component.ts @@ -70,7 +70,7 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { private noiDocumentService: NoiDocumentService, private noiSubmissionService: NoticeOfIntentSubmissionService, private noiParcelService: NoticeOfIntentParcelService, - private toastService: ToastService + private toastService: ToastService, ) {} ngOnInit(): void { @@ -117,6 +117,7 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { visibilityFlags.push('P'); } + const file = this.pendingFile; const dto: UpdateNoticeOfIntentDocumentDto = { fileName: this.name.value!, source: this.source.value as DOCUMENT_SOURCE, @@ -124,9 +125,9 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { visibilityFlags, parcelUuid: this.parcelId.value ?? undefined, ownerUuid: this.ownerId.value ?? undefined, + file, }; - const file = this.pendingFile; this.isSaving = true; if (this.data.existingDocument) { await this.noiDocumentService.update(this.data.existingDocument.uuid, dto); diff --git a/alcs-frontend/src/app/features/notification/documents/document-upload-dialog/document-upload-dialog.component.ts b/alcs-frontend/src/app/features/notification/documents/document-upload-dialog/document-upload-dialog.component.ts index 58a010c3a6..94489d8ebb 100644 --- a/alcs-frontend/src/app/features/notification/documents/document-upload-dialog/document-upload-dialog.component.ts +++ b/alcs-frontend/src/app/features/notification/documents/document-upload-dialog/document-upload-dialog.component.ts @@ -56,7 +56,7 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { public data: { fileId: string; existingDocument?: NotificationDocumentDto }, protected dialog: MatDialogRef<any>, private notificationDocumentService: NotificationDocumentService, - private toastService: ToastService + private toastService: ToastService, ) {} ngOnInit(): void { @@ -94,14 +94,15 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { visibilityFlags.push('P'); } + const file = this.pendingFile; const dto: UpdateNoticeOfIntentDocumentDto = { fileName: this.name.value!, source: this.source.value as DOCUMENT_SOURCE, typeCode: this.type.value as DOCUMENT_TYPE, visibilityFlags, + file, }; - const file = this.pendingFile; this.isSaving = true; if (this.data.existingDocument) { await this.notificationDocumentService.update(this.data.existingDocument.uuid, dto); @@ -171,7 +172,7 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { if (this.data.existingDocument) { await this.notificationDocumentService.download( this.data.existingDocument.uuid, - this.data.existingDocument.fileName + this.data.existingDocument.fileName, ); } } From 4f1e36ffdbeac4bad0780fe32479ab7b5194e51e Mon Sep 17 00:00:00 2001 From: Liam Stoddard <lstodd@protonmail.com> Date: Tue, 26 Mar 2024 11:39:36 -0700 Subject: [PATCH 050/153] planning refferal working with card 'hack' --- .../migrate_planning_review.py | 16 ++ .../planning_review_base_update.py | 11 +- .../planning_review_staff_journal.py | 150 ++++++++++++++++++ .../planning_review/referrals/__init__.py | 8 + .../referrals/planning_review_card_init.py | 145 +++++++++++++++++ .../planning_review_referrals_init.py | 149 +++++++++++++++++ .../sql/planning_review_base_update.sql | 7 +- .../sql/planning_review_staff_journal.sql | 3 + .../planning_review_staff_journal_count.sql | 3 + .../referrals/init_planning_review_cards.sql | 3 + .../init_planning_review_cards_count.sql | 3 + .../insert_planning_review_referrals.sql | 11 ++ ...insert_planning_review_referrals_count.sql | 5 + 13 files changed, 511 insertions(+), 3 deletions(-) create mode 100644 bin/migrate-oats-data/planning_review/planning_review_staff_journal.py create mode 100644 bin/migrate-oats-data/planning_review/referrals/__init__.py create mode 100644 bin/migrate-oats-data/planning_review/referrals/planning_review_card_init.py create mode 100644 bin/migrate-oats-data/planning_review/referrals/planning_review_referrals_init.py create mode 100644 bin/migrate-oats-data/planning_review/sql/planning_review_staff_journal.sql create mode 100644 bin/migrate-oats-data/planning_review/sql/planning_review_staff_journal_count.sql create mode 100644 bin/migrate-oats-data/planning_review/sql/referrals/init_planning_review_cards.sql create mode 100644 bin/migrate-oats-data/planning_review/sql/referrals/init_planning_review_cards_count.sql create mode 100644 bin/migrate-oats-data/planning_review/sql/referrals/insert_planning_review_referrals.sql create mode 100644 bin/migrate-oats-data/planning_review/sql/referrals/insert_planning_review_referrals_count.sql diff --git a/bin/migrate-oats-data/planning_review/migrate_planning_review.py b/bin/migrate-oats-data/planning_review/migrate_planning_review.py index fc695942c1..00639286a8 100644 --- a/bin/migrate-oats-data/planning_review/migrate_planning_review.py +++ b/bin/migrate-oats-data/planning_review/migrate_planning_review.py @@ -3,12 +3,28 @@ clean_initial_planning_review, ) from .planning_review_base_update import update_planning_review_base_fields +from .planning_review_staff_journal import ( + process_planning_review_staff_journal, + clean_planning_review_staff_journal, +) +from .referrals import ( + process_planning_review_referral, + clean_planning_referrals, + init_planning_review_cards, + clean_planning_review_cards, +) def process_planning_review(batch_size): init_planning_review_base(batch_size) + process_planning_review_staff_journal(batch_size) update_planning_review_base_fields(batch_size) + init_planning_review_cards(batch_size) + process_planning_review_referral(batch_size) def clean_planning_review(): + clean_planning_review_staff_journal() + clean_planning_referrals() + clean_planning_review_cards() clean_initial_planning_review() diff --git a/bin/migrate-oats-data/planning_review/planning_review_base_update.py b/bin/migrate-oats-data/planning_review/planning_review_base_update.py index 16eac2842e..02a4b6e423 100644 --- a/bin/migrate-oats-data/planning_review/planning_review_base_update.py +++ b/bin/migrate-oats-data/planning_review/planning_review_base_update.py @@ -98,7 +98,8 @@ def _update_base_fields(conn, batch_size, cursor, rows): UPDATE alcs.planning_review SET open = %(open_indicator)s, type_code = %(alcs_planning_review_code)s, - legacy_id = %(legacy_number)s + legacy_id = %(legacy_number)s, + closed_by_uuid = %(closed_by_uuid)s WHERE alcs.planning_review.file_number = %(planning_review_id)s::text """ @@ -112,6 +113,7 @@ def _prepare_oats_planning_review_data(row_data_list): "open_indicator": _map_is_open(row), "alcs_planning_review_code": _map_planning_code(row), "legacy_number": row["legacy_planning_review_nbr"], + "closed_by_uuid": _map_closed_by(row), } ) @@ -128,6 +130,13 @@ def _map_is_open(data): return True +def _map_closed_by(data): + if data.get("open_ind") == "N": + return data.get("author_uuid") + else: + return None + + def _map_planning_code(data): oats_code = data.get("planning_review_code") try: diff --git a/bin/migrate-oats-data/planning_review/planning_review_staff_journal.py b/bin/migrate-oats-data/planning_review/planning_review_staff_journal.py new file mode 100644 index 0000000000..d68c4baaee --- /dev/null +++ b/bin/migrate-oats-data/planning_review/planning_review_staff_journal.py @@ -0,0 +1,150 @@ +from common import ( + BATCH_UPLOAD_SIZE, + OATS_ETL_USER, + setup_and_get_logger, + add_timezone_and_keep_date_part, + DEFAULT_ETL_USER_UUID, +) +from db import inject_conn_pool +from psycopg2.extras import RealDictCursor, execute_batch + +etl_name = "planning_review_staff_journal" +logger = setup_and_get_logger(etl_name) + + +@inject_conn_pool +def process_planning_review_staff_journal(conn=None, batch_size=BATCH_UPLOAD_SIZE): + """ + This function is responsible for initializing entries for notifications in staff_journal table in ALCS. + + Args: + conn (psycopg2.extensions.connection): PostgreSQL database connection. Provided by the decorator. + batch_size (int): The number of items to process at once. Defaults to BATCH_UPLOAD_SIZE. + """ + + logger.info(f"Start {etl_name}") + with conn.cursor(cursor_factory=RealDictCursor) as cursor: + with open( + "planning_review/sql/planning_review_staff_journal_count.sql", + "r", + encoding="utf-8", + ) as sql_file: + count_query = sql_file.read() + cursor.execute(count_query) + count_total = dict(cursor.fetchone())["count"] + logger.info(f"Total staff journal entry data to insert: {count_total}") + + failed_inserts_count = 0 + successful_inserts_count = 0 + last_entry_id = 0 + with open( + "planning_review/sql/planning_review_staff_journal.sql", + "r", + encoding="utf-8", + ) as sql_file: + submission_sql = sql_file.read() + while True: + cursor.execute( + f"{submission_sql} WHERE oprn.planning_review_note_id > '{last_entry_id}' ORDER BY oprn.planning_review_note_id;" + ) + + rows = cursor.fetchmany(batch_size) + + if not rows: + break + try: + users_to_be_inserted_count = len(rows) + + _insert_entries(conn, batch_size, cursor, rows) + + successful_inserts_count = ( + successful_inserts_count + users_to_be_inserted_count + ) + last_entry_id = dict(rows[-1])["planning_review_note_id"] + + logger.debug( + f"retrieved/inserted items count: {users_to_be_inserted_count}; total successfully inserted entries so far {successful_inserts_count}; last inserted note_id: {last_entry_id}" + ) + except Exception as err: + logger.exception("") + conn.rollback() + failed_inserts_count = count_total - successful_inserts_count + last_entry_id = last_entry_id + 1 + + logger.info( + f"Finished {etl_name}: total amount of successful inserts {successful_inserts_count}, total failed inserts {failed_inserts_count}" + ) + + +def _insert_entries(conn, batch_size, cursor, rows): + query = _get_insert_query() + parsed_data_list = _prepare_journal_data(rows) + + if len(parsed_data_list) > 0: + execute_batch(cursor, query, parsed_data_list, page_size=batch_size) + + conn.commit() + + +def _get_insert_query(): + query = f""" + INSERT INTO alcs.staff_journal ( + body, + edited, + planning_review_uuid, + created_at, + author_uuid, + audit_created_by + ) + VALUES ( + %(note_text)s, + %(edit)s, + %(uuid)s, + %(note_date)s, + %(user)s, + '{OATS_ETL_USER}' + ) + ON CONFLICT DO NOTHING; + """ + return query + + +def _prepare_journal_data(row_data_list): + data_list = [] + for row in row_data_list: + data = dict(row) + data = _map_revision(data) + data = _map_timezone(data) + data["user"] = DEFAULT_ETL_USER_UUID + data_list.append(dict(data)) + return data_list + + +def _map_revision(data): + revision = data.get("revision_count", "") + # check if edited + if revision == 0: + data["edit"] = False + else: + data["edit"] = True + return data + + +def _map_timezone(data): + date = data.get("note_date", "") + note_date = add_timezone_and_keep_date_part(date) + data["note_date"] = note_date + return data + + +@inject_conn_pool +def clean_planning_review_staff_journal(conn=None): + logger.info("Start staff journal cleaning") + # Only clean planning_reviews + with conn.cursor() as cursor: + cursor.execute( + f"DELETE FROM alcs.staff_journal asj WHERE asj.audit_created_by = '{OATS_ETL_USER}' AND asj.planning_review_uuid IS NOT NULL" + ) + logger.info(f"Deleted items count = {cursor.rowcount}") + + conn.commit() diff --git a/bin/migrate-oats-data/planning_review/referrals/__init__.py b/bin/migrate-oats-data/planning_review/referrals/__init__.py new file mode 100644 index 0000000000..1b8eb297c6 --- /dev/null +++ b/bin/migrate-oats-data/planning_review/referrals/__init__.py @@ -0,0 +1,8 @@ +from .planning_review_referrals_init import ( + process_planning_review_referral, + clean_planning_referrals, +) +from .planning_review_card_init import ( + init_planning_review_cards, + clean_planning_review_cards, +) diff --git a/bin/migrate-oats-data/planning_review/referrals/planning_review_card_init.py b/bin/migrate-oats-data/planning_review/referrals/planning_review_card_init.py new file mode 100644 index 0000000000..93f4caa4c3 --- /dev/null +++ b/bin/migrate-oats-data/planning_review/referrals/planning_review_card_init.py @@ -0,0 +1,145 @@ +from common import ( + BATCH_UPLOAD_SIZE, + OATS_ETL_USER, + setup_and_get_logger, + add_timezone_and_keep_date_part, + OatsToAlcsPlanningReviewType, +) +from db import inject_conn_pool +from psycopg2.extras import RealDictCursor, execute_batch + +etl_name = "init_planning_review_cards" +logger = setup_and_get_logger(etl_name) + + +@inject_conn_pool +def init_planning_review_cards(conn=None, batch_size=BATCH_UPLOAD_SIZE): + """ + This function is responsible for populating planning review cards in ALCS. + + Args: + conn (psycopg2.extensions.connection): PostgreSQL database connection. Provided by the decorator. + batch_size (int): The number of items to process at once. Defaults to BATCH_UPLOAD_SIZE. + """ + + logger.info("Start insert planning review card fields") + with conn.cursor(cursor_factory=RealDictCursor) as cursor: + with open( + "planning_review/sql/referrals/init_planning_review_cards_count.sql", + "r", + encoding="utf-8", + ) as sql_file: + count_query = sql_file.read() + cursor.execute(count_query) + count_total = dict(cursor.fetchone())["count"] + logger.info(f"Total Planning Review data to insert: {count_total}") + + failed_inserts = 0 + successful_updates_count = 0 + last_planning_review_id = 0 + + with open( + "planning_review/sql/referrals/init_planning_review_cards.sql", + "r", + encoding="utf-8", + ) as sql_file: + query = sql_file.read() + while True: + cursor.execute( + f""" + {query} + WHERE opr.planning_review_id > {last_planning_review_id} ORDER BY opr.planning_review_id; + """ + ) + + rows = cursor.fetchmany(batch_size) + + if not rows: + break + try: + updated_data = _insert_base_fields(conn, batch_size, cursor, rows) + + successful_updates_count = successful_updates_count + len( + updated_data + ) + last_planning_review_id = dict(updated_data[-1])[ + "planning_review_id" + ] + + logger.debug( + f"Retrieved/updated items count: {len(updated_data)}; total successfully inserted cards so far {successful_updates_count}; last updated planning_review_id: {last_planning_review_id}" + ) + except Exception as err: + # this is NOT going to be caused by actual data update failure. This code is only executed when the code error appears or connection to DB is lost + logger.exception(err) + conn.rollback() + failed_inserts = count_total - successful_updates_count + last_planning_review_id = last_planning_review_id + 1 + + logger.info( + f"Finished {etl_name}: total amount of successful inserts {successful_updates_count}, total failed inserts {failed_inserts}" + ) + + +def _insert_base_fields(conn, batch_size, cursor, rows): + parsed_data_list = _prepare_oats_planning_review_card_data(rows) + + execute_batch( + cursor, + query, + parsed_data_list, + page_size=batch_size, + ) + + conn.commit() + return parsed_data_list + + +# use audit_updated_by as temp placeholder for pr uuid +query = f""" + INSERT INTO alcs.card ( + audit_updated_by, + type_code, + audit_created_by, + archived, + status_code + ) + VALUES ( + %(uuid)s, + %(type_code)s, + '{OATS_ETL_USER}', + %(archived)s, + %(status_code)s + ) +""" + + +def _prepare_oats_planning_review_card_data(row_data_list): + mapped_data_list = [] + for row in row_data_list: + mapped_data_list.append( + { + "planning_review_id": row["planning_review_id"], + "uuid": row["uuid"], + "type_code": "PLAN", + "status_code": "PREL", + "archived": True, + } + ) + + return mapped_data_list + + +@inject_conn_pool +def clean_planning_review_cards(conn=None): + logger.info("Start card cleaning") + with conn.cursor() as cursor: + cursor.execute( + f"DELETE FROM alcs.card nos WHERE nos.audit_created_by = '{OATS_ETL_USER}' " + ) + logger.info(f"Deleted items count = {cursor.rowcount}") + + conn.commit() + + +# and nos.audit_updated_by is NULL" diff --git a/bin/migrate-oats-data/planning_review/referrals/planning_review_referrals_init.py b/bin/migrate-oats-data/planning_review/referrals/planning_review_referrals_init.py new file mode 100644 index 0000000000..7c513368e3 --- /dev/null +++ b/bin/migrate-oats-data/planning_review/referrals/planning_review_referrals_init.py @@ -0,0 +1,149 @@ +from common import ( + BATCH_UPLOAD_SIZE, + OATS_ETL_USER, + setup_and_get_logger, + add_timezone_and_keep_date_part, + OatsToAlcsPlanningReviewType, +) +from db import inject_conn_pool +from psycopg2.extras import RealDictCursor, execute_batch + +etl_name = "init_planning_review_referral" +logger = setup_and_get_logger(etl_name) + + +@inject_conn_pool +def process_planning_review_referral(conn=None, batch_size=BATCH_UPLOAD_SIZE): + """ + This function is responsible for populating date_submitted_to_alc in alcs.planning_referral in ALCS. + + Args: + conn (psycopg2.extensions.connection): PostgreSQL database connection. Provided by the decorator. + batch_size (int): The number of items to process at once. Defaults to BATCH_UPLOAD_SIZE. + """ + + logger.info("Start insert planning referral fields") + with conn.cursor(cursor_factory=RealDictCursor) as cursor: + with open( + "planning_review/sql/referrals/insert_planning_review_referrals_count.sql", + "r", + encoding="utf-8", + ) as sql_file: + count_query = sql_file.read() + cursor.execute(count_query) + count_total = dict(cursor.fetchone())["count"] + logger.info(f"Total Planning Referral data to insert: {count_total}") + + failed_inserts = 0 + successful_updates_count = 0 + last_planning_review_id = 0 + + with open( + "planning_review/sql/referrals/insert_planning_review_referrals.sql", + "r", + encoding="utf-8", + ) as sql_file: + query = sql_file.read() + while True: + cursor.execute( + f""" + {query} + WHERE opr.planning_review_id > {last_planning_review_id} ORDER BY opr.planning_review_id; + """ + ) + + rows = cursor.fetchmany(batch_size) + + if not rows: + break + try: + updated_data = _insert_base_fields(conn, batch_size, cursor, rows) + + successful_updates_count = successful_updates_count + len( + updated_data + ) + last_planning_review_id = dict(updated_data[-1])[ + "planning_review_id" + ] + + logger.debug( + f"Retrieved/updated items count: {len(updated_data)}; total successfully updated planning referral so far {successful_updates_count}; last updated planning_review_id: {last_planning_review_id}" + ) + except Exception as err: + # this is NOT going to be caused by actual data update failure. This code is only executed when the code error appears or connection to DB is lost + logger.exception(err) + logger.info(updated_data) + conn.rollback() + failed_inserts = count_total - successful_updates_count + last_planning_review_id = last_planning_review_id + 1 + + logger.info( + f"Finished {etl_name}: total amount of successful updates {successful_updates_count}, total failed updates {failed_inserts}" + ) + + +def _insert_base_fields(conn, batch_size, cursor, rows): + parsed_data_list = _prepare_oats_planning_review_data(rows) + + execute_batch( + cursor, + query, + parsed_data_list, + page_size=batch_size, + ) + + conn.commit() + return parsed_data_list + + +query = f""" + INSERT INTO alcs.planning_referral ( + planning_review_uuid, + referral_description, + audit_created_by, + submission_date, + card_uuid + ) + VALUES ( + %(review_uuid)s, + %(description)s, + '{OATS_ETL_USER}', + %(rx_date)s, + %(card_uuid)s + ) +""" + + +def _prepare_oats_planning_review_data(row_data_list): + mapped_data_list = [] + for row in row_data_list: + mapped_data_list.append( + { + "planning_review_id": row["planning_review_id"], + "file_number": row["file_number"], + "review_uuid": row["uuid"], + "description": row["description"], + "rx_date": _map_rx_date(row), + "card_uuid": row["card_uuid"], + } + ) + + return mapped_data_list + + +def _map_rx_date(data): + date = data.get("received_date", "") + rx_date = add_timezone_and_keep_date_part(date) + return rx_date + + +@inject_conn_pool +def clean_planning_referrals(conn=None): + logger.info("Start planning_referral cleaning") + with conn.cursor() as cursor: + cursor.execute( + f"DELETE FROM alcs.planning_referral nos WHERE nos.audit_created_by = '{OATS_ETL_USER}' and nos.audit_updated_by is NULL" + ) + logger.info(f"Deleted items count = {cursor.rowcount}") + + conn.commit() diff --git a/bin/migrate-oats-data/planning_review/sql/planning_review_base_update.sql b/bin/migrate-oats-data/planning_review/sql/planning_review_base_update.sql index 78792d0513..449cf01790 100644 --- a/bin/migrate-oats-data/planning_review/sql/planning_review_base_update.sql +++ b/bin/migrate-oats-data/planning_review/sql/planning_review_base_update.sql @@ -2,7 +2,10 @@ SELECT ops.open_ind, ops.planning_review_id, ops.planning_review_code, - ops.legacy_planning_review_nbr + ops.legacy_planning_review_nbr, + sj.author_uuid FROM oats.oats_planning_reviews ops - JOIN alcs.planning_review apr ON ops.planning_review_id::TEXT = apr.file_number \ No newline at end of file + JOIN alcs.planning_review apr ON ops.planning_review_id::TEXT = apr.file_number + LEFT JOIN alcs.staff_journal sj ON apr."uuid" = sj.planning_review_uuid + \ No newline at end of file diff --git a/bin/migrate-oats-data/planning_review/sql/planning_review_staff_journal.sql b/bin/migrate-oats-data/planning_review/sql/planning_review_staff_journal.sql new file mode 100644 index 0000000000..9caedab7bc --- /dev/null +++ b/bin/migrate-oats-data/planning_review/sql/planning_review_staff_journal.sql @@ -0,0 +1,3 @@ +SELECT oprn.note_date, oprn.note_text, oprn.revision_count, oprn.planning_review_note_id, pr."uuid" +FROM oats.oats_planning_review_notes oprn +JOIN alcs.planning_review pr ON pr.file_number = oprn.planning_review_id::TEXT \ No newline at end of file diff --git a/bin/migrate-oats-data/planning_review/sql/planning_review_staff_journal_count.sql b/bin/migrate-oats-data/planning_review/sql/planning_review_staff_journal_count.sql new file mode 100644 index 0000000000..1f44da2a42 --- /dev/null +++ b/bin/migrate-oats-data/planning_review/sql/planning_review_staff_journal_count.sql @@ -0,0 +1,3 @@ +SELECT count (*) +FROM oats.oats_planning_review_notes oprn +JOIN alcs.planning_review pr ON pr.file_number = oprn.planning_review_id::TEXT \ No newline at end of file diff --git a/bin/migrate-oats-data/planning_review/sql/referrals/init_planning_review_cards.sql b/bin/migrate-oats-data/planning_review/sql/referrals/init_planning_review_cards.sql new file mode 100644 index 0000000000..e8b77e5983 --- /dev/null +++ b/bin/migrate-oats-data/planning_review/sql/referrals/init_planning_review_cards.sql @@ -0,0 +1,3 @@ +SELECT opr.planning_review_id, pr.file_number, pr."uuid" +FROM alcs.planning_review pr +JOIN oats.oats_planning_reviews opr ON pr.file_number = opr.planning_review_id::TEXT \ No newline at end of file diff --git a/bin/migrate-oats-data/planning_review/sql/referrals/init_planning_review_cards_count.sql b/bin/migrate-oats-data/planning_review/sql/referrals/init_planning_review_cards_count.sql new file mode 100644 index 0000000000..dabb52683b --- /dev/null +++ b/bin/migrate-oats-data/planning_review/sql/referrals/init_planning_review_cards_count.sql @@ -0,0 +1,3 @@ +SELECT COUNT(*) +FROM alcs.planning_review pr +JOIN oats.oats_planning_reviews opr ON pr.file_number = opr.planning_review_id::TEXT; \ No newline at end of file diff --git a/bin/migrate-oats-data/planning_review/sql/referrals/insert_planning_review_referrals.sql b/bin/migrate-oats-data/planning_review/sql/referrals/insert_planning_review_referrals.sql new file mode 100644 index 0000000000..9c0791af97 --- /dev/null +++ b/bin/migrate-oats-data/planning_review/sql/referrals/insert_planning_review_referrals.sql @@ -0,0 +1,11 @@ +SELECT + pr."uuid", + opr.description, + opr.received_date, + opr.planning_review_id::TEXT AS file_number, + ac."uuid" AS card_uuid, + opr.planning_review_id +FROM + alcs.planning_review pr + JOIN oats.oats_planning_reviews opr ON pr.file_number = opr.planning_review_id::TEXT + JOIN alcs.card ac ON pr."uuid"::TEXT = ac.audit_updated_by \ No newline at end of file diff --git a/bin/migrate-oats-data/planning_review/sql/referrals/insert_planning_review_referrals_count.sql b/bin/migrate-oats-data/planning_review/sql/referrals/insert_planning_review_referrals_count.sql new file mode 100644 index 0000000000..b4b2aa2864 --- /dev/null +++ b/bin/migrate-oats-data/planning_review/sql/referrals/insert_planning_review_referrals_count.sql @@ -0,0 +1,5 @@ +SELECT + COUNT(*) +FROM + alcs.planning_review pr + JOIN oats.oats_planning_reviews opr ON pr.file_number = opr.planning_review_id::TEXT; \ No newline at end of file From 342e588deb90484ad9c8fd8e80ffab820ccfd7e8 Mon Sep 17 00:00:00 2001 From: Liam Stoddard <lstodd@protonmail.com> Date: Tue, 26 Mar 2024 12:12:56 -0700 Subject: [PATCH 051/153] clean up cards --- .../migrate_planning_review.py | 2 + .../planning_review/referrals/__init__.py | 1 + .../referrals/planning_review_card_init.py | 5 +- .../referrals/planning_review_card_update.py | 114 ++++++++++++++++++ .../planning_review_referrals_init.py | 1 - .../update_planning_review_cards.sql | 7 ++ .../update_planning_review_cards_count.sql | 7 ++ 7 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py create mode 100644 bin/migrate-oats-data/planning_review/sql/referrals/update_planning_review_cards.sql create mode 100644 bin/migrate-oats-data/planning_review/sql/referrals/update_planning_review_cards_count.sql diff --git a/bin/migrate-oats-data/planning_review/migrate_planning_review.py b/bin/migrate-oats-data/planning_review/migrate_planning_review.py index 00639286a8..d517bd6461 100644 --- a/bin/migrate-oats-data/planning_review/migrate_planning_review.py +++ b/bin/migrate-oats-data/planning_review/migrate_planning_review.py @@ -12,6 +12,7 @@ clean_planning_referrals, init_planning_review_cards, clean_planning_review_cards, + update_planning_review_cards, ) @@ -21,6 +22,7 @@ def process_planning_review(batch_size): update_planning_review_base_fields(batch_size) init_planning_review_cards(batch_size) process_planning_review_referral(batch_size) + update_planning_review_cards(batch_size) def clean_planning_review(): diff --git a/bin/migrate-oats-data/planning_review/referrals/__init__.py b/bin/migrate-oats-data/planning_review/referrals/__init__.py index 1b8eb297c6..7b1dbb6f51 100644 --- a/bin/migrate-oats-data/planning_review/referrals/__init__.py +++ b/bin/migrate-oats-data/planning_review/referrals/__init__.py @@ -6,3 +6,4 @@ init_planning_review_cards, clean_planning_review_cards, ) +from .planning_review_card_update import update_planning_review_cards diff --git a/bin/migrate-oats-data/planning_review/referrals/planning_review_card_init.py b/bin/migrate-oats-data/planning_review/referrals/planning_review_card_init.py index 93f4caa4c3..b006ca2d92 100644 --- a/bin/migrate-oats-data/planning_review/referrals/planning_review_card_init.py +++ b/bin/migrate-oats-data/planning_review/referrals/planning_review_card_init.py @@ -135,11 +135,8 @@ def clean_planning_review_cards(conn=None): logger.info("Start card cleaning") with conn.cursor() as cursor: cursor.execute( - f"DELETE FROM alcs.card nos WHERE nos.audit_created_by = '{OATS_ETL_USER}' " + f"DELETE FROM alcs.card nos WHERE nos.audit_created_by = '{OATS_ETL_USER}' and nos.audit_updated_by is NULL" ) logger.info(f"Deleted items count = {cursor.rowcount}") conn.commit() - - -# and nos.audit_updated_by is NULL" diff --git a/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py b/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py new file mode 100644 index 0000000000..258fbdfd4a --- /dev/null +++ b/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py @@ -0,0 +1,114 @@ +from common import ( + BATCH_UPLOAD_SIZE, + setup_and_get_logger, + add_timezone_and_keep_date_part, + OatsToAlcsPlanningReviewType, +) +from db import inject_conn_pool +from psycopg2.extras import RealDictCursor, execute_batch + +etl_name = "update_planning_review_cards" +logger = setup_and_get_logger(etl_name) + + +@inject_conn_pool +def update_planning_review_cards(conn=None, batch_size=BATCH_UPLOAD_SIZE): + """ + This function is responsible for updating planning review cards in alcs.cards in ALCS. + + Args: + conn (psycopg2.extensions.connection): PostgreSQL database connection. Provided by the decorator. + batch_size (int): The number of items to process at once. Defaults to BATCH_UPLOAD_SIZE. + """ + + logger.info("Start update planning review base fields") + with conn.cursor(cursor_factory=RealDictCursor) as cursor: + with open( + "planning_review/sql/referrals/update_planning_review_cards_count.sql", + "r", + encoding="utf-8", + ) as sql_file: + count_query = sql_file.read() + cursor.execute(count_query) + count_total = dict(cursor.fetchone())["count"] + logger.info(f"Total Planning Review data to update: {count_total}") + + failed_inserts = 0 + successful_updates_count = 0 + last_planning_review_id = "00000000-0000-0000-0000-000000000000" + + with open( + "planning_review/sql/referrals/update_planning_review_cards.sql", + "r", + encoding="utf-8", + ) as sql_file: + query = sql_file.read() + while True: + cursor.execute( + f""" + {query} + AND uuid > '{last_planning_review_id}' ORDER BY uuid; + """ + ) + + rows = cursor.fetchmany(batch_size) + + if not rows: + break + try: + updated_data = _update_base_fields(conn, batch_size, cursor, rows) + + successful_updates_count = successful_updates_count + len( + updated_data + ) + last_planning_review_id = dict(updated_data[-1])["uuid"] + + logger.debug( + f"Retrieved/updated items count: {len(updated_data)}; total successfully updated planning_review cards so far {successful_updates_count}; last updated planning_review card uuid: {last_planning_review_id}" + ) + except Exception as err: + # this is NOT going to be caused by actual data update failure. This code is only executed when the code error appears or connection to DB is lost + logger.exception(err) + conn.rollback() + failed_inserts = count_total - successful_updates_count + last_planning_review_id = last_planning_review_id + 1 + + logger.info( + f"Finished {etl_name}: total amount of successful updates {successful_updates_count}, total failed updates {failed_inserts}" + ) + + +def _update_base_fields(conn, batch_size, cursor, rows): + parsed_data_list = _prepare_oats_planning_review_data(rows) + + execute_batch( + cursor, + _rx_items_query, + parsed_data_list, + page_size=batch_size, + ) + + conn.commit() + return parsed_data_list + + +_rx_items_query = """ + UPDATE alcs.card + SET board_uuid = %(board_uuid)s, + audit_updated_by = %(audit_updated_by)s + WHERE alcs.card.uuid = %(uuid)s +""" + + +def _prepare_oats_planning_review_data(row_data_list): + mapped_data_list = [] + for row in row_data_list: + mapped_data_list.append( + { + "uuid": row["uuid"], + "board_uuid": "d8c18278-cb41-474e-a180-534a101243ab", + "audit_updated_by": None, + } + ) + + return mapped_data_list diff --git a/bin/migrate-oats-data/planning_review/referrals/planning_review_referrals_init.py b/bin/migrate-oats-data/planning_review/referrals/planning_review_referrals_init.py index 7c513368e3..3f226d3b2a 100644 --- a/bin/migrate-oats-data/planning_review/referrals/planning_review_referrals_init.py +++ b/bin/migrate-oats-data/planning_review/referrals/planning_review_referrals_init.py @@ -72,7 +72,6 @@ def process_planning_review_referral(conn=None, batch_size=BATCH_UPLOAD_SIZE): except Exception as err: # this is NOT going to be caused by actual data update failure. This code is only executed when the code error appears or connection to DB is lost logger.exception(err) - logger.info(updated_data) conn.rollback() failed_inserts = count_total - successful_updates_count last_planning_review_id = last_planning_review_id + 1 diff --git a/bin/migrate-oats-data/planning_review/sql/referrals/update_planning_review_cards.sql b/bin/migrate-oats-data/planning_review/sql/referrals/update_planning_review_cards.sql new file mode 100644 index 0000000000..fae747fa3b --- /dev/null +++ b/bin/migrate-oats-data/planning_review/sql/referrals/update_planning_review_cards.sql @@ -0,0 +1,7 @@ +SELECT + "uuid" +FROM + alcs.card +WHERE + audit_created_by = 'oats_etl' + AND type_code = 'PLAN' \ No newline at end of file diff --git a/bin/migrate-oats-data/planning_review/sql/referrals/update_planning_review_cards_count.sql b/bin/migrate-oats-data/planning_review/sql/referrals/update_planning_review_cards_count.sql new file mode 100644 index 0000000000..7933188257 --- /dev/null +++ b/bin/migrate-oats-data/planning_review/sql/referrals/update_planning_review_cards_count.sql @@ -0,0 +1,7 @@ +SELECT + COUNT(*) +FROM + alcs.card +WHERE + audit_created_by = 'oats_etl' + AND type_code = 'PLAN' \ No newline at end of file From 82e2d93e2fa41a89ceaf7ac12aff4789b73f0056 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Tue, 26 Mar 2024 12:32:23 -0700 Subject: [PATCH 052/153] Add Inquiries to Home page Subtasks + Assigned to Me --- .../home/assigned/assigned.component.html | 5 +++ .../home/assigned/assigned.component.spec.ts | 1 + .../home/assigned/assigned.component.ts | 25 ++++++++++++ .../home/subtask/subtask.component.html | 4 ++ .../home/subtask/subtask.component.ts | 10 ++++- .../src/app/services/home/home.service.ts | 2 + .../card/card-subtask/card-subtask.dto.ts | 2 + .../src/alcs/home/home.controller.spec.ts | 37 +++++++++++++++++- .../alcs/src/alcs/home/home.controller.ts | 39 +++++++++++++++++++ .../apps/alcs/src/alcs/home/home.module.ts | 2 + 10 files changed, 125 insertions(+), 2 deletions(-) diff --git a/alcs-frontend/src/app/features/home/assigned/assigned.component.html b/alcs-frontend/src/app/features/home/assigned/assigned.component.html index 093529bc02..099e744049 100644 --- a/alcs-frontend/src/app/features/home/assigned/assigned.component.html +++ b/alcs-frontend/src/app/features/home/assigned/assigned.component.html @@ -16,6 +16,11 @@ <h4>Cards Assigned to Me: {{ totalFiles }}</h4> <app-assigned-table [assignedFiles]="planningReferrals"></app-assigned-table> </section> + <section *ngIf="inquiries.length"> + <div class="subheading2">Inquiries</div> + <app-assigned-table [assignedFiles]="inquiries"></app-assigned-table> + </section> + <section *ngIf="notifications.length"> <div class="subheading2">Notifications</div> <app-assigned-table [assignedFiles]="notifications"></app-assigned-table> 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 445abfb5ea..246849af6f 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 @@ -42,6 +42,7 @@ describe('AssignedComponent', () => { reconsiderations: [], noticeOfIntents: [], notifications: [], + inquiries: [], }); fixture.detectChanges(); 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 5cf145b80b..2545c29ecc 100644 --- a/alcs-frontend/src/app/features/home/assigned/assigned.component.ts +++ b/alcs-frontend/src/app/features/home/assigned/assigned.component.ts @@ -4,6 +4,7 @@ import { ApplicationReconsiderationDto } from '../../../services/application/app import { ApplicationDto } from '../../../services/application/application.dto'; import { ApplicationService } from '../../../services/application/application.service'; import { HomeService } from '../../../services/home/home.service'; +import { InquiryDto } from '../../../services/inquiry/inquiry.dto'; 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'; @@ -26,6 +27,7 @@ export class AssignedComponent implements OnInit { applications: AssignedToMeFile[] = []; planningReferrals: AssignedToMeFile[] = []; notifications: AssignedToMeFile[] = []; + inquiries: AssignedToMeFile[] = []; totalFiles = 0; constructor( @@ -47,6 +49,7 @@ export class AssignedComponent implements OnInit { noticeOfIntents, noticeOfIntentModifications, notifications, + inquiries, } = await this.homeService.fetchAssignedToMe(); this.noticeOfIntents = [ @@ -105,6 +108,17 @@ export class AssignedComponent implements OnInit { .sort((a, b) => a.date! - b.date!), ]; + this.inquiries = [ + ...inquiries + .filter((r) => r.card!.highPriority) + .map((r) => this.mapInquiry(r)) + .sort((a, b) => a.date! - b.date!), + ...inquiries + .filter((r) => !r.card!.highPriority) + .map((r) => this.mapInquiry(r)) + .sort((a, b) => a.date! - b.date!), + ]; + this.notifications = [ ...notifications .filter((r) => r.card.highPriority) @@ -203,4 +217,15 @@ export class AssignedComponent implements OnInit { labels: [NOTIFICATION_LABEL], }; } + + private mapInquiry(inquiry: InquiryDto) { + return { + title: `${inquiry.fileNumber} (${inquiry.inquirerLastName ?? 'Unknown'})`, + type: inquiry.card!.type, + card: inquiry.card!, + date: inquiry.dateSubmittedToAlc, + highPriority: inquiry.card!.highPriority, + labels: [inquiry.type], + }; + } } diff --git a/alcs-frontend/src/app/features/home/subtask/subtask.component.html b/alcs-frontend/src/app/features/home/subtask/subtask.component.html index f30b734b54..72e338d01a 100644 --- a/alcs-frontend/src/app/features/home/subtask/subtask.component.html +++ b/alcs-frontend/src/app/features/home/subtask/subtask.component.html @@ -13,6 +13,10 @@ <h4>{{ subtaskLabel }} Subtasks: {{ totalSubtaskCount }}</h4> <div class="subheading2">Planning Reviews</div> <app-subtask-table [subtasks]="planningReviewSubtasks" [users]="users"></app-subtask-table> </section> + <section *ngIf="inquirySubtasks.length"> + <div class="subheading2">Inquiries</div> + <app-subtask-table [subtasks]="inquirySubtasks" [users]="users"></app-subtask-table> + </section> <section *ngIf="notificationSubtasks.length"> <div class="subheading2">Notifications</div> <app-subtask-table [subtasks]="notificationSubtasks" [users]="users"></app-subtask-table> diff --git a/alcs-frontend/src/app/features/home/subtask/subtask.component.ts b/alcs-frontend/src/app/features/home/subtask/subtask.component.ts index f01b49e517..c52f114f97 100644 --- a/alcs-frontend/src/app/features/home/subtask/subtask.component.ts +++ b/alcs-frontend/src/app/features/home/subtask/subtask.component.ts @@ -25,6 +25,7 @@ export class SubtaskComponent implements OnInit, OnDestroy { noticeOfIntentSubtasks: HomepageSubtaskDto[] = []; planningReviewSubtasks: HomepageSubtaskDto[] = []; notificationSubtasks: HomepageSubtaskDto[] = []; + inquirySubtasks: HomepageSubtaskDto[] = []; showNoi = true; showAppAndNonApp = true; @@ -60,6 +61,7 @@ export class SubtaskComponent implements OnInit, OnDestroy { const nois = allSubtasks.filter((s) => s.card.type === CardType.NOI); const noiModifications = allSubtasks.filter((s) => s.card.type === CardType.NOI_MODI); const notifications = allSubtasks.filter((s) => s.card.type === CardType.NOTIFICATION); + const inquiries = allSubtasks.filter((s) => s.card.type === CardType.INQUIRY); this.applicationSubtasks = [ ...applications.filter((a) => a.card.highPriority).sort((a, b) => b.activeDays! - a.activeDays!), @@ -87,6 +89,11 @@ export class SubtaskComponent implements OnInit, OnDestroy { ...notifications.filter((r) => !r.card.highPriority).sort((a, b) => a.createdAt! - b.createdAt!), ]; + this.inquirySubtasks = [ + ...inquiries.filter((r) => r.card.highPriority).sort((a, b) => a.createdAt! - b.createdAt!), + ...inquiries.filter((r) => !r.card.highPriority).sort((a, b) => a.createdAt! - b.createdAt!), + ]; + if (this.showNoi) { this.totalSubtaskCount = this.noticeOfIntentSubtasks.length + this.notificationSubtasks.length; } @@ -101,7 +108,8 @@ export class SubtaskComponent implements OnInit, OnDestroy { this.applicationSubtasks.length + this.noticeOfIntentSubtasks.length + this.planningReviewSubtasks.length + - this.notificationSubtasks.length; + this.notificationSubtasks.length + + this.inquirySubtasks.length; } } diff --git a/alcs-frontend/src/app/services/home/home.service.ts b/alcs-frontend/src/app/services/home/home.service.ts index dccd47e755..2276302e87 100644 --- a/alcs-frontend/src/app/services/home/home.service.ts +++ b/alcs-frontend/src/app/services/home/home.service.ts @@ -6,6 +6,7 @@ import { ApplicationModificationDto } from '../application/application-modificat import { ApplicationReconsiderationDto } from '../application/application-reconsideration/application-reconsideration.dto'; import { ApplicationDto } from '../application/application.dto'; import { CARD_SUBTASK_TYPE, HomepageSubtaskDto } from '../card/card-subtask/card-subtask.dto'; +import { InquiryDto } from '../inquiry/inquiry.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'; @@ -27,6 +28,7 @@ export class HomeService { noticeOfIntents: NoticeOfIntentDto[]; noticeOfIntentModifications: NoticeOfIntentModificationDto[]; notifications: NotificationDto[]; + inquiries: InquiryDto[]; }>(`${environment.apiUrl}/home/assigned`), ); } diff --git a/services/apps/alcs/src/alcs/card/card-subtask/card-subtask.dto.ts b/services/apps/alcs/src/alcs/card/card-subtask/card-subtask.dto.ts index 4f8ec51eb1..27715961b8 100644 --- a/services/apps/alcs/src/alcs/card/card-subtask/card-subtask.dto.ts +++ b/services/apps/alcs/src/alcs/card/card-subtask/card-subtask.dto.ts @@ -13,7 +13,9 @@ export enum PARENT_TYPE { PLANNING_REVIEW = 'planning-review', NOTICE_OF_INTENT = 'notice-of-intent', NOTIFICATION = 'notification', + INQUIRY = 'inquiry', } + export class UpdateCardSubtaskDto { @AutoMap() @IsUUID() 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 c5369c4b15..8ea1e2b566 100644 --- a/services/apps/alcs/src/alcs/home/home.controller.spec.ts +++ b/services/apps/alcs/src/alcs/home/home.controller.spec.ts @@ -14,6 +14,7 @@ import { mockKeyCloakProviders } from '../../../test/mocks/mockTypes'; import { ApplicationSubtaskProfile } from '../../common/automapper/application-subtask.automapper.profile'; import { ApplicationProfile } from '../../common/automapper/application.automapper.profile'; import { CardProfile } from '../../common/automapper/card.automapper.profile'; +import { InquiryProfile } from '../../common/automapper/inquiry.automapper.profile'; import { NotificationProfile } from '../../common/automapper/notification.automapper.profile'; import { UserProfile } from '../../common/automapper/user.automapper.profile'; import { ApplicationModificationService } from '../application-decision/application-modification/application-modification.service'; @@ -24,13 +25,14 @@ import { CARD_STATUS } from '../card/card-status/card-status.entity'; import { CARD_SUBTASK_TYPE } from '../card/card-subtask/card-subtask.dto'; import { CardSubtaskService } from '../card/card-subtask/card-subtask.service'; import { CodeService } from '../code/code.service'; +import { Inquiry } from '../inquiry/inquiry.entity'; +import { InquiryService } from '../inquiry/inquiry.service'; import { NoticeOfIntentModification } from '../notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.entity'; import { NoticeOfIntentModificationService } from '../notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.service'; 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 { PlanningReferral } from '../planning-review/planning-referral/planning-referral.entity'; import { PlanningReferralService } from '../planning-review/planning-referral/planning-referral.service'; import { HomeController } from './home.controller'; @@ -45,6 +47,7 @@ describe('HomeController', () => { let mockNoticeOfIntentModificationService: DeepMocked<NoticeOfIntentModificationService>; let mockNotificationService: DeepMocked<NotificationService>; let mockPlanningReferralService: DeepMocked<PlanningReferralService>; + let mockInquiryService: DeepMocked<InquiryService>; beforeEach(async () => { mockApplicationService = createMock(); @@ -56,6 +59,7 @@ describe('HomeController', () => { mockNoticeOfIntentModificationService = createMock(); mockNotificationService = createMock(); mockPlanningReferralService = createMock(); + mockInquiryService = createMock(); const module: TestingModule = await Test.createTestingModule({ imports: [ @@ -109,11 +113,16 @@ describe('HomeController', () => { provide: PlanningReferralService, useValue: mockPlanningReferralService, }, + { + provide: InquiryService, + useValue: mockInquiryService, + }, ApplicationProfile, ApplicationSubtaskProfile, UserProfile, CardProfile, NotificationProfile, + InquiryProfile, ...mockKeyCloakProviders, ], }).compile(); @@ -134,6 +143,8 @@ describe('HomeController', () => { mockNotificationService.mapToDtos.mockResolvedValue([]); mockPlanningReferralService.getBy.mockResolvedValue([]); mockPlanningReferralService.mapToDtos.mockResolvedValue([]); + mockInquiryService.getBy.mockResolvedValue([]); + mockInquiryService.mapToDtos.mockResolvedValue([]); mockNoticeOfIntentService.getTimes.mockResolvedValue(new Map()); mockApplicationTimeTrackingService.fetchActiveTimes.mockResolvedValue( @@ -162,6 +173,7 @@ describe('HomeController', () => { mockPlanningReferralService.getWithIncompleteSubtaskByType.mockResolvedValue( [], ); + mockInquiryService.getWithIncompleteSubtaskByType.mockResolvedValue([]); }); it('should be defined', () => { @@ -411,5 +423,28 @@ describe('HomeController', () => { expect(res[0].title).toContain(mockNotification.fileNumber); expect(res[0].title).toContain(mockNotification.applicant); }); + + it('should call Inquiry Service and map it', async () => { + const mockInquiry = new Inquiry({ + fileNumber: 'fileNumber', + card: initCardMockEntity('222'), + inquirerLastName: 'lastName', + }); + mockInquiryService.getWithIncompleteSubtaskByType.mockResolvedValue([ + mockInquiry, + ]); + + const res = await controller.getIncompleteSubtasksByType( + CARD_SUBTASK_TYPE.PEER_REVIEW, + ); + + expect(res.length).toEqual(1); + expect( + mockInquiryService.getWithIncompleteSubtaskByType, + ).toHaveBeenCalledTimes(1); + + expect(res[0].title).toContain(mockInquiry.fileNumber); + expect(res[0].title).toContain(mockInquiry.inquirerLastName); + }); }); }); diff --git a/services/apps/alcs/src/alcs/home/home.controller.ts b/services/apps/alcs/src/alcs/home/home.controller.ts index 9b2397130a..f5233a2d87 100644 --- a/services/apps/alcs/src/alcs/home/home.controller.ts +++ b/services/apps/alcs/src/alcs/home/home.controller.ts @@ -27,6 +27,9 @@ import { } from '../card/card-subtask/card-subtask.dto'; import { CardDto } from '../card/card.dto'; import { Card } from '../card/card.entity'; +import { InquiryDto } from '../inquiry/inquiry.dto'; +import { Inquiry } from '../inquiry/inquiry.entity'; +import { InquiryService } from '../inquiry/inquiry.service'; import { NoticeOfIntentModificationDto } from '../notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.dto'; import { NoticeOfIntentModification } from '../notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.entity'; import { NoticeOfIntentModificationService } from '../notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.service'; @@ -59,6 +62,7 @@ export class HomeController { private noticeOfIntentModificationService: NoticeOfIntentModificationService, private notificationService: NotificationService, private planningReferralService: PlanningReferralService, + private inquiryService: InquiryService, ) {} @Get('/assigned') @@ -71,6 +75,7 @@ export class HomeController { planningReferrals: PlanningReferralDto[]; modifications: ApplicationModificationDto[]; notifications: NotificationDto[]; + inquiries: InquiryDto[]; }> { const userId = req.user.entity.uuid; const assignedFindOptions = { @@ -103,6 +108,8 @@ export class HomeController { const notifications = await this.notificationService.getBy(assignedFindOptions); + const inquiries = await this.inquiryService.getBy(assignedFindOptions); + return { noticeOfIntents: await this.noticeOfIntentService.mapToDtos(noticeOfIntents), @@ -117,6 +124,7 @@ export class HomeController { await this.planningReferralService.mapToDtos(planningReviews), modifications: await this.modificationService.mapToDtos(modifications), notifications: await this.notificationService.mapToDtos(notifications), + inquiries: await this.inquiryService.mapToDtos(inquiries), }; } else { return { @@ -127,6 +135,7 @@ export class HomeController { planningReferrals: [], modifications: [], notifications: [], + inquiries: [], }; } } @@ -188,6 +197,11 @@ export class HomeController { notificationsWithSubtasks, ); + const inquiriesWIthSubtasks = + await this.inquiryService.getWithIncompleteSubtaskByType(subtaskType); + + const inquirySubtasks = this.mapInquiriesToDtos(inquiriesWIthSubtasks); + return [ ...noticeOfIntentSubtasks, ...applicationSubtasks, @@ -196,6 +210,7 @@ export class HomeController { ...noiModificationsSubtasks, ...planningReferralSubtasks, ...notificationSubtasks, + ...inquirySubtasks, ]; } @@ -374,4 +389,28 @@ export class HomeController { } return result; } + + private mapInquiriesToDtos(inquiries: Inquiry[]) { + const result: HomepageSubtaskDTO[] = []; + for (const inquiry of inquiries) { + if (inquiry.card) { + for (const subtask of inquiry.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(inquiry.card, Card, CardDto), + completedAt: subtask.completedAt?.getTime(), + paused: false, + title: `${inquiry.fileNumber} (${ + inquiry.inquirerLastName ?? 'Unknown' + })`, + parentType: PARENT_TYPE.INQUIRY, + }); + } + } + } + return result; + } } diff --git a/services/apps/alcs/src/alcs/home/home.module.ts b/services/apps/alcs/src/alcs/home/home.module.ts index 01ac60fd28..42fef95051 100644 --- a/services/apps/alcs/src/alcs/home/home.module.ts +++ b/services/apps/alcs/src/alcs/home/home.module.ts @@ -3,6 +3,7 @@ import { ApplicationSubtaskProfile } from '../../common/automapper/application-s import { UserModule } from '../../user/user.module'; import { ApplicationDecisionModule } from '../application-decision/application-decision.module'; import { ApplicationModule } from '../application/application.module'; +import { InquiryModule } from '../inquiry/inquiry.module'; import { NoticeOfIntentDecisionModule } from '../notice-of-intent-decision/notice-of-intent-decision.module'; import { NoticeOfIntentModule } from '../notice-of-intent/notice-of-intent.module'; import { NotificationModule } from '../notification/notification.module'; @@ -18,6 +19,7 @@ import { HomeController } from './home.controller'; NoticeOfIntentModule, NoticeOfIntentDecisionModule, NotificationModule, + InquiryModule, ], providers: [ApplicationSubtaskProfile], controllers: [HomeController], From 8dc839ff5a7a3823457bfc8a58cc7521863bb7e9 Mon Sep 17 00:00:00 2001 From: Liam Stoddard <lstodd@protonmail.com> Date: Tue, 26 Mar 2024 12:35:50 -0700 Subject: [PATCH 053/153] more card updates --- .../referrals/planning_review_card_update.py | 15 ++++++++++++--- .../referrals/update_planning_review_cards.sql | 11 ++++++----- .../update_planning_review_cards_count.sql | 7 +++---- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py b/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py index 258fbdfd4a..b6683da51e 100644 --- a/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py +++ b/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py @@ -46,8 +46,7 @@ def update_planning_review_cards(conn=None, batch_size=BATCH_UPLOAD_SIZE): while True: cursor.execute( f""" - {query} - AND uuid > '{last_planning_review_id}' ORDER BY uuid; + {query} WHERE ac."uuid" > '{last_planning_review_id}' ORDER BY ac."uuid"; """ ) @@ -71,7 +70,7 @@ def update_planning_review_cards(conn=None, batch_size=BATCH_UPLOAD_SIZE): logger.exception(err) conn.rollback() failed_inserts = count_total - successful_updates_count - last_planning_review_id = last_planning_review_id + 1 + last_planning_review_id = rows[1]["uuid"] logger.info( f"Finished {etl_name}: total amount of successful updates {successful_updates_count}, total failed updates {failed_inserts}" @@ -95,6 +94,7 @@ def _update_base_fields(conn, batch_size, cursor, rows): _rx_items_query = """ UPDATE alcs.card SET board_uuid = %(board_uuid)s, + status_code = %(status_code)s, audit_updated_by = %(audit_updated_by)s WHERE alcs.card.uuid = %(uuid)s """ @@ -108,7 +108,16 @@ def _prepare_oats_planning_review_data(row_data_list): "uuid": row["uuid"], "board_uuid": "d8c18278-cb41-474e-a180-534a101243ab", "audit_updated_by": None, + "status_code": _map_card_status(row), } ) return mapped_data_list + + +def _map_card_status(row): + review_status = row.get("open") + if review_status is False: + return "COMP" + else: + return row.get("status_code") diff --git a/bin/migrate-oats-data/planning_review/sql/referrals/update_planning_review_cards.sql b/bin/migrate-oats-data/planning_review/sql/referrals/update_planning_review_cards.sql index fae747fa3b..dee7cca035 100644 --- a/bin/migrate-oats-data/planning_review/sql/referrals/update_planning_review_cards.sql +++ b/bin/migrate-oats-data/planning_review/sql/referrals/update_planning_review_cards.sql @@ -1,7 +1,8 @@ SELECT - "uuid" + ac."uuid", + pr2.open, + ac.status_code FROM - alcs.card -WHERE - audit_created_by = 'oats_etl' - AND type_code = 'PLAN' \ No newline at end of file + alcs.planning_referral pr + JOIN alcs.card ac ON pr.card_uuid = ac."uuid" + JOIN alcs.planning_review pr2 ON pr.planning_review_uuid = pr2."uuid" \ No newline at end of file diff --git a/bin/migrate-oats-data/planning_review/sql/referrals/update_planning_review_cards_count.sql b/bin/migrate-oats-data/planning_review/sql/referrals/update_planning_review_cards_count.sql index 7933188257..2e4db902c7 100644 --- a/bin/migrate-oats-data/planning_review/sql/referrals/update_planning_review_cards_count.sql +++ b/bin/migrate-oats-data/planning_review/sql/referrals/update_planning_review_cards_count.sql @@ -1,7 +1,6 @@ SELECT COUNT(*) FROM - alcs.card -WHERE - audit_created_by = 'oats_etl' - AND type_code = 'PLAN' \ No newline at end of file + alcs.planning_referral pr + JOIN alcs.card ac ON pr.card_uuid = ac."uuid" + JOIN alcs.planning_review pr2 ON pr.planning_review_uuid = pr2."uuid" \ No newline at end of file From 73ccffb225e52ce139539ea7f62336b265daf0ac Mon Sep 17 00:00:00 2001 From: Liam Stoddard <lstodd@protonmail.com> Date: Tue, 26 Mar 2024 12:42:32 -0700 Subject: [PATCH 054/153] fix typos --- .../referrals/planning_review_referrals_init.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/migrate-oats-data/planning_review/referrals/planning_review_referrals_init.py b/bin/migrate-oats-data/planning_review/referrals/planning_review_referrals_init.py index 3f226d3b2a..7dc615237e 100644 --- a/bin/migrate-oats-data/planning_review/referrals/planning_review_referrals_init.py +++ b/bin/migrate-oats-data/planning_review/referrals/planning_review_referrals_init.py @@ -15,7 +15,7 @@ @inject_conn_pool def process_planning_review_referral(conn=None, batch_size=BATCH_UPLOAD_SIZE): """ - This function is responsible for populating date_submitted_to_alc in alcs.planning_referral in ALCS. + This function is responsible for populating alcs.planning_referral in ALCS. Args: conn (psycopg2.extensions.connection): PostgreSQL database connection. Provided by the decorator. @@ -67,7 +67,7 @@ def process_planning_review_referral(conn=None, batch_size=BATCH_UPLOAD_SIZE): ] logger.debug( - f"Retrieved/updated items count: {len(updated_data)}; total successfully updated planning referral so far {successful_updates_count}; last updated planning_review_id: {last_planning_review_id}" + f"Retrieved/updated items count: {len(updated_data)}; total successfully inserted planning referral so far {successful_updates_count}; last updated planning_review_id: {last_planning_review_id}" ) except Exception as err: # this is NOT going to be caused by actual data update failure. This code is only executed when the code error appears or connection to DB is lost @@ -77,7 +77,7 @@ def process_planning_review_referral(conn=None, batch_size=BATCH_UPLOAD_SIZE): last_planning_review_id = last_planning_review_id + 1 logger.info( - f"Finished {etl_name}: total amount of successful updates {successful_updates_count}, total failed updates {failed_inserts}" + f"Finished {etl_name}: total amount of successful inserts {successful_updates_count}, total failed updates {failed_inserts}" ) From 728a309f376830158391b0af406801f5f4b9a740 Mon Sep 17 00:00:00 2001 From: mhuseinov <61513701+mhuseinov@users.noreply.github.com> Date: Tue, 26 Mar 2024 12:42:59 -0700 Subject: [PATCH 055/153] Feature/alcs 1809 inquiry inquirer part2 (#1546) map inquirer --- .../inquiry/inquiry_inquirer_info.py | 139 ++++++++++++++++++ .../inquiry/inquiry_migration.py | 2 + .../inquiry/sql/inquiry_inquirer_info.sql | 17 +++ .../sql/inquiry_inquirer_info_count.sql | 6 + 4 files changed, 164 insertions(+) create mode 100644 bin/migrate-oats-data/inquiry/inquiry_inquirer_info.py create mode 100644 bin/migrate-oats-data/inquiry/sql/inquiry_inquirer_info.sql create mode 100644 bin/migrate-oats-data/inquiry/sql/inquiry_inquirer_info_count.sql diff --git a/bin/migrate-oats-data/inquiry/inquiry_inquirer_info.py b/bin/migrate-oats-data/inquiry/inquiry_inquirer_info.py new file mode 100644 index 0000000000..2153f6f0ca --- /dev/null +++ b/bin/migrate-oats-data/inquiry/inquiry_inquirer_info.py @@ -0,0 +1,139 @@ +from common import ( + BATCH_UPLOAD_SIZE, + setup_and_get_logger, + add_timezone_and_keep_date_part, +) +from db import inject_conn_pool +from psycopg2.extras import RealDictCursor, execute_batch + +etl_name = "process_inquiry_inquirer_fields" +logger = setup_and_get_logger(etl_name) + + +@inject_conn_pool +def process_inquiry_inquirer_fields(conn=None, batch_size=BATCH_UPLOAD_SIZE): + """ + This function is responsible for populating inquirer field in alcs.inquiry in ALCS. + + Args: + conn (psycopg2.extensions.connection): PostgreSQL database connection. Provided by the decorator. + batch_size (int): The number of items to process at once. Defaults to BATCH_UPLOAD_SIZE. + """ + + logger.info("Start update inquiry inquirer fields") + with conn.cursor(cursor_factory=RealDictCursor) as cursor: + with open( + "inquiry/sql/inquiry_inquirer_info_count.sql", + "r", + encoding="utf-8", + ) as sql_file: + count_query = sql_file.read() + cursor.execute(count_query) + count_total = dict(cursor.fetchone())["count"] + logger.info(f"Total inquiry data to update: {count_total}") + + failed_inserts = 0 + successful_updates_count = 0 + last_issue_id = 0 + + with open( + "inquiry/sql/inquiry_inquirer_info.sql", + "r", + encoding="utf-8", + ) as sql_file: + query = sql_file.read() + while True: + cursor.execute( + f""" + {query} + WHERE oi.issue_id > {last_issue_id} + ORDER BY oi.issue_id; + """ + ) + + rows = cursor.fetchmany(batch_size) + + if not rows: + break + try: + updated_data = _update_base_fields(conn, batch_size, cursor, rows) + + successful_updates_count = successful_updates_count + len( + updated_data + ) + last_issue_id = dict(updated_data[-1])["issue_id"] + + logger.debug( + f"Retrieved/updated items count: {len(updated_data)}; total successfully updated Inquiry so far {successful_updates_count}; last updated issue_id: {last_issue_id}" + ) + except Exception as err: + # this is NOT going to be caused by actual data update failure. This code is only executed when the code error appears or connection to DB is lost + logger.exception(err) + conn.rollback() + failed_inserts = count_total - successful_updates_count + last_issue_id = last_issue_id + 1 + + logger.info( + f"Finished {etl_name}: total amount of successful updates {successful_updates_count}, total failed updates {failed_inserts}" + ) + + +def _update_base_fields(conn, batch_size, cursor, rows): + parsed_data_list = _prepare_oats_data(rows) + + execute_batch( + cursor, + _rx_items_query, + parsed_data_list, + page_size=batch_size, + ) + + conn.commit() + return parsed_data_list + + +_rx_items_query = """ + UPDATE alcs.inquiry + SET inquirer_first_name = %(inquirer_first_name)s, + inquirer_last_name = %(inquirer_last_name)s, + inquirer_phone = %(inquirer_phone)s, + inquirer_email = %(inquirer_email)s, + inquirer_organization = %(inquirer_organization)s + WHERE alcs.inquiry.file_number = %(issue_id)s::text +""" + + +def _prepare_oats_data(row_data_list): + mapped_data_list = [] + for row in row_data_list: + mapped_data_list.append( + { + "issue_id": row["issue_id"], + "inquirer_first_name": _get_first_name(row), + "inquirer_last_name": row["last_name"], + "inquirer_phone": row.get("phone_number", "cell_phone_number"), + "inquirer_email": row["email_address"], + "inquirer_organization": _get_organization_name(row), + } + ) + + return mapped_data_list + + +def _get_first_name(row): + first_name = row.get("first_name", None) + middle_name = row.get("middle_name", None) + + return " ".join( + [name for name in (first_name, middle_name) if name is not None] + ).strip() + + +def _get_organization_name(row): + organization_name = (row.get("organization_name") or "").strip() + alias_name = (row.get("alias_name") or "").strip() + + if not organization_name and not alias_name: + return None + + return f"{organization_name} {alias_name}".strip() diff --git a/bin/migrate-oats-data/inquiry/inquiry_migration.py b/bin/migrate-oats-data/inquiry/inquiry_migration.py index fd89b70a69..7102a5c531 100644 --- a/bin/migrate-oats-data/inquiry/inquiry_migration.py +++ b/bin/migrate-oats-data/inquiry/inquiry_migration.py @@ -1,8 +1,10 @@ from .inquiry_base import init_inquiries, clean_inquiries +from .inquiry_inquirer_info import process_inquiry_inquirer_fields def process_inquiry(batch_size): init_inquiries(batch_size) + process_inquiry_inquirer_fields(batch_size) def clean_inquiry(): diff --git a/bin/migrate-oats-data/inquiry/sql/inquiry_inquirer_info.sql b/bin/migrate-oats-data/inquiry/sql/inquiry_inquirer_info.sql new file mode 100644 index 0000000000..66a3b42af0 --- /dev/null +++ b/bin/migrate-oats-data/inquiry/sql/inquiry_inquirer_info.sql @@ -0,0 +1,17 @@ +SELECT oi.issue_id, + opo.phone_number, + opo.cell_phone_number, + opo.email_address, + op.person_id, + op.first_name, + op.last_name, + op.middle_name, + oo.organization_id, + oo.organization_name, + oo.alias_name, + opo.person_organization_id +FROM oats.oats_issues oi + JOIN alcs.inquiry i ON i.file_number = oi.issue_id::TEXT + JOIN oats.oats_person_organizations opo ON opo.person_organization_id = oi.filed_by_pog_id + LEFT JOIN oats.oats_persons op ON opo.person_id = op.person_id + LEFT JOIN oats.oats_organizations oo ON oo.organization_id = opo.organization_id \ No newline at end of file diff --git a/bin/migrate-oats-data/inquiry/sql/inquiry_inquirer_info_count.sql b/bin/migrate-oats-data/inquiry/sql/inquiry_inquirer_info_count.sql new file mode 100644 index 0000000000..b69cac902c --- /dev/null +++ b/bin/migrate-oats-data/inquiry/sql/inquiry_inquirer_info_count.sql @@ -0,0 +1,6 @@ +SELECT count(*) +FROM oats.oats_issues oi + JOIN alcs.inquiry i ON i.file_number = oi.issue_id::TEXT + JOIN oats.oats_person_organizations opo ON opo.person_organization_id = oi.filed_by_pog_id + LEFT JOIN oats.oats_persons op ON opo.person_id = op.person_id + LEFT JOIN oats.oats_organizations oo ON oo.organization_id = opo.organization_id \ No newline at end of file From cba2ce26359c6f13e0a38b5e7453d6a70b13ccd5 Mon Sep 17 00:00:00 2001 From: Liam Stoddard <lstodd@protonmail.com> Date: Tue, 26 Mar 2024 13:17:51 -0700 Subject: [PATCH 056/153] change board to regional planning --- .../planning_review/referrals/planning_review_card_update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py b/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py index b6683da51e..7c8b3bb3a8 100644 --- a/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py +++ b/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py @@ -106,7 +106,7 @@ def _prepare_oats_planning_review_data(row_data_list): mapped_data_list.append( { "uuid": row["uuid"], - "board_uuid": "d8c18278-cb41-474e-a180-534a101243ab", + "board_uuid": "e7b18852-4f8f-419e-83e3-60e706b4a494", "audit_updated_by": None, "status_code": _map_card_status(row), } From be462633ccf896b7b224ac7a5519ea38d154fa35 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Wed, 27 Mar 2024 10:44:46 -0700 Subject: [PATCH 057/153] Inquiry Detailed Page * Detailed component * Add Overview and Details Page --- alcs-frontend/src/app/app-routing.module.ts | 8 ++ .../inquiry/inquiry-dialog.component.html | 2 +- .../inquiry/detail/details.component.html | 66 ++++++++++++++ .../inquiry/detail/details.component.scss | 20 +++++ .../inquiry/detail/details.component.spec.ts | 64 ++++++++++++++ .../inquiry/detail/details.component.ts | 87 +++++++++++++++++++ .../inquiry/header/header.component.html | 49 +++++++++++ .../inquiry/header/header.component.scss | 58 +++++++++++++ .../inquiry/header/header.component.spec.ts | 58 +++++++++++++ .../inquiry/header/header.component.ts | 45 ++++++++++ .../features/inquiry/inquiry.component.html | 23 +++++ .../features/inquiry/inquiry.component.scss | 78 +++++++++++++++++ .../inquiry/inquiry.component.spec.ts | 46 ++++++++++ .../app/features/inquiry/inquiry.component.ts | 63 ++++++++++++++ .../app/features/inquiry/inquiry.module.ts | 26 ++++++ .../inquiry/overview/overview.component.html | 33 +++++++ .../inquiry/overview/overview.component.scss | 7 ++ .../overview/overview.component.spec.ts | 38 ++++++++ .../inquiry/overview/overview.component.ts | 44 ++++++++++ .../decision/decision.component.html | 2 +- .../staff-journal.dto.ts | 5 ++ .../staff-journal.service.ts | 8 ++ .../inquiry/inquiry-detail.service.spec.ts | 56 ++++++++++++ .../inquiry/inquiry-detail.service.ts | 30 +++++++ .../src/app/services/inquiry/inquiry.dto.ts | 22 +---- .../app/services/inquiry/inquiry.service.ts | 22 ++--- .../inline-text/inline-text.component.html | 6 +- .../inline-text/inline-text.component.ts | 1 + .../staff-journal/staff-journal.component.ts | 55 +++++++----- .../alcs/inquiry/inquiry.controller.spec.ts | 7 ++ .../src/alcs/inquiry/inquiry.controller.ts | 50 ++++++++++- .../apps/alcs/src/alcs/inquiry/inquiry.dto.ts | 29 ++++--- .../alcs/src/alcs/inquiry/inquiry.entity.ts | 2 +- .../src/alcs/inquiry/inquiry.service.spec.ts | 3 +- .../alcs/src/alcs/inquiry/inquiry.service.ts | 25 +++--- .../staff-journal/staff-journal.controller.ts | 16 ++++ .../alcs/staff-journal/staff-journal.dto.ts | 16 ++-- .../staff-journal/staff-journal.service.ts | 13 +++ 38 files changed, 1098 insertions(+), 85 deletions(-) create mode 100644 alcs-frontend/src/app/features/inquiry/detail/details.component.html create mode 100644 alcs-frontend/src/app/features/inquiry/detail/details.component.scss create mode 100644 alcs-frontend/src/app/features/inquiry/detail/details.component.spec.ts create mode 100644 alcs-frontend/src/app/features/inquiry/detail/details.component.ts create mode 100644 alcs-frontend/src/app/features/inquiry/header/header.component.html create mode 100644 alcs-frontend/src/app/features/inquiry/header/header.component.scss create mode 100644 alcs-frontend/src/app/features/inquiry/header/header.component.spec.ts create mode 100644 alcs-frontend/src/app/features/inquiry/header/header.component.ts create mode 100644 alcs-frontend/src/app/features/inquiry/inquiry.component.html create mode 100644 alcs-frontend/src/app/features/inquiry/inquiry.component.scss create mode 100644 alcs-frontend/src/app/features/inquiry/inquiry.component.spec.ts create mode 100644 alcs-frontend/src/app/features/inquiry/inquiry.component.ts create mode 100644 alcs-frontend/src/app/features/inquiry/inquiry.module.ts create mode 100644 alcs-frontend/src/app/features/inquiry/overview/overview.component.html create mode 100644 alcs-frontend/src/app/features/inquiry/overview/overview.component.scss create mode 100644 alcs-frontend/src/app/features/inquiry/overview/overview.component.spec.ts create mode 100644 alcs-frontend/src/app/features/inquiry/overview/overview.component.ts create mode 100644 alcs-frontend/src/app/services/inquiry/inquiry-detail.service.spec.ts create mode 100644 alcs-frontend/src/app/services/inquiry/inquiry-detail.service.ts diff --git a/alcs-frontend/src/app/app-routing.module.ts b/alcs-frontend/src/app/app-routing.module.ts index ae6b1aa322..2f426129de 100644 --- a/alcs-frontend/src/app/app-routing.module.ts +++ b/alcs-frontend/src/app/app-routing.module.ts @@ -61,6 +61,14 @@ const routes: Routes = [ }, loadChildren: () => import('./features/planning-review/planning-review.module').then((m) => m.PlanningReviewModule), }, + { + path: 'inquiry', + canActivate: [HasRolesGuard], + data: { + roles: ROLES_ALLOWED_APPLICATIONS, + }, + loadChildren: () => import('./features/inquiry/inquiry.module').then((m) => m.InquiryModule), + }, { path: 'schedule', canActivate: [HasRolesGuard], diff --git a/alcs-frontend/src/app/features/board/dialogs/inquiry/inquiry-dialog.component.html b/alcs-frontend/src/app/features/board/dialogs/inquiry/inquiry-dialog.component.html index 2fff90b240..2698755af4 100644 --- a/alcs-frontend/src/app/features/board/dialogs/inquiry/inquiry-dialog.component.html +++ b/alcs-frontend/src/app/features/board/dialogs/inquiry/inquiry-dialog.component.html @@ -17,7 +17,7 @@ <h3 class="card-title center"> color="accent" mat-flat-button [mat-dialog-close]="isDirty" - [routerLink]="['planning-review', inquiry.fileNumber]" + [routerLink]="['inquiry', inquiry.fileNumber]" > View Detail </button> diff --git a/alcs-frontend/src/app/features/inquiry/detail/details.component.html b/alcs-frontend/src/app/features/inquiry/detail/details.component.html new file mode 100644 index 0000000000..58b37d2ec7 --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/detail/details.component.html @@ -0,0 +1,66 @@ +<div class="split"> + <h3>Inquiry Details</h3> +</div> +<section *ngIf="inquiry"> + <div class="two-item-row"> + <div class="full-width"> + <h5>Type</h5> + <div style="width: 50%"> + <app-inline-dropdown (save)="onSaveType($event)" [options]="types" [value]="inquiry.type.code" /> + </div> + </div> + + <div class="full-width"> + <h5>Submitted to ALC</h5> + <div style="width: 50%"> + <app-inline-datepicker (save)="onSaveSubmittedToALC($event)" [value]="inquiry.dateSubmittedToAlc" /> + </div> + </div> + </div> +</section> +<section *ngIf="inquiry"> + <h5>L/FNG Information</h5> + <div class="two-item-row"> + <div> + <div class="subheading2">Local/First Nation Government</div> + <app-inline-dropdown (save)="onSaveType($event)" [options]="governments" [value]="inquiry.localGovernmentUuid" /> + </div> + + <div> + <div class="subheading2">Region</div> + <app-inline-dropdown (save)="onSaveType($event)" [options]="regions" [value]="inquiry.regionCode" /> + </div> + </div> +</section> +<section *ngIf="inquiry"> + <h5>Inquirer</h5> + <div class="two-item-row"> + <div> + <div class="subheading2">First Name</div> + <app-inline-text [value]="inquiry.inquirerFirstName" (save)="onSaveTextField($event, 'inquirerFirstName')" /> + </div> + + <div> + <div class="subheading2">Last Name</div> + <app-inline-text [value]="inquiry.inquirerLastName" (save)="onSaveTextField($event, 'inquirerLastName')" /> + </div> + + <div class="full-width"> + <div class="subheading2">Organization</div> + <app-inline-text + [value]="inquiry.inquirerOrganization" + (save)="onSaveTextField($event, 'inquirerOrganization')" + /> + </div> + + <div> + <div class="subheading2">Phone</div> + <app-inline-text mask="(000) 000-0000" [value]="inquiry.inquirerPhone" (save)="onSaveTextField($event, 'inquirerPhone')" /> + </div> + + <div> + <div class="subheading2">Email</div> + <app-inline-text [value]="inquiry.inquirerEmail" (save)="onSaveTextField($event, 'inquirerEmail')" /> + </div> + </div> +</section> diff --git a/alcs-frontend/src/app/features/inquiry/detail/details.component.scss b/alcs-frontend/src/app/features/inquiry/detail/details.component.scss new file mode 100644 index 0000000000..c8345a4c49 --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/detail/details.component.scss @@ -0,0 +1,20 @@ +h5 { + margin: 16px 0 !important; +} + +section { + margin: 32px 0 48px; +} + +.two-item-row { + display: grid; + grid-template-columns: 1fr 1fr; + grid-column-gap: 24px; + grid-row-gap: 24px; + margin-bottom: 24px; +} + +.full-width { + grid-column: 1 /3; + width: 100%; +} diff --git a/alcs-frontend/src/app/features/inquiry/detail/details.component.spec.ts b/alcs-frontend/src/app/features/inquiry/detail/details.component.spec.ts new file mode 100644 index 0000000000..e04a27f0c9 --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/detail/details.component.spec.ts @@ -0,0 +1,64 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { BehaviorSubject } from 'rxjs'; +import { ApplicationRegionDto } from '../../../services/application/application-code.dto'; +import { ApplicationLocalGovernmentService } from '../../../services/application/application-local-government/application-local-government.service'; +import { ApplicationService } from '../../../services/application/application.service'; +import { InquiryDetailService } from '../../../services/inquiry/inquiry-detail.service'; +import { InquiryDto } from '../../../services/inquiry/inquiry.dto'; +import { InquiryService } from '../../../services/inquiry/inquiry.service'; + +import { DetailsComponent } from './details.component'; + +describe('DetailsComponent', () => { + let component: DetailsComponent; + let fixture: ComponentFixture<DetailsComponent>; + + let inquiryDetailService: DeepMocked<InquiryDetailService>; + let inquiryService: DeepMocked<InquiryService>; + let applicationService: DeepMocked<ApplicationService>; + let localGovernmentService: DeepMocked<ApplicationLocalGovernmentService>; + + beforeEach(async () => { + inquiryDetailService = createMock(); + inquiryService = createMock(); + applicationService = createMock(); + localGovernmentService = createMock(); + + localGovernmentService.list.mockResolvedValue([]); + inquiryDetailService.$inquiry = new BehaviorSubject<InquiryDto | undefined>(undefined); + applicationService.$applicationRegions = new BehaviorSubject<ApplicationRegionDto[]>([]); + + await TestBed.configureTestingModule({ + providers: [ + { + provide: InquiryDetailService, + useValue: inquiryDetailService, + }, + { + provide: InquiryService, + useValue: inquiryService, + }, + { + provide: ApplicationService, + useValue: applicationService, + }, + { + provide: ApplicationLocalGovernmentService, + useValue: localGovernmentService, + }, + ], + declarations: [DetailsComponent], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(DetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alcs-frontend/src/app/features/inquiry/detail/details.component.ts b/alcs-frontend/src/app/features/inquiry/detail/details.component.ts new file mode 100644 index 0000000000..412b171396 --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/detail/details.component.ts @@ -0,0 +1,87 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Subject, takeUntil } from 'rxjs'; +import { ApplicationLocalGovernmentService } from '../../../services/application/application-local-government/application-local-government.service'; +import { ApplicationService } from '../../../services/application/application.service'; +import { InquiryDetailService } from '../../../services/inquiry/inquiry-detail.service'; +import { InquiryDto, UpdateInquiryDto } from '../../../services/inquiry/inquiry.dto'; +import { InquiryService } from '../../../services/inquiry/inquiry.service'; + +@Component({ + selector: 'app-detail', + templateUrl: './details.component.html', + styleUrls: ['./details.component.scss'], +}) +export class DetailsComponent implements OnInit, OnDestroy { + $destroy = new Subject<void>(); + inquiry?: InquiryDto; + types: { label: string; value: string }[] = []; + governments: { label: string; value: string }[] = []; + regions: { label: string; value: string }[] = []; + + constructor( + private inquiryDetailService: InquiryDetailService, + private inquiryService: InquiryService, + private applicationService: ApplicationService, + private localGovernmentService: ApplicationLocalGovernmentService, + ) {} + + ngOnInit(): void { + this.inquiryDetailService.$inquiry.pipe(takeUntil(this.$destroy)).subscribe((inquiry) => { + this.inquiry = inquiry; + this.loadTypes(); + }); + + this.applicationService.$applicationRegions.pipe(takeUntil(this.$destroy)).subscribe((regions) => { + this.regions = regions.map((region) => ({ + label: region.label, + value: region.code, + })); + }); + + this.localGovernmentService.list().then((res) => { + this.governments = res.map((government) => ({ + label: government.name, + value: government.uuid, + })); + }); + } + + ngOnDestroy(): void { + this.$destroy.next(); + this.$destroy.complete(); + } + + async onSaveType($event: string | string[] | null) { + if ($event && !Array.isArray($event) && this.inquiry) { + await this.inquiryDetailService.update(this.inquiry.fileNumber, { + typeCode: $event, + }); + } + } + + private async loadTypes() { + const types = await this.inquiryService.fetchTypes(); + if (types) { + this.types = types.map((type) => ({ + label: type.label, + value: type.code, + })); + } + } + + async onSaveSubmittedToALC($event: number) { + if (this.inquiry) { + await this.inquiryDetailService.update(this.inquiry.fileNumber, { + dateSubmittedToAlc: $event, + }); + } + } + + async onSaveTextField($event: string | null, inquirerFirstName: keyof UpdateInquiryDto) { + if (this.inquiry) { + await this.inquiryDetailService.update(this.inquiry.fileNumber, { + [inquirerFirstName]: $event, + }); + } + } +} diff --git a/alcs-frontend/src/app/features/inquiry/header/header.component.html b/alcs-frontend/src/app/features/inquiry/header/header.component.html new file mode 100644 index 0000000000..4216460cdd --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/header/header.component.html @@ -0,0 +1,49 @@ +<div class="header"> + <div> + <span class="subtext heading">Inquiry</span> + </div> + <div class="first-row"> + <div class="title"> + <h5>{{ inquiry.fileNumber }} ({{ inquiry.inquirerLastName ?? 'Unknown' }})</h5> + <div class="labels"> + <app-application-type-pill [type]="inquiry.type"></app-application-type-pill> + </div> + </div> + <div class="center"> + <button + *ngIf="linkedCards.length === 1" + class="menu-item" + mat-flat-button + color="accent" + (click)="onGoToCard(linkedCards[0])" + > + <div class="center"> + Go to card + <mat-icon style="transform: scale(1.1)">arrow_right_alt</mat-icon> + </div> + </button> + <ng-container *ngIf="linkedCards.length > 1"> + <button class="menu-item center" mat-flat-button color="accent" [matMenuTriggerFor]="goToMenu"> + Go to card â–¾ + </button> + <mat-menu class="move-board-menu" xPosition="before" #goToMenu="matMenu"> + <button *ngFor="let card of linkedCards" mat-menu-item (click)="onGoToCard(card)"> + {{ card.displayName }} + </button> + </mat-menu> + </ng-container> + </div> + </div> + <div class="sub-heading"> + <div> + <div class="subheading2">Local/First Nation Government:</div> + <div class="body-text"> + {{ inquiry.localGovernment.name }} + <app-no-data *ngIf="!inquiry.localGovernment"></app-no-data> + </div> + </div> + <div class="status-wrapper"> + <app-application-submission-status-type-pill [type]="statusPill"></app-application-submission-status-type-pill> + </div> + </div> +</div> diff --git a/alcs-frontend/src/app/features/inquiry/header/header.component.scss b/alcs-frontend/src/app/features/inquiry/header/header.component.scss new file mode 100644 index 0000000000..9c6cbbe865 --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/header/header.component.scss @@ -0,0 +1,58 @@ +@use '../../../../styles/colors'; + +.heading { + color: colors.$primary-color-dark; + margin-bottom: 8px; +} + +.header { + padding: 16px 80px; + border-bottom: 1px solid colors.$primary-color-dark; + + .first-row { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; + } + + .title { + display: flex; + flex-wrap: wrap; + align-items: center; + line-height: 32px; + + h5 { + margin-right: 8px !important; + } + } + + .labels { + display: flex; + flex-wrap: wrap; + margin-top: -8px; + margin-right: 8px; + + app-application-type-pill { + margin-top: 8px; + } + } + + .sub-heading { + margin-top: 16px; + margin-bottom: 8px; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + } + + .subheading2 { + margin-bottom: 6px !important; + } +} + +.status-wrapper { + display: flex; + flex-direction: row-reverse; + align-items: flex-end; +} diff --git a/alcs-frontend/src/app/features/inquiry/header/header.component.spec.ts b/alcs-frontend/src/app/features/inquiry/header/header.component.spec.ts new file mode 100644 index 0000000000..d13f537196 --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/header/header.component.spec.ts @@ -0,0 +1,58 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { HeaderComponent } from './header.component'; + +describe('HeaderComponent', () => { + let component: HeaderComponent; + let fixture: ComponentFixture<HeaderComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [RouterTestingModule], + declarations: [HeaderComponent], + providers: [], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(HeaderComponent); + component = fixture.componentInstance; + + component.inquiry = { + dateSubmittedToAlc: 0, + localGovernmentUuid: '', + regionCode: '', + summary: '', + typeCode: '', + fileNumber: '', + localGovernment: { + uuid: '', + name: '', + isFirstNation: false, + preferredRegionCode: '', + }, + open: false, + region: { + label: '', + code: '', + description: '', + }, + type: { + code: '', + description: '', + label: '', + backgroundColor: '', + shortLabel: '', + textColor: '', + }, + uuid: '', + }; + + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alcs-frontend/src/app/features/inquiry/header/header.component.ts b/alcs-frontend/src/app/features/inquiry/header/header.component.ts new file mode 100644 index 0000000000..d5eae13100 --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/header/header.component.ts @@ -0,0 +1,45 @@ +import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { Router } from '@angular/router'; +import { Subject } from 'rxjs'; +import { CardDto } from '../../../services/card/card.dto'; +import { CommissionerPlanningReviewDto } from '../../../services/commissioner/commissioner.dto'; +import { InquiryDto } from '../../../services/inquiry/inquiry.dto'; +import { PlanningReviewDetailedDto } from '../../../services/planning-review/planning-review.dto'; +import { CLOSED_PR_LABEL, OPEN_PR_LABEL } from '../../../shared/application-type-pill/application-type-pill.constants'; + +@Component({ + selector: 'app-inquiry-header[inquiry]', + templateUrl: './header.component.html', + styleUrls: ['./header.component.scss'], +}) +export class HeaderComponent implements OnChanges { + destroy = new Subject<void>(); + + @Input() inquiry!: InquiryDto; + + linkedCards: (CardDto & { displayName: string })[] = []; + statusPill = OPEN_PR_LABEL; + + constructor(private router: Router) {} + + async onGoToCard(card: CardDto) { + const boardCode = card.boardCode; + const cardUuid = card.uuid; + const cardTypeCode = card.type; + await this.router.navigateByUrl(`/board/${boardCode}?card=${cardUuid}&type=${cardTypeCode}`); + } + + async setupLinkedCards() { + if (this.inquiry.card) { + this.linkedCards.push({ + ...this.inquiry.card, + displayName: `Inquiry`, + }); + } + } + + ngOnChanges(): void { + this.setupLinkedCards(); + this.statusPill = this.inquiry.open ? OPEN_PR_LABEL : CLOSED_PR_LABEL; + } +} diff --git a/alcs-frontend/src/app/features/inquiry/inquiry.component.html b/alcs-frontend/src/app/features/inquiry/inquiry.component.html new file mode 100644 index 0000000000..a928fa8438 --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/inquiry.component.html @@ -0,0 +1,23 @@ +<div class="layout"> + <div class="application"> + <app-inquiry-header *ngIf="inquiry" [inquiry]="inquiry"></app-inquiry-header> + <div class="content"> + <div class="nav"> + <div *ngFor="let route of childRoutes" class="nav-link"> + <div + [routerLink]="route.path ? route.path : './'" + routerLinkActive="active" + [routerLinkActiveOptions]="{ exact: route.path === '' }" + class="nav-item nav-text" + > + <mat-icon>{{ route.icon }}</mat-icon> + {{ route.menuTitle }} + </div> + </div> + </div> + <div class="child-content"> + <router-outlet></router-outlet> + </div> + </div> + </div> +</div> diff --git a/alcs-frontend/src/app/features/inquiry/inquiry.component.scss b/alcs-frontend/src/app/features/inquiry/inquiry.component.scss new file mode 100644 index 0000000000..55dfeb4d75 --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/inquiry.component.scss @@ -0,0 +1,78 @@ +@use '../../../styles/colors'; + +.layout { + display: flex; + flex-direction: row; + height: 100%; + width: 100%; + justify-content: center; +} + +.application { + width: 100%; + display: flex; + flex-direction: column; +} + +.content { + display: flex; + flex-grow: 1; + padding-right: 80px; +} + +.child-content { + margin: 24px 0 0 48px; + flex-grow: 1; +} + +.nav { + background-color: colors.$bg-color; + min-width: 240px; + width: 240px; + height: 100%; +} + +.nav-link { + + div { + padding: 12px 24px; + border: 2px solid transparent; + } + + div.active { + font-weight: bold; + background-color: colors.$primary-color-dark; + color: colors.$white; + border-color: colors.$primary-color-dark; + + &:hover { + cursor: default; + background-color: colors.$primary-color-dark; + color: colors.$white; + } + } + + div:not(.disabled):hover { + cursor: pointer; + border-color: colors.$primary-color-dark; + color: colors.$dark-contrast-text; + } + + div.active:not(.disabled):hover { + color: colors.$white; + } +} + +.nav-item { + display: flex; + align-items: center; + white-space: pre-wrap; + + .mat-icon { + padding-right: 32px; + } + + &.disabled { + opacity: 0.5; + } +} diff --git a/alcs-frontend/src/app/features/inquiry/inquiry.component.spec.ts b/alcs-frontend/src/app/features/inquiry/inquiry.component.spec.ts new file mode 100644 index 0000000000..0d9f91a0b0 --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/inquiry.component.spec.ts @@ -0,0 +1,46 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActivatedRoute, ParamMap } from '@angular/router'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { InquiryDetailService } from '../../services/inquiry/inquiry-detail.service'; +import { InquiryDto } from '../../services/inquiry/inquiry.dto'; + +import { InquiryComponent } from './inquiry.component'; + +describe('InquiryComponent', () => { + let component: InquiryComponent; + let fixture: ComponentFixture<InquiryComponent>; + let mockPlanningReviewDetailService: DeepMocked<InquiryDetailService>; + let mockActivateRoute: DeepMocked<ActivatedRoute>; + + beforeEach(() => { + mockPlanningReviewDetailService = createMock(); + mockActivateRoute = createMock(); + + Object.assign(mockActivateRoute, { params: new Observable<ParamMap>() }); + mockPlanningReviewDetailService.$inquiry = new BehaviorSubject<InquiryDto | undefined>(undefined); + + TestBed.configureTestingModule({ + declarations: [InquiryComponent], + providers: [ + { + provide: InquiryDetailService, + useValue: mockPlanningReviewDetailService, + }, + { + provide: ActivatedRoute, + useValue: mockActivateRoute, + }, + ], + schemas: [NO_ERRORS_SCHEMA], + }); + fixture = TestBed.createComponent(InquiryComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alcs-frontend/src/app/features/inquiry/inquiry.component.ts b/alcs-frontend/src/app/features/inquiry/inquiry.component.ts new file mode 100644 index 0000000000..dda6d43306 --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/inquiry.component.ts @@ -0,0 +1,63 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Subject, takeUntil } from 'rxjs'; +import { InquiryDetailService } from '../../services/inquiry/inquiry-detail.service'; +import { InquiryDto } from '../../services/inquiry/inquiry.dto'; +import { DetailsComponent } from './detail/details.component'; +import { OverviewComponent } from './overview/overview.component'; + +export const childRoutes = [ + { + path: '', + menuTitle: 'Overview', + icon: 'summarize', + component: OverviewComponent, + }, + { + path: 'details', + menuTitle: 'Details', + icon: 'person', + component: DetailsComponent, + }, +]; + +@Component({ + selector: 'app-planning-review', + templateUrl: './inquiry.component.html', + styleUrls: ['./inquiry.component.scss'], +}) +export class InquiryComponent implements OnInit, OnDestroy { + $destroy = new Subject<void>(); + + inquiry?: InquiryDto; + fileNumber?: string; + childRoutes = childRoutes; + + constructor( + private planningReviewService: InquiryDetailService, + private route: ActivatedRoute, + ) {} + + ngOnInit(): void { + this.route.params.pipe(takeUntil(this.$destroy)).subscribe(async (routeParams) => { + const { fileNumber } = routeParams; + this.fileNumber = fileNumber; + await this.loadReview(); + }); + + this.planningReviewService.$inquiry.pipe(takeUntil(this.$destroy)).subscribe((inquiry) => { + this.inquiry = inquiry; + }); + } + + private async loadReview() { + if (this.fileNumber) { + await this.planningReviewService.loadInquiry(this.fileNumber); + } + } + + ngOnDestroy(): void { + this.$destroy.next(); + this.$destroy.complete(); + } +} diff --git a/alcs-frontend/src/app/features/inquiry/inquiry.module.ts b/alcs-frontend/src/app/features/inquiry/inquiry.module.ts new file mode 100644 index 0000000000..34e0f6c779 --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/inquiry.module.ts @@ -0,0 +1,26 @@ +import { CdkDrag, CdkDropList } from '@angular/cdk/drag-drop'; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { InquiryDetailService } from '../../services/inquiry/inquiry-detail.service'; +import { SharedModule } from '../../shared/shared.module'; +import { DetailsComponent } from './detail/details.component'; +import { HeaderComponent } from './header/header.component'; +import { childRoutes, InquiryComponent } from './inquiry.component'; +import { OverviewComponent } from './overview/overview.component'; + +const routes: Routes = [ + { + path: ':fileNumber', + component: InquiryComponent, + children: childRoutes, + }, +]; + +@NgModule({ + providers: [InquiryDetailService], + declarations: [InquiryComponent, OverviewComponent, HeaderComponent, DetailsComponent], + imports: [CommonModule, SharedModule, RouterModule.forChild(routes), CdkDropList, CdkDrag], + exports: [HeaderComponent], +}) +export class InquiryModule {} diff --git a/alcs-frontend/src/app/features/inquiry/overview/overview.component.html b/alcs-frontend/src/app/features/inquiry/overview/overview.component.html new file mode 100644 index 0000000000..39d41ccf5a --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/overview/overview.component.html @@ -0,0 +1,33 @@ +<div class="split"> + <h3>Overview</h3> +</div> +<section *ngIf="inquiry"> + <h5>Summary</h5> + <app-inline-edit + [value]="inquiry.summary" + placeholder="Add Proposal Summary" + (save)="onSaveSummary($event)" + ></app-inline-edit> +</section> +<section *ngIf="inquiry"> + <h5>Status</h5> + <div style="width: 50%"> + <app-inline-button-toggle + (save)="onSaveStatus($event)" + [selectedValue]="inquiry.open ? 'Open' : 'Closed'" + [options]="[ + { + label: 'Open', + value: 'Open' + }, + { + label: 'Closed', + value: 'Closed' + } + ]" + /> + </div> +</section> +<section> + <app-staff-journal *ngIf="inquiry" parentType="Inquiry" [parentUuid]="inquiry.uuid" /> +</section> diff --git a/alcs-frontend/src/app/features/inquiry/overview/overview.component.scss b/alcs-frontend/src/app/features/inquiry/overview/overview.component.scss new file mode 100644 index 0000000000..439e3e77c2 --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/overview/overview.component.scss @@ -0,0 +1,7 @@ +h5 { + margin: 16px 0 !important; +} + +section { + margin: 32px 0; +} diff --git a/alcs-frontend/src/app/features/inquiry/overview/overview.component.spec.ts b/alcs-frontend/src/app/features/inquiry/overview/overview.component.spec.ts new file mode 100644 index 0000000000..c28b3a8533 --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/overview/overview.component.spec.ts @@ -0,0 +1,38 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { BehaviorSubject } from 'rxjs'; +import { InquiryDetailService } from '../../../services/inquiry/inquiry-detail.service'; +import { InquiryDto } from '../../../services/inquiry/inquiry.dto'; + +import { OverviewComponent } from './overview.component'; + +describe('OverviewComponent', () => { + let component: OverviewComponent; + let fixture: ComponentFixture<OverviewComponent>; + let inquiryDetailService: DeepMocked<InquiryDetailService>; + + beforeEach(async () => { + inquiryDetailService = createMock(); + inquiryDetailService.$inquiry = new BehaviorSubject<InquiryDto | undefined>(undefined); + + await TestBed.configureTestingModule({ + providers: [ + { + provide: InquiryDetailService, + useValue: inquiryDetailService, + }, + ], + declarations: [OverviewComponent], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(OverviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alcs-frontend/src/app/features/inquiry/overview/overview.component.ts b/alcs-frontend/src/app/features/inquiry/overview/overview.component.ts new file mode 100644 index 0000000000..9eb0aaec13 --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/overview/overview.component.ts @@ -0,0 +1,44 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Subject, takeUntil } from 'rxjs'; +import { InquiryDetailService } from '../../../services/inquiry/inquiry-detail.service'; +import { InquiryDto } from '../../../services/inquiry/inquiry.dto'; +import { TimelineEventDto } from '../../../services/planning-review/planning-review-timeline/planning-review-timeline.dto'; + +@Component({ + selector: 'app-overview', + templateUrl: './overview.component.html', + styleUrls: ['./overview.component.scss'], +}) +export class OverviewComponent implements OnInit, OnDestroy { + $destroy = new Subject<void>(); + inquiry?: InquiryDto; + + constructor(private inquiryDetailService: InquiryDetailService) {} + + ngOnInit(): void { + this.inquiryDetailService.$inquiry.pipe(takeUntil(this.$destroy)).subscribe((inquiry) => { + this.inquiry = inquiry; + }); + } + + ngOnDestroy(): void { + this.$destroy.next(); + this.$destroy.complete(); + } + + async onSaveStatus($event: string) { + if (this.inquiry) { + await this.inquiryDetailService.update(this.inquiry.fileNumber, { + open: $event === 'Open', + }); + } + } + + async onSaveSummary($event: string) { + if (this.inquiry) { + await this.inquiryDetailService.update(this.inquiry.fileNumber, { + summary: $event, + }); + } + } +} diff --git a/alcs-frontend/src/app/features/planning-review/decision/decision.component.html b/alcs-frontend/src/app/features/planning-review/decision/decision.component.html index bfbf8398f7..7bc6d68495 100644 --- a/alcs-frontend/src/app/features/planning-review/decision/decision.component.html +++ b/alcs-frontend/src/app/features/planning-review/decision/decision.component.html @@ -1,5 +1,5 @@ <div class="split"> - <div><h1>Decisions</h1></div> + <div><h3>Decisions</h3></div> <div matTooltip="{{ disabledCreateBtnTooltip }}" [matTooltipDisabled]="!isDraftExists"> <button [disabled]="isDraftExists" mat-flat-button color="primary" (click)="onCreate()">+ Decision Draft</button> </div> diff --git a/alcs-frontend/src/app/services/application/application-staff-journal/staff-journal.dto.ts b/alcs-frontend/src/app/services/application/application-staff-journal/staff-journal.dto.ts index d7928b25ec..7749819978 100644 --- a/alcs-frontend/src/app/services/application/application-staff-journal/staff-journal.dto.ts +++ b/alcs-frontend/src/app/services/application/application-staff-journal/staff-journal.dto.ts @@ -27,6 +27,11 @@ export interface CreatePlanningReviewStaffJournalDto { body: string; } +export interface CreateInquiryStaffJournalDto { + inquiryUuid: string; + body: string; +} + export interface UpdateStaffJournalDto { uuid: string; body: string; diff --git a/alcs-frontend/src/app/services/application/application-staff-journal/staff-journal.service.ts b/alcs-frontend/src/app/services/application/application-staff-journal/staff-journal.service.ts index eb15da98e1..14c12fa854 100644 --- a/alcs-frontend/src/app/services/application/application-staff-journal/staff-journal.service.ts +++ b/alcs-frontend/src/app/services/application/application-staff-journal/staff-journal.service.ts @@ -10,6 +10,7 @@ import { CreateNoticeOfIntentStaffJournalDto, CreateNotificationStaffJournalDto, CreatePlanningReviewStaffJournalDto, + CreateInquiryStaffJournalDto, } from './staff-journal.dto'; @Injectable({ @@ -17,6 +18,7 @@ import { }) export class StaffJournalService { baseUrl = `${environment.apiUrl}/application-staff-journal`; + constructor( private http: HttpClient, private toastService: ToastService, @@ -50,6 +52,12 @@ export class StaffJournalService { return createdNote; } + async createNoteForInquiry(note: CreateInquiryStaffJournalDto) { + const createdNote = firstValueFrom(this.http.post<StaffJournalDto>(`${this.baseUrl}/inquiry`, note)); + this.toastService.showSuccessToast('Journal note created'); + return createdNote; + } + async updateNote(note: UpdateStaffJournalDto) { const updatedNote = firstValueFrom(this.http.patch<StaffJournalDto>(`${this.baseUrl}`, note)); this.toastService.showSuccessToast('Journal note updated'); diff --git a/alcs-frontend/src/app/services/inquiry/inquiry-detail.service.spec.ts b/alcs-frontend/src/app/services/inquiry/inquiry-detail.service.spec.ts new file mode 100644 index 0000000000..c89e9070f1 --- /dev/null +++ b/alcs-frontend/src/app/services/inquiry/inquiry-detail.service.spec.ts @@ -0,0 +1,56 @@ +import { TestBed } from '@angular/core/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { firstValueFrom } from 'rxjs'; +import { InquiryDetailService } from './inquiry-detail.service'; +import { InquiryDto } from './inquiry.dto'; +import { InquiryService } from './inquiry.service'; + +describe('InquiryDetailService', () => { + let service: InquiryDetailService; + let mockInquiryService: DeepMocked<InquiryService>; + + beforeEach(() => { + mockInquiryService = createMock(); + + TestBed.configureTestingModule({ + providers: [ + InquiryDetailService, + { + provide: InquiryService, + useValue: mockInquiryService, + }, + ], + }); + service = TestBed.inject(InquiryDetailService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should publish the loaded application', async () => { + mockInquiryService.fetch.mockResolvedValue({ + fileNumber: '1', + } as InquiryDto); + + await service.loadInquiry('1'); + const res = await firstValueFrom(service.$inquiry); + + expect(mockInquiryService.fetch).toHaveBeenCalledTimes(1); + expect(res).toBeDefined(); + expect(res!.fileNumber).toEqual('1'); + }); + + it('should publish the updated application for update', async () => { + mockInquiryService.update.mockResolvedValue({ + fileNumber: '1', + } as InquiryDto); + + await service.update('1', {}); + const res = await firstValueFrom(service.$inquiry); + + expect(mockInquiryService.update).toHaveBeenCalledTimes(1); + expect(res).toBeDefined(); + expect(res!.fileNumber).toEqual('1'); + }); +}); diff --git a/alcs-frontend/src/app/services/inquiry/inquiry-detail.service.ts b/alcs-frontend/src/app/services/inquiry/inquiry-detail.service.ts new file mode 100644 index 0000000000..1a907c5663 --- /dev/null +++ b/alcs-frontend/src/app/services/inquiry/inquiry-detail.service.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; +import { InquiryDto, UpdateInquiryDto } from './inquiry.dto'; +import { InquiryService } from './inquiry.service'; + +@Injectable() +export class InquiryDetailService { + $inquiry = new BehaviorSubject<InquiryDto | undefined>(undefined); + + constructor(private inquiryService: InquiryService) {} + + async loadInquiry(fileNumber: string) { + this.clearReview(); + + const planningReview = await this.inquiryService.fetch(fileNumber); + this.$inquiry.next(planningReview); + } + + async clearReview() { + this.$inquiry.next(undefined); + } + + async update(fileNumber: string, updateDto: UpdateInquiryDto) { + const updatedApp = await this.inquiryService.update(fileNumber, updateDto); + if (updatedApp) { + this.$inquiry.next(updatedApp); + } + return updatedApp; + } +} diff --git a/alcs-frontend/src/app/services/inquiry/inquiry.dto.ts b/alcs-frontend/src/app/services/inquiry/inquiry.dto.ts index 22cf737e3f..a98a5d547f 100644 --- a/alcs-frontend/src/app/services/inquiry/inquiry.dto.ts +++ b/alcs-frontend/src/app/services/inquiry/inquiry.dto.ts @@ -47,24 +47,10 @@ export interface InquiryDto { } export interface UpdateInquiryDto { - uuid: string; - summary: string; - dateSubmittedToAlc: number; - typeCode: string; - inquirerFirstName?: string; - inquirerLastName?: string; - inquirerOrganization?: string; - inquirerPhone?: string; - inquirerEmail?: string; - parcels?: InquiryParcelCreateDto[]; -} - -export interface CreateInquiryServiceDto { - summary: string; - dateSubmittedToAlc: Date; - localGovernmentUuid: string; - typeCode: string; - regionCode: string; + summary?: string; + open?: boolean; + dateSubmittedToAlc?: number; + typeCode?: string; inquirerFirstName?: string; inquirerLastName?: string; inquirerOrganization?: string; diff --git a/alcs-frontend/src/app/services/inquiry/inquiry.service.ts b/alcs-frontend/src/app/services/inquiry/inquiry.service.ts index 5c6ace217d..eeb37e2925 100644 --- a/alcs-frontend/src/app/services/inquiry/inquiry.service.ts +++ b/alcs-frontend/src/app/services/inquiry/inquiry.service.ts @@ -48,19 +48,19 @@ export class InquiryService { return; } - // async fetchDetailedByFileNumber(fileNumber: string) { - // try { - // return await firstValueFrom(this.http.get<PlanningReviewDetailedDto>(`${this.url}/${fileNumber}`)); - // } catch (err) { - // console.error(err); - // this.toastService.showErrorToast('Failed to fetch inquiry'); - // } - // return; - // } - async update(fileNumber: string, updateDto: UpdateInquiryDto) { try { - return await firstValueFrom(this.http.post<UpdateInquiryDto>(`${this.url}/${fileNumber}`, updateDto)); + return await firstValueFrom(this.http.patch<InquiryDto>(`${this.url}/${fileNumber}`, updateDto)); + } catch (err) { + console.error(err); + this.toastService.showErrorToast('Failed to update inquiry'); + } + return; + } + + async fetch(fileNumber: string) { + try { + return await firstValueFrom(this.http.get<InquiryDto>(`${this.url}/${fileNumber}`)); } catch (err) { console.error(err); this.toastService.showErrorToast('Failed to update inquiry'); diff --git a/alcs-frontend/src/app/shared/inline-editors/inline-text/inline-text.component.html b/alcs-frontend/src/app/shared/inline-editors/inline-text/inline-text.component.html index f1997963cb..64e6cbaae3 100644 --- a/alcs-frontend/src/app/shared/inline-editors/inline-text/inline-text.component.html +++ b/alcs-frontend/src/app/shared/inline-editors/inline-text/inline-text.component.html @@ -1,8 +1,11 @@ <div class="inline-number-wrapper"> <span class="left" *ngIf="!isEditing"> <a (click)="startEdit()" class="add" *ngIf="!value"> Add text </a> - <span *ngIf="value"> + <span *ngIf="value && !mask"> {{ value }} + </span> + <span *ngIf="value && mask"> + {{ value | mask: mask }} </span> <button *ngIf="value" class="edit-button" mat-icon-button (click)="startEdit()"> <mat-icon class="edit-icon">edit</mat-icon> @@ -26,6 +29,7 @@ [(ngModel)]="pendingValue" (keydown.enter)="confirmEdit()" (keydown.escape)="cancelEdit()" + [mask]="mask" /> </mat-form-field> </form> diff --git a/alcs-frontend/src/app/shared/inline-editors/inline-text/inline-text.component.ts b/alcs-frontend/src/app/shared/inline-editors/inline-text/inline-text.component.ts index 28c1d39a2c..1cb6fef6ee 100644 --- a/alcs-frontend/src/app/shared/inline-editors/inline-text/inline-text.component.ts +++ b/alcs-frontend/src/app/shared/inline-editors/inline-text/inline-text.component.ts @@ -20,6 +20,7 @@ export class InlineTextComponent implements AfterContentChecked { @Input() placeholder: string = 'Enter a value'; @Input() required = false; @Output() save = new EventEmitter<string | null>(); + @Input() mask?: string | undefined; @ViewChild('editInput') textInput!: ElementRef; diff --git a/alcs-frontend/src/app/shared/staff-journal/staff-journal.component.ts b/alcs-frontend/src/app/shared/staff-journal/staff-journal.component.ts index 3187274382..8ed84e4d1a 100644 --- a/alcs-frontend/src/app/shared/staff-journal/staff-journal.component.ts +++ b/alcs-frontend/src/app/shared/staff-journal/staff-journal.component.ts @@ -14,7 +14,7 @@ import { ConfirmationDialogService } from '../confirmation-dialog/confirmation-d }) export class StaffJournalComponent implements OnChanges { @Input() parentUuid: string = ''; - @Input() parentType: 'Application' | 'NOI' | 'Notification' | 'Planning Review' = 'Application'; + @Input() parentType: 'Application' | 'NOI' | 'Notification' | 'Planning Review' | 'Inquiry' = 'Application'; labelText = 'Add a journal note'; @@ -58,26 +58,39 @@ export class StaffJournalComponent implements OnChanges { async onSave(note: string) { this.isSaving = true; - if (this.parentType === 'Application') { - await this.staffJournalService.createNoteForApplication({ - applicationUuid: this.parentUuid, - body: note, - }); - } else if (this.parentType === 'Notification') { - await this.staffJournalService.createNoteForNotification({ - notificationUuid: this.parentUuid, - body: note, - }); - } else if (this.parentType === 'Planning Review') { - await this.staffJournalService.createNoteForPlanningReview({ - planningReviewUuid: this.parentUuid, - body: note, - }); - } else { - await this.staffJournalService.createNoteForNoticeOfIntent({ - noticeOfIntentUuid: this.parentUuid, - body: note, - }); + switch (this.parentType) { + case 'Application': + await this.staffJournalService.createNoteForApplication({ + applicationUuid: this.parentUuid, + body: note, + }); + break; + case 'NOI': + await this.staffJournalService.createNoteForNoticeOfIntent({ + noticeOfIntentUuid: this.parentUuid, + body: note, + }); + break; + case 'Notification': + await this.staffJournalService.createNoteForNotification({ + notificationUuid: this.parentUuid, + body: note, + }); + break; + case 'Planning Review': + await this.staffJournalService.createNoteForPlanningReview({ + planningReviewUuid: this.parentUuid, + body: note, + }); + break; + case 'Inquiry': + await this.staffJournalService.createNoteForInquiry({ + inquiryUuid: this.parentUuid, + body: note, + }); + break; + default: + console.error(`${this.parentType} has not been setup for staff journal`); } this.isSaving = false; diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.controller.spec.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.controller.spec.ts index 64dc1cf36e..6775fe9501 100644 --- a/services/apps/alcs/src/alcs/inquiry/inquiry.controller.spec.ts +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.controller.spec.ts @@ -2,6 +2,8 @@ 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 { InquiryProfile } from '../../common/automapper/inquiry.automapper.profile'; import { Board } from '../board/board.entity'; import { BoardService } from '../board/board.service'; @@ -36,6 +38,11 @@ describe('InquiryController', () => { provide: BoardService, useValue: mockBoardService, }, + { + provide: ClsService, + useValue: {}, + }, + ...mockKeyCloakProviders, ], }).compile(); diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.controller.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.controller.ts index 7f89faa17e..d31ca1083e 100644 --- a/services/apps/alcs/src/alcs/inquiry/inquiry.controller.ts +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.controller.ts @@ -1,16 +1,35 @@ -import { Body, Controller, Get, Param, Post } from '@nestjs/common'; +import { + Body, + Controller, + Get, + Param, + Patch, + Post, + Req, + UseGuards, +} from '@nestjs/common'; import { Mapper } from 'automapper-core'; import { InjectMapper } from 'automapper-nestjs'; -import { ROLES_ALLOWED_BOARDS } from '../../common/authorization/roles'; +import { + ANY_AUTH_ROLE, + ROLES_ALLOWED_BOARDS, +} from '../../common/authorization/roles'; +import { RolesGuard } from '../../common/authorization/roles-guard.service'; import { UserRoles } from '../../common/authorization/roles.decorator'; import { formatIncomingDate } from '../../utils/incoming-date.formatter'; import { BoardService } from '../board/board.service'; import { InquiryType } from './inquiry-type.entity'; -import { CreateInquiryDto, InquiryDto, InquiryTypeDto } from './inquiry.dto'; +import { + CreateInquiryDto, + InquiryDto, + InquiryTypeDto, + UpdateInquiryDto, +} from './inquiry.dto'; import { Inquiry } from './inquiry.entity'; import { InquiryService } from './inquiry.service'; @Controller('inquiry') +@UseGuards(RolesGuard) export class InquiryController { constructor( private inquiryService: InquiryService, @@ -19,12 +38,14 @@ export class InquiryController { ) {} @Get('types') + @UserRoles(...ANY_AUTH_ROLE) async getTypes() { const types = await this.inquiryService.listTypes(); return this.mapper.mapArray(types, InquiryType, InquiryTypeDto); } @Post() + @UserRoles(...ANY_AUTH_ROLE) async create(@Body() createDto: CreateInquiryDto) { const targetBoard = await this.boardService.getOneOrFail({ code: createDto.boardCode, @@ -40,6 +61,29 @@ export class InquiryController { return this.mapper.map(createdInquiry, Inquiry, InquiryDto); } + @Patch(':fileNumber') + @UserRoles(...ANY_AUTH_ROLE) + async update( + @Param('fileNumber') fileNumber: string, + @Body() updateDto: UpdateInquiryDto, + @Req() req, + ) { + const updatedInquiry = await this.inquiryService.update( + fileNumber, + updateDto, + req.user.entity, + ); + return this.mapper.map(updatedInquiry, Inquiry, InquiryDto); + } + + @Get(':fileNumber') + @UserRoles(...ROLES_ALLOWED_BOARDS) + async get(@Param('fileNumber') fileNumber: string) { + const notification = await this.inquiryService.getByFileNumber(fileNumber); + const mapped = await this.inquiryService.mapToDtos([notification]); + return mapped[0]; + } + @Get('/card/:uuid') @UserRoles(...ROLES_ALLOWED_BOARDS) async getByCard(@Param('uuid') cardUuid: string) { diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.dto.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.dto.ts index caeff2f111..97d2f9e8c7 100644 --- a/services/apps/alcs/src/alcs/inquiry/inquiry.dto.ts +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.dto.ts @@ -1,6 +1,12 @@ import { AutoMap } from 'automapper-classes'; import { Type } from 'class-transformer'; -import { IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; +import { + IsBoolean, + 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'; @@ -72,6 +78,9 @@ export class InquiryDto { @AutoMap() uuid: string; + @AutoMap() + open: boolean; + @AutoMap() fileNumber: string; @@ -123,20 +132,20 @@ export class InquiryDto { export class UpdateInquiryDto { @IsString() - @IsNotEmpty() - uuid: string; + @IsOptional() + summary?: string; - @IsString() - @IsNotEmpty() - summary: string; + @IsBoolean() + @IsOptional() + open?: boolean; @IsNumber() - @IsNotEmpty() - dateSubmittedToAlc: number; + @IsOptional() + dateSubmittedToAlc?: number; @IsString() - @IsNotEmpty() - typeCode: string; + @IsOptional() + typeCode?: string; @IsString() @IsOptional() diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.entity.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.entity.ts index 9ea5972941..d7a62c0fd2 100644 --- a/services/apps/alcs/src/alcs/inquiry/inquiry.entity.ts +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.entity.ts @@ -62,7 +62,7 @@ export class Inquiry extends Base { open: boolean; @ManyToOne(() => User) - closedBy: User; + closedBy: User | null; @Column({ type: 'timestamptz', nullable: true }) closedDate: Date | null; diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.service.spec.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.service.spec.ts index a39205c3b7..16e0483fb1 100644 --- a/services/apps/alcs/src/alcs/inquiry/inquiry.service.spec.ts +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.service.spec.ts @@ -1,4 +1,4 @@ -import { DeepMocked, createMock } from '@golevelup/nestjs-testing'; +import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { classes } from 'automapper-classes'; @@ -198,7 +198,6 @@ describe('InquiryService', () => { const fakeFileNumber = 'fake_num'; const payload: UpdateInquiryDto = { - uuid: 'fake', dateSubmittedToAlc: 0, summary: 'fake_s', typeCode: 'fake_type', diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.service.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.service.ts index a6d03e653d..6f551fa9c7 100644 --- a/services/apps/alcs/src/alcs/inquiry/inquiry.service.ts +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.service.ts @@ -12,6 +12,7 @@ import { Repository, } from 'typeorm'; import { FileNumberService } from '../../file-number/file-number.service'; +import { User } from '../../user/user.entity'; import { formatIncomingDate } from '../../utils/incoming-date.formatter'; import { filterUndefined } from '../../utils/undefined'; import { Board } from '../board/board.entity'; @@ -199,16 +200,17 @@ export class InquiryService { }); } - async update(fileNumber: string, updateDto: UpdateInquiryDto) { + async update(fileNumber: string, updateDto: UpdateInquiryDto, user?: User) { const inquiry = await this.getByFileNumber(fileNumber); inquiry.summary = filterUndefined(updateDto.summary, inquiry.summary); if (updateDto.typeCode) { - this.typeRepository.findOneByOrFail({ + const newType = await this.typeRepository.findOneByOrFail({ code: updateDto.typeCode, }); inquiry.typeCode = filterUndefined(updateDto.typeCode, inquiry.typeCode); + inquiry.type = newType; } inquiry.dateSubmittedToAlc = filterUndefined( @@ -241,16 +243,17 @@ export class InquiryService { inquiry.inquirerEmail, ); - // TODO complete open and closed - // inquiry.open = filterUndefined(updateDto.open, inquiry.open); + if (updateDto.open === false && inquiry.open && user) { + inquiry.open = false; + inquiry.closedDate = new Date(); + inquiry.closedBy = user; + } - // if (updateDto.closedDate) { - // inquiry.closedDate = filterUndefined( - // formatIncomingDate(updateDto.closedDate), - // inquiry.closedDate, - // ); - // inquiry.open = false; - // } + if (updateDto.open === true && !inquiry.open) { + inquiry.open = true; + inquiry.closedDate = null; + inquiry.closedBy = null; + } await this.repository.save(inquiry); diff --git a/services/apps/alcs/src/alcs/staff-journal/staff-journal.controller.ts b/services/apps/alcs/src/alcs/staff-journal/staff-journal.controller.ts index 6d3c7fcb52..c7d256e776 100644 --- a/services/apps/alcs/src/alcs/staff-journal/staff-journal.controller.ts +++ b/services/apps/alcs/src/alcs/staff-journal/staff-journal.controller.ts @@ -25,6 +25,7 @@ import { CreateNoticeOfIntentStaffJournalDto, CreateNotificationStaffJournalDto, CreatePlanningReviewStaffJournalDto, + CreateInquiryStaffJournalDto, } from './staff-journal.dto'; import { StaffJournal } from './staff-journal.entity'; import { StaffJournalService } from './staff-journal.service'; @@ -108,6 +109,21 @@ export class StaffJournalController { return this.autoMapper.map(newRecord, StaffJournal, StaffJournalDto); } + @Post('/inquiry') + @UserRoles(...ROLES_ALLOWED_BOARDS) + async createForInquiry( + @Body() record: CreateInquiryStaffJournalDto, + @Req() req, + ): Promise<StaffJournalDto> { + const newRecord = await this.staffJournalService.createForInquiry( + record.inquiryUuid, + record.body, + req.user.entity, + ); + + return this.autoMapper.map(newRecord, StaffJournal, StaffJournalDto); + } + @Patch() @UserRoles(...ROLES_ALLOWED_BOARDS) async update( diff --git a/services/apps/alcs/src/alcs/staff-journal/staff-journal.dto.ts b/services/apps/alcs/src/alcs/staff-journal/staff-journal.dto.ts index 4e15dcd8af..ba1f166345 100644 --- a/services/apps/alcs/src/alcs/staff-journal/staff-journal.dto.ts +++ b/services/apps/alcs/src/alcs/staff-journal/staff-journal.dto.ts @@ -1,5 +1,5 @@ import { AutoMap } from 'automapper-classes'; -import { IsNotEmpty, IsString } from 'class-validator'; +import { IsNotEmpty, IsString, IsUUID } from 'class-validator'; export class StaffJournalDto { @AutoMap() @@ -27,29 +27,35 @@ class BaseCreateStaffJournalDto { } export class CreateApplicationStaffJournalDto extends BaseCreateStaffJournalDto { - @IsString() + @IsUUID() @IsNotEmpty() applicationUuid: string; } export class CreateNoticeOfIntentStaffJournalDto extends BaseCreateStaffJournalDto { - @IsString() + @IsUUID() @IsNotEmpty() noticeOfIntentUuid: string; } export class CreateNotificationStaffJournalDto extends BaseCreateStaffJournalDto { - @IsString() + @IsUUID() @IsNotEmpty() notificationUuid: string; } export class CreatePlanningReviewStaffJournalDto extends BaseCreateStaffJournalDto { - @IsString() + @IsUUID() @IsNotEmpty() planningReviewUuid: string; } +export class CreateInquiryStaffJournalDto extends BaseCreateStaffJournalDto { + @IsUUID() + @IsNotEmpty() + inquiryUuid: string; +} + export class UpdateStaffJournalDto extends BaseCreateStaffJournalDto { @IsString() @IsNotEmpty() diff --git a/services/apps/alcs/src/alcs/staff-journal/staff-journal.service.ts b/services/apps/alcs/src/alcs/staff-journal/staff-journal.service.ts index a3ab5c8c59..e3c78bc997 100644 --- a/services/apps/alcs/src/alcs/staff-journal/staff-journal.service.ts +++ b/services/apps/alcs/src/alcs/staff-journal/staff-journal.service.ts @@ -35,6 +35,9 @@ export class StaffJournalService { { planningReviewUuid: parentUuid, }, + { + inquiryUuid: parentUuid, + }, ], relations: this.DEFAULT_STAFF_JOURNAL_RELATIONS, order: { @@ -108,6 +111,16 @@ export class StaffJournalService { return await this.staffJournalRepository.save(record); } + async createForInquiry(inquiryUuid: string, noteBody: string, author: User) { + const record = new StaffJournal({ + body: noteBody, + inquiryUuid, + author, + }); + + return await this.staffJournalRepository.save(record); + } + async delete(uuid: string): Promise<void> { const note = await this.staffJournalRepository.findOne({ where: { uuid }, From 5b5f49f5b9f9fb37fd4e3d1375c6d95658ead872 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Wed, 27 Mar 2024 10:51:59 -0700 Subject: [PATCH 058/153] Code Review Feedback --- .../src/app/features/inquiry/inquiry.component.ts | 8 ++++---- .../app/features/inquiry/overview/overview.component.ts | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/alcs-frontend/src/app/features/inquiry/inquiry.component.ts b/alcs-frontend/src/app/features/inquiry/inquiry.component.ts index dda6d43306..e4b0dd3df5 100644 --- a/alcs-frontend/src/app/features/inquiry/inquiry.component.ts +++ b/alcs-frontend/src/app/features/inquiry/inquiry.component.ts @@ -22,7 +22,7 @@ export const childRoutes = [ ]; @Component({ - selector: 'app-planning-review', + selector: 'app-inquiry', templateUrl: './inquiry.component.html', styleUrls: ['./inquiry.component.scss'], }) @@ -34,7 +34,7 @@ export class InquiryComponent implements OnInit, OnDestroy { childRoutes = childRoutes; constructor( - private planningReviewService: InquiryDetailService, + private inquiryDetailService: InquiryDetailService, private route: ActivatedRoute, ) {} @@ -45,14 +45,14 @@ export class InquiryComponent implements OnInit, OnDestroy { await this.loadReview(); }); - this.planningReviewService.$inquiry.pipe(takeUntil(this.$destroy)).subscribe((inquiry) => { + this.inquiryDetailService.$inquiry.pipe(takeUntil(this.$destroy)).subscribe((inquiry) => { this.inquiry = inquiry; }); } private async loadReview() { if (this.fileNumber) { - await this.planningReviewService.loadInquiry(this.fileNumber); + await this.inquiryDetailService.loadInquiry(this.fileNumber); } } diff --git a/alcs-frontend/src/app/features/inquiry/overview/overview.component.ts b/alcs-frontend/src/app/features/inquiry/overview/overview.component.ts index 9eb0aaec13..52e738475e 100644 --- a/alcs-frontend/src/app/features/inquiry/overview/overview.component.ts +++ b/alcs-frontend/src/app/features/inquiry/overview/overview.component.ts @@ -2,7 +2,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { Subject, takeUntil } from 'rxjs'; import { InquiryDetailService } from '../../../services/inquiry/inquiry-detail.service'; import { InquiryDto } from '../../../services/inquiry/inquiry.dto'; -import { TimelineEventDto } from '../../../services/planning-review/planning-review-timeline/planning-review-timeline.dto'; @Component({ selector: 'app-overview', From 7471343d5afc28b9fbb9a8ce9395f3ab7ad6fe72 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Wed, 27 Mar 2024 13:28:50 -0700 Subject: [PATCH 059/153] Add Parcel Tab to Inquiries --- .../app/features/inquiry/inquiry.component.ts | 7 ++ .../app/features/inquiry/inquiry.module.ts | 3 +- .../inquiry/parcel/parcels.component.html | 76 +++++++++++++ .../inquiry/parcel/parcels.component.scss | 32 ++++++ .../inquiry/parcel/parcels.component.spec.ts | 46 ++++++++ .../inquiry/parcel/parcels.component.ts | 104 ++++++++++++++++++ .../inquiry-parcel/inquiry-parcel.dto.ts | 2 +- .../src/app/services/inquiry/inquiry.dto.ts | 4 +- .../inquiry-parcel.controller.spec.ts | 18 --- .../inquiry-parcel.controller.ts | 6 - .../inquiry-parcel/inquiry-parcel.dto.ts | 3 +- .../inquiry-parcel/inquiry-parcel.entity.ts | 6 +- .../inquiry-parcel.service.spec.ts | 18 --- .../inquiry-parcel/inquiry-parcel.service.ts | 6 - .../apps/alcs/src/alcs/inquiry/inquiry.dto.ts | 14 ++- .../alcs/src/alcs/inquiry/inquiry.module.ts | 15 +-- .../alcs/src/alcs/inquiry/inquiry.service.ts | 13 +++ .../automapper/inquiry.automapper.profile.ts | 3 + 18 files changed, 304 insertions(+), 72 deletions(-) create mode 100644 alcs-frontend/src/app/features/inquiry/parcel/parcels.component.html create mode 100644 alcs-frontend/src/app/features/inquiry/parcel/parcels.component.scss create mode 100644 alcs-frontend/src/app/features/inquiry/parcel/parcels.component.spec.ts create mode 100644 alcs-frontend/src/app/features/inquiry/parcel/parcels.component.ts delete mode 100644 services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.controller.spec.ts delete mode 100644 services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.controller.ts delete mode 100644 services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.service.spec.ts delete mode 100644 services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.service.ts diff --git a/alcs-frontend/src/app/features/inquiry/inquiry.component.ts b/alcs-frontend/src/app/features/inquiry/inquiry.component.ts index e4b0dd3df5..8a9c37cc50 100644 --- a/alcs-frontend/src/app/features/inquiry/inquiry.component.ts +++ b/alcs-frontend/src/app/features/inquiry/inquiry.component.ts @@ -5,6 +5,7 @@ import { InquiryDetailService } from '../../services/inquiry/inquiry-detail.serv import { InquiryDto } from '../../services/inquiry/inquiry.dto'; import { DetailsComponent } from './detail/details.component'; import { OverviewComponent } from './overview/overview.component'; +import { ParcelsComponent } from './parcel/parcels.component'; export const childRoutes = [ { @@ -19,6 +20,12 @@ export const childRoutes = [ icon: 'person', component: DetailsComponent, }, + { + path: 'parcels', + menuTitle: 'Parcels', + icon: 'crop_free', + component: ParcelsComponent, + }, ]; @Component({ diff --git a/alcs-frontend/src/app/features/inquiry/inquiry.module.ts b/alcs-frontend/src/app/features/inquiry/inquiry.module.ts index 34e0f6c779..7f856e5ba4 100644 --- a/alcs-frontend/src/app/features/inquiry/inquiry.module.ts +++ b/alcs-frontend/src/app/features/inquiry/inquiry.module.ts @@ -8,6 +8,7 @@ import { DetailsComponent } from './detail/details.component'; import { HeaderComponent } from './header/header.component'; import { childRoutes, InquiryComponent } from './inquiry.component'; import { OverviewComponent } from './overview/overview.component'; +import { ParcelsComponent } from './parcel/parcels.component'; const routes: Routes = [ { @@ -19,7 +20,7 @@ const routes: Routes = [ @NgModule({ providers: [InquiryDetailService], - declarations: [InquiryComponent, OverviewComponent, HeaderComponent, DetailsComponent], + declarations: [InquiryComponent, OverviewComponent, HeaderComponent, DetailsComponent, ParcelsComponent], imports: [CommonModule, SharedModule, RouterModule.forChild(routes), CdkDropList, CdkDrag], exports: [HeaderComponent], }) diff --git a/alcs-frontend/src/app/features/inquiry/parcel/parcels.component.html b/alcs-frontend/src/app/features/inquiry/parcel/parcels.component.html new file mode 100644 index 0000000000..0b67e196d3 --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/parcel/parcels.component.html @@ -0,0 +1,76 @@ +<div class="split"> + <h3>Parcels</h3> + <button *ngIf="!isEditing" color="primary" mat-flat-button (click)="onAddParcel()">+Add Parcel</button> +</div> +<section> + <table mat-table [dataSource]="tableSource"> + <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> + <tr mat-row *matRowDef="let row; columns: displayedColumns"></tr> + + <ng-container matColumnDef="index"> + <th mat-header-cell *matHeaderCellDef>#</th> + <td mat-cell *matCellDef="let row; let i = index"> + {{ i + 1 }} + </td> + </ng-container> + + <ng-container matColumnDef="address"> + <th mat-header-cell *matHeaderCellDef>Civic Address<span *ngIf="isEditing">*</span></th> + <td mat-cell [matTooltip]="row.civicAddress" [matTooltipDisabled]="isEditing" *matCellDef="let row; let i = index"> + <span *ngIf="!isEditing">{{ row.civicAddress }}</span> + <mat-form-field *ngIf="isEditing" appearance="outline"> + <input required [ngModelOptions]="{ standalone: true }" matInput [(ngModel)]="parcels[i].civicAddress" /> + </mat-form-field> + </td> + </ng-container> + + <ng-container matColumnDef="pid"> + <th mat-header-cell *matHeaderCellDef>PID</th> + <td mat-cell *matCellDef="let row; let i = index"> + <span *ngIf="!isEditing">{{ row.pid | mask: '000-000-000' }}</span> + <mat-form-field *ngIf="isEditing" appearance="outline"> + <input mask="000-000-000" [ngModelOptions]="{ standalone: true }" matInput [(ngModel)]="parcels[i].pid" /> + </mat-form-field> + </td> + </ng-container> + + <ng-container matColumnDef="pin"> + <th mat-header-cell *matHeaderCellDef>PIN</th> + <td mat-cell *matCellDef="let row; let i = index"> + <span *ngIf="!isEditing">{{ row.pin }}</span> + <mat-form-field *ngIf="isEditing" appearance="outline"> + <input [ngModelOptions]="{ standalone: true }" matInput [(ngModel)]="parcels[i].pin" /> + </mat-form-field> + </td> + </ng-container> + + <ng-container matColumnDef="actions"> + <th mat-header-cell *matHeaderCellDef>Action</th> + <td mat-cell *matCellDef="let row; let i = index"> + <button *ngIf="!isEditing" type="button" class="edit-btn" mat-flat-button (click)="onEditParcel()"> + <mat-icon color="warn">edit</mat-icon> + </button> + <button type="button" class="edit-btn" mat-flat-button (click)="onRemoveParcel(i)"> + <mat-icon color="warn">delete</mat-icon> + </button> + </td> + </ng-container> + + <tr class="mat-row no-data" *matNoDataRow> + <td class="text-center" colspan="5">No Parcels</td> + </tr> + </table> + <button + style="margin-top: 36px !important" + *ngIf="isEditing" + color="primary" + mat-stroked-button + (click)="onAddParcel()" + > + + Add Another Parcel + </button> + <div *ngIf="isEditing" class="right"> + <button mat-stroked-button color="primary" (click)="onCancelEdit()">Cancel</button> + <button mat-flat-button color="primary" [disabled]="!areParcelsValid()" (click)="onSave()">Save</button> + </div> +</section> diff --git a/alcs-frontend/src/app/features/inquiry/parcel/parcels.component.scss b/alcs-frontend/src/app/features/inquiry/parcel/parcels.component.scss new file mode 100644 index 0000000000..4a4f2a7fa0 --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/parcel/parcels.component.scss @@ -0,0 +1,32 @@ +h5 { + margin: 16px 0 !important; +} + +section { + margin: 32px 0 48px; +} + +.two-item-row { + display: grid; + grid-template-columns: 1fr 1fr; + grid-column-gap: 24px; + grid-row-gap: 24px; + margin-bottom: 24px; +} + +.full-width { + grid-column: 1 /3; + width: 100%; +} + +.mat-mdc-table { + border-collapse: separate !important; + border-spacing: 0 8px !important; + + .mat-mdc-cell { + text-wrap: nowrap; + text-overflow: ellipsis; + overflow: hidden; + max-width: calc(100vw - 840px); + } +} diff --git a/alcs-frontend/src/app/features/inquiry/parcel/parcels.component.spec.ts b/alcs-frontend/src/app/features/inquiry/parcel/parcels.component.spec.ts new file mode 100644 index 0000000000..fc60615ad9 --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/parcel/parcels.component.spec.ts @@ -0,0 +1,46 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { BehaviorSubject } from 'rxjs'; +import { InquiryDetailService } from '../../../services/inquiry/inquiry-detail.service'; +import { InquiryDto } from '../../../services/inquiry/inquiry.dto'; +import { ConfirmationDialogService } from '../../../shared/confirmation-dialog/confirmation-dialog.service'; + +import { ParcelsComponent } from './parcels.component'; + +describe('ParcelsComponent', () => { + let component: ParcelsComponent; + let fixture: ComponentFixture<ParcelsComponent>; + + let inquiryDetailService: DeepMocked<InquiryDetailService>; + let confirmationDialogService: DeepMocked<ConfirmationDialogService>; + + beforeEach(async () => { + inquiryDetailService = createMock(); + confirmationDialogService = createMock(); + inquiryDetailService.$inquiry = new BehaviorSubject<InquiryDto | undefined>(undefined); + + await TestBed.configureTestingModule({ + providers: [ + { + provide: InquiryDetailService, + useValue: inquiryDetailService, + }, + { + provide: ConfirmationDialogService, + useValue: confirmationDialogService, + }, + ], + declarations: [ParcelsComponent], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(ParcelsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alcs-frontend/src/app/features/inquiry/parcel/parcels.component.ts b/alcs-frontend/src/app/features/inquiry/parcel/parcels.component.ts new file mode 100644 index 0000000000..90e34c547a --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/parcel/parcels.component.ts @@ -0,0 +1,104 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { MatTableDataSource } from '@angular/material/table'; +import { Subject, takeUntil } from 'rxjs'; +import { InquiryDetailService } from '../../../services/inquiry/inquiry-detail.service'; +import { InquiryParcelUpdateDto } from '../../../services/inquiry/inquiry-parcel/inquiry-parcel.dto'; +import { InquiryDto } from '../../../services/inquiry/inquiry.dto'; +import { ConfirmationDialogService } from '../../../shared/confirmation-dialog/confirmation-dialog.service'; + +@Component({ + selector: 'app-parcel', + templateUrl: './parcels.component.html', + styleUrls: ['./parcels.component.scss'], +}) +export class ParcelsComponent implements OnInit, OnDestroy { + $destroy = new Subject<void>(); + inquiry?: InquiryDto; + isEditing = false; + + displayedColumns = ['index', 'address', 'pid', 'pin', 'actions']; + parcels: InquiryParcelUpdateDto[] = []; + tableSource: MatTableDataSource<InquiryParcelUpdateDto> = new MatTableDataSource(); + + constructor( + private inquiryDetailService: InquiryDetailService, + private confirmationDialogService: ConfirmationDialogService, + ) {} + + ngOnInit(): void { + this.inquiryDetailService.$inquiry.pipe(takeUntil(this.$destroy)).subscribe((inquiry) => { + this.inquiry = inquiry; + if (inquiry && inquiry.parcels) { + this.parcels = inquiry.parcels; + this.tableSource = new MatTableDataSource<InquiryParcelUpdateDto>(inquiry.parcels); + } + }); + } + + ngOnDestroy(): void { + this.$destroy.next(); + this.$destroy.complete(); + } + + onRemoveParcel(index: number) { + if (this.isEditing) { + this.onRemoveEditParcel(index); + } else { + this.onRemoveConfirmParcel(index); + } + } + + onRemoveConfirmParcel(index: number) { + this.confirmationDialogService + .openDialog({ + body: 'Are you sure you want to delete this parcel?', + }) + .subscribe((onConfirm) => { + if (onConfirm) { + this.parcels.splice(index, 1); + this.onSave(); + } + }); + } + + onRemoveEditParcel(index: number) { + this.parcels.splice(index, 1); + this.tableSource = new MatTableDataSource(this.parcels); + } + + onAddParcel() { + this.isEditing = true; + this.parcels.push({ + civicAddress: '', + }); + this.tableSource = new MatTableDataSource(this.parcels); + } + + async onSave() { + if (this.inquiry) { + await this.inquiryDetailService.update(this.inquiry.fileNumber, { + parcels: this.parcels, + }); + this.isEditing = false; + } + } + + areParcelsValid() { + return this.parcels.reduce((previousValue, parcel) => { + const addressValid = !!parcel.civicAddress && parcel.civicAddress.length > 0; + const pidValid = parcel.pid ? parcel.pid.length === 9 : true; + return previousValue && pidValid && addressValid; + }, true); + } + + onEditParcel() { + this.isEditing = true; + } + + async onCancelEdit() { + if (this.inquiry) { + await this.inquiryDetailService.loadInquiry(this.inquiry.fileNumber); + this.isEditing = false; + } + } +} diff --git a/alcs-frontend/src/app/services/inquiry/inquiry-parcel/inquiry-parcel.dto.ts b/alcs-frontend/src/app/services/inquiry/inquiry-parcel/inquiry-parcel.dto.ts index 455528c5a9..d9acb3b2f1 100644 --- a/alcs-frontend/src/app/services/inquiry/inquiry-parcel/inquiry-parcel.dto.ts +++ b/alcs-frontend/src/app/services/inquiry/inquiry-parcel/inquiry-parcel.dto.ts @@ -13,7 +13,7 @@ export interface InquiryParcelCreateDto { } export interface InquiryParcelUpdateDto { - uuid: string; + uuid?: string; pid?: string | null; pin?: string | null; civicAddress: string; diff --git a/alcs-frontend/src/app/services/inquiry/inquiry.dto.ts b/alcs-frontend/src/app/services/inquiry/inquiry.dto.ts index a98a5d547f..4a34a6dd56 100644 --- a/alcs-frontend/src/app/services/inquiry/inquiry.dto.ts +++ b/alcs-frontend/src/app/services/inquiry/inquiry.dto.ts @@ -2,7 +2,7 @@ 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'; -import { InquiryParcelCreateDto } from './inquiry-parcel/inquiry-parcel.dto'; +import { InquiryParcelCreateDto, InquiryParcelDto } from './inquiry-parcel/inquiry-parcel.dto'; export interface InquiryTypeDto extends BaseCodeDto { shortLabel: string; @@ -39,7 +39,7 @@ export interface InquiryDto { inquirerOrganization?: string; inquirerPhone?: string; inquirerEmail?: string; - parcels?: InquiryParcelCreateDto[]; + parcels?: InquiryParcelDto[]; localGovernment: ApplicationLocalGovernmentDto; region: ApplicationRegionDto; card?: CardDto; diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.controller.spec.ts b/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.controller.spec.ts deleted file mode 100644 index f466738fc7..0000000000 --- a/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.controller.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { InquiryParcelController } from './inquiry-parcel.controller'; - -describe('InquiryParcelController', () => { - let controller: InquiryParcelController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [InquiryParcelController], - }).compile(); - - controller = module.get<InquiryParcelController>(InquiryParcelController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.controller.ts b/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.controller.ts deleted file mode 100644 index 878e3cd553..0000000000 --- a/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.controller.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Controller } from '@nestjs/common'; - -@Controller('inquiry-parcel') -export class InquiryParcelController { - // TODO will be implemented in other ticket -} diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.dto.ts b/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.dto.ts index b66de650f2..1880ed0b8b 100644 --- a/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.dto.ts +++ b/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.dto.ts @@ -33,8 +33,9 @@ export class InquiryParcelCreateDto { } export class InquiryParcelUpdateDto { + @IsOptional() @IsString() - uuid: string; + uuid?: string; @IsString() @IsOptional() diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.entity.ts b/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.entity.ts index 66d117fe9a..103c04c5f3 100644 --- a/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.entity.ts +++ b/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.entity.ts @@ -37,7 +37,11 @@ export class InquiryParcel extends Base { civicAddress: string; @AutoMap() - @ManyToOne(() => Inquiry) + @ManyToOne(() => Inquiry, (inquiry) => inquiry.parcels, { + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + orphanedRowAction: 'delete', + }) inquiry: Inquiry; @AutoMap() diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.service.spec.ts b/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.service.spec.ts deleted file mode 100644 index abe4f4baee..0000000000 --- a/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { InquiryParcelService } from './inquiry-parcel.service'; - -describe('InquiryParcelService', () => { - let service: InquiryParcelService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [InquiryParcelService], - }).compile(); - - service = module.get<InquiryParcelService>(InquiryParcelService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.service.ts b/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.service.ts deleted file mode 100644 index c998cc3d38..0000000000 --- a/services/apps/alcs/src/alcs/inquiry/inquiry-parcel/inquiry-parcel.service.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -@Injectable() -export class InquiryParcelService { - // TODO will be implemented in other ticket -} diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.dto.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.dto.ts index 97d2f9e8c7..a644fde7c2 100644 --- a/services/apps/alcs/src/alcs/inquiry/inquiry.dto.ts +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.dto.ts @@ -11,7 +11,11 @@ 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'; -import { InquiryParcelCreateDto } from './inquiry-parcel/inquiry-parcel.dto'; +import { + InquiryParcelCreateDto, + InquiryParcelDto, + InquiryParcelUpdateDto, +} from './inquiry-parcel/inquiry-parcel.dto'; export class InquiryTypeDto extends BaseCodeDto { @AutoMap() @@ -114,8 +118,8 @@ export class InquiryDto { @AutoMap() inquirerEmail?: string; - @Type(() => InquiryParcelCreateDto) - parcels?: InquiryParcelCreateDto[]; + @Type(() => InquiryParcelDto) + parcels?: InquiryParcelDto[]; @AutoMap(() => LocalGovernmentDto) localGovernment: LocalGovernmentDto; @@ -168,8 +172,8 @@ export class UpdateInquiryDto { inquirerEmail?: string; @IsOptional() - @Type(() => InquiryParcelCreateDto) - parcels?: InquiryParcelCreateDto[]; + @Type(() => InquiryParcelUpdateDto) + parcels?: InquiryParcelUpdateDto[]; } export class CreateInquiryServiceDto { diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.module.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.module.ts index d3cf62ea89..976289586c 100644 --- a/services/apps/alcs/src/alcs/inquiry/inquiry.module.ts +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.module.ts @@ -8,9 +8,7 @@ import { CardModule } from '../card/card.module'; import { InquiryDocumentController } from './inquiry-document/inquiry-document.controller'; import { InquiryDocument } from './inquiry-document/inquiry-document.entity'; import { InquiryDocumentService } from './inquiry-document/inquiry-document.service'; -import { InquiryParcelController } from './inquiry-parcel/inquiry-parcel.controller'; import { InquiryParcel } from './inquiry-parcel/inquiry-parcel.entity'; -import { InquiryParcelService } from './inquiry-parcel/inquiry-parcel.service'; import { InquiryType } from './inquiry-type.entity'; import { InquiryController } from './inquiry.controller'; import { Inquiry } from './inquiry.entity'; @@ -31,17 +29,8 @@ import { DocumentCode } from '../../document/document-code.entity'; FileNumberModule, DocumentModule, ], - providers: [ - InquiryService, - InquiryParcelService, - InquiryDocumentService, - InquiryProfile, - ], - controllers: [ - InquiryController, - InquiryParcelController, - InquiryDocumentController, - ], + providers: [InquiryService, InquiryDocumentService, InquiryProfile], + controllers: [InquiryController, InquiryDocumentController], exports: [InquiryProfile, InquiryService], }) export class InquiryModule {} diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.service.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.service.ts index 6f551fa9c7..395d5e35f0 100644 --- a/services/apps/alcs/src/alcs/inquiry/inquiry.service.ts +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.service.ts @@ -43,6 +43,7 @@ export class InquiryService { localGovernment: true, region: true, type: true, + parcels: true, }; constructor( @@ -255,6 +256,18 @@ export class InquiryService { inquiry.closedBy = null; } + if (updateDto.parcels) { + inquiry.parcels = updateDto.parcels.map( + (parcelDto) => + new InquiryParcel({ + uuid: parcelDto.uuid, + pin: parcelDto.pin, + pid: parcelDto.pid, + civicAddress: parcelDto.civicAddress, + }), + ); + } + await this.repository.save(inquiry); return this.getByFileNumber(inquiry.fileNumber); diff --git a/services/apps/alcs/src/common/automapper/inquiry.automapper.profile.ts b/services/apps/alcs/src/common/automapper/inquiry.automapper.profile.ts index 76996d0a54..a91857f521 100644 --- a/services/apps/alcs/src/common/automapper/inquiry.automapper.profile.ts +++ b/services/apps/alcs/src/common/automapper/inquiry.automapper.profile.ts @@ -3,6 +3,8 @@ import { createMap, forMember, mapFrom, Mapper } from 'automapper-core'; import { AutomapperProfile, InjectMapper } from 'automapper-nestjs'; import { InquiryDocumentDto } from '../../alcs/inquiry/inquiry-document/inquiry-document.dto'; import { InquiryDocument } from '../../alcs/inquiry/inquiry-document/inquiry-document.entity'; +import { InquiryParcelDto } from '../../alcs/inquiry/inquiry-parcel/inquiry-parcel.dto'; +import { InquiryParcel } from '../../alcs/inquiry/inquiry-parcel/inquiry-parcel.entity'; import { InquiryType } from '../../alcs/inquiry/inquiry-type.entity'; import { InquiryDto, InquiryTypeDto } from '../../alcs/inquiry/inquiry.dto'; import { Inquiry } from '../../alcs/inquiry/inquiry.entity'; @@ -18,6 +20,7 @@ export class InquiryProfile extends AutomapperProfile { override get profile() { return (mapper) => { createMap(mapper, InquiryType, InquiryTypeDto); + createMap(mapper, InquiryParcel, InquiryParcelDto); createMap( mapper, From 49caa12a4d8ffda30f20f5f91c21fa5107c476cf Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Wed, 27 Mar 2024 14:51:35 -0700 Subject: [PATCH 060/153] Add Documents Tab to Inquiries --- .../document-upload-dialog.component.html | 100 +++++++++++ .../document-upload-dialog.component.scss | 55 ++++++ .../document-upload-dialog.component.spec.ts | 49 +++++ .../document-upload-dialog.component.ts | 167 ++++++++++++++++++ .../documents/documents.component.html | 54 ++++++ .../documents/documents.component.scss | 44 +++++ .../documents/documents.component.spec.ts | 59 +++++++ .../inquiry/documents/documents.component.ts | 118 +++++++++++++ .../app/features/inquiry/inquiry.component.ts | 7 + .../app/features/inquiry/inquiry.module.ts | 12 +- .../document-upload-dialog.component.html | 29 +-- .../document-upload-dialog.component.ts | 13 -- .../inquiry-document/inquiry-document.dto.ts | 32 ++++ .../inquiry-document.service.spec.ts | 94 ++++++++++ .../inquiry-document.service.ts | 88 +++++++++ .../planning-review-document.dto.ts | 2 - .../planning-review-document.service.ts | 7 - 17 files changed, 880 insertions(+), 50 deletions(-) create mode 100644 alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.html create mode 100644 alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.scss create mode 100644 alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.spec.ts create mode 100644 alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.ts create mode 100644 alcs-frontend/src/app/features/inquiry/documents/documents.component.html create mode 100644 alcs-frontend/src/app/features/inquiry/documents/documents.component.scss create mode 100644 alcs-frontend/src/app/features/inquiry/documents/documents.component.spec.ts create mode 100644 alcs-frontend/src/app/features/inquiry/documents/documents.component.ts create mode 100644 alcs-frontend/src/app/services/inquiry/inquiry-document/inquiry-document.dto.ts create mode 100644 alcs-frontend/src/app/services/inquiry/inquiry-document/inquiry-document.service.spec.ts create mode 100644 alcs-frontend/src/app/services/inquiry/inquiry-document/inquiry-document.service.ts diff --git a/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.html b/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.html new file mode 100644 index 0000000000..329efcbed6 --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.html @@ -0,0 +1,100 @@ +<div mat-dialog-title> + <h4>{{ title }} Document</h4> +</div> +<div mat-dialog-content> + <form class="form" [formGroup]="form"> + <div class="double"> + <div> + <mat-label>Document Upload*</mat-label> + </div> + <input hidden type="file" #fileInput (change)="uploadFile($event)" placeholder="Upload file" /> + <button + *ngIf="!pendingFile && !existingFile" + class="full-width upload-button" + mat-flat-button + color="accent" + [ngClass]="{ + error: showVirusError + }" + (click)="fileInput.click()" + > + Upload + </button> + <div class="file" *ngIf="pendingFile"> + <div> + <a (click)="openFile()">{{ pendingFile.name }}</a> +  ({{ pendingFile.size | filesize }}) + </div> + <button [disabled]="!allowsFileEdit" (click)="onRemoveFile()" mat-button> + <mat-icon>close</mat-icon> + Remove + </button> + </div> + <div class="file" *ngIf="existingFile"> + <div> + <a (click)="openExistingFile()">{{ existingFile.name }}</a> +  ({{ existingFile.size | filesize }}) + </div> + <button [disabled]="!allowsFileEdit" (click)="onRemoveFile()" mat-button> + <mat-icon>close</mat-icon> + Remove + </button> + </div> + <mat-error class="left" style="display: flex" *ngIf="showVirusError"> + <mat-icon>warning</mat-icon> A virus was detected in the file. Choose another file and try again. + </mat-error> + </div> + + <div class="double"> + <mat-form-field class="full-width" appearance="outline"> + <mat-label>Document Name</mat-label> + <input required matInput id="name" [formControl]="name" name="name" /> + </mat-form-field> + </div> + + <div> + <ng-select + appearance="outline" + required + [items]="documentTypes" + placeholder="Document Type*" + bindLabel="label" + bindValue="code" + [ngModelOptions]="{ standalone: true }" + [(ngModel)]="documentTypeAhead" + [searchFn]="filterDocumentTypes" + (change)="onDocTypeSelected($event)" + appendTo="body" + > + </ng-select> + </div> + <div> + <mat-form-field class="full-width" appearance="outline"> + <mat-label>Source</mat-label> + <mat-select [formControl]="source"> + <mat-option *ngFor="let source of documentSources" [value]="source">{{ source }}</mat-option> + </mat-select> + </mat-form-field> + </div> + </form> + + <mat-dialog-actions align="end"> + <div class="button-container"> + <button type="button" mat-stroked-button color="primary" [mat-dialog-close]="false">Close</button> + <button + *ngIf="!isSaving" + type="button" + mat-flat-button + color="primary" + [disabled]="!form.valid || (!pendingFile && !existingFile)" + (click)="onSubmit()" + > + Save + </button> + <button *ngIf="isSaving" type="button" mat-flat-button color="primary" [disabled]="true"> + <mat-spinner class="spinner" diameter="20"></mat-spinner> + Uploading + </button> + </div> + </mat-dialog-actions> +</div> diff --git a/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.scss b/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.scss new file mode 100644 index 0000000000..e4fa72a650 --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.scss @@ -0,0 +1,55 @@ +@use '../../../../../styles/colors'; + +.form { + display: grid; + grid-template-columns: 1fr 1fr; + row-gap: 32px; + column-gap: 32px; + + .double { + grid-column: 1/3; + } +} + +.full-width { + width: 100%; +} + +a { + word-break: break-all; +} + +.file { + border: 1px solid #000; + border-radius: 8px; + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px; +} + +.upload-button { + margin-top: 6px !important; + + &.error { + border: 2px solid colors.$error-color; + } +} + +.spinner { + display: inline-block; + margin-right: 4px; +} + +:host::ng-deep { + .mdc-button__label { + display: flex; + align-items: center; + } +} + +.superseded-warning { + background-color: colors.$secondary-color-dark; + color: #fff; + padding: 0 4px; +} diff --git a/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.spec.ts b/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.spec.ts new file mode 100644 index 0000000000..1571be1108 --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.spec.ts @@ -0,0 +1,49 @@ +import { EventEmitter, NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { InquiryDocumentService } from '../../../../services/inquiry/inquiry-document/inquiry-document.service'; +import { ToastService } from '../../../../services/toast/toast.service'; + +import { DocumentUploadDialogComponent } from './document-upload-dialog.component'; + +describe('DocumentUploadDialogComponent', () => { + let component: DocumentUploadDialogComponent; + let fixture: ComponentFixture<DocumentUploadDialogComponent>; + + let mockAppDocService: DeepMocked<InquiryDocumentService>; + + beforeEach(async () => { + mockAppDocService = createMock(); + + const mockDialogRef = { + close: jest.fn(), + afterClosed: jest.fn(), + subscribe: jest.fn(), + backdropClick: () => new EventEmitter(), + }; + + await TestBed.configureTestingModule({ + declarations: [DocumentUploadDialogComponent], + providers: [ + { + provide: InquiryDocumentService, + useValue: mockAppDocService, + }, + { provide: MatDialogRef, useValue: mockDialogRef }, + { provide: MAT_DIALOG_DATA, useValue: {} }, + { provide: ToastService, useValue: {} }, + ], + imports: [MatDialogModule], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(DocumentUploadDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.ts b/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.ts new file mode 100644 index 0000000000..7c980dd6e9 --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.ts @@ -0,0 +1,167 @@ +import { HttpErrorResponse } from '@angular/common/http'; +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 { Subject } from 'rxjs'; +import { + InquiryDocumentDto, + UpdateDocumentDto, +} from '../../../../services/inquiry/inquiry-document/inquiry-document.dto'; +import { InquiryDocumentService } from '../../../../services/inquiry/inquiry-document/inquiry-document.service'; +import { ToastService } from '../../../../services/toast/toast.service'; +import { + DOCUMENT_SOURCE, + DOCUMENT_SYSTEM, + DOCUMENT_TYPE, + DocumentTypeDto, +} from '../../../../shared/document/document.dto'; + +@Component({ + selector: 'app-document-upload-dialog', + templateUrl: './document-upload-dialog.component.html', + styleUrls: ['./document-upload-dialog.component.scss'], +}) +export class DocumentUploadDialogComponent implements OnInit, OnDestroy { + $destroy = new Subject<void>(); + DOCUMENT_TYPE = DOCUMENT_TYPE; + + title = 'Create'; + isDirty = false; + isSaving = false; + allowsFileEdit = true; + documentTypeAhead: string | undefined = undefined; + + name = new FormControl<string>('', [Validators.required]); + type = new FormControl<string | undefined>(undefined, [Validators.required]); + source = new FormControl<string>('', [Validators.required]); + + documentTypes: DocumentTypeDto[] = []; + documentSources = Object.values(DOCUMENT_SOURCE); + + form = new FormGroup({ + name: this.name, + type: this.type, + source: this.source, + }); + + pendingFile: File | undefined; + existingFile: { name: string; size: number } | undefined; + showVirusError = false; + + constructor( + @Inject(MAT_DIALOG_DATA) + public data: { fileId: string; existingDocument?: InquiryDocumentDto }, + protected dialog: MatDialogRef<any>, + private inquiryDocumentService: InquiryDocumentService, + private toastService: ToastService, + ) {} + + ngOnInit(): void { + this.loadDocumentTypes(); + + if (this.data.existingDocument) { + const document = this.data.existingDocument; + this.title = 'Edit'; + this.allowsFileEdit = document.system === DOCUMENT_SYSTEM.ALCS; + this.form.patchValue({ + name: document.fileName, + type: document.type?.code, + source: document.source, + }); + this.documentTypeAhead = document.type!.code; + this.existingFile = { + name: document.fileName, + size: 0, + }; + } + } + + async onSubmit() { + const file = this.pendingFile; + const dto: UpdateDocumentDto = { + fileName: this.name.value!, + source: this.source.value as DOCUMENT_SOURCE, + typeCode: this.type.value as DOCUMENT_TYPE, + file, + }; + + this.isSaving = true; + if (this.data.existingDocument) { + await this.inquiryDocumentService.update(this.data.existingDocument.uuid, dto); + } else if (file !== undefined) { + try { + await this.inquiryDocumentService.upload(this.data.fileId, { + ...dto, + file, + }); + } catch (err) { + this.toastService.showErrorToast('Document upload failed'); + if (err instanceof HttpErrorResponse && err.status === 403) { + this.showVirusError = true; + this.isSaving = false; + this.pendingFile = undefined; + return; + } + } + this.showVirusError = false; + } + + this.dialog.close(true); + this.isSaving = false; + } + + ngOnDestroy(): void { + this.$destroy.next(); + this.$destroy.complete(); + } + + filterDocumentTypes(term: string, item: DocumentTypeDto) { + const termLower = term.toLocaleLowerCase(); + return ( + item.label.toLocaleLowerCase().indexOf(termLower) > -1 || + item.oatsCode.toLocaleLowerCase().indexOf(termLower) > -1 + ); + } + + async onDocTypeSelected($event?: DocumentTypeDto) { + if ($event) { + this.type.setValue($event.code); + } else { + this.type.setValue(undefined); + } + } + + uploadFile(event: Event) { + const element = event.target as HTMLInputElement; + const selectedFiles = element.files; + if (selectedFiles && selectedFiles[0]) { + this.pendingFile = selectedFiles[0]; + this.name.setValue(selectedFiles[0].name); + this.showVirusError = false; + } + } + + onRemoveFile() { + this.pendingFile = undefined; + this.existingFile = undefined; + } + + openFile() { + if (this.pendingFile) { + const fileURL = URL.createObjectURL(this.pendingFile); + window.open(fileURL, '_blank'); + } + } + + async openExistingFile() { + if (this.data.existingDocument) { + await this.inquiryDocumentService.download(this.data.existingDocument.uuid, this.data.existingDocument.fileName); + } + } + + private async loadDocumentTypes() { + const docTypes = await this.inquiryDocumentService.fetchTypes(); + docTypes.sort((a, b) => (a.label > b.label ? 1 : -1)); + this.documentTypes = docTypes.filter((type) => type.code !== DOCUMENT_TYPE.ORIGINAL_APPLICATION); + } +} diff --git a/alcs-frontend/src/app/features/inquiry/documents/documents.component.html b/alcs-frontend/src/app/features/inquiry/documents/documents.component.html new file mode 100644 index 0000000000..caea7696a0 --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/documents/documents.component.html @@ -0,0 +1,54 @@ +<div class="header"> + <h3>Documents</h3> + <button (click)="onUploadFile()" mat-flat-button color="primary">+ Add Document</button> +</div> +<table mat-table [dataSource]="dataSource" matSort class="mat-elevation-z3 documents"> + <ng-container matColumnDef="type"> + <th mat-header-cell *matHeaderCellDef mat-sort-header sortActionDescription="Sort by type">Type</th> + <td mat-cell *matCellDef="let element" [matTooltip]="element.type.label"> + {{ element.type.oatsCode }} + </td> + </ng-container> + + <ng-container matColumnDef="fileName"> + <th mat-header-cell *matHeaderCellDef mat-sort-header sortActionDescription="Sort by name">Document Name</th> + <td mat-cell *matCellDef="let element"> + <a (click)="openFile(element.uuid, element.fileName)">{{ element.fileName }}</a> + </td> + </ng-container> + + <ng-container matColumnDef="source"> + <th mat-header-cell *matHeaderCellDef mat-sort-header sortActionDescription="Sort by source">Source - System</th> + <td mat-cell *matCellDef="let element">{{ element.source }} - {{ element.system }}</td> + </ng-container> + + <ng-container matColumnDef="uploadedAt"> + <th mat-header-cell *matHeaderCellDef mat-sort-header sortActionDescription="Sort by date">Upload Date</th> + <td mat-cell *matCellDef="let element">{{ element.uploadedAt | date }}</td> + </ng-container> + + <ng-container matColumnDef="actions"> + <th mat-header-cell *matHeaderCellDef>Actions</th> + <td mat-cell *matCellDef="let element"> + <button mat-icon-button (click)="downloadFile(element.uuid, element.fileName)"> + <mat-icon>file_download</mat-icon> + </button> + <button mat-icon-button (click)="onEditFile(element)"> + <mat-icon>edit</mat-icon> + </button> + <button + *ngIf="element.system === DOCUMENT_SYSTEM.ALCS || element.system === DOCUMENT_SYSTEM.OATS" + mat-icon-button + (click)="onDeleteFile(element)" + > + <mat-icon color="warn">delete</mat-icon> + </button> + </td> + </ng-container> + + <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> + <tr mat-row *matRowDef="let row; columns: displayedColumns"></tr> + <tr class="mat-row" *matNoDataRow> + <td class="text-center" colspan="6">No Documents</td> + </tr> +</table> diff --git a/alcs-frontend/src/app/features/inquiry/documents/documents.component.scss b/alcs-frontend/src/app/features/inquiry/documents/documents.component.scss new file mode 100644 index 0000000000..dadc053396 --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/documents/documents.component.scss @@ -0,0 +1,44 @@ +@use '../../../../styles/colors'; + +:host { + display: block; + padding-bottom: 48px; +} + +.header { + display: flex; + justify-content: space-between; +} + +.documents { + margin-top: 64px; +} + +.mat-mdc-no-data-row { + height: 56px; + color: colors.$grey-dark; +} + +a { + word-break: break-all; +} + +table { + position: relative; + + th mat-header-cell { + position: relative; + } + + .subheading { + font-size: 11px; + line-height: 16px; + font-weight: 400; + position: absolute; + top: 100%; /* Position it below the header text */ + left: 0; /* Align it to the left edge of the header cell */ + } +} + + + diff --git a/alcs-frontend/src/app/features/inquiry/documents/documents.component.spec.ts b/alcs-frontend/src/app/features/inquiry/documents/documents.component.spec.ts new file mode 100644 index 0000000000..4bcb500254 --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/documents/documents.component.spec.ts @@ -0,0 +1,59 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatDialog } from '@angular/material/dialog'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { BehaviorSubject } from 'rxjs'; +import { InquiryDetailService } from '../../../services/inquiry/inquiry-detail.service'; +import { InquiryDocumentService } from '../../../services/inquiry/inquiry-document/inquiry-document.service'; +import { InquiryDto } from '../../../services/inquiry/inquiry.dto'; +import { ToastService } from '../../../services/toast/toast.service'; + +import { DocumentsComponent } from './documents.component'; + +describe('DocumentsComponent', () => { + let component: DocumentsComponent; + let fixture: ComponentFixture<DocumentsComponent>; + let mockInquiryDocumentService: DeepMocked<InquiryDocumentService>; + let mockInquiryDetailService: DeepMocked<InquiryDetailService>; + let mockDialog: DeepMocked<MatDialog>; + let mockToastService: DeepMocked<ToastService>; + + beforeEach(async () => { + mockInquiryDocumentService = createMock(); + mockInquiryDetailService = createMock(); + mockDialog = createMock(); + mockToastService = createMock(); + mockInquiryDetailService.$inquiry = new BehaviorSubject<InquiryDto | undefined>(undefined); + + await TestBed.configureTestingModule({ + declarations: [DocumentsComponent], + providers: [ + { + provide: InquiryDocumentService, + useValue: mockInquiryDocumentService, + }, + { + provide: InquiryDetailService, + useValue: mockInquiryDetailService, + }, + { + provide: MatDialog, + useValue: mockDialog, + }, + { + provide: ToastService, + useValue: mockToastService, + }, + ], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(DocumentsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alcs-frontend/src/app/features/inquiry/documents/documents.component.ts b/alcs-frontend/src/app/features/inquiry/documents/documents.component.ts new file mode 100644 index 0000000000..dba86a2a81 --- /dev/null +++ b/alcs-frontend/src/app/features/inquiry/documents/documents.component.ts @@ -0,0 +1,118 @@ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { MatSort } from '@angular/material/sort'; +import { MatTableDataSource } from '@angular/material/table'; +import { InquiryDetailService } from '../../../services/inquiry/inquiry-detail.service'; +import { InquiryDocumentDto } from '../../../services/inquiry/inquiry-document/inquiry-document.dto'; +import { InquiryDocumentService } from '../../../services/inquiry/inquiry-document/inquiry-document.service'; +import { PlanningReviewDocumentDto } from '../../../services/planning-review/planning-review-document/planning-review-document.dto'; +import { ToastService } from '../../../services/toast/toast.service'; +import { ConfirmationDialogService } from '../../../shared/confirmation-dialog/confirmation-dialog.service'; +import { DOCUMENT_SYSTEM } from '../../../shared/document/document.dto'; +import { DocumentUploadDialogComponent } from './document-upload-dialog/document-upload-dialog.component'; + +@Component({ + selector: 'app-documents', + templateUrl: './documents.component.html', + styleUrls: ['./documents.component.scss'], +}) +export class DocumentsComponent implements OnInit { + displayedColumns: string[] = ['type', 'fileName', 'source', 'uploadedAt', 'actions']; + documents: InquiryDocumentDto[] = []; + private fileId = ''; + + DOCUMENT_SYSTEM = DOCUMENT_SYSTEM; + + @ViewChild(MatSort) sort!: MatSort; + dataSource: MatTableDataSource<InquiryDocumentDto> = new MatTableDataSource<InquiryDocumentDto>(); + + constructor( + private planningReviewDocumentService: InquiryDocumentService, + private inquiryDetailService: InquiryDetailService, + private confirmationDialogService: ConfirmationDialogService, + private toastService: ToastService, + public dialog: MatDialog, + ) {} + + ngOnInit(): void { + this.inquiryDetailService.$inquiry.subscribe((inquiry) => { + if (inquiry) { + this.fileId = inquiry.fileNumber; + this.loadDocuments(inquiry.fileNumber); + } + }); + } + + async onUploadFile() { + this.dialog + .open(DocumentUploadDialogComponent, { + minWidth: '600px', + maxWidth: '800px', + width: '70%', + data: { + fileId: this.fileId, + }, + }) + .beforeClosed() + .subscribe((isDirty) => { + if (isDirty) { + this.loadDocuments(this.fileId); + } + }); + } + + async openFile(uuid: string, fileName: string) { + await this.planningReviewDocumentService.download(uuid, fileName); + } + + async downloadFile(uuid: string, fileName: string) { + await this.planningReviewDocumentService.download(uuid, fileName, false); + } + + private async loadDocuments(fileNumber: string) { + this.documents = await this.planningReviewDocumentService.listAll(fileNumber); + this.dataSource = new MatTableDataSource(this.documents); + this.dataSource.sortingDataAccessor = (item, property) => { + switch (property) { + case 'type': + return item.type?.oatsCode; + default: // @ts-ignore Does not like using String for Key access, but that's what Angular provides + return item[property]; + } + }; + this.dataSource.sort = this.sort; + } + + onEditFile(element: PlanningReviewDocumentDto) { + this.dialog + .open(DocumentUploadDialogComponent, { + minWidth: '600px', + maxWidth: '800px', + width: '70%', + data: { + fileId: this.fileId, + existingDocument: element, + }, + }) + .beforeClosed() + .subscribe((isDirty: boolean) => { + if (isDirty) { + this.loadDocuments(this.fileId); + } + }); + } + + onDeleteFile(element: PlanningReviewDocumentDto) { + this.confirmationDialogService + .openDialog({ + body: 'Are you sure you want to delete the selected file?', + }) + .subscribe(async (accepted) => { + if (accepted) { + await this.planningReviewDocumentService.delete(element.uuid); + this.loadDocuments(this.fileId); + this.toastService.showSuccessToast('Document deleted'); + } + }); + } +} diff --git a/alcs-frontend/src/app/features/inquiry/inquiry.component.ts b/alcs-frontend/src/app/features/inquiry/inquiry.component.ts index 8a9c37cc50..3cb9ca673c 100644 --- a/alcs-frontend/src/app/features/inquiry/inquiry.component.ts +++ b/alcs-frontend/src/app/features/inquiry/inquiry.component.ts @@ -4,6 +4,7 @@ import { Subject, takeUntil } from 'rxjs'; import { InquiryDetailService } from '../../services/inquiry/inquiry-detail.service'; import { InquiryDto } from '../../services/inquiry/inquiry.dto'; import { DetailsComponent } from './detail/details.component'; +import { DocumentsComponent } from './documents/documents.component'; import { OverviewComponent } from './overview/overview.component'; import { ParcelsComponent } from './parcel/parcels.component'; @@ -26,6 +27,12 @@ export const childRoutes = [ icon: 'crop_free', component: ParcelsComponent, }, + { + path: 'documents', + menuTitle: 'Documents', + icon: 'description', + component: DocumentsComponent, + }, ]; @Component({ diff --git a/alcs-frontend/src/app/features/inquiry/inquiry.module.ts b/alcs-frontend/src/app/features/inquiry/inquiry.module.ts index 7f856e5ba4..2df479ebe1 100644 --- a/alcs-frontend/src/app/features/inquiry/inquiry.module.ts +++ b/alcs-frontend/src/app/features/inquiry/inquiry.module.ts @@ -5,6 +5,8 @@ import { RouterModule, Routes } from '@angular/router'; import { InquiryDetailService } from '../../services/inquiry/inquiry-detail.service'; import { SharedModule } from '../../shared/shared.module'; import { DetailsComponent } from './detail/details.component'; +import { DocumentUploadDialogComponent } from './documents/document-upload-dialog/document-upload-dialog.component'; +import { DocumentsComponent } from './documents/documents.component'; import { HeaderComponent } from './header/header.component'; import { childRoutes, InquiryComponent } from './inquiry.component'; import { OverviewComponent } from './overview/overview.component'; @@ -20,7 +22,15 @@ const routes: Routes = [ @NgModule({ providers: [InquiryDetailService], - declarations: [InquiryComponent, OverviewComponent, HeaderComponent, DetailsComponent, ParcelsComponent], + declarations: [ + InquiryComponent, + OverviewComponent, + HeaderComponent, + DetailsComponent, + ParcelsComponent, + DocumentsComponent, + DocumentUploadDialogComponent, + ], imports: [CommonModule, SharedModule, RouterModule.forChild(routes), CdkDropList, CdkDrag], exports: [HeaderComponent], }) diff --git a/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.html b/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.html index 9c81054cd2..2f5ff97df7 100644 --- a/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.html +++ b/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.html @@ -1,8 +1,5 @@ <div mat-dialog-title> <h4>{{ title }} Document</h4> - <span class="superseded-warning" *ngIf="showSupersededWarning" - >Superseded - Not associated with Applicant Submission in Portal</span - > </div> <div mat-dialog-content> <form class="form" [formGroup]="form"> @@ -28,7 +25,7 @@ <h4>{{ title }} Document</h4> <a (click)="openFile()">{{ pendingFile.name }}</a>  ({{ pendingFile.size | filesize }}) </div> - <button [disabled]="!allowsFileEdit" (click)="onRemoveFile()" mat-button> + <button (click)="onRemoveFile()" mat-button> <mat-icon>close</mat-icon> Remove </button> @@ -38,7 +35,7 @@ <h4>{{ title }} Document</h4> <a (click)="openExistingFile()">{{ existingFile.name }}</a>  ({{ existingFile.size | filesize }}) </div> - <button [disabled]="!allowsFileEdit" (click)="onRemoveFile()" mat-button> + <button (click)="onRemoveFile()" mat-button> <mat-icon>close</mat-icon> Remove </button> @@ -79,28 +76,6 @@ <h4>{{ title }} Document</h4> </mat-select> </mat-form-field> </div> - <div *ngIf="type.value === DOCUMENT_TYPE.CERTIFICATE_OF_TITLE"> - <mat-form-field class="full-width" appearance="outline"> - <mat-label>Associated Parcel</mat-label> - <mat-select [formControl]="parcelId"> - <mat-option *ngFor="let parcel of selectableParcels" [value]="parcel.uuid"> - #{{ parcel.index + 1 }} PID: - <span *ngIf="parcel.pid">{{ parcel.pid | mask : '000-000-000' }}</span> - <span *ngIf="!parcel.pid">No Data</span></mat-option - > - </mat-select> - </mat-form-field> - </div> - <div *ngIf="type.value === DOCUMENT_TYPE.CORPORATE_SUMMARY"> - <mat-form-field class="full-width" appearance="outline"> - <mat-label>Associated Organization</mat-label> - <mat-select [formControl]="ownerId"> - <mat-option *ngFor="let owner of selectableOwners" [value]="owner.uuid"> - {{ owner.label }} - </mat-option> - </mat-select> - </mat-form-field> - </div> <div class="double"> <mat-label>Visible To:</mat-label> <div> diff --git a/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.ts b/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.ts index 923a358a34..284a8a0ea6 100644 --- a/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.ts +++ b/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.ts @@ -28,35 +28,25 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { title = 'Create'; isDirty = false; isSaving = false; - allowsFileEdit = true; documentTypeAhead: string | undefined = undefined; name = new FormControl<string>('', [Validators.required]); type = new FormControl<string | undefined>(undefined, [Validators.required]); source = new FormControl<string>('', [Validators.required]); - - parcelId = new FormControl<string | null>(null); - ownerId = new FormControl<string | null>(null); - visibleToCommissioner = new FormControl<boolean>(false, [Validators.required]); documentTypes: DocumentTypeDto[] = []; documentSources = Object.values(DOCUMENT_SOURCE); - selectableParcels: { uuid: string; index: number; pid?: string }[] = []; - selectableOwners: { uuid: string; label: string }[] = []; form = new FormGroup({ name: this.name, type: this.type, source: this.source, visibleToCommissioner: this.visibleToCommissioner, - parcelId: this.parcelId, - ownerId: this.ownerId, }); pendingFile: File | undefined; existingFile: { name: string; size: number } | undefined; - showSupersededWarning = false; showVirusError = false; constructor( @@ -73,7 +63,6 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { if (this.data.existingDocument) { const document = this.data.existingDocument; this.title = 'Edit'; - this.allowsFileEdit = document.system === DOCUMENT_SYSTEM.ALCS; this.form.patchValue({ name: document.fileName, type: document.type?.code, @@ -101,8 +90,6 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { source: this.source.value as DOCUMENT_SOURCE, typeCode: this.type.value as DOCUMENT_TYPE, visibilityFlags, - parcelUuid: this.parcelId.value ?? undefined, - ownerUuid: this.ownerId.value ?? undefined, file, }; diff --git a/alcs-frontend/src/app/services/inquiry/inquiry-document/inquiry-document.dto.ts b/alcs-frontend/src/app/services/inquiry/inquiry-document/inquiry-document.dto.ts new file mode 100644 index 0000000000..adda3a503d --- /dev/null +++ b/alcs-frontend/src/app/services/inquiry/inquiry-document/inquiry-document.dto.ts @@ -0,0 +1,32 @@ +import { + DOCUMENT_SOURCE, + DOCUMENT_SYSTEM, + DOCUMENT_TYPE, + DocumentTypeDto, +} from '../../../shared/document/document.dto'; + +export interface InquiryDocumentDto { + uuid: string; + documentUuid: string; + type?: DocumentTypeDto; + description?: string; + source: DOCUMENT_SOURCE; + system: DOCUMENT_SYSTEM; + fileName: string; + mimeType: string; + uploadedBy: string; + uploadedAt: number; +} + +export interface UpdateDocumentDto { + file?: File; + parcelUuid?: string; + ownerUuid?: string; + fileName: string; + typeCode: DOCUMENT_TYPE; + source: DOCUMENT_SOURCE; +} + +export interface CreateDocumentDto extends UpdateDocumentDto { + file: File; +} diff --git a/alcs-frontend/src/app/services/inquiry/inquiry-document/inquiry-document.service.spec.ts b/alcs-frontend/src/app/services/inquiry/inquiry-document/inquiry-document.service.spec.ts new file mode 100644 index 0000000000..154b461426 --- /dev/null +++ b/alcs-frontend/src/app/services/inquiry/inquiry-document/inquiry-document.service.spec.ts @@ -0,0 +1,94 @@ +import { HttpClient } from '@angular/common/http'; +import { TestBed } from '@angular/core/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { of } from 'rxjs'; +import { environment } from '../../../../environments/environment'; +import { DOCUMENT_SOURCE, DOCUMENT_TYPE } from '../../../shared/document/document.dto'; +import { ToastService } from '../../toast/toast.service'; +import { InquiryDocumentService } from './inquiry-document.service'; + +describe('InquiryDocumentService', () => { + let service: InquiryDocumentService; + let httpClient: DeepMocked<HttpClient>; + let toastService: DeepMocked<ToastService>; + + beforeEach(() => { + httpClient = createMock(); + toastService = createMock(); + + TestBed.configureTestingModule({ + providers: [ + { + provide: HttpClient, + useValue: httpClient, + }, + { + provide: ToastService, + useValue: toastService, + }, + ], + }); + service = TestBed.inject(InquiryDocumentService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should make a get call for list', async () => { + httpClient.get.mockReturnValue( + of([ + { + uuid: '1', + }, + ]), + ); + + const res = await service.listAll('1'); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(res.length).toEqual(1); + expect(res[0].uuid).toEqual('1'); + }); + + it('should make a delete call for delete', async () => { + httpClient.delete.mockReturnValue( + of({ + uuid: '1', + }), + ); + + const res = await service.delete('1'); + + expect(httpClient.delete).toHaveBeenCalledTimes(1); + expect(res).toBeDefined(); + expect(res.uuid).toEqual('1'); + }); + + it('should show a toast warning when uploading a file thats too large', async () => { + const file = createMock<File>(); + Object.defineProperty(file, 'size', { value: environment.maxFileSize + 1 }); + + await service.upload('', { + file, + fileName: '', + typeCode: DOCUMENT_TYPE.AUTHORIZATION_LETTER, + source: DOCUMENT_SOURCE.APPLICANT, + }); + + expect(toastService.showWarningToast).toHaveBeenCalledTimes(1); + expect(httpClient.post).toHaveBeenCalledTimes(0); + }); + + it('should make a post call for sort', async () => { + httpClient.post.mockReturnValue( + of({ + uuid: '1', + }), + ); + + await service.updateSort([]); + + expect(httpClient.post).toHaveBeenCalledTimes(1); + }); +}); diff --git a/alcs-frontend/src/app/services/inquiry/inquiry-document/inquiry-document.service.ts b/alcs-frontend/src/app/services/inquiry/inquiry-document/inquiry-document.service.ts new file mode 100644 index 0000000000..1b05959458 --- /dev/null +++ b/alcs-frontend/src/app/services/inquiry/inquiry-document/inquiry-document.service.ts @@ -0,0 +1,88 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { firstValueFrom } from 'rxjs'; +import { environment } from '../../../../environments/environment'; +import { DocumentTypeDto } from '../../../shared/document/document.dto'; +import { downloadFileFromUrl, openFileInline } from '../../../shared/utils/file'; +import { verifyFileSize } from '../../../shared/utils/file-size-checker'; +import { ToastService } from '../../toast/toast.service'; +import { CreateDocumentDto, InquiryDocumentDto, UpdateDocumentDto } from './inquiry-document.dto'; + +@Injectable({ + providedIn: 'root', +}) +export class InquiryDocumentService { + private url = `${environment.apiUrl}/inquiry-document`; + + constructor( + private http: HttpClient, + private toastService: ToastService, + ) {} + + async listAll(fileNumber: string) { + return firstValueFrom(this.http.get<InquiryDocumentDto[]>(`${this.url}/inquiry/${fileNumber}`)); + } + + async upload(fileNumber: string, createDto: CreateDocumentDto) { + const file = createDto.file; + const isValidSize = verifyFileSize(file, this.toastService); + if (!isValidSize) { + return; + } + let formData = this.convertDtoToFormData(createDto); + + const res = await firstValueFrom(this.http.post(`${this.url}/inquiry/${fileNumber}`, formData)); + this.toastService.showSuccessToast('Document uploaded'); + return res; + } + + async delete(uuid: string) { + return firstValueFrom(this.http.delete<InquiryDocumentDto>(`${this.url}/${uuid}`)); + } + + async download(uuid: string, fileName: string, isInline = true) { + const url = isInline ? `${this.url}/${uuid}/open` : `${this.url}/${uuid}/download`; + const data = await firstValueFrom(this.http.get<{ url: string }>(url)); + if (isInline) { + openFileInline(data.url, fileName); + } else { + downloadFileFromUrl(data.url, fileName); + } + } + + async fetchTypes() { + return firstValueFrom(this.http.get<DocumentTypeDto[]>(`${this.url}/types`)); + } + + async update(uuid: string, updateDto: UpdateDocumentDto) { + let formData = this.convertDtoToFormData(updateDto); + const res = await firstValueFrom(this.http.post(`${this.url}/${uuid}`, formData)); + this.toastService.showSuccessToast('Document uploaded'); + return res; + } + + async updateSort(sortOrder: { uuid: string; order: number }[]) { + try { + await firstValueFrom(this.http.post<DocumentTypeDto[]>(`${this.url}/sort`, sortOrder)); + } catch (e) { + this.toastService.showErrorToast(`Failed to save document order`); + } + } + + private convertDtoToFormData(dto: UpdateDocumentDto) { + let formData: FormData = new FormData(); + formData.append('documentType', dto.typeCode); + formData.append('source', dto.source); + formData.append('fileName', dto.fileName); + if (dto.file) { + formData.append('file', dto.file, dto.file.name); + } + if (dto.parcelUuid) { + formData.append('parcelUuid', dto.parcelUuid); + } + if (dto.ownerUuid) { + formData.append('ownerUuid', dto.ownerUuid); + } + return formData; + } +} diff --git a/alcs-frontend/src/app/services/planning-review/planning-review-document/planning-review-document.dto.ts b/alcs-frontend/src/app/services/planning-review/planning-review-document/planning-review-document.dto.ts index 29bedaf646..cf6233c77e 100644 --- a/alcs-frontend/src/app/services/planning-review/planning-review-document/planning-review-document.dto.ts +++ b/alcs-frontend/src/app/services/planning-review/planning-review-document/planning-review-document.dto.ts @@ -22,8 +22,6 @@ export interface PlanningReviewDocumentDto { export interface UpdateDocumentDto { file?: File; - parcelUuid?: string; - ownerUuid?: string; fileName: string; typeCode: DOCUMENT_TYPE; source: DOCUMENT_SOURCE; diff --git a/alcs-frontend/src/app/services/planning-review/planning-review-document/planning-review-document.service.ts b/alcs-frontend/src/app/services/planning-review/planning-review-document/planning-review-document.service.ts index bfafc6d8d1..545ea10ecb 100644 --- a/alcs-frontend/src/app/services/planning-review/planning-review-document/planning-review-document.service.ts +++ b/alcs-frontend/src/app/services/planning-review/planning-review-document/planning-review-document.service.ts @@ -85,17 +85,10 @@ export class PlanningReviewDocumentService { let formData: FormData = new FormData(); formData.append('documentType', dto.typeCode); formData.append('source', dto.source); - formData.append('visibilityFlags', dto.visibilityFlags.join(', ')); formData.append('fileName', dto.fileName); if (dto.file) { formData.append('file', dto.file, dto.file.name); } - if (dto.parcelUuid) { - formData.append('parcelUuid', dto.parcelUuid); - } - if (dto.ownerUuid) { - formData.append('ownerUuid', dto.ownerUuid); - } return formData; } } From d1a9147531b3f3ac39377eb34596383726c215d2 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Thu, 28 Mar 2024 09:27:21 -0700 Subject: [PATCH 061/153] Bug Fixes --- .../inquiry/inquiry-dialog.component.html | 56 +++++++++---------- .../inquiry/header/header.component.ts | 4 +- .../header/header.component.ts | 2 +- 3 files changed, 30 insertions(+), 32 deletions(-) diff --git a/alcs-frontend/src/app/features/board/dialogs/inquiry/inquiry-dialog.component.html b/alcs-frontend/src/app/features/board/dialogs/inquiry/inquiry-dialog.component.html index 2698755af4..fcbd7347f9 100644 --- a/alcs-frontend/src/app/features/board/dialogs/inquiry/inquiry-dialog.component.html +++ b/alcs-frontend/src/app/features/board/dialogs/inquiry/inquiry-dialog.component.html @@ -1,7 +1,7 @@ <div mat-dialog-title> <div class="close"> - <h6 class="card-type-label">Planning Review</h6> - <button mat-icon-button [mat-dialog-close]="isDirty"> + <h6 class="card-type-label">Inquiry</h6> + <button [mat-dialog-close]="isDirty" mat-icon-button> <mat-icon>close</mat-icon> </button> </div> @@ -14,10 +14,10 @@ <h3 class="card-title center"> </div> <div class="center"> <button - color="accent" - mat-flat-button [mat-dialog-close]="isDirty" [routerLink]="['inquiry', inquiry.fileNumber]" + color="accent" + mat-flat-button > View Detail </button> @@ -32,44 +32,44 @@ <h3 class="card-title center"> <app-application-type-pill *ngIf="!inquiry.open" [type]="CLOSED_TYPE"></app-application-type-pill> </div> <div class="right"> - <button matTooltip="Move Board" [matMenuTriggerFor]="moveMenu" mat-icon-button> + <button [matMenuTriggerFor]="moveMenu" mat-icon-button matTooltip="Move Board"> <mat-icon>move_down</mat-icon> </button> - <mat-menu class="move-board-menu" xPosition="before" #moveMenu="matMenu"> + <mat-menu #moveMenu="matMenu" class="move-board-menu" xPosition="before"> <button (click)="onBoardSelected(board)" *ngFor="let board of allowedBoards" mat-menu-item> <div class="board-menu-item"> <span class="favourite-board-icon-container" - ><mat-icon *ngIf="board.isFavourite" class="favourite-board-icon">star</mat-icon> + ><mat-icon *ngIf="board.isFavourite" class="favourite-board-icon">star</mat-icon> </span> <span>{{ board.title }}</span> <span *ngIf="card && card.boardCode === board.code" - ><mat-icon class="selected-board-icon">check</mat-icon></span + ><mat-icon class="selected-board-icon">check</mat-icon></span > </div> </button> </mat-menu> <button + (click)="onArchiveCard()" *ngIf="canArchive" - matTooltip="Archive Card" class="toggle-priority" - (click)="onArchiveCard()" mat-icon-button + matTooltip="Archive Card" > <mat-icon>archive</mat-icon> </button> <button + (click)="onTogglePriority()" *ngIf="card" [matTooltip]="card.highPriority ? 'Remove Priority' : 'Add Priority'" class="toggle-priority" - (click)="onTogglePriority()" mat-icon-button > <div - class="priority" [ngClass]="{ 'filled-priority': card.highPriority, 'empty-priority': !card.highPriority }" + class="priority" ></div> </button> </div> @@ -78,38 +78,38 @@ <h3 class="card-title center"> <mat-dialog-content> <div class="select-container"> <ng-select - class="card-type" - appearance="outline" + (change)="onStatusSelected($event)" + [(ngModel)]="selectedApplicationStatus" + [clearable]="false" [items]="boardStatuses" - placeholder="Workflow Stage" + appearance="outline" bindLabel="label" bindValue="statusCode" - [clearable]="false" - [(ngModel)]="selectedApplicationStatus" - (change)="onStatusSelected($event)" + class="card-type" + placeholder="Workflow Stage" > - <ng-template ng-option-tmp let-item="item"> + <ng-template let-item="item" ng-option-tmp> <span [innerHTML]="item.label"> </span> </ng-template> - <ng-template ng-label-tmp let-item="item"> + <ng-template let-item="item" ng-label-tmp> <span [innerHTML]="item.label"> </span> </ng-template> </ng-select> <ng-select - class="card-assignee" - appearance="outline" + (change)="onAssigneeSelected($event)" + [(ngModel)]="selectedAssigneeName" [items]="$users | async" - placeholder="Assigned Planner" + [searchFn]="filterAssigneeList" + appearance="outline" bindLabel="prettyName" bindValue="prettyName" - [(ngModel)]="selectedAssigneeName" - [searchFn]="filterAssigneeList" - (change)="onAssigneeSelected($event)" + class="card-assignee" + placeholder="Assigned Planner" > - <ng-template ng-option-tmp let-item="item" let-search="searchTerm"> + <ng-template let-item="item" let-search="searchTerm" ng-option-tmp> <div class="assignee-card-body"> <p [ngOptionHighlight]="search" class="assignee-card-name">{{ item.prettyName }}</p> - <p class="assignee-card-email" [ngOptionHighlight]="search">{{ item.email }}</p> + <p [ngOptionHighlight]="search" class="assignee-card-email">{{ item.email }}</p> </div> </ng-template> </ng-select> diff --git a/alcs-frontend/src/app/features/inquiry/header/header.component.ts b/alcs-frontend/src/app/features/inquiry/header/header.component.ts index d5eae13100..8bc2e4426d 100644 --- a/alcs-frontend/src/app/features/inquiry/header/header.component.ts +++ b/alcs-frontend/src/app/features/inquiry/header/header.component.ts @@ -1,10 +1,8 @@ -import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { Component, Input, OnChanges } from '@angular/core'; import { Router } from '@angular/router'; import { Subject } from 'rxjs'; import { CardDto } from '../../../services/card/card.dto'; -import { CommissionerPlanningReviewDto } from '../../../services/commissioner/commissioner.dto'; import { InquiryDto } from '../../../services/inquiry/inquiry.dto'; -import { PlanningReviewDetailedDto } from '../../../services/planning-review/planning-review.dto'; import { CLOSED_PR_LABEL, OPEN_PR_LABEL } from '../../../shared/application-type-pill/application-type-pill.constants'; @Component({ diff --git a/alcs-frontend/src/app/features/planning-review/header/header.component.ts b/alcs-frontend/src/app/features/planning-review/header/header.component.ts index 6af7d8a2a7..488602c79d 100644 --- a/alcs-frontend/src/app/features/planning-review/header/header.component.ts +++ b/alcs-frontend/src/app/features/planning-review/header/header.component.ts @@ -34,7 +34,7 @@ export class HeaderComponent implements OnChanges { for (const [index, referral] of this.planningReview.referrals.entries()) { this.linkedCards.push({ ...referral.card, - displayName: `Referral ${index}`, + displayName: `Referral #${this.planningReview.referrals.length - index}`, }); } } From fbb9d1d2f7d4bfe6df9724b04414fc1fb5f6cd28 Mon Sep 17 00:00:00 2001 From: Tristan Slater <1631008+trslater@users.noreply.github.com> Date: Thu, 28 Mar 2024 10:48:03 -0700 Subject: [PATCH 062/153] Refactor to use boolean value directly --- .../decision-condition.component.html | 4 ++-- .../decision-condition.component.ts | 12 +++--------- .../decision-condition.component.html | 4 ++-- .../decision-condition.component.ts | 12 +++--------- 4 files changed, 10 insertions(+), 22 deletions(-) diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html index ead69cd822..b139093071 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html @@ -17,8 +17,8 @@ <h5>{{ data.type?.label }}</h5> <div> <mat-label>Approval Dependent*</mat-label> <mat-button-toggle-group required formControlName="approvalDependant"> - <mat-button-toggle value="true">Yes</mat-button-toggle> - <mat-button-toggle value="false">No</mat-button-toggle> + <mat-button-toggle [value]="true">Yes</mat-button-toggle> + <mat-button-toggle [value]="false">No</mat-button-toggle> </mat-button-toggle-group> </div> diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.ts b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.ts index 127a55cde7..127733e1ed 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.ts +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.ts @@ -1,6 +1,5 @@ import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { parseStringToBoolean } from '../../../../../../../shared/utils/boolean-helper'; import { SelectableComponent, TempApplicationDecisionConditionDto } from '../decision-conditions.component'; @Component({ @@ -16,7 +15,7 @@ export class DecisionConditionComponent implements OnInit, OnChanges { @Input() selectableComponents: SelectableComponent[] = []; componentsToCondition = new FormControl<string[] | null>(null, [Validators.required]); - approvalDependant = new FormControl<string | null>(null, [Validators.required]); + approvalDependant = new FormControl<boolean | null>(null, [Validators.required]); securityAmount = new FormControl<string | null>(null); administrativeFee = new FormControl<string | null>(null); @@ -32,11 +31,6 @@ export class DecisionConditionComponent implements OnInit, OnChanges { ngOnInit(): void { if (this.data) { - let approvalDependant = null; - if (this.data.approvalDependant !== null) { - approvalDependant = this.data.approvalDependant ? 'true' : 'false'; - } - const selectedOptions = this.selectableComponents .filter((component) => this.data.componentsToCondition?.map((e) => e.tempId)?.includes(component.tempId)) .map((e) => ({ @@ -48,7 +42,7 @@ export class DecisionConditionComponent implements OnInit, OnChanges { this.componentsToCondition.setValue(selectedOptions.map((e) => e.tempId) ?? null); this.form.patchValue({ - approvalDependant, + approvalDependant: this.data.approvalDependant, securityAmount: this.data.securityAmount?.toString() ?? null, administrativeFee: this.data.administrativeFee?.toString() ?? null, description: this.data.description ?? null, @@ -68,7 +62,7 @@ export class DecisionConditionComponent implements OnInit, OnChanges { type: this.data.type, tempUuid: this.data.tempUuid, uuid: this.data.uuid, - approvalDependant: parseStringToBoolean(this.approvalDependant.value), + approvalDependant: this.approvalDependant.value, securityAmount: this.securityAmount.value !== null ? parseFloat(this.securityAmount.value) : undefined, administrativeFee: this.administrativeFee.value !== null ? parseFloat(this.administrativeFee.value) : undefined, description: this.description.value ?? undefined, diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html index ead69cd822..b139093071 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html @@ -17,8 +17,8 @@ <h5>{{ data.type?.label }}</h5> <div> <mat-label>Approval Dependent*</mat-label> <mat-button-toggle-group required formControlName="approvalDependant"> - <mat-button-toggle value="true">Yes</mat-button-toggle> - <mat-button-toggle value="false">No</mat-button-toggle> + <mat-button-toggle [value]="true">Yes</mat-button-toggle> + <mat-button-toggle [value]="false">No</mat-button-toggle> </mat-button-toggle-group> </div> diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.ts index 80a2895424..36ae5b676c 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.ts @@ -1,6 +1,5 @@ import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { parseStringToBoolean } from '../../../../../../../shared/utils/boolean-helper'; import { SelectableComponent, TempNoticeOfIntentDecisionConditionDto } from '../decision-conditions.component'; @Component({ @@ -16,7 +15,7 @@ export class DecisionConditionComponent implements OnInit, OnChanges { @Input() selectableComponents: SelectableComponent[] = []; componentsToCondition = new FormControl<string[] | null>(null, [Validators.required]); - approvalDependant = new FormControl<string | null>(null, [Validators.required]); + approvalDependant = new FormControl<boolean | null>(null, [Validators.required]); securityAmount = new FormControl<string | null>(null); administrativeFee = new FormControl<string | null>(null); @@ -32,11 +31,6 @@ export class DecisionConditionComponent implements OnInit, OnChanges { ngOnInit(): void { if (this.data) { - let approvalDependant = null; - if (this.data.approvalDependant !== null) { - approvalDependant = this.data.approvalDependant ? 'true' : 'false'; - } - const selectedOptions = this.selectableComponents .filter((component) => this.data.componentsToCondition?.map((e) => e.tempId)?.includes(component.tempId)) .map((e) => ({ @@ -48,7 +42,7 @@ export class DecisionConditionComponent implements OnInit, OnChanges { this.componentsToCondition.setValue(selectedOptions.map((e) => e.tempId) ?? null); this.form.patchValue({ - approvalDependant, + approvalDependant: this.data.approvalDependant, securityAmount: this.data.securityAmount?.toString() ?? null, administrativeFee: this.data.administrativeFee?.toString() ?? null, description: this.data.description ?? null, @@ -68,7 +62,7 @@ export class DecisionConditionComponent implements OnInit, OnChanges { type: this.data.type, tempUuid: this.data.tempUuid, uuid: this.data.uuid, - approvalDependant: parseStringToBoolean(this.approvalDependant.value), + approvalDependant: this.approvalDependant.value, securityAmount: this.securityAmount.value !== null ? parseFloat(this.securityAmount.value) : undefined, administrativeFee: this.administrativeFee.value !== null ? parseFloat(this.administrativeFee.value) : undefined, description: this.description.value ?? undefined, From ec5d3f9429a2e5ee0b0ce9cd4a6d37b975be0dba Mon Sep 17 00:00:00 2001 From: Tristan Slater <1631008+trslater@users.noreply.github.com> Date: Thu, 28 Mar 2024 11:29:57 -0700 Subject: [PATCH 063/153] Add error class when touched and invalid --- .../decision-condition/decision-condition.component.html | 8 +++++++- .../decision-condition/decision-condition.component.html | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html index b139093071..8224b0609f 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html @@ -16,7 +16,13 @@ <h5>{{ data.type?.label }}</h5> <div> <mat-label>Approval Dependent*</mat-label> - <mat-button-toggle-group required formControlName="approvalDependant"> + <mat-button-toggle-group + [ngClass]="{ + 'error-field-outlined': !approvalDependant.valid && approvalDependant.touched + }" + required + formControlName="approvalDependant" + > <mat-button-toggle [value]="true">Yes</mat-button-toggle> <mat-button-toggle [value]="false">No</mat-button-toggle> </mat-button-toggle-group> diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html index b139093071..8224b0609f 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html @@ -16,7 +16,13 @@ <h5>{{ data.type?.label }}</h5> <div> <mat-label>Approval Dependent*</mat-label> - <mat-button-toggle-group required formControlName="approvalDependant"> + <mat-button-toggle-group + [ngClass]="{ + 'error-field-outlined': !approvalDependant.valid && approvalDependant.touched + }" + required + formControlName="approvalDependant" + > <mat-button-toggle [value]="true">Yes</mat-button-toggle> <mat-button-toggle [value]="false">No</mat-button-toggle> </mat-button-toggle-group> From cee587a52c128ffff7335762d2108d9a54cd7200 Mon Sep 17 00:00:00 2001 From: Liam Stoddard <lstodd@protonmail.com> Date: Thu, 28 Mar 2024 12:01:37 -0700 Subject: [PATCH 064/153] MR feedback, more detailed explanation of card/referral linking & creatiion --- .../migrate_planning_review.py | 3 +++ .../referrals/planning_review_card_init.py | 3 ++- .../referrals/planning_review_card_update.py | 1 + .../planning_review_referrals_init.py | 20 +++++++++---------- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/bin/migrate-oats-data/planning_review/migrate_planning_review.py b/bin/migrate-oats-data/planning_review/migrate_planning_review.py index d517bd6461..d6ae76bac9 100644 --- a/bin/migrate-oats-data/planning_review/migrate_planning_review.py +++ b/bin/migrate-oats-data/planning_review/migrate_planning_review.py @@ -20,8 +20,11 @@ def process_planning_review(batch_size): init_planning_review_base(batch_size) process_planning_review_staff_journal(batch_size) update_planning_review_base_fields(batch_size) + # planning review cards are initialized with planning_review.uuid 1:1 mapping init_planning_review_cards(batch_size) + # card_uuid is transferred to referrals process_planning_review_referral(batch_size) + # planning review cards are updated to remove pr.uuid from audit_updated_by column as they are now matched by referrals update_planning_review_cards(batch_size) diff --git a/bin/migrate-oats-data/planning_review/referrals/planning_review_card_init.py b/bin/migrate-oats-data/planning_review/referrals/planning_review_card_init.py index b006ca2d92..388346ece7 100644 --- a/bin/migrate-oats-data/planning_review/referrals/planning_review_card_init.py +++ b/bin/migrate-oats-data/planning_review/referrals/planning_review_card_init.py @@ -95,7 +95,7 @@ def _insert_base_fields(conn, batch_size, cursor, rows): return parsed_data_list -# use audit_updated_by as temp placeholder for pr uuid +# use audit_updated_by as temp placeholder for pr.uuid query = f""" INSERT INTO alcs.card ( audit_updated_by, @@ -120,6 +120,7 @@ def _prepare_oats_planning_review_card_data(row_data_list): mapped_data_list.append( { "planning_review_id": row["planning_review_id"], + # planning_review_uuid is used as a temporary placeholder in order to create and match referrals 1:1 to cards "uuid": row["uuid"], "type_code": "PLAN", "status_code": "PREL", diff --git a/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py b/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py index 7c8b3bb3a8..3078ffae97 100644 --- a/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py +++ b/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py @@ -91,6 +91,7 @@ def _update_base_fields(conn, batch_size, cursor, rows): return parsed_data_list +# audit_updated_by is removed as the uuid is no longer needed to link referrals and cards _rx_items_query = """ UPDATE alcs.card SET board_uuid = %(board_uuid)s, diff --git a/bin/migrate-oats-data/planning_review/referrals/planning_review_referrals_init.py b/bin/migrate-oats-data/planning_review/referrals/planning_review_referrals_init.py index 7dc615237e..6eb05438f1 100644 --- a/bin/migrate-oats-data/planning_review/referrals/planning_review_referrals_init.py +++ b/bin/migrate-oats-data/planning_review/referrals/planning_review_referrals_init.py @@ -34,8 +34,8 @@ def process_planning_review_referral(conn=None, batch_size=BATCH_UPLOAD_SIZE): count_total = dict(cursor.fetchone())["count"] logger.info(f"Total Planning Referral data to insert: {count_total}") - failed_inserts = 0 - successful_updates_count = 0 + failed_inserts_count = 0 + successful_inserts_count = 0 last_planning_review_id = 0 with open( @@ -57,27 +57,27 @@ def process_planning_review_referral(conn=None, batch_size=BATCH_UPLOAD_SIZE): if not rows: break try: - updated_data = _insert_base_fields(conn, batch_size, cursor, rows) + inserted_data = _insert_base_fields(conn, batch_size, cursor, rows) - successful_updates_count = successful_updates_count + len( - updated_data + successful_inserts_count = successful_inserts_count + len( + inserted_data ) - last_planning_review_id = dict(updated_data[-1])[ + last_planning_review_id = dict(inserted_data[-1])[ "planning_review_id" ] logger.debug( - f"Retrieved/updated items count: {len(updated_data)}; total successfully inserted planning referral so far {successful_updates_count}; last updated planning_review_id: {last_planning_review_id}" + f"Retrieved/updated items count: {len(inserted_data)}; total successfully inserted planning referral so far {successful_inserts_count}; last updated planning_review_id: {last_planning_review_id}" ) except Exception as err: - # this is NOT going to be caused by actual data update failure. This code is only executed when the code error appears or connection to DB is lost + # this is NOT going to be caused by actual data insert failure. This code is only executed when the code error appears or connection to DB is lost logger.exception(err) conn.rollback() - failed_inserts = count_total - successful_updates_count + failed_inserts_count = count_total - successful_inserts_count last_planning_review_id = last_planning_review_id + 1 logger.info( - f"Finished {etl_name}: total amount of successful inserts {successful_updates_count}, total failed updates {failed_inserts}" + f"Finished {etl_name}: total amount of successful inserts {successful_inserts_count}, total failed inserts {failed_inserts_count}" ) From e47f1e8418ffeab8233156dbb511435bf0c8b5e9 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Thu, 28 Mar 2024 13:15:45 -0700 Subject: [PATCH 065/153] Move file extensions to un-editable suffix --- ...sion-document-upload-dialog.component.html | 55 +++++++------- ...cision-document-upload-dialog.component.ts | 24 +++++-- .../document-upload-dialog.component.html | 65 ++++++++--------- .../document-upload-dialog.component.ts | 14 +++- .../document-upload-dialog.component.html | 55 +++++++------- .../document-upload-dialog.component.ts | 17 ++++- ...sion-document-upload-dialog.component.html | 55 +++++++------- ...cision-document-upload-dialog.component.ts | 20 ++++-- .../document-upload-dialog.component.html | 71 ++++++++++--------- .../document-upload-dialog.component.ts | 23 +++--- .../document-upload-dialog.component.html | 61 ++++++++-------- .../document-upload-dialog.component.ts | 13 +++- ...sion-document-upload-dialog.component.html | 57 ++++++++------- ...cision-document-upload-dialog.component.ts | 24 +++++-- .../document-upload-dialog.component.html | 51 ++++++------- .../document-upload-dialog.component.ts | 21 +++--- .../inquiry-document.service.ts | 6 -- .../planning-review-document.service.ts | 3 +- alcs-frontend/src/app/shared/utils/file.ts | 7 ++ alcs-frontend/src/styles.scss | 4 ++ 20 files changed, 371 insertions(+), 275 deletions(-) diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html index c800316e76..645cfe598c 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html @@ -2,66 +2,69 @@ <h4>{{ title }} Document</h4> </div> <div mat-dialog-content> - <form class="form" [formGroup]="form"> + <form [formGroup]="form" class="form"> <div class="double"> <div> <mat-label>Document Upload*</mat-label> </div> - <input hidden type="file" #fileInput (change)="uploadFile($event)" placeholder="Upload file" /> + <input #fileInput (change)="uploadFile($event)" hidden placeholder="Upload file" type="file" /> <button + (click)="fileInput.click()" *ngIf="!pendingFile && !existingFile" - class="full-width upload-button" - mat-flat-button - color="accent" [ngClass]="{ error: showVirusError }" - (click)="fileInput.click()" + class="full-width upload-button" + color="accent" + mat-flat-button > Upload </button> - <div class="file" *ngIf="pendingFile"> + <div *ngIf="pendingFile" class="file"> <div> <a (click)="openFile()">{{ pendingFile.name }}</a>  ({{ pendingFile.size | filesize }}) </div> - <button [disabled]="!allowsFileEdit" (click)="onRemoveFile()" mat-button> - <mat-icon>close</mat-icon>Remove + <button (click)="onRemoveFile()" [disabled]="!allowsFileEdit" mat-button> + <mat-icon>close</mat-icon> + Remove </button> </div> - <div class="file" *ngIf="existingFile"> + <div *ngIf="existingFile" class="file"> <div> <a (click)="openExistingFile()">{{ existingFile }}</a> </div> - <button [disabled]="!allowsFileEdit" (click)="onRemoveFile()" mat-button> - <mat-icon>close</mat-icon>Remove + <button (click)="onRemoveFile()" [disabled]="!allowsFileEdit" mat-button> + <mat-icon>close</mat-icon> + Remove </button> </div> - <mat-error class="left" style="display: flex" *ngIf="showVirusError"> + <mat-error *ngIf="showVirusError" class="left" style="display: flex"> <mat-icon>warning</mat-icon> A virus was detected in the file. Choose another file and try again. </mat-error> </div> <div class="double"> - <mat-form-field class="full-width" appearance="outline"> + <mat-form-field appearance="outline" class="full-width"> <mat-label>Document Name</mat-label> - <input required matInput id="name" [formControl]="name" name="name" /> + <input [formControl]="name" id="name" matInput name="name" required /> + <span matTextSuffix>{{ extension }}</span> </mat-form-field> </div> <div> <ng-select - appearance="outline" - required - placeholder="Document Type*" - [ngModelOptions]="{ standalone: true }" [(ngModel)]="documentType" + [ngModelOptions]="{ standalone: true }" + appearance="outline" disabled="true" + placeholder="Document Type*" + required > </ng-select> </div> <div> - <mat-form-field class="full-width" appearance="outline"> + <mat-form-field appearance="outline" class="full-width"> <mat-label>Source</mat-label> <mat-select [formControl]="source"> <mat-option *ngFor="let source of documentSources" [value]="source">{{ source }}</mat-option> @@ -81,18 +84,18 @@ <h4>{{ title }} Document</h4> <mat-dialog-actions align="end"> <div class="button-container"> - <button type="button" mat-stroked-button color="primary" [mat-dialog-close]="false">Close</button> + <button [mat-dialog-close]="false" color="primary" mat-stroked-button type="button">Close</button> <button + (click)="onSubmit()" *ngIf="!isSaving" - type="button" - mat-flat-button - color="primary" [disabled]="!form.valid || (!pendingFile && !existingFile)" - (click)="onSubmit()" + color="primary" + mat-flat-button + type="button" > Save </button> - <button *ngIf="isSaving" type="button" mat-flat-button color="primary" [disabled]="true"> + <button *ngIf="isSaving" [disabled]="true" color="primary" mat-flat-button type="button"> <mat-spinner class="spinner" diameter="20"></mat-spinner> Uploading </button> diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts index 56c5132c3e..ecea8098b1 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts @@ -6,6 +6,7 @@ import { ApplicationDecisionDocumentDto } from '../../../../../../services/appli import { ApplicationDecisionV2Service } from '../../../../../../services/application/decision/application-decision-v2/application-decision-v2.service'; import { ToastService } from '../../../../../../services/toast/toast.service'; import { DOCUMENT_SOURCE } from '../../../../../../shared/document/document.dto'; +import { splitExtension } from '../../../../../../shared/utils/file'; @Component({ selector: 'app-app-decision-document-upload-dialog', @@ -39,21 +40,25 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { pendingFile: File | undefined; existingFile: string | undefined; showVirusError = false; + extension = ''; constructor( @Inject(MAT_DIALOG_DATA) public data: { fileId: string; decisionUuid: string; existingDocument?: ApplicationDecisionDocumentDto }, protected dialog: MatDialogRef<any>, private decisionService: ApplicationDecisionV2Service, - private toastService: ToastService + private toastService: ToastService, ) {} ngOnInit(): void { if (this.data.existingDocument) { const document = this.data.existingDocument; this.title = 'Edit'; + + const { fileName, extension } = splitExtension(document.fileName); + this.extension = extension; this.form.patchValue({ - name: document.fileName, + name: fileName, }); this.existingFile = document.fileName; } @@ -62,7 +67,7 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { async onSubmit() { const file = this.pendingFile; if (file) { - const renamedFile = new File([file], this.name.value ?? file.name); + const renamedFile = new File([file], this.name.value + this.extension ?? file.name); this.isSaving = true; if (this.data.existingDocument) { await this.decisionService.deleteFile(this.data.decisionUuid, this.data.existingDocument.uuid); @@ -84,7 +89,11 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { this.isSaving = false; } else if (this.data.existingDocument) { this.isSaving = true; - await this.decisionService.updateFile(this.data.decisionUuid, this.data.existingDocument.uuid, this.name.value!); + await this.decisionService.updateFile( + this.data.decisionUuid, + this.data.existingDocument.uuid, + this.name.value! + this.extension, + ); this.dialog.close(true); this.isSaving = false; @@ -96,7 +105,9 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { const selectedFiles = element.files; if (selectedFiles && selectedFiles[0]) { this.pendingFile = selectedFiles[0]; - this.name.setValue(selectedFiles[0].name); + const { fileName, extension } = splitExtension(selectedFiles[0].name); + this.name.setValue(fileName); + this.extension = extension; this.showVirusError = false; } } @@ -104,6 +115,7 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { onRemoveFile() { this.pendingFile = undefined; this.existingFile = undefined; + this.extension = ''; } openFile() { @@ -118,7 +130,7 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { await this.decisionService.downloadFile( this.data.decisionUuid, this.data.existingDocument.uuid, - this.data.existingDocument.fileName + this.data.existingDocument.fileName, ); } } diff --git a/alcs-frontend/src/app/features/application/documents/document-upload-dialog/document-upload-dialog.component.html b/alcs-frontend/src/app/features/application/documents/document-upload-dialog/document-upload-dialog.component.html index 25b4adc33f..70b0887678 100644 --- a/alcs-frontend/src/app/features/application/documents/document-upload-dialog/document-upload-dialog.component.html +++ b/alcs-frontend/src/app/features/application/documents/document-upload-dialog/document-upload-dialog.component.html @@ -1,78 +1,79 @@ <div mat-dialog-title> <h4>{{ title }} Document</h4> - <span class="superseded-warning" *ngIf="showSupersededWarning" - >Superseded - Not associated with Applicant Submission in Portal</span + <span *ngIf="showSupersededWarning" class="superseded-warning" + >Superseded - Not associated with Applicant Submission in Portal</span > </div> <div mat-dialog-content> - <form class="form" [formGroup]="form"> + <form [formGroup]="form" class="form"> <div class="double"> <div> <mat-label>Document Upload*</mat-label> </div> - <input hidden type="file" #fileInput (change)="uploadFile($event)" placeholder="Upload file" /> + <input #fileInput (change)="uploadFile($event)" hidden placeholder="Upload file" type="file" /> <button + (click)="fileInput.click()" *ngIf="!pendingFile && !existingFile" - class="full-width upload-button" - mat-flat-button - color="accent" [ngClass]="{ error: showVirusError }" - (click)="fileInput.click()" + class="full-width upload-button" + color="accent" + mat-flat-button > Upload </button> - <div class="file" *ngIf="pendingFile"> + <div *ngIf="pendingFile" class="file"> <div> <a (click)="openFile()">{{ pendingFile.name }}</a>  ({{ pendingFile.size | filesize }}) </div> - <button [disabled]="!allowsFileEdit" (click)="onRemoveFile()" mat-button> + <button (click)="onRemoveFile()" [disabled]="!allowsFileEdit" mat-button> <mat-icon>close</mat-icon> Remove </button> </div> - <div class="file" *ngIf="existingFile"> + <div *ngIf="existingFile" class="file"> <div> <a (click)="openExistingFile()">{{ existingFile.name }}</a>  ({{ existingFile.size | filesize }}) </div> - <button [disabled]="!allowsFileEdit" (click)="onRemoveFile()" mat-button> + <button (click)="onRemoveFile()" [disabled]="!allowsFileEdit" mat-button> <mat-icon>close</mat-icon> Remove </button> </div> - <mat-error class="left" style="display: flex" *ngIf="showVirusError"> + <mat-error *ngIf="showVirusError" class="left" style="display: flex"> <mat-icon>warning</mat-icon> A virus was detected in the file. Choose another file and try again. </mat-error> </div> <div class="double"> - <mat-form-field class="full-width" appearance="outline"> + <mat-form-field appearance="outline" class="full-width"> <mat-label>Document Name</mat-label> - <input required matInput id="name" [formControl]="name" name="name" /> + <input [formControl]="name" id="name" matInput name="name" required /> + <span matTextSuffix>{{ extension }}</span> </mat-form-field> </div> <div> <ng-select - appearance="outline" - required + (change)="onDocTypeSelected($event)" + [(ngModel)]="documentTypeAhead" [items]="documentTypes" - placeholder="Document Type*" - bindLabel="label" - bindValue="code" [ngModelOptions]="{ standalone: true }" - [(ngModel)]="documentTypeAhead" [searchFn]="filterDocumentTypes" - (change)="onDocTypeSelected($event)" + appearance="outline" appendTo="body" + bindLabel="label" + bindValue="code" + placeholder="Document Type*" + required > </ng-select> </div> <div> - <mat-form-field class="full-width" appearance="outline"> + <mat-form-field appearance="outline" class="full-width"> <mat-label>Source</mat-label> <mat-select [formControl]="source"> <mat-option *ngFor="let source of documentSources" [value]="source">{{ source }}</mat-option> @@ -80,19 +81,19 @@ <h4>{{ title }} Document</h4> </mat-form-field> </div> <div *ngIf="type.value === DOCUMENT_TYPE.CERTIFICATE_OF_TITLE"> - <mat-form-field class="full-width" appearance="outline"> + <mat-form-field appearance="outline" class="full-width"> <mat-label>Associated Parcel</mat-label> <mat-select [formControl]="parcelId"> <mat-option *ngFor="let parcel of selectableParcels" [value]="parcel.uuid"> #{{ parcel.index + 1 }} PID: - <span *ngIf="parcel.pid">{{ parcel.pid | mask : '000-000-000' }}</span> + <span *ngIf="parcel.pid">{{ parcel.pid | mask: '000-000-000' }}</span> <span *ngIf="!parcel.pid">No Data</span></mat-option > </mat-select> </mat-form-field> </div> <div *ngIf="type.value === DOCUMENT_TYPE.CORPORATE_SUMMARY"> - <mat-form-field class="full-width" appearance="outline"> + <mat-form-field appearance="outline" class="full-width"> <mat-label>Associated Organization</mat-label> <mat-select [formControl]="ownerId"> <mat-option *ngFor="let owner of selectableOwners" [value]="owner.uuid"> @@ -114,18 +115,18 @@ <h4>{{ title }} Document</h4> <mat-dialog-actions align="end"> <div class="button-container"> - <button type="button" mat-stroked-button color="primary" [mat-dialog-close]="false">Close</button> + <button [mat-dialog-close]="false" color="primary" mat-stroked-button type="button">Close</button> <button + (click)="onSubmit()" *ngIf="!isSaving" - type="button" - mat-flat-button - color="primary" [disabled]="!form.valid || (!pendingFile && !existingFile)" - (click)="onSubmit()" + color="primary" + mat-flat-button + type="button" > Save </button> - <button *ngIf="isSaving" type="button" mat-flat-button color="primary" [disabled]="true"> + <button *ngIf="isSaving" [disabled]="true" color="primary" mat-flat-button type="button"> <mat-spinner class="spinner" diameter="20"></mat-spinner> Uploading </button> diff --git a/alcs-frontend/src/app/features/application/documents/document-upload-dialog/document-upload-dialog.component.ts b/alcs-frontend/src/app/features/application/documents/document-upload-dialog/document-upload-dialog.component.ts index 9bab97854e..14648f65ca 100644 --- a/alcs-frontend/src/app/features/application/documents/document-upload-dialog/document-upload-dialog.component.ts +++ b/alcs-frontend/src/app/features/application/documents/document-upload-dialog/document-upload-dialog.component.ts @@ -17,6 +17,7 @@ import { DOCUMENT_TYPE, DocumentTypeDto, } from '../../../../shared/document/document.dto'; +import { splitExtension } from '../../../../shared/utils/file'; @Component({ selector: 'app-document-upload-dialog', @@ -62,6 +63,7 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { existingFile: { name: string; size: number } | undefined; showSupersededWarning = false; showVirusError = false; + extension = ''; constructor( @Inject(MAT_DIALOG_DATA) @@ -88,8 +90,11 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { this.allowsFileEdit = false; this.prepareCorporateSummaryUpload(document.uuid); } + + const { fileName, extension } = splitExtension(document.fileName); + this.extension = extension; this.form.patchValue({ - name: document.fileName, + name: fileName, type: document.type?.code, source: document.source, visibleToInternal: document.visibilityFlags.includes('C') || document.visibilityFlags.includes('A'), @@ -118,7 +123,7 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { const file = this.pendingFile; const dto: UpdateDocumentDto = { - fileName: this.name.value!, + fileName: this.name.value! + this.extension, source: this.source.value as DOCUMENT_SOURCE, typeCode: this.type.value as DOCUMENT_TYPE, visibilityFlags, @@ -242,7 +247,9 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { const selectedFiles = element.files; if (selectedFiles && selectedFiles[0]) { this.pendingFile = selectedFiles[0]; - this.name.setValue(selectedFiles[0].name); + const { fileName, extension } = splitExtension(this.pendingFile.name); + this.extension = extension; + this.name.setValue(fileName); this.showVirusError = false; } } @@ -250,6 +257,7 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { onRemoveFile() { this.pendingFile = undefined; this.existingFile = undefined; + this.extension = ''; } openFile() { diff --git a/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.html b/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.html index 329efcbed6..2bf390f45c 100644 --- a/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.html +++ b/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.html @@ -2,74 +2,75 @@ <h4>{{ title }} Document</h4> </div> <div mat-dialog-content> - <form class="form" [formGroup]="form"> + <form [formGroup]="form" class="form"> <div class="double"> <div> <mat-label>Document Upload*</mat-label> </div> - <input hidden type="file" #fileInput (change)="uploadFile($event)" placeholder="Upload file" /> + <input #fileInput (change)="uploadFile($event)" hidden placeholder="Upload file" type="file" /> <button + (click)="fileInput.click()" *ngIf="!pendingFile && !existingFile" - class="full-width upload-button" - mat-flat-button - color="accent" [ngClass]="{ error: showVirusError }" - (click)="fileInput.click()" + class="full-width upload-button" + color="accent" + mat-flat-button > Upload </button> - <div class="file" *ngIf="pendingFile"> + <div *ngIf="pendingFile" class="file"> <div> <a (click)="openFile()">{{ pendingFile.name }}</a>  ({{ pendingFile.size | filesize }}) </div> - <button [disabled]="!allowsFileEdit" (click)="onRemoveFile()" mat-button> + <button (click)="onRemoveFile()" [disabled]="!allowsFileEdit" mat-button> <mat-icon>close</mat-icon> Remove </button> </div> - <div class="file" *ngIf="existingFile"> + <div *ngIf="existingFile" class="file"> <div> <a (click)="openExistingFile()">{{ existingFile.name }}</a>  ({{ existingFile.size | filesize }}) </div> - <button [disabled]="!allowsFileEdit" (click)="onRemoveFile()" mat-button> + <button (click)="onRemoveFile()" [disabled]="!allowsFileEdit" mat-button> <mat-icon>close</mat-icon> Remove </button> </div> - <mat-error class="left" style="display: flex" *ngIf="showVirusError"> + <mat-error *ngIf="showVirusError" class="left" style="display: flex"> <mat-icon>warning</mat-icon> A virus was detected in the file. Choose another file and try again. </mat-error> </div> <div class="double"> - <mat-form-field class="full-width" appearance="outline"> + <mat-form-field appearance="outline" class="full-width"> <mat-label>Document Name</mat-label> - <input required matInput id="name" [formControl]="name" name="name" /> + <input [formControl]="name" id="name" matInput name="name" required /> + <span matTextSuffix>{{ extension }}</span> </mat-form-field> </div> <div> <ng-select - appearance="outline" - required + (change)="onDocTypeSelected($event)" + [(ngModel)]="documentTypeAhead" [items]="documentTypes" - placeholder="Document Type*" - bindLabel="label" - bindValue="code" [ngModelOptions]="{ standalone: true }" - [(ngModel)]="documentTypeAhead" [searchFn]="filterDocumentTypes" - (change)="onDocTypeSelected($event)" + appearance="outline" appendTo="body" + bindLabel="label" + bindValue="code" + placeholder="Document Type*" + required > </ng-select> </div> <div> - <mat-form-field class="full-width" appearance="outline"> + <mat-form-field appearance="outline" class="full-width"> <mat-label>Source</mat-label> <mat-select [formControl]="source"> <mat-option *ngFor="let source of documentSources" [value]="source">{{ source }}</mat-option> @@ -80,18 +81,18 @@ <h4>{{ title }} Document</h4> <mat-dialog-actions align="end"> <div class="button-container"> - <button type="button" mat-stroked-button color="primary" [mat-dialog-close]="false">Close</button> + <button [mat-dialog-close]="false" color="primary" mat-stroked-button type="button">Close</button> <button + (click)="onSubmit()" *ngIf="!isSaving" - type="button" - mat-flat-button - color="primary" [disabled]="!form.valid || (!pendingFile && !existingFile)" - (click)="onSubmit()" + color="primary" + mat-flat-button + type="button" > Save </button> - <button *ngIf="isSaving" type="button" mat-flat-button color="primary" [disabled]="true"> + <button *ngIf="isSaving" [disabled]="true" color="primary" mat-flat-button type="button"> <mat-spinner class="spinner" diameter="20"></mat-spinner> Uploading </button> diff --git a/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.ts b/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.ts index 7c980dd6e9..b312dfb09c 100644 --- a/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.ts +++ b/alcs-frontend/src/app/features/inquiry/documents/document-upload-dialog/document-upload-dialog.component.ts @@ -15,6 +15,7 @@ import { DOCUMENT_TYPE, DocumentTypeDto, } from '../../../../shared/document/document.dto'; +import { splitExtension } from '../../../../shared/utils/file'; @Component({ selector: 'app-document-upload-dialog', @@ -47,6 +48,7 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { pendingFile: File | undefined; existingFile: { name: string; size: number } | undefined; showVirusError = false; + extension = ''; constructor( @Inject(MAT_DIALOG_DATA) @@ -63,12 +65,16 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { const document = this.data.existingDocument; this.title = 'Edit'; this.allowsFileEdit = document.system === DOCUMENT_SYSTEM.ALCS; + const { fileName, extension } = splitExtension(document.fileName); + this.extension = extension; + this.form.patchValue({ - name: document.fileName, + name: fileName, type: document.type?.code, source: document.source, }); this.documentTypeAhead = document.type!.code; + this.existingFile = { name: document.fileName, size: 0, @@ -79,7 +85,7 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { async onSubmit() { const file = this.pendingFile; const dto: UpdateDocumentDto = { - fileName: this.name.value!, + fileName: this.name.value! + this.extension, source: this.source.value as DOCUMENT_SOURCE, typeCode: this.type.value as DOCUMENT_TYPE, file, @@ -136,7 +142,11 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { const selectedFiles = element.files; if (selectedFiles && selectedFiles[0]) { this.pendingFile = selectedFiles[0]; - this.name.setValue(selectedFiles[0].name); + + const documentName = selectedFiles[0].name; + const { fileName, extension } = splitExtension(documentName); + this.name.setValue(fileName); + this.extension = extension; this.showVirusError = false; } } @@ -144,6 +154,7 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { onRemoveFile() { this.pendingFile = undefined; this.existingFile = undefined; + this.extension = ''; } openFile() { diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html index c800316e76..645cfe598c 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html @@ -2,66 +2,69 @@ <h4>{{ title }} Document</h4> </div> <div mat-dialog-content> - <form class="form" [formGroup]="form"> + <form [formGroup]="form" class="form"> <div class="double"> <div> <mat-label>Document Upload*</mat-label> </div> - <input hidden type="file" #fileInput (change)="uploadFile($event)" placeholder="Upload file" /> + <input #fileInput (change)="uploadFile($event)" hidden placeholder="Upload file" type="file" /> <button + (click)="fileInput.click()" *ngIf="!pendingFile && !existingFile" - class="full-width upload-button" - mat-flat-button - color="accent" [ngClass]="{ error: showVirusError }" - (click)="fileInput.click()" + class="full-width upload-button" + color="accent" + mat-flat-button > Upload </button> - <div class="file" *ngIf="pendingFile"> + <div *ngIf="pendingFile" class="file"> <div> <a (click)="openFile()">{{ pendingFile.name }}</a>  ({{ pendingFile.size | filesize }}) </div> - <button [disabled]="!allowsFileEdit" (click)="onRemoveFile()" mat-button> - <mat-icon>close</mat-icon>Remove + <button (click)="onRemoveFile()" [disabled]="!allowsFileEdit" mat-button> + <mat-icon>close</mat-icon> + Remove </button> </div> - <div class="file" *ngIf="existingFile"> + <div *ngIf="existingFile" class="file"> <div> <a (click)="openExistingFile()">{{ existingFile }}</a> </div> - <button [disabled]="!allowsFileEdit" (click)="onRemoveFile()" mat-button> - <mat-icon>close</mat-icon>Remove + <button (click)="onRemoveFile()" [disabled]="!allowsFileEdit" mat-button> + <mat-icon>close</mat-icon> + Remove </button> </div> - <mat-error class="left" style="display: flex" *ngIf="showVirusError"> + <mat-error *ngIf="showVirusError" class="left" style="display: flex"> <mat-icon>warning</mat-icon> A virus was detected in the file. Choose another file and try again. </mat-error> </div> <div class="double"> - <mat-form-field class="full-width" appearance="outline"> + <mat-form-field appearance="outline" class="full-width"> <mat-label>Document Name</mat-label> - <input required matInput id="name" [formControl]="name" name="name" /> + <input [formControl]="name" id="name" matInput name="name" required /> + <span matTextSuffix>{{ extension }}</span> </mat-form-field> </div> <div> <ng-select - appearance="outline" - required - placeholder="Document Type*" - [ngModelOptions]="{ standalone: true }" [(ngModel)]="documentType" + [ngModelOptions]="{ standalone: true }" + appearance="outline" disabled="true" + placeholder="Document Type*" + required > </ng-select> </div> <div> - <mat-form-field class="full-width" appearance="outline"> + <mat-form-field appearance="outline" class="full-width"> <mat-label>Source</mat-label> <mat-select [formControl]="source"> <mat-option *ngFor="let source of documentSources" [value]="source">{{ source }}</mat-option> @@ -81,18 +84,18 @@ <h4>{{ title }} Document</h4> <mat-dialog-actions align="end"> <div class="button-container"> - <button type="button" mat-stroked-button color="primary" [mat-dialog-close]="false">Close</button> + <button [mat-dialog-close]="false" color="primary" mat-stroked-button type="button">Close</button> <button + (click)="onSubmit()" *ngIf="!isSaving" - type="button" - mat-flat-button - color="primary" [disabled]="!form.valid || (!pendingFile && !existingFile)" - (click)="onSubmit()" + color="primary" + mat-flat-button + type="button" > Save </button> - <button *ngIf="isSaving" type="button" mat-flat-button color="primary" [disabled]="true"> + <button *ngIf="isSaving" [disabled]="true" color="primary" mat-flat-button type="button"> <mat-spinner class="spinner" diameter="20"></mat-spinner> Uploading </button> diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts index cf968253e6..d0a844876d 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts @@ -6,6 +6,7 @@ import { NoticeOfIntentDecisionV2Service } from '../../../../../../services/noti import { NoticeOfIntentDecisionDocumentDto } from '../../../../../../services/notice-of-intent/decision/notice-of-intent-decision.dto'; import { ToastService } from '../../../../../../services/toast/toast.service'; import { DOCUMENT_SOURCE } from '../../../../../../shared/document/document.dto'; +import { splitExtension } from '../../../../../../shared/utils/file'; @Component({ selector: 'app-noi-decision-document-upload-dialog', @@ -39,6 +40,7 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { pendingFile: File | undefined; existingFile: string | undefined; showVirusError = false; + extension = ''; constructor( @Inject(MAT_DIALOG_DATA) @@ -52,8 +54,10 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { if (this.data.existingDocument) { const document = this.data.existingDocument; this.title = 'Edit'; + const { fileName, extension } = splitExtension(document.fileName); + this.extension = extension; this.form.patchValue({ - name: document.fileName, + name: fileName, }); this.existingFile = document.fileName; } @@ -62,7 +66,7 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { async onSubmit() { const file = this.pendingFile; if (file) { - const renamedFile = new File([file], this.name.value ?? file.name); + const renamedFile = new File([file], this.name.value + this.extension ?? file.name); this.isSaving = true; if (this.data.existingDocument) { await this.decisionService.deleteFile(this.data.decisionUuid, this.data.existingDocument.uuid); @@ -84,7 +88,11 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { this.isSaving = false; } else if (this.data.existingDocument) { this.isSaving = true; - await this.decisionService.updateFile(this.data.decisionUuid, this.data.existingDocument.uuid, this.name.value!); + await this.decisionService.updateFile( + this.data.decisionUuid, + this.data.existingDocument.uuid, + this.name.value! + this.extension, + ); this.dialog.close(true); this.isSaving = false; @@ -96,7 +104,10 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { const selectedFiles = element.files; if (selectedFiles && selectedFiles[0]) { this.pendingFile = selectedFiles[0]; - this.name.setValue(selectedFiles[0].name); + + const { fileName, extension } = splitExtension(this.pendingFile.name); + this.name.setValue(fileName); + this.extension = extension; this.showVirusError = false; } } @@ -104,6 +115,7 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { onRemoveFile() { this.pendingFile = undefined; this.existingFile = undefined; + this.extension = ''; } openFile() { diff --git a/alcs-frontend/src/app/features/notice-of-intent/documents/document-upload-dialog/document-upload-dialog.component.html b/alcs-frontend/src/app/features/notice-of-intent/documents/document-upload-dialog/document-upload-dialog.component.html index 2bbe634011..220c3f6f85 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/documents/document-upload-dialog/document-upload-dialog.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/documents/document-upload-dialog/document-upload-dialog.component.html @@ -1,76 +1,79 @@ <div mat-dialog-title> <h4>{{ title }} Document</h4> - <span class="superseded-warning" *ngIf="showSupersededWarning" - >Superseded - Not associated with Applicant Submission in Portal</span + <span *ngIf="showSupersededWarning" class="superseded-warning" + >Superseded - Not associated with Applicant Submission in Portal</span > </div> <div mat-dialog-content> - <form class="form" [formGroup]="form"> + <form [formGroup]="form" class="form"> <div class="double"> <div> <mat-label>Document Upload*</mat-label> </div> - <input hidden type="file" #fileInput (change)="uploadFile($event)" placeholder="Upload file" /> + <input #fileInput (change)="uploadFile($event)" hidden placeholder="Upload file" type="file" /> <button + (click)="fileInput.click()" *ngIf="!pendingFile && !existingFile" - class="full-width upload-button" - mat-flat-button - color="accent" [ngClass]="{ error: showVirusError }" - (click)="fileInput.click()" + class="full-width upload-button" + color="accent" + mat-flat-button > Upload </button> - <div class="file" *ngIf="pendingFile"> + <div *ngIf="pendingFile" class="file"> <div> <a (click)="openFile()">{{ pendingFile.name }}</a>  ({{ pendingFile.size | filesize }}) </div> - <button [disabled]="!allowsFileEdit" (click)="onRemoveFile()" mat-button> - <mat-icon>close</mat-icon>Remove + <button (click)="onRemoveFile()" [disabled]="!allowsFileEdit" mat-button> + <mat-icon>close</mat-icon> + Remove </button> </div> - <div class="file" *ngIf="existingFile"> + <div *ngIf="existingFile" class="file"> <div> <a (click)="openExistingFile()">{{ existingFile.name }}</a>  ({{ existingFile.size | filesize }}) </div> - <button [disabled]="!allowsFileEdit" (click)="onRemoveFile()" mat-button> - <mat-icon>close</mat-icon>Remove + <button (click)="onRemoveFile()" [disabled]="!allowsFileEdit" mat-button> + <mat-icon>close</mat-icon> + Remove </button> </div> - <mat-error class="left" style="display: flex" *ngIf="showVirusError"> + <mat-error *ngIf="showVirusError" class="left" style="display: flex"> <mat-icon>warning</mat-icon> A virus was detected in the file. Choose another file and try again. </mat-error> </div> <div class="double"> - <mat-form-field class="full-width" appearance="outline"> + <mat-form-field appearance="outline" class="full-width"> <mat-label>Document Name</mat-label> - <input required matInput id="name" [formControl]="name" name="name" /> + <input [formControl]="name" id="name" matInput name="name" required /> + <span matTextSuffix>{{ this.extension }}</span> </mat-form-field> </div> <div> <ng-select - appearance="outline" - required + (change)="onDocTypeSelected($event)" + [(ngModel)]="documentTypeAhead" [items]="documentTypes" - placeholder="Document Type*" - bindLabel="label" - bindValue="code" [ngModelOptions]="{ standalone: true }" - [(ngModel)]="documentTypeAhead" [searchFn]="filterDocumentTypes" - (change)="onDocTypeSelected($event)" + appearance="outline" appendTo="body" + bindLabel="label" + bindValue="code" + placeholder="Document Type*" + required > </ng-select> </div> <div> - <mat-form-field class="full-width" appearance="outline"> + <mat-form-field appearance="outline" class="full-width"> <mat-label>Source</mat-label> <mat-select [formControl]="source"> <mat-option *ngFor="let source of documentSources" [value]="source">{{ source }}</mat-option> @@ -78,19 +81,19 @@ <h4>{{ title }} Document</h4> </mat-form-field> </div> <div *ngIf="type.value === DOCUMENT_TYPE.CERTIFICATE_OF_TITLE"> - <mat-form-field class="full-width" appearance="outline"> + <mat-form-field appearance="outline" class="full-width"> <mat-label>Associated Parcel</mat-label> <mat-select [formControl]="parcelId"> <mat-option *ngFor="let parcel of selectableParcels" [value]="parcel.uuid"> #{{ parcel.index + 1 }} PID: - <span *ngIf="parcel.pid">{{ parcel.pid | mask : '000-000-000' }}</span> + <span *ngIf="parcel.pid">{{ parcel.pid | mask: '000-000-000' }}</span> <span *ngIf="!parcel.pid">No Data</span></mat-option > </mat-select> </mat-form-field> </div> <div *ngIf="type.value === DOCUMENT_TYPE.CORPORATE_SUMMARY"> - <mat-form-field class="full-width" appearance="outline"> + <mat-form-field appearance="outline" class="full-width"> <mat-label>Associated Organization</mat-label> <mat-select [formControl]="ownerId"> <mat-option *ngFor="let owner of selectableOwners" [value]="owner.uuid"> @@ -112,18 +115,18 @@ <h4>{{ title }} Document</h4> <mat-dialog-actions align="end"> <div class="button-container"> - <button type="button" mat-stroked-button color="primary" [mat-dialog-close]="false">Close</button> + <button [mat-dialog-close]="false" color="primary" mat-stroked-button type="button">Close</button> <button + (click)="onSubmit()" *ngIf="!isSaving" - type="button" - mat-flat-button - color="primary" [disabled]="!form.valid || (!pendingFile && !existingFile)" - (click)="onSubmit()" + color="primary" + mat-flat-button + type="button" > Save </button> - <button *ngIf="isSaving" type="button" mat-flat-button color="primary" [disabled]="true"> + <button *ngIf="isSaving" [disabled]="true" color="primary" mat-flat-button type="button"> <mat-spinner class="spinner" diameter="20"></mat-spinner> Uploading </button> diff --git a/alcs-frontend/src/app/features/notice-of-intent/documents/document-upload-dialog/document-upload-dialog.component.ts b/alcs-frontend/src/app/features/notice-of-intent/documents/document-upload-dialog/document-upload-dialog.component.ts index 4aac473798..2a32cbf38a 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/documents/document-upload-dialog/document-upload-dialog.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/documents/document-upload-dialog/document-upload-dialog.component.ts @@ -3,6 +3,11 @@ 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 { Subject } from 'rxjs'; +import { + NoticeOfIntentDocumentDto, + UpdateNoticeOfIntentDocumentDto, +} from '../../../../services/notice-of-intent/noi-document/noi-document.dto'; +import { NoiDocumentService } from '../../../../services/notice-of-intent/noi-document/noi-document.service'; import { NoticeOfIntentParcelService } from '../../../../services/notice-of-intent/notice-of-intent-parcel/notice-of-intent-parcel.service'; import { NoticeOfIntentSubmissionService } from '../../../../services/notice-of-intent/notice-of-intent-submission/notice-of-intent-submission.service'; import { ToastService } from '../../../../services/toast/toast.service'; @@ -12,11 +17,7 @@ import { DOCUMENT_TYPE, DocumentTypeDto, } from '../../../../shared/document/document.dto'; -import { - NoticeOfIntentDocumentDto, - UpdateNoticeOfIntentDocumentDto, -} from '../../../../services/notice-of-intent/noi-document/noi-document.dto'; -import { NoiDocumentService } from '../../../../services/notice-of-intent/noi-document/noi-document.service'; +import { splitExtension } from '../../../../shared/utils/file'; @Component({ selector: 'app-document-upload-dialog', @@ -62,6 +63,7 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { existingFile: { name: string; size: number } | undefined; showSupersededWarning = false; showVirusError = false; + extension = ''; constructor( @Inject(MAT_DIALOG_DATA) @@ -90,8 +92,10 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { this.prepareCorporateSummaryUpload(document.uuid); } + const { fileName, extension } = splitExtension(document.fileName); + this.extension = extension; this.form.patchValue({ - name: document.fileName, + name: fileName, type: document.type?.code, source: document.source, visibleToInternal: document.visibilityFlags.includes('A'), @@ -119,7 +123,7 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { const file = this.pendingFile; const dto: UpdateNoticeOfIntentDocumentDto = { - fileName: this.name.value!, + fileName: this.name.value! + this.extension, source: this.source.value as DOCUMENT_SOURCE, typeCode: this.type.value as DOCUMENT_TYPE, visibilityFlags, @@ -195,13 +199,16 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { const selectedFiles = element.files; if (selectedFiles && selectedFiles[0]) { this.pendingFile = selectedFiles[0]; - this.name.setValue(selectedFiles[0].name); + const { fileName, extension } = splitExtension(this.pendingFile.name); + this.name.setValue(fileName); + this.extension = extension; } } onRemoveFile() { this.pendingFile = undefined; this.existingFile = undefined; + this.extension = ''; } openFile() { diff --git a/alcs-frontend/src/app/features/notification/documents/document-upload-dialog/document-upload-dialog.component.html b/alcs-frontend/src/app/features/notification/documents/document-upload-dialog/document-upload-dialog.component.html index 2ac5d3f661..e7847c3adb 100644 --- a/alcs-frontend/src/app/features/notification/documents/document-upload-dialog/document-upload-dialog.component.html +++ b/alcs-frontend/src/app/features/notification/documents/document-upload-dialog/document-upload-dialog.component.html @@ -2,72 +2,75 @@ <h4>{{ title }} Document</h4> </div> <div mat-dialog-content> - <form class="form" [formGroup]="form"> + <form [formGroup]="form" class="form"> <div class="double"> <div> <mat-label>Document Upload*</mat-label> </div> - <input hidden type="file" #fileInput (change)="uploadFile($event)" placeholder="Upload file" /> + <input #fileInput (change)="uploadFile($event)" hidden placeholder="Upload file" type="file" /> <button + (click)="fileInput.click()" *ngIf="!pendingFile && !existingFile" - class="full-width upload-button" - mat-flat-button - color="accent" [ngClass]="{ error: showVirusError }" - (click)="fileInput.click()" + class="full-width upload-button" + color="accent" + mat-flat-button > Upload </button> - <div class="file" *ngIf="pendingFile"> + <div *ngIf="pendingFile" class="file"> <div> <a (click)="openFile()">{{ pendingFile.name }}</a>  ({{ pendingFile.size | filesize }}) </div> - <button [disabled]="!allowsFileEdit" (click)="onRemoveFile()" mat-button> - <mat-icon>close</mat-icon>Remove + <button (click)="onRemoveFile()" [disabled]="!allowsFileEdit" mat-button> + <mat-icon>close</mat-icon> + Remove </button> </div> - <div class="file" *ngIf="existingFile"> + <div *ngIf="existingFile" class="file"> <div> <a (click)="openExistingFile()">{{ existingFile.name }}</a>  ({{ existingFile.size | filesize }}) </div> - <button [disabled]="!allowsFileEdit" (click)="onRemoveFile()" mat-button> - <mat-icon>close</mat-icon>Remove + <button (click)="onRemoveFile()" [disabled]="!allowsFileEdit" mat-button> + <mat-icon>close</mat-icon> + Remove </button> </div> - <mat-error class="left" style="display: flex" *ngIf="showVirusError"> + <mat-error *ngIf="showVirusError" class="left" style="display: flex"> <mat-icon>warning</mat-icon> A virus was detected in the file. Choose another file and try again. </mat-error> </div> <div class="double"> - <mat-form-field class="full-width" appearance="outline"> + <mat-form-field appearance="outline" class="full-width"> <mat-label>Document Name</mat-label> - <input required matInput id="name" [formControl]="name" name="name" /> + <input [formControl]="name" id="name" matInput name="name" required /> + <span matTextSuffix>{{ extension }}</span> </mat-form-field> </div> <div> <ng-select - appearance="outline" - required + (change)="onDocTypeSelected($event)" + [(ngModel)]="documentTypeAhead" [items]="documentTypes" - placeholder="Document Type*" - bindLabel="label" - bindValue="code" [ngModelOptions]="{ standalone: true }" - [(ngModel)]="documentTypeAhead" [searchFn]="filterDocumentTypes" - (change)="onDocTypeSelected($event)" + appearance="outline" appendTo="body" + bindLabel="label" + bindValue="code" + placeholder="Document Type*" + required > </ng-select> </div> <div> - <mat-form-field class="full-width" appearance="outline"> + <mat-form-field appearance="outline" class="full-width"> <mat-label>Source</mat-label> <mat-select [formControl]="source"> <mat-option *ngFor="let source of documentSources" [value]="source">{{ source }}</mat-option> @@ -87,18 +90,18 @@ <h4>{{ title }} Document</h4> <mat-dialog-actions align="end"> <div class="button-container"> - <button type="button" mat-stroked-button color="primary" [mat-dialog-close]="false">Close</button> + <button [mat-dialog-close]="false" color="primary" mat-stroked-button type="button">Close</button> <button + (click)="onSubmit()" *ngIf="!isSaving" - type="button" - mat-flat-button - color="primary" [disabled]="!form.valid || (!pendingFile && !existingFile)" - (click)="onSubmit()" + color="primary" + mat-flat-button + type="button" > Save </button> - <button *ngIf="isSaving" type="button" mat-flat-button color="primary" [disabled]="true"> + <button *ngIf="isSaving" [disabled]="true" color="primary" mat-flat-button type="button"> <mat-spinner class="spinner" diameter="20"></mat-spinner> Uploading </button> diff --git a/alcs-frontend/src/app/features/notification/documents/document-upload-dialog/document-upload-dialog.component.ts b/alcs-frontend/src/app/features/notification/documents/document-upload-dialog/document-upload-dialog.component.ts index 94489d8ebb..816419dead 100644 --- a/alcs-frontend/src/app/features/notification/documents/document-upload-dialog/document-upload-dialog.component.ts +++ b/alcs-frontend/src/app/features/notification/documents/document-upload-dialog/document-upload-dialog.component.ts @@ -13,6 +13,7 @@ import { DOCUMENT_TYPE, DocumentTypeDto, } from '../../../../shared/document/document.dto'; +import { splitExtension } from '../../../../shared/utils/file'; @Component({ selector: 'app-document-upload-dialog', @@ -50,6 +51,7 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { pendingFile: File | undefined; existingFile: { name: string; size: number } | undefined; showVirusError = false; + extension = ''; constructor( @Inject(MAT_DIALOG_DATA) @@ -67,8 +69,10 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { this.title = 'Edit'; this.allowsFileEdit = document.system === DOCUMENT_SYSTEM.ALCS; + const { fileName, extension } = splitExtension(document.fileName); + this.extension = extension; this.form.patchValue({ - name: document.fileName, + name: fileName, type: document.type?.code, source: document.source, visibleToInternal: document.visibilityFlags.includes('A'), @@ -96,7 +100,7 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { const file = this.pendingFile; const dto: UpdateNoticeOfIntentDocumentDto = { - fileName: this.name.value!, + fileName: this.name.value! + this.extension, source: this.source.value as DOCUMENT_SOURCE, typeCode: this.type.value as DOCUMENT_TYPE, visibilityFlags, @@ -151,14 +155,17 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { const element = event.target as HTMLInputElement; const selectedFiles = element.files; if (selectedFiles && selectedFiles[0]) { + const { fileName, extension } = splitExtension(selectedFiles[0].name); this.pendingFile = selectedFiles[0]; - this.name.setValue(selectedFiles[0].name); + this.name.setValue(fileName); + this.extension = extension; } } onRemoveFile() { this.pendingFile = undefined; this.existingFile = undefined; + this.extension = ''; } openFile() { diff --git a/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html b/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html index 2cbdbbadfa..0b3e7ea42a 100644 --- a/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html +++ b/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.html @@ -2,66 +2,69 @@ <h4>{{ title }} Document</h4> </div> <div mat-dialog-content> - <form class="form" [formGroup]="form"> + <form [formGroup]="form" class="form"> <div class="double"> <div> <mat-label>Document Upload*</mat-label> </div> - <input hidden type="file" #fileInput (change)="uploadFile($event)" placeholder="Upload file" /> + <input #fileInput (change)="uploadFile($event)" hidden placeholder="Upload file" type="file" /> <button + (click)="fileInput.click()" *ngIf="!pendingFile && !existingFile" - class="full-width upload-button" - mat-flat-button - color="accent" [ngClass]="{ error: showVirusError }" - (click)="fileInput.click()" + class="full-width upload-button" + color="accent" + mat-flat-button > Upload </button> - <div class="file" *ngIf="pendingFile"> + <div *ngIf="pendingFile" class="file"> <div> <a (click)="openFile()">{{ pendingFile.name }}</a>  ({{ pendingFile.size | filesize }}) </div> - <button [disabled]="!allowsFileEdit" (click)="onRemoveFile()" mat-button> - <mat-icon>close</mat-icon>Remove + <button (click)="onRemoveFile()" [disabled]="!allowsFileEdit" mat-button> + <mat-icon>close</mat-icon> + Remove </button> </div> - <div class="file" *ngIf="existingFile"> + <div *ngIf="existingFile" class="file"> <div> <a (click)="openExistingFile()">{{ existingFile }}</a> </div> - <button [disabled]="!allowsFileEdit" (click)="onRemoveFile()" mat-button> - <mat-icon>close</mat-icon>Remove + <button (click)="onRemoveFile()" [disabled]="!allowsFileEdit" mat-button> + <mat-icon>close</mat-icon> + Remove </button> </div> - <mat-error class="left" style="display: flex" *ngIf="showVirusError"> + <mat-error *ngIf="showVirusError" class="left" style="display: flex"> <mat-icon>warning</mat-icon> A virus was detected in the file. Choose another file and try again. </mat-error> </div> <div class="double"> - <mat-form-field class="full-width" appearance="outline"> + <mat-form-field appearance="outline" class="full-width"> <mat-label>Document Name</mat-label> - <input required matInput id="name" [formControl]="name" name="name" /> + <input [formControl]="name" id="name" matInput name="name" required /> + <span matTextSuffix>{{ extension }}</span> </mat-form-field> </div> <div> <ng-select - appearance="outline" - required - placeholder="Document Type*" - [ngModelOptions]="{ standalone: true }" [(ngModel)]="documentType" + [ngModelOptions]="{ standalone: true }" + appearance="outline" disabled="true" + placeholder="Document Type*" + required > </ng-select> </div> <div> - <mat-form-field class="full-width" appearance="outline"> + <mat-form-field appearance="outline" class="full-width"> <mat-label>Source</mat-label> <mat-select [formControl]="source"> <mat-option *ngFor="let source of documentSources" [value]="source">{{ source }}</mat-option> @@ -71,25 +74,25 @@ <h4>{{ title }} Document</h4> <div class="double"> <mat-label>Visible To:</mat-label> <div> - <mat-checkbox [formControl]="visibleToComissioner">Commissioner</mat-checkbox> + <mat-checkbox [formControl]="visibleToCommissioner">Commissioner</mat-checkbox> </div> </div> </form> <mat-dialog-actions align="end"> <div class="button-container"> - <button type="button" mat-stroked-button color="primary" [mat-dialog-close]="false">Close</button> + <button [mat-dialog-close]="false" color="primary" mat-stroked-button type="button">Close</button> <button + (click)="onSubmit()" *ngIf="!isSaving" - type="button" - mat-flat-button - color="primary" [disabled]="!form.valid || (!pendingFile && !existingFile)" - (click)="onSubmit()" + color="primary" + mat-flat-button + type="button" > Save </button> - <button *ngIf="isSaving" type="button" mat-flat-button color="primary" [disabled]="true"> + <button *ngIf="isSaving" [disabled]="true" color="primary" mat-flat-button type="button"> <mat-spinner class="spinner" diameter="20"></mat-spinner> Uploading </button> diff --git a/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts b/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts index d58cd35f4f..605894007e 100644 --- a/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts +++ b/alcs-frontend/src/app/features/planning-review/decision/decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component.ts @@ -6,6 +6,7 @@ import { PlanningReviewDecisionDocumentDto } from '../../../../../services/plann import { PlanningReviewDecisionService } from '../../../../../services/planning-review/planning-review-decision/planning-review-decision.service'; import { ToastService } from '../../../../../services/toast/toast.service'; import { DOCUMENT_SOURCE } from '../../../../../shared/document/document.dto'; +import { splitExtension } from '../../../../../shared/utils/file'; @Component({ selector: 'app-app-decision-document-upload-dialog', @@ -22,7 +23,7 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { name = new FormControl<string>('', [Validators.required]); type = new FormControl<string | undefined>({ disabled: true, value: undefined }, [Validators.required]); source = new FormControl<string>({ disabled: true, value: DOCUMENT_SOURCE.ALC }, [Validators.required]); - visibleToComissioner = new FormControl<boolean>({ disabled: true, value: true }, [Validators.required]); + visibleToCommissioner = new FormControl<boolean>({ disabled: true, value: true }, [Validators.required]); documentSources = Object.values(DOCUMENT_SOURCE); @@ -30,12 +31,13 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { name: this.name, type: this.type, source: this.source, - visibleToComissioner: this.visibleToComissioner, + visibleToCommissioner: this.visibleToCommissioner, }); pendingFile: File | undefined; existingFile: string | undefined; showVirusError = false; + extension = ''; constructor( @Inject(MAT_DIALOG_DATA) @@ -49,8 +51,10 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { if (this.data.existingDocument) { const document = this.data.existingDocument; this.title = 'Edit'; + const { fileName, extension } = splitExtension(document.fileName); + this.extension = extension; this.form.patchValue({ - name: document.fileName, + name: fileName, }); this.existingFile = document.fileName; } @@ -59,7 +63,7 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { async onSubmit() { const file = this.pendingFile; if (file) { - const renamedFile = new File([file], this.name.value ?? file.name); + const renamedFile = new File([file], this.name.value + this.extension ?? file.name); this.isSaving = true; if (this.data.existingDocument) { await this.decisionService.deleteFile(this.data.decisionUuid, this.data.existingDocument.uuid); @@ -81,7 +85,11 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { this.isSaving = false; } else if (this.data.existingDocument) { this.isSaving = true; - await this.decisionService.updateFile(this.data.decisionUuid, this.data.existingDocument.uuid, this.name.value!); + await this.decisionService.updateFile( + this.data.decisionUuid, + this.data.existingDocument.uuid, + this.name.value! + this.extension, + ); this.dialog.close(true); this.isSaving = false; @@ -93,7 +101,10 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { const selectedFiles = element.files; if (selectedFiles && selectedFiles[0]) { this.pendingFile = selectedFiles[0]; - this.name.setValue(selectedFiles[0].name); + + const { fileName, extension } = splitExtension(selectedFiles[0].name); + this.extension = extension; + this.name.setValue(fileName); this.showVirusError = false; } } @@ -101,6 +112,7 @@ export class DecisionDocumentUploadDialogComponent implements OnInit { onRemoveFile() { this.pendingFile = undefined; this.existingFile = undefined; + this.extension = ''; } openFile() { diff --git a/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.html b/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.html index 2f5ff97df7..52f5c1a2d4 100644 --- a/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.html +++ b/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.html @@ -2,25 +2,25 @@ <h4>{{ title }} Document</h4> </div> <div mat-dialog-content> - <form class="form" [formGroup]="form"> + <form [formGroup]="form" class="form"> <div class="double"> <div> <mat-label>Document Upload*</mat-label> </div> - <input hidden type="file" #fileInput (change)="uploadFile($event)" placeholder="Upload file" /> + <input #fileInput (change)="uploadFile($event)" hidden placeholder="Upload file" type="file" /> <button + (click)="fileInput.click()" *ngIf="!pendingFile && !existingFile" - class="full-width upload-button" - mat-flat-button - color="accent" [ngClass]="{ error: showVirusError }" - (click)="fileInput.click()" + class="full-width upload-button" + color="accent" + mat-flat-button > Upload </button> - <div class="file" *ngIf="pendingFile"> + <div *ngIf="pendingFile" class="file"> <div> <a (click)="openFile()">{{ pendingFile.name }}</a>  ({{ pendingFile.size | filesize }}) @@ -30,7 +30,7 @@ <h4>{{ title }} Document</h4> Remove </button> </div> - <div class="file" *ngIf="existingFile"> + <div *ngIf="existingFile" class="file"> <div> <a (click)="openExistingFile()">{{ existingFile.name }}</a>  ({{ existingFile.size | filesize }}) @@ -40,36 +40,37 @@ <h4>{{ title }} Document</h4> Remove </button> </div> - <mat-error class="left" style="display: flex" *ngIf="showVirusError"> + <mat-error *ngIf="showVirusError" class="left" style="display: flex"> <mat-icon>warning</mat-icon> A virus was detected in the file. Choose another file and try again. </mat-error> </div> <div class="double"> - <mat-form-field class="full-width" appearance="outline"> + <mat-form-field appearance="outline" class="full-width"> <mat-label>Document Name</mat-label> - <input required matInput id="name" [formControl]="name" name="name" /> + <input [formControl]="name" id="name" matInput name="name" required /> + <span matTextSuffix>{{ extension }}</span> </mat-form-field> </div> <div> <ng-select - appearance="outline" - required + (change)="onDocTypeSelected($event)" + [(ngModel)]="documentTypeAhead" [items]="documentTypes" - placeholder="Document Type*" - bindLabel="label" - bindValue="code" [ngModelOptions]="{ standalone: true }" - [(ngModel)]="documentTypeAhead" [searchFn]="filterDocumentTypes" - (change)="onDocTypeSelected($event)" + appearance="outline" appendTo="body" + bindLabel="label" + bindValue="code" + placeholder="Document Type*" + required > </ng-select> </div> <div> - <mat-form-field class="full-width" appearance="outline"> + <mat-form-field appearance="outline" class="full-width"> <mat-label>Source</mat-label> <mat-select [formControl]="source"> <mat-option *ngFor="let source of documentSources" [value]="source">{{ source }}</mat-option> @@ -86,18 +87,18 @@ <h4>{{ title }} Document</h4> <mat-dialog-actions align="end"> <div class="button-container"> - <button type="button" mat-stroked-button color="primary" [mat-dialog-close]="false">Close</button> + <button [mat-dialog-close]="false" color="primary" mat-stroked-button type="button">Close</button> <button + (click)="onSubmit()" *ngIf="!isSaving" - type="button" - mat-flat-button - color="primary" [disabled]="!form.valid || (!pendingFile && !existingFile)" - (click)="onSubmit()" + color="primary" + mat-flat-button + type="button" > Save </button> - <button *ngIf="isSaving" type="button" mat-flat-button color="primary" [disabled]="true"> + <button *ngIf="isSaving" [disabled]="true" color="primary" mat-flat-button type="button"> <mat-spinner class="spinner" diameter="20"></mat-spinner> Uploading </button> diff --git a/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.ts b/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.ts index 284a8a0ea6..986784e9df 100644 --- a/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.ts +++ b/alcs-frontend/src/app/features/planning-review/documents/document-upload-dialog/document-upload-dialog.component.ts @@ -9,12 +9,8 @@ import { } from '../../../../services/planning-review/planning-review-document/planning-review-document.dto'; import { PlanningReviewDocumentService } from '../../../../services/planning-review/planning-review-document/planning-review-document.service'; import { ToastService } from '../../../../services/toast/toast.service'; -import { - DOCUMENT_SOURCE, - DOCUMENT_SYSTEM, - DOCUMENT_TYPE, - DocumentTypeDto, -} from '../../../../shared/document/document.dto'; +import { DOCUMENT_SOURCE, DOCUMENT_TYPE, DocumentTypeDto } from '../../../../shared/document/document.dto'; +import { splitExtension } from '../../../../shared/utils/file'; @Component({ selector: 'app-document-upload-dialog', @@ -48,6 +44,7 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { pendingFile: File | undefined; existingFile: { name: string; size: number } | undefined; showVirusError = false; + extension = ''; constructor( @Inject(MAT_DIALOG_DATA) @@ -63,8 +60,11 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { if (this.data.existingDocument) { const document = this.data.existingDocument; this.title = 'Edit'; + const { fileName, extension } = splitExtension(document.fileName); + this.extension = extension; + this.form.patchValue({ - name: document.fileName, + name: fileName, type: document.type?.code, source: document.source, visibleToCommissioner: document.visibilityFlags.includes('C'), @@ -86,7 +86,7 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { const file = this.pendingFile; const dto: UpdateDocumentDto = { - fileName: this.name.value!, + fileName: this.name.value! + this.extension, source: this.source.value as DOCUMENT_SOURCE, typeCode: this.type.value as DOCUMENT_TYPE, visibilityFlags, @@ -144,7 +144,9 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { const selectedFiles = element.files; if (selectedFiles && selectedFiles[0]) { this.pendingFile = selectedFiles[0]; - this.name.setValue(selectedFiles[0].name); + const { fileName, extension } = splitExtension(this.pendingFile.name); + this.extension = extension; + this.name.setValue(fileName); this.showVirusError = false; } } @@ -152,6 +154,7 @@ export class DocumentUploadDialogComponent implements OnInit, OnDestroy { onRemoveFile() { this.pendingFile = undefined; this.existingFile = undefined; + this.extension = ''; } openFile() { diff --git a/alcs-frontend/src/app/services/inquiry/inquiry-document/inquiry-document.service.ts b/alcs-frontend/src/app/services/inquiry/inquiry-document/inquiry-document.service.ts index 1b05959458..080d26d45e 100644 --- a/alcs-frontend/src/app/services/inquiry/inquiry-document/inquiry-document.service.ts +++ b/alcs-frontend/src/app/services/inquiry/inquiry-document/inquiry-document.service.ts @@ -77,12 +77,6 @@ export class InquiryDocumentService { if (dto.file) { formData.append('file', dto.file, dto.file.name); } - if (dto.parcelUuid) { - formData.append('parcelUuid', dto.parcelUuid); - } - if (dto.ownerUuid) { - formData.append('ownerUuid', dto.ownerUuid); - } return formData; } } diff --git a/alcs-frontend/src/app/services/planning-review/planning-review-document/planning-review-document.service.ts b/alcs-frontend/src/app/services/planning-review/planning-review-document/planning-review-document.service.ts index 545ea10ecb..1714e195fc 100644 --- a/alcs-frontend/src/app/services/planning-review/planning-review-document/planning-review-document.service.ts +++ b/alcs-frontend/src/app/services/planning-review/planning-review-document/planning-review-document.service.ts @@ -6,7 +6,7 @@ import { DocumentTypeDto } from '../../../shared/document/document.dto'; import { downloadFileFromUrl, openFileInline } from '../../../shared/utils/file'; import { verifyFileSize } from '../../../shared/utils/file-size-checker'; import { ToastService } from '../../toast/toast.service'; -import { PlanningReviewDocumentDto, CreateDocumentDto, UpdateDocumentDto } from './planning-review-document.dto'; +import { CreateDocumentDto, PlanningReviewDocumentDto, UpdateDocumentDto } from './planning-review-document.dto'; @Injectable({ providedIn: 'root', @@ -86,6 +86,7 @@ export class PlanningReviewDocumentService { formData.append('documentType', dto.typeCode); formData.append('source', dto.source); formData.append('fileName', dto.fileName); + formData.append('visibilityFlags', dto.visibilityFlags.join(', ')); if (dto.file) { formData.append('file', dto.file, dto.file.name); } diff --git a/alcs-frontend/src/app/shared/utils/file.ts b/alcs-frontend/src/app/shared/utils/file.ts index 46fd3ee062..21284d7223 100644 --- a/alcs-frontend/src/app/shared/utils/file.ts +++ b/alcs-frontend/src/app/shared/utils/file.ts @@ -29,3 +29,10 @@ export const openFileInline = (url: string, fileName: string) => { newWindow.document.body.style.overflow = 'hidden'; } }; + +export const splitExtension = (documentName: string) => { + const lastPeriod = documentName.lastIndexOf('.'); + const extension = documentName.substring(lastPeriod); + const fileName = documentName.substring(0, lastPeriod); + return { fileName, extension }; +}; diff --git a/alcs-frontend/src/styles.scss b/alcs-frontend/src/styles.scss index 6c3be8512c..80ff3ee7de 100644 --- a/alcs-frontend/src/styles.scss +++ b/alcs-frontend/src/styles.scss @@ -314,6 +314,10 @@ mat-button-toggle-group { font-weight: 700 !important; } +.mat-mdc-form-field-text-suffix { + color: colors.$black; +} + @mixin text-ellipsis($lines: 1) { text-overflow: ellipsis; overflow: hidden; From 11ab17a0636e83adb9b771eda3478e8657aed3e6 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Thu, 28 Mar 2024 14:01:22 -0700 Subject: [PATCH 066/153] UI Changes * Change detailed page layout for all types to scroll the child content not the window * Add index column to evidentiary records * Add scrollable to document sorting to it scrolls * Add mask to PID on Proposal --- .../app/features/admin/admin.component.scss | 5 +- .../application/application.component.html | 22 ++++----- .../application/application.component.scss | 5 +- .../parcel-prep/parcel-prep.component.html | 46 +++++++++--------- .../features/inquiry/inquiry.component.scss | 5 +- .../notice-of-intent.component.scss | 5 +- .../parcel-prep/parcel-prep.component.html | 46 +++++++++--------- .../notification/intake/intake.component.ts | 40 ++++++++-------- .../notification/notification.component.scss | 5 +- .../planning-review.component.scss | 6 +-- .../evidentiary-record.component.html | 48 +++++++++++-------- .../evidentiary-record.component.ts | 6 +-- .../application-document.component.html | 48 +++++++++++-------- .../application-document.component.ts | 4 +- 14 files changed, 153 insertions(+), 138 deletions(-) diff --git a/alcs-frontend/src/app/features/admin/admin.component.scss b/alcs-frontend/src/app/features/admin/admin.component.scss index 0290be29d1..f5af453ae5 100644 --- a/alcs-frontend/src/app/features/admin/admin.component.scss +++ b/alcs-frontend/src/app/features/admin/admin.component.scss @@ -17,12 +17,13 @@ .content { display: flex; flex-grow: 1; - padding-right: 80px; } .child-content { - margin: 24px 0 0 48px; + padding: 24px 80px 20px 48px; flex-grow: 1; + height: calc(100vh - 246px); + overflow-y: auto; } .nav { diff --git a/alcs-frontend/src/app/features/application/application.component.html b/alcs-frontend/src/app/features/application/application.component.html index e43724be1b..82ccfb9d5c 100644 --- a/alcs-frontend/src/app/features/application/application.component.html +++ b/alcs-frontend/src/app/features/application/application.component.html @@ -2,14 +2,14 @@ <div class="application"> <app-details-header [application]="application" - [reconsiderations]="reconsiderations" [modifications]="modifications" - days="Business Days" - heading="Application" + [reconsiderations]="reconsiderations" [showStatus]="true" [submissionStatusService]="applicationStatusService" + days="Business Days" + heading="Application" ></app-details-header> - <div class="content" *ngIf="application"> + <div *ngIf="application" class="content"> <div *ngIf="showSubmittedToAlcMenuItems" class="nav"> <div *ngFor="let route of childRoutes" class="nav-link"> <div @@ -17,10 +17,10 @@ (!route.portalOnly || isApplicantSubmission) && (route.appTypes ? route.appTypes.includes(application.type.code) : true) " - [routerLink]="route.path ? route.path : './'" - routerLinkActive="active" [routerLinkActiveOptions]="{ exact: route.path === '' }" + [routerLink]="route.path ? route.path : './'" class="nav-item nav-text" + routerLinkActive="active" > <mat-icon>{{ route.icon }}</mat-icon> {{ route.menuTitle }} @@ -30,10 +30,10 @@ <div *ngIf="showSubmittedToLfngMenuItems" class="nav"> <div *ngFor="let route of submittedLfngRoutes" class="nav-link"> <div - [routerLink]="route.path ? route.path : './'" - routerLinkActive="active" [routerLinkActiveOptions]="{ exact: route.path === '' }" + [routerLink]="route.path ? route.path : './'" class="nav-item nav-text" + routerLinkActive="active" > <mat-icon>{{ route.icon }}</mat-icon> {{ route.menuTitle }} @@ -43,17 +43,17 @@ <div *ngIf="!showSubmittedToAlcMenuItems && !showSubmittedToLfngMenuItems" class="nav"> <div *ngFor="let route of unsubmittedRoutes" class="nav-link"> <div - [routerLink]="route.path ? route.path : './'" - routerLinkActive="active" [routerLinkActiveOptions]="{ exact: route.path === '' }" + [routerLink]="route.path ? route.path : './'" class="nav-item nav-text" + routerLinkActive="active" > <mat-icon>{{ route.icon }}</mat-icon> {{ route.menuTitle }} </div> </div> </div> - <div class="child-content"> + <div cdkScrollable class="child-content"> <router-outlet></router-outlet> </div> </div> diff --git a/alcs-frontend/src/app/features/application/application.component.scss b/alcs-frontend/src/app/features/application/application.component.scss index 1e03bb92e4..3064e360b5 100644 --- a/alcs-frontend/src/app/features/application/application.component.scss +++ b/alcs-frontend/src/app/features/application/application.component.scss @@ -17,12 +17,13 @@ .content { display: flex; flex-grow: 1; - padding-right: 80px; } .child-content { - margin: 24px 0 0 48px; + padding: 24px 80px 20px 48px; flex-grow: 1; + height: calc(100vh - 246px); + overflow-y: auto; } .nav { diff --git a/alcs-frontend/src/app/features/application/proposal/parcel-prep/parcel-prep.component.html b/alcs-frontend/src/app/features/application/proposal/parcel-prep/parcel-prep.component.html index 020932bf98..b3804d480c 100644 --- a/alcs-frontend/src/app/features/application/proposal/parcel-prep/parcel-prep.component.html +++ b/alcs-frontend/src/app/features/application/proposal/parcel-prep/parcel-prep.component.html @@ -1,57 +1,57 @@ <div *ngIf="!parcels" class="center"> <mat-spinner></mat-spinner> </div> -<table *ngIf="parcels" mat-table [dataSource]="parcels"> - <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> - <tr mat-row *matRowDef="let row; columns: displayedColumns"></tr> +<table *ngIf="parcels" [dataSource]="parcels" mat-table> + <tr *matHeaderRowDef="displayedColumns" mat-header-row></tr> + <tr *matRowDef="let row; columns: displayedColumns" mat-row></tr> <ng-container matColumnDef="number"> - <th mat-header-cell *matHeaderCellDef>#</th> - <td mat-cell *matCellDef="let row; let i = index">{{ i + 1 }}</td> + <th *matHeaderCellDef mat-header-cell>#</th> + <td *matCellDef="let row; let i = index" mat-cell>{{ i + 1 }}</td> </ng-container> <ng-container matColumnDef="pid"> - <th mat-header-cell *matHeaderCellDef>PID</th> - <td mat-cell *matCellDef="let row"> - {{ row.pid }} + <th *matHeaderCellDef mat-header-cell>PID</th> + <td *matCellDef="let row" mat-cell> + {{ row.pid | mask: '000-000-000' }} <app-no-data *ngIf="!row.pid"></app-no-data> </td> </ng-container> <ng-container matColumnDef="pin"> - <th mat-header-cell *matHeaderCellDef>PIN</th> - <td mat-cell *matCellDef="let row"> + <th *matHeaderCellDef mat-header-cell>PIN</th> + <td *matCellDef="let row" mat-cell> {{ row.pin }} <app-no-data *ngIf="!row.pin"></app-no-data> </td> </ng-container> <ng-container matColumnDef="civicAddress"> - <th mat-header-cell *matHeaderCellDef>Civic Address</th> - <td style="max-width: 300px" mat-cell *matCellDef="let row"> - <span class="civic-address" [matTooltip]="row.civicAddress">{{ row.civicAddress }}</span> + <th *matHeaderCellDef mat-header-cell>Civic Address</th> + <td *matCellDef="let row" mat-cell style="max-width: 300px"> + <span [matTooltip]="row.civicAddress" class="civic-address">{{ row.civicAddress }}</span> </td> </ng-container> <ng-container matColumnDef="area"> - <th mat-header-cell *matHeaderCellDef>Area (ha)</th> - <td mat-cell *matCellDef="let row">{{ row.mapAreaHectares }}</td> + <th *matHeaderCellDef mat-header-cell>Area (ha)</th> + <td *matCellDef="let row" mat-cell>{{ row.mapAreaHectares }}</td> </ng-container> <ng-container matColumnDef="alrArea"> - <th mat-header-cell *matHeaderCellDef>ALR Area (ha)</th> - <td mat-cell *matCellDef="let row"> - <app-inline-number [value]="row.alrArea" (save)="saveParcel(row.uuid, $event)" [decimals]="5"></app-inline-number> + <th *matHeaderCellDef mat-header-cell>ALR Area (ha)</th> + <td *matCellDef="let row" mat-cell> + <app-inline-number (save)="saveParcel(row.uuid, $event)" [decimals]="5" [value]="row.alrArea"></app-inline-number> </td> </ng-container> <ng-container matColumnDef="owners"> - <th mat-header-cell *matHeaderCellDef>Owner(s)</th> - <td mat-cell *matCellDef="let row"> - <span [matTooltip]="row.fullOwners" [matTooltipDisabled]="!row.hasManyOwners">{{ row.owners }}</span> + <th *matHeaderCellDef mat-header-cell>Owner(s)</th> + <td *matCellDef="let row" mat-cell> + <span [matTooltipDisabled]="!row.hasManyOwners" [matTooltip]="row.fullOwners">{{ row.owners }}</span> </td> </ng-container> <ng-container matColumnDef="actions"> - <th mat-header-cell *matHeaderCellDef>Actions</th> - <td mat-cell *matCellDef="let row"> + <th *matHeaderCellDef mat-header-cell>Actions</th> + <td *matCellDef="let row" mat-cell> <button (click)="navigateToParcelDetails(row.uuid)" class="link-button" diff --git a/alcs-frontend/src/app/features/inquiry/inquiry.component.scss b/alcs-frontend/src/app/features/inquiry/inquiry.component.scss index 55dfeb4d75..267cb53afa 100644 --- a/alcs-frontend/src/app/features/inquiry/inquiry.component.scss +++ b/alcs-frontend/src/app/features/inquiry/inquiry.component.scss @@ -17,12 +17,13 @@ .content { display: flex; flex-grow: 1; - padding-right: 80px; } .child-content { - margin: 24px 0 0 48px; + padding: 24px 80px 20px 48px; flex-grow: 1; + height: calc(100vh - 246px); + overflow-y: auto; } .nav { diff --git a/alcs-frontend/src/app/features/notice-of-intent/notice-of-intent.component.scss b/alcs-frontend/src/app/features/notice-of-intent/notice-of-intent.component.scss index 55dfeb4d75..267cb53afa 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/notice-of-intent.component.scss +++ b/alcs-frontend/src/app/features/notice-of-intent/notice-of-intent.component.scss @@ -17,12 +17,13 @@ .content { display: flex; flex-grow: 1; - padding-right: 80px; } .child-content { - margin: 24px 0 0 48px; + padding: 24px 80px 20px 48px; flex-grow: 1; + height: calc(100vh - 246px); + overflow-y: auto; } .nav { diff --git a/alcs-frontend/src/app/features/notice-of-intent/proposal/parcel-prep/parcel-prep.component.html b/alcs-frontend/src/app/features/notice-of-intent/proposal/parcel-prep/parcel-prep.component.html index 020932bf98..b3804d480c 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/proposal/parcel-prep/parcel-prep.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/proposal/parcel-prep/parcel-prep.component.html @@ -1,57 +1,57 @@ <div *ngIf="!parcels" class="center"> <mat-spinner></mat-spinner> </div> -<table *ngIf="parcels" mat-table [dataSource]="parcels"> - <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> - <tr mat-row *matRowDef="let row; columns: displayedColumns"></tr> +<table *ngIf="parcels" [dataSource]="parcels" mat-table> + <tr *matHeaderRowDef="displayedColumns" mat-header-row></tr> + <tr *matRowDef="let row; columns: displayedColumns" mat-row></tr> <ng-container matColumnDef="number"> - <th mat-header-cell *matHeaderCellDef>#</th> - <td mat-cell *matCellDef="let row; let i = index">{{ i + 1 }}</td> + <th *matHeaderCellDef mat-header-cell>#</th> + <td *matCellDef="let row; let i = index" mat-cell>{{ i + 1 }}</td> </ng-container> <ng-container matColumnDef="pid"> - <th mat-header-cell *matHeaderCellDef>PID</th> - <td mat-cell *matCellDef="let row"> - {{ row.pid }} + <th *matHeaderCellDef mat-header-cell>PID</th> + <td *matCellDef="let row" mat-cell> + {{ row.pid | mask: '000-000-000' }} <app-no-data *ngIf="!row.pid"></app-no-data> </td> </ng-container> <ng-container matColumnDef="pin"> - <th mat-header-cell *matHeaderCellDef>PIN</th> - <td mat-cell *matCellDef="let row"> + <th *matHeaderCellDef mat-header-cell>PIN</th> + <td *matCellDef="let row" mat-cell> {{ row.pin }} <app-no-data *ngIf="!row.pin"></app-no-data> </td> </ng-container> <ng-container matColumnDef="civicAddress"> - <th mat-header-cell *matHeaderCellDef>Civic Address</th> - <td style="max-width: 300px" mat-cell *matCellDef="let row"> - <span class="civic-address" [matTooltip]="row.civicAddress">{{ row.civicAddress }}</span> + <th *matHeaderCellDef mat-header-cell>Civic Address</th> + <td *matCellDef="let row" mat-cell style="max-width: 300px"> + <span [matTooltip]="row.civicAddress" class="civic-address">{{ row.civicAddress }}</span> </td> </ng-container> <ng-container matColumnDef="area"> - <th mat-header-cell *matHeaderCellDef>Area (ha)</th> - <td mat-cell *matCellDef="let row">{{ row.mapAreaHectares }}</td> + <th *matHeaderCellDef mat-header-cell>Area (ha)</th> + <td *matCellDef="let row" mat-cell>{{ row.mapAreaHectares }}</td> </ng-container> <ng-container matColumnDef="alrArea"> - <th mat-header-cell *matHeaderCellDef>ALR Area (ha)</th> - <td mat-cell *matCellDef="let row"> - <app-inline-number [value]="row.alrArea" (save)="saveParcel(row.uuid, $event)" [decimals]="5"></app-inline-number> + <th *matHeaderCellDef mat-header-cell>ALR Area (ha)</th> + <td *matCellDef="let row" mat-cell> + <app-inline-number (save)="saveParcel(row.uuid, $event)" [decimals]="5" [value]="row.alrArea"></app-inline-number> </td> </ng-container> <ng-container matColumnDef="owners"> - <th mat-header-cell *matHeaderCellDef>Owner(s)</th> - <td mat-cell *matCellDef="let row"> - <span [matTooltip]="row.fullOwners" [matTooltipDisabled]="!row.hasManyOwners">{{ row.owners }}</span> + <th *matHeaderCellDef mat-header-cell>Owner(s)</th> + <td *matCellDef="let row" mat-cell> + <span [matTooltipDisabled]="!row.hasManyOwners" [matTooltip]="row.fullOwners">{{ row.owners }}</span> </td> </ng-container> <ng-container matColumnDef="actions"> - <th mat-header-cell *matHeaderCellDef>Actions</th> - <td mat-cell *matCellDef="let row"> + <th *matHeaderCellDef mat-header-cell>Actions</th> + <td *matCellDef="let row" mat-cell> <button (click)="navigateToParcelDetails(row.uuid)" class="link-button" diff --git a/alcs-frontend/src/app/features/notification/intake/intake.component.ts b/alcs-frontend/src/app/features/notification/intake/intake.component.ts index 820635fb5a..6fc3c99fab 100644 --- a/alcs-frontend/src/app/features/notification/intake/intake.component.ts +++ b/alcs-frontend/src/app/features/notification/intake/intake.component.ts @@ -4,14 +4,10 @@ import { environment } from '../../../../environments/environment'; import { ApplicationLocalGovernmentService } from '../../../services/application/application-local-government/application-local-government.service'; import { NotificationDetailService } from '../../../services/notification/notification-detail.service'; import { NotificationSubmissionService } from '../../../services/notification/notification-submission/notification-submission.service'; -import { - NOTIFICATION_STATUS, - NotificationDto, - UpdateNotificationDto, -} from '../../../services/notification/notification.dto'; +import { NotificationTimelineService } from '../../../services/notification/notification-timeline/notification-timeline.service'; +import { NotificationDto, UpdateNotificationDto } from '../../../services/notification/notification.dto'; import { ToastService } from '../../../services/toast/toast.service'; import { ConfirmationDialogService } from '../../../shared/confirmation-dialog/confirmation-dialog.service'; -import { NotificationTimelineService } from '../../../services/notification/notification-timeline/notification-timeline.service'; @Component({ selector: 'app-intake', @@ -84,14 +80,6 @@ export class IntakeComponent implements OnInit { }); } - private async loadGovernments() { - const localGovernment = await this.localGovernmentService.list(); - this.localGovernments = localGovernment.map((government) => ({ - label: government.name, - value: government.uuid, - })); - } - async onSaveLocalGovernment($value: string | string[] | null) { this.confirmationDialogService .openDialog({ @@ -113,6 +101,23 @@ export class IntakeComponent implements OnInit { }); } + async resendResponse() { + if (this.notification) { + const res = await this.notificationDetailService.resendResponse(this.notification.fileNumber); + if (res) { + this.toastService.showSuccessToast('Response Sent'); + } + } + } + + private async loadGovernments() { + const localGovernment = await this.localGovernmentService.list(); + this.localGovernments = localGovernment.map((government) => ({ + label: government.name, + value: government.uuid, + })); + } + private async loadSubmission(fileNumber: string) { const submission = await this.notificationSubmissionService.fetchSubmission(fileNumber); this.contactEmail = submission.contactEmail; @@ -123,11 +128,4 @@ export class IntakeComponent implements OnInit { this.responseSent = alcResponseEvent !== undefined; this.responseDate = alcResponseEvent?.startDate ?? null; } - - async resendResponse() { - if (this.notification) { - await this.notificationDetailService.resendResponse(this.notification.fileNumber); - this.toastService.showSuccessToast('Response Sent'); - } - } } diff --git a/alcs-frontend/src/app/features/notification/notification.component.scss b/alcs-frontend/src/app/features/notification/notification.component.scss index 55dfeb4d75..267cb53afa 100644 --- a/alcs-frontend/src/app/features/notification/notification.component.scss +++ b/alcs-frontend/src/app/features/notification/notification.component.scss @@ -17,12 +17,13 @@ .content { display: flex; flex-grow: 1; - padding-right: 80px; } .child-content { - margin: 24px 0 0 48px; + padding: 24px 80px 20px 48px; flex-grow: 1; + height: calc(100vh - 246px); + overflow-y: auto; } .nav { diff --git a/alcs-frontend/src/app/features/planning-review/planning-review.component.scss b/alcs-frontend/src/app/features/planning-review/planning-review.component.scss index 55dfeb4d75..141d867134 100644 --- a/alcs-frontend/src/app/features/planning-review/planning-review.component.scss +++ b/alcs-frontend/src/app/features/planning-review/planning-review.component.scss @@ -17,12 +17,13 @@ .content { display: flex; flex-grow: 1; - padding-right: 80px; } .child-content { - margin: 24px 0 0 48px; + padding: 24px 80px 20px 48px; flex-grow: 1; + height: calc(100vh - 246px); + overflow-y: auto; } .nav { @@ -33,7 +34,6 @@ } .nav-link { - div { padding: 12px 24px; border: 2px solid transparent; diff --git a/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.html b/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.html index c695d1e2c1..84c70e07f3 100644 --- a/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.html +++ b/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.html @@ -5,60 +5,66 @@ <h4>{{ tableTitle }}</h4> </div> <table - cdkDropList - [cdkDropListDisabled]='!sortable' (cdkDropListDropped)="onRowDropped($event)" - mat-table + [cdkDropListDisabled]="!sortable" [dataSource]="dataSource" + cdkDropList + cdkDropListAutoScrollStep="10" class="mat-elevation-z3 width-100" + mat-table > + <ng-container matColumnDef="index"> + <th *matHeaderCellDef mat-header-cell></th> + <td *matCellDef="let element; let i = index" mat-cell>{{ i + 1 }}</td> + </ng-container> + <ng-container matColumnDef="type"> - <th mat-header-cell *matHeaderCellDef>Type</th> - <td mat-cell *matCellDef="let element">{{ element.type?.label }}</td> + <th *matHeaderCellDef mat-header-cell>Type</th> + <td *matCellDef="let element" mat-cell>{{ element.type?.label }}</td> </ng-container> <ng-container matColumnDef="fileName"> - <th mat-header-cell *matHeaderCellDef>File Name</th> - <td mat-cell *matCellDef="let element"> + <th *matHeaderCellDef mat-header-cell>File Name</th> + <td *matCellDef="let element" mat-cell> <a (click)="onOpen(element.uuid, element.fileName)">{{ element.fileName }}</a> </td> </ng-container> <ng-container matColumnDef="source"> - <th mat-header-cell *matHeaderCellDef>Source</th> - <td mat-cell *matCellDef="let element">{{ element.source }}</td> + <th *matHeaderCellDef mat-header-cell>Source</th> + <td *matCellDef="let element" mat-cell>{{ element.source }}</td> </ng-container> <ng-container matColumnDef="uploadedAt"> - <th mat-header-cell *matHeaderCellDef>Upload Date</th> - <td mat-cell *matCellDef="let element">{{ element.uploadedAt | momentFormat }}</td> + <th *matHeaderCellDef mat-header-cell>Upload Date</th> + <td *matCellDef="let element" mat-cell>{{ element.uploadedAt | momentFormat }}</td> </ng-container> <ng-container matColumnDef="action"> - <th mat-header-cell *matHeaderCellDef>Action</th> - <td mat-cell *matCellDef="let element"> - <button title="Download" mat-icon-button class="action-btn" (click)="onDownload(element.uuid, element.fileName)"> + <th *matHeaderCellDef mat-header-cell>Action</th> + <td *matCellDef="let element" mat-cell> + <button (click)="onDownload(element.uuid, element.fileName)" class="action-btn" mat-icon-button title="Download"> <mat-icon>download</mat-icon> </button> </td> </ng-container> <ng-container matColumnDef="sorting"> - <th mat-header-cell *matHeaderCellDef></th> - <td class='drag-cell' mat-cell *matCellDef="let element"> + <th *matHeaderCellDef mat-header-cell></th> + <td *matCellDef="let element" class="drag-cell" mat-cell> <mat-icon>drag_indicator</mat-icon> </td> </ng-container> - <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> + <tr *matHeaderRowDef="displayedColumns" mat-header-row></tr> <tr - style="background-color: #fff" - mat-row + *matRowDef="let row; columns: displayedColumns" cdkDrag cdkDragLockAxis="y" - *matRowDef="let row; columns: displayedColumns" + mat-row + style="background-color: #fff" ></tr> - <tr class="mat-row" *matNoDataRow> + <tr *matNoDataRow class="mat-row"> <td class="text-center" colspan="4">No files</td> </tr> </table> diff --git a/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.ts b/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.ts index 04e7588ff2..75144d0511 100644 --- a/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.ts +++ b/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.ts @@ -1,5 +1,5 @@ import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; -import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { Component, Input, OnChanges } from '@angular/core'; import { MatTableDataSource } from '@angular/material/table'; import { PlanningReviewDocumentDto } from '../../../../services/planning-review/planning-review-document/planning-review-document.dto'; import { PlanningReviewDocumentService } from '../../../../services/planning-review/planning-review-document/planning-review-document.service'; @@ -15,7 +15,7 @@ export class EvidentiaryRecordComponent implements OnChanges { @Input() visibilityFlags: string[] = []; @Input() sortable = false; - displayedColumns: string[] = ['type', 'fileName', 'source', 'uploadedAt', 'action', 'sorting']; + displayedColumns: string[] = ['index', 'type', 'fileName', 'source', 'uploadedAt', 'action', 'sorting']; documents: PlanningReviewDocumentDto[] = []; dataSource = new MatTableDataSource<PlanningReviewDocumentDto>([]); @@ -24,7 +24,7 @@ export class EvidentiaryRecordComponent implements OnChanges { ngOnChanges(): void { this.loadDocuments(); if (!this.sortable) { - this.displayedColumns = ['type', 'fileName', 'source', 'uploadedAt', 'action']; + this.displayedColumns = ['index', 'type', 'fileName', 'source', 'uploadedAt', 'action']; } } diff --git a/alcs-frontend/src/app/shared/application-document/application-document.component.html b/alcs-frontend/src/app/shared/application-document/application-document.component.html index c695d1e2c1..84c70e07f3 100644 --- a/alcs-frontend/src/app/shared/application-document/application-document.component.html +++ b/alcs-frontend/src/app/shared/application-document/application-document.component.html @@ -5,60 +5,66 @@ <h4>{{ tableTitle }}</h4> </div> <table - cdkDropList - [cdkDropListDisabled]='!sortable' (cdkDropListDropped)="onRowDropped($event)" - mat-table + [cdkDropListDisabled]="!sortable" [dataSource]="dataSource" + cdkDropList + cdkDropListAutoScrollStep="10" class="mat-elevation-z3 width-100" + mat-table > + <ng-container matColumnDef="index"> + <th *matHeaderCellDef mat-header-cell></th> + <td *matCellDef="let element; let i = index" mat-cell>{{ i + 1 }}</td> + </ng-container> + <ng-container matColumnDef="type"> - <th mat-header-cell *matHeaderCellDef>Type</th> - <td mat-cell *matCellDef="let element">{{ element.type?.label }}</td> + <th *matHeaderCellDef mat-header-cell>Type</th> + <td *matCellDef="let element" mat-cell>{{ element.type?.label }}</td> </ng-container> <ng-container matColumnDef="fileName"> - <th mat-header-cell *matHeaderCellDef>File Name</th> - <td mat-cell *matCellDef="let element"> + <th *matHeaderCellDef mat-header-cell>File Name</th> + <td *matCellDef="let element" mat-cell> <a (click)="onOpen(element.uuid, element.fileName)">{{ element.fileName }}</a> </td> </ng-container> <ng-container matColumnDef="source"> - <th mat-header-cell *matHeaderCellDef>Source</th> - <td mat-cell *matCellDef="let element">{{ element.source }}</td> + <th *matHeaderCellDef mat-header-cell>Source</th> + <td *matCellDef="let element" mat-cell>{{ element.source }}</td> </ng-container> <ng-container matColumnDef="uploadedAt"> - <th mat-header-cell *matHeaderCellDef>Upload Date</th> - <td mat-cell *matCellDef="let element">{{ element.uploadedAt | momentFormat }}</td> + <th *matHeaderCellDef mat-header-cell>Upload Date</th> + <td *matCellDef="let element" mat-cell>{{ element.uploadedAt | momentFormat }}</td> </ng-container> <ng-container matColumnDef="action"> - <th mat-header-cell *matHeaderCellDef>Action</th> - <td mat-cell *matCellDef="let element"> - <button title="Download" mat-icon-button class="action-btn" (click)="onDownload(element.uuid, element.fileName)"> + <th *matHeaderCellDef mat-header-cell>Action</th> + <td *matCellDef="let element" mat-cell> + <button (click)="onDownload(element.uuid, element.fileName)" class="action-btn" mat-icon-button title="Download"> <mat-icon>download</mat-icon> </button> </td> </ng-container> <ng-container matColumnDef="sorting"> - <th mat-header-cell *matHeaderCellDef></th> - <td class='drag-cell' mat-cell *matCellDef="let element"> + <th *matHeaderCellDef mat-header-cell></th> + <td *matCellDef="let element" class="drag-cell" mat-cell> <mat-icon>drag_indicator</mat-icon> </td> </ng-container> - <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> + <tr *matHeaderRowDef="displayedColumns" mat-header-row></tr> <tr - style="background-color: #fff" - mat-row + *matRowDef="let row; columns: displayedColumns" cdkDrag cdkDragLockAxis="y" - *matRowDef="let row; columns: displayedColumns" + mat-row + style="background-color: #fff" ></tr> - <tr class="mat-row" *matNoDataRow> + <tr *matNoDataRow class="mat-row"> <td class="text-center" colspan="4">No files</td> </tr> </table> diff --git a/alcs-frontend/src/app/shared/application-document/application-document.component.ts b/alcs-frontend/src/app/shared/application-document/application-document.component.ts index 1dba1bd0d9..ccbbe7c72d 100644 --- a/alcs-frontend/src/app/shared/application-document/application-document.component.ts +++ b/alcs-frontend/src/app/shared/application-document/application-document.component.ts @@ -15,7 +15,7 @@ export class ApplicationDocumentComponent implements OnChanges { @Input() visibilityFlags: string[] = []; @Input() sortable = false; - displayedColumns: string[] = ['type', 'fileName', 'source', 'uploadedAt', 'action', 'sorting']; + displayedColumns: string[] = ['index', 'type', 'fileName', 'source', 'uploadedAt', 'action', 'sorting']; documents: ApplicationDocumentDto[] = []; dataSource = new MatTableDataSource<ApplicationDocumentDto>([]); @@ -24,7 +24,7 @@ export class ApplicationDocumentComponent implements OnChanges { ngOnChanges(changes: SimpleChanges): void { this.loadDocuments(); if (!this.sortable) { - this.displayedColumns = ['type', 'fileName', 'source', 'uploadedAt', 'action']; + this.displayedColumns = ['index', 'type', 'fileName', 'source', 'uploadedAt', 'action']; } } From 7c7aeb9b30dacf58a80eed0b095cf0690672f223 Mon Sep 17 00:00:00 2001 From: Tristan Slater <1631008+trslater@users.noreply.github.com> Date: Thu, 28 Mar 2024 15:24:44 -0700 Subject: [PATCH 067/153] Show 'Not Applicable' for individual NOI owners' corporate summary --- .../notice-of-intent-details/parcel/parcel.component.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/parcel/parcel.component.html b/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/parcel/parcel.component.html index e3c8258402..d79b3b19f2 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/parcel/parcel.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/parcel/parcel.component.html @@ -127,7 +127,10 @@ <h5>Government Parcel Contact</h5> <a *ngIf="owner.corporateSummary" (click)="onOpenFile(owner.corporateSummary)">{{ owner.corporateSummary.fileName }}</a> - <app-no-data *ngIf="!owner.corporateSummary"></app-no-data> + <div class="no-data" *ngIf="!owner.corporateSummary"> + <div *ngIf="owner.type.code === 'INDV'" class="no-data-text">Not Applicable</div> + <div *ngIf="owner.type.code !== 'INDV'" class="no-data-text">No Data</div> + </div> </div> </ng-container> <app-no-data *ngIf="parcel.owners.length === 0"></app-no-data> From e19ae8895a1871873c143becff5e214524b26ae9 Mon Sep 17 00:00:00 2001 From: Liam Stoddard <lstodd@protonmail.com> Date: Thu, 28 Mar 2024 17:26:22 -0700 Subject: [PATCH 068/153] init pr documents --- ..._documents_to_planning_review_documents.py | 147 ++++++++++++++ .../post_launch/migrate_documents.py | 10 + ...ments_to_alcs_documents_planning_review.py | 189 ++++++++++++++++++ ...documents_to_planning_review_documents.sql | 32 +++ ...nts_to_planning_review_documents_count.sql | 23 +++ .../oats_documents_to_alcs_documents.sql | 28 +++ ...oats_documents_to_alcs_documents_count.sql | 20 ++ .../planning-review-document.entity.ts | 16 ++ .../apps/alcs/src/document/document.entity.ts | 7 + ...388-add_planning_review_id_to_documents.ts | 47 +++++ 10 files changed, 519 insertions(+) create mode 100644 bin/migrate-oats-data/documents/post_launch/alcs_documents_to_planning_review_documents.py create mode 100644 bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_planning_review.py create mode 100644 bin/migrate-oats-data/documents/post_launch/sql/planning_review/alcs_documents_to_planning_review_documents.sql create mode 100644 bin/migrate-oats-data/documents/post_launch/sql/planning_review/alcs_documents_to_planning_review_documents_count.sql create mode 100644 bin/migrate-oats-data/documents/post_launch/sql/planning_review/oats_documents_to_alcs_documents.sql create mode 100644 bin/migrate-oats-data/documents/post_launch/sql/planning_review/oats_documents_to_alcs_documents_count.sql create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1711670649388-add_planning_review_id_to_documents.ts diff --git a/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_planning_review_documents.py b/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_planning_review_documents.py new file mode 100644 index 0000000000..5783388f8f --- /dev/null +++ b/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_planning_review_documents.py @@ -0,0 +1,147 @@ +from common import ( + setup_and_get_logger, + BATCH_UPLOAD_SIZE, + OATS_ETL_USER, +) +from db import inject_conn_pool +from psycopg2.extras import RealDictCursor + +etl_name = "link_srw_documents_from_alcs" +logger = setup_and_get_logger(etl_name) + +""" + This script connects to postgress version of OATS DB and links data from ALCS documents to ALCS notification_document table. + + NOTE: + Before performing document import you need to import SRWs and SRW documents. +""" + + +@inject_conn_pool +def link_srw_documents(conn=None, batch_size=BATCH_UPLOAD_SIZE): + """ + function uses a decorator pattern @inject_conn_pool to inject a database connection pool to the function. It fetches the total count of documents and prints it to the console. Then, it fetches the documents to insert in batches using document IDs, constructs an insert query, and processes them. + """ + logger.info(f"Start {etl_name}") + with conn.cursor(cursor_factory=RealDictCursor) as cursor: + with open( + "documents/post_launch/sql/alcs_documents_to_notification_documents_count.sql", + "r", + encoding="utf-8", + ) as sql_file: + count_query = sql_file.read() + cursor.execute(count_query) + total_count = dict(cursor.fetchone())["count"] + logger.info(f"Total count of documents to transfer: {total_count}") + + failed_inserts_count = 0 + successful_inserts_count = 0 + last_document_id = 0 + + with open( + "documents/post_launch/sql/alcs_documents_to_notification_documents.sql", + "r", + encoding="utf-8", + ) as sql_file: + documents_to_insert_sql = sql_file.read() + while True: + cursor.execute( + f"{documents_to_insert_sql} WHERE oats_document_id > {last_document_id} ORDER BY oats_document_id;" + ) + rows = cursor.fetchmany(batch_size) + if not rows: + break + try: + documents_to_be_inserted_count = len(rows) + + _insert_records(conn, cursor, rows) + + last_document_id = dict(rows[-1])["oats_document_id"] + successful_inserts_count = ( + successful_inserts_count + documents_to_be_inserted_count + ) + + logger.debug( + f"retrieved/inserted items count: {documents_to_be_inserted_count}; total successfully inserted/updated documents so far {successful_inserts_count}; last inserted oats_document_id: {last_document_id}" + ) + except Exception as e: + conn.rollback() + logger.exception(f"Error {e}") + failed_inserts_count += len(rows) + last_document_id = last_document_id + 1 + + logger.info(f"Total amount of successful inserts: {successful_inserts_count}") + logger.info(f"Total amount of failed inserts: {failed_inserts_count}") + + +def _insert_records(conn, cursor, rows): + number_of_rows_to_insert = len(rows) + + if number_of_rows_to_insert > 0: + insert_query = _compile_insert_query(number_of_rows_to_insert) + rows_to_insert = _prepare_data_to_insert(rows) + cursor.execute(insert_query, rows_to_insert) + conn.commit() + + +def _compile_insert_query(number_of_rows_to_insert): + documents_to_insert = ",".join(["%s"] * number_of_rows_to_insert) + return f""" + INSERT INTO alcs.notification_document( + notification_uuid, + document_uuid, + type_code, + visibility_flags, + oats_document_id, + oats_planning_review_id, + audit_created_by, + survey_plan_number, + control_number, + description + ) + VALUES{documents_to_insert} + ON CONFLICT (oats_document_id, oats_planning_review_id) DO UPDATE SET + notification_uuid = EXCLUDED.notification_uuid, + document_uuid = EXCLUDED.document_uuid, + type_code = EXCLUDED.type_code, + visibility_flags = EXCLUDED.visibility_flags, + audit_created_by = EXCLUDED.audit_created_by, + survey_plan_number = EXCLUDED.survey_plan_number, + control_number = EXCLUDED.control_number, + description = EXCLUDED.description; + """ + + +def _prepare_data_to_insert(rows): + row_without_last_element = [] + for row in rows: + mapped_row = _map_data(row) + row_without_last_element.append(tuple(mapped_row.values())) + + return row_without_last_element + + +def _map_data(row): + return { + "notification_uuid": row["notification_uuid"], + "document_uuid": row["document_uuid"], + "type_code": row["type_code"], + "visibility_flags": row["visibility_flags"], + "oats_document_id": row["oats_document_id"], + "oats_planning_review_id": row["oats_planning_review_id"], + "audit_created_by": OATS_ETL_USER, + "plan_number": row["plan_no"], + "control_number": row["control_no"], + "description": row["description"], + } + + +@inject_conn_pool +def clean_notification_documents(conn=None): + logger.info("Start documents cleaning") + with conn.cursor() as cursor: + cursor.execute( + f"DELETE FROM alcs.notification_document WHERE audit_created_by = '{OATS_ETL_USER}';" + ) + conn.commit() + logger.info(f"Deleted items count = {cursor.rowcount}") diff --git a/bin/migrate-oats-data/documents/post_launch/migrate_documents.py b/bin/migrate-oats-data/documents/post_launch/migrate_documents.py index 08d6181d6a..a2c2544e3e 100644 --- a/bin/migrate-oats-data/documents/post_launch/migrate_documents.py +++ b/bin/migrate-oats-data/documents/post_launch/migrate_documents.py @@ -16,3 +16,13 @@ def import_documents(batch_size): def clean_documents(): clean_notification_documents() document_clean() + + +def import_prs_documents(batch_size): + # fill + return + + +def clean_pr_documents(): + # fill + return diff --git a/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_planning_review.py b/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_planning_review.py new file mode 100644 index 0000000000..9669ca8fac --- /dev/null +++ b/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_planning_review.py @@ -0,0 +1,189 @@ +from common import ( + setup_and_get_logger, + BATCH_UPLOAD_SIZE, + OATS_ETL_USER, + add_timezone_and_keep_date_part, + OatsToAlcsDocumentSourceCode, +) +from db import inject_conn_pool +from psycopg2.extras import RealDictCursor +import os + +etl_name = "import_pr_documents_from_oats" +logger = setup_and_get_logger(etl_name) + +""" + This script connects to postgress version of OATS DB and transfers data from OATS documents table to ALCS documents table. + + NOTE: + Before performing document import you need to import SRWs from oats. +""" + + +@inject_conn_pool +def import_oats_pr_documents(conn=None, batch_size=BATCH_UPLOAD_SIZE): + """ + function uses a decorator pattern @inject_conn_pool to inject a database connection pool to the function. It fetches the total count of documents and prints it to the console. Then, it fetches the documents to insert in batches using document IDs, constructs an insert query, and processes them. + """ + logger.info(f"Start {etl_name}") + with conn.cursor(cursor_factory=RealDictCursor) as cursor: + with open( + "documents/post_launch/sql/planning_review/oats_documents_to_alcs_documents_count.sql", + "r", + encoding="utf-8", + ) as sql_file: + count_query = sql_file.read() + cursor.execute(count_query) + total_count = dict(cursor.fetchone())["count"] + logger.info(f"Total count of documents to transfer: {total_count}") + + failed_inserts_count = 0 + successful_inserts_count = 0 + last_document_id = 0 + + with open( + "documents/post_launch/sql/planning_review/oats_documents_to_alcs_documents.sql", + "r", + encoding="utf-8", + ) as sql_file: + documents_to_insert_sql = sql_file.read() + while True: + cursor.execute( + f"{documents_to_insert_sql} WHERE document_id > {last_document_id} ORDER BY document_id;" + ) + rows = cursor.fetchmany(batch_size) + if not rows: + break + try: + documents_to_be_inserted_count = len(rows) + + _insert_records(conn, cursor, rows) + + last_document_id = dict(rows[-1])["document_id"] + successful_inserts_count = ( + successful_inserts_count + documents_to_be_inserted_count + ) + + logger.debug( + f"retrieved/inserted items count: {documents_to_be_inserted_count}; total successfully inserted/updated documents so far {successful_inserts_count}; last inserted oats_document_id: {last_document_id}" + ) + except Exception as e: + conn.rollback() + logger.exception(f"Error {e}") + failed_inserts_count += len(rows) + last_document_id = last_document_id + 1 + + logger.info(f"Total amount of successful inserts: {successful_inserts_count}") + logger.info(f"Total amount of failed inserts: {failed_inserts_count}") + + +def _insert_records(conn, cursor, rows): + number_of_rows_to_insert = len(rows) + + if number_of_rows_to_insert > 0: + insert_query = _compile_insert_query(number_of_rows_to_insert) + rows_to_insert = _prepare_data_to_insert(rows) + cursor.execute(insert_query, rows_to_insert) + conn.commit() + + +def _compile_insert_query(number_of_rows_to_insert): + documents_to_insert = ",".join(["%s"] * number_of_rows_to_insert) + return f""" + INSERT INTO alcs."document"( + oats_document_id, + file_name, + oats_planning_review_id, + audit_created_by, + file_key, + mime_type, + tags, + "system", + uploaded_at, + source + ) + VALUES{documents_to_insert} + ON CONFLICT (oats_document_id) DO UPDATE SET + oats_document_id = EXCLUDED.oats_document_id, + file_name = EXCLUDED.file_name, + oats_planning_review_id = EXCLUDED.oats_planning_review_id, + audit_created_by = EXCLUDED.audit_created_by, + file_key = EXCLUDED.file_key, + mime_type = EXCLUDED.mime_type, + tags = EXCLUDED.tags, + "system" = EXCLUDED."system", + uploaded_at = EXCLUDED.uploaded_at, + source = EXCLUDED.source; + """ + + +def _prepare_data_to_insert(rows): + row_without_last_element = [] + for row in rows: + mapped_row = _map_data(row) + row_without_last_element.append(tuple(mapped_row.values())) + + return row_without_last_element + + +def _map_data(row): + return { + "oats_document_id": row["oats_document_id"], + "file_name": row["file_name"], + "oats_planning_review_id": row["oats_planning_review_id"], + "audit_created_by": OATS_ETL_USER, + "file_key": row["file_key"], + "mime_type": _get_mime_type(row), + "tags": row["tags"], + "system": _map_system(row), + "file_upload_date": _get_upload_date(row), + "file_source": _get_document_source(row), + } + + +def _map_system(row): + who_created = row["who_created"] + if who_created in ("PROXY_OATS_LOCGOV", "PROXY_OATS_APPLICANT"): + sys = "OATS_P" + else: + sys = "OATS" + return sys + + +def _get_upload_date(data): + upload_date = data.get("uploaded_date", "") + created_date = data.get("when_created", "") + if upload_date: + return add_timezone_and_keep_date_part(upload_date) + else: + return add_timezone_and_keep_date_part(created_date) + + +def _get_document_source(data): + source = data.get("document_source_code", "") + if source: + source = str(OatsToAlcsDocumentSourceCode[source].value) + + return source + + +def _get_mime_type(data): + file_name = data.get("file_name", "") + extension = os.path.splitext(file_name)[-1].lower().strip() + if extension == ".pdf": + return "planning_review/pdf" + else: + return "planning_review/octet-stream" + + +@inject_conn_pool +def document_pr_clean(conn=None): + logger.info("Start planning review related documents cleaning") + with conn.cursor() as cursor: + cursor.execute( + f"DELETE FROM alcs.document WHERE audit_created_by = '{OATS_ETL_USER}' AND oats_planning_review_id IS NOT NULL AND audit_created_at > '2024-02-08';" + ) + conn.commit() + logger.info(f"Deleted items count = {cursor.rowcount}") + + conn.commit() diff --git a/bin/migrate-oats-data/documents/post_launch/sql/planning_review/alcs_documents_to_planning_review_documents.sql b/bin/migrate-oats-data/documents/post_launch/sql/planning_review/alcs_documents_to_planning_review_documents.sql new file mode 100644 index 0000000000..88b9d43f78 --- /dev/null +++ b/bin/migrate-oats-data/documents/post_launch/sql/planning_review/alcs_documents_to_planning_review_documents.sql @@ -0,0 +1,32 @@ +with oats_documents_to_map as ( + select n.uuid as planning_review_uuid, + d.uuid as document_uuid, + adc.code, + publicly_viewable_ind as is_public, + app_lg_viewable_ind as is_app_lg, + od.document_id as oats_document_id, + od.alr_planning_review_id as oats_planning_review_id, + od."description" + from oats.oats_documents od + join alcs."document" d on d.oats_document_id = od.document_id::text + join alcs.document_code adc on adc.oats_code = od.document_code + join alcs.planning_review n on n.file_number = od.planning_review_id::text +) +select otm.planning_review_uuid, + otm.document_uuid, + otm.code as type_code, + ( + case + when is_public = 'Y' + and is_app_lg = 'Y' then '{P, A, C, G}'::text [] + when is_public = 'Y' then '{P}'::text [] + when is_app_lg = 'Y' then '{A, C, G}'::text [] + else '{}'::text [] + end + ) as visibility_flags, + oats_document_id, + oats_planning_review_id, + plan_no, + control_no, + otm."description" +from oats_documents_to_map otm \ No newline at end of file diff --git a/bin/migrate-oats-data/documents/post_launch/sql/planning_review/alcs_documents_to_planning_review_documents_count.sql b/bin/migrate-oats-data/documents/post_launch/sql/planning_review/alcs_documents_to_planning_review_documents_count.sql new file mode 100644 index 0000000000..d49ccc5249 --- /dev/null +++ b/bin/migrate-oats-data/documents/post_launch/sql/planning_review/alcs_documents_to_planning_review_documents_count.sql @@ -0,0 +1,23 @@ +with oats_documents_to_map as ( + select + n.uuid as planning_review_uuid, + d.uuid as document_uuid, + adc.code, + publicly_viewable_ind as is_public, + app_lg_viewable_ind as is_app_lg, + od.document_id as oats_document_id, + od.planning_review_id as oats_planning_review_id + from oats.oats_documents od + + join alcs."document" d + on d.oats_document_id = od.document_id::text + + join alcs.document_code adc + on adc.oats_code = od.document_code + + join alcs.planning_review n + on n.file_number = od.planning_review_id::text +) +select + count(*) +from oats_documents_to_map otm \ No newline at end of file diff --git a/bin/migrate-oats-data/documents/post_launch/sql/planning_review/oats_documents_to_alcs_documents.sql b/bin/migrate-oats-data/documents/post_launch/sql/planning_review/oats_documents_to_alcs_documents.sql new file mode 100644 index 0000000000..5a0137d490 --- /dev/null +++ b/bin/migrate-oats-data/documents/post_launch/sql/planning_review/oats_documents_to_alcs_documents.sql @@ -0,0 +1,28 @@ +with oats_documents_to_insert as ( + select od.planning_review_id, + document_id, + document_code, + file_name, + od.who_created, + od.document_source_code, + od.uploaded_date, + od.when_created + from oats.oats_documents od + where od.alr_application_id is null + and document_code is not null + and od.issue_id is null + and od.planning_review_id is not null +) +SELECT document_id::text AS oats_document_id, + file_name, + planning_review_id::text AS oats_planning_review_id, + 'migrate/planning_review/' || planning_review_id || '/' || document_id || '_' || file_name AS file_key, + 'pdf' AS mime_type, + '{"ORCS Classification: 85400-20"}'::text [] as tags, + who_created, + document_source_code, + uploaded_date, + when_created, + document_id +FROM oats_documents_to_insert oti + JOIN alcs.planning_review n ON n.file_number = oti.planning_review_id::text \ No newline at end of file diff --git a/bin/migrate-oats-data/documents/post_launch/sql/planning_review/oats_documents_to_alcs_documents_count.sql b/bin/migrate-oats-data/documents/post_launch/sql/planning_review/oats_documents_to_alcs_documents_count.sql new file mode 100644 index 0000000000..7c75c6b5bd --- /dev/null +++ b/bin/migrate-oats-data/documents/post_launch/sql/planning_review/oats_documents_to_alcs_documents_count.sql @@ -0,0 +1,20 @@ +with + oats_documents_to_insert as ( + select + od.planning_review_id, + document_id, + document_code, + file_name + from + oats.oats_documents od + where + od.alr_application_id is null + and document_code is not null + and od.issue_id is null + and od.planning_review_id is not null + ) +SELECT + count(*) +FROM + oats_documents_to_insert oti + JOIN alcs.planning_review n ON n.file_number = oti.planning_review_id::text \ No newline at end of file diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.entity.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.entity.ts index 3ae3f2c7b2..2a255485c9 100644 --- a/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.entity.ts +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.entity.ts @@ -58,6 +58,22 @@ export class PlanningReviewDocument extends BaseEntity { @Column({ nullable: true, type: 'int' }) evidentiaryRecordSorting?: number | null; + @Column({ + type: 'text', + nullable: true, + comment: + 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.planning_review to alcs.planning_review_document.', + }) + oatsPlanningReviewId?: string | null; + + @Column({ + type: 'text', + nullable: true, + comment: + 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.documents/alcs.documents to alcs.planning_review_document.', + }) + oatsDocumentId?: string | null; + @OneToOne(() => Document) @JoinColumn() document: Document; diff --git a/services/apps/alcs/src/document/document.entity.ts b/services/apps/alcs/src/document/document.entity.ts index a57596fc0d..b6159d99e7 100644 --- a/services/apps/alcs/src/document/document.entity.ts +++ b/services/apps/alcs/src/document/document.entity.ts @@ -54,6 +54,13 @@ export class Document extends Base { }) oatsApplicationId?: string | null; + @Column({ + nullable: true, + type: 'text', + comment: 'used only for oats etl process', + }) + oatsPlanningReviewId?: string | null; + @Column({ nullable: true, type: 'text', diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1711670649388-add_planning_review_id_to_documents.ts b/services/apps/alcs/src/providers/typeorm/migrations/1711670649388-add_planning_review_id_to_documents.ts new file mode 100644 index 0000000000..210903c9f2 --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1711670649388-add_planning_review_id_to_documents.ts @@ -0,0 +1,47 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddPlanningReviewIdToDocuments1711670649388 + implements MigrationInterface +{ + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_document" ADD "oats_document_id" text`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_document" ADD "oats_planning_review_id" text`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."document" ADD "oats_planning_review_id" text`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "alcs"."planning_review_document"."oats_document_id" IS 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.documents/alcs.documents to alcs.planning_review_document.'`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "alcs"."planning_review_document"."oats_planning_review_id" IS 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.documents/alcs.documents to alcs.planning_review_document.'`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "alcs"."document"."oats_planning_review_id" IS 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.documents/alcs.documents to alcs.document.'`, + ); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `COMMENT ON COLUMN "alcs"."planning_review_document"."oats_document_id" IS 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.documents/alcs.documents to alcs.planning_review_document.'`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "alcs"."planning_review_document"."oats_planning_review_id" IS 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.documents/alcs.documents to alcs.planning_review_document.'`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_document" DROP COLUMN "oats_document_id"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_document" DROP COLUMN "oats_planning_review_id"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."document" DROP COLUMN "oats_planning_review_id"`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "alcs"."document"."oats_planning_review_id" IS 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.documents/alcs.documents to alcs.document.'`, + ); + } +} From 0eb96a4223a2bb62687eac1193ec46a3e00d79cc Mon Sep 17 00:00:00 2001 From: mhuseinov <61513701+mhuseinov@users.noreply.github.com> Date: Tue, 2 Apr 2024 08:45:09 -0700 Subject: [PATCH 069/153] Feature/alcs 1446 inquiry advanced search (#1553) inquiry advanced search fix typo in inquiry board code bonus: slight increase of search API coverage --- .../inquiry-search-table.component.html | 82 ++++++ .../inquiry-search-table.component.scss | 0 .../inquiry-search-table.component.spec.ts | 34 +++ .../inquiry-search-table.component.ts | 81 ++++++ .../app/features/search/search.component.html | 18 +- .../app/features/search/search.component.ts | 24 +- .../src/app/features/search/search.module.ts | 2 + .../file-type-data-source.service.ts | 26 ++ .../src/app/services/search/search.dto.ts | 13 + .../services/search/search.service.spec.ts | 29 ++ .../src/app/services/search/search.service.ts | 18 +- .../header/search-bar/search-bar.component.ts | 13 +- .../apps/alcs/src/alcs/board/board.dto.ts | 2 +- .../apps/alcs/src/alcs/inquiry/inquiry.dto.ts | 10 + ...pplication-advanced-search.service.spec.ts | 42 ++- .../inquiry-advanced-search.service.spec.ts | 134 +++++++++ .../inquiry-advanced-search.service.ts | 262 ++++++++++++++++++ .../inquiry/inquiry-search-view.entity.ts | 80 ++++++ ...-of-intent-advanced-search.service.spec.ts | 37 ++- ...tification-advanced-search.service.spec.ts | 26 ++ ...ing-review-advanced-search.service.spec.ts | 26 +- .../src/alcs/search/search.controller.spec.ts | 50 +++- .../alcs/src/alcs/search/search.controller.ts | 106 +++++++ .../apps/alcs/src/alcs/search/search.dto.ts | 25 +- .../alcs/src/alcs/search/search.module.ts | 6 + .../src/alcs/search/search.service.spec.ts | 40 ++- .../alcs/src/alcs/search/search.service.ts | 14 + .../1711476530253-inquiry_advanced_search.ts | 28 ++ .../1711565723047-update_inquiry_board.ts | 13 + 29 files changed, 1208 insertions(+), 33 deletions(-) create mode 100644 alcs-frontend/src/app/features/search/inquiry-search-table/inquiry-search-table.component.html create mode 100644 alcs-frontend/src/app/features/search/inquiry-search-table/inquiry-search-table.component.scss create mode 100644 alcs-frontend/src/app/features/search/inquiry-search-table/inquiry-search-table.component.spec.ts create mode 100644 alcs-frontend/src/app/features/search/inquiry-search-table/inquiry-search-table.component.ts create mode 100644 services/apps/alcs/src/alcs/search/inquiry/inquiry-advanced-search.service.spec.ts create mode 100644 services/apps/alcs/src/alcs/search/inquiry/inquiry-advanced-search.service.ts create mode 100644 services/apps/alcs/src/alcs/search/inquiry/inquiry-search-view.entity.ts create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1711476530253-inquiry_advanced_search.ts create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1711565723047-update_inquiry_board.ts diff --git a/alcs-frontend/src/app/features/search/inquiry-search-table/inquiry-search-table.component.html b/alcs-frontend/src/app/features/search/inquiry-search-table/inquiry-search-table.component.html new file mode 100644 index 0000000000..5f2a622796 --- /dev/null +++ b/alcs-frontend/src/app/features/search/inquiry-search-table/inquiry-search-table.component.html @@ -0,0 +1,82 @@ +<ng-container *ngIf="!isLoading && totalCount"> + <table + mat-table + [dataSource]="dataSource" + class="mat-elevation-z2 table" + matSort + [matSortActive]="sortField" + [matSortDirection]="sortDirection" + (matSortChange)="onSortChange($event)" + matSortDisableClear + > + <ng-container matColumnDef="fileId"> + <th mat-header-cell *matHeaderCellDef mat-sort-header>File ID</th> + <td mat-cell *matCellDef="let element"> + {{ element.fileNumber | emptyColumn }} + </td> + </ng-container> + + <ng-container matColumnDef="dateSubmitted"> + <th mat-header-cell *matHeaderCellDef mat-sort-header>Date Submitted to ALC</th> + <td mat-cell *matCellDef="let element"> + {{ element.dateSubmitted | date | emptyColumn }} + </td> + </ng-container> + + <ng-container matColumnDef="applicant"> + <th mat-header-cell *matHeaderCellDef mat-sort-header>Inquirer Name</th> + <td mat-cell *matCellDef="let element"> + {{ element.inquirerLastName | emptyColumn }} + </td> + </ng-container> + + <ng-container matColumnDef="type"> + <th mat-header-cell *matHeaderCellDef mat-sort-header>Type</th> + + <td mat-cell *matCellDef="let element"> + <app-application-type-pill [useShortLabel]="true" [type]="element.type" /> + </td> + </ng-container> + + <ng-container matColumnDef="government"> + <th mat-header-cell *matHeaderCellDef mat-sort-header>Local/First Nation Government</th> + <td mat-cell *matCellDef="let element"> + {{ element.localGovernmentName | emptyColumn }} + </td> + </ng-container> + + <ng-container matColumnDef="status"> + <th mat-header-cell *matHeaderCellDef mat-sort-header>Status</th> + <td mat-cell *matCellDef="let element"> + <app-application-type-pill *ngIf="element.open" [type]="OPEN_TYPE" /> + <app-application-type-pill *ngIf="!element.open" [type]="CLOSED_TYPE" /> + </td> + </ng-container> + + <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> + <tr (click)="onSelectRecord(row)" mat-row *matRowDef="let row; columns: displayedColumns"></tr> + + <tr class="mat-row no-data" *matNoDataRow> + <td class="text-center" colspan="4"> + <div><b>No inquiries found.</b></div> + <div>Please adjust criteria and try again.</div> + </td> + </tr> + </table> + <mat-paginator + [length]="totalCount" + [pageIndex]="pageIndex" + [pageSize]="itemsPerPage" + [pageSizeOptions]="[20, 50, 100]" + (page)="onPageChange($event)" + aria-label="Select page" + ></mat-paginator> +</ng-container> +<div *ngIf="isLoading" class="center"> + <mat-spinner /> +</div> + +<div class="no-data" *ngIf="totalCount === 0"> + <div><b>No inquiries found.</b></div> + <div>Please adjust criteria and try again.</div> +</div> diff --git a/alcs-frontend/src/app/features/search/inquiry-search-table/inquiry-search-table.component.scss b/alcs-frontend/src/app/features/search/inquiry-search-table/inquiry-search-table.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/alcs-frontend/src/app/features/search/inquiry-search-table/inquiry-search-table.component.spec.ts b/alcs-frontend/src/app/features/search/inquiry-search-table/inquiry-search-table.component.spec.ts new file mode 100644 index 0000000000..f112e50ba4 --- /dev/null +++ b/alcs-frontend/src/app/features/search/inquiry-search-table/inquiry-search-table.component.spec.ts @@ -0,0 +1,34 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { Router } from '@angular/router'; +import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { InquirySearchTableComponent } from './inquiry-search-table.component'; + +describe('InquirySearchTableComponent', () => { + let component: InquirySearchTableComponent; + let fixture: ComponentFixture<InquirySearchTableComponent>; + let mockRouter: DeepMocked<Router>; + + beforeEach(() => { + mockRouter = createMock(); + + TestBed.configureTestingModule({ + declarations: [InquirySearchTableComponent], + providers: [ + { + provide: Router, + useValue: mockRouter, + }, + ], + schemas: [NO_ERRORS_SCHEMA], + }); + fixture = TestBed.createComponent(InquirySearchTableComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alcs-frontend/src/app/features/search/inquiry-search-table/inquiry-search-table.component.ts b/alcs-frontend/src/app/features/search/inquiry-search-table/inquiry-search-table.component.ts new file mode 100644 index 0000000000..d8a054aee8 --- /dev/null +++ b/alcs-frontend/src/app/features/search/inquiry-search-table/inquiry-search-table.component.ts @@ -0,0 +1,81 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { InquirySearchResultDto } from '../../../services/search/search.dto'; +import { PageEvent } from '@angular/material/paginator'; +import { Sort, SortDirection } from '@angular/material/sort'; +import { Router } from '@angular/router'; +import { CLOSED_PR_LABEL, OPEN_PR_LABEL } from '../../../shared/application-type-pill/application-type-pill.constants'; +import { TableChange } from '../search.interface'; + +interface SearchResult { + fileNumber: string; + applicant: string; + dateSubmitted: string; + localGovernmentName?: string; + inquiryUuid: string; + board?: string; + class: string; +} + +@Component({ + selector: 'app-inquiry-search-table', + templateUrl: './inquiry-search-table.component.html', + styleUrls: ['./inquiry-search-table.component.scss'], +}) +export class InquirySearchTableComponent { + _planningReviews: InquirySearchResultDto[] = []; + @Input() set inquiries(inquiries: InquirySearchResultDto[]) { + this._planningReviews = inquiries; + this.isLoading = false; + this.dataSource = inquiries; + } + + OPEN_TYPE = OPEN_PR_LABEL; + CLOSED_TYPE = CLOSED_PR_LABEL; + + @Input() totalCount: number | undefined; + @Input() pageIndex: number = 0; + + @Output() tableChange = new EventEmitter<TableChange>(); + + displayedColumns = ['fileId', 'dateSubmitted', 'applicant', 'type', 'government', 'status']; + dataSource: InquirySearchResultDto[] = []; + + itemsPerPage = 20; + total = 0; + sortDirection: SortDirection = 'desc'; + sortField = 'fileId'; + isLoading = false; + + constructor(private router: Router) {} + + onTableChange() { + this.isLoading = true; + this.tableChange.emit({ + pageIndex: this.pageIndex, + itemsPerPage: this.itemsPerPage, + sortDirection: this.sortDirection, + sortField: this.sortField, + tableType: 'INQR', + }); + } + + onPageChange($event: PageEvent) { + this.pageIndex = $event.pageIndex; + this.itemsPerPage = $event.pageSize; + + this.onTableChange(); + } + + onSortChange(sort: Sort) { + this.pageIndex = 0; + this.sortDirection = sort.direction; + this.sortField = sort.active; + this.onTableChange(); + } + + onSelectRecord(record: SearchResult) { + const url = this.router.serializeUrl(this.router.createUrlTree([`/inquiry/${record.fileNumber}`])); + + window.open(url, '_blank'); + } +} diff --git a/alcs-frontend/src/app/features/search/search.component.html b/alcs-frontend/src/app/features/search/search.component.html index d0dc989bc4..e8e8392c47 100644 --- a/alcs-frontend/src/app/features/search/search.component.html +++ b/alcs-frontend/src/app/features/search/search.component.html @@ -21,7 +21,8 @@ <h6 class="subheading">Provide one or more of the following criteria:</h6> <input id="name" matInput formControlName="name" minlength="3" /> </mat-form-field> <div class="subtext"> - Search by Primary Contact, Parcel Owner, Organization, Ministry or Department, SRW Transferee, PR Document Name + Search by Primary Contact, Parcel Owner, Organization, Ministry or Department, Inquirer, SRW Transferee, PR Document + Name </div> <div *ngIf="nameControl.invalid && (nameControl.dirty || nameControl.touched)" class="field-error"> <mat-icon>warning</mat-icon> @@ -308,5 +309,20 @@ <h2 class="search-title">Search Results:</h2> <mat-spinner class="spinner" *ngIf="isLoading"></mat-spinner> </div> </mat-tab> + + <mat-tab> + <ng-template mat-tab-label> Inquiries: {{ inquiriesTotal }} </ng-template> + <app-inquiry-search-table + *ngIf="!isLoading" + [inquiries]="inquiries" + [totalCount]="inquiriesTotal" + [pageIndex]="pageIndex" + (tableChange)="onTableChange($event)" + > + </app-inquiry-search-table> + <div class="center"> + <mat-spinner class="spinner" *ngIf="isLoading"></mat-spinner> + </div> + </mat-tab> </mat-tab-group> </div> diff --git a/alcs-frontend/src/app/features/search/search.component.ts b/alcs-frontend/src/app/features/search/search.component.ts index d4e97a5b4d..9ff767d60e 100644 --- a/alcs-frontend/src/app/features/search/search.component.ts +++ b/alcs-frontend/src/app/features/search/search.component.ts @@ -1,13 +1,12 @@ import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; -import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MatPaginator } from '@angular/material/paginator'; import { MatSort, SortDirection } from '@angular/material/sort'; import { MatTabGroup } from '@angular/material/tabs'; import { Title } from '@angular/platform-browser'; import { ActivatedRoute } from '@angular/router'; import moment from 'moment'; -import { combineLatestWith, map, Observable, startWith, Subject, takeUntil } from 'rxjs'; +import { Observable, Subject, combineLatestWith, map, startWith, 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'; @@ -20,6 +19,7 @@ import { NotificationSubmissionStatusDto } from '../../services/notification/not import { AdvancedSearchResponseDto, ApplicationSearchResultDto, + InquirySearchResultDto, NoticeOfIntentSearchResultDto, NotificationSearchResultDto, PlanningReviewSearchResultDto, @@ -59,6 +59,9 @@ export class SearchComponent implements OnInit, OnDestroy { notifications: NotificationSearchResultDto[] = []; notificationTotal = 0; + inquiries: InquirySearchResultDto[] = []; + inquiriesTotal = 0; + isSearchExpanded = false; pageIndex = 0; itemsPerPage = 20; @@ -292,6 +295,14 @@ export class SearchComponent implements OnInit, OnDestroy { this.notificationTotal = result?.total ?? 0; } + async onInquirySearch() { + const searchParams = this.getSearchParams(); + const result = await this.searchService.advancedSearchInquiryFetch(searchParams); + + this.inquiries = result?.data ?? []; + this.inquiriesTotal = result?.total ?? 0; + } + async onTableChange(event: TableChange) { this.pageIndex = event.pageIndex; this.itemsPerPage = event.itemsPerPage; @@ -311,6 +322,9 @@ export class SearchComponent implements OnInit, OnDestroy { case 'NOTI': await this.onNotificationSearch(); break; + case 'INQR': + await this.onInquirySearch(); + break; default: this.toastService.showErrorToast('Not implemented'); } @@ -342,10 +356,12 @@ export class SearchComponent implements OnInit, OnDestroy { noticeOfIntents: [], planningReviews: [], notifications: [], + inquiries: [], totalApplications: 0, totalNoticeOfIntents: 0, totalNotifications: 0, totalPlanningReviews: 0, + totalInquiries: 0, }; } @@ -360,6 +376,9 @@ export class SearchComponent implements OnInit, OnDestroy { this.notifications = searchResult.notifications; this.notificationTotal = searchResult.totalNotifications; + + this.inquiries = searchResult.inquiries; + this.inquiriesTotal = searchResult.totalInquiries; } private setActiveTab() { @@ -369,6 +388,7 @@ export class SearchComponent implements OnInit, OnDestroy { this.noticeOfIntentTotal, this.planningReviewsTotal, this.notificationTotal, + this.inquiriesTotal, ]; this.tabGroup.selectedIndex = searchCounts.indexOf(Math.max(...searchCounts)); diff --git a/alcs-frontend/src/app/features/search/search.module.ts b/alcs-frontend/src/app/features/search/search.module.ts index e26045094e..ca8a3a27c4 100644 --- a/alcs-frontend/src/app/features/search/search.module.ts +++ b/alcs-frontend/src/app/features/search/search.module.ts @@ -11,6 +11,7 @@ import { PlanningReviewSearchTableComponent } from './planning-review-search-tab import { NoticeOfIntentSearchTableComponent } from './notice-of-intent-search-table/notice-of-intent-search-table.component'; import { NotificationSearchTableComponent } from './notification-search-table/notification-search-table.component'; import { SearchComponent } from './search.component'; +import { InquirySearchTableComponent } from './inquiry-search-table/inquiry-search-table.component'; const routes: Routes = [ { @@ -27,6 +28,7 @@ const routes: Routes = [ PlanningReviewSearchTableComponent, NotificationSearchTableComponent, FileTypeFilterDropDownComponent, + InquirySearchTableComponent, ], imports: [ CommonModule, diff --git a/alcs-frontend/src/app/services/search/file-type/file-type-data-source.service.ts b/alcs-frontend/src/app/services/search/file-type/file-type-data-source.service.ts index de4c3c2b67..fd81eb8949 100644 --- a/alcs-frontend/src/app/services/search/file-type/file-type-data-source.service.ts +++ b/alcs-frontend/src/app/services/search/file-type/file-type-data-source.service.ts @@ -101,6 +101,32 @@ const TREE_DATA: TreeNode[] = [ }, ], }, + { + item: { label: 'Inquiry', value: null }, + children: [ + { + item: { label: 'ALR Boundary Definition', value: 'ABDF' }, + }, + { + item: { label: 'Area of Interest', value: 'AOIN' }, + }, + { + item: { label: 'General Correspondence', value: 'GENC' }, + }, + { + item: { label: 'Inquiry for Investigation', value: 'INVN' }, + }, + { + item: { label: 'Parcel Under 2 Acres', value: 'P2AC' }, + }, + { + item: { label: 'Referral', value: 'REFR' }, + }, + { + item: { label: 'Subdivision by Approving Officer', value: 'SAOF' }, + }, + ], + }, ]; @Injectable({ providedIn: 'root' }) diff --git a/alcs-frontend/src/app/services/search/search.dto.ts b/alcs-frontend/src/app/services/search/search.dto.ts index ba2514e168..19a73d1614 100644 --- a/alcs-frontend/src/app/services/search/search.dto.ts +++ b/alcs-frontend/src/app/services/search/search.dto.ts @@ -22,20 +22,33 @@ export interface PlanningReviewSearchResultDto { documentName: string | null; referenceId: string | null; localGovernmentName: string | null; + dateSubmitted: number; fileNumber: string; class: 'PLAN'; open: boolean; } +export interface InquirySearchResultDto { + type: string | null; + documentName: string | null; + referenceId: string | null; + localGovernmentName: string | null; + fileNumber: string; + class: 'INQR'; + open: boolean; +} + export interface AdvancedSearchResponseDto { applications: ApplicationSearchResultDto[]; noticeOfIntents: NoticeOfIntentSearchResultDto[]; planningReviews: PlanningReviewSearchResultDto[]; notifications: NotificationSearchResultDto[]; + inquiries: InquirySearchResultDto[]; totalApplications: number; totalNoticeOfIntents: number; totalPlanningReviews: number; totalNotifications: number; + totalInquiries: number; } export interface AdvancedSearchEntityResponseDto<T> { diff --git a/alcs-frontend/src/app/services/search/search.service.spec.ts b/alcs-frontend/src/app/services/search/search.service.spec.ts index 4207669a49..3deb6605af 100644 --- a/alcs-frontend/src/app/services/search/search.service.spec.ts +++ b/alcs-frontend/src/app/services/search/search.service.spec.ts @@ -51,6 +51,8 @@ describe('SearchService', () => { totalNonApplications: 0, planningReviews: [], totalPlanningReviews: 0, + inquiries: [], + totalInquiries: 0, }; const mockSearchRequestDto = { @@ -121,6 +123,8 @@ describe('SearchService', () => { expect(res?.noticeOfIntents).toEqual([]); expect(res?.totalPlanningReviews).toEqual(0); expect(res?.planningReviews).toEqual([]); + expect(res?.totalInquiries).toEqual(0); + expect(res?.inquiries).toEqual([]); }); it('should show an error toast message if search fails', async () => { @@ -211,4 +215,29 @@ describe('SearchService', () => { expect(res).toBeUndefined(); expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); }); + + it('should fetch Inquiry advanced search results by AdvancedSearchRequestDto', async () => { + mockHttpClient.post.mockReturnValue(of(mockAdvancedSearchEntityResult)); + + const res = await service.advancedSearchInquiryFetch(mockSearchRequestDto); + + expect(mockHttpClient.post).toHaveBeenCalledTimes(1); + expect(res).toBeDefined(); + expect(res?.total).toEqual(0); + expect(res?.data).toEqual([]); + }); + + it('should show an error toast message if Inquiry advanced search fails', async () => { + mockHttpClient.post.mockReturnValue( + throwError(() => { + new Error(''); + }), + ); + + const res = await service.advancedSearchInquiryFetch(mockSearchRequestDto); + + expect(mockHttpClient.post).toHaveBeenCalledTimes(1); + expect(res).toBeUndefined(); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); }); diff --git a/alcs-frontend/src/app/services/search/search.service.ts b/alcs-frontend/src/app/services/search/search.service.ts index 2c4e03c40e..9a019340bf 100644 --- a/alcs-frontend/src/app/services/search/search.service.ts +++ b/alcs-frontend/src/app/services/search/search.service.ts @@ -7,9 +7,10 @@ import { AdvancedSearchEntityResponseDto, AdvancedSearchResponseDto, ApplicationSearchResultDto, - PlanningReviewSearchResultDto, + InquirySearchResultDto, NoticeOfIntentSearchResultDto, NotificationSearchResultDto, + PlanningReviewSearchResultDto, SearchRequestDto, SearchResultDto, } from './search.dto'; @@ -104,4 +105,19 @@ export class SearchService { return undefined; } } + + async advancedSearchInquiryFetch(searchDto: SearchRequestDto) { + try { + return await firstValueFrom( + this.http.post<AdvancedSearchEntityResponseDto<InquirySearchResultDto>>( + `${this.baseUrl}/advanced/inquiries`, + searchDto, + ), + ); + } catch (e) { + console.error(e); + this.toastService.showErrorToast(`Search failed. Please refresh the page and try again`); + return undefined; + } + } } diff --git a/alcs-frontend/src/app/shared/header/search-bar/search-bar.component.ts b/alcs-frontend/src/app/shared/header/search-bar/search-bar.component.ts index eede68d038..d2c74f9367 100644 --- a/alcs-frontend/src/app/shared/header/search-bar/search-bar.component.ts +++ b/alcs-frontend/src/app/shared/header/search-bar/search-bar.component.ts @@ -1,9 +1,9 @@ +import { animate, style, transition, trigger } from '@angular/animations'; import { HttpErrorResponse } from '@angular/common/http'; import { AfterViewInit, Component, ElementRef, HostListener, QueryList, ViewChildren } from '@angular/core'; import { Router } from '@angular/router'; import { SearchService } from '../../../services/search/search.service'; import { ToastService } from '../../../services/toast/toast.service'; -import { animate, style, transition, trigger } from '@angular/animations'; @Component({ selector: 'app-search-bar', @@ -19,7 +19,11 @@ export class SearchBarComponent implements AfterViewInit { wasInside = false; @ViewChildren('searchInput') input!: QueryList<ElementRef>; - constructor(private toastService: ToastService, private router: Router, private searchService: SearchService) {} + constructor( + private toastService: ToastService, + private router: Router, + private searchService: SearchService, + ) {} @HostListener('click') clickInside() { @@ -88,10 +92,13 @@ export class SearchBarComponent implements AfterViewInit { case 'NOTI': await this.router.navigate(['notification', result.referenceId]); break; + case 'INQR': + await this.router.navigate(['inquiry', result.referenceId]); + break; case 'COV': case 'PLAN': await this.router.navigateByUrl( - `/board/${result.boardCode}?card=${result.referenceId}&type=${result.type}` + `/board/${result.boardCode}?card=${result.referenceId}&type=${result.type}`, ); break; default: diff --git a/services/apps/alcs/src/alcs/board/board.dto.ts b/services/apps/alcs/src/alcs/board/board.dto.ts index 50396bcd67..47308b9b83 100644 --- a/services/apps/alcs/src/alcs/board/board.dto.ts +++ b/services/apps/alcs/src/alcs/board/board.dto.ts @@ -6,7 +6,7 @@ export enum BOARD_CODES { SOIL = 'soil', EXECUTIVE_COMMITTEE = 'exec', REGIONAL_PLANNING = 'rppp', - INQUIRY = 'incr', + INQUIRY = 'inqr', } export class MinimalBoardDto { diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.dto.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.dto.ts index a644fde7c2..4ea6e0b526 100644 --- a/services/apps/alcs/src/alcs/inquiry/inquiry.dto.ts +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.dto.ts @@ -17,6 +17,16 @@ import { InquiryParcelUpdateDto, } from './inquiry-parcel/inquiry-parcel.dto'; +export enum INQUIRY_TYPES { + GENC = 'GENC', + INVN = 'INVN', + SAOF = 'SAOF', + REFR = 'REFR', + AOIN = 'AOIN', + P2AC = 'P2AC', + ABDF = 'ABDF', +} + export class InquiryTypeDto extends BaseCodeDto { @AutoMap() shortLabel: string; diff --git a/services/apps/alcs/src/alcs/search/application/application-advanced-search.service.spec.ts b/services/apps/alcs/src/alcs/search/application/application-advanced-search.service.spec.ts index 48ff11fcda..00c8e75046 100644 --- a/services/apps/alcs/src/alcs/search/application/application-advanced-search.service.spec.ts +++ b/services/apps/alcs/src/alcs/search/application/application-advanced-search.service.spec.ts @@ -13,6 +13,13 @@ describe('ApplicationAdvancedSearchService', () => { Repository<ApplicationSubmissionSearchView> >; let mockLocalGovernmentRepository: DeepMocked<Repository<LocalGovernment>>; + const sortFields = [ + 'fileId', + 'type', + 'government', + 'portalStatus', + 'dateSubmitted', + ]; const mockSearchRequestDto: SearchRequestDto = { fileNumber: '123', @@ -99,9 +106,9 @@ describe('ApplicationAdvancedSearchService', () => { expect(result).toEqual({ data: [], total: 0 }); expect( mockApplicationSubmissionSearchViewRepository.createQueryBuilder, - ).toBeCalledTimes(1); - expect(mockQuery.andWhere).toBeCalledTimes(15); - expect(mockQuery.where).toBeCalledTimes(1); + ).toHaveBeenCalledTimes(1); + expect(mockQuery.andWhere).toHaveBeenCalledTimes(15); + expect(mockQuery.where).toHaveBeenCalledTimes(1); }); it('should call compileApplicationSearchQuery method correctly', async () => { @@ -117,7 +124,7 @@ describe('ApplicationAdvancedSearchService', () => { ); expect(result).toEqual({ data: [], total: 0 }); - expect(compileApplicationSearchQuerySpy).toBeCalledWith( + expect(compileApplicationSearchQuerySpy).toHaveBeenCalledWith( mockSearchRequestDto, {}, ); @@ -125,4 +132,31 @@ describe('ApplicationAdvancedSearchService', () => { expect(mockQuery.offset).toHaveBeenCalledTimes(1); expect(mockQuery.limit).toHaveBeenCalledTimes(1); }); + + sortFields.forEach((sortField) => { + it(`should sort by ${sortField}`, async () => { + const mockQueryRunner = createMock<QueryRunner>(); + + const compileApplicationSearchQuerySpy = jest + .spyOn(service as any, 'compileApplicationSearchQuery') + .mockResolvedValue(mockQuery); + + mockSearchRequestDto.sortField = sortField; + mockSearchRequestDto.sortDirection = 'DESC'; + + const result = await service.searchApplications( + mockSearchRequestDto, + mockQueryRunner, + ); + + expect(result).toEqual({ data: [], total: 0 }); + expect(compileApplicationSearchQuerySpy).toHaveBeenCalledWith( + 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/inquiry/inquiry-advanced-search.service.spec.ts b/services/apps/alcs/src/alcs/search/inquiry/inquiry-advanced-search.service.spec.ts new file mode 100644 index 0000000000..6e1878d9ac --- /dev/null +++ b/services/apps/alcs/src/alcs/search/inquiry/inquiry-advanced-search.service.spec.ts @@ -0,0 +1,134 @@ +import { DeepMocked, createMock } from '@golevelup/nestjs-testing'; +import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { LocalGovernment } from '../../local-government/local-government.entity'; +import { SearchRequestDto } from '../search.dto'; +import { InquiryAdvancedSearchService } from './inquiry-advanced-search.service'; +import { InquirySearchView } from './inquiry-search-view.entity'; + +describe('InquiryAdvancedSearchService', () => { + let service: InquiryAdvancedSearchService; + let mockInquirySearchViewRepository: DeepMocked< + Repository<InquirySearchView> + >; + let mockLocalGovernmentRepository: DeepMocked<Repository<LocalGovernment>>; + + const sortFields = [ + 'fileId', + 'type', + 'government', + 'status', + 'dateSubmitted', + ]; + + const mockSearchDto: SearchRequestDto = { + fileNumber: '123', + governmentName: 'B', + regionCode: 'C', + name: 'D', + pid: 'E', + civicAddress: 'F', + dateSubmittedFrom: new Date('2020-10-10').getTime(), + dateSubmittedTo: new Date('2021-10-10').getTime(), + fileTypes: ['type1', 'type2'], + page: 1, + pageSize: 10, + sortField: 'applicant', + sortDirection: 'ASC', + }; + + let mockQuery: any = {}; + + beforeEach(async () => { + mockInquirySearchViewRepository = createMock(); + mockLocalGovernmentRepository = createMock(); + + mockQuery = { + getManyAndCount: jest.fn().mockResolvedValue([[], 0]), + orderBy: jest.fn().mockReturnThis(), + offset: jest.fn().mockReturnThis(), + limit: jest.fn().mockReturnThis(), + innerJoinAndMapOne: jest.fn().mockReturnThis(), + groupBy: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + andWhere: jest.fn().mockReturnThis(), + setParameters: jest.fn().mockReturnThis(), + leftJoin: jest.fn().mockReturnThis(), + withDeleted: jest.fn().mockReturnThis(), + }; + + const module: TestingModule = await Test.createTestingModule({ + providers: [ + InquiryAdvancedSearchService, + { + provide: getRepositoryToken(InquirySearchView), + useValue: mockInquirySearchViewRepository, + }, + { + provide: getRepositoryToken(LocalGovernment), + useValue: mockLocalGovernmentRepository, + }, + ], + }).compile(); + + service = module.get<InquiryAdvancedSearchService>( + InquiryAdvancedSearchService, + ); + + mockLocalGovernmentRepository.findOneByOrFail.mockResolvedValue( + new LocalGovernment(), + ); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + it('should successfully build a query using all search parameters defined', async () => { + mockInquirySearchViewRepository.createQueryBuilder.mockReturnValue( + mockQuery as any, + ); + + const result = await service.search(mockSearchDto); + + expect(result).toEqual({ data: [], total: 0 }); + expect( + mockInquirySearchViewRepository.createQueryBuilder, + ).toHaveBeenCalledTimes(1); + expect(mockQuery.andWhere).toHaveBeenCalledTimes(9); + }); + + it('should call compileInquirySearchQuery method correctly', async () => { + const compileSearchQuerySpy = jest + .spyOn(service as any, 'compileInquirySearchQuery') + .mockResolvedValue(mockQuery); + + const result = await service.search(mockSearchDto); + + expect(result).toEqual({ data: [], total: 0 }); + expect(compileSearchQuerySpy).toHaveBeenCalledWith(mockSearchDto); + expect(mockQuery.orderBy).toHaveBeenCalledTimes(1); + expect(mockQuery.offset).toHaveBeenCalledTimes(1); + expect(mockQuery.limit).toHaveBeenCalledTimes(1); + }); + + sortFields.forEach((sortField) => { + it(`should sort by ${sortField}`, async () => { + const compileSearchQuerySpy = jest + .spyOn(service as any, 'compileInquirySearchQuery') + .mockResolvedValue(mockQuery); + + mockSearchDto.sortField = sortField; + mockSearchDto.sortDirection = 'DESC'; + + const result = await service.search(mockSearchDto); + + expect(result).toEqual({ data: [], total: 0 }); + expect(compileSearchQuerySpy).toHaveBeenCalledWith(mockSearchDto); + expect(mockQuery.orderBy).toHaveBeenCalledTimes(1); + expect(mockQuery.offset).toHaveBeenCalledTimes(1); + expect(mockQuery.limit).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/services/apps/alcs/src/alcs/search/inquiry/inquiry-advanced-search.service.ts b/services/apps/alcs/src/alcs/search/inquiry/inquiry-advanced-search.service.ts new file mode 100644 index 0000000000..976a186ad5 --- /dev/null +++ b/services/apps/alcs/src/alcs/search/inquiry/inquiry-advanced-search.service.ts @@ -0,0 +1,262 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Brackets, Repository, SelectQueryBuilder } from 'typeorm'; +import { + getNextDayToPacific, + getStartOfDayToPacific, +} from '../../../utils/pacific-date-time-helper'; +import { formatStringToPostgresSearchStringArrayWithWildCard } from '../../../utils/search-helper'; +import { InquiryParcel } from '../../inquiry/inquiry-parcel/inquiry-parcel.entity'; +import { LocalGovernment } from '../../local-government/local-government.entity'; +import { AdvancedSearchResultDto, SearchRequestDto } from '../search.dto'; +import { InquirySearchView } from './inquiry-search-view.entity'; + +@Injectable() +export class InquiryAdvancedSearchService { + constructor( + @InjectRepository(InquirySearchView) + private inquirySearchViewRepository: Repository<InquirySearchView>, + @InjectRepository(LocalGovernment) + private governmentRepository: Repository<LocalGovernment>, + ) {} + + async search( + searchDto: SearchRequestDto, + ): Promise<AdvancedSearchResultDto<InquirySearchView[]>> { + let query = await this.compileInquirySearchQuery(searchDto); + + query = this.compileGroupBySearchQuery(query); + + 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 'fileId': + return '"inquirySearch"."file_number"'; + + case 'type': + return '"inquirySearch"."inquiry_type_code"'; + + case 'applicant': + return '"inquirySearch"."inquirer_last_name"'; + + case 'government': + return '"inquirySearch"."local_government_name"'; + + case 'status': + return '"inquirySearch"."open"'; + + default: + case 'dateSubmitted': + return '"inquirySearch"."date_submitted_to_alc"'; + } + } + + private compileGroupBySearchQuery( + query: SelectQueryBuilder<InquirySearchView>, + ) { + query = query + // FIXME: This is a quick fix for the search performance issues. It temporarily allows + // submissions with deleted submission types to be shown. For now, there are no + // deleted submission types, so this should be fine, but should be fixed soon. + .withDeleted() + .innerJoinAndMapOne( + 'inquirySearch.inquiryType', + 'inquirySearch.inquiryType', + 'inquiryType', + ) + .groupBy( + ` + "inquirySearch"."inquiry_uuid" + , "inquirySearch"."inquiry_region_code" + , "inquirySearch"."file_number" + , "inquirySearch"."local_government_uuid" + , "inquirySearch"."local_government_name" + , "inquirySearch"."inquirer_first_name" + , "inquirySearch"."inquirer_last_name" + , "inquirySearch"."inquirer_organization" + , "inquirySearch"."open" + , "inquirySearch"."inquiry_type_code" + , "inquirySearch"."date_submitted_to_alc" + , "inquiryType"."audit_deleted_date_at" + , "inquiryType"."audit_created_at" + , "inquiryType"."audit_updated_by" + , "inquiryType"."audit_updated_at" + , "inquiryType"."audit_created_by" + , "inquiryType"."short_label" + , "inquiryType"."label" + , "inquiryType"."code" + , "inquiryType"."html_description" + `, + ); + return query; + } + + private async compileInquirySearchQuery(searchDto: SearchRequestDto) { + let query = + this.inquirySearchViewRepository.createQueryBuilder('inquirySearch'); + + if (searchDto.fileNumber) { + query = query + .andWhere('inquirySearch.file_number = :fileNumber') + .setParameters({ fileNumber: searchDto.fileNumber ?? null }); + } + + if (searchDto.governmentName) { + const government = await this.governmentRepository.findOneByOrFail({ + name: searchDto.governmentName, + }); + + query = query.andWhere( + 'inquirySearch.local_government_uuid = :local_government_uuid', + { + local_government_uuid: government.uuid, + }, + ); + } + + if (searchDto.regionCode) { + query = query.andWhere( + 'inquirySearch.inquiry_region_code = :region_code', + { + region_code: searchDto.regionCode, + }, + ); + } + + query = this.compileSearchByNameQuery(searchDto, query); + query = this.compileParcelSearchQuery(searchDto, query); + query = this.compileDateRangeSearchQuery(searchDto, query); + query = this.compileFileTypeSearchQuery(searchDto, query); + + return query; + } + + private compileDateRangeSearchQuery(searchDto: SearchRequestDto, query) { + if (searchDto.dateSubmittedFrom) { + query = query.andWhere( + 'inquirySearch.date_submitted_to_alc >= :date_submitted_from_alc', + { + date_submitted_from_alc: getStartOfDayToPacific( + searchDto.dateSubmittedFrom, + ).toISOString(), + }, + ); + } + + if (searchDto.dateSubmittedTo) { + query = query.andWhere( + 'inquirySearch.date_submitted_to_alc < :date_submitted_to_alc', + { + date_submitted_to_alc: getNextDayToPacific( + searchDto.dateSubmittedTo, + ).toISOString(), + }, + ); + } + + return query; + } + + private compileParcelSearchQuery( + searchDto: SearchRequestDto, + query: SelectQueryBuilder<InquirySearchView>, + ) { + if (searchDto.pid || searchDto.civicAddress) { + query = query.leftJoin( + InquiryParcel, + 'parcel', + 'parcel.inquiry_uuid = inquirySearch.inquiry_uuid', + ); + } + + if (searchDto.pid) { + query = query.andWhere('parcel.pid = :pid', { pid: searchDto.pid }); + } + + if (searchDto.civicAddress) { + query = query.andWhere( + 'LOWER(parcel.civic_address) like LOWER(:civic_address)', + { + civic_address: `%${searchDto.civicAddress}%`.toLowerCase(), + }, + ); + } + return query; + } + + private compileSearchByNameQuery( + searchDto: SearchRequestDto, + query: SelectQueryBuilder<InquirySearchView>, + ) { + if (searchDto.name) { + const formattedSearchString = + formatStringToPostgresSearchStringArrayWithWildCard(searchDto.name!); + + query = query.andWhere( + new Brackets((qb) => + qb + .where( + "LOWER(inquirySearch.inquirer_first_name || ' ' || inquirySearch.inquirer_last_name) LIKE ANY (:names)", + { + names: formattedSearchString, + }, + ) + .orWhere( + 'LOWER(inquirySearch.inquirer_first_name) LIKE ANY (:names)', + { + names: formattedSearchString, + }, + ) + .orWhere( + 'LOWER(inquirySearch.inquirer_last_name) LIKE ANY (:names)', + { + names: formattedSearchString, + }, + ) + .orWhere( + 'LOWER(inquirySearch.inquirer_organization) LIKE ANY (:names)', + { + names: formattedSearchString, + }, + ), + ), + ); + } + return query; + } + + private compileFileTypeSearchQuery( + searchDto: SearchRequestDto, + query: SelectQueryBuilder<InquirySearchView>, + ) { + if (searchDto.fileTypes.length > 0) { + query = query.andWhere( + new Brackets((qb) => + qb.where('inquirySearch.inquiry_type_code IN (:...typeCodes)', { + typeCodes: searchDto.fileTypes, + }), + ), + ); + } + + return query; + } +} diff --git a/services/apps/alcs/src/alcs/search/inquiry/inquiry-search-view.entity.ts b/services/apps/alcs/src/alcs/search/inquiry/inquiry-search-view.entity.ts new file mode 100644 index 0000000000..d9b62c0a59 --- /dev/null +++ b/services/apps/alcs/src/alcs/search/inquiry/inquiry-search-view.entity.ts @@ -0,0 +1,80 @@ +import { + DataSource, + JoinColumn, + ManyToOne, + PrimaryColumn, + ViewColumn, + ViewEntity, +} from 'typeorm'; +import { InquiryType } from '../../inquiry/inquiry-type.entity'; +import { Inquiry } from '../../inquiry/inquiry.entity'; +import { LocalGovernment } from '../../local-government/local-government.entity'; + +@ViewEntity({ + expression: (datasource: DataSource) => + datasource + .createQueryBuilder() + .select('inquiry.uuid', 'inquiry_uuid') + .addSelect('inquiry.file_number', 'file_number') + .addSelect('inquiry.inquirer_first_name', 'inquirer_first_name') + .addSelect('inquiry.inquirer_last_name', 'inquirer_last_name') + .addSelect('inquiry.inquirer_organization', 'inquirer_organization') + .addSelect('inquiry.type_code', 'inquiry_type_code') + .addSelect('inquiry.open', 'open') + .addSelect('inquiry.date_submitted_to_alc', 'date_submitted_to_alc') + .addSelect('inquiry.local_government_uuid', 'local_government_uuid') + .addSelect('localGovernment.name', 'local_government_name') + .addSelect('inquiry.region_code', 'inquiry_region_code') + .from(Inquiry, 'inquiry') + .innerJoinAndSelect( + InquiryType, + 'inquiryType', + 'inquiry.type_code = inquiryType.code', + ) + .leftJoin( + LocalGovernment, + 'localGovernment', + 'inquiry.local_government_uuid = localGovernment.uuid', + ), +}) +export class InquirySearchView { + @ViewColumn() + @PrimaryColumn() + inquiryUuid: string; + + @ViewColumn() + inquiryRegionCode?: string; + + @ViewColumn() + fileNumber: string; + + @ViewColumn() + inquirerFirstName?: string; + + @ViewColumn() + inquirerLastName?: string; + + @ViewColumn() + inquirerOrganization?: string; + + @ViewColumn() + localGovernmentUuid?: string; + + @ViewColumn() + localGovernmentName?: string; + + @ViewColumn() + inquiryTypeCode: string; + + @ViewColumn() + open: boolean; + + @ViewColumn() + dateSubmittedToAlc: Date | null; + + @ManyToOne(() => InquiryType, { + nullable: false, + }) + @JoinColumn({ name: 'inquiry_type_code' }) + inquiryType: InquiryType; +} diff --git a/services/apps/alcs/src/alcs/search/notice-of-intent/notice-of-intent-advanced-search.service.spec.ts b/services/apps/alcs/src/alcs/search/notice-of-intent/notice-of-intent-advanced-search.service.spec.ts index ec12ea3290..4389497052 100644 --- a/services/apps/alcs/src/alcs/search/notice-of-intent/notice-of-intent-advanced-search.service.spec.ts +++ b/services/apps/alcs/src/alcs/search/notice-of-intent/notice-of-intent-advanced-search.service.spec.ts @@ -14,6 +14,14 @@ describe('NoticeOfIntentService', () => { >; let mockLocalGovernmentRepository: DeepMocked<Repository<LocalGovernment>>; + const sortFields = [ + 'fileId', + 'type', + 'government', + 'portalStatus', + 'dateSubmitted', + ]; + const mockSearchDto: SearchRequestDto = { fileNumber: '123', portalStatusCode: 'A', @@ -92,9 +100,9 @@ describe('NoticeOfIntentService', () => { expect(result).toEqual({ data: [], total: 0 }); expect( mockNoticeOfIntentSubmissionSearchViewRepository.createQueryBuilder, - ).toBeCalledTimes(1); - expect(mockQuery.andWhere).toBeCalledTimes(13); - expect(mockQuery.where).toBeCalledTimes(1); + ).toHaveBeenCalledTimes(1); + expect(mockQuery.andWhere).toHaveBeenCalledTimes(13); + expect(mockQuery.where).toHaveBeenCalledTimes(1); }); it('should call compileNoticeOfIntentSearchQuery method correctly', async () => { @@ -105,9 +113,30 @@ describe('NoticeOfIntentService', () => { const result = await service.searchNoticeOfIntents(mockSearchDto); expect(result).toEqual({ data: [], total: 0 }); - expect(compileApplicationSearchQuerySpy).toBeCalledWith(mockSearchDto); + expect(compileApplicationSearchQuerySpy).toHaveBeenCalledWith( + mockSearchDto, + ); expect(mockQuery.orderBy).toHaveBeenCalledTimes(1); expect(mockQuery.offset).toHaveBeenCalledTimes(1); expect(mockQuery.limit).toHaveBeenCalledTimes(1); }); + + sortFields.forEach((sortField) => { + it(`should sort by ${sortField}`, async () => { + const compileSearchQuerySpy = jest + .spyOn(service as any, 'compileNoticeOfIntentSearchQuery') + .mockResolvedValue(mockQuery); + + mockSearchDto.sortField = sortField; + mockSearchDto.sortDirection = 'DESC'; + + const result = await service.searchNoticeOfIntents(mockSearchDto); + + expect(result).toEqual({ data: [], total: 0 }); + expect(compileSearchQuerySpy).toHaveBeenCalledWith(mockSearchDto); + expect(mockQuery.orderBy).toHaveBeenCalledTimes(1); + expect(mockQuery.offset).toHaveBeenCalledTimes(1); + expect(mockQuery.limit).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/services/apps/alcs/src/alcs/search/notification/notification-advanced-search.service.spec.ts b/services/apps/alcs/src/alcs/search/notification/notification-advanced-search.service.spec.ts index 05cacfbc75..e2995795db 100644 --- a/services/apps/alcs/src/alcs/search/notification/notification-advanced-search.service.spec.ts +++ b/services/apps/alcs/src/alcs/search/notification/notification-advanced-search.service.spec.ts @@ -13,6 +13,13 @@ describe('NotificationAdvancedSearchService', () => { Repository<NotificationSubmissionSearchView> >; let mockLocalGovernmentRepository: DeepMocked<Repository<LocalGovernment>>; + const sortFields = [ + 'fileId', + 'type', + 'government', + 'portalStatus', + 'dateSubmitted', + ]; const mockSearchDto: SearchRequestDto = { fileNumber: '123', @@ -109,4 +116,23 @@ describe('NotificationAdvancedSearchService', () => { expect(mockQuery.offset).toHaveBeenCalledTimes(1); expect(mockQuery.limit).toHaveBeenCalledTimes(1); }); + + sortFields.forEach((sortField) => { + it(`should sort by ${sortField}`, async () => { + const compileSearchQuerySpy = jest + .spyOn(service as any, 'compileNotificationSearchQuery') + .mockResolvedValue(mockQuery); + + mockSearchDto.sortField = sortField; + mockSearchDto.sortDirection = 'DESC'; + + const result = await service.search(mockSearchDto); + + expect(result).toEqual({ data: [], total: 0 }); + expect(compileSearchQuerySpy).toHaveBeenCalledWith(mockSearchDto); + expect(mockQuery.orderBy).toHaveBeenCalledTimes(1); + expect(mockQuery.offset).toHaveBeenCalledTimes(1); + expect(mockQuery.limit).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.spec.ts b/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.spec.ts index b1dbf5dfef..7d4003cbbf 100644 --- a/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.spec.ts +++ b/services/apps/alcs/src/alcs/search/planning-review/planning-review-advanced-search.service.spec.ts @@ -11,6 +11,7 @@ describe('PlanningReviewAdvancedSearchService', () => { let service: PlanningReviewAdvancedSearchService; let mockPRSearchView: DeepMocked<Repository<PlanningReviewSearchView>>; let mockLocalGovernmentRepository: DeepMocked<Repository<LocalGovernment>>; + const sortFields = ['fileId', 'type', 'government', 'dateSubmitted']; const mockSearchDto: SearchRequestDto = { fileNumber: '123', @@ -86,8 +87,8 @@ describe('PlanningReviewAdvancedSearchService', () => { const result = await service.search(mockSearchDto); expect(result).toEqual({ data: [], total: 0 }); - expect(mockPRSearchView.createQueryBuilder).toBeCalledTimes(1); - expect(mockQuery.andWhere).toBeCalledTimes(8); + expect(mockPRSearchView.createQueryBuilder).toHaveBeenCalledTimes(1); + expect(mockQuery.andWhere).toHaveBeenCalledTimes(8); }); it('should call compileSearchQuery method correctly', async () => { @@ -98,9 +99,28 @@ describe('PlanningReviewAdvancedSearchService', () => { const result = await service.search(mockSearchDto); expect(result).toEqual({ data: [], total: 0 }); - expect(compileSearchQuerySpy).toBeCalledWith(mockSearchDto); + expect(compileSearchQuerySpy).toHaveBeenCalledWith(mockSearchDto); expect(mockQuery.orderBy).toHaveBeenCalledTimes(1); expect(mockQuery.offset).toHaveBeenCalledTimes(1); expect(mockQuery.limit).toHaveBeenCalledTimes(1); }); + + sortFields.forEach((sortField) => { + it(`should sort by ${sortField}`, async () => { + const compileSearchQuerySpy = jest + .spyOn(service as any, 'compileSearchQuery') + .mockResolvedValue(mockQuery); + + mockSearchDto.sortField = sortField; + mockSearchDto.sortDirection = 'DESC'; + + const result = await service.search(mockSearchDto); + + expect(result).toEqual({ data: [], total: 0 }); + expect(compileSearchQuerySpy).toHaveBeenCalledWith(mockSearchDto); + expect(mockQuery.orderBy).toHaveBeenCalledTimes(1); + expect(mockQuery.offset).toHaveBeenCalledTimes(1); + expect(mockQuery.limit).toHaveBeenCalledTimes(1); + }); + }); }); 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 1a12d72b65..2a5cf907fa 100644 --- a/services/apps/alcs/src/alcs/search/search.controller.spec.ts +++ b/services/apps/alcs/src/alcs/search/search.controller.spec.ts @@ -8,10 +8,12 @@ import { DataSource, QueryRunner, Repository } from 'typeorm'; import { mockKeyCloakProviders } from '../../../test/mocks/mockTypes'; import { Application } from '../application/application.entity'; import { ApplicationType } from '../code/application-code/application-type/application-type.entity'; +import { Inquiry } from '../inquiry/inquiry.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 { InquiryAdvancedSearchService } from './inquiry/inquiry-advanced-search.service'; import { NoticeOfIntentAdvancedSearchService } from './notice-of-intent/notice-of-intent-advanced-search.service'; import { NotificationAdvancedSearchService } from './notification/notification-advanced-search.service'; import { PlanningReviewAdvancedSearchService } from './planning-review/planning-review-advanced-search.service'; @@ -26,6 +28,7 @@ describe('SearchController', () => { let mockApplicationAdvancedSearchService: DeepMocked<ApplicationAdvancedSearchService>; let mockNotificationAdvancedSearchService: DeepMocked<NotificationAdvancedSearchService>; let mockPlanningReviewAdvancedSearchService: DeepMocked<PlanningReviewAdvancedSearchService>; + let mockInquiryAdvancedSearchService: DeepMocked<InquiryAdvancedSearchService>; let mockDataSource: DeepMocked<DataSource>; let mockQueryRunner: DeepMocked<QueryRunner>; let mockAppTypeRepo: DeepMocked<Repository<ApplicationType>>; @@ -36,6 +39,7 @@ describe('SearchController', () => { mockApplicationAdvancedSearchService = createMock(); mockNotificationAdvancedSearchService = createMock(); mockPlanningReviewAdvancedSearchService = createMock(); + mockInquiryAdvancedSearchService = createMock(); mockDataSource = createMock(); mockAppTypeRepo = createMock(); @@ -66,6 +70,10 @@ describe('SearchController', () => { provide: PlanningReviewAdvancedSearchService, useValue: mockPlanningReviewAdvancedSearchService, }, + { + provide: InquiryAdvancedSearchService, + useValue: mockInquiryAdvancedSearchService, + }, { provide: DataSource, useValue: mockDataSource, @@ -93,6 +101,7 @@ describe('SearchController', () => { mockSearchService.getNoi.mockResolvedValue(new NoticeOfIntent()); mockSearchService.getNotification.mockResolvedValue(new Notification()); mockSearchService.getPlanningReview.mockResolvedValue(new PlanningReview()); + mockSearchService.getInquiry.mockResolvedValue(new Inquiry()); mockNoticeOfIntentAdvancedSearchService.searchNoticeOfIntents.mockResolvedValue( { @@ -115,6 +124,11 @@ describe('SearchController', () => { data: [], total: 0, }); + + mockInquiryAdvancedSearchService.search.mockResolvedValue({ + data: [], + total: 0, + }); }); it('should be defined', () => { @@ -125,9 +139,9 @@ describe('SearchController', () => { const searchString = 'fake'; const result = await controller.search(searchString); - expect(mockSearchService.getApplication).toBeCalledTimes(1); + expect(mockSearchService.getApplication).toHaveBeenCalledTimes(1); expect(mockSearchService.getApplication).toHaveBeenCalledWith(searchString); - expect(mockSearchService.getNoi).toBeCalledTimes(1); + expect(mockSearchService.getNoi).toHaveBeenCalledTimes(1); expect(mockSearchService.getNoi).toHaveBeenCalledWith(searchString); expect(mockSearchService.getPlanningReview).toHaveBeenCalledTimes(1); expect(mockSearchService.getPlanningReview).toHaveBeenCalledWith( @@ -138,10 +152,10 @@ describe('SearchController', () => { searchString, ); expect(result).toBeDefined(); - expect(result.length).toBe(4); + expect(result.length).toBe(5); }); - it('should call advanced search to retrieve Applications, NOIs, PlanningReviews, Covenants, Notifications', async () => { + it('should call advanced search to retrieve Applications, NOIs, PlanningReviews, Covenants, Notifications, Inquiries', async () => { const mockSearchRequestDto = { pageSize: 1, page: 1, @@ -172,6 +186,13 @@ describe('SearchController', () => { ).toHaveBeenCalledWith(mockSearchRequestDto); expect(result.noticeOfIntents).toBeDefined(); expect(result.totalNoticeOfIntents).toBe(0); + + expect(mockInquiryAdvancedSearchService.search).toHaveBeenCalledTimes(1); + expect(mockInquiryAdvancedSearchService.search).toHaveBeenCalledWith( + mockSearchRequestDto, + ); + expect(result.noticeOfIntents).toBeDefined(); + expect(result.totalNoticeOfIntents).toBe(0); }); it('should call applications advanced search to retrieve Applications', async () => { @@ -269,4 +290,25 @@ describe('SearchController', () => { expect(result.noticeOfIntents).toBeDefined(); expect(result.totalNoticeOfIntents).toBe(0); }); + + it('should call advanced search to retrieve Inquiries only when Inquiry file type selected', async () => { + const mockSearchRequestDto = { + pageSize: 1, + page: 1, + sortField: '1', + sortDirection: 'ASC', + fileTypes: ['GENC'], + }; + + const result = await controller.advancedSearch( + mockSearchRequestDto as SearchRequestDto, + ); + + expect(mockInquiryAdvancedSearchService.search).toHaveBeenCalledTimes(1); + expect(mockInquiryAdvancedSearchService.search).toHaveBeenCalledWith( + mockSearchRequestDto, + ); + expect(result.inquiries).toBeDefined(); + expect(result.totalInquiries).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 3c2d4b888b..75b6b79f6e 100644 --- a/services/apps/alcs/src/alcs/search/search.controller.ts +++ b/services/apps/alcs/src/alcs/search/search.controller.ts @@ -14,11 +14,14 @@ import { Application } from '../application/application.entity'; 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'; +import { Inquiry } from '../inquiry/inquiry.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 { ApplicationSubmissionSearchView } from './application/application-search-view.entity'; +import { InquiryAdvancedSearchService } from './inquiry/inquiry-advanced-search.service'; +import { InquirySearchView } from './inquiry/inquiry-search-view.entity'; 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'; @@ -29,6 +32,7 @@ import { AdvancedSearchResponseDto, AdvancedSearchResultDto, ApplicationSearchResultDto, + InquirySearchResultDto, NoticeOfIntentSearchResultDto, NotificationSearchResultDto, PlanningReviewSearchResultDto, @@ -36,6 +40,7 @@ import { SearchResultDto, } from './search.dto'; import { SearchService } from './search.service'; +import { INQUIRY_TYPES } from '../inquiry/inquiry.dto'; @ApiOAuth2(config.get<string[]>('KEYCLOAK.SCOPES')) @UseGuards(RolesGuard) @@ -48,6 +53,7 @@ export class SearchController { private applicationSearchService: ApplicationAdvancedSearchService, private notificationSearchService: NotificationAdvancedSearchService, private planningReviewSearchService: PlanningReviewAdvancedSearchService, + private inquirySearchService: InquiryAdvancedSearchService, @InjectRepository(ApplicationType) private appTypeRepo: Repository<ApplicationType>, @InjectDataSource() @@ -62,6 +68,7 @@ export class SearchController { const notification = await this.searchService.getNotification(searchTerm); const planningReview = await this.searchService.getPlanningReview(searchTerm); + const inquiry = await this.searchService.getInquiry(searchTerm); const result: SearchResultDto[] = []; @@ -71,6 +78,7 @@ export class SearchController { noi, planningReview, notification, + inquiry, ); return result; @@ -82,6 +90,7 @@ export class SearchController { noi: NoticeOfIntent | null, planningReview: PlanningReview | null, notification: Notification | null, + inquiry: Inquiry | null, ) { if (application) { result.push(this.mapApplicationToSearchResult(application)); @@ -98,6 +107,10 @@ export class SearchController { if (notification) { result.push(this.mapNotificationToSearchResult(notification)); } + + if (inquiry) { + result.push(this.mapInquiryToSearchResult(inquiry)); + } } @Post('/advanced') @@ -108,6 +121,7 @@ export class SearchController { searchNoi, searchPlanningReviews, searchNotifications, + searchInquiries, } = this.getEntitiesTypeToSearch(searchDto); const queryRunner = this.dataSource.createQueryRunner('slave'); @@ -148,11 +162,17 @@ export class SearchController { await this.planningReviewSearchService.search(searchDto); } + let inquiries: AdvancedSearchResultDto<InquirySearchView[]> | null = null; + if (searchInquiries) { + inquiries = await this.inquirySearchService.search(searchDto); + } + return await this.mapAdvancedSearchResults( applicationSearchResult, noticeOfIntentSearchService, planningReviews, notifications, + inquiries, ); } finally { await queryRunner.release(); @@ -178,6 +198,7 @@ export class SearchController { null, null, null, + null, ); return { @@ -202,6 +223,7 @@ export class SearchController { noticeOfIntents, null, null, + null, ); return { @@ -223,6 +245,7 @@ export class SearchController { null, null, notifications, + null, ); return { @@ -231,12 +254,34 @@ export class SearchController { }; } + @Post('/advanced/inquiries') + @UserRoles(...ROLES_ALLOWED_APPLICATIONS) + async advancedSearchInquiries( + @Body() searchDto: SearchRequestDto, + ): Promise<AdvancedSearchResultDto<InquirySearchResultDto[]>> { + const inquiries = await this.inquirySearchService.search(searchDto); + + const mappedSearchResult = await this.mapAdvancedSearchResults( + null, + null, + null, + null, + inquiries, + ); + + return { + total: mappedSearchResult.totalInquiries, + data: mappedSearchResult.inquiries, + }; + } + private getEntitiesTypeToSearch(searchDto: SearchRequestDto) { let searchApplications = true; let planningReviewTypeSpecified = false; let noiTypeSpecified = false; let notificationTypeSpecified = false; + let inquiriesTypeSpecified = false; if (searchDto.fileTypes.length > 0) { searchApplications = searchDto.fileTypes.filter((searchType) => @@ -254,6 +299,13 @@ export class SearchController { planningReviewTypeSpecified = searchDto.fileTypes.some((searchType) => ['PLAN'].includes(searchType), ); + + inquiriesTypeSpecified = + searchDto.fileTypes.filter((searchType) => + Object.values(INQUIRY_TYPES).includes( + INQUIRY_TYPES[searchType as keyof typeof INQUIRY_TYPES], + ), + ).length > 0; } const searchNoi = searchDto.fileTypes.length > 0 ? noiTypeSpecified : true; @@ -272,11 +324,20 @@ export class SearchController { !searchDto.resolutionYear && !isStringSetAndNotEmpty(searchDto.legacyId); + const searchInquiries = + (searchDto.fileTypes.length > 0 ? inquiriesTypeSpecified : true) && + !searchDto.dateDecidedFrom && + !searchDto.dateDecidedTo && + !searchDto.resolutionNumber && + !searchDto.resolutionYear && + !isStringSetAndNotEmpty(searchDto.legacyId); + return { searchApplications, searchNoi, searchPlanningReviews, searchNotifications, + searchInquiries, }; } @@ -291,6 +352,7 @@ export class SearchController { notifications: AdvancedSearchResultDto< NotificationSubmissionSearchView[] > | null, + inquiries: AdvancedSearchResultDto<InquirySearchView[]> | null, ) { const response = new AdvancedSearchResponseDto(); @@ -340,6 +402,15 @@ export class SearchController { ); } + const mappedInquiries: InquirySearchResultDto[] = []; + if (inquiries && inquiries.data && inquiries.data.length > 0) { + mappedInquiries.push( + ...inquiries.data.map((inquiry) => + this.mapInquiryToAdvancedSearchResult(inquiry), + ), + ); + } + response.applications = mappedApplications; response.totalApplications = applications?.total ?? 0; response.noticeOfIntents = mappedNoticeOfIntents; @@ -348,6 +419,8 @@ export class SearchController { response.totalNotifications = notifications?.total ?? 0; response.planningReviews = mappedPlanningReviews; response.totalPlanningReviews = planningReviews?.total ?? 0; + response.inquiries = mappedInquiries; + response.totalInquiries = inquiries?.total ?? 0; return response; } @@ -393,6 +466,16 @@ export class SearchController { }; } + private mapInquiryToSearchResult(inquiry: Inquiry): SearchResultDto { + return { + type: CARD_TYPE.INQUIRY, + referenceId: inquiry.fileNumber, + localGovernmentName: inquiry.localGovernment?.name, + applicant: inquiry.inquirerLastName ?? undefined, + fileNumber: inquiry.fileNumber, + }; + } + private mapNotificationToSearchResult( notification: Notification, ): SearchResultDto { @@ -483,4 +566,27 @@ export class SearchController { class: 'PLAN', }; } + + private mapInquiryToAdvancedSearchResult( + inquiry: InquirySearchView, + ): InquirySearchResultDto { + return { + fileNumber: inquiry.fileNumber, + open: inquiry.open, + type: { + code: inquiry.inquiryTypeCode, + label: inquiry.inquiryType.label, + backgroundColor: inquiry.inquiryType.backgroundColor, + textColor: inquiry.inquiryType.textColor, + description: '', + shortLabel: inquiry.inquiryType.shortLabel, + }, + localGovernmentName: inquiry.localGovernmentName ?? null, + dateSubmitted: inquiry.dateSubmittedToAlc?.getTime(), + inquirerFirstName: inquiry.inquirerFirstName, + inquirerLastName: inquiry.inquirerLastName, + inquirerOrganizationName: inquiry.inquirerOrganization, + class: 'INQR', + }; + } } diff --git a/services/apps/alcs/src/alcs/search/search.dto.ts b/services/apps/alcs/src/alcs/search/search.dto.ts index 2b9c69583f..c4b714c64b 100644 --- a/services/apps/alcs/src/alcs/search/search.dto.ts +++ b/services/apps/alcs/src/alcs/search/search.dto.ts @@ -1,12 +1,12 @@ import { IsArray, - IsBoolean, IsNumber, IsOptional, IsString, MinLength, } from 'class-validator'; import { ApplicationTypeDto } from '../code/application-code/application-type/application-type.dto'; +import { InquiryTypeDto } from '../inquiry/inquiry.dto'; import { PlanningReviewTypeDto } from '../planning-review/planning-review.dto'; export class SearchResultDto { @@ -19,7 +19,13 @@ export class SearchResultDto { label?: ApplicationTypeDto; } -export type SearchEntityClass = 'APP' | 'NOI' | 'PLAN' | 'COV' | 'NOTI'; +export type SearchEntityClass = + | 'APP' + | 'NOI' + | 'PLAN' + | 'COV' + | 'NOTI' + | 'INQR'; export class ApplicationSearchResultDto { type: ApplicationTypeDto; @@ -67,15 +73,30 @@ export class NotificationSearchResultDto { class: SearchEntityClass; } +export class InquirySearchResultDto { + type: InquiryTypeDto; + inquirerFirstName?: string; + inquirerLastName?: string; + inquirerOrganizationName?: string; + localGovernmentName: string | null; + fileNumber: string; + boardCode?: string; + dateSubmitted?: number; + class: SearchEntityClass; + open: boolean; +} + export class AdvancedSearchResponseDto { applications: ApplicationSearchResultDto[]; noticeOfIntents: NoticeOfIntentSearchResultDto[]; notifications: NotificationSearchResultDto[]; planningReviews: PlanningReviewSearchResultDto[]; + inquiries: InquirySearchResultDto[]; totalApplications: number; totalNoticeOfIntents: number; totalPlanningReviews: number; totalNotifications: number; + totalInquiries: number; } export class AdvancedSearchResultDto<T> { diff --git a/services/apps/alcs/src/alcs/search/search.module.ts b/services/apps/alcs/src/alcs/search/search.module.ts index a3897bfb64..8fc6a539de 100644 --- a/services/apps/alcs/src/alcs/search/search.module.ts +++ b/services/apps/alcs/src/alcs/search/search.module.ts @@ -3,12 +3,15 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { ApplicationProfile } from '../../common/automapper/application.automapper.profile'; import { Application } from '../application/application.entity'; import { ApplicationType } from '../code/application-code/application-type/application-type.entity'; +import { Inquiry } from '../inquiry/inquiry.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 { ApplicationAdvancedSearchService } from './application/application-advanced-search.service'; import { ApplicationSubmissionSearchView } from './application/application-search-view.entity'; +import { InquiryAdvancedSearchService } from './inquiry/inquiry-advanced-search.service'; +import { InquirySearchView } from './inquiry/inquiry-search-view.entity'; 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'; @@ -26,11 +29,13 @@ import { SearchService } from './search.service'; NoticeOfIntent, PlanningReview, Notification, + Inquiry, LocalGovernment, ApplicationSubmissionSearchView, NoticeOfIntentSubmissionSearchView, NotificationSubmissionSearchView, PlanningReviewSearchView, + InquirySearchView, ]), ], providers: [ @@ -40,6 +45,7 @@ import { SearchService } from './search.service'; NoticeOfIntentAdvancedSearchService, NotificationAdvancedSearchService, PlanningReviewAdvancedSearchService, + InquiryAdvancedSearchService, ], 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 0ee1ff6bdb..f764abc326 100644 --- a/services/apps/alcs/src/alcs/search/search.service.spec.ts +++ b/services/apps/alcs/src/alcs/search/search.service.spec.ts @@ -3,6 +3,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Application } from '../application/application.entity'; +import { Inquiry } from '../inquiry/inquiry.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'; @@ -20,6 +21,7 @@ describe('SearchService', () => { >; let mockLocalGovernment: DeepMocked<Repository<LocalGovernment>>; let mockNotificationRepository: DeepMocked<Repository<Notification>>; + let mockInquiryRepository: DeepMocked<Repository<Inquiry>>; const fakeFileNumber = 'fake'; @@ -30,6 +32,7 @@ describe('SearchService', () => { mockApplicationSubmissionSearchView = createMock(); mockLocalGovernment = createMock(); mockNotificationRepository = createMock(); + mockInquiryRepository = createMock(); const module: TestingModule = await Test.createTestingModule({ providers: [ @@ -54,6 +57,10 @@ describe('SearchService', () => { provide: getRepositoryToken(ApplicationSubmissionSearchView), useValue: mockApplicationSubmissionSearchView, }, + { + provide: getRepositoryToken(Inquiry), + useValue: mockInquiryRepository, + }, { provide: getRepositoryToken(LocalGovernment), useValue: mockLocalGovernment, @@ -73,8 +80,8 @@ describe('SearchService', () => { const result = await service.getNoi('fake'); - expect(mockNoiRepository.findOne).toBeCalledTimes(1); - expect(mockNoiRepository.findOne).toBeCalledWith({ + expect(mockNoiRepository.findOne).toHaveBeenCalledTimes(1); + expect(mockNoiRepository.findOne).toHaveBeenCalledWith({ where: { fileNumber: fakeFileNumber, }, @@ -91,8 +98,8 @@ describe('SearchService', () => { const result = await service.getApplication('fake'); - expect(mockApplicationRepository.findOne).toBeCalledTimes(1); - expect(mockApplicationRepository.findOne).toBeCalledWith({ + expect(mockApplicationRepository.findOne).toHaveBeenCalledTimes(1); + expect(mockApplicationRepository.findOne).toHaveBeenCalledWith({ where: { fileNumber: fakeFileNumber, }, @@ -112,8 +119,8 @@ describe('SearchService', () => { const result = await service.getPlanningReview('fake'); - expect(mockPlanningReviewRepository.findOne).toBeCalledTimes(1); - expect(mockPlanningReviewRepository.findOne).toBeCalledWith({ + expect(mockPlanningReviewRepository.findOne).toHaveBeenCalledTimes(1); + expect(mockPlanningReviewRepository.findOne).toHaveBeenCalledWith({ where: { fileNumber: fakeFileNumber, }, @@ -129,8 +136,8 @@ describe('SearchService', () => { const result = await service.getNotification('fake'); - expect(mockNotificationRepository.findOne).toBeCalledTimes(1); - expect(mockNotificationRepository.findOne).toBeCalledWith({ + expect(mockNotificationRepository.findOne).toHaveBeenCalledTimes(1); + expect(mockNotificationRepository.findOne).toHaveBeenCalledWith({ where: { fileNumber: fakeFileNumber, }, @@ -143,4 +150,21 @@ describe('SearchService', () => { }); expect(result).toBeDefined(); }); + + it('should call repository to get application', async () => { + mockInquiryRepository.findOne.mockResolvedValue(new Inquiry()); + + const result = await service.getInquiry('fake'); + + expect(mockInquiryRepository.findOne).toHaveBeenCalledTimes(1); + expect(mockInquiryRepository.findOne).toHaveBeenCalledWith({ + where: { + fileNumber: fakeFileNumber, + }, + relations: { + localGovernment: true, + }, + }); + expect(result).toBeDefined(); + }); }); diff --git a/services/apps/alcs/src/alcs/search/search.service.ts b/services/apps/alcs/src/alcs/search/search.service.ts index c53df14ad1..53efa5294f 100644 --- a/services/apps/alcs/src/alcs/search/search.service.ts +++ b/services/apps/alcs/src/alcs/search/search.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Application } from '../application/application.entity'; +import { Inquiry } from '../inquiry/inquiry.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'; @@ -24,6 +25,8 @@ export class SearchService { private notificationRepository: Repository<Notification>, @InjectRepository(PlanningReview) private planningReviewRepository: Repository<PlanningReview>, + @InjectRepository(Inquiry) + private inquiryRepository: Repository<Inquiry>, ) {} async getApplication(fileNumber: string) { @@ -70,4 +73,15 @@ export class SearchService { }, }); } + + async getInquiry(fileNumber: string) { + return await this.inquiryRepository.findOne({ + where: { + fileNumber, + }, + relations: { + localGovernment: true, + }, + }); + } } diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1711476530253-inquiry_advanced_search.ts b/services/apps/alcs/src/providers/typeorm/migrations/1711476530253-inquiry_advanced_search.ts new file mode 100644 index 0000000000..5c25a4a936 --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1711476530253-inquiry_advanced_search.ts @@ -0,0 +1,28 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class InquiryAdvancedSearch1711476530253 implements MigrationInterface { + name = 'InquiryAdvancedSearch1711476530253'; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `CREATE VIEW "alcs"."inquiry_search_view" AS SELECT "inquiry"."uuid" AS "inquiry_uuid", "inquiry"."open" AS "open", "inquiryType"."audit_deleted_date_at" AS "inquiryType_audit_deleted_date_at", "inquiryType"."audit_created_at" AS "inquiryType_audit_created_at", "inquiryType"."audit_updated_at" AS "inquiryType_audit_updated_at", "inquiryType"."audit_created_by" AS "inquiryType_audit_created_by", "inquiryType"."audit_updated_by" AS "inquiryType_audit_updated_by", "inquiryType"."label" AS "inquiryType_label", "inquiryType"."code" AS "inquiryType_code", "inquiryType"."description" AS "inquiryType_description", "inquiryType"."short_label" AS "inquiryType_short_label", "inquiryType"."background_color" AS "inquiryType_background_color", "inquiryType"."text_color" AS "inquiryType_text_color", "inquiryType"."html_description" AS "inquiryType_html_description", "localGovernment"."name" AS "local_government_name", "inquiry"."file_number" AS "file_number", "inquiry"."inquirer_first_name" AS "inquirer_first_name", "inquiry"."inquirer_last_name" AS "inquirer_last_name", "inquiry"."inquirer_organization" AS "inquirer_organization", "inquiry"."type_code" AS "inquiry_type_code", "inquiry"."date_submitted_to_alc" AS "date_submitted_to_alc", "inquiry"."local_government_uuid" AS "local_government_uuid", "inquiry"."region_code" AS "inquiry_region_code" FROM "alcs"."inquiry" "inquiry" INNER JOIN "alcs"."inquiry_type" "inquiryType" ON "inquiry"."type_code" = "inquiryType"."code" AND "inquiryType"."audit_deleted_date_at" IS NULL LEFT JOIN "alcs"."local_government" "localGovernment" ON "inquiry"."local_government_uuid" = "localGovernment"."uuid" AND "localGovernment"."audit_deleted_date_at" IS NULL WHERE "inquiry"."audit_deleted_date_at" IS NULL`, + ); + await queryRunner.query( + `INSERT INTO "alcs"."typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES (DEFAULT, $1, DEFAULT, $2, $3, $4)`, + [ + 'alcs', + 'VIEW', + 'inquiry_search_view', + 'SELECT "inquiry"."uuid" AS "inquiry_uuid", "inquiry"."open" AS "open", "inquiryType"."audit_deleted_date_at" AS "inquiryType_audit_deleted_date_at", "inquiryType"."audit_created_at" AS "inquiryType_audit_created_at", "inquiryType"."audit_updated_at" AS "inquiryType_audit_updated_at", "inquiryType"."audit_created_by" AS "inquiryType_audit_created_by", "inquiryType"."audit_updated_by" AS "inquiryType_audit_updated_by", "inquiryType"."label" AS "inquiryType_label", "inquiryType"."code" AS "inquiryType_code", "inquiryType"."description" AS "inquiryType_description", "inquiryType"."short_label" AS "inquiryType_short_label", "inquiryType"."background_color" AS "inquiryType_background_color", "inquiryType"."text_color" AS "inquiryType_text_color", "inquiryType"."html_description" AS "inquiryType_html_description", "localGovernment"."name" AS "local_government_name", "inquiry"."file_number" AS "file_number", "inquiry"."inquirer_first_name" AS "inquirer_first_name", "inquiry"."inquirer_last_name" AS "inquirer_last_name", "inquiry"."inquirer_organization" AS "inquirer_organization", "inquiry"."type_code" AS "inquiry_type_code", "inquiry"."date_submitted_to_alc" AS "date_submitted_to_alc", "inquiry"."local_government_uuid" AS "local_government_uuid", "inquiry"."region_code" AS "inquiry_region_code" FROM "alcs"."inquiry" "inquiry" INNER JOIN "alcs"."inquiry_type" "inquiryType" ON "inquiry"."type_code" = "inquiryType"."code" AND "inquiryType"."audit_deleted_date_at" IS NULL LEFT JOIN "alcs"."local_government" "localGovernment" ON "inquiry"."local_government_uuid" = "localGovernment"."uuid" AND "localGovernment"."audit_deleted_date_at" IS NULL WHERE "inquiry"."audit_deleted_date_at" IS NULL', + ], + ); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `DELETE FROM "alcs"."typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, + ['VIEW', 'inquiry_search_view', 'alcs'], + ); + await queryRunner.query(`DROP VIEW "alcs"."inquiry_search_view"`); + } +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1711565723047-update_inquiry_board.ts b/services/apps/alcs/src/providers/typeorm/migrations/1711565723047-update_inquiry_board.ts new file mode 100644 index 0000000000..7f242ddb7a --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1711565723047-update_inquiry_board.ts @@ -0,0 +1,13 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateInquiryBoard1711565723047 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise<void> { + queryRunner.query(` + UPDATE alcs.board SET code = 'inqr' where uuid = 'c24234e9-748c-48db-9a0f-88e447473c8e'; + `); + } + + public async down(): Promise<void> { + // nope + } +} From 1674f988298f8c206c5e1e53a56f6b201757b9c8 Mon Sep 17 00:00:00 2001 From: Tristan Slater <1631008+trslater@users.noreply.github.com> Date: Tue, 2 Apr 2024 10:01:38 -0700 Subject: [PATCH 070/153] Improve app/NOI gen res number button visibility --- .../decision-v2/decision-input/decision-input-v2.component.html | 2 +- .../decision-v2/decision-input/decision-input-v2.component.scss | 2 ++ .../decision-v2/decision-input/decision-input-v2.component.html | 2 +- .../decision-v2/decision-input/decision-input-v2.component.scss | 2 ++ 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-input-v2.component.html b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-input-v2.component.html index 4250009f3f..fe4ce32c5f 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-input-v2.component.html +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-input-v2.component.html @@ -30,7 +30,7 @@ <h3>Resolution</h3> 'error-field-outlined ng-invalid': !resolutionNumberControl.valid && resolutionNumberControl.touched }" > - Generate Number + Get Resolution Number </button> <app-error-message *ngIf="!resolutionNumberControl.valid && resolutionNumberControl.touched" diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-input-v2.component.scss b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-input-v2.component.scss index 9e994e5cd3..a247ac4318 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-input-v2.component.scss +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-input-v2.component.scss @@ -96,6 +96,8 @@ form { align-items: center; .generate-number-btn { + color: white; + background-color: colors.$accent-color-dark; width: 100%; } diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.html b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.html index 77a6911341..930c73185d 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.html @@ -30,7 +30,7 @@ <h3>Resolution</h3> 'error-field-outlined ng-invalid': !resolutionNumberControl.valid && resolutionNumberControl.touched }" > - Generate Number + Get Resolution Number </button> <app-error-message *ngIf="!resolutionNumberControl.valid && resolutionNumberControl.touched" diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.scss b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.scss index 000036bef8..4a5cbd01a7 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.scss +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.scss @@ -97,6 +97,8 @@ form { align-items: center; .generate-number-btn { + color: white; + background-color: colors.$accent-color-dark; width: 100%; } From be9daaba154711783eeb76c535019d6bffbdc5bb Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Tue, 2 Apr 2024 10:40:35 -0700 Subject: [PATCH 071/153] Add Right Click Sorting Menu * Add a Move to Top / Bottom menu when right clicking on either Application or PR evidentiary records --- .../evidentiary-record.component.html | 12 ++++ .../evidentiary-record.component.scss | 27 ++++++++ .../evidentiary-record.component.ts | 64 +++++++++++++++++-- .../application-document.component.html | 12 ++++ .../application-document.component.scss | 30 ++++++++- .../application-document.component.ts | 61 +++++++++++++++++- 6 files changed, 198 insertions(+), 8 deletions(-) diff --git a/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.html b/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.html index 84c70e07f3..b6928902a4 100644 --- a/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.html +++ b/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.html @@ -1,3 +1,14 @@ +<ng-template #orderMenu let-record> + <div class="menu"> + <div class="menu-item"> + <button (click)="sendToTop(record)" mat-menu-item>Move to Top</button> + </div> + <div class="menu-item"> + <button (click)="sendToBottom(record)" mat-menu-item>Move to Bottom</button> + </div> + </div> +</ng-template> + <div class="table-header"> <div> <h4>{{ tableTitle }}</h4> @@ -58,6 +69,7 @@ <h4>{{ tableTitle }}</h4> <tr *matHeaderRowDef="displayedColumns" mat-header-row></tr> <tr + (contextmenu)="openMenu($event, row)" *matRowDef="let row; columns: displayedColumns" cdkDrag cdkDragLockAxis="y" diff --git a/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.scss b/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.scss index 117b2949ea..b4c0dca3ea 100644 --- a/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.scss +++ b/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.scss @@ -1,5 +1,32 @@ @use '../../../../../styles/colors'; +.menu { + background-color: colors.$white; + box-shadow: 0 4px 4px 0 #00000040; +} + +.menu-item { + display: flex; + flex-direction: column; + margin: 8px 0; + + button { + font-weight: normal !important; + text-align: left; + background-color: colors.$white; + border: none; + cursor: pointer; + } + + button:hover { + background-color: colors.$secondary-color-light; + } + + button.mat-mdc-menu-item { + padding-left: 16px !important; + } +} + .action-btn.delete { color: colors.$error-color; } diff --git a/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.ts b/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.ts index 75144d0511..203e610bf9 100644 --- a/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.ts +++ b/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.ts @@ -1,6 +1,9 @@ import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; -import { Component, Input, OnChanges } from '@angular/core'; +import { Overlay, OverlayRef } from '@angular/cdk/overlay'; +import { TemplatePortal } from '@angular/cdk/portal'; +import { Component, Input, OnChanges, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core'; import { MatTableDataSource } from '@angular/material/table'; +import { ApplicationDocumentDto } from '../../../../services/application/application-document/application-document.dto'; import { PlanningReviewDocumentDto } from '../../../../services/planning-review/planning-review-document/planning-review-document.dto'; import { PlanningReviewDocumentService } from '../../../../services/planning-review/planning-review-document/planning-review-document.service'; @@ -15,11 +18,18 @@ export class EvidentiaryRecordComponent implements OnChanges { @Input() visibilityFlags: string[] = []; @Input() sortable = false; + @ViewChild('orderMenu') orderMenu!: TemplateRef<any>; + displayedColumns: string[] = ['index', 'type', 'fileName', 'source', 'uploadedAt', 'action', 'sorting']; documents: PlanningReviewDocumentDto[] = []; dataSource = new MatTableDataSource<PlanningReviewDocumentDto>([]); + overlayRef: OverlayRef | null = null; - constructor(private planningReviewDocumentService: PlanningReviewDocumentService) {} + constructor( + private planningReviewDocumentService: PlanningReviewDocumentService, + private overlay: Overlay, + private viewContainerRef: ViewContainerRef, + ) {} ngOnChanges(): void { this.loadDocuments(); @@ -44,8 +54,54 @@ export class EvidentiaryRecordComponent implements OnChanges { await this.planningReviewDocumentService.download(uuid, fileName); } - async onRowDropped(event: CdkDragDrop<PlanningReviewDocumentDto, any>) { - moveItemInArray(this.documents, event.previousIndex, event.currentIndex); + async onRowDropped(event: CdkDragDrop<ApplicationDocumentDto, any>) { + this.moveItem(event.previousIndex, event.currentIndex); + } + + async openMenu($event: MouseEvent, record: ApplicationDocumentDto) { + if (!this.sortable) { + return; + } + this.overlayRef?.detach(); + $event.preventDefault(); + const positionStrategy = this.overlay + .position() + .flexibleConnectedTo({ x: $event.x, y: $event.y }) + .withPositions([ + { + originX: 'end', + originY: 'bottom', + overlayX: 'start', + overlayY: 'top', + }, + ]); + + this.overlayRef = this.overlay.create({ + positionStrategy, + scrollStrategy: this.overlay.scrollStrategies.close(), + }); + + this.overlayRef.attach( + new TemplatePortal(this.orderMenu, this.viewContainerRef, { + $implicit: record, + }), + ); + } + + sendToBottom(record: ApplicationDocumentDto) { + const currentIndex = this.documents.findIndex((item) => item.uuid === record.uuid); + this.moveItem(currentIndex, this.documents.length - 1); + this.overlayRef?.detach(); + } + + sendToTop(record: ApplicationDocumentDto) { + const currentIndex = this.documents.findIndex((item) => item.uuid === record.uuid); + this.moveItem(currentIndex, 0); + this.overlayRef?.detach(); + } + + private moveItem(currentIndex: number, targetIndex: number) { + moveItemInArray(this.documents, currentIndex, targetIndex); const order = this.documents.map((doc, index) => ({ uuid: doc.uuid, order: index, diff --git a/alcs-frontend/src/app/shared/application-document/application-document.component.html b/alcs-frontend/src/app/shared/application-document/application-document.component.html index 84c70e07f3..b6928902a4 100644 --- a/alcs-frontend/src/app/shared/application-document/application-document.component.html +++ b/alcs-frontend/src/app/shared/application-document/application-document.component.html @@ -1,3 +1,14 @@ +<ng-template #orderMenu let-record> + <div class="menu"> + <div class="menu-item"> + <button (click)="sendToTop(record)" mat-menu-item>Move to Top</button> + </div> + <div class="menu-item"> + <button (click)="sendToBottom(record)" mat-menu-item>Move to Bottom</button> + </div> + </div> +</ng-template> + <div class="table-header"> <div> <h4>{{ tableTitle }}</h4> @@ -58,6 +69,7 @@ <h4>{{ tableTitle }}</h4> <tr *matHeaderRowDef="displayedColumns" mat-header-row></tr> <tr + (contextmenu)="openMenu($event, row)" *matRowDef="let row; columns: displayedColumns" cdkDrag cdkDragLockAxis="y" diff --git a/alcs-frontend/src/app/shared/application-document/application-document.component.scss b/alcs-frontend/src/app/shared/application-document/application-document.component.scss index ddccb94e92..0394306b1c 100644 --- a/alcs-frontend/src/app/shared/application-document/application-document.component.scss +++ b/alcs-frontend/src/app/shared/application-document/application-document.component.scss @@ -1,5 +1,32 @@ @use '../../../styles/colors'; +.menu { + background-color: colors.$white; + box-shadow: 0 4px 4px 0 #00000040; +} + +.menu-item { + display: flex; + flex-direction: column; + margin: 8px 0; + + button { + font-weight: normal !important; + text-align: left; + background-color: colors.$white; + border: none; + cursor: pointer; + } + + button:hover { + background-color: colors.$secondary-color-light; + } + + button.mat-mdc-menu-item { + padding-left: 16px !important; + } +} + .action-btn.delete { color: colors.$error-color; } @@ -41,6 +68,7 @@ a { } } -.cdk-drag-placeholder, .cdk-drag-preview { +.cdk-drag-placeholder, +.cdk-drag-preview { background-color: colors.$grey-light !important; } diff --git a/alcs-frontend/src/app/shared/application-document/application-document.component.ts b/alcs-frontend/src/app/shared/application-document/application-document.component.ts index ccbbe7c72d..c377a9fcab 100644 --- a/alcs-frontend/src/app/shared/application-document/application-document.component.ts +++ b/alcs-frontend/src/app/shared/application-document/application-document.component.ts @@ -1,5 +1,7 @@ import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; -import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { Overlay, OverlayRef } from '@angular/cdk/overlay'; +import { TemplatePortal } from '@angular/cdk/portal'; +import { Component, Input, OnChanges, SimpleChanges, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core'; import { MatTableDataSource } from '@angular/material/table'; import { ApplicationDocumentDto } from '../../services/application/application-document/application-document.dto'; import { ApplicationDocumentService } from '../../services/application/application-document/application-document.service'; @@ -15,11 +17,18 @@ export class ApplicationDocumentComponent implements OnChanges { @Input() visibilityFlags: string[] = []; @Input() sortable = false; + @ViewChild('orderMenu') orderMenu!: TemplateRef<any>; + displayedColumns: string[] = ['index', 'type', 'fileName', 'source', 'uploadedAt', 'action', 'sorting']; documents: ApplicationDocumentDto[] = []; dataSource = new MatTableDataSource<ApplicationDocumentDto>([]); + overlayRef: OverlayRef | null = null; - constructor(private applicationDocumentService: ApplicationDocumentService) {} + constructor( + private applicationDocumentService: ApplicationDocumentService, + private overlay: Overlay, + private viewContainerRef: ViewContainerRef, + ) {} ngOnChanges(changes: SimpleChanges): void { this.loadDocuments(); @@ -45,7 +54,53 @@ export class ApplicationDocumentComponent implements OnChanges { } async onRowDropped(event: CdkDragDrop<ApplicationDocumentDto, any>) { - moveItemInArray(this.documents, event.previousIndex, event.currentIndex); + this.moveItem(event.previousIndex, event.currentIndex); + } + + async openMenu($event: MouseEvent, record: ApplicationDocumentDto) { + if (!this.sortable) { + return; + } + this.overlayRef?.detach(); + $event.preventDefault(); + const positionStrategy = this.overlay + .position() + .flexibleConnectedTo({ x: $event.x, y: $event.y }) + .withPositions([ + { + originX: 'end', + originY: 'bottom', + overlayX: 'start', + overlayY: 'top', + }, + ]); + + this.overlayRef = this.overlay.create({ + positionStrategy, + scrollStrategy: this.overlay.scrollStrategies.close(), + }); + + this.overlayRef.attach( + new TemplatePortal(this.orderMenu, this.viewContainerRef, { + $implicit: record, + }), + ); + } + + sendToBottom(record: ApplicationDocumentDto) { + const currentIndex = this.documents.findIndex((item) => item.uuid === record.uuid); + this.moveItem(currentIndex, this.documents.length - 1); + this.overlayRef?.detach(); + } + + sendToTop(record: ApplicationDocumentDto) { + const currentIndex = this.documents.findIndex((item) => item.uuid === record.uuid); + this.moveItem(currentIndex, 0); + this.overlayRef?.detach(); + } + + private moveItem(currentIndex: number, targetIndex: number) { + moveItemInArray(this.documents, currentIndex, targetIndex); const order = this.documents.map((doc, index) => ({ uuid: doc.uuid, order: index, From 19e861ddada59ce9ccf36ee9f512aca5a4e37695 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Tue, 2 Apr 2024 10:50:44 -0700 Subject: [PATCH 072/153] Add snowplow to Portal --- portal-frontend/src/index.html | 49 +++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/portal-frontend/src/index.html b/portal-frontend/src/index.html index d969d9f565..ca34a10771 100644 --- a/portal-frontend/src/index.html +++ b/portal-frontend/src/index.html @@ -1,16 +1,51 @@ -<!doctype html> +<!DOCTYPE html> <html lang="en"> <head> - <meta charset="utf-8"> + <meta charset="utf-8" /> <title>ALCS Application Portal - - - - + + + + + + - + From a24cffc655d516bf51e5069a3f976aef78b95381 Mon Sep 17 00:00:00 2001 From: Tristan Slater <1631008+trslater@users.noreply.github.com> Date: Tue, 2 Apr 2024 12:00:45 -0700 Subject: [PATCH 073/153] Improve app/NOI decision long text field UX Make decision description and decision condition description text fields into `textarea`s. --- .../decision-condition/decision-condition.component.html | 2 +- .../decision-v2/decision-input/decision-input-v2.component.html | 2 +- .../decision-condition/decision-condition.component.html | 2 +- .../decision-v2/decision-input/decision-input-v2.component.html | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html index 8224b0609f..5d68b4f57f 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html @@ -52,7 +52,7 @@
{{ data.type?.label }}
Description - +
diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-input-v2.component.html b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-input-v2.component.html index fe4ce32c5f..8f78c897a6 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-input-v2.component.html +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-input-v2.component.html @@ -155,7 +155,7 @@

Resolution

Decision Description - +
diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html index 8224b0609f..5d68b4f57f 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html @@ -52,7 +52,7 @@
{{ data.type?.label }}
Description - +
diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.html b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.html index 930c73185d..dd81948487 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.html @@ -133,7 +133,7 @@

Resolution

Decision Description - +
From 14966f6e39703d92ea75074ce5f66cd655c7d086 Mon Sep 17 00:00:00 2001 From: Tristan Slater <1631008+trslater@users.noreply.github.com> Date: Tue, 2 Apr 2024 13:30:16 -0700 Subject: [PATCH 074/153] Remove redundant mixin def --- alcs-frontend/src/styles.scss | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/alcs-frontend/src/styles.scss b/alcs-frontend/src/styles.scss index 80ff3ee7de..349eeebe37 100644 --- a/alcs-frontend/src/styles.scss +++ b/alcs-frontend/src/styles.scss @@ -132,13 +132,13 @@ $alcs-warn: mat.define-palette($mat-custom-warn); // Create the theme object. A theme consists of configurations for individual // theming systems such as "color" or "typography". $alcs-theme: mat.define-light-theme( - ( - color: ( - primary: $alcs-primary, - accent: $alcs-accent, - warn: $alcs-warn, - ), - ) + ( + color: ( + primary: $alcs-primary, + accent: $alcs-accent, + warn: $alcs-warn, + ), + ) ); // Include theme styles for core and each component used in your app. @@ -285,18 +285,6 @@ mat-button-toggle-group { background-image: none; } -@mixin text-ellipsis($lines: 1) { - text-overflow: ellipsis; - overflow: hidden; - @if ($lines > 1) { - display: -webkit-box; - -webkit-line-clamp: $lines; - -webkit-box-orient: vertical; - } @else { - white-space: nowrap; - } -} - .margin-right { margin-right: 12px; } From 7b1c8bb74add1b3d56535e027eeaa8f93e8bc1e5 Mon Sep 17 00:00:00 2001 From: Tristan Slater <1631008+trslater@users.noreply.github.com> Date: Tue, 2 Apr 2024 13:41:37 -0700 Subject: [PATCH 075/153] Preserve new lines app/NOI decision text fields Affects decision description and decision condition description. --- .../conditions/condition/condition.component.html | 9 ++++++--- .../conditions/condition/condition.component.scss | 4 ++++ .../decision/decision-v2/decision-v2.component.html | 2 +- .../decision/decision-v2/decision-v2.component.scss | 4 ++++ .../conditions/condition/condition.component.html | 9 ++++++--- .../conditions/condition/condition.component.scss | 4 ++++ .../decision/decision-v2/decision-v2.component.html | 2 +- .../decision/decision-v2/decision-v2.component.scss | 4 ++++ 8 files changed, 30 insertions(+), 8 deletions(-) diff --git a/alcs-frontend/src/app/features/application/decision/conditions/condition/condition.component.html b/alcs-frontend/src/app/features/application/decision/conditions/condition/condition.component.html index 30a7671c59..a5beb464f5 100644 --- a/alcs-frontend/src/app/features/application/decision/conditions/condition/condition.component.html +++ b/alcs-frontend/src/app/features/application/decision/conditions/condition/condition.component.html @@ -109,9 +109,12 @@

{{ condition.type.label }}

Description
- {{ - condition.description - }} + {{ condition.description }} -
+
Decision Description
{{ decision.decisionDescription }} {{ condition.type.label }}
Description
- {{ - condition.description - }} + {{ condition.description }}
-
+
Decision Description
{{ decision.decisionDescription }} Date: Tue, 2 Apr 2024 13:47:31 -0700 Subject: [PATCH 076/153] commit before cleaning pr-import commands --- ..._documents_to_planning_review_documents.py | 26 +++++++------------ .../post_launch/migrate_documents.py | 18 +++++++++---- ...documents_to_planning_review_documents.sql | 4 +-- .../command_parser/document_command_parser.py | 21 +++++++++++++++ bin/migrate-oats-data/menu/menu.py | 4 +++ .../menu/post_launch_commands/documents.py | 26 ++++++++++++++++++- bin/migrate-oats-data/migrate.py | 6 +++++ .../planning-review-document.entity.ts | 3 +++ .../1712088761489-add_pr_audit_created_by.ts | 15 +++++++++++ .../1712090189047-add_contraint_to_pr_docs.ts | 15 +++++++++++ 10 files changed, 113 insertions(+), 25 deletions(-) create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1712088761489-add_pr_audit_created_by.ts create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1712090189047-add_contraint_to_pr_docs.ts diff --git a/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_planning_review_documents.py b/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_planning_review_documents.py index 5783388f8f..2d59f37237 100644 --- a/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_planning_review_documents.py +++ b/bin/migrate-oats-data/documents/post_launch/alcs_documents_to_planning_review_documents.py @@ -10,7 +10,7 @@ logger = setup_and_get_logger(etl_name) """ - This script connects to postgress version of OATS DB and links data from ALCS documents to ALCS notification_document table. + This script connects to postgress version of OATS DB and links data from ALCS documents to ALCS planning_review_document table. NOTE: Before performing document import you need to import SRWs and SRW documents. @@ -18,14 +18,14 @@ @inject_conn_pool -def link_srw_documents(conn=None, batch_size=BATCH_UPLOAD_SIZE): +def link_pr_documents(conn=None, batch_size=BATCH_UPLOAD_SIZE): """ function uses a decorator pattern @inject_conn_pool to inject a database connection pool to the function. It fetches the total count of documents and prints it to the console. Then, it fetches the documents to insert in batches using document IDs, constructs an insert query, and processes them. """ logger.info(f"Start {etl_name}") with conn.cursor(cursor_factory=RealDictCursor) as cursor: with open( - "documents/post_launch/sql/alcs_documents_to_notification_documents_count.sql", + "documents/post_launch/sql/planning_review/alcs_documents_to_planning_review_documents_count.sql", "r", encoding="utf-8", ) as sql_file: @@ -39,7 +39,7 @@ def link_srw_documents(conn=None, batch_size=BATCH_UPLOAD_SIZE): last_document_id = 0 with open( - "documents/post_launch/sql/alcs_documents_to_notification_documents.sql", + "documents/post_launch/sql/planning_review/alcs_documents_to_planning_review_documents.sql", "r", encoding="utf-8", ) as sql_file: @@ -87,27 +87,23 @@ def _insert_records(conn, cursor, rows): def _compile_insert_query(number_of_rows_to_insert): documents_to_insert = ",".join(["%s"] * number_of_rows_to_insert) return f""" - INSERT INTO alcs.notification_document( - notification_uuid, + INSERT INTO alcs.planning_review_document( + planning_review_uuid, document_uuid, type_code, visibility_flags, oats_document_id, oats_planning_review_id, audit_created_by, - survey_plan_number, - control_number, description ) VALUES{documents_to_insert} ON CONFLICT (oats_document_id, oats_planning_review_id) DO UPDATE SET - notification_uuid = EXCLUDED.notification_uuid, + planning_review_uuid = EXCLUDED.planning_review_uuid, document_uuid = EXCLUDED.document_uuid, type_code = EXCLUDED.type_code, visibility_flags = EXCLUDED.visibility_flags, audit_created_by = EXCLUDED.audit_created_by, - survey_plan_number = EXCLUDED.survey_plan_number, - control_number = EXCLUDED.control_number, description = EXCLUDED.description; """ @@ -123,25 +119,23 @@ def _prepare_data_to_insert(rows): def _map_data(row): return { - "notification_uuid": row["notification_uuid"], + "planning_review_uuid": row["planning_review_uuid"], "document_uuid": row["document_uuid"], "type_code": row["type_code"], "visibility_flags": row["visibility_flags"], "oats_document_id": row["oats_document_id"], "oats_planning_review_id": row["oats_planning_review_id"], "audit_created_by": OATS_ETL_USER, - "plan_number": row["plan_no"], - "control_number": row["control_no"], "description": row["description"], } @inject_conn_pool -def clean_notification_documents(conn=None): +def clean_planning_review_documents(conn=None): logger.info("Start documents cleaning") with conn.cursor() as cursor: cursor.execute( - f"DELETE FROM alcs.notification_document WHERE audit_created_by = '{OATS_ETL_USER}';" + f"DELETE FROM alcs.planning_review_document WHERE audit_created_by = '{OATS_ETL_USER}';" ) conn.commit() logger.info(f"Deleted items count = {cursor.rowcount}") diff --git a/bin/migrate-oats-data/documents/post_launch/migrate_documents.py b/bin/migrate-oats-data/documents/post_launch/migrate_documents.py index a2c2544e3e..9f253a2d45 100644 --- a/bin/migrate-oats-data/documents/post_launch/migrate_documents.py +++ b/bin/migrate-oats-data/documents/post_launch/migrate_documents.py @@ -6,6 +6,14 @@ link_srw_documents, clean_notification_documents, ) +from .alcs_documents_to_planning_review_documents import ( + link_pr_documents, + clean_planning_review_documents, +) +from .oats_documents_to_alcs_documents_planning_review import ( + import_oats_pr_documents, + document_pr_clean, +) def import_documents(batch_size): @@ -19,10 +27,10 @@ def clean_documents(): def import_prs_documents(batch_size): - # fill - return + import_oats_pr_documents(batch_size), + link_pr_documents(batch_size) -def clean_pr_documents(): - # fill - return +def clean_prs_documents(): + clean_planning_review_documents() + document_pr_clean() diff --git a/bin/migrate-oats-data/documents/post_launch/sql/planning_review/alcs_documents_to_planning_review_documents.sql b/bin/migrate-oats-data/documents/post_launch/sql/planning_review/alcs_documents_to_planning_review_documents.sql index 88b9d43f78..a9ebec6a29 100644 --- a/bin/migrate-oats-data/documents/post_launch/sql/planning_review/alcs_documents_to_planning_review_documents.sql +++ b/bin/migrate-oats-data/documents/post_launch/sql/planning_review/alcs_documents_to_planning_review_documents.sql @@ -5,7 +5,7 @@ with oats_documents_to_map as ( publicly_viewable_ind as is_public, app_lg_viewable_ind as is_app_lg, od.document_id as oats_document_id, - od.alr_planning_review_id as oats_planning_review_id, + od.planning_review_id as oats_planning_review_id, od."description" from oats.oats_documents od join alcs."document" d on d.oats_document_id = od.document_id::text @@ -26,7 +26,5 @@ select otm.planning_review_uuid, ) as visibility_flags, oats_document_id, oats_planning_review_id, - plan_no, - control_no, otm."description" from oats_documents_to_map otm \ No newline at end of file diff --git a/bin/migrate-oats-data/menu/command_parser/document_command_parser.py b/bin/migrate-oats-data/menu/command_parser/document_command_parser.py index fc175314a9..2813f6afe2 100644 --- a/bin/migrate-oats-data/menu/command_parser/document_command_parser.py +++ b/bin/migrate-oats-data/menu/command_parser/document_command_parser.py @@ -53,3 +53,24 @@ def noi_document_import_command_parser(import_batch_size, subparsers): metavar="", help=f"batch size (default: {import_batch_size})", ) + + +def pr_document_import_command_parser(import_batch_size, subparsers): + pr_document_import_command = subparsers.add_parser( + "pr-document-import", + help=f"imports & links pr documents specified batch size: (default: {import_batch_size})", + ) + pr_document_import_command.add_argument( + "--batch-size", + type=int, + default=import_batch_size, + metavar="", + help=f"batch size (default: {import_batch_size})", + ) + + +def pr_document_clean_command_parser(subparsers): + subparsers.add_parser( + "pr-document-clean", + help="Clean planning review documents", + ) diff --git a/bin/migrate-oats-data/menu/menu.py b/bin/migrate-oats-data/menu/menu.py index 53b44eb072..08b795f6e0 100644 --- a/bin/migrate-oats-data/menu/menu.py +++ b/bin/migrate-oats-data/menu/menu.py @@ -12,6 +12,8 @@ application_document_import_command_parser, document_noi_import_command_parser, noi_document_import_command_parser, + pr_document_import_command_parser, + pr_document_clean_command_parser, ) from .command_parser.user_command_parser import ( user_import_command_parser, @@ -68,6 +70,8 @@ def setup_menu_args_parser(import_batch_size): srw_clean_command_parser(subparsers) planning_review_import_command_parser(import_batch_size, subparsers) planning_review_clean_command_parser(subparsers) + pr_document_import_command_parser(import_batch_size, subparsers) + pr_document_clean_command_parser(subparsers) inquiry_import_command_parser(import_batch_size, subparsers) inquiry_clean_command_parser(subparsers) subparsers.add_parser("clean", help="Clean all imported data") diff --git a/bin/migrate-oats-data/menu/post_launch_commands/documents.py b/bin/migrate-oats-data/menu/post_launch_commands/documents.py index d3daba0c60..b959e196b5 100644 --- a/bin/migrate-oats-data/menu/post_launch_commands/documents.py +++ b/bin/migrate-oats-data/menu/post_launch_commands/documents.py @@ -1,4 +1,9 @@ -from documents.post_launch import import_documents, clean_documents +from documents.post_launch import ( + import_documents, + clean_documents, + import_prs_documents, + clean_prs_documents, +) from srw.post_launch.srw_migration import srw_survey_plan_update @@ -21,3 +26,22 @@ def document_clean(console): console.log("Beginning ALCS Document clean") with console.status("[bold green]Cleaning ALCS Documents...\n") as status: clean_documents() + + +def document_import_pr(console, args): + console.log("Beginning OATS -> ALCS document import process") + with console.status( + "[bold green]document import (Document related table update in ALCS)...\n" + ) as status: + if args.batch_size: + import_batch_size = args.batch_size + + console.log(f"Processing documents import in batch size = {import_batch_size}") + + import_prs_documents(batch_size=import_batch_size) + + +def document_clean_pr(console): + console.log("Beginning ALCS Document clean") + with console.status("[bold green]Cleaning ALCS Documents...\n") as status: + clean_prs_documents() diff --git a/bin/migrate-oats-data/migrate.py b/bin/migrate-oats-data/migrate.py index 25f92cbd5a..4f3c4514bb 100644 --- a/bin/migrate-oats-data/migrate.py +++ b/bin/migrate-oats-data/migrate.py @@ -20,6 +20,8 @@ planning_review_import, inquiry_import, inquiry_clean, + document_import_pr, + document_clean_pr, ) from db import connection_pool from common import BATCH_UPLOAD_SIZE, setup_and_get_logger @@ -65,6 +67,10 @@ inquiry_import(console, args) case "inquiry-clean": inquiry_clean(console) + case "pr-document-import": + document_import_pr(console, args) + case "pr-document-clean": + document_clean_pr(console) finally: if connection_pool: diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.entity.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.entity.ts index 2a255485c9..038844b189 100644 --- a/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.entity.ts +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.entity.ts @@ -74,6 +74,9 @@ export class PlanningReviewDocument extends BaseEntity { }) oatsDocumentId?: string | null; + @Column({ nullable: true }) + auditCreatedBy: string; + @OneToOne(() => Document) @JoinColumn() document: Document; diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1712088761489-add_pr_audit_created_by.ts b/services/apps/alcs/src/providers/typeorm/migrations/1712088761489-add_pr_audit_created_by.ts new file mode 100644 index 0000000000..9fe43dd91c --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1712088761489-add_pr_audit_created_by.ts @@ -0,0 +1,15 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddPrAuditCreatedBy1712088761489 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_document" ADD "audit_created_by" varchar`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_document" DROP COLUMN "audit_created_by"`, + ); + } +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1712090189047-add_contraint_to_pr_docs.ts b/services/apps/alcs/src/providers/typeorm/migrations/1712090189047-add_contraint_to_pr_docs.ts new file mode 100644 index 0000000000..e76d8ac055 --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1712090189047-add_contraint_to_pr_docs.ts @@ -0,0 +1,15 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddContraintToPrDocs1712090189047 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE alcs.planning_review_document ADD CONSTRAINT unique_oats_ids UNIQUE(oats_document_id, oats_planning_review_id)`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review_document" DROP CONSTRAINT unique_oats_ids`, + ); + } +} From ed7b985b351cd75084ede66ae0ea9750d6b6d81c Mon Sep 17 00:00:00 2001 From: Liam Stoddard Date: Tue, 2 Apr 2024 14:08:16 -0700 Subject: [PATCH 077/153] update commands --- .../post_launch/migrate_documents.py | 12 +++-------- .../command_parser/document_command_parser.py | 20 +++--------------- bin/migrate-oats-data/menu/menu.py | 6 ++---- .../menu/post_launch_commands/clean_all.py | 2 ++ .../menu/post_launch_commands/documents.py | 21 ------------------- .../menu/post_launch_commands/import_all.py | 6 ++++++ bin/migrate-oats-data/migrate.py | 6 ------ 7 files changed, 16 insertions(+), 57 deletions(-) diff --git a/bin/migrate-oats-data/documents/post_launch/migrate_documents.py b/bin/migrate-oats-data/documents/post_launch/migrate_documents.py index 9f253a2d45..6e693d723a 100644 --- a/bin/migrate-oats-data/documents/post_launch/migrate_documents.py +++ b/bin/migrate-oats-data/documents/post_launch/migrate_documents.py @@ -19,18 +19,12 @@ def import_documents(batch_size): import_oats_srw_documents(batch_size) link_srw_documents(batch_size) - - -def clean_documents(): - clean_notification_documents() - document_clean() - - -def import_prs_documents(batch_size): import_oats_pr_documents(batch_size), link_pr_documents(batch_size) -def clean_prs_documents(): +def clean_documents(): + clean_notification_documents() clean_planning_review_documents() + document_clean() document_pr_clean() diff --git a/bin/migrate-oats-data/menu/command_parser/document_command_parser.py b/bin/migrate-oats-data/menu/command_parser/document_command_parser.py index 2813f6afe2..4b35510502 100644 --- a/bin/migrate-oats-data/menu/command_parser/document_command_parser.py +++ b/bin/migrate-oats-data/menu/command_parser/document_command_parser.py @@ -55,22 +55,8 @@ def noi_document_import_command_parser(import_batch_size, subparsers): ) -def pr_document_import_command_parser(import_batch_size, subparsers): - pr_document_import_command = subparsers.add_parser( - "pr-document-import", - help=f"imports & links pr documents specified batch size: (default: {import_batch_size})", - ) - pr_document_import_command.add_argument( - "--batch-size", - type=int, - default=import_batch_size, - metavar="", - help=f"batch size (default: {import_batch_size})", - ) - - -def pr_document_clean_command_parser(subparsers): +def document_clean_command_parser(subparsers): subparsers.add_parser( - "pr-document-clean", - help="Clean planning review documents", + "document-clean", + help="Clean documents", ) diff --git a/bin/migrate-oats-data/menu/menu.py b/bin/migrate-oats-data/menu/menu.py index 08b795f6e0..00c64b186a 100644 --- a/bin/migrate-oats-data/menu/menu.py +++ b/bin/migrate-oats-data/menu/menu.py @@ -12,8 +12,7 @@ application_document_import_command_parser, document_noi_import_command_parser, noi_document_import_command_parser, - pr_document_import_command_parser, - pr_document_clean_command_parser, + document_clean_command_parser, ) from .command_parser.user_command_parser import ( user_import_command_parser, @@ -58,6 +57,7 @@ def setup_menu_args_parser(import_batch_size): application_import_command_parser(import_batch_size, subparsers) app_clean_command_parser(subparsers) document_import_command_parser(import_batch_size, subparsers) + document_clean_command_parser(subparsers) application_document_import_command_parser(import_batch_size, subparsers) noi_import_command_parser(import_batch_size, subparsers) noi_clean_command_parser(subparsers) @@ -70,8 +70,6 @@ def setup_menu_args_parser(import_batch_size): srw_clean_command_parser(subparsers) planning_review_import_command_parser(import_batch_size, subparsers) planning_review_clean_command_parser(subparsers) - pr_document_import_command_parser(import_batch_size, subparsers) - pr_document_clean_command_parser(subparsers) inquiry_import_command_parser(import_batch_size, subparsers) inquiry_clean_command_parser(subparsers) subparsers.add_parser("clean", help="Clean all imported data") diff --git a/bin/migrate-oats-data/menu/post_launch_commands/clean_all.py b/bin/migrate-oats-data/menu/post_launch_commands/clean_all.py index 7204891f36..c6cdf5c9cc 100644 --- a/bin/migrate-oats-data/menu/post_launch_commands/clean_all.py +++ b/bin/migrate-oats-data/menu/post_launch_commands/clean_all.py @@ -4,6 +4,7 @@ from noi.post_launch import clean_notice_of_intent from srw.post_launch import clean_srw from documents.post_launch import clean_documents +from planning_review.migrate_planning_review import clean_planning_review def clean_all(console, args): @@ -13,5 +14,6 @@ def clean_all(console, args): clean_alcs_applications() clean_notice_of_intent() clean_srw() + clean_planning_review() console.log("Done") diff --git a/bin/migrate-oats-data/menu/post_launch_commands/documents.py b/bin/migrate-oats-data/menu/post_launch_commands/documents.py index b959e196b5..d0a1edf93e 100644 --- a/bin/migrate-oats-data/menu/post_launch_commands/documents.py +++ b/bin/migrate-oats-data/menu/post_launch_commands/documents.py @@ -1,8 +1,6 @@ from documents.post_launch import ( import_documents, clean_documents, - import_prs_documents, - clean_prs_documents, ) from srw.post_launch.srw_migration import srw_survey_plan_update @@ -26,22 +24,3 @@ def document_clean(console): console.log("Beginning ALCS Document clean") with console.status("[bold green]Cleaning ALCS Documents...\n") as status: clean_documents() - - -def document_import_pr(console, args): - console.log("Beginning OATS -> ALCS document import process") - with console.status( - "[bold green]document import (Document related table update in ALCS)...\n" - ) as status: - if args.batch_size: - import_batch_size = args.batch_size - - console.log(f"Processing documents import in batch size = {import_batch_size}") - - import_prs_documents(batch_size=import_batch_size) - - -def document_clean_pr(console): - console.log("Beginning ALCS Document clean") - with console.status("[bold green]Cleaning ALCS Documents...\n") as status: - clean_prs_documents() diff --git a/bin/migrate-oats-data/menu/post_launch_commands/import_all.py b/bin/migrate-oats-data/menu/post_launch_commands/import_all.py index 691e924f0c..4fde3a61eb 100644 --- a/bin/migrate-oats-data/menu/post_launch_commands/import_all.py +++ b/bin/migrate-oats-data/menu/post_launch_commands/import_all.py @@ -4,6 +4,9 @@ ) from srw.post_launch.srw_migration import process_srw, srw_survey_plan_update from documents.post_launch.migrate_documents import import_documents +from planning_review.migrate_planning_review import ( + process_planning_review, +) def import_all(console, args): @@ -30,4 +33,7 @@ def import_all(console, args): # These are updates that need to happen after the SRW document import srw_survey_plan_update(batch_size=import_batch_size) + console.log("Process Planning Reviews") + process_planning_review(batch_size=import_batch_size) + console.log("Done") diff --git a/bin/migrate-oats-data/migrate.py b/bin/migrate-oats-data/migrate.py index 4f3c4514bb..25f92cbd5a 100644 --- a/bin/migrate-oats-data/migrate.py +++ b/bin/migrate-oats-data/migrate.py @@ -20,8 +20,6 @@ planning_review_import, inquiry_import, inquiry_clean, - document_import_pr, - document_clean_pr, ) from db import connection_pool from common import BATCH_UPLOAD_SIZE, setup_and_get_logger @@ -67,10 +65,6 @@ inquiry_import(console, args) case "inquiry-clean": inquiry_clean(console) - case "pr-document-import": - document_import_pr(console, args) - case "pr-document-clean": - document_clean_pr(console) finally: if connection_pool: From 4315fde5f851c49f626a9261f54c11daa21cf18c Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Tue, 2 Apr 2024 14:33:41 -0700 Subject: [PATCH 078/153] Add some unit tests --- .../application-document.service.spec.ts | 59 ++++++- .../application-submission.service.spec.ts | 27 ++++ .../application-submission.service.ts | 13 -- ...ication-decision-condition.service.spec.ts | 69 +++++++- .../inquiry-document.service.spec.ts | 34 +++- ...tice-of-intent-decision-v2.service.spec.ts | 76 +++++++-- .../noi-document/noi-document.service.spec.ts | 73 ++++++++- .../notification-document.service.spec.ts | 73 ++++++++- .../planning-referral.service.spec.ts | 153 ++++++++++++++++++ .../planning-review-decision.service.spec.ts | 55 +++++++ .../planning-review-document.service.spec.ts | 51 +++++- .../planning-review-meeting.service.spec.ts | 153 ++++++++++++++++++ 12 files changed, 793 insertions(+), 43 deletions(-) create mode 100644 alcs-frontend/src/app/services/planning-review/planning-referral.service.spec.ts create mode 100644 alcs-frontend/src/app/services/planning-review/planning-review-meeting/planning-review-meeting.service.spec.ts diff --git a/alcs-frontend/src/app/services/application/application-document/application-document.service.spec.ts b/alcs-frontend/src/app/services/application/application-document/application-document.service.spec.ts index ac93079ba1..9bf621a197 100644 --- a/alcs-frontend/src/app/services/application/application-document/application-document.service.spec.ts +++ b/alcs-frontend/src/app/services/application/application-document/application-document.service.spec.ts @@ -35,13 +35,29 @@ describe('ApplicationDocumentService', () => { expect(service).toBeTruthy(); }); - it('should make a get call for list', async () => { + it('should make a get call for fetchTypes', async () => { + httpClient.get.mockReturnValue( + of([ + { + code: '1', + }, + ]), + ); + + const res = await service.fetchTypes(); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(res.length).toEqual(1); + expect(res[0].code).toEqual('1'); + }); + + it('should make a get call for listByVisibility', async () => { httpClient.get.mockReturnValue( of([ { uuid: '1', }, - ]) + ]), ); const res = await service.listByVisibility('1', []); @@ -51,11 +67,27 @@ describe('ApplicationDocumentService', () => { expect(res[0].uuid).toEqual('1'); }); + it('should make a get call for getApplicantDocuments', async () => { + httpClient.get.mockReturnValue( + of([ + { + uuid: '1', + }, + ]), + ); + + const res = await service.getApplicantDocuments('1'); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(res.length).toEqual(1); + expect(res[0].uuid).toEqual('1'); + }); + it('should make a delete call for delete', async () => { httpClient.delete.mockReturnValue( of({ uuid: '1', - }) + }), ); const res = await service.delete('1'); @@ -87,7 +119,7 @@ describe('ApplicationDocumentService', () => { { uuid: '1', }, - ]) + ]), ); const res = await service.getReviewDocuments('1'); @@ -101,11 +133,28 @@ describe('ApplicationDocumentService', () => { httpClient.post.mockReturnValue( of({ uuid: '1', - }) + }), ); await service.updateSort([]); expect(httpClient.post).toHaveBeenCalledTimes(1); }); + + it('should make a post call for update', async () => { + httpClient.post.mockReturnValue( + of({ + uuid: '1', + }), + ); + + const res = await service.update('uuid', { + fileName: '', + source: DOCUMENT_SOURCE.APPLICANT, + typeCode: DOCUMENT_TYPE.CERTIFICATE_OF_TITLE, + visibilityFlags: [], + }); + + expect(httpClient.post).toHaveBeenCalledTimes(1); + }); }); diff --git a/alcs-frontend/src/app/services/application/application-submission/application-submission.service.spec.ts b/alcs-frontend/src/app/services/application/application-submission/application-submission.service.spec.ts index 6093edbf74..5f94cfdd01 100644 --- a/alcs-frontend/src/app/services/application/application-submission/application-submission.service.spec.ts +++ b/alcs-frontend/src/app/services/application/application-submission/application-submission.service.spec.ts @@ -140,4 +140,31 @@ describe('ApplicationSubmissionService', () => { expect(result).toEqual(mockSubmittedApplication); expect(mockHttpClient.get).toBeCalledTimes(1); }); + + it('should call patch when updating a submission', async () => { + mockHttpClient.patch.mockReturnValue(of(mockSubmittedApplication)); + + const result = await service.update('1', {}); + + expect(result).toEqual(mockSubmittedApplication); + expect(mockHttpClient.patch).toBeCalledTimes(1); + }); + + it('should call get for loading transferees', async () => { + mockHttpClient.get.mockReturnValue(of(mockSubmittedApplication)); + + const result = await service.fetchTransferees('1'); + + expect(result).toEqual(mockSubmittedApplication); + expect(mockHttpClient.get).toBeCalledTimes(1); + }); + + it('should call post for return to lfng', async () => { + mockHttpClient.post.mockReturnValue(of(mockSubmittedApplication)); + + const result = await service.returnToLfng('1', ''); + + expect(result).toEqual(mockSubmittedApplication); + expect(mockHttpClient.post).toBeCalledTimes(1); + }); }); diff --git a/alcs-frontend/src/app/services/application/application-submission/application-submission.service.ts b/alcs-frontend/src/app/services/application/application-submission/application-submission.service.ts index ee3bad5a7f..aa5a12eeac 100644 --- a/alcs-frontend/src/app/services/application/application-submission/application-submission.service.ts +++ b/alcs-frontend/src/app/services/application/application-submission/application-submission.service.ts @@ -25,19 +25,6 @@ export class ApplicationSubmissionService { } } - async setSubmissionStatus(fileNumber: string, statusCode: string): Promise { - try { - return firstValueFrom( - this.http.patch(`${this.baseUrl}/${fileNumber}/update-status`, { - statusCode, - }), - ); - } catch (e) { - this.toastService.showErrorToast('Failed to update Application Submission Status'); - throw e; - } - } - update(fileNumber: string, update: UpdateApplicationSubmissionDto) { try { return firstValueFrom(this.http.patch(`${this.baseUrl}/${fileNumber}`, update)); diff --git a/alcs-frontend/src/app/services/application/decision/application-decision-v2/application-decision-condition/application-decision-condition.service.spec.ts b/alcs-frontend/src/app/services/application/decision/application-decision-v2/application-decision-condition/application-decision-condition.service.spec.ts index f778a957d6..48f91f9ef4 100644 --- a/alcs-frontend/src/app/services/application/decision/application-decision-v2/application-decision-condition/application-decision-condition.service.spec.ts +++ b/alcs-frontend/src/app/services/application/decision/application-decision-v2/application-decision-condition/application-decision-condition.service.spec.ts @@ -1,8 +1,8 @@ import { HttpClient } from '@angular/common/http'; import { TestBed } from '@angular/core/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { ToastService } from '../../../../toast/toast.service'; import { of, throwError } from 'rxjs'; +import { ToastService } from '../../../../toast/toast.service'; import { ApplicationDecisionConditionService } from './application-decision-condition.service'; @@ -12,8 +12,8 @@ describe('ApplicationDecisionConditionService', () => { let mockToastService: DeepMocked; beforeEach(() => { - mockHttpClient = createMock() - mockToastService = createMock() + mockHttpClient = createMock(); + mockToastService = createMock(); TestBed.configureTestingModule({ providers: [ @@ -38,7 +38,7 @@ describe('ApplicationDecisionConditionService', () => { mockHttpClient.patch.mockReturnValue( of({ applicationFileNumber: '1', - }) + }), ); await service.update('1', {}); @@ -51,7 +51,7 @@ describe('ApplicationDecisionConditionService', () => { mockHttpClient.patch.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); try { @@ -63,4 +63,63 @@ describe('ApplicationDecisionConditionService', () => { expect(mockHttpClient.patch).toHaveBeenCalledTimes(1); expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); }); + + it('should make an http get and show a success toast when fetchPlanNumbers succeeds', async () => { + mockHttpClient.get.mockReturnValue( + of({ + applicationFileNumber: '1', + }), + ); + + await service.fetchPlanNumbers('1'); + + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + }); + + it('should show a toast message if fetchPlanNumbers fails', async () => { + mockHttpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }), + ); + + try { + await service.fetchPlanNumbers('1'); + } catch (e) { + //OM NOM NOM + } + + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + + it('should make an http patch and show a success toast when updatePlanNumbers succeeds', async () => { + mockHttpClient.patch.mockReturnValue( + of({ + applicationFileNumber: '1', + }), + ); + + await service.updatePlanNumbers('1', '', null); + + expect(mockHttpClient.patch).toHaveBeenCalledTimes(1); + expect(mockToastService.showSuccessToast).toHaveBeenCalledTimes(1); + }); + + it('should show a failure toast message if updatePlanNumbers fails', async () => { + mockHttpClient.patch.mockReturnValue( + throwError(() => { + new Error(''); + }), + ); + + try { + await service.updatePlanNumbers('1', '', null); + } catch (e) { + //OM NOM NOM + } + + expect(mockHttpClient.patch).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); }); diff --git a/alcs-frontend/src/app/services/inquiry/inquiry-document/inquiry-document.service.spec.ts b/alcs-frontend/src/app/services/inquiry/inquiry-document/inquiry-document.service.spec.ts index 154b461426..78aacf814c 100644 --- a/alcs-frontend/src/app/services/inquiry/inquiry-document/inquiry-document.service.spec.ts +++ b/alcs-frontend/src/app/services/inquiry/inquiry-document/inquiry-document.service.spec.ts @@ -35,7 +35,7 @@ describe('InquiryDocumentService', () => { expect(service).toBeTruthy(); }); - it('should make a get call for list', async () => { + it('should make a get call for listAll', async () => { httpClient.get.mockReturnValue( of([ { @@ -51,6 +51,22 @@ describe('InquiryDocumentService', () => { expect(res[0].uuid).toEqual('1'); }); + it('should make a get call for fetchTypes', async () => { + httpClient.get.mockReturnValue( + of([ + { + code: '1', + }, + ]), + ); + + const res = await service.fetchTypes(); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(res.length).toEqual(1); + expect(res[0].code).toEqual('1'); + }); + it('should make a delete call for delete', async () => { httpClient.delete.mockReturnValue( of({ @@ -91,4 +107,20 @@ describe('InquiryDocumentService', () => { expect(httpClient.post).toHaveBeenCalledTimes(1); }); + + it('should make a post call for update', async () => { + httpClient.post.mockReturnValue( + of({ + uuid: '1', + }), + ); + + const res = await service.update('uuid', { + fileName: '', + source: DOCUMENT_SOURCE.APPLICANT, + typeCode: DOCUMENT_TYPE.CERTIFICATE_OF_TITLE, + }); + + expect(httpClient.post).toHaveBeenCalledTimes(1); + }); }); diff --git a/alcs-frontend/src/app/services/notice-of-intent/decision-v2/notice-of-intent-decision-v2.service.spec.ts b/alcs-frontend/src/app/services/notice-of-intent/decision-v2/notice-of-intent-decision-v2.service.spec.ts index de9ed5c6ef..44f0deefc6 100644 --- a/alcs-frontend/src/app/services/notice-of-intent/decision-v2/notice-of-intent-decision-v2.service.spec.ts +++ b/alcs-frontend/src/app/services/notice-of-intent/decision-v2/notice-of-intent-decision-v2.service.spec.ts @@ -40,20 +40,21 @@ describe('NoticeOfIntentDecisionV2Service', () => { { fileNumber: '1', }, - ]) + ]), ); const res = await service.fetchByFileNumber('1'); expect(res.length).toEqual(1); expect(res[0].fileNumber).toEqual('1'); + expect(httpClient.get).toHaveBeenCalledTimes(1); }); it('should show a toast message if fetch fails', async () => { httpClient.get.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); const res = await service.fetchByFileNumber('1'); @@ -62,11 +63,39 @@ describe('NoticeOfIntentDecisionV2Service', () => { expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); }); + it('should fetch and return types', async () => { + httpClient.get.mockReturnValue( + of([ + { + fileNumber: '1', + }, + ]), + ); + + const res = await service.fetchCodes(); + + expect(res).toBeDefined(); + expect(httpClient.get).toHaveBeenCalledTimes(1); + }); + + it('should show a toast message if fetch types fails', async () => { + httpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }), + ); + + const res = await service.fetchCodes(); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + it('should make an http patch and show a success toast when updating', async () => { httpClient.patch.mockReturnValue( of({ fileNumber: '1', - }) + }), ); await service.update('1', { @@ -81,7 +110,7 @@ describe('NoticeOfIntentDecisionV2Service', () => { httpClient.patch.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); try { @@ -100,7 +129,7 @@ describe('NoticeOfIntentDecisionV2Service', () => { httpClient.post.mockReturnValue( of({ fileNumber: '1', - }) + }), ); await service.create({ @@ -121,7 +150,7 @@ describe('NoticeOfIntentDecisionV2Service', () => { httpClient.post.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); try { @@ -146,7 +175,7 @@ describe('NoticeOfIntentDecisionV2Service', () => { httpClient.delete.mockReturnValue( of({ fileNumber: '1', - }) + }), ); await service.delete(''); @@ -159,7 +188,7 @@ describe('NoticeOfIntentDecisionV2Service', () => { httpClient.delete.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); try { @@ -188,7 +217,7 @@ describe('NoticeOfIntentDecisionV2Service', () => { { fileNumber: '1', }, - ]) + ]), ); await service.updateFile('', '', ''); @@ -199,7 +228,7 @@ describe('NoticeOfIntentDecisionV2Service', () => { httpClient.delete.mockReturnValue( of({ fileNumber: '1', - }) + }), ); await service.deleteFile('', ''); @@ -214,4 +243,31 @@ describe('NoticeOfIntentDecisionV2Service', () => { expect(httpClient.get).toHaveBeenCalledTimes(1); }); + + it('should fetch and load a decision', async () => { + httpClient.get.mockReturnValue( + of([ + { + fileNumber: '1', + }, + ]), + ); + + await service.loadDecision('uuid'); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + }); + + it('should show a toast message if load decision fails', async () => { + httpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }), + ); + + const res = await service.loadDecision('uuid'); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); + }); }); diff --git a/alcs-frontend/src/app/services/notice-of-intent/noi-document/noi-document.service.spec.ts b/alcs-frontend/src/app/services/notice-of-intent/noi-document/noi-document.service.spec.ts index c5afd1260c..3de906e3b9 100644 --- a/alcs-frontend/src/app/services/notice-of-intent/noi-document/noi-document.service.spec.ts +++ b/alcs-frontend/src/app/services/notice-of-intent/noi-document/noi-document.service.spec.ts @@ -35,13 +35,45 @@ describe('NoiDocumentService', () => { expect(service).toBeTruthy(); }); - it('should make a get call for list', async () => { + it('should make a get call for listAll', async () => { httpClient.get.mockReturnValue( of([ { uuid: '1', }, - ]) + ]), + ); + + const res = await service.listAll('1'); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(res.length).toEqual(1); + expect(res[0].uuid).toEqual('1'); + }); + + it('should make a get call for fetchTypes', async () => { + httpClient.get.mockReturnValue( + of([ + { + code: '1', + }, + ]), + ); + + const res = await service.fetchTypes(); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(res.length).toEqual(1); + expect(res[0].code).toEqual('1'); + }); + + it('should make a get call for listByVisibility', async () => { + httpClient.get.mockReturnValue( + of([ + { + uuid: '1', + }, + ]), ); const res = await service.listByVisibility('1', []); @@ -51,11 +83,27 @@ describe('NoiDocumentService', () => { expect(res[0].uuid).toEqual('1'); }); + it('should make a get call for getApplicantDocuments', async () => { + httpClient.get.mockReturnValue( + of([ + { + uuid: '1', + }, + ]), + ); + + const res = await service.getApplicantDocuments('1'); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(res.length).toEqual(1); + expect(res[0].uuid).toEqual('1'); + }); + it('should make a delete call for delete', async () => { httpClient.delete.mockReturnValue( of({ uuid: '1', - }) + }), ); const res = await service.delete('1'); @@ -87,7 +135,7 @@ describe('NoiDocumentService', () => { { uuid: '1', }, - ]) + ]), ); const res = await service.getReviewDocuments('1'); @@ -96,4 +144,21 @@ describe('NoiDocumentService', () => { expect(res.length).toEqual(1); expect(res[0].uuid).toEqual('1'); }); + + it('should make a post call for update', async () => { + httpClient.post.mockReturnValue( + of({ + uuid: '1', + }), + ); + + const res = await service.update('uuid', { + fileName: '', + source: DOCUMENT_SOURCE.APPLICANT, + typeCode: DOCUMENT_TYPE.CERTIFICATE_OF_TITLE, + visibilityFlags: [], + }); + + expect(httpClient.post).toHaveBeenCalledTimes(1); + }); }); diff --git a/alcs-frontend/src/app/services/notification/notification-document/notification-document.service.spec.ts b/alcs-frontend/src/app/services/notification/notification-document/notification-document.service.spec.ts index d31350dfd9..7fc3b38b7a 100644 --- a/alcs-frontend/src/app/services/notification/notification-document/notification-document.service.spec.ts +++ b/alcs-frontend/src/app/services/notification/notification-document/notification-document.service.spec.ts @@ -35,13 +35,45 @@ describe('NoiDocumentService', () => { expect(service).toBeTruthy(); }); - it('should make a get call for list', async () => { + it('should make a get call for listAll', async () => { httpClient.get.mockReturnValue( of([ { uuid: '1', }, - ]) + ]), + ); + + const res = await service.listAll('1'); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(res.length).toEqual(1); + expect(res[0].uuid).toEqual('1'); + }); + + it('should make a get call for fetchTypes', async () => { + httpClient.get.mockReturnValue( + of([ + { + code: '1', + }, + ]), + ); + + const res = await service.fetchTypes(); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(res.length).toEqual(1); + expect(res[0].code).toEqual('1'); + }); + + it('should make a get call for listByVisibility', async () => { + httpClient.get.mockReturnValue( + of([ + { + uuid: '1', + }, + ]), ); const res = await service.listByVisibility('1', []); @@ -51,11 +83,27 @@ describe('NoiDocumentService', () => { expect(res[0].uuid).toEqual('1'); }); + it('should make a get call for getApplicantDocuments', async () => { + httpClient.get.mockReturnValue( + of([ + { + uuid: '1', + }, + ]), + ); + + const res = await service.getApplicantDocuments('1'); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(res.length).toEqual(1); + expect(res[0].uuid).toEqual('1'); + }); + it('should make a delete call for delete', async () => { httpClient.delete.mockReturnValue( of({ uuid: '1', - }) + }), ); const res = await service.delete('1'); @@ -87,7 +135,7 @@ describe('NoiDocumentService', () => { { uuid: '1', }, - ]) + ]), ); const res = await service.getReviewDocuments('1'); @@ -96,4 +144,21 @@ describe('NoiDocumentService', () => { expect(res.length).toEqual(1); expect(res[0].uuid).toEqual('1'); }); + + it('should make a post call for update', async () => { + httpClient.post.mockReturnValue( + of({ + uuid: '1', + }), + ); + + const res = await service.update('uuid', { + fileName: '', + source: DOCUMENT_SOURCE.APPLICANT, + typeCode: DOCUMENT_TYPE.CERTIFICATE_OF_TITLE, + visibilityFlags: [], + }); + + expect(httpClient.post).toHaveBeenCalledTimes(1); + }); }); diff --git a/alcs-frontend/src/app/services/planning-review/planning-referral.service.spec.ts b/alcs-frontend/src/app/services/planning-review/planning-referral.service.spec.ts new file mode 100644 index 0000000000..58c4077163 --- /dev/null +++ b/alcs-frontend/src/app/services/planning-review/planning-referral.service.spec.ts @@ -0,0 +1,153 @@ +import { HttpClient } from '@angular/common/http'; +import { TestBed } from '@angular/core/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { of, throwError } from 'rxjs'; +import { ToastService } from '../toast/toast.service'; +import { PlanningReferralService } from './planning-referral.service'; + +describe('PlanningReferralService', () => { + let service: PlanningReferralService; + let httpClient: DeepMocked; + let toastService: DeepMocked; + + beforeEach(() => { + httpClient = createMock(); + toastService = createMock(); + + TestBed.configureTestingModule({ + providers: [ + { + provide: HttpClient, + useValue: httpClient, + }, + { + provide: ToastService, + useValue: toastService, + }, + ], + }); + service = TestBed.inject(PlanningReferralService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should call get for fetchByCardUuid', async () => { + httpClient.get.mockReturnValue( + of({ + fileNumber: '1', + }), + ); + + await service.fetchByCardUuid('card-uuid'); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + }); + + it('should show an error toast message if fetchByCardUuid fails', async () => { + httpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }), + ); + + const res = await service.fetchByCardUuid('card-uuid'); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(res).toBeUndefined(); + expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + + it('should call post for create', async () => { + httpClient.post.mockReturnValue( + of({ + fileNumber: '1', + }), + ); + + await service.create({ + planningReviewUuid: '', + referralDescription: '', + submissionDate: 0, + }); + + expect(httpClient.post).toHaveBeenCalledTimes(1); + }); + + it('should show an error toast message if create fails', async () => { + httpClient.post.mockReturnValue( + throwError(() => { + new Error(''); + }), + ); + + const res = await service.create({ + planningReviewUuid: '', + referralDescription: '', + submissionDate: 0, + }); + + expect(httpClient.post).toHaveBeenCalledTimes(1); + expect(res).toBeUndefined(); + expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + + it('should call patch for update', async () => { + httpClient.patch.mockReturnValue( + of({ + fileNumber: '1', + }), + ); + + await service.update('', { + referralDescription: '', + submissionDate: 0, + }); + + expect(httpClient.patch).toHaveBeenCalledTimes(1); + }); + + it('should show an error toast message if update fails', async () => { + httpClient.patch.mockReturnValue( + throwError(() => { + new Error(''); + }), + ); + + const res = await service.update('', { + referralDescription: '', + submissionDate: 0, + }); + + expect(httpClient.patch).toHaveBeenCalledTimes(1); + expect(res).toBeUndefined(); + expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + + it('should call delete for delete', async () => { + httpClient.delete.mockReturnValue( + of({ + fileNumber: '1', + }), + ); + + await service.delete(''); + + expect(httpClient.delete).toHaveBeenCalledTimes(1); + }); + + it('should show an error toast message if delete fails', async () => { + httpClient.delete.mockReturnValue( + throwError(() => { + new Error(''); + }), + ); + + const res = await service.delete(''); + + expect(httpClient.delete).toHaveBeenCalledTimes(1); + expect(res).toBeUndefined(); + expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); + }); +}); diff --git a/alcs-frontend/src/app/services/planning-review/planning-review-decision/planning-review-decision.service.spec.ts b/alcs-frontend/src/app/services/planning-review/planning-review-decision/planning-review-decision.service.spec.ts index 7f2a333d63..60cb7c3f79 100644 --- a/alcs-frontend/src/app/services/planning-review/planning-review-decision/planning-review-decision.service.spec.ts +++ b/alcs-frontend/src/app/services/planning-review/planning-review-decision/planning-review-decision.service.spec.ts @@ -62,6 +62,34 @@ describe('PlanningReviewDecisionService', () => { expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); }); + it('should fetch and return codes', async () => { + httpClient.get.mockReturnValue( + of([ + { + fileNumber: '1', + }, + ]), + ); + + const res = await service.fetchCodes(); + + expect(res).toBeDefined(); + expect(httpClient.get).toHaveBeenCalledTimes(1); + }); + + it('should show a toast message if fetch codes fails', async () => { + httpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }), + ); + + const res = await service.fetchCodes(); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + it('should make an http patch and show a success toast when updating', async () => { httpClient.patch.mockReturnValue( of({ @@ -198,4 +226,31 @@ describe('PlanningReviewDecisionService', () => { expect(httpClient.get).toHaveBeenCalledTimes(1); }); + + it('should fetch and load a decision', async () => { + httpClient.get.mockReturnValue( + of([ + { + fileNumber: '1', + }, + ]), + ); + + await service.loadDecision('uuid'); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + }); + + it('should show a toast message if load decision fails', async () => { + httpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }), + ); + + const res = await service.loadDecision('uuid'); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); + }); }); diff --git a/alcs-frontend/src/app/services/planning-review/planning-review-document/planning-review-document.service.spec.ts b/alcs-frontend/src/app/services/planning-review/planning-review-document/planning-review-document.service.spec.ts index c79917d60c..810c34059b 100644 --- a/alcs-frontend/src/app/services/planning-review/planning-review-document/planning-review-document.service.spec.ts +++ b/alcs-frontend/src/app/services/planning-review/planning-review-document/planning-review-document.service.spec.ts @@ -35,7 +35,39 @@ describe('PlanningReviewDocumentService', () => { expect(service).toBeTruthy(); }); - it('should make a get call for list', async () => { + it('should make a get call for listAll', async () => { + httpClient.get.mockReturnValue( + of([ + { + uuid: '1', + }, + ]), + ); + + const res = await service.listAll('1'); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(res.length).toEqual(1); + expect(res[0].uuid).toEqual('1'); + }); + + it('should make a get call for fetchTypes', async () => { + httpClient.get.mockReturnValue( + of([ + { + code: '1', + }, + ]), + ); + + const res = await service.fetchTypes(); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(res.length).toEqual(1); + expect(res[0].code).toEqual('1'); + }); + + it('should make a get call for listByVisibility', async () => { httpClient.get.mockReturnValue( of([ { @@ -108,4 +140,21 @@ describe('PlanningReviewDocumentService', () => { expect(httpClient.post).toHaveBeenCalledTimes(1); }); + + it('should make a post call for update', async () => { + httpClient.post.mockReturnValue( + of({ + uuid: '1', + }), + ); + + const res = await service.update('uuid', { + fileName: '', + source: DOCUMENT_SOURCE.APPLICANT, + typeCode: DOCUMENT_TYPE.CERTIFICATE_OF_TITLE, + visibilityFlags: [], + }); + + expect(httpClient.post).toHaveBeenCalledTimes(1); + }); }); diff --git a/alcs-frontend/src/app/services/planning-review/planning-review-meeting/planning-review-meeting.service.spec.ts b/alcs-frontend/src/app/services/planning-review/planning-review-meeting/planning-review-meeting.service.spec.ts new file mode 100644 index 0000000000..c9ac1fd702 --- /dev/null +++ b/alcs-frontend/src/app/services/planning-review/planning-review-meeting/planning-review-meeting.service.spec.ts @@ -0,0 +1,153 @@ +import { HttpClient } from '@angular/common/http'; +import { TestBed } from '@angular/core/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { of, throwError } from 'rxjs'; +import { environment } from '../../../../environments/environment'; +import { ToastService } from '../../toast/toast.service'; +import { + CreatePlanningReviewMeetingDto, + PlanningReviewMeetingDto, + PlanningReviewMeetingTypeDto, + UpdatePlanningReviewMeetingDto, +} from './planning-review-meeting.dto'; +import { PlanningReviewMeetingService } from './planning-review-meeting.service'; + +describe('PlanningReviewMeetingService', () => { + let service: PlanningReviewMeetingService; + let httpClient: DeepMocked; + let mockToastService: DeepMocked; + const apiUrl = `${environment.apiUrl}/planning-review-meeting`; + + beforeEach(() => { + mockToastService = createMock(); + httpClient = createMock(); + + TestBed.configureTestingModule({ + providers: [ + PlanningReviewMeetingService, + { + provide: ToastService, + useValue: mockToastService, + }, + { + provide: HttpClient, + useValue: httpClient, + }, + ], + }); + service = TestBed.inject(PlanningReviewMeetingService); + }); + + it('should list meetings by planning review', async () => { + const planningReviewUuid = 'test-uuid'; + const mockMeetings = [{ uuid: 'meeting-uuid-1' }, { uuid: 'meeting-uuid-2' }] as PlanningReviewMeetingDto[]; + + httpClient.get.mockReturnValue(of(mockMeetings)); + + const res = await service.listByPlanningReview(planningReviewUuid); + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(res?.length).toEqual(2); + }); + + it('should create a new meeting', async () => { + const createDto = { + planningReviewUuid: 'test-uuid', + } as CreatePlanningReviewMeetingDto; + const mockMeeting = { + uuid: 'new-meeting-uuid', + } as PlanningReviewMeetingDto; + + httpClient.post.mockReturnValue(of(mockMeeting)); + + const res = await service.create(createDto); + + expect(httpClient.post).toHaveBeenCalledTimes(1); + expect(res).toEqual(mockMeeting); + }); + + it('should show a toast message if create fails', async () => { + httpClient.post.mockReturnValue( + throwError(() => { + new Error(''); + }), + ); + + const createDto = { + planningReviewUuid: 'test-uuid', + } as CreatePlanningReviewMeetingDto; + + await service.create(createDto); + + expect(httpClient.post).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + + it('should fetch meeting types', async () => { + const mockTypes = [{ code: 'type-code-1' }, { code: 'type-code-2' }] as PlanningReviewMeetingTypeDto[]; + httpClient.get.mockReturnValue(of(mockTypes)); + + const res = await service.fetchTypes(); + + expect(res).toEqual(mockTypes); + expect(httpClient.get).toHaveBeenCalledTimes(1); + }); + + it('should show a toast message if fetch meeting types fails', async () => { + httpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }), + ); + + await service.fetchTypes(); + + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + + it('should update a meeting', async () => { + const uuid = 'meeting-uuid'; + const updateDto: UpdatePlanningReviewMeetingDto = { + date: 5, + typeCode: 'Cats', + }; + const mockMeeting = { + uuid: 'meeting-uuid', + } as PlanningReviewMeetingDto; + httpClient.patch.mockReturnValue(of(mockMeeting)); + + const res = await service.update(uuid, updateDto); + + expect(httpClient.patch).toHaveBeenCalledTimes(1); + expect(res).toEqual(mockMeeting); + }); + + it('should show a toast message if update meeting fails', async () => { + httpClient.patch.mockReturnValue( + throwError(() => { + new Error(''); + }), + ); + + const updateDto: UpdatePlanningReviewMeetingDto = { + date: 5, + typeCode: 'Cats', + }; + + await service.update('meeting-uuid', updateDto); + + expect(httpClient.patch).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + + it('should delete a meeting', async () => { + const uuid = 'meeting-uuid'; + const mockResponse = { success: true }; + httpClient.delete.mockReturnValue(of(mockResponse)); + + const res = await service.delete(uuid); + + expect(httpClient.delete).toHaveBeenCalledTimes(1); + expect(res).toEqual(mockResponse); + }); +}); From 26da85056185fb81e87b46038395ad58ae3e2722 Mon Sep 17 00:00:00 2001 From: Tristan Slater <1631008+trslater@users.noreply.github.com> Date: Tue, 2 Apr 2024 16:02:44 -0700 Subject: [PATCH 079/153] Make L/FNG review staff report not required --- .../review-attachments/review-attachments.component.html | 1 - 1 file changed, 1 deletion(-) diff --git a/portal-frontend/src/app/features/applications/review-submission/review-attachments/review-attachments.component.html b/portal-frontend/src/app/features/applications/review-submission/review-attachments/review-attachments.component.html index a5f08ac19b..65a6b1af19 100644 --- a/portal-frontend/src/app/features/applications/review-submission/review-attachments/review-attachments.component.html +++ b/portal-frontend/src/app/features/applications/review-submission/review-attachments/review-attachments.component.html @@ -29,7 +29,6 @@

Attachments

(uploadFiles)="attachStaffReport($event)" (deleteFile)="deleteFile($event)" (openFile)="openFile($event)" - [isRequired]="true" [showErrors]="showErrors" [showVirusError]="showStaffReportVirusError" > From 1786a0085a06346b8cc5a242ab2cf352374ff0d1 Mon Sep 17 00:00:00 2001 From: Liam Stoddard Date: Tue, 2 Apr 2024 16:18:58 -0700 Subject: [PATCH 080/153] update changes, from gdoc comments --- .../planning_review_base_update.py | 15 +++++++++++++-- .../referrals/planning_review_card_init.py | 3 ++- .../referrals/planning_review_card_update.py | 12 +----------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/bin/migrate-oats-data/planning_review/planning_review_base_update.py b/bin/migrate-oats-data/planning_review/planning_review_base_update.py index 02a4b6e423..e0adfa9a93 100644 --- a/bin/migrate-oats-data/planning_review/planning_review_base_update.py +++ b/bin/migrate-oats-data/planning_review/planning_review_base_update.py @@ -3,6 +3,7 @@ setup_and_get_logger, add_timezone_and_keep_date_part, OatsToAlcsPlanningReviewType, + DEFAULT_ETL_USER_UUID, ) from db import inject_conn_pool from psycopg2.extras import RealDictCursor, execute_batch @@ -99,7 +100,8 @@ def _update_base_fields(conn, batch_size, cursor, rows): SET open = %(open_indicator)s, type_code = %(alcs_planning_review_code)s, legacy_id = %(legacy_number)s, - closed_by_uuid = %(closed_by_uuid)s + closed_by_uuid = %(closed_by_uuid)s, + closed_date = %(closed_date)s WHERE alcs.planning_review.file_number = %(planning_review_id)s::text """ @@ -114,6 +116,7 @@ def _prepare_oats_planning_review_data(row_data_list): "alcs_planning_review_code": _map_planning_code(row), "legacy_number": row["legacy_planning_review_nbr"], "closed_by_uuid": _map_closed_by(row), + "closed_date": _map_closed_date(row), } ) @@ -132,7 +135,15 @@ def _map_is_open(data): def _map_closed_by(data): if data.get("open_ind") == "N": - return data.get("author_uuid") + return DEFAULT_ETL_USER_UUID + else: + return None + + +def _map_closed_date(data): + if data.get("open_ind") == "N": + date_str = "0001-01-01 00:00:00.000 -0800" + return date_str else: return None diff --git a/bin/migrate-oats-data/planning_review/referrals/planning_review_card_init.py b/bin/migrate-oats-data/planning_review/referrals/planning_review_card_init.py index 388346ece7..8d7a1ae6a1 100644 --- a/bin/migrate-oats-data/planning_review/referrals/planning_review_card_init.py +++ b/bin/migrate-oats-data/planning_review/referrals/planning_review_card_init.py @@ -123,7 +123,8 @@ def _prepare_oats_planning_review_card_data(row_data_list): # planning_review_uuid is used as a temporary placeholder in order to create and match referrals 1:1 to cards "uuid": row["uuid"], "type_code": "PLAN", - "status_code": "PREL", + # Planning board only has single column of SUBM + "status_code": "SUBM", "archived": True, } ) diff --git a/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py b/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py index 3078ffae97..3e72f14ca3 100644 --- a/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py +++ b/bin/migrate-oats-data/planning_review/referrals/planning_review_card_update.py @@ -95,8 +95,7 @@ def _update_base_fields(conn, batch_size, cursor, rows): _rx_items_query = """ UPDATE alcs.card SET board_uuid = %(board_uuid)s, - status_code = %(status_code)s, - audit_updated_by = %(audit_updated_by)s + audit_updated_by = %(audit_updated_by)s, WHERE alcs.card.uuid = %(uuid)s """ @@ -109,16 +108,7 @@ def _prepare_oats_planning_review_data(row_data_list): "uuid": row["uuid"], "board_uuid": "e7b18852-4f8f-419e-83e3-60e706b4a494", "audit_updated_by": None, - "status_code": _map_card_status(row), } ) return mapped_data_list - - -def _map_card_status(row): - review_status = row.get("open") - if review_status is False: - return "COMP" - else: - return row.get("status_code") From 651142be50d2df5b70c7e65dae1a7223ba027c54 Mon Sep 17 00:00:00 2001 From: Liam Stoddard Date: Tue, 2 Apr 2024 16:30:46 -0700 Subject: [PATCH 081/153] MR feedback --- .../oats_documents_to_alcs_documents_planning_review.py | 2 +- .../planning-review-document.entity.ts | 4 +++- services/apps/alcs/src/document/document.entity.ts | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_planning_review.py b/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_planning_review.py index 9669ca8fac..fe232df1bd 100644 --- a/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_planning_review.py +++ b/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_planning_review.py @@ -16,7 +16,7 @@ This script connects to postgress version of OATS DB and transfers data from OATS documents table to ALCS documents table. NOTE: - Before performing document import you need to import SRWs from oats. + Before performing document import you need to import Planning Reviews from oats. """ diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.entity.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.entity.ts index 038844b189..1fd9cd8f91 100644 --- a/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.entity.ts +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.entity.ts @@ -61,6 +61,7 @@ export class PlanningReviewDocument extends BaseEntity { @Column({ type: 'text', nullable: true, + select: false, comment: 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.planning_review to alcs.planning_review_document.', }) @@ -69,13 +70,14 @@ export class PlanningReviewDocument extends BaseEntity { @Column({ type: 'text', nullable: true, + select: false, comment: 'This column is NOT related to any functionality in ALCS. It is only used for ETL and backtracking of imported data from OATS. It links oats.documents/alcs.documents to alcs.planning_review_document.', }) oatsDocumentId?: string | null; @Column({ nullable: true }) - auditCreatedBy: string; + auditCreatedBy?: string | null; @OneToOne(() => Document) @JoinColumn() diff --git a/services/apps/alcs/src/document/document.entity.ts b/services/apps/alcs/src/document/document.entity.ts index b6159d99e7..308dfc136a 100644 --- a/services/apps/alcs/src/document/document.entity.ts +++ b/services/apps/alcs/src/document/document.entity.ts @@ -56,6 +56,7 @@ export class Document extends Base { @Column({ nullable: true, + select: false, type: 'text', comment: 'used only for oats etl process', }) From c7c491683d82cd214ca3b4d7500c628dbd92c51f Mon Sep 17 00:00:00 2001 From: Liam Stoddard Date: Tue, 2 Apr 2024 16:32:23 -0700 Subject: [PATCH 082/153] remove created_by filter on clean --- .../oats_documents_to_alcs_documents_planning_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_planning_review.py b/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_planning_review.py index fe232df1bd..5b7a5edb8b 100644 --- a/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_planning_review.py +++ b/bin/migrate-oats-data/documents/post_launch/oats_documents_to_alcs_documents_planning_review.py @@ -181,7 +181,7 @@ def document_pr_clean(conn=None): logger.info("Start planning review related documents cleaning") with conn.cursor() as cursor: cursor.execute( - f"DELETE FROM alcs.document WHERE audit_created_by = '{OATS_ETL_USER}' AND oats_planning_review_id IS NOT NULL AND audit_created_at > '2024-02-08';" + f"DELETE FROM alcs.document WHERE audit_created_by = '{OATS_ETL_USER}' AND oats_planning_review_id IS NOT NULL;" ) conn.commit() logger.info(f"Deleted items count = {cursor.rowcount}") From f926f334ace574383aafe6de1d60cf666707cd14 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Tue, 2 Apr 2024 16:46:29 -0700 Subject: [PATCH 083/153] Inquiry Fixes * Set title on inquiry page * Add code for updating inquiry region and local government * Add error message to create dialog * Add spacing to create parcel table --- .../create-inquiry-dialog.component.html | 129 +++++++++--------- .../create-inquiry-dialog.component.scss | 25 +++- .../inquiry/detail/details.component.html | 26 +++- .../inquiry/detail/details.component.ts | 24 ++-- .../inquiry/header/header.component.ts | 10 +- .../app/features/inquiry/inquiry.component.ts | 16 ++- .../inquiry/parcel/parcels.component.html | 55 ++++---- .../src/app/services/inquiry/inquiry.dto.ts | 2 + .../apps/alcs/src/alcs/inquiry/inquiry.dto.ts | 8 ++ .../alcs/src/alcs/inquiry/inquiry.module.ts | 6 +- .../src/alcs/inquiry/inquiry.service.spec.ts | 14 ++ .../alcs/src/alcs/inquiry/inquiry.service.ts | 44 ++++-- 12 files changed, 230 insertions(+), 129 deletions(-) diff --git a/alcs-frontend/src/app/features/board/dialogs/inquiry/create/create-inquiry-dialog.component.html b/alcs-frontend/src/app/features/board/dialogs/inquiry/create/create-inquiry-dialog.component.html index 91a92f3b09..55dcf280b2 100644 --- a/alcs-frontend/src/app/features/board/dialogs/inquiry/create/create-inquiry-dialog.component.html +++ b/alcs-frontend/src/app/features/board/dialogs/inquiry/create/create-inquiry-dialog.component.html @@ -2,13 +2,13 @@

Create Inquiry

-
-

Type

+ +

Type

Select an inquiry type to learn more
- + {{ type.label }} -
+
{{ selectedType.label }}
{{ selectedType.description }} @@ -16,8 +16,8 @@

Type

- - +
@@ -25,157 +25,162 @@

Type

-
+ -

Details

+

Details

- + Submitted to ALC - +
- +
{{ item.name }}
- + Summary - +
-

Inquirer

+

Inquirer

First Name - + Last Name - + - + Organization - + Phone - + Email - +
-

Parcels

+

Parcels

- - - +
+ + - - + - - + - - + - - + - - + - +
# + # {{ i + 1 }} Civic Address* + Civic Address* - + PID + PID - + + + warning + PID must be 9 digits including leading zeroes + PIN + PIN - + Action - Action +
No Parcels
- +
- - +
diff --git a/alcs-frontend/src/app/features/board/dialogs/inquiry/create/create-inquiry-dialog.component.scss b/alcs-frontend/src/app/features/board/dialogs/inquiry/create/create-inquiry-dialog.component.scss index c23f4c5928..0d9ac99377 100644 --- a/alcs-frontend/src/app/features/board/dialogs/inquiry/create/create-inquiry-dialog.component.scss +++ b/alcs-frontend/src/app/features/board/dialogs/inquiry/create/create-inquiry-dialog.component.scss @@ -39,7 +39,30 @@ margin: 12px 0; } -h2 { +h2, h4 { color: colors.$black !important; margin-bottom: 18px !important; } + +.mat-mdc-table { + border-collapse: separate !important; + border-spacing: 0 8px !important; + + .mat-mdc-cell { + text-wrap: nowrap; + text-overflow: ellipsis; + overflow: hidden; + max-width: calc(100vw - 840px); + } +} + +.error { + display: flex; + color: colors.$error-color; + font-weight: bold; + text-wrap: wrap; + + .mat-icon { + min-width: 24px; + } +} diff --git a/alcs-frontend/src/app/features/inquiry/detail/details.component.html b/alcs-frontend/src/app/features/inquiry/detail/details.component.html index 58b37d2ec7..9e4d05e71b 100644 --- a/alcs-frontend/src/app/features/inquiry/detail/details.component.html +++ b/alcs-frontend/src/app/features/inquiry/detail/details.component.html @@ -23,12 +23,20 @@
L/FNG Information
Local/First Nation Government
- +
Region
- +
@@ -37,30 +45,34 @@
Inquirer
First Name
- +
Last Name
- +
Organization
Phone
- +
Email
- +
diff --git a/alcs-frontend/src/app/features/inquiry/detail/details.component.ts b/alcs-frontend/src/app/features/inquiry/detail/details.component.ts index 412b171396..ea10d66c4b 100644 --- a/alcs-frontend/src/app/features/inquiry/detail/details.component.ts +++ b/alcs-frontend/src/app/features/inquiry/detail/details.component.ts @@ -59,16 +59,6 @@ export class DetailsComponent implements OnInit, OnDestroy { } } - private async loadTypes() { - const types = await this.inquiryService.fetchTypes(); - if (types) { - this.types = types.map((type) => ({ - label: type.label, - value: type.code, - })); - } - } - async onSaveSubmittedToALC($event: number) { if (this.inquiry) { await this.inquiryDetailService.update(this.inquiry.fileNumber, { @@ -77,11 +67,21 @@ export class DetailsComponent implements OnInit, OnDestroy { } } - async onSaveTextField($event: string | null, inquirerFirstName: keyof UpdateInquiryDto) { + async onSaveTextField($event: string | null | string[], key: keyof UpdateInquiryDto) { if (this.inquiry) { await this.inquiryDetailService.update(this.inquiry.fileNumber, { - [inquirerFirstName]: $event, + [key]: $event, }); } } + + private async loadTypes() { + const types = await this.inquiryService.fetchTypes(); + if (types) { + this.types = types.map((type) => ({ + label: type.label, + value: type.code, + })); + } + } } diff --git a/alcs-frontend/src/app/features/inquiry/header/header.component.ts b/alcs-frontend/src/app/features/inquiry/header/header.component.ts index 8bc2e4426d..836bd6ba8b 100644 --- a/alcs-frontend/src/app/features/inquiry/header/header.component.ts +++ b/alcs-frontend/src/app/features/inquiry/header/header.component.ts @@ -29,10 +29,12 @@ export class HeaderComponent implements OnChanges { async setupLinkedCards() { if (this.inquiry.card) { - this.linkedCards.push({ - ...this.inquiry.card, - displayName: `Inquiry`, - }); + this.linkedCards = [ + { + ...this.inquiry.card, + displayName: `Inquiry`, + }, + ]; } } diff --git a/alcs-frontend/src/app/features/inquiry/inquiry.component.ts b/alcs-frontend/src/app/features/inquiry/inquiry.component.ts index 3cb9ca673c..bbc3ed34db 100644 --- a/alcs-frontend/src/app/features/inquiry/inquiry.component.ts +++ b/alcs-frontend/src/app/features/inquiry/inquiry.component.ts @@ -1,6 +1,8 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Title } from '@angular/platform-browser'; import { ActivatedRoute } from '@angular/router'; import { Subject, takeUntil } from 'rxjs'; +import { environment } from '../../../environments/environment'; import { InquiryDetailService } from '../../services/inquiry/inquiry-detail.service'; import { InquiryDto } from '../../services/inquiry/inquiry.dto'; import { DetailsComponent } from './detail/details.component'; @@ -50,6 +52,7 @@ export class InquiryComponent implements OnInit, OnDestroy { constructor( private inquiryDetailService: InquiryDetailService, private route: ActivatedRoute, + private titleService: Title, ) {} ngOnInit(): void { @@ -61,17 +64,20 @@ export class InquiryComponent implements OnInit, OnDestroy { this.inquiryDetailService.$inquiry.pipe(takeUntil(this.$destroy)).subscribe((inquiry) => { this.inquiry = inquiry; + this.titleService.setTitle( + `${environment.siteName} | ${inquiry?.fileNumber} (${inquiry?.inquirerLastName ?? 'Unknown'})`, + ); }); } + ngOnDestroy(): void { + this.$destroy.next(); + this.$destroy.complete(); + } + private async loadReview() { if (this.fileNumber) { await this.inquiryDetailService.loadInquiry(this.fileNumber); } } - - ngOnDestroy(): void { - this.$destroy.next(); - this.$destroy.complete(); - } } diff --git a/alcs-frontend/src/app/features/inquiry/parcel/parcels.component.html b/alcs-frontend/src/app/features/inquiry/parcel/parcels.component.html index 0b67e196d3..05898b9a8f 100644 --- a/alcs-frontend/src/app/features/inquiry/parcel/parcels.component.html +++ b/alcs-frontend/src/app/features/inquiry/parcel/parcels.component.html @@ -1,76 +1,81 @@

Parcels

- +
- - - +
+ + - - + - - + - - + - - + - - + - +
# + # {{ i + 1 }} Civic Address* + Civic Address* {{ row.civicAddress }} - + PID + PID {{ row.pid | mask: '000-000-000' }} - + PIN + PIN {{ row.pin }} - + Action - Action + -
No Parcels
- - + +
diff --git a/alcs-frontend/src/app/services/inquiry/inquiry.dto.ts b/alcs-frontend/src/app/services/inquiry/inquiry.dto.ts index 4a34a6dd56..8d16a679ce 100644 --- a/alcs-frontend/src/app/services/inquiry/inquiry.dto.ts +++ b/alcs-frontend/src/app/services/inquiry/inquiry.dto.ts @@ -57,4 +57,6 @@ export interface UpdateInquiryDto { inquirerPhone?: string; inquirerEmail?: string; parcels?: InquiryParcelCreateDto[]; + regionCode?: string; + localGovernmentUuid?: string; } diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.dto.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.dto.ts index 4ea6e0b526..c3baf375f9 100644 --- a/services/apps/alcs/src/alcs/inquiry/inquiry.dto.ts +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.dto.ts @@ -181,6 +181,14 @@ export class UpdateInquiryDto { @IsOptional() inquirerEmail?: string; + @IsString() + @IsOptional() + regionCode?: string; + + @IsString() + @IsOptional() + localGovernmentUuid?: string; + @IsOptional() @Type(() => InquiryParcelUpdateDto) parcels?: InquiryParcelUpdateDto[]; diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.module.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.module.ts index 976289586c..c4f3e6bc49 100644 --- a/services/apps/alcs/src/alcs/inquiry/inquiry.module.ts +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.module.ts @@ -1,10 +1,13 @@ import { forwardRef, Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { InquiryProfile } from '../../common/automapper/inquiry.automapper.profile'; +import { DocumentCode } from '../../document/document-code.entity'; import { DocumentModule } from '../../document/document.module'; 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 { LocalGovernmentModule } from '../local-government/local-government.module'; import { InquiryDocumentController } from './inquiry-document/inquiry-document.controller'; import { InquiryDocument } from './inquiry-document/inquiry-document.entity'; import { InquiryDocumentService } from './inquiry-document/inquiry-document.service'; @@ -13,7 +16,6 @@ import { InquiryType } from './inquiry-type.entity'; import { InquiryController } from './inquiry.controller'; import { Inquiry } from './inquiry.entity'; import { InquiryService } from './inquiry.service'; -import { DocumentCode } from '../../document/document-code.entity'; @Module({ imports: [ @@ -25,9 +27,11 @@ import { DocumentCode } from '../../document/document-code.entity'; DocumentCode, ]), CardModule, + LocalGovernmentModule, forwardRef(() => BoardModule), FileNumberModule, DocumentModule, + CodeModule, ], providers: [InquiryService, InquiryDocumentService, InquiryProfile], controllers: [InquiryController, InquiryDocumentController], diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.service.spec.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.service.spec.ts index 16e0483fb1..47388379be 100644 --- a/services/apps/alcs/src/alcs/inquiry/inquiry.service.spec.ts +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.service.spec.ts @@ -10,6 +10,8 @@ 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 { CodeService } from '../code/code.service'; +import { LocalGovernmentService } from '../local-government/local-government.service'; import { InquiryParcel } from './inquiry-parcel/inquiry-parcel.entity'; import { InquiryType } from './inquiry-type.entity'; import { UpdateInquiryDto } from './inquiry.dto'; @@ -22,12 +24,16 @@ describe('InquiryService', () => { let mockRepository: DeepMocked>; let mockTypeRepository: DeepMocked>; let mockFileNumberService: DeepMocked; + let mockGovernmentService: DeepMocked; + let mockCodeService: DeepMocked; beforeEach(async () => { mockCardService = createMock(); mockRepository = createMock(); mockTypeRepository = createMock(); mockFileNumberService = createMock(); + mockGovernmentService = createMock(); + mockCodeService = createMock(); const module: TestingModule = await Test.createTestingModule({ imports: [ @@ -54,6 +60,14 @@ describe('InquiryService', () => { provide: FileNumberService, useValue: mockFileNumberService, }, + { + provide: LocalGovernmentService, + useValue: mockGovernmentService, + }, + { + provide: CodeService, + useValue: mockCodeService, + }, ], }).compile(); diff --git a/services/apps/alcs/src/alcs/inquiry/inquiry.service.ts b/services/apps/alcs/src/alcs/inquiry/inquiry.service.ts index 395d5e35f0..962452a576 100644 --- a/services/apps/alcs/src/alcs/inquiry/inquiry.service.ts +++ b/services/apps/alcs/src/alcs/inquiry/inquiry.service.ts @@ -18,6 +18,8 @@ import { filterUndefined } from '../../utils/undefined'; import { Board } from '../board/board.entity'; import { CARD_TYPE } from '../card/card-type/card-type.entity'; import { CardService } from '../card/card.service'; +import { CodeService } from '../code/code.service'; +import { LocalGovernmentService } from '../local-government/local-government.service'; import { InquiryParcel } from './inquiry-parcel/inquiry-parcel.entity'; import { InquiryType } from './inquiry-type.entity'; import { @@ -54,6 +56,8 @@ export class InquiryService { private typeRepository: Repository, @InjectMapper() private mapper: Mapper, private fileNumberService: FileNumberService, + private localGovernmentService: LocalGovernmentService, + private codeService: CodeService, ) {} async create(createDto: CreateInquiryServiceDto, board?: Board) { @@ -152,18 +156,6 @@ export class InquiryService { }); } - private get(uuid: string) { - return this.repository.findOne({ - where: { - uuid, - }, - relations: { - ...this.DEFAULT_RELATIONS, - card: { ...this.CARD_RELATIONS, board: false }, - }, - }); - } - async getByBoard(boardUuid: string) { return this.repository.find({ where: { card: { boardUuid } }, @@ -214,6 +206,22 @@ export class InquiryService { inquiry.type = newType; } + if (updateDto.localGovernmentUuid) { + const newGovernment = await this.localGovernmentService.getByUuid( + updateDto.localGovernmentUuid, + ); + inquiry.localGovernment = newGovernment; + inquiry.localGovernmentUuid = newGovernment.uuid; + } + + if (updateDto.regionCode) { + const newRegion = await this.codeService.fetchRegion( + updateDto.regionCode, + ); + inquiry.regionCode = newRegion.code; + inquiry.region = newRegion; + } + inquiry.dateSubmittedToAlc = filterUndefined( formatIncomingDate(updateDto.dateSubmittedToAlc), inquiry.dateSubmittedToAlc, @@ -315,4 +323,16 @@ export class InquiryService { }); return inquiry.uuid; } + + private get(uuid: string) { + return this.repository.findOne({ + where: { + uuid, + }, + relations: { + ...this.DEFAULT_RELATIONS, + card: { ...this.CARD_RELATIONS, board: false }, + }, + }); + } } From 5eb492795da154d78ec96429f2657ff3de3baee6 Mon Sep 17 00:00:00 2001 From: mhuseinov <61513701+mhuseinov@users.noreply.github.com> Date: Wed, 3 Apr 2024 09:36:25 -0700 Subject: [PATCH 084/153] table comments (#1563) added table comments to entities --- .../src/alcs/admin/holiday/holiday.entity.ts | 4 +- .../application-boundary-amendment.entity.ts | 5 +- .../application-ceo-criterion.entity.ts | 5 +- ...plication-decision-component-lot.entity.ts | 4 +- ...ision-condition-to-component-lot.entity.ts | 5 +- ...mponent-to-condition-plan-number.entity.ts | 5 +- ...lication-decision-condition-code.entity.ts | 4 +- .../application-decision-condition.entity.ts | 3 +- .../application-decision-document.entity.ts | 5 +- .../application-decision-maker.entity.ts | 2 +- ...pplication-decision-outcome-type.entity.ts | 5 +- .../application-decision-outcome.entity.ts | 4 +- ...lication-decision-component-type.entity.ts | 4 +- .../application-decision-component.entity.ts | 2 +- .../application-decision.entity.ts | 11 +- ...cation-modification-outcome-type.entity.ts | 4 +- .../application-modification.entity.ts | 4 +- .../application-reconsideration.entity.ts | 5 +- ...ion-reconsideration-outcome-type.entity.ts | 5 +- ...application-reconsideration-type.entity.ts | 2 +- .../application-decision-meeting.entity.ts | 2 +- .../application-document.entity.ts | 5 +- .../application-meeting.entity.ts | 2 +- .../application/application-paused.entity.ts | 4 +- .../submission-status-type.entity.ts | 4 +- .../submission-status.entity.ts | 2 +- .../alcs/application/application.entity.ts | 7 +- .../src/alcs/board/board-status.entity.ts | 4 +- .../apps/alcs/src/alcs/board/board.entity.ts | 4 +- .../card/card-history/card-history.entity.ts | 7 +- .../card/card-status/card-status.entity.ts | 4 +- .../card-subtask-type.entity.ts | 2 +- .../card/card-subtask/card-subtask.entity.ts | 6 +- .../alcs/card/card-type/card-type.entity.ts | 4 +- .../apps/alcs/src/alcs/card/card.entity.ts | 8 +- .../application-meeting-type.entity.ts | 4 +- .../application-region.entity.ts | 4 +- .../application-type.entity.ts | 4 +- .../alcs/src/alcs/comment/comment.entity.ts | 6 +- .../comment/mention/comment-mention.entity.ts | 2 +- .../local-government.entity.ts | 5 +- .../alcs/src/alcs/message/message.entity.ts | 2 +- ...f-intent-decision-component-type.entity.ts | 4 +- ...ice-of-intent-decision-component.entity.ts | 4 +- ...f-intent-decision-condition-code.entity.ts | 4 +- ...ice-of-intent-decision-condition.entity.ts | 4 +- ...tice-of-intent-decision-document.entity.ts | 4 +- ...otice-of-intent-decision-outcome.entity.ts | 4 +- .../notice-of-intent-decision.entity.ts | 4 +- ...intent-modification-outcome-type.entity.ts | 2 +- .../notice-of-intent-modification.entity.ts | 4 +- .../notice-of-intent-document.entity.ts | 5 +- .../notice-of-intent-meeting-type.entity.ts | 4 +- .../notice-of-intent-meeting.entity.ts | 4 +- .../notice-of-intent-status-type.entity.ts | 4 +- .../notice-of-intent-status.entity.ts | 4 +- .../notice-of-intent-subtype.entity.ts | 4 +- .../notice-of-intent-type.entity.ts | 2 +- .../notice-of-intent.entity.ts | 7 +- .../notification-document.entity.ts | 4 +- .../notification-status-type.entity.ts | 4 +- .../notification-status.entity.ts | 2 +- .../notification-type.entity.ts | 2 +- .../alcs/notification/notification.entity.ts | 4 +- ...lanning-review-decision-document.entity.ts | 5 +- ...planning-review-decision-outcome.entity.ts | 2 +- .../planning-review-decision.entity.ts | 5 +- .../planning-review-type.entity.ts | 2 +- .../staff-journal/staff-journal.entity.ts | 5 +- .../common/entities/configuration.entity.ts | 2 +- .../parcel-ownership-type.entity.ts | 4 +- .../common/owner-type/owner-type.entity.ts | 4 +- .../src/common/tracking/file-viewed.entity.ts | 4 +- .../alcs/src/document/document-code.entity.ts | 2 +- .../apps/alcs/src/document/document.entity.ts | 4 +- .../src/healthcheck/healthcheck.entity.ts | 5 +- .../application-submission-review.entity.ts | 4 +- .../application-owner.entity.ts | 7 +- .../application-parcel.entity.ts | 2 +- .../application-submission.entity.ts | 6 +- .../covenant-transferee.entity.ts | 4 +- .../naru-subtype/naru-subtype.entity.ts | 5 +- .../notice-of-intent-owner.entity.ts | 4 +- .../notice-of-intent-parcel.entity.ts | 2 +- .../notice-of-intent-submission.entity.ts | 2 +- .../notification-parcel.entity.ts | 2 +- .../notification-submission.entity.ts | 4 +- .../notification-transferee.entity.ts | 4 +- .../src/portal/parcel/parcel-lookup.entity.ts | 4 +- .../providers/email/email-status.entity.ts | 4 +- .../1712083862347-table_comments.ts | 455 ++++++++++++++++++ .../1712088247956-table_comments_2.ts | 67 +++ services/apps/alcs/src/user/user.entity.ts | 4 +- 93 files changed, 782 insertions(+), 106 deletions(-) create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1712083862347-table_comments.ts create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1712088247956-table_comments_2.ts diff --git a/services/apps/alcs/src/alcs/admin/holiday/holiday.entity.ts b/services/apps/alcs/src/alcs/admin/holiday/holiday.entity.ts index 26da8863b2..5dd375f2a4 100644 --- a/services/apps/alcs/src/alcs/admin/holiday/holiday.entity.ts +++ b/services/apps/alcs/src/alcs/admin/holiday/holiday.entity.ts @@ -1,6 +1,8 @@ import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; -@Entity() +@Entity({ + comment: 'Holidays used by the application active day tracking function', +}) export class HolidayEntity extends BaseEntity { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/application-decision/application-boundary-amendment/application-boundary-amendment.entity.ts b/services/apps/alcs/src/alcs/application-decision/application-boundary-amendment/application-boundary-amendment.entity.ts index 56fa3b4a12..fa7e408155 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-boundary-amendment/application-boundary-amendment.entity.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-boundary-amendment/application-boundary-amendment.entity.ts @@ -4,7 +4,10 @@ import { Base } from '../../../common/entities/base.entity'; import { ColumnNumericTransformer } from '../../../utils/column-numeric-transform'; import { ApplicationDecisionComponent } from '../application-decision-v2/application-decision/component/application-decision-component.entity'; -@Entity() +@Entity({ + comment: + 'Used by ALC GIS Staff to track Inclusion / Exclusion decisions and their ALR boundary impact over time', +}) export class ApplicationBoundaryAmendment extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/application-decision/application-ceo-criterion/application-ceo-criterion.entity.ts b/services/apps/alcs/src/alcs/application-decision/application-ceo-criterion/application-ceo-criterion.entity.ts index 4f8c246223..f22b5ac291 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-ceo-criterion/application-ceo-criterion.entity.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-ceo-criterion/application-ceo-criterion.entity.ts @@ -2,7 +2,10 @@ import { AutoMap } from 'automapper-classes'; import { Column, Entity } from 'typeorm'; import { BaseCodeEntity } from '../../../common/entities/base.code.entity'; -@Entity() +@Entity({ + comment: + 'Code table for criteria under which the CEO can make a decision on an application', +}) export class ApplicationCeoCriterionCode extends BaseCodeEntity { @AutoMap() @Column({ unique: true }) diff --git a/services/apps/alcs/src/alcs/application-decision/application-component-lot/application-decision-component-lot.entity.ts b/services/apps/alcs/src/alcs/application-decision/application-component-lot/application-decision-component-lot.entity.ts index 948d200611..adc98c2251 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-component-lot/application-decision-component-lot.entity.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-component-lot/application-decision-component-lot.entity.ts @@ -14,7 +14,9 @@ export class ProposedLot { } //Contains the approved subdivision lots -@Entity() +@Entity({ + comment: 'Approved lots on the subdivision decision component', +}) export class ApplicationDecisionComponentLot extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/application-decision/application-condition-to-component-lot/application-decision-condition-to-component-lot.entity.ts b/services/apps/alcs/src/alcs/application-decision/application-condition-to-component-lot/application-decision-condition-to-component-lot.entity.ts index efa362131f..b1bf811db2 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-condition-to-component-lot/application-decision-condition-to-component-lot.entity.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-condition-to-component-lot/application-decision-condition-to-component-lot.entity.ts @@ -4,7 +4,10 @@ import { Base } from '../../../common/entities/base.entity'; import { ApplicationDecisionComponentLot } from '../application-component-lot/application-decision-component-lot.entity'; import { ApplicationDecisionCondition } from '../application-decision-condition/application-decision-condition.entity'; -@Entity() +@Entity({ + comment: + 'Join table to link approved subdivision lots between condition and components and provide plan numbers associated with survey plan per lot', +}) export class ApplicationDecisionConditionToComponentLot extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-component-to-condition/application-decision-component-to-condition-plan-number.entity.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-component-to-condition/application-decision-component-to-condition-plan-number.entity.ts index 7eeb6416ce..20fe0c5cf4 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-component-to-condition/application-decision-component-to-condition-plan-number.entity.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-decision-component-to-condition/application-decision-component-to-condition-plan-number.entity.ts @@ -10,7 +10,10 @@ import { import { ApplicationDecisionCondition } from '../application-decision-condition/application-decision-condition.entity'; import { ApplicationDecisionComponent } from '../application-decision-v2/application-decision/component/application-decision-component.entity'; -@Entity() +@Entity({ + comment: + 'Survey plan numbers associated with survey plan conditions on decision components', +}) export class ApplicationDecisionConditionComponentPlanNumber extends BaseEntity { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition-code.entity.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition-code.entity.ts index 2e8b524e68..ff73b3903f 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition-code.entity.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition-code.entity.ts @@ -1,5 +1,7 @@ import { Entity } from 'typeorm'; import { BaseCodeEntity } from '../../../common/entities/base.code.entity'; -@Entity() +@Entity({ + comment: 'Code table for the possible application decision condition types', +}) export class ApplicationDecisionConditionType extends BaseCodeEntity {} diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.entity.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.entity.ts index cbf673462f..0bf70828e2 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.entity.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.entity.ts @@ -9,13 +9,12 @@ import { } from 'typeorm'; import { Base } from '../../../common/entities/base.entity'; import { ColumnNumericTransformer } from '../../../utils/column-numeric-transform'; -import { ApplicationDecisionConditionToComponentLot } from '../application-condition-to-component-lot/application-decision-condition-to-component-lot.entity'; import { ApplicationDecisionConditionComponentPlanNumber } from '../application-decision-component-to-condition/application-decision-component-to-condition-plan-number.entity'; import { ApplicationDecisionComponent } from '../application-decision-v2/application-decision/component/application-decision-component.entity'; import { ApplicationDecision } from '../application-decision.entity'; import { ApplicationDecisionConditionType } from './application-decision-condition-code.entity'; -@Entity() +@Entity({ comment: 'Fields present on the application decision conditions' }) export class ApplicationDecisionCondition extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-document/application-decision-document.entity.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-document/application-decision-document.entity.ts index 6737266c6e..062f46c023 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-document/application-decision-document.entity.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-decision-document/application-decision-document.entity.ts @@ -11,7 +11,10 @@ import { Auditable } from '../../../common/entities/audit.entity'; import { Document } from '../../../document/document.entity'; import { ApplicationDecision } from '../application-decision.entity'; -@Entity() +@Entity({ + comment: + "Links application decision document with the decision it's saved to", +}) export class ApplicationDecisionDocument extends Auditable { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-maker/application-decision-maker.entity.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-maker/application-decision-maker.entity.ts index 59fe23f059..39ba143ad8 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-maker/application-decision-maker.entity.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-decision-maker/application-decision-maker.entity.ts @@ -1,7 +1,7 @@ import { Column, Entity } from 'typeorm'; import { BaseCodeEntity } from '../../../common/entities/base.code.entity'; -@Entity() +@Entity({ comment: 'Code table for the possible application decision makers' }) export class ApplicationDecisionMakerCode extends BaseCodeEntity { @Column({ default: true }) isActive: boolean; diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-outcome-type/application-decision-outcome-type.entity.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-outcome-type/application-decision-outcome-type.entity.ts index be7f81013a..ed62bc3044 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-outcome-type/application-decision-outcome-type.entity.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-decision-outcome-type/application-decision-outcome-type.entity.ts @@ -1,5 +1,8 @@ import { Entity } from 'typeorm'; import { BaseCodeEntity } from '../../../common/entities/base.code.entity'; -@Entity() +@Entity({ + comment: + "Code table for the possible outcomes of the chair's application decision review", +}) export class ApplicationDecisionChairReviewOutcomeType extends BaseCodeEntity {} diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-outcome.entity.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-outcome.entity.ts index 3c70baa534..4a416a0a3a 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-outcome.entity.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-decision-outcome.entity.ts @@ -1,7 +1,9 @@ import { Column, Entity } from 'typeorm'; import { BaseCodeEntity } from '../../common/entities/base.code.entity'; -@Entity() +@Entity({ + comment: 'Code table for the possible application decision outcomes', +}) export class ApplicationDecisionOutcomeCode extends BaseCodeEntity { @Column({ default: true, diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-v2/application-decision/component/application-decision-component-type.entity.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-v2/application-decision/component/application-decision-component-type.entity.ts index 4723727399..d3b006b425 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-v2/application-decision/component/application-decision-component-type.entity.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-decision-v2/application-decision/component/application-decision-component-type.entity.ts @@ -1,7 +1,9 @@ import { Entity } from 'typeorm'; import { BaseCodeEntity } from '../../../../../common/entities/base.code.entity'; -@Entity() +@Entity({ + comment: 'Code table for the possible application decision component types', +}) export class ApplicationDecisionComponentType extends BaseCodeEntity { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-v2/application-decision/component/application-decision-component.entity.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-v2/application-decision/component/application-decision-component.entity.ts index a152e39009..bedb03e031 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-v2/application-decision/component/application-decision-component.entity.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-decision-v2/application-decision/component/application-decision-component.entity.ts @@ -9,7 +9,7 @@ import { ApplicationDecisionCondition } from '../../../application-decision-cond import { ApplicationDecision } from '../../../application-decision.entity'; import { ApplicationDecisionComponentType } from './application-decision-component-type.entity'; -@Entity() +@Entity({ comment: 'Fields present on the application decision components' }) export class ApplicationDecisionComponent extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision.entity.ts b/services/apps/alcs/src/alcs/application-decision/application-decision.entity.ts index de28aac917..27900bcb20 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision.entity.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-decision.entity.ts @@ -12,17 +12,20 @@ import { } from 'typeorm'; import { Base } from '../../common/entities/base.entity'; import { Application } from '../application/application.entity'; -import { ApplicationDecisionOutcomeCode } from './application-decision-outcome.entity'; -import { ApplicationModification } from './application-modification/application-modification.entity'; -import { ApplicationReconsideration } from './application-reconsideration/application-reconsideration.entity'; import { ApplicationCeoCriterionCode } from './application-ceo-criterion/application-ceo-criterion.entity'; import { ApplicationDecisionCondition } from './application-decision-condition/application-decision-condition.entity'; import { ApplicationDecisionDocument } from './application-decision-document/application-decision-document.entity'; import { ApplicationDecisionMakerCode } from './application-decision-maker/application-decision-maker.entity'; import { ApplicationDecisionChairReviewOutcomeType } from './application-decision-outcome-type/application-decision-outcome-type.entity'; +import { ApplicationDecisionOutcomeCode } from './application-decision-outcome.entity'; import { ApplicationDecisionComponent } from './application-decision-v2/application-decision/component/application-decision-component.entity'; +import { ApplicationModification } from './application-modification/application-modification.entity'; +import { ApplicationReconsideration } from './application-reconsideration/application-reconsideration.entity'; -@Entity() +@Entity({ + comment: + 'Decisions saved to applications, incl. those linked to the recon/modification request', +}) @Index(['resolutionNumber', 'resolutionYear'], { unique: true, where: '"audit_deleted_date_at" is null and "resolution_number" is not null', diff --git a/services/apps/alcs/src/alcs/application-decision/application-modification/application-modification-outcome-type/application-modification-outcome-type.entity.ts b/services/apps/alcs/src/alcs/application-decision/application-modification/application-modification-outcome-type/application-modification-outcome-type.entity.ts index d24be6e88d..766add0220 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-modification/application-modification-outcome-type/application-modification-outcome-type.entity.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-modification/application-modification-outcome-type/application-modification-outcome-type.entity.ts @@ -1,5 +1,7 @@ import { Entity } from 'typeorm'; import { BaseCodeEntity } from '../../../../common/entities/base.code.entity'; -@Entity() +@Entity({ + comment: 'Code table for possible application modification review outcomes', +}) export class ApplicationModificationOutcomeType extends BaseCodeEntity {} diff --git a/services/apps/alcs/src/alcs/application-decision/application-modification/application-modification.entity.ts b/services/apps/alcs/src/alcs/application-decision/application-modification/application-modification.entity.ts index b324a7972a..cf31a13399 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-modification/application-modification.entity.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-modification/application-modification.entity.ts @@ -15,7 +15,9 @@ import { Card } from '../../card/card.entity'; import { ApplicationDecision } from '../application-decision.entity'; import { ApplicationModificationOutcomeType } from './application-modification-outcome-type/application-modification-outcome-type.entity'; -@Entity() +@Entity({ + comment: 'Application modification requests linked to card and application', +}) export class ApplicationModification extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/application-decision/application-reconsideration/application-reconsideration.entity.ts b/services/apps/alcs/src/alcs/application-decision/application-reconsideration/application-reconsideration.entity.ts index c389dc2185..8cde54d273 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-reconsideration/application-reconsideration.entity.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-reconsideration/application-reconsideration.entity.ts @@ -17,7 +17,10 @@ import { ApplicationDecision } from '../application-decision.entity'; import { ApplicationReconsiderationOutcomeType } from './reconsideration-outcome-type/application-reconsideration-outcome-type.entity'; import { ApplicationReconsiderationType } from './reconsideration-type/application-reconsideration-type.entity'; -@Entity() +@Entity({ + comment: + 'Application reconsideration requests linked to card and application', +}) export class ApplicationReconsideration extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/application-decision/application-reconsideration/reconsideration-outcome-type/application-reconsideration-outcome-type.entity.ts b/services/apps/alcs/src/alcs/application-decision/application-reconsideration/reconsideration-outcome-type/application-reconsideration-outcome-type.entity.ts index 6406e91c55..06479b85b5 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-reconsideration/reconsideration-outcome-type/application-reconsideration-outcome-type.entity.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-reconsideration/reconsideration-outcome-type/application-reconsideration-outcome-type.entity.ts @@ -1,5 +1,8 @@ import { Entity } from 'typeorm'; import { BaseCodeEntity } from '../../../../common/entities/base.code.entity'; -@Entity() +@Entity({ + comment: + 'Code table for possible application reconsideration review outcomes', +}) export class ApplicationReconsiderationOutcomeType extends BaseCodeEntity {} diff --git a/services/apps/alcs/src/alcs/application-decision/application-reconsideration/reconsideration-type/application-reconsideration-type.entity.ts b/services/apps/alcs/src/alcs/application-decision/application-reconsideration/reconsideration-type/application-reconsideration-type.entity.ts index bae0604b8c..9423a138e9 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-reconsideration/reconsideration-type/application-reconsideration-type.entity.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-reconsideration/reconsideration-type/application-reconsideration-type.entity.ts @@ -1,5 +1,5 @@ import { Entity } from 'typeorm'; import { BaseCodeEntity } from '../../../../common/entities/base.code.entity'; -@Entity() +@Entity({ comment: 'Code table for possible types of reconsiderations' }) export class ApplicationReconsiderationType extends BaseCodeEntity {} diff --git a/services/apps/alcs/src/alcs/application/application-decision-meeting/application-decision-meeting.entity.ts b/services/apps/alcs/src/alcs/application/application-decision-meeting/application-decision-meeting.entity.ts index 5c00e9bdb4..61fc14dac9 100644 --- a/services/apps/alcs/src/alcs/application/application-decision-meeting/application-decision-meeting.entity.ts +++ b/services/apps/alcs/src/alcs/application/application-decision-meeting/application-decision-meeting.entity.ts @@ -3,7 +3,7 @@ import { Column, Entity, Index, ManyToOne } from 'typeorm'; import { Base } from '../../../common/entities/base.entity'; import { Application } from '../application.entity'; -@Entity() +@Entity({ comment: 'Dates for application review discussions' }) export class ApplicationDecisionMeeting extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/application/application-document/application-document.entity.ts b/services/apps/alcs/src/alcs/application/application-document/application-document.entity.ts index f067b28b95..c4c7ca7208 100644 --- a/services/apps/alcs/src/alcs/application/application-document/application-document.entity.ts +++ b/services/apps/alcs/src/alcs/application/application-document/application-document.entity.ts @@ -22,7 +22,10 @@ export enum VISIBILITY_FLAG { } @Unique('OATS_UQ_DOCUMENTS', ['oatsDocumentId', 'oatsApplicationId']) -@Entity() +@Entity({ + comment: + "Links application documents with the applications they're saved to and logs other attributes", +}) export class ApplicationDocument extends BaseEntity { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/application/application-meeting/application-meeting.entity.ts b/services/apps/alcs/src/alcs/application/application-meeting/application-meeting.entity.ts index a2458e018c..6f7e1ccd5b 100644 --- a/services/apps/alcs/src/alcs/application/application-meeting/application-meeting.entity.ts +++ b/services/apps/alcs/src/alcs/application/application-meeting/application-meeting.entity.ts @@ -13,7 +13,7 @@ import { ApplicationMeetingType } from '../../code/application-code/application- import { ApplicationPaused } from '../application-paused.entity'; import { Application } from '../application.entity'; -@Entity() +@Entity({ comment: 'Actions that un/pause applications' }) export class ApplicationMeeting extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/application/application-paused.entity.ts b/services/apps/alcs/src/alcs/application/application-paused.entity.ts index 7b3a89ea1a..a3fe037a4d 100644 --- a/services/apps/alcs/src/alcs/application/application-paused.entity.ts +++ b/services/apps/alcs/src/alcs/application/application-paused.entity.ts @@ -2,7 +2,9 @@ import { Column, Entity, Index, ManyToOne } from 'typeorm'; import { Base } from '../../common/entities/base.entity'; import { Application } from './application.entity'; -@Entity() +@Entity({ + comment: 'Date ranges responsible for un/pausing an application', +}) export class ApplicationPaused extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/application/application-submission-status/submission-status-type.entity.ts b/services/apps/alcs/src/alcs/application/application-submission-status/submission-status-type.entity.ts index 3ea5175efa..4109ed89bd 100644 --- a/services/apps/alcs/src/alcs/application/application-submission-status/submission-status-type.entity.ts +++ b/services/apps/alcs/src/alcs/application/application-submission-status/submission-status-type.entity.ts @@ -3,7 +3,9 @@ import { Column, Entity, OneToMany } from 'typeorm'; import { BaseCodeEntity } from '../../../common/entities/base.code.entity'; import { ApplicationSubmissionToSubmissionStatus } from './submission-status.entity'; -@Entity() +@Entity({ + comment: 'Code table for possible application portal statuses', +}) export class ApplicationSubmissionStatusType extends BaseCodeEntity { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/application/application-submission-status/submission-status.entity.ts b/services/apps/alcs/src/alcs/application/application-submission-status/submission-status.entity.ts index dde1b09b3d..6e541bcffa 100644 --- a/services/apps/alcs/src/alcs/application/application-submission-status/submission-status.entity.ts +++ b/services/apps/alcs/src/alcs/application/application-submission-status/submission-status.entity.ts @@ -10,7 +10,7 @@ import { import { ApplicationSubmission } from '../../../portal/application-submission/application-submission.entity'; import { ApplicationSubmissionStatusType } from './submission-status-type.entity'; -@Entity() +@Entity({ comment: 'Join table that links submission with its status' }) export class ApplicationSubmissionToSubmissionStatus extends BaseEntity { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/application/application.entity.ts b/services/apps/alcs/src/alcs/application/application.entity.ts index 80ff2d87ea..fa83fd6717 100644 --- a/services/apps/alcs/src/alcs/application/application.entity.ts +++ b/services/apps/alcs/src/alcs/application/application.entity.ts @@ -14,17 +14,20 @@ import { Base } from '../../common/entities/base.entity'; import { FILE_NUMBER_SEQUENCE } from '../../file-number/file-number.constants'; import { ApplicationSubmissionReview } from '../../portal/application-submission-review/application-submission-review.entity'; import { ColumnNumericTransformer } from '../../utils/column-numeric-transform'; -import { ApplicationDecisionMeeting } from './application-decision-meeting/application-decision-meeting.entity'; import { ApplicationReconsideration } from '../application-decision/application-reconsideration/application-reconsideration.entity'; import { Card } from '../card/card.entity'; import { ApplicationRegion } from '../code/application-code/application-region/application-region.entity'; import { ApplicationType } from '../code/application-code/application-type/application-type.entity'; import { LocalGovernment } from '../local-government/local-government.entity'; +import { ApplicationDecisionMeeting } from './application-decision-meeting/application-decision-meeting.entity'; import { ApplicationDocument } from './application-document/application-document.entity'; import { ApplicationMeeting } from './application-meeting/application-meeting.entity'; import { ApplicationPaused } from './application-paused.entity'; -@Entity() +@Entity({ + comment: + 'Base data for applications including the ID, key dates, and the date of the first decision', +}) export class Application extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/board/board-status.entity.ts b/services/apps/alcs/src/alcs/board/board-status.entity.ts index 1eac4bee80..2b011d8422 100644 --- a/services/apps/alcs/src/alcs/board/board-status.entity.ts +++ b/services/apps/alcs/src/alcs/board/board-status.entity.ts @@ -1,10 +1,10 @@ import { AutoMap } from 'automapper-classes'; import { Column, Entity, ManyToOne, Unique } from 'typeorm'; -import { CardStatus } from '../card/card-status/card-status.entity'; import { Base } from '../../common/entities/base.entity'; +import { CardStatus } from '../card/card-status/card-status.entity'; import { Board } from './board.entity'; -@Entity() +@Entity({ comment: 'Columns on each kanban board' }) @Unique(['board', 'status']) export class BoardStatus extends Base { constructor(data?: Partial) { diff --git a/services/apps/alcs/src/alcs/board/board.entity.ts b/services/apps/alcs/src/alcs/board/board.entity.ts index 2d2c5e9987..a429215fd3 100644 --- a/services/apps/alcs/src/alcs/board/board.entity.ts +++ b/services/apps/alcs/src/alcs/board/board.entity.ts @@ -5,7 +5,9 @@ import { CardType } from '../card/card-type/card-type.entity'; import { Card } from '../card/card.entity'; import { BoardStatus } from './board-status.entity'; -@Entity() +@Entity({ + comment: 'Kanban boards that exist in ALCS', +}) export class Board extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/card/card-history/card-history.entity.ts b/services/apps/alcs/src/alcs/card/card-history/card-history.entity.ts index 632568e9cc..e74af16a30 100644 --- a/services/apps/alcs/src/alcs/card/card-history/card-history.entity.ts +++ b/services/apps/alcs/src/alcs/card/card-history/card-history.entity.ts @@ -1,8 +1,11 @@ import { Column, Entity, ManyToOne } from 'typeorm'; -import { Card } from '../card.entity'; import { EntityHistory } from '../../../common/entities/history.entity'; +import { Card } from '../card.entity'; -@Entity() +@Entity({ + comment: + "History of card status i.e. the column history of the card's journey on boards", +}) export class CardHistory extends EntityHistory { constructor() { super(); diff --git a/services/apps/alcs/src/alcs/card/card-status/card-status.entity.ts b/services/apps/alcs/src/alcs/card/card-status/card-status.entity.ts index 8679487ea7..87c57f1cdf 100644 --- a/services/apps/alcs/src/alcs/card/card-status/card-status.entity.ts +++ b/services/apps/alcs/src/alcs/card/card-status/card-status.entity.ts @@ -7,5 +7,7 @@ export enum CARD_STATUS { READY_FOR_REVIEW = 'READ', } -@Entity() +@Entity({ + comment: 'Code table for possible kanban columns that cards can be in', +}) export class CardStatus extends BaseCodeEntity {} diff --git a/services/apps/alcs/src/alcs/card/card-subtask/card-subtask-type/card-subtask-type.entity.ts b/services/apps/alcs/src/alcs/card/card-subtask/card-subtask-type/card-subtask-type.entity.ts index 9481c5d4e0..d076066ac6 100644 --- a/services/apps/alcs/src/alcs/card/card-subtask/card-subtask-type/card-subtask-type.entity.ts +++ b/services/apps/alcs/src/alcs/card/card-subtask/card-subtask-type/card-subtask-type.entity.ts @@ -2,7 +2,7 @@ import { AutoMap } from 'automapper-classes'; import { Column, Entity } from 'typeorm'; import { BaseCodeEntity } from '../../../../common/entities/base.code.entity'; -@Entity() +@Entity({ comment: 'Code table for possible subtask types' }) export class CardSubtaskType extends BaseCodeEntity { @AutoMap() @Column() diff --git a/services/apps/alcs/src/alcs/card/card-subtask/card-subtask.entity.ts b/services/apps/alcs/src/alcs/card/card-subtask/card-subtask.entity.ts index 1b2fa15112..df6e924946 100644 --- a/services/apps/alcs/src/alcs/card/card-subtask/card-subtask.entity.ts +++ b/services/apps/alcs/src/alcs/card/card-subtask/card-subtask.entity.ts @@ -1,10 +1,12 @@ import { Column, CreateDateColumn, Entity, ManyToOne } from 'typeorm'; -import { Card } from '../card.entity'; import { Base } from '../../../common/entities/base.entity'; import { User } from '../../../user/user.entity'; +import { Card } from '../card.entity'; import { CardSubtaskType } from './card-subtask-type/card-subtask-type.entity'; -@Entity() +@Entity({ + comment: 'Attributes for card subtasks', +}) export class CardSubtask extends Base { constructor(data?: Partial) { super(); 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 464a826d54..fce2922919 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 @@ -14,7 +14,9 @@ export enum CARD_TYPE { INQUIRY = 'INQR', } -@Entity() +@Entity({ + comment: 'Code table for possible card types', +}) export class CardType extends BaseCodeEntity { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/card/card.entity.ts b/services/apps/alcs/src/alcs/card/card.entity.ts index 52501f2ec3..2da5d95e8f 100644 --- a/services/apps/alcs/src/alcs/card/card.entity.ts +++ b/services/apps/alcs/src/alcs/card/card.entity.ts @@ -7,16 +7,18 @@ import { ManyToOne, OneToMany, } from 'typeorm'; -import { Board } from '../board/board.entity'; -import { Comment } from '../comment/comment.entity'; import { Base } from '../../common/entities/base.entity'; import { User } from '../../user/user.entity'; +import { Board } from '../board/board.entity'; +import { Comment } from '../comment/comment.entity'; import { CardHistory } from './card-history/card-history.entity'; import { CardStatus } from './card-status/card-status.entity'; import { CardSubtask } from './card-subtask/card-subtask.entity'; import { CardType } from './card-type/card-type.entity'; -@Entity() +@Entity({ + comment: 'Kanban board cards', +}) export class Card extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/code/application-code/application-meeting-type/application-meeting-type.entity.ts b/services/apps/alcs/src/alcs/code/application-code/application-meeting-type/application-meeting-type.entity.ts index 35905da1ee..0d5e8b475b 100644 --- a/services/apps/alcs/src/alcs/code/application-code/application-meeting-type/application-meeting-type.entity.ts +++ b/services/apps/alcs/src/alcs/code/application-code/application-meeting-type/application-meeting-type.entity.ts @@ -1,5 +1,7 @@ import { Entity } from 'typeorm'; import { BaseCodeEntity } from '../../../../common/entities/base.code.entity'; -@Entity() +@Entity({ + comment: 'Code table for possible action types that un/pause applications', +}) export class ApplicationMeetingType extends BaseCodeEntity {} diff --git a/services/apps/alcs/src/alcs/code/application-code/application-region/application-region.entity.ts b/services/apps/alcs/src/alcs/code/application-code/application-region/application-region.entity.ts index a1b801aed6..74d01ba645 100644 --- a/services/apps/alcs/src/alcs/code/application-code/application-region/application-region.entity.ts +++ b/services/apps/alcs/src/alcs/code/application-code/application-region/application-region.entity.ts @@ -1,5 +1,7 @@ import { Entity } from 'typeorm'; import { BaseCodeEntity } from '../../../../common/entities/base.code.entity'; -@Entity() +@Entity({ + comment: 'Code table for possible administrative regions in the province', +}) export class ApplicationRegion extends BaseCodeEntity {} diff --git a/services/apps/alcs/src/alcs/code/application-code/application-type/application-type.entity.ts b/services/apps/alcs/src/alcs/code/application-code/application-type/application-type.entity.ts index 5f30b1375c..ab077f02ca 100644 --- a/services/apps/alcs/src/alcs/code/application-code/application-type/application-type.entity.ts +++ b/services/apps/alcs/src/alcs/code/application-code/application-type/application-type.entity.ts @@ -3,7 +3,9 @@ import { Column, Entity } from 'typeorm'; import { BaseCodeEntity } from '../../../../common/entities/base.code.entity'; import { ColumnNumericTransformer } from '../../../../utils/column-numeric-transform'; -@Entity() +@Entity({ + comment: 'Code table for possible application types', +}) export class ApplicationType extends BaseCodeEntity { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/comment/comment.entity.ts b/services/apps/alcs/src/alcs/comment/comment.entity.ts index 867c1097a5..004f0ed658 100644 --- a/services/apps/alcs/src/alcs/comment/comment.entity.ts +++ b/services/apps/alcs/src/alcs/comment/comment.entity.ts @@ -7,12 +7,14 @@ import { ManyToOne, OneToMany, } from 'typeorm'; -import { Card } from '../card/card.entity'; import { Base } from '../../common/entities/base.entity'; import { User } from '../../user/user.entity'; +import { Card } from '../card/card.entity'; import { CommentMention } from './mention/comment-mention.entity'; -@Entity() +@Entity({ + comment: 'Attributes for card comments', +}) export class Comment extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/comment/mention/comment-mention.entity.ts b/services/apps/alcs/src/alcs/comment/mention/comment-mention.entity.ts index afdebf4a78..bc378c296b 100644 --- a/services/apps/alcs/src/alcs/comment/mention/comment-mention.entity.ts +++ b/services/apps/alcs/src/alcs/comment/mention/comment-mention.entity.ts @@ -4,7 +4,7 @@ import { Base } from '../../../common/entities/base.entity'; import { User } from '../../../user/user.entity'; import { Comment } from '../comment.entity'; -@Entity() +@Entity({ comment: 'Links comment mentions with the corresponding user' }) export class CommentMention extends Base { @AutoMap() @Column() diff --git a/services/apps/alcs/src/alcs/local-government/local-government.entity.ts b/services/apps/alcs/src/alcs/local-government/local-government.entity.ts index 83b21bb024..841ad54711 100644 --- a/services/apps/alcs/src/alcs/local-government/local-government.entity.ts +++ b/services/apps/alcs/src/alcs/local-government/local-government.entity.ts @@ -3,7 +3,10 @@ import { Column, Entity, ManyToOne } from 'typeorm'; import { Base } from '../../common/entities/base.entity'; import { ApplicationRegion } from '../code/application-code/application-region/application-region.entity'; -@Entity() +@Entity({ + comment: + 'Status, type, BCeID, and contact info of local or first nation governments', +}) export class LocalGovernment extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/message/message.entity.ts b/services/apps/alcs/src/alcs/message/message.entity.ts index a18c9fee28..0293cda84e 100644 --- a/services/apps/alcs/src/alcs/message/message.entity.ts +++ b/services/apps/alcs/src/alcs/message/message.entity.ts @@ -8,7 +8,7 @@ import { } from 'typeorm'; import { User } from '../../user/user.entity'; -@Entity() +@Entity({ comment: 'In-app messages' }) export class Message { constructor(data?: Partial) { if (data) { diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-component/notice-of-intent-decision-component-type.entity.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-component/notice-of-intent-decision-component-type.entity.ts index 77f3e40f43..7ac214ec56 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-component/notice-of-intent-decision-component-type.entity.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-component/notice-of-intent-decision-component-type.entity.ts @@ -1,7 +1,9 @@ import { Entity } from 'typeorm'; import { BaseCodeEntity } from '../../../common/entities/base.code.entity'; -@Entity() +@Entity({ + comment: 'Decision Component Types Code Table for Notice of Intents', +}) export class NoticeOfIntentDecisionComponentType extends BaseCodeEntity { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-component/notice-of-intent-decision-component.entity.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-component/notice-of-intent-decision-component.entity.ts index 746f8afe8a..131fd1dc93 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-component/notice-of-intent-decision-component.entity.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-component/notice-of-intent-decision-component.entity.ts @@ -6,7 +6,9 @@ import { NoticeOfIntentDecisionCondition } from '../notice-of-intent-decision-co import { NoticeOfIntentDecision } from '../notice-of-intent-decision.entity'; import { NoticeOfIntentDecisionComponentType } from './notice-of-intent-decision-component-type.entity'; -@Entity() +@Entity({ + comment: 'Decision Components for Notice of Intents', +}) @Index( ['noticeOfIntentDecisionComponentTypeCode', 'noticeOfIntentDecisionUuid'], { diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition-code.entity.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition-code.entity.ts index 595624db07..8abb15a907 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition-code.entity.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition-code.entity.ts @@ -1,5 +1,7 @@ import { Entity } from 'typeorm'; import { BaseCodeEntity } from '../../../common/entities/base.code.entity'; -@Entity() +@Entity({ + comment: 'Decision Condition Types Code Table for Notice of Intents', +}) export class NoticeOfIntentDecisionConditionType extends BaseCodeEntity {} diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.entity.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.entity.ts index 08cb17bb90..0c82f0e70a 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.entity.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.entity.ts @@ -6,7 +6,9 @@ import { NoticeOfIntentDecisionComponent } from '../notice-of-intent-decision-co import { NoticeOfIntentDecision } from '../notice-of-intent-decision.entity'; import { NoticeOfIntentDecisionConditionType } from './notice-of-intent-decision-condition-code.entity'; -@Entity() +@Entity({ + comment: 'Decision Conditions for Notice of Intents', +}) export class NoticeOfIntentDecisionCondition extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-document/notice-of-intent-decision-document.entity.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-document/notice-of-intent-decision-document.entity.ts index 39601a4016..e313a97ce6 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-document/notice-of-intent-decision-document.entity.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-document/notice-of-intent-decision-document.entity.ts @@ -11,7 +11,9 @@ import { Auditable } from '../../../common/entities/audit.entity'; import { Document } from '../../../document/document.entity'; import { NoticeOfIntentDecision } from '../notice-of-intent-decision.entity'; -@Entity() +@Entity({ + comment: "Links NOI decision document with the decision it's saved to", +}) export class NoticeOfIntentDecisionDocument extends Auditable { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-outcome.entity.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-outcome.entity.ts index a306fc647a..59313c04f4 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-outcome.entity.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-outcome.entity.ts @@ -1,5 +1,7 @@ import { Entity } from 'typeorm'; import { BaseCodeEntity } from '../../common/entities/base.code.entity'; -@Entity() +@Entity({ + comment: 'Code table for possible NOI decision outcomes', +}) export class NoticeOfIntentDecisionOutcome extends BaseCodeEntity {} diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision.entity.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision.entity.ts index 52a8dcf72c..493ce3bad5 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision.entity.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision.entity.ts @@ -18,7 +18,9 @@ import { NoticeOfIntentDecisionDocument } from './notice-of-intent-decision-docu import { NoticeOfIntentDecisionOutcome } from './notice-of-intent-decision-outcome.entity'; import { NoticeOfIntentModification } from './notice-of-intent-modification/notice-of-intent-modification.entity'; -@Entity() +@Entity({ + comment: 'Decisions saved to NOIs, linked to the modification request', +}) @Index(['resolutionNumber', 'resolutionYear'], { unique: true, where: '"audit_deleted_date_at" is null and "resolution_number" is not null', diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification-outcome-type/notice-of-intent-modification-outcome-type.entity.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification-outcome-type/notice-of-intent-modification-outcome-type.entity.ts index 66669276ec..a4018ac409 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification-outcome-type/notice-of-intent-modification-outcome-type.entity.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification-outcome-type/notice-of-intent-modification-outcome-type.entity.ts @@ -1,5 +1,5 @@ import { Entity } from 'typeorm'; import { BaseCodeEntity } from '../../../../common/entities/base.code.entity'; -@Entity() +@Entity({ comment: 'Code table for possible NOI modification review outcomes' }) export class NoticeOfIntentModificationOutcomeType extends BaseCodeEntity {} diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.entity.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.entity.ts index 64eb281b48..16d0b8081a 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.entity.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.entity.ts @@ -15,7 +15,9 @@ import { NoticeOfIntent } from '../../notice-of-intent/notice-of-intent.entity'; import { NoticeOfIntentDecision } from '../notice-of-intent-decision.entity'; import { NoticeOfIntentModificationOutcomeType } from './notice-of-intent-modification-outcome-type/notice-of-intent-modification-outcome-type.entity'; -@Entity() +@Entity({ + comment: 'NOI modification requests linked to card and application', +}) export class NoticeOfIntentModification extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-document/notice-of-intent-document.entity.ts b/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-document/notice-of-intent-document.entity.ts index 2cdb7db666..94613738a3 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-document/notice-of-intent-document.entity.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-document/notice-of-intent-document.entity.ts @@ -21,7 +21,10 @@ export enum VISIBILITY_FLAG { } @Unique('OATS_NOI_UQ_DOCUMENTS', ['oatsDocumentId', 'oatsApplicationId']) -@Entity() +@Entity({ + comment: + "Links NOI documents with the NOIs they're saved to and logs other attributes", +}) export class NoticeOfIntentDocument extends BaseEntity { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-meeting/notice-of-intent-meeting-type.entity.ts b/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-meeting/notice-of-intent-meeting-type.entity.ts index 96368799e7..401f622488 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-meeting/notice-of-intent-meeting-type.entity.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-meeting/notice-of-intent-meeting-type.entity.ts @@ -1,5 +1,7 @@ import { Entity } from 'typeorm'; import { BaseCodeEntity } from '../../../common/entities/base.code.entity'; -@Entity() +@Entity({ + comment: 'Code table for possible action types that un/pause NOIs', +}) export class NoticeOfIntentMeetingType extends BaseCodeEntity {} diff --git a/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-meeting/notice-of-intent-meeting.entity.ts b/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-meeting/notice-of-intent-meeting.entity.ts index e13c050e2c..f636537a35 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-meeting/notice-of-intent-meeting.entity.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-meeting/notice-of-intent-meeting.entity.ts @@ -4,7 +4,9 @@ import { Base } from '../../../common/entities/base.entity'; import { NoticeOfIntent } from '../notice-of-intent.entity'; import { NoticeOfIntentMeetingType } from './notice-of-intent-meeting-type.entity'; -@Entity() +@Entity({ + comment: 'Actions that un/pause NOIs', +}) export class NoticeOfIntentMeeting extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-submission-status/notice-of-intent-status-type.entity.ts b/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-submission-status/notice-of-intent-status-type.entity.ts index 533ca6018b..09c3036b6a 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-submission-status/notice-of-intent-status-type.entity.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-submission-status/notice-of-intent-status-type.entity.ts @@ -3,7 +3,9 @@ import { Column, Entity, OneToMany } from 'typeorm'; import { BaseCodeEntity } from '../../../common/entities/base.code.entity'; import { NoticeOfIntentSubmissionToSubmissionStatus } from './notice-of-intent-status.entity'; -@Entity() +@Entity({ + comment: 'The code table for Notice of Intent Submissions Statuses', +}) export class NoticeOfIntentSubmissionStatusType extends BaseCodeEntity { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-submission-status/notice-of-intent-status.entity.ts b/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-submission-status/notice-of-intent-status.entity.ts index 87bd00dcbf..4c3956cb89 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-submission-status/notice-of-intent-status.entity.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-submission-status/notice-of-intent-status.entity.ts @@ -10,7 +10,9 @@ import { import { NoticeOfIntentSubmission } from '../../../portal/notice-of-intent-submission/notice-of-intent-submission.entity'; import { NoticeOfIntentSubmissionStatusType } from './notice-of-intent-status-type.entity'; -@Entity() +@Entity({ + comment: 'Join table to link Notice of Intent Submissions to their Statuses', +}) export class NoticeOfIntentSubmissionToSubmissionStatus extends BaseEntity { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-subtype.entity.ts b/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-subtype.entity.ts index f33e272ce9..24c3d339e4 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-subtype.entity.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-subtype.entity.ts @@ -1,7 +1,9 @@ import { Column, Entity } from 'typeorm'; import { BaseCodeEntity } from '../../common/entities/base.code.entity'; -@Entity() +@Entity({ + comment: 'Code table for possible NOI subtypes', +}) export class NoticeOfIntentSubtype extends BaseCodeEntity { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-type/notice-of-intent-type.entity.ts b/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-type/notice-of-intent-type.entity.ts index bfa5597cf5..8fabc9a65b 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-type/notice-of-intent-type.entity.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent-type/notice-of-intent-type.entity.ts @@ -3,7 +3,7 @@ import { Column, Entity } from 'typeorm'; import { BaseCodeEntity } from '../../../common/entities/base.code.entity'; import { ColumnNumericTransformer } from '../../../utils/column-numeric-transform'; -@Entity() +@Entity({ comment: 'Code table for possible NOI types' }) export class NoticeOfIntentType extends BaseCodeEntity { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent.entity.ts b/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent.entity.ts index 7ba5440fe5..3359c3dc8c 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent.entity.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent/notice-of-intent.entity.ts @@ -15,12 +15,15 @@ import { Base } from '../../common/entities/base.entity'; import { ColumnNumericTransformer } from '../../utils/column-numeric-transform'; import { Card } from '../card/card.entity'; import { ApplicationRegion } from '../code/application-code/application-region/application-region.entity'; -import { NoticeOfIntentType } from './notice-of-intent-type/notice-of-intent-type.entity'; import { LocalGovernment } from '../local-government/local-government.entity'; import { NoticeOfIntentDocument } from './notice-of-intent-document/notice-of-intent-document.entity'; import { NoticeOfIntentSubtype } from './notice-of-intent-subtype.entity'; +import { NoticeOfIntentType } from './notice-of-intent-type/notice-of-intent-type.entity'; -@Entity() +@Entity({ + comment: + 'Base data for Notice of Intents incl. the ID, key dates, and the date of the first decision', +}) export class NoticeOfIntent extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/notification/notification-document/notification-document.entity.ts b/services/apps/alcs/src/alcs/notification/notification-document/notification-document.entity.ts index b7c2713f7b..c766e3d632 100644 --- a/services/apps/alcs/src/alcs/notification/notification-document/notification-document.entity.ts +++ b/services/apps/alcs/src/alcs/notification/notification-document/notification-document.entity.ts @@ -19,7 +19,9 @@ export enum VISIBILITY_FLAG { GOVERNMENT = 'G', } -@Entity() +@Entity({ + comment: 'Documents for Notifications', +}) export class NotificationDocument extends BaseEntity { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/notification/notification-submission-status/notification-status-type.entity.ts b/services/apps/alcs/src/alcs/notification/notification-submission-status/notification-status-type.entity.ts index b713164001..aeaf267ce8 100644 --- a/services/apps/alcs/src/alcs/notification/notification-submission-status/notification-status-type.entity.ts +++ b/services/apps/alcs/src/alcs/notification/notification-submission-status/notification-status-type.entity.ts @@ -3,7 +3,9 @@ import { Column, Entity, OneToMany } from 'typeorm'; import { BaseCodeEntity } from '../../../common/entities/base.code.entity'; import { NotificationSubmissionToSubmissionStatus } from './notification-status.entity'; -@Entity() +@Entity({ + comment: 'Statuses for Notification Submissions', +}) export class NotificationSubmissionStatusType extends BaseCodeEntity { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/notification/notification-submission-status/notification-status.entity.ts b/services/apps/alcs/src/alcs/notification/notification-submission-status/notification-status.entity.ts index 006d8af6ad..036c87b4f2 100644 --- a/services/apps/alcs/src/alcs/notification/notification-submission-status/notification-status.entity.ts +++ b/services/apps/alcs/src/alcs/notification/notification-submission-status/notification-status.entity.ts @@ -10,7 +10,7 @@ import { import { NotificationSubmission } from '../../../portal/notification-submission/notification-submission.entity'; import { NotificationSubmissionStatusType } from './notification-status-type.entity'; -@Entity() +@Entity({ comment: 'Links Notifications to their Statuses with Dates' }) export class NotificationSubmissionToSubmissionStatus extends BaseEntity { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/notification/notification-type/notification-type.entity.ts b/services/apps/alcs/src/alcs/notification/notification-type/notification-type.entity.ts index cb900a170f..5dfa21fe83 100644 --- a/services/apps/alcs/src/alcs/notification/notification-type/notification-type.entity.ts +++ b/services/apps/alcs/src/alcs/notification/notification-type/notification-type.entity.ts @@ -2,7 +2,7 @@ import { AutoMap } from 'automapper-classes'; import { Column, Entity } from 'typeorm'; import { BaseCodeEntity } from '../../../common/entities/base.code.entity'; -@Entity() +@Entity({ comment: 'Code table for possible Notification types' }) export class NotificationType extends BaseCodeEntity { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/notification/notification.entity.ts b/services/apps/alcs/src/alcs/notification/notification.entity.ts index f54bc9c2c6..0a093bc6aa 100644 --- a/services/apps/alcs/src/alcs/notification/notification.entity.ts +++ b/services/apps/alcs/src/alcs/notification/notification.entity.ts @@ -16,7 +16,9 @@ import { LocalGovernment } from '../local-government/local-government.entity'; import { NotificationDocument } from './notification-document/notification-document.entity'; import { NotificationType } from './notification-type/notification-type.entity'; -@Entity() +@Entity({ + comment: 'Stores Notification Class Applications such as SRWs', +}) export class Notification extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision-document/planning-review-decision-document.entity.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision-document/planning-review-decision-document.entity.ts index b91910325b..15d9984ba6 100644 --- a/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision-document/planning-review-decision-document.entity.ts +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision-document/planning-review-decision-document.entity.ts @@ -11,7 +11,10 @@ import { Auditable } from '../../../../common/entities/audit.entity'; import { Document } from '../../../../document/document.entity'; import { PlanningReviewDecision } from '../planning-review-decision.entity'; -@Entity() +@Entity({ + comment: + "Links Planning Review decision document with the decision it's saved to", +}) export class PlanningReviewDecisionDocument extends Auditable { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision-outcome.entity.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision-outcome.entity.ts index bbd6440c9b..87ff4ad5e5 100644 --- a/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision-outcome.entity.ts +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision-outcome.entity.ts @@ -1,5 +1,5 @@ import { Entity } from 'typeorm'; import { BaseCodeEntity } from '../../../common/entities/base.code.entity'; -@Entity() +@Entity({ comment: 'Possible decision outcome types for Planning Review' }) export class PlanningReviewDecisionOutcomeCode extends BaseCodeEntity {} diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.entity.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.entity.ts index 3c39793741..14244fedae 100644 --- a/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.entity.ts +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-decision/planning-review-decision.entity.ts @@ -12,7 +12,10 @@ import { PlanningReview } from '../planning-review.entity'; import { PlanningReviewDecisionDocument } from './planning-review-decision-document/planning-review-decision-document.entity'; import { PlanningReviewDecisionOutcomeCode } from './planning-review-decision-outcome.entity'; -@Entity() +@Entity({ + comment: + "Links Planning Review decision document with the decision it's saved to", +}) @Index(['resolutionNumber', 'resolutionYear'], { unique: true, where: '"audit_deleted_date_at" is null and "resolution_number" is not null', 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 index 471a88d482..3016ec6d88 100644 --- 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 @@ -2,7 +2,7 @@ import { AutoMap } from 'automapper-classes'; import { Column, Entity } from 'typeorm'; import { BaseCodeEntity } from '../../common/entities/base.code.entity'; -@Entity() +@Entity({ comment: 'Code table for possible Planning Review types' }) export class PlanningReviewType extends BaseCodeEntity { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/alcs/staff-journal/staff-journal.entity.ts b/services/apps/alcs/src/alcs/staff-journal/staff-journal.entity.ts index de2401b84e..ee2d92870d 100644 --- a/services/apps/alcs/src/alcs/staff-journal/staff-journal.entity.ts +++ b/services/apps/alcs/src/alcs/staff-journal/staff-journal.entity.ts @@ -8,7 +8,10 @@ import { NoticeOfIntent } from '../notice-of-intent/notice-of-intent.entity'; import { Notification } from '../notification/notification.entity'; import { PlanningReview } from '../planning-review/planning-review.entity'; -@Entity() +@Entity({ + comment: + 'Staff journal entries saved to applications and NOIs, SRWs, Inquiries etc.', +}) export class StaffJournal extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/common/entities/configuration.entity.ts b/services/apps/alcs/src/common/entities/configuration.entity.ts index e4779b6250..c6ac80a4c9 100644 --- a/services/apps/alcs/src/common/entities/configuration.entity.ts +++ b/services/apps/alcs/src/common/entities/configuration.entity.ts @@ -4,7 +4,7 @@ export enum CONFIG_VALUE { PORTAL_MAINTENANCE_MODE = 'portal_maintenance_mode', } -@Entity() +@Entity({ comment: 'Stores real time config values editable by ALCS Admin.' }) export class Configuration extends BaseEntity { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/common/entities/parcel-ownership-type/parcel-ownership-type.entity.ts b/services/apps/alcs/src/common/entities/parcel-ownership-type/parcel-ownership-type.entity.ts index 6ae47f4d92..cc4c14b819 100644 --- a/services/apps/alcs/src/common/entities/parcel-ownership-type/parcel-ownership-type.entity.ts +++ b/services/apps/alcs/src/common/entities/parcel-ownership-type/parcel-ownership-type.entity.ts @@ -4,5 +4,7 @@ import { BaseCodeEntity } from '../base.code.entity'; export class ParcelOwnershipTypeDto extends BaseCodeDto {} -@Entity() +@Entity({ + comment: 'Code table for possible land ownership types (Fee simple vs Crown)', +}) export class ParcelOwnershipType extends BaseCodeEntity {} diff --git a/services/apps/alcs/src/common/owner-type/owner-type.entity.ts b/services/apps/alcs/src/common/owner-type/owner-type.entity.ts index 558d3e31ca..4a5d288e08 100644 --- a/services/apps/alcs/src/common/owner-type/owner-type.entity.ts +++ b/services/apps/alcs/src/common/owner-type/owner-type.entity.ts @@ -12,7 +12,9 @@ export enum OWNER_TYPE { export class OwnerTypeDto extends BaseCodeDto {} -@Entity() +@Entity({ + comment: 'Code table for possible types of owners or primary contacts', +}) export class OwnerType extends BaseCodeEntity { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/common/tracking/file-viewed.entity.ts b/services/apps/alcs/src/common/tracking/file-viewed.entity.ts index 8798b877cb..a9dfdbfa7b 100644 --- a/services/apps/alcs/src/common/tracking/file-viewed.entity.ts +++ b/services/apps/alcs/src/common/tracking/file-viewed.entity.ts @@ -7,7 +7,9 @@ import { PrimaryGeneratedColumn, } from 'typeorm'; -@Entity() +@Entity({ + comment: 'Stores when the file(Application, NOI etc.) was last viewed.', +}) export class FileViewed extends BaseEntity { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/document/document-code.entity.ts b/services/apps/alcs/src/document/document-code.entity.ts index 697fe3632e..8bb9abfbef 100644 --- a/services/apps/alcs/src/document/document-code.entity.ts +++ b/services/apps/alcs/src/document/document-code.entity.ts @@ -47,7 +47,7 @@ export const DEFAULT_PUBLIC_TYPES = [ DOCUMENT_TYPE.CROSS_SECTIONS, ]; -@Entity() +@Entity({ comment: 'Code table for possible document types' }) export class DocumentCode extends BaseCodeEntity { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/document/document.entity.ts b/services/apps/alcs/src/document/document.entity.ts index a57596fc0d..b6456ec6d8 100644 --- a/services/apps/alcs/src/document/document.entity.ts +++ b/services/apps/alcs/src/document/document.entity.ts @@ -2,7 +2,9 @@ import { Column, CreateDateColumn, Entity, ManyToOne } from 'typeorm'; import { Base } from '../common/entities/base.entity'; import { User } from '../user/user.entity'; -@Entity() +@Entity({ + comment: 'Attributes for documents including their ORCS classification', +}) export class Document extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/healthcheck/healthcheck.entity.ts b/services/apps/alcs/src/healthcheck/healthcheck.entity.ts index 97105eb492..05ad27a433 100644 --- a/services/apps/alcs/src/healthcheck/healthcheck.entity.ts +++ b/services/apps/alcs/src/healthcheck/healthcheck.entity.ts @@ -1,6 +1,9 @@ import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; -@Entity() +@Entity({ + comment: + 'Unix timestamp of the last time the connection from API to database was checked and succeeded', +}) export class HealthCheck extends BaseEntity { @PrimaryGeneratedColumn() id: number; diff --git a/services/apps/alcs/src/portal/application-submission-review/application-submission-review.entity.ts b/services/apps/alcs/src/portal/application-submission-review/application-submission-review.entity.ts index 9f33a3a52d..7f708e3c8b 100644 --- a/services/apps/alcs/src/portal/application-submission-review/application-submission-review.entity.ts +++ b/services/apps/alcs/src/portal/application-submission-review/application-submission-review.entity.ts @@ -4,7 +4,9 @@ import { Application } from '../../alcs/application/application.entity'; import { Base } from '../../common/entities/base.entity'; import { User } from '../../user/user.entity'; -@Entity() +@Entity({ + comment: 'Portal local or first nation government review form fields', +}) export class ApplicationSubmissionReview extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/portal/application-submission/application-owner/application-owner.entity.ts b/services/apps/alcs/src/portal/application-submission/application-owner/application-owner.entity.ts index 5116fe9aab..9230a08674 100644 --- a/services/apps/alcs/src/portal/application-submission/application-owner/application-owner.entity.ts +++ b/services/apps/alcs/src/portal/application-submission/application-owner/application-owner.entity.ts @@ -7,7 +7,10 @@ import { OwnerType } from '../../../common/owner-type/owner-type.entity'; import { ApplicationParcel } from '../application-parcel/application-parcel.entity'; import { ApplicationSubmission } from '../application-submission.entity'; -@Entity() +@Entity({ + comment: + 'Contact information, type, and corporate summary document UUID for owner or primary contact', +}) export class ApplicationOwner extends Base { constructor(data?: Partial) { super(); @@ -105,7 +108,7 @@ export class ApplicationOwner extends Base { @Column({ type: 'varchar', nullable: true, - default: null + default: null, }) crownLandOwnerType?: string | null; } diff --git a/services/apps/alcs/src/portal/application-submission/application-parcel/application-parcel.entity.ts b/services/apps/alcs/src/portal/application-submission/application-parcel/application-parcel.entity.ts index 0af3654168..852aa10b4c 100644 --- a/services/apps/alcs/src/portal/application-submission/application-parcel/application-parcel.entity.ts +++ b/services/apps/alcs/src/portal/application-submission/application-parcel/application-parcel.entity.ts @@ -15,7 +15,7 @@ import { ColumnNumericTransformer } from '../../../utils/column-numeric-transfor import { ApplicationOwner } from '../application-owner/application-owner.entity'; import { ApplicationSubmission } from '../application-submission.entity'; -@Entity() +@Entity({ comment: 'Parcels associated with application submissions' }) export class ApplicationParcel extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/portal/application-submission/application-submission.entity.ts b/services/apps/alcs/src/portal/application-submission/application-submission.entity.ts index d25cdd80ab..59db1d3596 100644 --- a/services/apps/alcs/src/portal/application-submission/application-submission.entity.ts +++ b/services/apps/alcs/src/portal/application-submission/application-submission.entity.ts @@ -9,8 +9,8 @@ import { OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; -import { Application } from '../../alcs/application/application.entity'; import { ApplicationSubmissionToSubmissionStatus } from '../../alcs/application/application-submission-status/submission-status.entity'; +import { Application } from '../../alcs/application/application.entity'; import { Base } from '../../common/entities/base.entity'; import { User } from '../../user/user.entity'; import { ColumnNumericTransformer } from '../../utils/column-numeric-transform'; @@ -25,7 +25,9 @@ export class ProposedLot { planNumbers: string | null; } -@Entity() +@Entity({ + comment: 'Portal intake form fields for applications', +}) export class ApplicationSubmission extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/portal/application-submission/covenant-transferee/covenant-transferee.entity.ts b/services/apps/alcs/src/portal/application-submission/covenant-transferee/covenant-transferee.entity.ts index 94d41c606d..5d94e25dec 100644 --- a/services/apps/alcs/src/portal/application-submission/covenant-transferee/covenant-transferee.entity.ts +++ b/services/apps/alcs/src/portal/application-submission/covenant-transferee/covenant-transferee.entity.ts @@ -4,7 +4,9 @@ import { Base } from '../../../common/entities/base.entity'; import { OwnerType } from '../../../common/owner-type/owner-type.entity'; import { ApplicationSubmission } from '../application-submission.entity'; -@Entity() +@Entity({ + comment: 'Stores Transferees for Restrictive Covenant Applications', +}) export class CovenantTransferee extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/portal/application-submission/naru-subtype/naru-subtype.entity.ts b/services/apps/alcs/src/portal/application-submission/naru-subtype/naru-subtype.entity.ts index ec8da8b98a..ebca406d09 100644 --- a/services/apps/alcs/src/portal/application-submission/naru-subtype/naru-subtype.entity.ts +++ b/services/apps/alcs/src/portal/application-submission/naru-subtype/naru-subtype.entity.ts @@ -1,7 +1,10 @@ import { Entity } from 'typeorm'; import { BaseCodeEntity } from '../../../common/entities/base.code.entity'; -@Entity() +@Entity({ + comment: + 'Code table for possible subtypes of Non-Adhering Residential Use applications', +}) export class NaruSubtype extends BaseCodeEntity { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/portal/notice-of-intent-submission/notice-of-intent-owner/notice-of-intent-owner.entity.ts b/services/apps/alcs/src/portal/notice-of-intent-submission/notice-of-intent-owner/notice-of-intent-owner.entity.ts index 5b0129dd48..e04ed3d2ff 100644 --- a/services/apps/alcs/src/portal/notice-of-intent-submission/notice-of-intent-owner/notice-of-intent-owner.entity.ts +++ b/services/apps/alcs/src/portal/notice-of-intent-submission/notice-of-intent-owner/notice-of-intent-owner.entity.ts @@ -7,7 +7,7 @@ import { OwnerType } from '../../../common/owner-type/owner-type.entity'; import { NoticeOfIntentParcel } from '../notice-of-intent-parcel/notice-of-intent-parcel.entity'; import { NoticeOfIntentSubmission } from '../notice-of-intent-submission.entity'; -@Entity() +@Entity({ comment: 'Owners for Notice of Intent Submissions' }) export class NoticeOfIntentOwner extends Base { constructor(data?: Partial) { super(); @@ -105,7 +105,7 @@ export class NoticeOfIntentOwner extends Base { @Column({ type: 'varchar', nullable: true, - default: null + default: null, }) crownLandOwnerType?: string | null; } diff --git a/services/apps/alcs/src/portal/notice-of-intent-submission/notice-of-intent-parcel/notice-of-intent-parcel.entity.ts b/services/apps/alcs/src/portal/notice-of-intent-submission/notice-of-intent-parcel/notice-of-intent-parcel.entity.ts index c742f73e3b..c3eca882a4 100644 --- a/services/apps/alcs/src/portal/notice-of-intent-submission/notice-of-intent-parcel/notice-of-intent-parcel.entity.ts +++ b/services/apps/alcs/src/portal/notice-of-intent-submission/notice-of-intent-parcel/notice-of-intent-parcel.entity.ts @@ -15,7 +15,7 @@ import { ColumnNumericTransformer } from '../../../utils/column-numeric-transfor import { NoticeOfIntentOwner } from '../notice-of-intent-owner/notice-of-intent-owner.entity'; import { NoticeOfIntentSubmission } from '../notice-of-intent-submission.entity'; -@Entity() +@Entity({ comment: 'Parcels that are linked to Notice of Intent Submissions' }) export class NoticeOfIntentParcel extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/portal/notice-of-intent-submission/notice-of-intent-submission.entity.ts b/services/apps/alcs/src/portal/notice-of-intent-submission/notice-of-intent-submission.entity.ts index a3eb93c876..81144bc6eb 100644 --- a/services/apps/alcs/src/portal/notice-of-intent-submission/notice-of-intent-submission.entity.ts +++ b/services/apps/alcs/src/portal/notice-of-intent-submission/notice-of-intent-submission.entity.ts @@ -29,7 +29,7 @@ export class ProposedStructure { area?: number | null; } -@Entity() +@Entity({ comment: 'Portal intake form fields for NOIs' }) export class NoticeOfIntentSubmission extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/portal/notification-submission/notification-parcel/notification-parcel.entity.ts b/services/apps/alcs/src/portal/notification-submission/notification-parcel/notification-parcel.entity.ts index 1a421653d0..1f386d4c7d 100644 --- a/services/apps/alcs/src/portal/notification-submission/notification-parcel/notification-parcel.entity.ts +++ b/services/apps/alcs/src/portal/notification-submission/notification-parcel/notification-parcel.entity.ts @@ -4,7 +4,7 @@ import { Base } from '../../../common/entities/base.entity'; import { ParcelOwnershipType } from '../../../common/entities/parcel-ownership-type/parcel-ownership-type.entity'; import { NotificationSubmission } from '../notification-submission.entity'; -@Entity() +@Entity({ comment: 'Parcels Related to Notification Applications' }) export class NotificationParcel extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/portal/notification-submission/notification-submission.entity.ts b/services/apps/alcs/src/portal/notification-submission/notification-submission.entity.ts index c8f6a9d2bd..ef99ca5f63 100644 --- a/services/apps/alcs/src/portal/notification-submission/notification-submission.entity.ts +++ b/services/apps/alcs/src/portal/notification-submission/notification-submission.entity.ts @@ -16,7 +16,9 @@ import { ColumnNumericTransformer } from '../../utils/column-numeric-transform'; import { NotificationParcel } from './notification-parcel/notification-parcel.entity'; import { NotificationTransferee } from './notification-transferee/notification-transferee.entity'; -@Entity() +@Entity({ + comment: 'Portal Submissions for Notifications', +}) export class NotificationSubmission extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/portal/notification-submission/notification-transferee/notification-transferee.entity.ts b/services/apps/alcs/src/portal/notification-submission/notification-transferee/notification-transferee.entity.ts index 919e8e4f5f..0d2ecb2bab 100644 --- a/services/apps/alcs/src/portal/notification-submission/notification-transferee/notification-transferee.entity.ts +++ b/services/apps/alcs/src/portal/notification-submission/notification-transferee/notification-transferee.entity.ts @@ -4,7 +4,9 @@ import { Base } from '../../../common/entities/base.entity'; import { OwnerType } from '../../../common/owner-type/owner-type.entity'; import { NotificationSubmission } from '../notification-submission.entity'; -@Entity() +@Entity({ + comment: 'The Transferees related to Notification Applications', +}) export class NotificationTransferee extends Base { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/portal/parcel/parcel-lookup.entity.ts b/services/apps/alcs/src/portal/parcel/parcel-lookup.entity.ts index 4a3e758979..88555323f3 100644 --- a/services/apps/alcs/src/portal/parcel/parcel-lookup.entity.ts +++ b/services/apps/alcs/src/portal/parcel/parcel-lookup.entity.ts @@ -1,6 +1,8 @@ import { Column, Entity, Index, PrimaryColumn } from 'typeorm'; -@Entity() +@Entity({ + comment: 'Data from ParcelMapBC for use in the Portal', +}) export class ParcelLookup { @PrimaryColumn({ type: 'int4' }) objectid: number; diff --git a/services/apps/alcs/src/providers/email/email-status.entity.ts b/services/apps/alcs/src/providers/email/email-status.entity.ts index 086928fe56..dd1b3dace7 100644 --- a/services/apps/alcs/src/providers/email/email-status.entity.ts +++ b/services/apps/alcs/src/providers/email/email-status.entity.ts @@ -7,7 +7,9 @@ import { PrimaryGeneratedColumn, } from 'typeorm'; -@Entity() +@Entity({ + comment: 'Success or failure of emails sent by ALCS', +}) export class EmailStatus extends BaseEntity { constructor(data?: Partial) { super(); diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1712083862347-table_comments.ts b/services/apps/alcs/src/providers/typeorm/migrations/1712083862347-table_comments.ts new file mode 100644 index 0000000000..5cfe88697b --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1712083862347-table_comments.ts @@ -0,0 +1,455 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class TableComments1712083862347 implements MigrationInterface { + name = 'TableComments1712083862347'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `COMMENT ON TABLE "alcs"."card_type" IS 'Code table for possible card types'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."card_status" IS 'Code table for possible kanban columns that cards can be in'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."board_status" IS 'Columns on each kanban board'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."board" IS 'Kanban boards that exist in ALCS'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."comment_mention" IS 'Links comment mentions with the corresponding user'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."comment" IS 'Attributes for card comments'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."card_history" IS 'History of card status i.e. the column history of the card''s journey on boards'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."card_subtask_type" IS 'Code table for possible subtask types'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."card_subtask" IS 'Attributes for card subtasks'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."card" IS 'Kanban board cards'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."document" IS 'Attributes for documents including their ORCS classification'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."user" IS 'Authenticated users and their attributes'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."health_check" IS 'Unix timestamp of the last time the connection from API to database was checked and succeeded'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."document_code" IS 'Code table for possible document types'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."email_status" IS 'Success or failure of emails sent by ALCS'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notification_submission_status_type" IS 'Statuses for Notification Submissions'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notification_submission_to_submission_status" IS 'Links Notifications to their Statuses with Dates'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_region" IS 'Code table for possible administrative regions in the province'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."local_government" IS 'Status, type, BCeID, and contact info of local or first nation governments'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notification_document" IS 'Documents for Notifications'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notification" IS 'Stores Notification Class Applications such as SRWs'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."parcel_ownership_type" IS 'Code table for possible land ownership types (Fee simple vs Crown)'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notification_parcel" IS 'Parcels Related to Notification Applications'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."owner_type" IS 'Code table for possible types of owners or primary contacts'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notification_transferee" IS 'The Transferees related to Notification Applications'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notification_submission" IS 'Portal Submissions for Notifications'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."parcel_lookup" IS 'Data from ParcelMapBC for use in the Portal'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_submission_status_type" IS 'The code table for Notice of Intent Submissions Statuses'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_submission_to_submission_status" IS 'Join table to link Notice of Intent Submissions to their Statuses'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_subtype" IS 'Code table for possible NOI subtypes'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent" IS 'Base data for Notice of Intents incl. the ID, key dates, and the date of the first decision'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_parcel" IS 'Parcels that are linked to Notice of Intent Submissions'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_owner" IS 'Owners for Notice of Intent Submissions'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_ceo_criterion_code" IS 'Code table for criteria under which the CEO can make a decision on an application'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."naru_subtype" IS 'Code table for possible subtypes of Non-Adhering Residential Use applications'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_decision_condition_to_component_lot" IS 'Join table to link approved subdivision lots between condition and components and provide plan numbers associated with survey plan per lot'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_decision_component_lot" IS 'Approved lots on the subdivision decision component'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_decision_component_type" IS 'Code table for the possible application decision component types'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_decision_component" IS 'Fields present on the application decision components'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_decision_condition_component_plan_number" IS 'Survey plan numbers associated with survey plan conditions on decision components'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_decision_condition_type" IS 'Code table for the possible application decision condition types'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_decision_condition" IS 'Fields present on the application decision conditions'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_decision_document" IS 'Links application decision document with the decision it''s saved to'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_decision_maker_code" IS 'Code table for the possible application decision makers'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_decision_chair_review_outcome_type" IS 'Code table for the possible outcomes of the chair''s application decision review'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_decision_outcome_code" IS 'Code table for the possible application decision outcomes'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_modification_outcome_type" IS 'Code table for possible application modification review outcomes'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_modification" IS 'Application modification requests linked to card and application'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_decision" IS 'Decisions saved to applications, incl. those linked to the recon/modification request'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_reconsideration_outcome_type" IS 'Code table for possible application reconsideration review outcomes'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_reconsideration_type" IS 'Code table for possible types of reconsiderations'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_reconsideration" IS 'Application reconsideration requests linked to card and application'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_type" IS 'Code table for possible application types'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_decision_meeting" IS 'Dates for application review discussions'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_document" IS 'Links application documents with the applications they''re saved to and logs other attributes'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_meeting_type" IS 'Code table for possible action types that un/pause applications'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_paused" IS 'Date ranges responsible for un/pausing an application'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_meeting" IS 'Actions that un/pause applications'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application" IS 'Base data for applications including the ID, key dates, and the date of the first decision'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_submission_review" IS 'Portal local or first nation government review form fields'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_submission_status_type" IS 'Code table for possible application portal statuses'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_submission_to_submission_status" IS 'Join table that links submission with its status'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_parcel" IS 'Parcels associated with application submissions'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_owner" IS 'Contact information, type, and corporate summary document UUID for owner or primary contact'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_submission" IS 'Portal intake form fields for applications'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."configuration" IS 'Stores real time config values editable by ALCS Admin.'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."staff_journal" IS 'Staff journal entries saved to applications and NOIs, SRWs, Inquiries etc.'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_decision_condition_type" IS 'Decision Condition Types Code Table for Notice of Intents'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_decision_condition" IS 'Decision Conditions for Notice of Intents'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_decision_component_type" IS 'Decision Component Types Code Table for Notice of Intents'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_decision_component" IS 'Decision Components for Notice of Intents'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_decision_document" IS 'Links NOI decision document with the decision it''s saved to'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_decision_outcome" IS 'Code table for possible NOI decision outcomes'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_modification_outcome_type" IS 'Code table for possible NOI modification review outcomes'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_modification" IS 'NOI modification requests linked to card and application'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_decision" IS 'Decisions saved to NOIs, linked to the modification request'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."covenant_transferee" IS 'Stores Transferees for Restrictive Covenant Applications'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_meeting_type" IS 'Code table for possible action types that un/pause NOIs'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_meeting" IS 'Actions that un/pause NOIs'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_boundary_amendment" IS 'Used by ALC GIS Staff to track Inclusion / Exclusion decisions and their ALR boundary impact over time'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."holiday_entity" IS 'Holidays used by the application active day tracking function'`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`COMMENT ON TABLE "alcs"."holiday_entity" IS NULL`); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_boundary_amendment" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_meeting" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_meeting_type" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."covenant_transferee" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_decision" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_modification" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_modification_outcome_type" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_decision_outcome" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_decision_document" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_decision_component" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_decision_component_type" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_decision_condition" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_decision_condition_type" IS NULL`, + ); + await queryRunner.query(`COMMENT ON TABLE "alcs"."staff_journal" IS NULL`); + await queryRunner.query(`COMMENT ON TABLE "alcs"."configuration" IS NULL`); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_submission" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_owner" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_parcel" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_submission_to_submission_status" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_submission_status_type" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_submission_review" IS NULL`, + ); + await queryRunner.query(`COMMENT ON TABLE "alcs"."application" IS NULL`); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_meeting" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_paused" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_meeting_type" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_document" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_decision_meeting" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_type" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_reconsideration" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_reconsideration_type" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_reconsideration_outcome_type" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_decision" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_modification" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_modification_outcome_type" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_decision_outcome_code" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_decision_chair_review_outcome_type" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_decision_maker_code" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_decision_document" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_decision_condition" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_decision_condition_type" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_decision_condition_component_plan_number" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_decision_component" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_decision_component_type" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_decision_component_lot" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_decision_condition_to_component_lot" IS NULL`, + ); + await queryRunner.query(`COMMENT ON TABLE "alcs"."naru_subtype" IS NULL`); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_ceo_criterion_code" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_owner" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_parcel" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_subtype" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_submission_to_submission_status" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_submission_status_type" IS NULL`, + ); + await queryRunner.query(`COMMENT ON TABLE "alcs"."parcel_lookup" IS NULL`); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notification_submission" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notification_transferee" IS NULL`, + ); + await queryRunner.query(`COMMENT ON TABLE "alcs"."owner_type" IS NULL`); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notification_parcel" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."parcel_ownership_type" IS NULL`, + ); + await queryRunner.query(`COMMENT ON TABLE "alcs"."notification" IS NULL`); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notification_document" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."local_government" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."application_region" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notification_submission_to_submission_status" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notification_submission_status_type" IS NULL`, + ); + await queryRunner.query(`COMMENT ON TABLE "alcs"."email_status" IS NULL`); + await queryRunner.query(`COMMENT ON TABLE "alcs"."document_code" IS NULL`); + await queryRunner.query(`COMMENT ON TABLE "alcs"."health_check" IS NULL`); + await queryRunner.query(`COMMENT ON TABLE "alcs"."user" IS NULL`); + await queryRunner.query(`COMMENT ON TABLE "alcs"."document" IS NULL`); + await queryRunner.query(`COMMENT ON TABLE "alcs"."card" IS NULL`); + await queryRunner.query(`COMMENT ON TABLE "alcs"."card_subtask" IS NULL`); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."card_subtask_type" IS NULL`, + ); + await queryRunner.query(`COMMENT ON TABLE "alcs"."card_history" IS NULL`); + await queryRunner.query(`COMMENT ON TABLE "alcs"."comment" IS NULL`); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."comment_mention" IS NULL`, + ); + await queryRunner.query(`COMMENT ON TABLE "alcs"."board" IS NULL`); + await queryRunner.query(`COMMENT ON TABLE "alcs"."board_status" IS NULL`); + await queryRunner.query(`COMMENT ON TABLE "alcs"."card_status" IS NULL`); + await queryRunner.query(`COMMENT ON TABLE "alcs"."card_type" IS NULL`); + } +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1712088247956-table_comments_2.ts b/services/apps/alcs/src/providers/typeorm/migrations/1712088247956-table_comments_2.ts new file mode 100644 index 0000000000..876b10f442 --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1712088247956-table_comments_2.ts @@ -0,0 +1,67 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class TableComments21712088247956 implements MigrationInterface { + name = 'TableComments21712088247956'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notification_type" IS 'Code table for possible Notification types'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_document" IS 'Links NOI documents with the NOIs they''re saved to and logs other attributes'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_type" IS 'Code table for possible NOI types'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_submission" IS 'Portal intake form fields for NOIs'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."file_viewed" IS 'Stores when the file(Application, NOI etc.) was last viewed.'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."planning_review_type" IS 'Code table for possible Planning Review types'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."planning_review_decision_document" IS 'Links Planning Review decision document with the decision it''s saved to'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."planning_review_decision_outcome_code" IS 'Possible decision outcome types for Planning Review'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."planning_review_decision" IS 'Links Planning Review decision document with the decision it''s saved to'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."message" IS 'In-app messages'`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `COMMENT ON TABLE "alcs"."planning_review_decision" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."planning_review_decision_outcome_code" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."planning_review_decision_document" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."planning_review_type" IS NULL`, + ); + await queryRunner.query(`COMMENT ON TABLE "alcs"."file_viewed" IS NULL`); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_submission" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_type" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notice_of_intent_document" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."notification_type" IS NULL`, + ); + await queryRunner.query(`COMMENT ON TABLE "alcs"."message" IS NULL`); + } +} diff --git a/services/apps/alcs/src/user/user.entity.ts b/services/apps/alcs/src/user/user.entity.ts index b180269642..65ccaf1d56 100644 --- a/services/apps/alcs/src/user/user.entity.ts +++ b/services/apps/alcs/src/user/user.entity.ts @@ -9,7 +9,9 @@ export class UserSettings { favoriteBoards: string[]; } -@Entity() +@Entity({ + comment: 'Authenticated users and their attributes', +}) export class User extends Base { constructor(data?: Partial) { super(); From 9b47e620ce3869cc1847d4400e068260294ef6c4 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Wed, 3 Apr 2024 10:45:43 -0700 Subject: [PATCH 085/153] Code Review Feedback --- .../application-document/application-document.service.spec.ts | 2 ++ .../application-document/application-document.service.ts | 2 +- .../application-decision-condition.service.spec.ts | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/alcs-frontend/src/app/services/application/application-document/application-document.service.spec.ts b/alcs-frontend/src/app/services/application/application-document/application-document.service.spec.ts index 9bf621a197..f124d7b998 100644 --- a/alcs-frontend/src/app/services/application/application-document/application-document.service.spec.ts +++ b/alcs-frontend/src/app/services/application/application-document/application-document.service.spec.ts @@ -156,5 +156,7 @@ describe('ApplicationDocumentService', () => { }); expect(httpClient.post).toHaveBeenCalledTimes(1); + expect(res).toBeDefined(); + expect(res.uuid).toEqual('1'); }); }); diff --git a/alcs-frontend/src/app/services/application/application-document/application-document.service.ts b/alcs-frontend/src/app/services/application/application-document/application-document.service.ts index 0b8486d93e..a109a0803b 100644 --- a/alcs-frontend/src/app/services/application/application-document/application-document.service.ts +++ b/alcs-frontend/src/app/services/application/application-document/application-document.service.ts @@ -74,7 +74,7 @@ export class ApplicationDocumentService { async update(uuid: string, updateDto: UpdateDocumentDto) { let formData = this.convertDtoToFormData(updateDto); - const res = await firstValueFrom(this.http.post(`${this.url}/${uuid}`, formData)); + const res = await firstValueFrom(this.http.post(`${this.url}/${uuid}`, formData)); this.toastService.showSuccessToast('Document uploaded'); return res; } diff --git a/alcs-frontend/src/app/services/application/decision/application-decision-v2/application-decision-condition/application-decision-condition.service.spec.ts b/alcs-frontend/src/app/services/application/decision/application-decision-v2/application-decision-condition/application-decision-condition.service.spec.ts index 48f91f9ef4..39d2f35d71 100644 --- a/alcs-frontend/src/app/services/application/decision/application-decision-v2/application-decision-condition/application-decision-condition.service.spec.ts +++ b/alcs-frontend/src/app/services/application/decision/application-decision-v2/application-decision-condition/application-decision-condition.service.spec.ts @@ -64,7 +64,7 @@ describe('ApplicationDecisionConditionService', () => { expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); }); - it('should make an http get and show a success toast when fetchPlanNumbers succeeds', async () => { + it('should make an http get for fetchPlanNumbers', async () => { mockHttpClient.get.mockReturnValue( of({ applicationFileNumber: '1', From f760ffbda89aaceb985dc6094b2636ee0ddacfb2 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Wed, 3 Apr 2024 10:57:25 -0700 Subject: [PATCH 086/153] Add some Portal Unit Tests --- .../application-decision.service.spec.ts | 43 +++++++ .../application-document.service.spec.ts | 36 ++++++ .../application-parcel.service.spec.ts | 59 +++++++++- .../document/document.service.spec.ts | 46 +++++++- .../app/services/inbox/inbox.service.spec.ts | 47 ++++++++ .../notice-of-intent-decision.service.spec.ts | 43 +++++++ .../notice-of-intent-document.service.spec.ts | 27 +++++ .../notice-of-intent-parcel.service.spec.ts | 59 +++++++++- .../notification-document.service.spec.ts | 36 ++++++ .../services/public/public.service.spec.ts | 110 +++++++++++++++++- .../app/services/toast/toast.service.spec.ts | 30 ++++- 11 files changed, 531 insertions(+), 5 deletions(-) diff --git a/portal-frontend/src/app/services/application-decision/application-decision.service.spec.ts b/portal-frontend/src/app/services/application-decision/application-decision.service.spec.ts index 7a90342489..8c375081ca 100644 --- a/portal-frontend/src/app/services/application-decision/application-decision.service.spec.ts +++ b/portal-frontend/src/app/services/application-decision/application-decision.service.spec.ts @@ -2,6 +2,7 @@ import { HttpClient } from '@angular/common/http'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { of, throwError } from 'rxjs'; import { ToastService } from '../toast/toast.service'; import { ApplicationDecisionService } from './application-decision.service'; @@ -33,4 +34,46 @@ describe('ApplicationDecisionService', () => { it('should create', () => { expect(service).toBeTruthy(); }); + + it('should make a get requests for getByFileID', async () => { + const mockResponse = {}; + mockHttpClient.get.mockReturnValue(of(mockResponse)); + + const res = await service.getByFileId('fileNumber'); + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(res).toEqual(mockResponse); + }); + + it('should show an error toast if getByFileID fails', async () => { + mockHttpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }) + ); + + await service.getByFileId('fileNumber'); + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + + it('should make a get requests for openFile', async () => { + const mockResponse = {}; + mockHttpClient.get.mockReturnValue(of(mockResponse)); + + const res = await service.openFile('fileNumber'); + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(res).toEqual(mockResponse); + }); + + it('should show an error toast if openFile fails', async () => { + mockHttpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }) + ); + + await service.openFile('documentId'); + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); }); diff --git a/portal-frontend/src/app/services/application-document/application-document.service.spec.ts b/portal-frontend/src/app/services/application-document/application-document.service.spec.ts index 285edba50b..1e11992a1c 100644 --- a/portal-frontend/src/app/services/application-document/application-document.service.spec.ts +++ b/portal-frontend/src/app/services/application-document/application-document.service.spec.ts @@ -53,6 +53,24 @@ describe('ApplicationDocumentService', () => { expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); }); + it('should make a get request for download file', async () => { + mockHttpClient.get.mockReturnValue(of({})); + + await service.downloadFile('fileId'); + + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(mockHttpClient.get.mock.calls[0][0]).toContain('application-document'); + }); + + it('should show an error toast if downloading a file fails', async () => { + mockHttpClient.get.mockReturnValue(throwError(() => ({}))); + + await service.downloadFile('fileId'); + + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + it('should make a delete request for delete file', async () => { mockHttpClient.delete.mockReturnValue(of({})); @@ -88,4 +106,22 @@ describe('ApplicationDocumentService', () => { expect(mockHttpClient.patch).toHaveBeenCalledTimes(1); expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); }); + + it('should make a get request for get by file id', async () => { + mockHttpClient.get.mockReturnValue(of({})); + + await service.getByFileId('fileId'); + + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(mockHttpClient.get.mock.calls[0][0]).toContain('application-document'); + }); + + it('should show an error toast if get by file id fails', async () => { + mockHttpClient.get.mockReturnValue(throwError(() => ({}))); + + await service.getByFileId('fileId'); + + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); }); diff --git a/portal-frontend/src/app/services/application-parcel/application-parcel.service.spec.ts b/portal-frontend/src/app/services/application-parcel/application-parcel.service.spec.ts index d8eda0216a..e9c2afcd58 100644 --- a/portal-frontend/src/app/services/application-parcel/application-parcel.service.spec.ts +++ b/portal-frontend/src/app/services/application-parcel/application-parcel.service.spec.ts @@ -1,10 +1,11 @@ import { HttpClient } from '@angular/common/http'; import { TestBed } from '@angular/core/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { ToastService } from '../toast/toast.service'; import { of, throwError } from 'rxjs'; +import { OverlaySpinnerService } from '../../shared/overlay-spinner/overlay-spinner.service'; import { DocumentService } from '../document/document.service'; +import { ToastService } from '../toast/toast.service'; import { ApplicationParcelUpdateDto } from './application-parcel.dto'; import { ApplicationParcelService } from './application-parcel.service'; @@ -13,12 +14,15 @@ describe('ApplicationParcelService', () => { let mockHttpClient: DeepMocked; let mockDocumentService: DeepMocked; let mockToastService: DeepMocked; + let mockOverlayService: DeepMocked; const mockUuid = 'fake_uuid'; beforeEach(() => { mockHttpClient = createMock(); mockToastService = createMock(); + mockDocumentService = createMock(); + mockOverlayService = createMock(); TestBed.configureTestingModule({ imports: [], @@ -35,6 +39,10 @@ describe('ApplicationParcelService', () => { provide: DocumentService, useValue: mockDocumentService, }, + { + provide: OverlaySpinnerService, + useValue: mockOverlayService, + }, ], }); service = TestBed.inject(ApplicationParcelService); @@ -99,4 +107,53 @@ describe('ApplicationParcelService', () => { expect(mockHttpClient.put).toHaveBeenCalledTimes(1); expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); }); + + it('should call document service for attaching certificate of title', async () => { + mockDocumentService.uploadFile.mockResolvedValue({}); + + await service.attachCertificateOfTitle('fileId', 'parcelUuid', {} as File); + + expect(mockDocumentService.uploadFile).toHaveBeenCalledTimes(1); + expect(mockToastService.showSuccessToast).toHaveBeenCalledTimes(1); + }); + + it('should show an error toast if document service fails', async () => { + mockDocumentService.uploadFile.mockRejectedValue({}); + + await service.attachCertificateOfTitle('fileId', 'parcelUuid', {} as File); + + expect(mockDocumentService.uploadFile).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + + it('should make a delete request and show the overlay for removing all parcels', async () => { + mockDocumentService.uploadFile.mockRejectedValue({}); + mockOverlayService.showSpinner.mockReturnValue(); + mockOverlayService.hideSpinner.mockReturnValue(); + mockHttpClient.delete.mockReturnValue(of({})); + + await service.deleteMany([]); + + expect(mockHttpClient.delete).toHaveBeenCalledTimes(1); + expect(mockOverlayService.showSpinner).toHaveBeenCalledTimes(1); + expect(mockToastService.showSuccessToast).toHaveBeenCalledTimes(1); + expect(mockOverlayService.hideSpinner).toHaveBeenCalledTimes(1); + }); + + it('should show an error toast if document service fails', async () => { + mockDocumentService.uploadFile.mockRejectedValue({}); + mockOverlayService.showSpinner.mockReturnValue(); + mockOverlayService.hideSpinner.mockReturnValue(); + mockHttpClient.delete.mockReturnValue( + throwError(() => { + new Error(''); + }) + ); + + await service.deleteMany([]); + + expect(mockOverlayService.showSpinner).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + expect(mockOverlayService.hideSpinner).toHaveBeenCalledTimes(1); + }); }); diff --git a/portal-frontend/src/app/services/document/document.service.spec.ts b/portal-frontend/src/app/services/document/document.service.spec.ts index 343e6999cf..ac3c1f875d 100644 --- a/portal-frontend/src/app/services/document/document.service.spec.ts +++ b/portal-frontend/src/app/services/document/document.service.spec.ts @@ -1,19 +1,40 @@ +import { HttpClient } from '@angular/common/http'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { of } from 'rxjs'; +import { environment } from '../../../environments/environment'; +import { DOCUMENT_SOURCE } from '../../shared/dto/document.dto'; +import { OverlaySpinnerService } from '../../shared/overlay-spinner/overlay-spinner.service'; import { ToastService } from '../toast/toast.service'; import { DocumentService } from './document.service'; describe('DocumentService', () => { let service: DocumentService; + let mockToastService: DeepMocked; + let mockHttpClient: DeepMocked; + let mockOverlayService: DeepMocked; beforeEach(() => { + mockToastService = createMock(); + mockHttpClient = createMock(); + mockOverlayService = createMock(); + TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [ { provide: ToastService, - useValue: {}, + useValue: mockToastService, + }, + { + provide: HttpClient, + useValue: mockHttpClient, + }, + { + provide: OverlaySpinnerService, + useValue: mockOverlayService, }, ], }); @@ -23,4 +44,27 @@ describe('DocumentService', () => { it('should be created', () => { expect(service).toBeTruthy(); }); + + it('should make a get request for getUploadUrl', async () => { + mockHttpClient.get.mockReturnValue(of({})); + + const res = await service.getUploadUrl('fileNumber', null); + expect(service).toBeTruthy(); + }); + + it('should show a warning toast if the file is too large', async () => { + mockHttpClient.get.mockReturnValue(of({})); + + const res = await service.uploadFile( + 'id', + { + size: environment.maxFileSize + 5, + } as File, + null, + DOCUMENT_SOURCE.APPLICANT, + '' + ); + + expect(mockToastService.showWarningToast).toHaveBeenCalledTimes(1); + }); }); diff --git a/portal-frontend/src/app/services/inbox/inbox.service.spec.ts b/portal-frontend/src/app/services/inbox/inbox.service.spec.ts index 00c9d5e9ff..8009dc6403 100644 --- a/portal-frontend/src/app/services/inbox/inbox.service.spec.ts +++ b/portal-frontend/src/app/services/inbox/inbox.service.spec.ts @@ -131,4 +131,51 @@ describe('InboxService', () => { expect(res).toBeUndefined(); expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); }); + + it('should fetch Notifications advanced search results by AdvancedSearchRequestDto', async () => { + mockHttpClient.post.mockReturnValue(of(mockSearchEntityResult)); + + const res = await service.searchNotifications(mockSearchRequestDto); + + expect(mockHttpClient.post).toHaveBeenCalledTimes(1); + expect(res).toBeDefined(); + expect(res?.total).toEqual(0); + expect(res?.data).toEqual([]); + }); + + it('should show an error toast message if Notification advanced search fails', async () => { + mockHttpClient.post.mockReturnValue( + throwError(() => { + new Error(''); + }) + ); + + const res = await service.searchNotifications(mockSearchRequestDto); + + expect(mockHttpClient.post).toHaveBeenCalledTimes(1); + expect(res).toBeUndefined(); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + + it('should call get for listing statuses', async () => { + mockHttpClient.get.mockReturnValue(of([])); + + const res = await service.listStatuses(); + + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(res).toBeDefined(); + }); + + it('should show an error toast message if listing statuses fails', async () => { + mockHttpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }) + ); + + const res = await service.listStatuses(); + + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); }); diff --git a/portal-frontend/src/app/services/notice-of-intent-decision/notice-of-intent-decision.service.spec.ts b/portal-frontend/src/app/services/notice-of-intent-decision/notice-of-intent-decision.service.spec.ts index 07166dd8d9..e9ffef75d4 100644 --- a/portal-frontend/src/app/services/notice-of-intent-decision/notice-of-intent-decision.service.spec.ts +++ b/portal-frontend/src/app/services/notice-of-intent-decision/notice-of-intent-decision.service.spec.ts @@ -2,6 +2,7 @@ import { HttpClient } from '@angular/common/http'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { of, throwError } from 'rxjs'; import { ToastService } from '../toast/toast.service'; import { NoticeOfIntentDecisionService } from './notice-of-intent-decision.service'; @@ -33,4 +34,46 @@ describe('NoticeOfIntentDecisionService', () => { it('should create', () => { expect(service).toBeTruthy(); }); + + it('should make a get requests for getByFileID', async () => { + const mockResponse = {}; + mockHttpClient.get.mockReturnValue(of(mockResponse)); + + const res = await service.getByFileId('fileNumber'); + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(res).toEqual(mockResponse); + }); + + it('should show an error toast if getByFileID fails', async () => { + mockHttpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }) + ); + + await service.getByFileId('fileNumber'); + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + + it('should make a get requests for openFile', async () => { + const mockResponse = {}; + mockHttpClient.get.mockReturnValue(of(mockResponse)); + + const res = await service.openFile('fileNumber'); + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(res).toEqual(mockResponse); + }); + + it('should show an error toast if openFile fails', async () => { + mockHttpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }) + ); + + await service.openFile('documentId'); + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); }); diff --git a/portal-frontend/src/app/services/notice-of-intent-document/notice-of-intent-document.service.spec.ts b/portal-frontend/src/app/services/notice-of-intent-document/notice-of-intent-document.service.spec.ts index f98fff51e7..eede416588 100644 --- a/portal-frontend/src/app/services/notice-of-intent-document/notice-of-intent-document.service.spec.ts +++ b/portal-frontend/src/app/services/notice-of-intent-document/notice-of-intent-document.service.spec.ts @@ -62,6 +62,15 @@ describe('NoticeOfIntentDocumentService', () => { expect(mockHttpClient.get.mock.calls[0][0]).toContain('notice-of-intent-document'); }); + it('should show an error toast if downloading a file fails', async () => { + mockHttpClient.get.mockReturnValue(throwError(() => ({}))); + + await service.downloadFile('fileId'); + + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + it('should make a delete request for delete file', async () => { mockHttpClient.delete.mockReturnValue(of({})); @@ -115,4 +124,22 @@ describe('NoticeOfIntentDocumentService', () => { expect(mockHttpClient.post).toHaveBeenCalledTimes(1); expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); }); + + it('should make a get request for getByFileId', async () => { + mockHttpClient.get.mockReturnValue(of({})); + + await service.getByFileId('fileId'); + + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(mockHttpClient.get.mock.calls[0][0]).toContain('notice-of-intent-document'); + }); + + it('should show an error toast if getByFileId fails', async () => { + mockHttpClient.get.mockReturnValue(throwError(() => ({}))); + + await service.getByFileId('fileId'); + + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); }); diff --git a/portal-frontend/src/app/services/notice-of-intent-parcel/notice-of-intent-parcel.service.spec.ts b/portal-frontend/src/app/services/notice-of-intent-parcel/notice-of-intent-parcel.service.spec.ts index 735991e006..e5dc086569 100644 --- a/portal-frontend/src/app/services/notice-of-intent-parcel/notice-of-intent-parcel.service.spec.ts +++ b/portal-frontend/src/app/services/notice-of-intent-parcel/notice-of-intent-parcel.service.spec.ts @@ -1,10 +1,11 @@ import { HttpClient } from '@angular/common/http'; import { TestBed } from '@angular/core/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { ToastService } from '../toast/toast.service'; import { of, throwError } from 'rxjs'; +import { OverlaySpinnerService } from '../../shared/overlay-spinner/overlay-spinner.service'; import { DocumentService } from '../document/document.service'; +import { ToastService } from '../toast/toast.service'; import { NoticeOfIntentParcelUpdateDto } from './notice-of-intent-parcel.dto'; import { NoticeOfIntentParcelService } from './notice-of-intent-parcel.service'; @@ -13,12 +14,15 @@ describe('NoticeOfIntentParcelService', () => { let mockHttpClient: DeepMocked; let mockDocumentService: DeepMocked; let mockToastService: DeepMocked; + let mockOverlayService: DeepMocked; const mockUuid = 'fake_uuid'; beforeEach(() => { mockHttpClient = createMock(); mockToastService = createMock(); + mockDocumentService = createMock(); + mockOverlayService = createMock(); TestBed.configureTestingModule({ imports: [], @@ -35,6 +39,10 @@ describe('NoticeOfIntentParcelService', () => { provide: DocumentService, useValue: mockDocumentService, }, + { + provide: OverlaySpinnerService, + useValue: mockOverlayService, + }, ], }); service = TestBed.inject(NoticeOfIntentParcelService); @@ -99,4 +107,53 @@ describe('NoticeOfIntentParcelService', () => { expect(mockHttpClient.put).toHaveBeenCalledTimes(1); expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); }); + + it('should call document service for attaching certificate of title', async () => { + mockDocumentService.uploadFile.mockResolvedValue({}); + + await service.attachCertificateOfTitle('fileId', 'parcelUuid', {} as File); + + expect(mockDocumentService.uploadFile).toHaveBeenCalledTimes(1); + expect(mockToastService.showSuccessToast).toHaveBeenCalledTimes(1); + }); + + it('should show an error toast if document service fails', async () => { + mockDocumentService.uploadFile.mockRejectedValue({}); + + await service.attachCertificateOfTitle('fileId', 'parcelUuid', {} as File); + + expect(mockDocumentService.uploadFile).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + + it('should make a delete request and show the overlay for removing all parcels', async () => { + mockDocumentService.uploadFile.mockRejectedValue({}); + mockOverlayService.showSpinner.mockReturnValue(); + mockOverlayService.hideSpinner.mockReturnValue(); + mockHttpClient.delete.mockReturnValue(of({})); + + await service.deleteMany([]); + + expect(mockHttpClient.delete).toHaveBeenCalledTimes(1); + expect(mockOverlayService.showSpinner).toHaveBeenCalledTimes(1); + expect(mockToastService.showSuccessToast).toHaveBeenCalledTimes(1); + expect(mockOverlayService.hideSpinner).toHaveBeenCalledTimes(1); + }); + + it('should show an error toast if document service fails', async () => { + mockDocumentService.uploadFile.mockRejectedValue({}); + mockOverlayService.showSpinner.mockReturnValue(); + mockOverlayService.hideSpinner.mockReturnValue(); + mockHttpClient.delete.mockReturnValue( + throwError(() => { + new Error(''); + }) + ); + + await service.deleteMany([]); + + expect(mockOverlayService.showSpinner).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + expect(mockOverlayService.hideSpinner).toHaveBeenCalledTimes(1); + }); }); diff --git a/portal-frontend/src/app/services/notification-document/notification-document.service.spec.ts b/portal-frontend/src/app/services/notification-document/notification-document.service.spec.ts index 6f57fdca49..b53f8737f8 100644 --- a/portal-frontend/src/app/services/notification-document/notification-document.service.spec.ts +++ b/portal-frontend/src/app/services/notification-document/notification-document.service.spec.ts @@ -53,6 +53,24 @@ describe('NotificationDocumentService', () => { expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); }); + it('should make a get request for download file', async () => { + mockHttpClient.get.mockReturnValue(of({})); + + await service.downloadFile('fileId'); + + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(mockHttpClient.get.mock.calls[0][0]).toContain('notification-document'); + }); + + it('should show an error toast if downloading a file fails', async () => { + mockHttpClient.get.mockReturnValue(throwError(() => ({}))); + + await service.downloadFile('fileId'); + + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + it('should make a delete request for delete file', async () => { mockHttpClient.delete.mockReturnValue(of({})); @@ -88,4 +106,22 @@ describe('NotificationDocumentService', () => { expect(mockHttpClient.patch).toHaveBeenCalledTimes(1); expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); }); + + it('should make a get request for get by file id', async () => { + mockHttpClient.get.mockReturnValue(of({})); + + await service.getByFileId('fileId'); + + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(mockHttpClient.get.mock.calls[0][0]).toContain('notification-document'); + }); + + it('should show an error toast if get by file id fails', async () => { + mockHttpClient.get.mockReturnValue(throwError(() => ({}))); + + await service.getByFileId('fileId'); + + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); }); diff --git a/portal-frontend/src/app/services/public/public.service.spec.ts b/portal-frontend/src/app/services/public/public.service.spec.ts index 574b4060d6..a99bdda898 100644 --- a/portal-frontend/src/app/services/public/public.service.spec.ts +++ b/portal-frontend/src/app/services/public/public.service.spec.ts @@ -1,7 +1,7 @@ import { HttpClient } from '@angular/common/http'; import { TestBed } from '@angular/core/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { of } from 'rxjs'; +import { of, throwError } from 'rxjs'; import { ToastService } from '../toast/toast.service'; import { PublicService } from './public.service'; @@ -42,6 +42,18 @@ describe('PublicService', () => { expect(res).toBeDefined(); }); + it('should show an error toast if getApplication fails', async () => { + mockHttpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }) + ); + + await service.getApplication('fileId'); + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + it('should call get for opening application files', async () => { mockHttpClient.get.mockReturnValue(of({})); @@ -51,6 +63,18 @@ describe('PublicService', () => { expect(res).toBeDefined(); }); + it('should show an error toast if getApplicationOpenFileUrl fails', async () => { + mockHttpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }) + ); + + await service.getApplicationOpenFileUrl('fileId', 'documentUuid'); + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + it('should call get for download application files', async () => { mockHttpClient.get.mockReturnValue(of({})); @@ -60,6 +84,18 @@ describe('PublicService', () => { expect(res).toBeDefined(); }); + it('should show an error toast if getApplicationDownloadFileUrl fails', async () => { + mockHttpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }) + ); + + await service.getApplicationDownloadFileUrl('fileId', 'documentUuid'); + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + it('should call get for loading Notice of Intent', async () => { mockHttpClient.get.mockReturnValue(of({})); @@ -69,6 +105,18 @@ describe('PublicService', () => { expect(res).toBeDefined(); }); + it('should show an error toast if getNoticeOfIntent fails', async () => { + mockHttpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }) + ); + + await service.getNoticeOfIntent('fileId'); + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + it('should call get for opening Notice of Intent files', async () => { mockHttpClient.get.mockReturnValue(of({})); @@ -78,6 +126,18 @@ describe('PublicService', () => { expect(res).toBeDefined(); }); + it('should show an error toast if getNoticeOfIntentOpenFileUrl fails', async () => { + mockHttpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }) + ); + + await service.getNoticeOfIntentOpenFileUrl('fileId', 'documentUuid'); + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + it('should call get for download Notice of Intent files', async () => { mockHttpClient.get.mockReturnValue(of({})); @@ -87,6 +147,18 @@ describe('PublicService', () => { expect(res).toBeDefined(); }); + it('should show an error toast if getNoticeOfIntentDownloadFileUrl fails', async () => { + mockHttpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }) + ); + + await service.getNoticeOfIntentDownloadFileUrl('fileId', 'documentUuid'); + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + it('should call get for loading Notification', async () => { mockHttpClient.get.mockReturnValue(of({})); @@ -96,6 +168,18 @@ describe('PublicService', () => { expect(res).toBeDefined(); }); + it('should show an error toast if getNotification fails', async () => { + mockHttpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }) + ); + + await service.getNotification('fileId'); + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + it('should call get for opening Notification files', async () => { mockHttpClient.get.mockReturnValue(of({})); @@ -105,6 +189,18 @@ describe('PublicService', () => { expect(res).toBeDefined(); }); + it('should show an error toast if getNotificationOpenFileUrl fails', async () => { + mockHttpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }) + ); + + await service.getNotificationOpenFileUrl('fileId', 'documentUuid'); + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); + it('should call get for download Notification files', async () => { mockHttpClient.get.mockReturnValue(of({})); @@ -113,4 +209,16 @@ describe('PublicService', () => { expect(mockHttpClient.get).toHaveBeenCalledTimes(1); expect(res).toBeDefined(); }); + + it('should show an error toast if getNotificationDownloadFileUrl fails', async () => { + mockHttpClient.get.mockReturnValue( + throwError(() => { + new Error(''); + }) + ); + + await service.getNotificationDownloadFileUrl('fileId', 'documentUuid'); + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1); + }); }); diff --git a/portal-frontend/src/app/services/toast/toast.service.spec.ts b/portal-frontend/src/app/services/toast/toast.service.spec.ts index 7216bbbb44..dce85dbb1f 100644 --- a/portal-frontend/src/app/services/toast/toast.service.spec.ts +++ b/portal-frontend/src/app/services/toast/toast.service.spec.ts @@ -1,14 +1,24 @@ import { TestBed } from '@angular/core/testing'; -import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ToastService } from './toast.service'; describe('ToastService', () => { let service: ToastService; + let mockSnackBarService: DeepMocked; beforeEach(() => { + mockSnackBarService = createMock(); + TestBed.configureTestingModule({ imports: [MatSnackBarModule], + providers: [ + { + provide: MatSnackBar, + useValue: mockSnackBarService, + }, + ], }); service = TestBed.inject(ToastService); }); @@ -16,4 +26,22 @@ describe('ToastService', () => { it('should be created', () => { expect(service).toBeTruthy(); }); + + it('should call snackBar for showSuccessToast', () => { + mockSnackBarService.open.mockReturnValue({} as any); + const res = service.showSuccessToast('text'); + expect(mockSnackBarService.open).toHaveBeenCalledTimes(1); + }); + + it('should call snackBar for showWarningToast', () => { + mockSnackBarService.open.mockReturnValue({} as any); + const res = service.showWarningToast('text'); + expect(mockSnackBarService.open).toHaveBeenCalledTimes(1); + }); + + it('should call snackBar for showErrorToast', () => { + mockSnackBarService.open.mockReturnValue({} as any); + const res = service.showErrorToast('text'); + expect(mockSnackBarService.open).toHaveBeenCalledTimes(1); + }); }); From 0ff1c82414d95b80845ed200f67ad9432fd23ecf Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Wed, 3 Apr 2024 11:46:39 -0700 Subject: [PATCH 087/153] Code Review Feedback --- .../src/app/services/document/document.service.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/portal-frontend/src/app/services/document/document.service.spec.ts b/portal-frontend/src/app/services/document/document.service.spec.ts index ac3c1f875d..023bee8459 100644 --- a/portal-frontend/src/app/services/document/document.service.spec.ts +++ b/portal-frontend/src/app/services/document/document.service.spec.ts @@ -49,7 +49,7 @@ describe('DocumentService', () => { mockHttpClient.get.mockReturnValue(of({})); const res = await service.getUploadUrl('fileNumber', null); - expect(service).toBeTruthy(); + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); }); it('should show a warning toast if the file is too large', async () => { From 0498ba518d759d5f84b909bce2e4c374a1ea125c Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Wed, 3 Apr 2024 12:49:58 -0700 Subject: [PATCH 088/153] Add styling for hover and selected rows for Evidentiary Records --- .../evidentiary-record.component.html | 5 +++++ .../evidentiary-record.component.scss | 19 ++++++++++++++++++- .../evidentiary-record.component.ts | 9 +++++++++ .../application-document.component.html | 5 +++++ .../application-document.component.scss | 16 ++++++++++++++++ .../application-document.component.ts | 9 +++++++++ 6 files changed, 62 insertions(+), 1 deletion(-) diff --git a/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.html b/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.html index b6928902a4..85d323e5e9 100644 --- a/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.html +++ b/alcs-frontend/src/app/features/planning-review/review/evidentiary-record/evidentiary-record.component.html @@ -70,7 +70,12 @@

{{ tableTitle }}

- - + - + - - - + - - + - - + - - + + - +
File ID + File ID {{ element.fileNumber | emptyColumn }} TypeType - + + Document Name + Document Name {{ element.documentName | emptyColumn }} Local/First Nation Government + Local/First Nation Government {{ element.localGovernmentName | emptyColumn }} Status + Status
-
No non-applications found.
+
No planning reviews found.
Please adjust criteria and try again.
@@ -69,7 +69,7 @@
-
-
No non-applications found.
+
+
No planning reviews found.
Please adjust criteria and try again.
diff --git a/alcs-frontend/src/app/services/application/application-decision-maker/application-decision-maker.service.ts b/alcs-frontend/src/app/services/application/application-decision-maker/application-decision-maker.service.ts index 891a034e32..cccb76831d 100644 --- a/alcs-frontend/src/app/services/application/application-decision-maker/application-decision-maker.service.ts +++ b/alcs-frontend/src/app/services/application/application-decision-maker/application-decision-maker.service.ts @@ -2,8 +2,8 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { firstValueFrom } from 'rxjs'; import { environment } from '../../../../environments/environment'; -import { DecisionMakerDto } from '../decision/application-decision-v1/application-decision.dto'; import { ToastService } from '../../toast/toast.service'; +import { DecisionMakerDto } from '../decision/application-decision-v2/application-decision.dto'; @Injectable({ providedIn: 'root', @@ -11,7 +11,10 @@ import { ToastService } from '../../toast/toast.service'; export class ApplicationDecisionMakerService { private url = `${environment.apiUrl}/decision-maker`; - constructor(private http: HttpClient, private toastService: ToastService) {} + constructor( + private http: HttpClient, + private toastService: ToastService, + ) {} async fetch() { try { diff --git a/alcs-frontend/src/app/services/application/application-modification/application-modification.dto.ts b/alcs-frontend/src/app/services/application/application-modification/application-modification.dto.ts index c21593c352..3901d532d9 100644 --- a/alcs-frontend/src/app/services/application/application-modification/application-modification.dto.ts +++ b/alcs-frontend/src/app/services/application/application-modification/application-modification.dto.ts @@ -3,7 +3,7 @@ import { CardDto } from '../../card/card.dto'; import { ApplicationRegionDto, ApplicationTypeDto } from '../application-code.dto'; import { ApplicationLocalGovernmentDto } from '../application-local-government/application-local-government.dto'; import { ApplicationDecisionMeetingDto } from '../application.dto'; -import { ApplicationDecisionDto } from '../decision/application-decision-v1/application-decision.dto'; +import { ApplicationDecisionDto } from '../decision/application-decision-v2/application-decision.dto'; export interface ApplicationModificationCreateDto { applicationFileNumber: string; diff --git a/alcs-frontend/src/app/services/application/application-reconsideration/application-reconsideration.dto.ts b/alcs-frontend/src/app/services/application/application-reconsideration/application-reconsideration.dto.ts index 00a488f758..8f3fdd7336 100644 --- a/alcs-frontend/src/app/services/application/application-reconsideration/application-reconsideration.dto.ts +++ b/alcs-frontend/src/app/services/application/application-reconsideration/application-reconsideration.dto.ts @@ -3,12 +3,13 @@ import { CardDto } from '../../card/card.dto'; import { ApplicationRegionDto, ApplicationTypeDto } from '../application-code.dto'; import { ApplicationLocalGovernmentDto } from '../application-local-government/application-local-government.dto'; import { ApplicationDecisionMeetingDto } from '../application.dto'; -import { ApplicationDecisionDto } from '../decision/application-decision-v1/application-decision.dto'; +import { ApplicationDecisionDto } from '../decision/application-decision-v2/application-decision.dto'; export const enum RECONSIDERATION_TYPE { T_33 = '33', T_33_1 = '33.1', } + export interface ReconsiderationTypeDto extends BaseCodeDto { backgroundColor: string; textColor: string; diff --git a/alcs-frontend/src/app/services/application/application.service.ts b/alcs-frontend/src/app/services/application/application.service.ts index feeddfa362..8b927b0ba7 100644 --- a/alcs-frontend/src/app/services/application/application.service.ts +++ b/alcs-frontend/src/app/services/application/application.service.ts @@ -16,13 +16,10 @@ import { ApplicationDto, CreateApplicationDto, UpdateApplicationDto } from './ap providedIn: 'root', }) export class ApplicationService { - constructor(private http: HttpClient, private toastService: ToastService) {} - - public $cardStatuses = new BehaviorSubject([]); - public $applicationTypes = new BehaviorSubject([]); - public $applicationRegions = new BehaviorSubject([]); - public $applicationStatuses = new BehaviorSubject([]); - + $cardStatuses = new BehaviorSubject([]); + $applicationTypes = new BehaviorSubject([]); + $applicationRegions = new BehaviorSubject([]); + $applicationStatuses = new BehaviorSubject([]); private baseUrl = `${environment.apiUrl}/application`; private statuses: CardStatusDto[] = []; private types: ApplicationTypeDto[] = []; @@ -30,6 +27,11 @@ export class ApplicationService { private applicationStatuses: ApplicationStatusDto[] = []; private isInitialized = false; + constructor( + private http: HttpClient, + private toastService: ToastService, + ) {} + async fetchApplication(fileNumber: string): Promise { await this.setup(); return firstValueFrom(this.http.get(`${this.baseUrl}/${fileNumber}`)); @@ -41,9 +43,7 @@ export class ApplicationService { return await firstValueFrom(this.http.post(`${this.baseUrl}`, application)); } catch (e) { if (e instanceof HttpErrorResponse && e.status === 400) { - this.toastService.showErrorToast( - `Covenant/Application/NOI with File ID ${application.fileNumber} already exists` - ); + this.toastService.showErrorToast(`Application/NOI with File ID ${application.fileNumber} already exists`); } else { this.toastService.showErrorToast('Failed to create Application'); } @@ -86,21 +86,6 @@ export class ApplicationService { } } - private async fetchCodes() { - const codes = await firstValueFrom(this.http.get(`${environment.apiUrl}/code`)); - this.statuses = codes.status; - this.$cardStatuses.next(this.statuses); - - this.types = codes.type; - this.$applicationTypes.next(this.types); - - this.regions = codes.region; - this.$applicationRegions.next(this.regions); - - this.applicationStatuses = codes.applicationStatusType; - this.$applicationStatuses.next(this.applicationStatuses); - } - async cancelApplication(fileNumber: string) { await this.setup(); try { @@ -120,4 +105,19 @@ export class ApplicationService { } return; } + + private async fetchCodes() { + const codes = await firstValueFrom(this.http.get(`${environment.apiUrl}/code`)); + this.statuses = codes.status; + this.$cardStatuses.next(this.statuses); + + this.types = codes.type; + this.$applicationTypes.next(this.types); + + this.regions = codes.region; + this.$applicationRegions.next(this.regions); + + this.applicationStatuses = codes.applicationStatusType; + this.$applicationStatuses.next(this.applicationStatuses); + } } diff --git a/alcs-frontend/src/app/services/application/decision/application-decision-v1/application-decision.service.spec.ts b/alcs-frontend/src/app/services/application/decision/application-decision-v1/application-decision.service.spec.ts deleted file mode 100644 index 3f5f83ff00..0000000000 --- a/alcs-frontend/src/app/services/application/decision/application-decision-v1/application-decision.service.spec.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { HttpClient } from '@angular/common/http'; -import { TestBed } from '@angular/core/testing'; -import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { of, throwError } from 'rxjs'; -import { environment } from '../../../../../environments/environment'; -import { ToastService } from '../../../toast/toast.service'; -import { ApplicationDecisionService } from './application-decision.service'; - -describe('ApplicationMeetingService', () => { - let service: ApplicationDecisionService; - let httpClient: DeepMocked; - let toastService: DeepMocked; - - beforeEach(() => { - httpClient = createMock(); - toastService = createMock(); - - TestBed.configureTestingModule({ - providers: [ - { - provide: HttpClient, - useValue: httpClient, - }, - { - provide: ToastService, - useValue: toastService, - }, - ], - }); - service = TestBed.inject(ApplicationDecisionService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); - - it('should fetch and return an applicationDto', async () => { - httpClient.get.mockReturnValue( - of([ - { - applicationFileNumber: '1', - }, - ]) - ); - - const res = await service.fetchByApplication('1'); - - expect(res.length).toEqual(1); - expect(res[0].applicationFileNumber).toEqual('1'); - }); - - it('should show a toast message if fetch fails', async () => { - httpClient.get.mockReturnValue( - throwError(() => { - new Error(''); - }) - ); - - const res = await service.fetchByApplication('1'); - - expect(res.length).toEqual(0); - expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); - }); - - it('should make an http patch and show a success toast when updating', async () => { - httpClient.patch.mockReturnValue( - of({ - applicationFileNumber: '1', - }) - ); - - await service.update('1', {}); - - expect(httpClient.patch).toHaveBeenCalledTimes(1); - expect(toastService.showSuccessToast).toHaveBeenCalledTimes(1); - }); - - it('should show a toast message if update fails', async () => { - httpClient.patch.mockReturnValue( - throwError(() => { - new Error(''); - }) - ); - - try { - await service.update('1', {}); - } catch (e) { - //OM NOM NOM - } - - expect(httpClient.patch).toHaveBeenCalledTimes(1); - expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); - }); - - it('should make an http post and show a success toast when creating', async () => { - httpClient.post.mockReturnValue( - of({ - applicationFileNumber: '1', - }) - ); - - await service.create({ - applicationFileNumber: '', - chairReviewRequired: false, - date: 0, - modifiesUuid: '', - outcomeCode: '', - reconsidersUuid: '', - resolutionNumber: 0, - resolutionYear: 0, - }); - - expect(httpClient.post).toHaveBeenCalledTimes(1); - expect(toastService.showSuccessToast).toHaveBeenCalledTimes(1); - }); - - it('should show a toast message if create fails', async () => { - httpClient.post.mockReturnValue( - throwError(() => { - new Error(''); - }) - ); - - try { - await service.create({ - applicationFileNumber: '', - chairReviewRequired: false, - date: 0, - modifiesUuid: '', - outcomeCode: '', - reconsidersUuid: '', - resolutionNumber: 0, - resolutionYear: 0, - }); - } catch (e) { - //OM NOM NOM - } - - expect(httpClient.post).toHaveBeenCalledTimes(1); - expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); - }); - - it('should make an http delete and show a success toast', async () => { - httpClient.delete.mockReturnValue( - of({ - applicationFileNumber: '1', - }) - ); - - await service.delete(''); - - expect(httpClient.delete).toHaveBeenCalledTimes(1); - expect(toastService.showSuccessToast).toHaveBeenCalledTimes(1); - }); - - it('should show a toast message if delete fails', async () => { - httpClient.delete.mockReturnValue( - throwError(() => { - new Error(''); - }) - ); - - try { - await service.delete(''); - } catch (e) { - //OM NOM NOM - } - - expect(httpClient.delete).toHaveBeenCalledTimes(1); - expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); - }); - - it('should show a toast warning when uploading a file thats too large', async () => { - const file = createMock(); - Object.defineProperty(file, 'size', { value: environment.maxFileSize + 1 }); - - await service.uploadFile('', file); - - expect(toastService.showWarningToast).toHaveBeenCalledTimes(1); - expect(httpClient.post).toHaveBeenCalledTimes(0); - }); - - it('should make an http delete when deleting a file', async () => { - httpClient.delete.mockReturnValue( - of({ - applicationFileNumber: '1', - }) - ); - - await service.deleteFile('', ''); - - expect(httpClient.delete).toHaveBeenCalledTimes(1); - }); -}); diff --git a/alcs-frontend/src/app/services/application/decision/application-decision-v1/application-decision.service.ts b/alcs-frontend/src/app/services/application/decision/application-decision-v1/application-decision.service.ts deleted file mode 100644 index 7947a987bb..0000000000 --- a/alcs-frontend/src/app/services/application/decision/application-decision-v1/application-decision.service.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { HttpClient, HttpErrorResponse } from '@angular/common/http'; -import { Injectable } from '@angular/core'; -import { firstValueFrom } from 'rxjs'; -import { environment } from '../../../../../environments/environment'; -import { downloadFileFromUrl, openFileInline } from '../../../../shared/utils/file'; -import { verifyFileSize } from '../../../../shared/utils/file-size-checker'; -import { ToastService } from '../../../toast/toast.service'; -import { - ApplicationDecisionDto, - CeoCriterionDto, - CreateApplicationDecisionDto, - DecisionMakerDto, - DecisionOutcomeCodeDto, - UpdateApplicationDecisionDto, -} from './application-decision.dto'; - -@Injectable({ - providedIn: 'root', -}) -export class ApplicationDecisionService { - private url = `${environment.apiUrl}/application-decision`; - - constructor(private http: HttpClient, private toastService: ToastService) {} - - async fetchByApplication(fileNumber: string) { - let decisions: ApplicationDecisionDto[] = []; - try { - decisions = await firstValueFrom( - this.http.get(`${this.url}/application/${fileNumber}`) - ); - } catch (err) { - this.toastService.showErrorToast('Failed to fetch decisions'); - } - return decisions; - } - - async fetchCodes() { - let outcomes: DecisionOutcomeCodeDto[] = []; - let decisionMakers: DecisionMakerDto[] = []; - let ceoCriterion: CeoCriterionDto[] = []; - try { - const res = await firstValueFrom( - this.http.get<{ - outcomes: DecisionOutcomeCodeDto[]; - decisionMakers: DecisionMakerDto[]; - ceoCriterion: CeoCriterionDto[]; - }>(`${this.url}/codes`) - ); - outcomes = res.outcomes; - decisionMakers = res.decisionMakers; - ceoCriterion = res.ceoCriterion; - } catch (err) { - this.toastService.showErrorToast('Failed to fetch decisions'); - } - return { - outcomes, - decisionMakers, - ceoCriterion, - }; - } - - async update(uuid: string, data: UpdateApplicationDecisionDto) { - try { - const res = await firstValueFrom(this.http.patch(`${this.url}/${uuid}`, data)); - this.toastService.showSuccessToast('Decision updated'); - return res; - } catch (e) { - if (e instanceof HttpErrorResponse && e.status === 400 && e.error?.message) { - this.toastService.showErrorToast(e.error.message); - } else { - this.toastService.showErrorToast('Failed to update decision'); - } - throw e; - } - } - - async create(decision: CreateApplicationDecisionDto) { - try { - const res = await firstValueFrom(this.http.post(`${this.url}`, decision)); - this.toastService.showSuccessToast('Decision created'); - return res; - } catch (e) { - if (e instanceof HttpErrorResponse && e.status === 400 && e.error?.message) { - this.toastService.showErrorToast(e.error.message); - } else { - this.toastService.showErrorToast(`Failed to create decision`); - } - throw e; - } - } - - async delete(uuid: string) { - try { - await firstValueFrom(this.http.delete(`${this.url}/${uuid}`)); - this.toastService.showSuccessToast('Decision deleted'); - } catch (err) { - this.toastService.showErrorToast('Failed to delete decision'); - } - } - - async uploadFile(decisionUuid: string, file: File) { - const isValidSize = verifyFileSize(file, this.toastService); - if (!isValidSize) { - return; - } - - let formData: FormData = new FormData(); - formData.append('file', file, file.name); - const res = await firstValueFrom(this.http.post(`${this.url}/${decisionUuid}/file`, formData)); - this.toastService.showSuccessToast('Document uploaded'); - return res; - } - - async downloadFile(decisionUuid: string, documentUuid: string, fileName: string, isInline = true) { - const url = `${this.url}/${decisionUuid}/file/${documentUuid}`; - const finalUrl = isInline ? `${url}/open` : `${url}/download`; - const data = await firstValueFrom(this.http.get<{ url: string }>(finalUrl)); - if (isInline) { - openFileInline(data.url, fileName); - } else { - downloadFileFromUrl(data.url, fileName); - } - } - - async deleteFile(decisionUuid: string, documentUuid: string) { - const url = `${this.url}/${decisionUuid}/file/${documentUuid}`; - return await firstValueFrom(this.http.delete<{ url: string }>(url)); - } -} diff --git a/alcs-frontend/src/app/services/application/decision/application-decision-v1/application-decision.dto.ts b/alcs-frontend/src/app/services/application/decision/application-decision-v2/application-decision.dto.ts similarity index 100% rename from alcs-frontend/src/app/services/application/decision/application-decision-v1/application-decision.dto.ts rename to alcs-frontend/src/app/services/application/decision/application-decision-v2/application-decision.dto.ts diff --git a/alcs-frontend/src/app/services/card/card-subtask/card-subtask.dto.ts b/alcs-frontend/src/app/services/card/card-subtask/card-subtask.dto.ts index 53ff23d830..00752979ed 100644 --- a/alcs-frontend/src/app/services/card/card-subtask/card-subtask.dto.ts +++ b/alcs-frontend/src/app/services/card/card-subtask/card-subtask.dto.ts @@ -28,7 +28,7 @@ export interface HomepageSubtaskDto extends CardSubtaskDto { activeDays?: number; paused: boolean; appType?: ApplicationTypeDto; - parentType: 'application' | 'reconsideration' | 'covenant' | 'modification' | 'planning-review' | 'notification'; + parentType: 'application' | 'reconsideration' | 'modification' | 'planning-review' | 'notification'; } export enum CARD_SUBTASK_TYPE { diff --git a/alcs-frontend/src/app/services/ceo-criterion/ceo-criterion.service.ts b/alcs-frontend/src/app/services/ceo-criterion/ceo-criterion.service.ts index eccdff3ba0..31ec4d9d8d 100644 --- a/alcs-frontend/src/app/services/ceo-criterion/ceo-criterion.service.ts +++ b/alcs-frontend/src/app/services/ceo-criterion/ceo-criterion.service.ts @@ -2,7 +2,7 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { firstValueFrom } from 'rxjs'; import { environment } from '../../../environments/environment'; -import { CeoCriterionDto } from '../application/decision/application-decision-v1/application-decision.dto'; +import { CeoCriterionDto } from '../application/decision/application-decision-v2/application-decision.dto'; import { ToastService } from '../toast/toast.service'; @Injectable({ @@ -11,7 +11,10 @@ import { ToastService } from '../toast/toast.service'; export class CeoCriterionService { private url = `${environment.apiUrl}/ceo-criterion`; - constructor(private http: HttpClient, private toastService: ToastService) {} + constructor( + private http: HttpClient, + private toastService: ToastService, + ) {} async fetch() { try { diff --git a/alcs-frontend/src/app/services/noi-subtype/noi-subtype.service.ts b/alcs-frontend/src/app/services/noi-subtype/noi-subtype.service.ts index 334ec463ed..f0724c8941 100644 --- a/alcs-frontend/src/app/services/noi-subtype/noi-subtype.service.ts +++ b/alcs-frontend/src/app/services/noi-subtype/noi-subtype.service.ts @@ -2,7 +2,7 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { firstValueFrom } from 'rxjs'; import { environment } from '../../../environments/environment'; -import { CeoCriterionDto } from '../application/decision/application-decision-v1/application-decision.dto'; +import { CeoCriterionDto } from '../application/decision/application-decision-v2/application-decision.dto'; import { NoticeOfIntentSubtypeDto } from '../notice-of-intent/notice-of-intent.dto'; import { ToastService } from '../toast/toast.service'; @@ -12,7 +12,10 @@ import { ToastService } from '../toast/toast.service'; export class NoiSubtypeService { private url = `${environment.apiUrl}/noi-subtype`; - constructor(private http: HttpClient, private toastService: ToastService) {} + constructor( + private http: HttpClient, + private toastService: ToastService, + ) {} async fetch() { try { diff --git a/alcs-frontend/src/app/services/notice-of-intent/decision-v2/notice-of-intent-decision-component/notice-of-intent-decision-component.service.ts b/alcs-frontend/src/app/services/notice-of-intent/decision-v2/notice-of-intent-decision-component/notice-of-intent-decision-component.service.ts index 76b9081acc..7af7abcb4b 100644 --- a/alcs-frontend/src/app/services/notice-of-intent/decision-v2/notice-of-intent-decision-component/notice-of-intent-decision-component.service.ts +++ b/alcs-frontend/src/app/services/notice-of-intent/decision-v2/notice-of-intent-decision-component/notice-of-intent-decision-component.service.ts @@ -6,7 +6,7 @@ import { ToastService } from '../../../toast/toast.service'; import { NoticeOfIntentDecisionComponentDto, UpdateNoticeOfIntentDecisionComponentDto, -} from '../../decision/notice-of-intent-decision.dto'; +} from '../notice-of-intent-decision.dto'; @Injectable({ providedIn: 'root', @@ -14,12 +14,15 @@ import { export class NoticeOfIntentDecisionComponentService { private url = `${environment.apiUrl}/notice-of-intent-decision-component`; - constructor(private http: HttpClient, private toastService: ToastService) {} + constructor( + private http: HttpClient, + private toastService: ToastService, + ) {} async update(uuid: string, data: UpdateNoticeOfIntentDecisionComponentDto) { try { const res = await firstValueFrom( - this.http.patch(`${this.url}/${uuid}`, data) + this.http.patch(`${this.url}/${uuid}`, data), ); this.toastService.showSuccessToast('Decision updated'); return res; diff --git a/alcs-frontend/src/app/services/notice-of-intent/decision-v2/notice-of-intent-decision-condition/notice-of-intent-decision-condition.service.ts b/alcs-frontend/src/app/services/notice-of-intent/decision-v2/notice-of-intent-decision-condition/notice-of-intent-decision-condition.service.ts index a719e47fb8..a7d7e4db25 100644 --- a/alcs-frontend/src/app/services/notice-of-intent/decision-v2/notice-of-intent-decision-condition/notice-of-intent-decision-condition.service.ts +++ b/alcs-frontend/src/app/services/notice-of-intent/decision-v2/notice-of-intent-decision-condition/notice-of-intent-decision-condition.service.ts @@ -6,7 +6,7 @@ import { ToastService } from '../../../toast/toast.service'; import { NoticeOfIntentDecisionConditionDto, UpdateNoticeOfIntentDecisionConditionDto, -} from '../../decision/notice-of-intent-decision.dto'; +} from '../notice-of-intent-decision.dto'; @Injectable({ providedIn: 'root', @@ -14,12 +14,15 @@ import { export class NoticeOfIntentDecisionConditionService { private url = `${environment.apiUrl}/notice-of-intent-decision-condition`; - constructor(private http: HttpClient, private toastService: ToastService) {} + constructor( + private http: HttpClient, + private toastService: ToastService, + ) {} async update(uuid: string, data: UpdateNoticeOfIntentDecisionConditionDto) { try { const res = await firstValueFrom( - this.http.patch(`${this.url}/${uuid}`, data) + this.http.patch(`${this.url}/${uuid}`, data), ); this.toastService.showSuccessToast('Condition updated'); return res; diff --git a/alcs-frontend/src/app/services/notice-of-intent/decision-v2/notice-of-intent-decision-v2.service.ts b/alcs-frontend/src/app/services/notice-of-intent/decision-v2/notice-of-intent-decision-v2.service.ts index 89075f04ef..9d652007c7 100644 --- a/alcs-frontend/src/app/services/notice-of-intent/decision-v2/notice-of-intent-decision-v2.service.ts +++ b/alcs-frontend/src/app/services/notice-of-intent/decision-v2/notice-of-intent-decision-v2.service.ts @@ -11,7 +11,7 @@ import { NoticeOfIntentDecisionDto, NoticeOfIntentDecisionWithLinkedResolutionDto, UpdateNoticeOfIntentDecisionDto, -} from '../decision/notice-of-intent-decision.dto'; +} from './notice-of-intent-decision.dto'; @Injectable({ providedIn: 'root', @@ -23,13 +23,16 @@ export class NoticeOfIntentDecisionV2Service { $decision = new BehaviorSubject(undefined); $decisions = new BehaviorSubject([]); - constructor(private http: HttpClient, private toastService: ToastService) {} + constructor( + private http: HttpClient, + private toastService: ToastService, + ) {} async fetchByFileNumber(fileNumber: string) { let decisions: NoticeOfIntentDecisionDto[] = []; try { decisions = await firstValueFrom( - this.http.get(`${this.url}/notice-of-intent/${fileNumber}`) + this.http.get(`${this.url}/notice-of-intent/${fileNumber}`), ); } catch (err) { this.toastService.showErrorToast('Failed to fetch decisions'); @@ -107,7 +110,7 @@ export class NoticeOfIntentDecisionV2Service { await firstValueFrom( this.http.patch(`${this.url}/${decisionUuid}/file/${documentUuid}`, { fileName, - }) + }), ); this.toastService.showSuccessToast('File updated'); } catch (err) { diff --git a/alcs-frontend/src/app/services/notice-of-intent/decision/notice-of-intent-decision.dto.ts b/alcs-frontend/src/app/services/notice-of-intent/decision-v2/notice-of-intent-decision.dto.ts similarity index 100% rename from alcs-frontend/src/app/services/notice-of-intent/decision/notice-of-intent-decision.dto.ts rename to alcs-frontend/src/app/services/notice-of-intent/decision-v2/notice-of-intent-decision.dto.ts diff --git a/alcs-frontend/src/app/services/notice-of-intent/decision/notice-of-intent-decision.service.spec.ts b/alcs-frontend/src/app/services/notice-of-intent/decision/notice-of-intent-decision.service.spec.ts deleted file mode 100644 index 41499dc576..0000000000 --- a/alcs-frontend/src/app/services/notice-of-intent/decision/notice-of-intent-decision.service.spec.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { HttpClient } from '@angular/common/http'; -import { TestBed } from '@angular/core/testing'; -import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { of, throwError } from 'rxjs'; -import { environment } from '../../../../environments/environment'; -import { ToastService } from '../../toast/toast.service'; -import { NoticeOfIntentDecisionService } from './notice-of-intent-decision.service'; - -describe('NoticeOfIntentDecisionService', () => { - let service: NoticeOfIntentDecisionService; - let httpClient: DeepMocked; - let toastService: DeepMocked; - - beforeEach(() => { - httpClient = createMock(); - toastService = createMock(); - - TestBed.configureTestingModule({ - providers: [ - { - provide: HttpClient, - useValue: httpClient, - }, - { - provide: ToastService, - useValue: toastService, - }, - ], - }); - service = TestBed.inject(NoticeOfIntentDecisionService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); - - it('should fetch and return an the dto', async () => { - httpClient.get.mockReturnValue( - of([ - { - fileNumber: '1', - }, - ]) - ); - - const res = await service.fetchByFileNumber('1'); - - expect(res.length).toEqual(1); - expect(res[0].fileNumber).toEqual('1'); - }); - - it('should show a toast message if fetch fails', async () => { - httpClient.get.mockReturnValue( - throwError(() => { - new Error(''); - }) - ); - - const res = await service.fetchByFileNumber('1'); - - expect(res.length).toEqual(0); - expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); - }); - - it('should make an http patch and show a success toast when updating', async () => { - httpClient.patch.mockReturnValue( - of({ - fileNumber: '1', - }) - ); - - await service.update('1', { - isDraft: false, - }); - - expect(httpClient.patch).toHaveBeenCalledTimes(1); - expect(toastService.showSuccessToast).toHaveBeenCalledTimes(1); - }); - - it('should show a toast message if update fails', async () => { - httpClient.patch.mockReturnValue( - throwError(() => { - new Error(''); - }) - ); - - try { - await service.update('1', {}); - } catch (e) { - //OM NOM NOM - } - - expect(httpClient.patch).toHaveBeenCalledTimes(1); - expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); - }); - - it('should make an http post and show a success toast when creating', async () => { - httpClient.post.mockReturnValue( - of({ - fileNumber: '1', - }) - ); - - await service.create({ - fileNumber: '', - date: 0, - outcomeCode: '', - resolutionNumber: 0, - resolutionYear: 0, - decisionMaker: '', - }); - - expect(httpClient.post).toHaveBeenCalledTimes(1); - expect(toastService.showSuccessToast).toHaveBeenCalledTimes(1); - }); - - it('should show a toast message if create fails', async () => { - httpClient.post.mockReturnValue( - throwError(() => { - new Error(''); - }) - ); - - try { - await service.create({ - fileNumber: '', - date: 0, - outcomeCode: '', - resolutionNumber: 0, - resolutionYear: 0, - decisionMaker: '', - }); - } catch (e) { - //OM NOM NOM - } - - expect(httpClient.post).toHaveBeenCalledTimes(1); - expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); - }); - - it('should make an http delete and show a success toast', async () => { - httpClient.delete.mockReturnValue( - of({ - fileNumber: '1', - }) - ); - - await service.delete(''); - - expect(httpClient.delete).toHaveBeenCalledTimes(1); - expect(toastService.showSuccessToast).toHaveBeenCalledTimes(1); - }); - - it('should show a toast message if delete fails', async () => { - httpClient.delete.mockReturnValue( - throwError(() => { - new Error(''); - }) - ); - - try { - await service.delete(''); - } catch (e) { - //OM NOM NOM - } - - expect(httpClient.delete).toHaveBeenCalledTimes(1); - expect(toastService.showErrorToast).toHaveBeenCalledTimes(1); - }); - - it('should show a toast warning when uploading a file thats too large', async () => { - const file = createMock(); - Object.defineProperty(file, 'size', { value: environment.maxFileSize + 1 }); - - await service.uploadFile('', file); - - expect(toastService.showWarningToast).toHaveBeenCalledTimes(1); - expect(httpClient.post).toHaveBeenCalledTimes(0); - }); - - it('should make an http delete when deleting a file', async () => { - httpClient.delete.mockReturnValue( - of({ - fileNumber: '1', - }) - ); - - await service.deleteFile('', ''); - - expect(httpClient.delete).toHaveBeenCalledTimes(1); - }); -}); diff --git a/alcs-frontend/src/app/services/notice-of-intent/decision/notice-of-intent-decision.service.ts b/alcs-frontend/src/app/services/notice-of-intent/decision/notice-of-intent-decision.service.ts deleted file mode 100644 index 247d4b2688..0000000000 --- a/alcs-frontend/src/app/services/notice-of-intent/decision/notice-of-intent-decision.service.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { HttpClient, HttpErrorResponse } from '@angular/common/http'; -import { Injectable } from '@angular/core'; -import { firstValueFrom } from 'rxjs'; -import { environment } from '../../../../environments/environment'; -import { downloadFileFromUrl, openFileInline } from '../../../shared/utils/file'; -import { verifyFileSize } from '../../../shared/utils/file-size-checker'; -import { ToastService } from '../../toast/toast.service'; -import { - NoticeOfIntentDecisionDto, - CreateNoticeOfIntentDecisionDto, - NoticeOfIntentDecisionOutcomeCodeDto, - UpdateNoticeOfIntentDecisionDto, -} from './notice-of-intent-decision.dto'; - -@Injectable({ - providedIn: 'root', -}) -export class NoticeOfIntentDecisionService { - private url = `${environment.apiUrl}/notice-of-intent-decision`; - - constructor(private http: HttpClient, private toastService: ToastService) {} - - async fetchByFileNumber(fileNumber: string) { - let decisions: NoticeOfIntentDecisionDto[] = []; - try { - decisions = await firstValueFrom( - this.http.get(`${this.url}/notice-of-intent/${fileNumber}`) - ); - } catch (err) { - this.toastService.showErrorToast('Failed to fetch decisions'); - } - return decisions; - } - - async fetchCodes() { - let outcomes: NoticeOfIntentDecisionOutcomeCodeDto[] = []; - try { - const res = await firstValueFrom( - this.http.get<{ - outcomes: NoticeOfIntentDecisionOutcomeCodeDto[]; - }>(`${this.url}/codes`) - ); - outcomes = res.outcomes; - } catch (err) { - this.toastService.showErrorToast('Failed to fetch decisions'); - } - return { - outcomes, - }; - } - - async update(uuid: string, data: UpdateNoticeOfIntentDecisionDto) { - try { - const res = await firstValueFrom(this.http.patch(`${this.url}/${uuid}`, data)); - this.toastService.showSuccessToast('Decision updated'); - return res; - } catch (e) { - if (e instanceof HttpErrorResponse && e.status === 400 && e.error?.message) { - this.toastService.showErrorToast(e.error.message); - } else { - this.toastService.showErrorToast('Failed to update decision'); - } - throw e; - } - } - - async create(decision: CreateNoticeOfIntentDecisionDto) { - try { - const res = await firstValueFrom(this.http.post(`${this.url}`, decision)); - this.toastService.showSuccessToast('Decision created'); - return res; - } catch (e) { - if (e instanceof HttpErrorResponse && e.status === 400 && e.error?.message) { - this.toastService.showErrorToast(e.error.message); - } else { - this.toastService.showErrorToast(`Failed to create decision`); - } - throw e; - } - } - - async delete(uuid: string) { - try { - await firstValueFrom(this.http.delete(`${this.url}/${uuid}`)); - this.toastService.showSuccessToast('Decision deleted'); - } catch (err) { - this.toastService.showErrorToast('Failed to delete decision'); - } - } - - async uploadFile(decisionUuid: string, file: File) { - const isValidSize = verifyFileSize(file, this.toastService); - if (!isValidSize) { - return; - } - - let formData: FormData = new FormData(); - formData.append('file', file, file.name); - const res = await firstValueFrom(this.http.post(`${this.url}/${decisionUuid}/file`, formData)); - this.toastService.showSuccessToast('Document uploaded'); - return res; - } - - async downloadFile(decisionUuid: string, documentUuid: string, fileName: string, isInline = true) { - const url = `${this.url}/${decisionUuid}/file/${documentUuid}`; - const finalUrl = isInline ? `${url}/open` : `${url}/download`; - const data = await firstValueFrom(this.http.get<{ url: string }>(finalUrl)); - if (isInline) { - openFileInline(data.url, fileName); - } else { - downloadFileFromUrl(data.url, fileName); - } - } - - async deleteFile(decisionUuid: string, documentUuid: string) { - const url = `${this.url}/${decisionUuid}/file/${documentUuid}`; - return await firstValueFrom(this.http.delete<{ url: string }>(url)); - } -} diff --git a/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent-modification/notice-of-intent-modification.dto.ts b/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent-modification/notice-of-intent-modification.dto.ts index 3dd8b1845e..e41670099f 100644 --- a/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent-modification/notice-of-intent-modification.dto.ts +++ b/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent-modification/notice-of-intent-modification.dto.ts @@ -2,7 +2,7 @@ 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'; -import { NoticeOfIntentDecisionDto } from '../decision/notice-of-intent-decision.dto'; +import { NoticeOfIntentDecisionDto } from '../decision-v2/notice-of-intent-decision.dto'; export interface NoticeOfIntentModificationCreateDto { fileNumber: string; diff --git a/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent.service.ts b/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent.service.ts index 0a28dde1b5..2c2556c7a4 100644 --- a/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent.service.ts +++ b/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent.service.ts @@ -17,7 +17,10 @@ import { export class NoticeOfIntentService { private url = `${environment.apiUrl}/notice-of-intent`; - constructor(private http: HttpClient, private toastService: ToastService) {} + constructor( + private http: HttpClient, + private toastService: ToastService, + ) {} async listSubtypes() { try { @@ -35,9 +38,7 @@ export class NoticeOfIntentService { } catch (e) { console.error(e); if (e instanceof HttpErrorResponse && e.status === 400) { - this.toastService.showErrorToast( - `Covenant/Application/NOI with File ID ${createDto.fileNumber} already exists` - ); + this.toastService.showErrorToast(`Application/NOI with File ID ${createDto.fileNumber} already exists`); } else { this.toastService.showErrorToast('Failed to create Notice of Intent'); } diff --git a/services/apps/alcs/src/alcs/card/card-subtask/card-subtask.dto.ts b/services/apps/alcs/src/alcs/card/card-subtask/card-subtask.dto.ts index 27715961b8..f555602d7e 100644 --- a/services/apps/alcs/src/alcs/card/card-subtask/card-subtask.dto.ts +++ b/services/apps/alcs/src/alcs/card/card-subtask/card-subtask.dto.ts @@ -1,6 +1,5 @@ import { AutoMap } from 'automapper-classes'; import { IsOptional, IsUUID } from 'class-validator'; -import { ApplicationTypeDto } from '../../code/application-code/application-type/application-type.dto'; import { AssigneeDto } from '../../../user/user.dto'; import { PlanningReviewTypeDto } from '../../planning-review/planning-review.dto'; import { CardDto } from '../card.dto'; @@ -8,7 +7,6 @@ import { CardDto } from '../card.dto'; export enum PARENT_TYPE { APPLICATION = 'application', RECONSIDERATION = 'reconsideration', - COVENANT = 'covenant', MODIFICATION = 'modification', PLANNING_REVIEW = 'planning-review', NOTICE_OF_INTENT = 'notice-of-intent', diff --git a/services/apps/alcs/src/alcs/import/noi-import.service.ts b/services/apps/alcs/src/alcs/import/noi-import.service.ts index 47619d4728..4c4e415325 100644 --- a/services/apps/alcs/src/alcs/import/noi-import.service.ts +++ b/services/apps/alcs/src/alcs/import/noi-import.service.ts @@ -9,7 +9,7 @@ import { FALLBACK_APPLICANT_NAME } from '../../utils/owner.constants'; import { BoardService } from '../board/board.service'; import { CardService } from '../card/card.service'; import { LocalGovernmentService } from '../local-government/local-government.service'; -import { NoticeOfIntentDecisionV1Service } from '../notice-of-intent-decision/notice-of-intent-decision-v1/notice-of-intent-decision-v1.service'; +import { NoticeOfIntentDecisionV2Service } from '../notice-of-intent-decision/notice-of-intent-decision-v2/notice-of-intent-decision-v2.service'; import { NoticeOfIntentMeetingService } from '../notice-of-intent/notice-of-intent-meeting/notice-of-intent-meeting.service'; import { NoticeOfIntentSubtype } from '../notice-of-intent/notice-of-intent-subtype.entity'; import { NoticeOfIntent } from '../notice-of-intent/notice-of-intent.entity'; @@ -82,7 +82,7 @@ export class NoticeOfIntentImportService { private boardService: BoardService, private localGovernmentService: LocalGovernmentService, private cardService: CardService, - private noticeOfIntentDecisionService: NoticeOfIntentDecisionV1Service, + private noticeOfIntentDecisionService: NoticeOfIntentDecisionV2Service, ) {} importNoiCsv() { @@ -96,7 +96,6 @@ export class NoticeOfIntentImportService { const filePath = path.resolve(__dirname, '..', 'NOI_Import.csv'); const stream = fs.createReadStream(filePath); const processing: Promise[] = []; - let i = 0; stream .pipe(csv()) .on('data', (data) => { @@ -110,7 +109,6 @@ export class NoticeOfIntentImportService { const promise = this.parseRow(data, mapping); processing.push(promise); - i++; }) .on('end', () => { Promise.all(processing).then(() => { @@ -141,9 +139,8 @@ export class NoticeOfIntentImportService { mappedRow.cityOrDistrict.trim(), ); - const localGovernment = await this.localGovernmentService.getByName( - mappedGovernmentName, - ); + const localGovernment = + await this.localGovernmentService.getByName(mappedGovernmentName); if (!localGovernment) { this.logger.error( diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-v1/notice-of-intent-decision-v1.controller.spec.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-v1/notice-of-intent-decision-v1.controller.spec.ts deleted file mode 100644 index 9fa7529397..0000000000 --- a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-v1/notice-of-intent-decision-v1.controller.spec.ts +++ /dev/null @@ -1,211 +0,0 @@ -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 { mockKeyCloakProviders } from '../../../../test/mocks/mockTypes'; -import { NoticeOfIntentDecisionProfile } from '../../../common/automapper/notice-of-intent-decision.automapper.profile'; -import { UserProfile } from '../../../common/automapper/user.automapper.profile'; -import { NoticeOfIntent } from '../../notice-of-intent/notice-of-intent.entity'; -import { NoticeOfIntentService } from '../../notice-of-intent/notice-of-intent.service'; -import { NoticeOfIntentDecisionOutcome } from '../notice-of-intent-decision-outcome.entity'; -import { NoticeOfIntentDecisionV1Controller } from './notice-of-intent-decision-v1.controller'; -import { - CreateNoticeOfIntentDecisionDto, - UpdateNoticeOfIntentDecisionDto, -} from '../notice-of-intent-decision.dto'; -import { NoticeOfIntentDecision } from '../notice-of-intent-decision.entity'; -import { NoticeOfIntentDecisionV1Service } from './notice-of-intent-decision-v1.service'; -import { NoticeOfIntentModificationService } from '../notice-of-intent-modification/notice-of-intent-modification.service'; - -describe('NoticeOfIntentDecisionController', () => { - let controller: NoticeOfIntentDecisionV1Controller; - let mockDecisionService: DeepMocked; - let mockNOIService: DeepMocked; - let mockNOIModificationService: DeepMocked; - - let mockNoi; - let mockDecision; - - beforeEach(async () => { - mockDecisionService = createMock(); - mockNOIService = createMock(); - mockNOIModificationService = createMock(); - - mockNoi = new NoticeOfIntent(); - mockDecision = new NoticeOfIntentDecision({ - date: new Date(), - noticeOfIntent: mockNoi, - }); - - mockNOIModificationService.getByFileNumber.mockResolvedValue([]); - - const module: TestingModule = await Test.createTestingModule({ - imports: [ - AutomapperModule.forRoot({ - strategyInitializer: classes(), - }), - ], - controllers: [NoticeOfIntentDecisionV1Controller], - providers: [ - NoticeOfIntentDecisionProfile, - UserProfile, - { - provide: NoticeOfIntentDecisionV1Service, - useValue: mockDecisionService, - }, - { - provide: NoticeOfIntentService, - useValue: mockNOIService, - }, - { - provide: NoticeOfIntentModificationService, - useValue: mockNOIModificationService, - }, - { - provide: ClsService, - useValue: {}, - }, - ...mockKeyCloakProviders, - ], - }).compile(); - - controller = module.get( - NoticeOfIntentDecisionV1Controller, - ); - - mockDecisionService.fetchCodes.mockResolvedValue({ - outcomes: [ - { - code: 'decision-code', - label: 'decision-label', - } as NoticeOfIntentDecisionOutcome, - ], - }); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); - - it('should get all for application', async () => { - mockDecisionService.getByFileNumber.mockResolvedValue([mockDecision]); - - const result = await controller.getByFileNumber('fake-number'); - - expect(mockDecisionService.getByFileNumber).toBeCalledTimes(1); - expect(result[0].uuid).toStrictEqual(mockDecision.uuid); - }); - - it('should get a specific decision', async () => { - mockDecisionService.get.mockResolvedValue(mockDecision); - const result = await controller.get('fake-uuid'); - - expect(mockDecisionService.get).toBeCalledTimes(1); - expect(result.uuid).toStrictEqual(mockDecision.uuid); - }); - - it('should call through for deletion', async () => { - mockDecisionService.delete.mockResolvedValue({} as any); - - await controller.delete('fake-uuid'); - - expect(mockDecisionService.delete).toBeCalledTimes(1); - expect(mockDecisionService.delete).toBeCalledWith('fake-uuid'); - }); - - it('should create the decision if noi exists', async () => { - mockNOIService.getByFileNumber.mockResolvedValue(mockNoi); - mockDecisionService.create.mockResolvedValue(mockDecision); - - const decisionToCreate = { - date: new Date(2022, 2, 2, 2, 2, 2, 2).valueOf(), - fileNumber: mockNoi.fileNumber, - outcomeCode: 'outcome', - } as CreateNoticeOfIntentDecisionDto; - - await controller.create(decisionToCreate); - - expect(mockDecisionService.create).toBeCalledTimes(1); - expect(mockDecisionService.create).toBeCalledWith( - { - applicationFileNumber: mockNoi.fileNumber, - outcomeCode: 'outcome', - date: decisionToCreate.date, - }, - mockNoi, - null, - ); - }); - - it('should update the decision', async () => { - mockDecisionService.update.mockResolvedValue(mockDecision); - const updates = { - outcomeCode: 'New Outcome', - date: new Date(2022, 2, 2, 2, 2, 2, 2).valueOf(), - } as UpdateNoticeOfIntentDecisionDto; - - await controller.update('fake-uuid', updates); - - expect(mockDecisionService.update).toBeCalledTimes(1); - expect(mockDecisionService.update).toBeCalledWith('fake-uuid', { - outcomeCode: 'New Outcome', - date: updates.date, - }); - }); - - it('should call through for attaching the document', async () => { - mockDecisionService.attachDocument.mockResolvedValue({} as any); - await controller.attachDocument('fake-uuid', { - isMultipart: () => true, - body: { - file: {}, - }, - user: { - entity: {}, - }, - }); - - expect(mockDecisionService.attachDocument).toBeCalledTimes(1); - }); - - it('should throw an exception if there is no file for file upload', async () => { - mockDecisionService.attachDocument.mockResolvedValue({} as any); - const promise = controller.attachDocument('fake-uuid', { - file: () => ({}), - isMultipart: () => false, - user: { - entity: {}, - }, - }); - - await expect(promise).rejects.toMatchObject( - new Error('Request is not multipart'), - ); - }); - - it('should call through for getting download url', async () => { - const fakeUrl = 'fake-url'; - mockDecisionService.getDownloadUrl.mockResolvedValue(fakeUrl); - const res = await controller.getDownloadUrl('fake-uuid', 'document-uuid'); - - expect(mockDecisionService.getDownloadUrl).toBeCalledTimes(1); - expect(res.url).toEqual(fakeUrl); - }); - - it('should call through for getting open url', async () => { - const fakeUrl = 'fake-url'; - mockDecisionService.getDownloadUrl.mockResolvedValue(fakeUrl); - const res = await controller.getOpenUrl('fake-uuid', 'document-uuid'); - - expect(mockDecisionService.getDownloadUrl).toBeCalledTimes(1); - expect(res.url).toEqual(fakeUrl); - }); - - it('should call through for document deletion', async () => { - mockDecisionService.deleteDocument.mockResolvedValue({} as any); - await controller.deleteDocument('fake-uuid', 'document-uuid'); - - expect(mockDecisionService.deleteDocument).toBeCalledTimes(1); - }); -}); diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-v1/notice-of-intent-decision-v1.controller.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-v1/notice-of-intent-decision-v1.controller.ts deleted file mode 100644 index 28bfd13cd1..0000000000 --- a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-v1/notice-of-intent-decision-v1.controller.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { Mapper } from 'automapper-core'; -import { InjectMapper } from 'automapper-nestjs'; -import { - BadRequestException, - Body, - Controller, - Delete, - Get, - Param, - Patch, - Post, - Req, - UseGuards, -} from '@nestjs/common'; -import { ApiOAuth2 } from '@nestjs/swagger'; -import * as config from 'config'; -import { ANY_AUTH_ROLE } from '../../../common/authorization/roles'; -import { RolesGuard } from '../../../common/authorization/roles-guard.service'; -import { UserRoles } from '../../../common/authorization/roles.decorator'; -import { NoticeOfIntentService } from '../../notice-of-intent/notice-of-intent.service'; -import { NoticeOfIntentDecisionOutcome } from '../notice-of-intent-decision-outcome.entity'; -import { NoticeOfIntentDecision } from '../notice-of-intent-decision.entity'; -import { - CreateNoticeOfIntentDecisionDto, - NoticeOfIntentDecisionDto, - NoticeOfIntentDecisionOutcomeCodeDto, - UpdateNoticeOfIntentDecisionDto, -} from '../notice-of-intent-decision.dto'; -import { NoticeOfIntentDecisionV1Service } from './notice-of-intent-decision-v1.service'; -import { NoticeOfIntentModificationService } from '../notice-of-intent-modification/notice-of-intent-modification.service'; - -@ApiOAuth2(config.get('KEYCLOAK.SCOPES')) -@Controller('notice-of-intent-decision') -@UseGuards(RolesGuard) -export class NoticeOfIntentDecisionV1Controller { - constructor( - private noticeOfIntentDecisionService: NoticeOfIntentDecisionV1Service, - private noticeOfIntentService: NoticeOfIntentService, - private noticeOfIntentModificationService: NoticeOfIntentModificationService, - @InjectMapper() private mapper: Mapper, - ) {} - - @Get('/notice-of-intent/:fileNumber') - @UserRoles(...ANY_AUTH_ROLE) - async getByFileNumber( - @Param('fileNumber') fileNumber, - ): Promise { - const decisions = - await this.noticeOfIntentDecisionService.getByFileNumber(fileNumber); - return await this.mapper.mapArrayAsync( - decisions, - NoticeOfIntentDecision, - NoticeOfIntentDecisionDto, - ); - } - - @Get('/:uuid') - @UserRoles(...ANY_AUTH_ROLE) - async get(@Param('uuid') uuid: string): Promise { - const meeting = await this.noticeOfIntentDecisionService.get(uuid); - return this.mapper.mapAsync( - meeting, - NoticeOfIntentDecision, - NoticeOfIntentDecisionDto, - ); - } - - @Post() - @UserRoles(...ANY_AUTH_ROLE) - async create( - @Body() createDto: CreateNoticeOfIntentDecisionDto, - ): Promise { - const noticeOfIntent = await this.noticeOfIntentService.getByFileNumber( - createDto.fileNumber, - ); - - const modifiedNotice = createDto.modifiesUuid - ? await this.noticeOfIntentModificationService.getByUuid( - createDto.modifiesUuid, - ) - : null; - - const newDecision = await this.noticeOfIntentDecisionService.create( - createDto, - noticeOfIntent, - modifiedNotice, - ); - - return this.mapper.mapAsync( - newDecision, - NoticeOfIntentDecision, - NoticeOfIntentDecisionDto, - ); - } - - @Patch('/:uuid') - @UserRoles(...ANY_AUTH_ROLE) - async update( - @Param('uuid') uuid: string, - @Body() updateDto: UpdateNoticeOfIntentDecisionDto, - ): Promise { - const updatedDecision = await this.noticeOfIntentDecisionService.update( - uuid, - updateDto, - ); - return this.mapper.mapAsync( - updatedDecision, - NoticeOfIntentDecision, - NoticeOfIntentDecisionDto, - ); - } - - @Delete('/:uuid') - @UserRoles(...ANY_AUTH_ROLE) - async delete(@Param('uuid') uuid: string) { - return await this.noticeOfIntentDecisionService.delete(uuid); - } - - @Post('/:uuid/file') - @UserRoles(...ANY_AUTH_ROLE) - async attachDocument(@Param('uuid') decisionUuid: string, @Req() req) { - if (!req.isMultipart()) { - throw new BadRequestException('Request is not multipart'); - } - - const file = req.body.file; - await this.noticeOfIntentDecisionService.attachDocument( - decisionUuid, - file, - req.user.entity, - ); - return { - uploaded: true, - }; - } - - @Get('/:uuid/file/:fileUuid/download') - @UserRoles(...ANY_AUTH_ROLE) - async getDownloadUrl( - @Param('uuid') decisionUuid: string, - @Param('fileUuid') documentUuid: string, - ) { - const downloadUrl = - await this.noticeOfIntentDecisionService.getDownloadUrl(documentUuid); - return { - url: downloadUrl, - }; - } - - @Get('/:uuid/file/:fileUuid/open') - @UserRoles(...ANY_AUTH_ROLE) - async getOpenUrl( - @Param('uuid') decisionUuid: string, - @Param('fileUuid') documentUuid: string, - ) { - const downloadUrl = await this.noticeOfIntentDecisionService.getDownloadUrl( - documentUuid, - true, - ); - return { - url: downloadUrl, - }; - } - - @Delete('/:uuid/file/:fileUuid') - @UserRoles(...ANY_AUTH_ROLE) - async deleteDocument( - @Param('uuid') decisionUuid: string, - @Param('fileUuid') documentUuid: string, - ) { - await this.noticeOfIntentDecisionService.deleteDocument(documentUuid); - return {}; - } - - @Get('/codes') - @UserRoles(...ANY_AUTH_ROLE) - async getCodes() { - const codes = await this.noticeOfIntentDecisionService.fetchCodes(); - return { - outcomes: await this.mapper.mapArrayAsync( - codes.outcomes, - NoticeOfIntentDecisionOutcome, - NoticeOfIntentDecisionOutcomeCodeDto, - ), - }; - } -} diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-v1/notice-of-intent-decision-v1.service.spec.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-v1/notice-of-intent-decision-v1.service.spec.ts deleted file mode 100644 index 12c0cd6d07..0000000000 --- a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-v1/notice-of-intent-decision-v1.service.spec.ts +++ /dev/null @@ -1,333 +0,0 @@ -import { - ServiceNotFoundException, - ServiceValidationException, -} from '@app/common/exceptions/base.exception'; -import { classes } from 'automapper-classes'; -import { AutomapperModule } from 'automapper-nestjs'; -import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; -import { Test, TestingModule } from '@nestjs/testing'; -import { getRepositoryToken } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { DocumentService } from '../../../document/document.service'; -import { NoticeOfIntent } from '../../notice-of-intent/notice-of-intent.entity'; -import { NoticeOfIntentService } from '../../notice-of-intent/notice-of-intent.service'; -import { NoticeOfIntentDecisionDocument } from '../notice-of-intent-decision-document/notice-of-intent-decision-document.entity'; -import { NoticeOfIntentDecisionOutcome } from '../notice-of-intent-decision-outcome.entity'; -import { - CreateNoticeOfIntentDecisionDto, - UpdateNoticeOfIntentDecisionDto, -} from '../notice-of-intent-decision.dto'; -import { NoticeOfIntentDecision } from '../notice-of-intent-decision.entity'; -import { NoticeOfIntentDecisionV1Service } from './notice-of-intent-decision-v1.service'; - -describe('NoticeOfIntentDecisionService', () => { - let service: NoticeOfIntentDecisionV1Service; - let mockDecisionRepository: DeepMocked>; - let mockDecisionDocumentRepository: DeepMocked< - Repository - >; - let mockDecisionOutcomeRepository: DeepMocked< - Repository - >; - let mockNOIService: DeepMocked; - let mockDocumentService: DeepMocked; - - let mockNOI; - let mockDecision; - - beforeEach(async () => { - mockNOIService = createMock(); - mockDocumentService = createMock(); - mockDecisionRepository = createMock>(); - mockDecisionDocumentRepository = - createMock>(); - mockDecisionOutcomeRepository = - createMock>(); - - const module: TestingModule = await Test.createTestingModule({ - imports: [ - AutomapperModule.forRoot({ - strategyInitializer: classes(), - }), - ], - providers: [ - NoticeOfIntentDecisionV1Service, - { - provide: getRepositoryToken(NoticeOfIntentDecision), - useValue: mockDecisionRepository, - }, - { - provide: getRepositoryToken(NoticeOfIntentDecisionDocument), - useValue: mockDecisionDocumentRepository, - }, - { - provide: getRepositoryToken(NoticeOfIntentDecisionOutcome), - useValue: mockDecisionOutcomeRepository, - }, - { - provide: NoticeOfIntentService, - useValue: mockNOIService, - }, - { - provide: DocumentService, - useValue: mockDocumentService, - }, - ], - }).compile(); - - service = module.get( - NoticeOfIntentDecisionV1Service, - ); - - mockNOI = new NoticeOfIntent({ - fileNumber: '1', - }); - mockDecision = new NoticeOfIntentDecision({ - noticeOfIntent: mockNOI, - documents: [], - }); - - mockDecisionRepository.find.mockResolvedValue([mockDecision]); - mockDecisionRepository.findOne.mockResolvedValue(mockDecision); - mockDecisionRepository.save.mockResolvedValue(mockDecision); - - mockDecisionDocumentRepository.find.mockResolvedValue([]); - - mockNOIService.getOrFailByUuid.mockResolvedValue(mockNOI); - mockNOIService.getByFileNumber.mockResolvedValue(mockNOI); - mockNOIService.update.mockResolvedValue({} as any); - mockNOIService.updateByUuid.mockResolvedValue({} as any); - - mockDecisionOutcomeRepository.find.mockResolvedValue([]); - mockDecisionOutcomeRepository.findOneOrFail.mockResolvedValue({} as any); - }); - - describe('NoticeOfIntentDecisionService Core Tests', () => { - it('should be defined', () => { - expect(service).toBeDefined(); - }); - - it('should get decisions by notice of intent', async () => { - const result = await service.getByFileNumber(mockNOI.fileNumber); - - expect(result).toStrictEqual([mockDecision]); - }); - - it('should return decisions by uuid', async () => { - const result = await service.get(mockDecision.uuid); - - expect(result).toStrictEqual(mockDecision); - }); - - it('should delete decision with uuid and update the noi', async () => { - mockDecisionRepository.softRemove.mockResolvedValue({} as any); - mockDecisionRepository.findOne.mockResolvedValue({ - ...mockDecision, - reconsiders: 'reconsider-uuid', - modifies: 'modified-uuid', - }); - mockDecisionRepository.find.mockResolvedValue([]); - - await service.delete(mockDecision.uuid); - expect(mockDecisionRepository.softRemove).toBeCalledTimes(1); - expect(mockNOIService.updateByUuid).toHaveBeenCalledTimes(1); - expect(mockNOIService.updateByUuid).toHaveBeenCalledWith(mockNOI.uuid, { - decisionDate: null, - }); - }); - - it('should create a decision and update the noi if this was the first decision', async () => { - mockDecisionRepository.find.mockResolvedValue([]); - mockDecisionRepository.findOne.mockResolvedValueOnce(null); - - const decisionDate = new Date(2022, 2, 2, 2, 2, 2, 2); - const decisionToCreate = { - date: decisionDate.getTime(), - fileNumber: 'file-number', - outcomeCode: 'Outcome', - } as CreateNoticeOfIntentDecisionDto; - - await service.create(decisionToCreate, mockNOI, null); - - expect(mockDecisionRepository.save).toBeCalledTimes(1); - expect(mockNOIService.updateByUuid).toHaveBeenCalledTimes(1); - expect(mockNOIService.updateByUuid).toHaveBeenCalledWith(mockNOI.uuid, { - decisionDate, - }); - }); - - it('should fail create a decision and update application if the resolution number is already in use', async () => { - mockDecisionRepository.findOne.mockResolvedValue( - {} as NoticeOfIntentDecision, - ); - - const decisionDate = new Date(2022, 2, 2, 2, 2, 2, 2); - const decisionToCreate = { - resolutionNumber: 1, - resolutionYear: 1, - date: decisionDate.getTime(), - fileNumber: 'file-number', - outcomeCode: 'Outcome', - } as CreateNoticeOfIntentDecisionDto; - - await expect( - service.create(decisionToCreate, mockNOI, null), - ).rejects.toMatchObject( - new ServiceValidationException( - `Resolution number #${decisionToCreate.resolutionNumber}/${decisionToCreate.resolutionYear} is already in use`, - ), - ); - - expect(mockDecisionRepository.save).toBeCalledTimes(0); - expect(mockNOIService.update).toHaveBeenCalledTimes(0); - }); - - it('should create a decision and NOT update the application if this was the second decision', async () => { - mockDecisionRepository.findOne.mockResolvedValueOnce(null); - - const decisionDate = new Date(2022, 2, 2, 2, 2, 2, 2); - const decisionToCreate = { - date: decisionDate.getTime(), - fileNumber: 'file-number', - outcomeCode: 'Outcome', - } as CreateNoticeOfIntentDecisionDto; - - await service.create(decisionToCreate, mockNOI, null); - - expect(mockDecisionRepository.save).toBeCalledTimes(1); - expect(mockNOIService.update).not.toHaveBeenCalled(); - }); - - it('should update the decision and update the application if it was the only decision', async () => { - const decisionDate = new Date(2022, 3, 3, 3, 3, 3, 3); - const decisionUpdate: UpdateNoticeOfIntentDecisionDto = { - date: decisionDate.getTime(), - outcomeCode: 'New Outcome', - }; - - await service.update(mockDecision.uuid, decisionUpdate); - - expect(mockDecisionRepository.findOne).toBeCalledTimes(2); - expect(mockDecisionRepository.save).toBeCalledTimes(1); - expect(mockNOIService.updateByUuid).toHaveBeenCalledTimes(1); - expect(mockNOIService.updateByUuid).toHaveBeenCalledWith(mockNOI.uuid, { - decisionDate, - }); - }); - - it('should not update the noi if this was not the first decision', async () => { - const secondDecision = new NoticeOfIntentDecision({ - noticeOfIntent: mockNOI, - }); - secondDecision.uuid = 'second-uuid'; - mockDecisionRepository.find.mockResolvedValue([ - secondDecision, - mockDecision, - ]); - mockDecisionRepository.findOne.mockResolvedValue(secondDecision); - - const decisionDate = new Date(2022, 3, 3, 3, 3, 3, 3); - const decisionUpdate: UpdateNoticeOfIntentDecisionDto = { - date: decisionDate.getTime(), - outcomeCode: 'New Outcome', - }; - - await service.update(mockDecision.uuid, decisionUpdate); - - expect(mockDecisionRepository.findOne).toBeCalledTimes(2); - expect(mockDecisionRepository.save).toBeCalledTimes(1); - expect(mockNOIService.update).not.toHaveBeenCalled(); - }); - - it('should fail on update if the decision is not found', async () => { - const nonExistantUuid = 'bad-uuid'; - mockDecisionRepository.findOne.mockResolvedValue(null); - const decisionUpdate: UpdateNoticeOfIntentDecisionDto = { - date: new Date(2022, 2, 2, 2, 2, 2, 2).getTime(), - outcomeCode: 'New Outcome', - }; - const promise = service.update(nonExistantUuid, decisionUpdate); - - await expect(promise).rejects.toMatchObject( - new ServiceNotFoundException( - `Decision with UUID ${nonExistantUuid} not found`, - ), - ); - expect(mockDecisionRepository.save).toBeCalledTimes(0); - }); - - it('should call through for get code', async () => { - await service.fetchCodes(); - expect(mockDecisionOutcomeRepository.find).toHaveBeenCalledTimes(1); - }); - }); - - describe('ApplicationDecisionService File Tests', () => { - let mockDocument; - beforeEach(() => { - mockDecisionDocumentRepository.findOne.mockResolvedValue(mockDocument); - mockDecisionDocumentRepository.save.mockResolvedValue(mockDocument); - - mockDocument = { - uuid: 'fake-uuid', - decisionUuid: 'decision-uuid', - } as NoticeOfIntentDecisionDocument; - }); - - it('should call the repository for attaching a file', async () => { - mockDocumentService.create.mockResolvedValue({} as any); - - await service.attachDocument('uuid', {} as any, {} as any); - expect(mockDecisionDocumentRepository.save).toHaveBeenCalledTimes(1); - expect(mockDocumentService.create).toHaveBeenCalledTimes(1); - }); - - it('should throw an exception when attaching a document to a non-existent decision', async () => { - mockDecisionRepository.findOne.mockResolvedValue(null); - await expect( - service.attachDocument('uuid', {} as any, {} as any), - ).rejects.toMatchObject( - new ServiceNotFoundException(`Decision with UUID uuid not found`), - ); - expect(mockDocumentService.create).not.toHaveBeenCalled(); - }); - - it('should call the repository to delete documents', async () => { - mockDecisionDocumentRepository.softRemove.mockResolvedValue({} as any); - - await service.deleteDocument('fake-uuid'); - expect(mockDecisionDocumentRepository.softRemove).toHaveBeenCalledTimes( - 1, - ); - }); - - it('should throw an exception when document not found for deletion', async () => { - mockDecisionDocumentRepository.findOne.mockResolvedValue(null); - await expect(service.deleteDocument('fake-uuid')).rejects.toMatchObject( - new ServiceNotFoundException( - `Failed to find document with uuid fake-uuid`, - ), - ); - expect(mockDocumentService.softRemove).not.toHaveBeenCalled(); - }); - - it('should call through to document service for download', async () => { - const downloadUrl = 'download-url'; - mockDocumentService.getDownloadUrl.mockResolvedValue(downloadUrl); - - const res = await service.getDownloadUrl('fake-uuid'); - - expect(mockDocumentService.getDownloadUrl).toHaveBeenCalledTimes(1); - expect(res).toEqual(downloadUrl); - }); - - it('should throw an exception when document not found for download', async () => { - mockDecisionDocumentRepository.findOne.mockResolvedValue(null); - await expect(service.getDownloadUrl('fake-uuid')).rejects.toMatchObject( - new ServiceNotFoundException( - `Failed to find document with uuid fake-uuid`, - ), - ); - }); - }); -}); diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-v1/notice-of-intent-decision-v1.service.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-v1/notice-of-intent-decision-v1.service.ts deleted file mode 100644 index 21a36ca7b7..0000000000 --- a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-v1/notice-of-intent-decision-v1.service.ts +++ /dev/null @@ -1,388 +0,0 @@ -import { - ServiceNotFoundException, - ServiceValidationException, -} from '@app/common/exceptions/base.exception'; -import { MultipartFile } from '@fastify/multipart'; -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { In, IsNull, Repository } from 'typeorm'; -import { - DOCUMENT_SOURCE, - DOCUMENT_SYSTEM, -} from '../../../document/document.dto'; -import { DocumentService } from '../../../document/document.service'; -import { User } from '../../../user/user.entity'; -import { formatIncomingDate } from '../../../utils/incoming-date.formatter'; -import { filterUndefined } from '../../../utils/undefined'; -import { NoticeOfIntent } from '../../notice-of-intent/notice-of-intent.entity'; -import { NoticeOfIntentService } from '../../notice-of-intent/notice-of-intent.service'; -import { NoticeOfIntentDecisionDocument } from '../notice-of-intent-decision-document/notice-of-intent-decision-document.entity'; -import { NoticeOfIntentDecisionOutcome } from '../notice-of-intent-decision-outcome.entity'; -import { NoticeOfIntentDecision } from '../notice-of-intent-decision.entity'; -import { - CreateNoticeOfIntentDecisionDto, - UpdateNoticeOfIntentDecisionDto, -} from '../notice-of-intent-decision.dto'; -import { NoticeOfIntentModification } from '../notice-of-intent-modification/notice-of-intent-modification.entity'; - -@Injectable() -export class NoticeOfIntentDecisionV1Service { - constructor( - @InjectRepository(NoticeOfIntentDecision) - private appDecisionRepository: Repository, - @InjectRepository(NoticeOfIntentDecisionDocument) - private decisionDocumentRepository: Repository, - @InjectRepository(NoticeOfIntentDecisionOutcome) - private decisionOutcomeRepository: Repository, - private noticeOfIntentService: NoticeOfIntentService, - private documentService: DocumentService, - ) {} - - async getByFileNumber(fileNumber: string) { - const noticeOfIntent = await this.noticeOfIntentService.getByFileNumber( - fileNumber, - ); - - const decisions = await this.appDecisionRepository.find({ - where: { - noticeOfIntentUuid: noticeOfIntent.uuid, - }, - order: { - date: 'DESC', - }, - relations: { - modifies: { - modifiesDecisions: true, - }, - outcome: true, - }, - }); - - // do not place modifiedBy into query above, it will kill performance - const decisionsWithModifiedBy = await this.appDecisionRepository.find({ - where: { - noticeOfIntentUuid: noticeOfIntent.uuid, - }, - relations: { - modifiedBy: { - resultingDecision: true, - reviewOutcome: true, - }, - }, - }); - - for (const decision of decisions) { - decision.modifiedBy = - decisionsWithModifiedBy.find((r) => r.uuid === decision.uuid) - ?.modifiedBy || []; - } - - //Query Documents separately as when added to the above joins caused performance issues - for (const decision of decisions) { - decision.documents = await this.decisionDocumentRepository.find({ - where: { - decisionUuid: decision.uuid, - document: { - auditDeletedDateAt: IsNull(), - }, - }, - relations: { - document: true, - }, - }); - } - return decisions; - } - - async get(uuid) { - const decision = await this.appDecisionRepository.findOne({ - where: { - uuid, - }, - relations: { - outcome: true, - documents: { - document: true, - }, - }, - }); - - if (!decision) { - throw new ServiceNotFoundException( - `Failed to load decision with uuid ${uuid}`, - ); - } - - decision.documents = decision.documents.filter( - (document) => !!document.document, - ); - return decision; - } - - async update(uuid: string, updateDto: UpdateNoticeOfIntentDecisionDto) { - const existingDecision: Partial = - await this.getOrFail(uuid); - - // resolution number is int64 in postgres, which means it is a string in JS - if ( - updateDto.resolutionNumber && - updateDto.resolutionYear && - (existingDecision.resolutionNumber !== updateDto.resolutionNumber || - existingDecision.resolutionYear !== updateDto.resolutionYear) - ) { - await this.validateResolutionNumber( - updateDto.resolutionNumber, - updateDto.resolutionYear, - ); - } - - existingDecision.auditDate = formatIncomingDate(updateDto.auditDate); - existingDecision.resolutionNumber = updateDto.resolutionNumber; - existingDecision.resolutionYear = updateDto.resolutionYear; - existingDecision.decisionMaker = filterUndefined( - updateDto.decisionMaker, - undefined, - ); - existingDecision.decisionMakerName = filterUndefined( - updateDto.decisionMakerName, - undefined, - ); - - if (updateDto.outcomeCode) { - existingDecision.outcome = await this.getOutcomeByCode( - updateDto.outcomeCode, - ); - } - - let dateHasChanged = false; - if (updateDto.date && existingDecision.date !== new Date(updateDto.date)) { - dateHasChanged = true; - existingDecision.date = new Date(updateDto.date); - } - - const updatedDecision = await this.appDecisionRepository.save( - existingDecision, - ); - - //If we are updating the date, we need to check if it's the first decision and if so update the application decisionDate - if (dateHasChanged) { - const existingDecisions = await this.getByFileNumber( - existingDecision.noticeOfIntent!.fileNumber, - ); - - const decisionIndex = existingDecisions.findIndex( - (dec) => dec.uuid === existingDecision.uuid, - ); - - if (decisionIndex === existingDecisions.length - 1) { - await this.noticeOfIntentService.updateByUuid( - existingDecision.noticeOfIntentUuid!, - { - decisionDate: updatedDecision.date, - }, - ); - } - } - - return this.get(existingDecision.uuid); - } - - private async getOrFail(uuid: string) { - const existingDecision = await this.appDecisionRepository.findOne({ - where: { - uuid, - }, - relations: { - noticeOfIntent: true, - }, - }); - - if (!existingDecision) { - throw new ServiceNotFoundException( - `Decision with UUID ${uuid} not found`, - ); - } - return existingDecision; - } - - async create( - createDto: CreateNoticeOfIntentDecisionDto, - noticeOfIntent: NoticeOfIntent, - modifies: NoticeOfIntentModification | undefined | null, - ) { - const decision = new NoticeOfIntentDecision({ - outcome: await this.getOutcomeByCode(createDto.outcomeCode ?? 'APPR'), - date: createDto.date ? new Date(createDto.date) : null, - resolutionNumber: createDto.resolutionNumber, - resolutionYear: createDto.resolutionYear, - decisionMaker: filterUndefined(createDto.decisionMaker, undefined), - decisionMakerName: filterUndefined( - createDto.decisionMakerName, - undefined, - ), - modifies, - noticeOfIntent, - auditDate: formatIncomingDate(createDto.auditDate), - }); - - await this.validateResolutionNumber( - createDto.resolutionNumber, - createDto.resolutionYear, - ); - - const existingDecisions = await this.getByFileNumber( - noticeOfIntent.fileNumber, - ); - - if (existingDecisions.length === 0) { - await this.noticeOfIntentService.updateByUuid(noticeOfIntent.uuid, { - decisionDate: decision.date, - }); - } - - const savedDecision = await this.appDecisionRepository.save(decision); - - return this.get(savedDecision.uuid); - } - - private async validateResolutionNumber(number, year) { - const existingDecision = await this.appDecisionRepository.findOne({ - where: { - resolutionNumber: number, - resolutionYear: year, - }, - withDeleted: true, - }); - - if (existingDecision) { - throw new ServiceValidationException( - `Resolution number #${number}/${year} is already in use`, - ); - } - } - - async delete(uuid) { - const noticeOfIntentDecision = await this.appDecisionRepository.findOne({ - where: { uuid }, - relations: { - outcome: true, - documents: { - document: true, - }, - noticeOfIntent: true, - }, - }); - - if (!noticeOfIntentDecision) { - throw new ServiceNotFoundException( - `Failed to find decision with uuid ${uuid}`, - ); - } - - for (const document of noticeOfIntentDecision.documents) { - await this.documentService.softRemove(document.document); - } - - //Clear potential links - await this.appDecisionRepository.save(noticeOfIntentDecision); - await this.appDecisionRepository.softRemove([noticeOfIntentDecision]); - - const existingDecisions = await this.getByFileNumber( - noticeOfIntentDecision.noticeOfIntent.fileNumber, - ); - if (existingDecisions.length === 0) { - await this.noticeOfIntentService.updateByUuid( - noticeOfIntentDecision.noticeOfIntentUuid, - { - decisionDate: null, - }, - ); - } else { - await this.noticeOfIntentService.updateByUuid( - noticeOfIntentDecision.noticeOfIntentUuid, - { - decisionDate: existingDecisions[existingDecisions.length - 1].date, - }, - ); - } - } - - async attachDocument(decisionUuid: string, file: MultipartFile, user: User) { - const decision = await this.getOrFail(decisionUuid); - const document = await this.documentService.create( - `noi-decision/${decision.uuid}`, - file.filename, - file, - user, - DOCUMENT_SOURCE.ALC, - DOCUMENT_SYSTEM.ALCS, - ); - const appDocument = new NoticeOfIntentDecisionDocument({ - decision, - document, - }); - - return this.decisionDocumentRepository.save(appDocument); - } - - async deleteDocument(decisionDocumentUuid: string) { - const decisionDocument = await this.getDecisionDocumentOrFail( - decisionDocumentUuid, - ); - - await this.decisionDocumentRepository.softRemove(decisionDocument); - return decisionDocument; - } - - async getDownloadUrl(decisionDocumentUuid: string, openInline = false) { - const decisionDocument = await this.getDecisionDocumentOrFail( - decisionDocumentUuid, - ); - - return this.documentService.getDownloadUrl( - decisionDocument.document, - openInline, - ); - } - - private async getDecisionDocumentOrFail(decisionDocumentUuid: string) { - const decisionDocument = await this.decisionDocumentRepository.findOne({ - where: { - uuid: decisionDocumentUuid, - }, - relations: { - document: true, - }, - }); - - if (!decisionDocument) { - throw new ServiceNotFoundException( - `Failed to find document with uuid ${decisionDocumentUuid}`, - ); - } - return decisionDocument; - } - - getOutcomeByCode(code: string) { - return this.decisionOutcomeRepository.findOneOrFail({ - where: { - code, - }, - }); - } - - async fetchCodes() { - const values = await Promise.all([this.decisionOutcomeRepository.find()]); - return { - outcomes: values[0], - }; - } - - getMany(modifiesDecisionUuids: string[]) { - return this.appDecisionRepository.find({ - where: { - uuid: In(modifiesDecisionUuids), - }, - }); - } -} diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision.module.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision.module.ts index fe7cf9c6c6..4583021b7c 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision.module.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision.module.ts @@ -2,6 +2,7 @@ import { forwardRef, Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { NoticeOfIntentDecisionProfile } from '../../common/automapper/notice-of-intent-decision.automapper.profile'; import { DocumentModule } from '../../document/document.module'; +import { NoticeOfIntentSubmissionModule } from '../../portal/notice-of-intent-submission/notice-of-intent-submission.module'; import { BoardModule } from '../board/board.module'; import { CardModule } from '../card/card.module'; import { NoticeOfIntentSubmissionStatusModule } from '../notice-of-intent/notice-of-intent-submission-status/notice-of-intent-submission-status.module'; @@ -16,8 +17,6 @@ import { NoticeOfIntentDecisionCondition } from './notice-of-intent-decision-con import { NoticeOfIntentDecisionConditionService } from './notice-of-intent-decision-condition/notice-of-intent-decision-condition.service'; import { NoticeOfIntentDecisionDocument } from './notice-of-intent-decision-document/notice-of-intent-decision-document.entity'; import { NoticeOfIntentDecisionOutcome } from './notice-of-intent-decision-outcome.entity'; -import { NoticeOfIntentDecisionV1Controller } from './notice-of-intent-decision-v1/notice-of-intent-decision-v1.controller'; -import { NoticeOfIntentDecisionV1Service } from './notice-of-intent-decision-v1/notice-of-intent-decision-v1.service'; import { NoticeOfIntentDecisionV2Controller } from './notice-of-intent-decision-v2/notice-of-intent-decision-v2.controller'; import { NoticeOfIntentDecisionV2Service } from './notice-of-intent-decision-v2/notice-of-intent-decision-v2.service'; import { NoticeOfIntentDecision } from './notice-of-intent-decision.entity'; @@ -25,7 +24,6 @@ import { NoticeOfIntentModificationOutcomeType } from './notice-of-intent-modifi import { NoticeOfIntentModificationController } from './notice-of-intent-modification/notice-of-intent-modification.controller'; import { NoticeOfIntentModification } from './notice-of-intent-modification/notice-of-intent-modification.entity'; import { NoticeOfIntentModificationService } from './notice-of-intent-modification/notice-of-intent-modification.service'; -import { NoticeOfIntentSubmissionModule } from '../../portal/notice-of-intent-submission/notice-of-intent-submission.module'; @Module({ imports: [ @@ -48,8 +46,6 @@ import { NoticeOfIntentSubmissionModule } from '../../portal/notice-of-intent-su forwardRef(() => NoticeOfIntentSubmissionModule), ], providers: [ - //These are in the same module, so be careful to import the correct one - NoticeOfIntentDecisionV1Service, NoticeOfIntentDecisionV2Service, NoticeOfIntentDecisionComponentService, NoticeOfIntentDecisionConditionService, @@ -57,16 +53,11 @@ import { NoticeOfIntentSubmissionModule } from '../../portal/notice-of-intent-su NoticeOfIntentModificationService, ], controllers: [ - NoticeOfIntentDecisionV1Controller, NoticeOfIntentDecisionV2Controller, NoticeOfIntentModificationController, NoticeOfIntentDecisionComponentController, NoticeOfIntentDecisionConditionController, ], - exports: [ - NoticeOfIntentModificationService, - NoticeOfIntentDecisionV1Service, - NoticeOfIntentDecisionV2Service, - ], + exports: [NoticeOfIntentModificationService, NoticeOfIntentDecisionV2Service], }) export class NoticeOfIntentDecisionModule {} diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.service.spec.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.service.spec.ts index 95c8835eff..64104d33d4 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.service.spec.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.service.spec.ts @@ -12,7 +12,7 @@ import { Card } from '../../card/card.entity'; import { CardService } from '../../card/card.service'; import { NoticeOfIntent } from '../../notice-of-intent/notice-of-intent.entity'; import { NoticeOfIntentService } from '../../notice-of-intent/notice-of-intent.service'; -import { NoticeOfIntentDecisionV1Service } from '../notice-of-intent-decision-v1/notice-of-intent-decision-v1.service'; +import { NoticeOfIntentDecisionV2Service } from '../notice-of-intent-decision-v2/notice-of-intent-decision-v2.service'; import { NoticeOfIntentDecision } from '../notice-of-intent-decision.entity'; import { NoticeOfIntentModificationCreateDto, @@ -26,7 +26,7 @@ describe('NoticeOfIntentModificationService', () => { let service: NoticeOfIntentModificationService; let noticeOfIntentServiceMock: DeepMocked; let cardServiceMock: DeepMocked; - let decisionServiceMock: DeepMocked; + let decisionServiceMock: DeepMocked; let mockModification; let mockModificationCreateDto: NoticeOfIntentModificationCreateDto; @@ -83,7 +83,7 @@ describe('NoticeOfIntentModificationService', () => { useValue: cardServiceMock, }, { - provide: NoticeOfIntentDecisionV1Service, + provide: NoticeOfIntentDecisionV2Service, useValue: decisionServiceMock, }, { diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.service.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.service.ts index 945f2346b6..01bd12bfac 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.service.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.service.ts @@ -15,7 +15,7 @@ import { Board } from '../../board/board.entity'; import { CARD_TYPE } from '../../card/card-type/card-type.entity'; import { CardService } from '../../card/card.service'; import { NoticeOfIntentService } from '../../notice-of-intent/notice-of-intent.service'; -import { NoticeOfIntentDecisionV1Service } from '../notice-of-intent-decision-v1/notice-of-intent-decision-v1.service'; +import { NoticeOfIntentDecisionV2Service } from '../notice-of-intent-decision-v2/notice-of-intent-decision-v2.service'; import { NoticeOfIntentModificationCreateDto, NoticeOfIntentModificationDto, @@ -25,15 +25,6 @@ import { NoticeOfIntentModification } from './notice-of-intent-modification.enti @Injectable() export class NoticeOfIntentModificationService { - constructor( - @InjectRepository(NoticeOfIntentModification) - private modificationRepository: Repository, - @InjectMapper() private mapper: Mapper, - private noticeOfIntentService: NoticeOfIntentService, - private noticeOfIntentDecisionService: NoticeOfIntentDecisionV1Service, - private cardService: CardService, - ) {} - private BOARD_RECONSIDERATION_RELATIONS: FindOptionsRelations = { noticeOfIntent: { @@ -47,7 +38,6 @@ export class NoticeOfIntentModificationService { assignee: true, }, }; - private DEFAULT_RELATIONS: FindOptionsRelations = { noticeOfIntent: { @@ -65,6 +55,15 @@ export class NoticeOfIntentModificationService { reviewOutcome: true, }; + constructor( + @InjectRepository(NoticeOfIntentModification) + private modificationRepository: Repository, + @InjectMapper() private mapper: Mapper, + private noticeOfIntentService: NoticeOfIntentService, + private noticeOfIntentDecisionService: NoticeOfIntentDecisionV2Service, + private cardService: CardService, + ) {} + getByBoard(boardUuid: string) { return this.modificationRepository.find({ where: { card: { boardUuid } }, @@ -167,20 +166,6 @@ export class NoticeOfIntentModificationService { return this.modificationRepository.softRemove([modification]); } - private async getByUuidOrFail(uuid: string) { - const modification = await this.modificationRepository.findOneBy({ - uuid, - }); - - if (!modification) { - throw new ServiceNotFoundException( - `Modification with uuid ${uuid} not found`, - ); - } - - return modification; - } - getByCardUuid(cardUuid: string) { return this.getOneByOrFail({ cardUuid }); } @@ -221,4 +206,18 @@ export class NoticeOfIntentModificationService { }, }); } + + private async getByUuidOrFail(uuid: string) { + const modification = await this.modificationRepository.findOneBy({ + uuid, + }); + + if (!modification) { + throw new ServiceNotFoundException( + `Modification with uuid ${uuid} not found`, + ); + } + + return modification; + } } diff --git a/services/apps/alcs/src/common/automapper/application-submission.automapper.profile.ts b/services/apps/alcs/src/common/automapper/application-submission.automapper.profile.ts index 07a8cd6a6b..486b48faf9 100644 --- a/services/apps/alcs/src/common/automapper/application-submission.automapper.profile.ts +++ b/services/apps/alcs/src/common/automapper/application-submission.automapper.profile.ts @@ -1,13 +1,13 @@ +import { Injectable } from '@nestjs/common'; import { createMap, forMember, mapFrom, Mapper } from 'automapper-core'; import { AutomapperProfile, InjectMapper } from 'automapper-nestjs'; -import { Injectable } from '@nestjs/common'; -import { AlcsApplicationSubmissionDto } from '../../alcs/application/application.dto'; import { ApplicationSubmissionStatusType } from '../../alcs/application/application-submission-status/submission-status-type.entity'; import { ApplicationStatusDto, ApplicationSubmissionToSubmissionStatusDto, } from '../../alcs/application/application-submission-status/submission-status.dto'; import { ApplicationSubmissionToSubmissionStatus } from '../../alcs/application/application-submission-status/submission-status.entity'; +import { AlcsApplicationSubmissionDto } from '../../alcs/application/application.dto'; import { ApplicationOwnerDetailedDto, ApplicationOwnerDto, @@ -22,8 +22,6 @@ import { ApplicationSubmission } from '../../portal/application-submission/appli import { CovenantTransfereeDto } from '../../portal/application-submission/covenant-transferee/covenant-transferee.dto'; import { CovenantTransferee } from '../../portal/application-submission/covenant-transferee/covenant-transferee.entity'; import { NaruSubtype } from '../../portal/application-submission/naru-subtype/naru-subtype.entity'; -import { NotificationTransfereeDto } from '../../portal/notification-submission/notification-transferee/notification-transferee.dto'; -import { NotificationTransferee } from '../../portal/notification-submission/notification-transferee/notification-transferee.entity'; @Injectable() export class ApplicationSubmissionProfile extends AutomapperProfile { From 1cc3f0ac892f56b2a256a7bd43d1bd96faf94463 Mon Sep 17 00:00:00 2001 From: Liam Stoddard Date: Wed, 3 Apr 2024 16:50:07 -0700 Subject: [PATCH 097/153] MR fixes --- .../oats_to_alcs_planning_review_type.py | 8 ++--- .../init_planning_review_decisions.py | 34 ++++++++----------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/bin/migrate-oats-data/common/oats_to_alcs_planning_review_type.py b/bin/migrate-oats-data/common/oats_to_alcs_planning_review_type.py index 2c130a92a5..867ce115ff 100644 --- a/bin/migrate-oats-data/common/oats_to_alcs_planning_review_type.py +++ b/bin/migrate-oats-data/common/oats_to_alcs_planning_review_type.py @@ -21,7 +21,7 @@ class OatsToAlcsPlanningReviewType(Enum): class OatsToAlcsDecisionOutcomes(Enum): - END = AlcsPlanningReviewOutcomes.ENDO - NOTEND = AlcsPlanningReviewOutcomes.NEND - PART = AlcsPlanningReviewOutcomes.PEND - UNC = AlcsPlanningReviewOutcomes.OTHR + END = AlcsPlanningReviewOutcomes.ENDO.value + NOTEND = AlcsPlanningReviewOutcomes.NEND.value + PART = AlcsPlanningReviewOutcomes.PEND.value + UNC = AlcsPlanningReviewOutcomes.OTHR.value diff --git a/bin/migrate-oats-data/planning_review/decisions/init_planning_review_decisions.py b/bin/migrate-oats-data/planning_review/decisions/init_planning_review_decisions.py index be5eaf6d70..a9bab23e14 100644 --- a/bin/migrate-oats-data/planning_review/decisions/init_planning_review_decisions.py +++ b/bin/migrate-oats-data/planning_review/decisions/init_planning_review_decisions.py @@ -17,7 +17,7 @@ @inject_conn_pool def process_planning_review_decisions(conn=None, batch_size=BATCH_UPLOAD_SIZE): """ - This function is responsible for populating alcs.planning_referral in ALCS. + This function is responsible for populating alcs.planning_review_decision in ALCS. Args: conn (psycopg2.extensions.connection): PostgreSQL database connection. Provided by the decorator. @@ -38,7 +38,7 @@ def process_planning_review_decisions(conn=None, batch_size=BATCH_UPLOAD_SIZE): failed_inserts_count = 0 successful_inserts_count = 0 - last_planning_review_id = 0 + last_planning_decision_id = 0 with open( "planning_review/sql/decisions/planning_review_decisions_insert.sql", @@ -50,7 +50,7 @@ def process_planning_review_decisions(conn=None, batch_size=BATCH_UPLOAD_SIZE): cursor.execute( f""" {query} - WHERE opd.planning_decision_id > {last_planning_review_id} ORDER BY opd.planning_decision_id; + WHERE opd.planning_decision_id > {last_planning_decision_id} ORDER BY opd.planning_decision_id; """ ) @@ -64,19 +64,19 @@ def process_planning_review_decisions(conn=None, batch_size=BATCH_UPLOAD_SIZE): successful_inserts_count = successful_inserts_count + len( inserted_data ) - last_planning_review_id = dict(inserted_data[-1])[ + last_planning_decision_id = dict(inserted_data[-1])[ "planning_decision_id" ] logger.debug( - f"Retrieved/updated items count: {len(inserted_data)}; total successfully inserted planning referral so far {successful_inserts_count}; last updated planning_decision_id: {last_planning_review_id}" + f"Retrieved/updated items count: {len(inserted_data)}; total successfully inserted planning referral so far {successful_inserts_count}; last updated planning_decision_id: {last_planning_decision_id}" ) except Exception as err: - # this is NOT going to be caused by actual data insert failure. This code is only executed when the code error appears or connection to DB is lost + logger.exception(err) conn.rollback() failed_inserts_count = count_total - successful_inserts_count - last_planning_review_id = last_planning_review_id + 1 + last_planning_decision_id = last_planning_decision_id + 1 logger.info( f"Finished {etl_name}: total amount of successful inserts {successful_inserts_count}, total failed inserts {failed_inserts_count}" @@ -148,17 +148,16 @@ def _map_date(data): def _map_outcome_code(data): oats_code = data.get("planning_acceptance_code", "") - try: - alcs_name = OatsToAlcsDecisionOutcomes[oats_code].value.value + if any(oats_code == item.name for item in OatsToAlcsDecisionOutcomes): + alcs_value = OatsToAlcsDecisionOutcomes[oats_code].value for outcome in AlcsPlanningReviewOutcomes: - if outcome.value == alcs_name: + if outcome.value == alcs_value: return outcome.name - except KeyError: - file_number = data.get("planning_decision_id") - logger.info( - f"Key Error for{file_number}, no match to {oats_code} in ALCS, Override to OTHR" - ) - return "OTHR" + file_number = data.get("planning_decision_id") + logger.info( + f"Key Error for planning_decision_id {file_number}, no match to {oats_code} in ALCS, Override to OTHR" + ) + return "OTHR" def _map_resolution_year(data): @@ -171,9 +170,6 @@ def _map_resolution_year(data): date_object = full_date year = date_object.year return year - # date_object = datetime.strptime(full_date, "%Y-%m-%d %H:%M:%S.%f") - # year = date_object.year - # return year @inject_conn_pool From 04ce504925d18f84fb263efe5cb0eb2007e78bd1 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Thu, 4 Apr 2024 10:13:12 -0700 Subject: [PATCH 098/153] Update Backend --- .../src/portal/guards/maintenance.guard.ts | 13 +- services/package-lock.json | 5922 ++++++++--------- services/package.json | 116 +- 3 files changed, 2977 insertions(+), 3074 deletions(-) diff --git a/services/apps/alcs/src/portal/guards/maintenance.guard.ts b/services/apps/alcs/src/portal/guards/maintenance.guard.ts index 2a0ff61f25..cd69aec2e0 100644 --- a/services/apps/alcs/src/portal/guards/maintenance.guard.ts +++ b/services/apps/alcs/src/portal/guards/maintenance.guard.ts @@ -6,12 +6,12 @@ import { Injectable, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { FastifyRequest } from 'fastify'; import { Repository } from 'typeorm'; import { CONFIG_VALUE, Configuration, } from '../../common/entities/configuration.entity'; -import { FastifyRequest } from 'fastify'; @Injectable() export class MaintenanceGuard implements CanActivate { @@ -28,11 +28,14 @@ export class MaintenanceGuard implements CanActivate { return true; } + const routeUrl = req.routeOptions.url; + if ( - req.routeOptions.url.startsWith('/portal') || - req.routeOptions.url.startsWith('/public') || - req.routeOptions.url.startsWith('/api/portal') || - req.routeOptions.url.startsWith('/api/public') + routeUrl && + (routeUrl.startsWith('/portal') || + routeUrl.startsWith('/public') || + routeUrl.startsWith('/api/portal') || + routeUrl.startsWith('/api/public')) ) { const maintenanceMode = await this.configurationRepository.findOne({ where: { diff --git a/services/package-lock.json b/services/package-lock.json index 5727f9f0b1..ef584fcb75 100644 --- a/services/package-lock.json +++ b/services/package-lock.json @@ -9,49 +9,49 @@ "version": "0.0.1", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-s3": "^3.441.0", - "@aws-sdk/s3-request-presigner": "^3.441.0", - "@fastify/cors": "^8.4.1", + "@aws-sdk/client-s3": "^3.540.0", + "@aws-sdk/s3-request-presigner": "^3.540.0", + "@fastify/cors": "^9.0.1", "@fastify/helmet": "^11.1.1", - "@fastify/multipart": "^8.0.0", - "@fastify/static": "^6.12.0", - "@grpc/grpc-js": "^1.8.7", - "@grpc/proto-loader": "^0.7.4", - "@nestjs/axios": "^3.0.1", - "@nestjs/bullmq": "^10.0.1", - "@nestjs/common": "^10.2.8", - "@nestjs/config": "^3.1.1", - "@nestjs/core": "^10.2.8", - "@nestjs/microservices": "^10.2.8", - "@nestjs/platform-fastify": "^10.2.8", - "@nestjs/swagger": "^7.1.14", + "@fastify/multipart": "^8.2.0", + "@fastify/static": "^7.0.3", + "@grpc/grpc-js": "^1.10.6", + "@grpc/proto-loader": "^0.7.12", + "@nestjs/axios": "^3.0.2", + "@nestjs/bullmq": "^10.1.1", + "@nestjs/common": "^10.3.7", + "@nestjs/config": "^3.2.1", + "@nestjs/core": "^10.3.7", + "@nestjs/microservices": "^10.3.7", + "@nestjs/platform-fastify": "^10.3.7", + "@nestjs/swagger": "^7.3.1", "@nestjs/typeorm": "^10.0.2", - "@types/clamscan": "^2.0.6", - "automapper-classes": "^8.7.11", - "automapper-core": "^8.7.11", - "automapper-nestjs": "^8.7.11", - "clamscan": "^2.1.2", + "@types/clamscan": "^2.0.8", + "automapper-classes": "^8.7.12", + "automapper-core": "^8.7.12", + "automapper-nestjs": "^8.7.12", + "clamscan": "^2.2.1", "class-transformer": "^0.5.1", - "class-validator": "^0.14.0", - "config": "^3.3.9", + "class-validator": "^0.14.1", + "config": "^3.3.11", "csv-parser": "^3.0.0", "dayjs": "^1.11.10", "handlebars": "^4.7.8", - "keycloak-connect": "^21.1.2", - "mjml": "^4.14.1", - "nest-keycloak-connect": "^1.9.2", - "nestjs-cls": "^3.6.0", - "nestjs-grpc-reflection": "^0.0.18", - "nestjs-pino": "^3.5.0", + "keycloak-connect": "^24.0.2", + "mjml": "^4.15.3", + "nest-keycloak-connect": "^1.10.0", + "nestjs-cls": "^4.3.0", + "nestjs-grpc-reflection": "^0.2.2", + "nestjs-pino": "^4.0.0", "nestjs-spelunker": "^1.3.0", "node-jose": "^2.2.0", - "pg": "^8.11.3", - "redis": "^4.6.10", - "reflect-metadata": "^0.1.13", + "pg": "^8.11.5", + "redis": "^4.6.13", + "reflect-metadata": "^0.1.14", "rimraf": "^5.0.5", "rxjs": "^7.8.1", "source-map-support": "^0.5.21", - "ts-proto": "^1.163.0", + "ts-proto": "^1.171.0", "ts-protoc-gen": "^0.15.0", "typeorm": "^0.3.20", "typeorm-naming-strategies": "^4.1.0", @@ -59,34 +59,34 @@ }, "devDependencies": { "@golevelup/nestjs-testing": "^0.1.2", - "@nestjs/cli": "^10.2.1", - "@nestjs/schematics": "^10.0.3", - "@nestjs/testing": "^10.2.8", - "@types/config": "^3.3.2", - "@types/express": "^4.17.20", - "@types/jest": "^29.5.7", - "@types/node": "^20.8.10", - "@types/node-jose": "^1.1.12", - "@types/source-map-support": "^0.5.9", - "@types/supertest": "^2.0.15", - "@types/uuid": "^9.0.6", - "@typescript-eslint/eslint-plugin": "^6.9.1", - "@typescript-eslint/parser": "^6.9.1", - "aws-sdk-client-mock": "^2.0.1", - "eslint": "^8.52.0", - "eslint-config-prettier": "^9.0.0", - "eslint-plugin-prettier": "^5.0.1", + "@nestjs/cli": "^10.3.2", + "@nestjs/schematics": "^10.1.1", + "@nestjs/testing": "^10.3.7", + "@types/config": "^3.3.4", + "@types/express": "^4.17.21", + "@types/jest": "^29.5.12", + "@types/node": "^20.12.4", + "@types/node-jose": "^1.1.13", + "@types/source-map-support": "^0.5.10", + "@types/supertest": "^6.0.2", + "@types/uuid": "^9.0.8", + "@typescript-eslint/eslint-plugin": "^7.5.0", + "@typescript-eslint/parser": "^7.5.0", + "aws-sdk-client-mock": "^4.0.0", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", "jest": "^29.7.0", - "memfs": "^4.6.0", - "pino-pretty": "^10.2.3", - "prettier": "^3.0.3", - "supertest": "^6.3.3", - "ts-jest": "^29.1.1", - "ts-loader": "^9.5.0", - "ts-node": "^10.9.1", + "memfs": "^4.8.1", + "pino-pretty": "^11.0.0", + "prettier": "^3.2.5", + "supertest": "^6.3.4", + "ts-jest": "^29.1.2", + "ts-loader": "^9.5.1", + "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", - "typescript": "^5.2.2", - "unionfs": "^4.5.1" + "typescript": "^5.4.3", + "unionfs": "^4.5.4" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -99,33 +99,33 @@ } }, "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@angular-devkit/core": { - "version": "16.2.8", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.8.tgz", - "integrity": "sha512-PTGozYvh1Bin5lB15PwcXa26Ayd17bWGLS3H8Rs0s+04mUDvfNofmweaX1LgumWWy3nCUTDuwHxX10M3G0wE2g==", + "version": "17.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.1.2.tgz", + "integrity": "sha512-ku+/W/HMCBacSWFppenr9y6Lx8mDuTuQvn1IkTyBLiJOpWnzgVbx9kHDeaDchGa1PwLlJUBBrv27t3qgJOIDPw==", "dev": true, "dependencies": { "ajv": "8.12.0", "ajv-formats": "2.1.1", "jsonc-parser": "3.2.0", - "picomatch": "2.3.1", + "picomatch": "3.0.1", "rxjs": "7.8.1", "source-map": "0.7.4" }, "engines": { - "node": "^16.14.0 || >=18.10.0", + "node": "^18.13.0 || >=20.9.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, @@ -139,33 +139,33 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "16.2.8", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.2.8.tgz", - "integrity": "sha512-MBiKZOlR9/YMdflALr7/7w/BGAfo/BGTrlkqsIB6rDWV1dYiCgxI+033HsiNssLS6RQyCFx/e7JA2aBBzu9zEg==", + "version": "17.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.1.2.tgz", + "integrity": "sha512-8S9RuM8olFN/gwN+mjbuF1CwHX61f0i59EGXz9tXLnKRUTjsRR+8vVMTAmX0dvVAT5fJTG/T69X+HX7FeumdqA==", "dev": true, "dependencies": { - "@angular-devkit/core": "16.2.8", + "@angular-devkit/core": "17.1.2", "jsonc-parser": "3.2.0", - "magic-string": "0.30.1", + "magic-string": "0.30.5", "ora": "5.4.1", "rxjs": "7.8.1" }, "engines": { - "node": "^16.14.0 || >=18.10.0", + "node": "^18.13.0 || >=20.9.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, "node_modules/@angular-devkit/schematics-cli": { - "version": "16.2.8", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-16.2.8.tgz", - "integrity": "sha512-EXURJCzWTVYCipiTT4vxQQOrF63asOUDbeOy3OtiSh7EwIUvxm3BPG6hquJqngEnI/N6bA75NJ1fBhU6Hrh7eA==", + "version": "17.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-17.1.2.tgz", + "integrity": "sha512-bvXykYzSST05qFdlgIzUguNOb3z0hCa8HaTwtqdmQo9aFPf+P+/AC56I64t1iTchMjQtf3JrBQhYM25gUdcGbg==", "dev": true, "dependencies": { - "@angular-devkit/core": "16.2.8", - "@angular-devkit/schematics": "16.2.8", + "@angular-devkit/core": "17.1.2", + "@angular-devkit/schematics": "17.1.2", "ansi-colors": "4.1.3", - "inquirer": "8.2.4", + "inquirer": "9.2.12", "symbol-observable": "4.0.0", "yargs-parser": "21.1.1" }, @@ -173,52 +173,114 @@ "schematics": "bin/schematics.js" }, "engines": { - "node": "^16.14.0 || >=18.10.0", + "node": "^18.13.0 || >=20.9.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, + "node_modules/@angular-devkit/schematics-cli/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@angular-devkit/schematics-cli/node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@angular-devkit/schematics-cli/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@angular-devkit/schematics-cli/node_modules/figures": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", + "integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^5.0.0", + "is-unicode-supported": "^1.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@angular-devkit/schematics-cli/node_modules/inquirer": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz", - "integrity": "sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==", + "version": "9.2.12", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.12.tgz", + "integrity": "sha512-mg3Fh9g2zfuVWJn6lhST0O7x4n03k7G8Tx5nvikJkbq8/CK47WDVm+UznF0G6s5Zi0KcyUisr6DU8T67N5U+1Q==", "dev": true, "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", + "@ljharb/through": "^2.3.11", + "ansi-escapes": "^4.3.2", + "chalk": "^5.3.0", "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", + "cli-width": "^4.1.0", + "external-editor": "^3.1.0", + "figures": "^5.0.0", "lodash": "^4.17.21", - "mute-stream": "0.0.8", + "mute-stream": "1.0.0", "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^7.0.0" + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.18.0" } }, - "node_modules/@angular-devkit/schematics-cli/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "node_modules/@angular-devkit/schematics-cli/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@angular-devkit/schematics-cli/node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@angular-devkit/schematics-cli/node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "dev": true, + "engines": { + "node": ">=0.12.0" } }, "node_modules/@aws-crypto/crc32": { @@ -347,634 +409,678 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@aws-sdk/client-s3": { - "version": "3.441.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.441.0.tgz", - "integrity": "sha512-tJUhHk4Nvakw/q3IVI2oDFCu48DzuPCMu2G3n42JPyvmY0RvmtRjduduoG1lYIGgRKJu81/MFr9i8CGYNK+/5A==", + "version": "3.540.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.540.0.tgz", + "integrity": "sha512-rYBuNB7uqCO9xZc0OAwM2K6QJAo2Syt1L5OhEaf7zG7FulNMyrK6kJPg1WrvNE90tW6gUdDaTy3XsQ7lq6O7uA==", "dependencies": { "@aws-crypto/sha1-browser": "3.0.0", "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.441.0", - "@aws-sdk/core": "3.441.0", - "@aws-sdk/credential-provider-node": "3.441.0", - "@aws-sdk/middleware-bucket-endpoint": "3.433.0", - "@aws-sdk/middleware-expect-continue": "3.433.0", - "@aws-sdk/middleware-flexible-checksums": "3.433.0", - "@aws-sdk/middleware-host-header": "3.433.0", - "@aws-sdk/middleware-location-constraint": "3.433.0", - "@aws-sdk/middleware-logger": "3.433.0", - "@aws-sdk/middleware-recursion-detection": "3.433.0", - "@aws-sdk/middleware-sdk-s3": "3.440.0", - "@aws-sdk/middleware-signing": "3.433.0", - "@aws-sdk/middleware-ssec": "3.433.0", - "@aws-sdk/middleware-user-agent": "3.438.0", - "@aws-sdk/region-config-resolver": "3.433.0", - "@aws-sdk/signature-v4-multi-region": "3.437.0", - "@aws-sdk/types": "3.433.0", - "@aws-sdk/util-endpoints": "3.438.0", - "@aws-sdk/util-user-agent-browser": "3.433.0", - "@aws-sdk/util-user-agent-node": "3.437.0", - "@aws-sdk/xml-builder": "3.310.0", - "@smithy/config-resolver": "^2.0.16", - "@smithy/eventstream-serde-browser": "^2.0.12", - "@smithy/eventstream-serde-config-resolver": "^2.0.12", - "@smithy/eventstream-serde-node": "^2.0.12", - "@smithy/fetch-http-handler": "^2.2.4", - "@smithy/hash-blob-browser": "^2.0.12", - "@smithy/hash-node": "^2.0.12", - "@smithy/hash-stream-node": "^2.0.12", - "@smithy/invalid-dependency": "^2.0.12", - "@smithy/md5-js": "^2.0.12", - "@smithy/middleware-content-length": "^2.0.14", - "@smithy/middleware-endpoint": "^2.1.3", - "@smithy/middleware-retry": "^2.0.18", - "@smithy/middleware-serde": "^2.0.12", - "@smithy/middleware-stack": "^2.0.6", - "@smithy/node-config-provider": "^2.1.3", - "@smithy/node-http-handler": "^2.1.8", - "@smithy/protocol-http": "^3.0.8", - "@smithy/smithy-client": "^2.1.12", - "@smithy/types": "^2.4.0", - "@smithy/url-parser": "^2.0.12", - "@smithy/util-base64": "^2.0.0", - "@smithy/util-body-length-browser": "^2.0.0", - "@smithy/util-body-length-node": "^2.1.0", - "@smithy/util-defaults-mode-browser": "^2.0.16", - "@smithy/util-defaults-mode-node": "^2.0.21", - "@smithy/util-endpoints": "^1.0.2", - "@smithy/util-retry": "^2.0.5", - "@smithy/util-stream": "^2.0.17", - "@smithy/util-utf8": "^2.0.0", - "@smithy/util-waiter": "^2.0.12", - "fast-xml-parser": "4.2.5", - "tslib": "^2.5.0" + "@aws-sdk/client-sts": "3.540.0", + "@aws-sdk/core": "3.535.0", + "@aws-sdk/credential-provider-node": "3.540.0", + "@aws-sdk/middleware-bucket-endpoint": "3.535.0", + "@aws-sdk/middleware-expect-continue": "3.535.0", + "@aws-sdk/middleware-flexible-checksums": "3.535.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-location-constraint": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-sdk-s3": "3.535.0", + "@aws-sdk/middleware-signing": "3.535.0", + "@aws-sdk/middleware-ssec": "3.537.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/signature-v4-multi-region": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@aws-sdk/xml-builder": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.0", + "@smithy/eventstream-serde-browser": "^2.2.0", + "@smithy/eventstream-serde-config-resolver": "^2.2.0", + "@smithy/eventstream-serde-node": "^2.2.0", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-blob-browser": "^2.2.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/hash-stream-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/md5-js": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.0", + "@smithy/middleware-retry": "^2.2.0", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.0", + "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-stream": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "@smithy/util-waiter": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.441.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.441.0.tgz", - "integrity": "sha512-gndGymu4cEIN7WWhQ67RO0JMda09EGBlay2L8IKCHBK/65Y34FHUX1tCNbO2qezEzsi6BPW5o2n53Rd9QqpHUw==", + "version": "3.540.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.540.0.tgz", + "integrity": "sha512-rrQZMuw4sxIo3eyAUUzPQRA336mPRnrAeSlSdVHBKZD8Fjvoy0lYry2vNhkPLpFZLso1J66KRyuIv4LzRR3v1Q==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.535.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.0", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.0", + "@smithy/middleware-retry": "^2.2.0", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.0", + "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.540.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.540.0.tgz", + "integrity": "sha512-LZYK0lBRQK8D8M3Sqc96XiXkAV2v70zhTtF6weyzEpgwxZMfSuFJjs0jFyhaeZBZbZv7BBghIdhJ5TPavNxGMQ==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.441.0", - "@aws-sdk/middleware-host-header": "3.433.0", - "@aws-sdk/middleware-logger": "3.433.0", - "@aws-sdk/middleware-recursion-detection": "3.433.0", - "@aws-sdk/middleware-user-agent": "3.438.0", - "@aws-sdk/region-config-resolver": "3.433.0", - "@aws-sdk/types": "3.433.0", - "@aws-sdk/util-endpoints": "3.438.0", - "@aws-sdk/util-user-agent-browser": "3.433.0", - "@aws-sdk/util-user-agent-node": "3.437.0", - "@smithy/config-resolver": "^2.0.16", - "@smithy/fetch-http-handler": "^2.2.4", - "@smithy/hash-node": "^2.0.12", - "@smithy/invalid-dependency": "^2.0.12", - "@smithy/middleware-content-length": "^2.0.14", - "@smithy/middleware-endpoint": "^2.1.3", - "@smithy/middleware-retry": "^2.0.18", - "@smithy/middleware-serde": "^2.0.12", - "@smithy/middleware-stack": "^2.0.6", - "@smithy/node-config-provider": "^2.1.3", - "@smithy/node-http-handler": "^2.1.8", - "@smithy/protocol-http": "^3.0.8", - "@smithy/smithy-client": "^2.1.12", - "@smithy/types": "^2.4.0", - "@smithy/url-parser": "^2.0.12", - "@smithy/util-base64": "^2.0.0", - "@smithy/util-body-length-browser": "^2.0.0", - "@smithy/util-body-length-node": "^2.1.0", - "@smithy/util-defaults-mode-browser": "^2.0.16", - "@smithy/util-defaults-mode-node": "^2.0.21", - "@smithy/util-endpoints": "^1.0.2", - "@smithy/util-retry": "^2.0.5", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.5.0" + "@aws-sdk/client-sts": "3.540.0", + "@aws-sdk/core": "3.535.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.0", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.0", + "@smithy/middleware-retry": "^2.2.0", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.0", + "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": "^3.540.0" } }, "node_modules/@aws-sdk/client-sts": { - "version": "3.441.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.441.0.tgz", - "integrity": "sha512-GL0Cw2v7XL1cn0T+Sk5VHLlgBJoUdMsysXsHa1mFdk0l6XHMAAnwXVXiNnjmoDSPrG0psz7dL2AKzPVRXbIUjA==", + "version": "3.540.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.540.0.tgz", + "integrity": "sha512-ITHUQxvpqfQX6obfpIi3KYGzZYfe/I5Ixjfxoi5lB7ISCtmxqObKB1fzD93wonkMJytJ7LUO8panZl/ojiJ1uw==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.441.0", - "@aws-sdk/credential-provider-node": "3.441.0", - "@aws-sdk/middleware-host-header": "3.433.0", - "@aws-sdk/middleware-logger": "3.433.0", - "@aws-sdk/middleware-recursion-detection": "3.433.0", - "@aws-sdk/middleware-sdk-sts": "3.433.0", - "@aws-sdk/middleware-signing": "3.433.0", - "@aws-sdk/middleware-user-agent": "3.438.0", - "@aws-sdk/region-config-resolver": "3.433.0", - "@aws-sdk/types": "3.433.0", - "@aws-sdk/util-endpoints": "3.438.0", - "@aws-sdk/util-user-agent-browser": "3.433.0", - "@aws-sdk/util-user-agent-node": "3.437.0", - "@smithy/config-resolver": "^2.0.16", - "@smithy/fetch-http-handler": "^2.2.4", - "@smithy/hash-node": "^2.0.12", - "@smithy/invalid-dependency": "^2.0.12", - "@smithy/middleware-content-length": "^2.0.14", - "@smithy/middleware-endpoint": "^2.1.3", - "@smithy/middleware-retry": "^2.0.18", - "@smithy/middleware-serde": "^2.0.12", - "@smithy/middleware-stack": "^2.0.6", - "@smithy/node-config-provider": "^2.1.3", - "@smithy/node-http-handler": "^2.1.8", - "@smithy/protocol-http": "^3.0.8", - "@smithy/smithy-client": "^2.1.12", - "@smithy/types": "^2.4.0", - "@smithy/url-parser": "^2.0.12", - "@smithy/util-base64": "^2.0.0", - "@smithy/util-body-length-browser": "^2.0.0", - "@smithy/util-body-length-node": "^2.1.0", - "@smithy/util-defaults-mode-browser": "^2.0.16", - "@smithy/util-defaults-mode-node": "^2.0.21", - "@smithy/util-endpoints": "^1.0.2", - "@smithy/util-retry": "^2.0.5", - "@smithy/util-utf8": "^2.0.0", - "fast-xml-parser": "4.2.5", - "tslib": "^2.5.0" + "@aws-sdk/core": "3.535.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.0", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.0", + "@smithy/middleware-retry": "^2.2.0", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.0", + "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": "^3.540.0" } }, "node_modules/@aws-sdk/core": { - "version": "3.441.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.441.0.tgz", - "integrity": "sha512-gV0eQwR0VnSPUYAbgDkbBtfXbSpZgl/K6UB13DP1IFFjQYbF/BxYwvcQe4jHoPOBifSgjEbl8MfOOeIyI7k9vg==", - "dependencies": { - "@smithy/smithy-client": "^2.1.12" + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.535.0.tgz", + "integrity": "sha512-+Yusa9HziuaEDta1UaLEtMAtmgvxdxhPn7jgfRY6PplqAqgsfa5FR83sxy5qr2q7xjQTwHtV4MjQVuOjG9JsLw==", + "dependencies": { + "@smithy/core": "^1.4.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/signature-v4": "^2.2.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "fast-xml-parser": "4.2.5", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.433.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.433.0.tgz", - "integrity": "sha512-Vl7Qz5qYyxBurMn6hfSiNJeUHSqfVUlMt0C1Bds3tCkl3IzecRWwyBOlxtxO3VCrgVeW3HqswLzCvhAFzPH6nQ==", + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.535.0.tgz", + "integrity": "sha512-XppwO8c0GCGSAvdzyJOhbtktSEaShg14VJKg8mpMa1XcgqzmcqqHQjtDWbx5rZheY1VdpXZhpEzJkB6LpQejpA==", "dependencies": { - "@aws-sdk/types": "3.433.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.535.0.tgz", + "integrity": "sha512-kdj1wCmOMZ29jSlUskRqN04S6fJ4dvt0Nq9Z32SA6wO7UG8ht6Ot9h/au/eTWJM3E1somZ7D771oK7dQt9b8yw==", + "dependencies": { + "@aws-sdk/types": "3.535.0", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/util-stream": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.441.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.441.0.tgz", - "integrity": "sha512-SQipQYxYqDUuSOfIhDmaTdwPTcndGQotGZXWJl56mMWqAhU8MkwjK+oMf3VgRt/umJC0QwUCF5HUHIj7gSB1JA==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.433.0", - "@aws-sdk/credential-provider-process": "3.433.0", - "@aws-sdk/credential-provider-sso": "3.441.0", - "@aws-sdk/credential-provider-web-identity": "3.433.0", - "@aws-sdk/types": "3.433.0", - "@smithy/credential-provider-imds": "^2.0.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/shared-ini-file-loader": "^2.0.6", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "version": "3.540.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.540.0.tgz", + "integrity": "sha512-igN/RbsnulIBwqXbwsWmR3srqmtbPF1dm+JteGvUY31FW65fTVvWvSr945Y/cf1UbhPmIQXntlsqESqpkhTHwg==", + "dependencies": { + "@aws-sdk/client-sts": "3.540.0", + "@aws-sdk/credential-provider-env": "3.535.0", + "@aws-sdk/credential-provider-process": "3.535.0", + "@aws-sdk/credential-provider-sso": "3.540.0", + "@aws-sdk/credential-provider-web-identity": "3.540.0", + "@aws-sdk/types": "3.535.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.441.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.441.0.tgz", - "integrity": "sha512-WB9p37yHq6fGJt6Vll29ijHbkh9VDbPM/n5ns73bTAgFD7R0ht5kPmdmHGQA6m3RKjcHLPbymQ3lXykkMwWf/Q==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.433.0", - "@aws-sdk/credential-provider-ini": "3.441.0", - "@aws-sdk/credential-provider-process": "3.433.0", - "@aws-sdk/credential-provider-sso": "3.441.0", - "@aws-sdk/credential-provider-web-identity": "3.433.0", - "@aws-sdk/types": "3.433.0", - "@smithy/credential-provider-imds": "^2.0.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/shared-ini-file-loader": "^2.0.6", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "version": "3.540.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.540.0.tgz", + "integrity": "sha512-HKQZJbLHlrHX9A0B1poiYNXIIQfy8whTjuosTCYKPDBhhUyVAQfxy/KG726j0v43IhaNPLgTGZCJve4hAsazSw==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.535.0", + "@aws-sdk/credential-provider-http": "3.535.0", + "@aws-sdk/credential-provider-ini": "3.540.0", + "@aws-sdk/credential-provider-process": "3.535.0", + "@aws-sdk/credential-provider-sso": "3.540.0", + "@aws-sdk/credential-provider-web-identity": "3.540.0", + "@aws-sdk/types": "3.535.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.433.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.433.0.tgz", - "integrity": "sha512-W7FcGlQjio9Y/PepcZGRyl5Bpwb0uWU7qIUCh+u4+q2mW4D5ZngXg8V/opL9/I/p4tUH9VXZLyLGwyBSkdhL+A==", + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.535.0.tgz", + "integrity": "sha512-9O1OaprGCnlb/kYl8RwmH7Mlg8JREZctB8r9sa1KhSsWFq/SWO0AuJTyowxD7zL5PkeS4eTvzFFHWCa3OO5epA==", "dependencies": { - "@aws-sdk/types": "3.433.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/shared-ini-file-loader": "^2.0.6", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.441.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.441.0.tgz", - "integrity": "sha512-pTg16G+62mWCE8yGKuQnEBqPdpG5g71remf2jUqXaI1c7GCzbnkQDV9eD4DaAGOvzIs0wo9zAQnS2kVDPFlCYA==", + "version": "3.540.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.540.0.tgz", + "integrity": "sha512-tKkFqK227LF5ajc5EL6asXS32p3nkofpP8G7NRpU7zOEOQCg01KUc4JRX+ItI0T007CiN1J19yNoFqHLT/SqHg==", "dependencies": { - "@aws-sdk/client-sso": "3.441.0", - "@aws-sdk/token-providers": "3.438.0", - "@aws-sdk/types": "3.433.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/shared-ini-file-loader": "^2.0.6", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@aws-sdk/client-sso": "3.540.0", + "@aws-sdk/token-providers": "3.540.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.433.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.433.0.tgz", - "integrity": "sha512-RlwjP1I5wO+aPpwyCp23Mk8nmRbRL33hqRASy73c4JA2z2YiRua+ryt6MalIxehhwQU6xvXUKulJnPG9VaMFZg==", + "version": "3.540.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.540.0.tgz", + "integrity": "sha512-OpDm9w3A168B44hSjpnvECP4rvnFzD86rN4VYdGADuCvEa5uEcdA/JuT5WclFPDqdWEmFBqS1pxBIJBf0g2Q9Q==", "dependencies": { - "@aws-sdk/types": "3.433.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@aws-sdk/client-sts": "3.540.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.433.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.433.0.tgz", - "integrity": "sha512-Lk1xIu2tWTRa1zDw5hCF1RrpWQYSodUhrS/q3oKz8IAoFqEy+lNaD5jx+fycuZb5EkE4IzWysT+8wVkd0mAnOg==", + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.535.0.tgz", + "integrity": "sha512-7sijlfQsc4UO9Fsl11mU26Y5f9E7g6UoNg/iJUBpC5pgvvmdBRO5UEhbB/gnqvOEPsBXyhmfzbstebq23Qdz7A==", "dependencies": { - "@aws-sdk/types": "3.433.0", - "@aws-sdk/util-arn-parser": "3.310.0", - "@smithy/node-config-provider": "^2.1.3", - "@smithy/protocol-http": "^3.0.8", - "@smithy/types": "^2.4.0", - "@smithy/util-config-provider": "^2.0.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-arn-parser": "3.535.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/types": "^2.12.0", + "@smithy/util-config-provider": "^2.3.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.433.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.433.0.tgz", - "integrity": "sha512-Uq2rPIsjz0CR2sulM/HyYr5WiqiefrSRLdwUZuA7opxFSfE808w5DBWSprHxbH3rbDSQR4nFiOiVYIH8Eth7nA==", + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.535.0.tgz", + "integrity": "sha512-hFKyqUBky0NWCVku8iZ9+PACehx0p6vuMw5YnZf8FVgHP0fode0b/NwQY6UY7oor/GftvRsAlRUAWGNFEGUpwA==", "dependencies": { - "@aws-sdk/types": "3.433.0", - "@smithy/protocol-http": "^3.0.8", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.535.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.433.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.433.0.tgz", - "integrity": "sha512-Ptssx373+I7EzFUWjp/i/YiNFt6I6sDuRHz6DOUR9nmmRTlHHqmdcBXlJL2d9wwFxoBRCN8/PXGsTc/DJ4c95Q==", + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.535.0.tgz", + "integrity": "sha512-rBIzldY9jjRATxICDX7t77aW6ctqmVDgnuAOgbVT5xgHftt4o7PGWKoMvl/45hYqoQgxVFnCBof9bxkqSBebVA==", "dependencies": { "@aws-crypto/crc32": "3.0.0", "@aws-crypto/crc32c": "3.0.0", - "@aws-sdk/types": "3.433.0", - "@smithy/is-array-buffer": "^2.0.0", - "@smithy/protocol-http": "^3.0.8", - "@smithy/types": "^2.4.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.535.0", + "@smithy/is-array-buffer": "^2.2.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/types": "^2.12.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.433.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.433.0.tgz", - "integrity": "sha512-mBTq3UWv1UzeHG+OfUQ2MB/5GEkt5LTKFaUqzL7ESwzW8XtpBgXnjZvIwu3Vcd3sEetMwijwaGiJhY0ae/YyaA==", + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.535.0.tgz", + "integrity": "sha512-0h6TWjBWtDaYwHMQJI9ulafeS4lLaw1vIxRjbpH0svFRt6Eve+Sy8NlVhECfTU2hNz/fLubvrUxsXoThaLBIew==", "dependencies": { - "@aws-sdk/types": "3.433.0", - "@smithy/protocol-http": "^3.0.8", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.535.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.433.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.433.0.tgz", - "integrity": "sha512-2YD860TGntwZifIUbxm+lFnNJJhByR/RB/+fV1I8oGKg+XX2rZU+94pRfHXRywoZKlCA0L+LGDA1I56jxrB9sw==", + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.535.0.tgz", + "integrity": "sha512-SxfS9wfidUZZ+WnlKRTCRn3h+XTsymXRXPJj8VV6hNRNeOwzNweoG3YhQbTowuuNfXf89m9v6meYkBBtkdacKw==", "dependencies": { - "@aws-sdk/types": "3.433.0", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.535.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.433.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.433.0.tgz", - "integrity": "sha512-We346Fb5xGonTGVZC9Nvqtnqy74VJzYuTLLiuuftA5sbNzftBDy/22QCfvYSTOAl3bvif+dkDUzQY2ihc5PwOQ==", + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.535.0.tgz", + "integrity": "sha512-huNHpONOrEDrdRTvSQr1cJiRMNf0S52NDXtaPzdxiubTkP+vni2MohmZANMOai/qT0olmEVX01LhZ0ZAOgmg6A==", "dependencies": { - "@aws-sdk/types": "3.433.0", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.535.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.433.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.433.0.tgz", - "integrity": "sha512-HEvYC9PQlWY/ccUYtLvAlwwf1iCif2TSAmLNr3YTBRVa98x6jKL0hlCrHWYklFeqOGSKy6XhE+NGJMUII0/HaQ==", + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.535.0.tgz", + "integrity": "sha512-am2qgGs+gwqmR4wHLWpzlZ8PWhm4ktj5bYSgDrsOfjhdBlWNxvPoID9/pDAz5RWL48+oH7I6SQzMqxXsFDikrw==", "dependencies": { - "@aws-sdk/types": "3.433.0", - "@smithy/protocol-http": "^3.0.8", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.535.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.440.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.440.0.tgz", - "integrity": "sha512-DVTSr+82Z8jR9xTwDN3YHzxX7qvi0n96V92OfxvSRDq2BldCEx/KEL1orUZjw97SAXhINOlUWjRR7j4HpwWQtQ==", - "dependencies": { - "@aws-sdk/types": "3.433.0", - "@aws-sdk/util-arn-parser": "3.310.0", - "@smithy/protocol-http": "^3.0.8", - "@smithy/smithy-client": "^2.1.12", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-sdk-sts": { - "version": "3.433.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.433.0.tgz", - "integrity": "sha512-ORYbJnBejUyonFl5FwIqhvI3Cq6sAp9j+JpkKZtFNma9tFPdrhmYgfCeNH32H/wGTQV/tUoQ3luh0gA4cuk6DA==", + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.535.0.tgz", + "integrity": "sha512-/dLG/E3af6ohxkQ5GBHT8tZfuPIg6eItKxCXuulvYj0Tqgf3Mb+xTsvSkxQsJF06RS4sH7Qsg/PnB8ZfrJrXpg==", "dependencies": { - "@aws-sdk/middleware-signing": "3.433.0", - "@aws-sdk/types": "3.433.0", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-arn-parser": "3.535.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/signature-v4": "^2.2.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/util-config-provider": "^2.3.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-signing": { - "version": "3.433.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.433.0.tgz", - "integrity": "sha512-jxPvt59NZo/epMNLNTu47ikmP8v0q217I6bQFGJG7JVFnfl36zDktMwGw+0xZR80qiK47/2BWrNpta61Zd2FxQ==", + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.535.0.tgz", + "integrity": "sha512-Rb4sfus1Gc5paRl9JJgymJGsb/i3gJKK/rTuFZICdd1PBBE5osIOHP5CpzWYBtc5LlyZE1a2QoxPMCyG+QUGPw==", "dependencies": { - "@aws-sdk/types": "3.433.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/protocol-http": "^3.0.8", - "@smithy/signature-v4": "^2.0.0", - "@smithy/types": "^2.4.0", - "@smithy/util-middleware": "^2.0.5", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/signature-v4": "^2.2.0", + "@smithy/types": "^2.12.0", + "@smithy/util-middleware": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-ssec": { - "version": "3.433.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.433.0.tgz", - "integrity": "sha512-2AMaPx0kYfCiekxoL7aqFqSSoA9du+yI4zefpQNLr+1cZOerYiDxdsZ4mbqStR1CVFaX6U6hrYokXzjInsvETw==", + "version": "3.537.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.537.0.tgz", + "integrity": "sha512-2QWMrbwd5eBy5KCYn9a15JEWBgrK2qFEKQN2lqb/6z0bhtevIOxIRfC99tzvRuPt6nixFQ+ynKuBjcfT4ZFrdQ==", "dependencies": { - "@aws-sdk/types": "3.433.0", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.535.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.438.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.438.0.tgz", - "integrity": "sha512-a+xHT1wOxT6EA6YyLmrfaroKWOkwwyiktUfXKM0FsUutGzNi4fKhb5NZ2al58NsXzHgHFrasSDp+Lqbd/X2cEw==", + "version": "3.540.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.540.0.tgz", + "integrity": "sha512-8Rd6wPeXDnOYzWj1XCmOKcx/Q87L0K1/EHqOBocGjLVbN3gmRxBvpmR1pRTjf7IsWfnnzN5btqtcAkfDPYQUMQ==", "dependencies": { - "@aws-sdk/types": "3.433.0", - "@aws-sdk/util-endpoints": "3.438.0", - "@smithy/protocol-http": "^3.0.8", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.433.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.433.0.tgz", - "integrity": "sha512-xpjRjCZW+CDFdcMmmhIYg81ST5UAnJh61IHziQEk0FXONrg4kjyYPZAOjEdzXQ+HxJQuGQLKPhRdzxmQnbX7pg==", + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.535.0.tgz", + "integrity": "sha512-IXOznDiaItBjsQy4Fil0kzX/J3HxIOknEphqHbOfUf+LpA5ugcsxuQQONrbEQusCBnfJyymrldBvBhFmtlU9Wg==", "dependencies": { - "@smithy/node-config-provider": "^2.1.3", - "@smithy/types": "^2.4.0", - "@smithy/util-config-provider": "^2.0.0", - "@smithy/util-middleware": "^2.0.5", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.535.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/types": "^2.12.0", + "@smithy/util-config-provider": "^2.3.0", + "@smithy/util-middleware": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/s3-request-presigner": { - "version": "3.441.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.441.0.tgz", - "integrity": "sha512-EUVsmy92imURMLoA/MX+PL1SIONQ8YSi424BHJA6xGEoaqvQiaVKlv8jJfCqJ6qQ8oLiCLe2hOBSBTY1XZiy/g==", + "version": "3.540.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.540.0.tgz", + "integrity": "sha512-alm+PiQOzAIfNrabxOG/Fk9uimQq8VCdqmhRvZRG7iDwtl4yrW+ZinoDssWFUgeZgPZQTymLcslC2hvMKHgY9g==", "dependencies": { - "@aws-sdk/signature-v4-multi-region": "3.437.0", - "@aws-sdk/types": "3.433.0", - "@aws-sdk/util-format-url": "3.433.0", - "@smithy/middleware-endpoint": "^2.1.3", - "@smithy/protocol-http": "^3.0.8", - "@smithy/smithy-client": "^2.1.12", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@aws-sdk/signature-v4-multi-region": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-format-url": "3.535.0", + "@smithy/middleware-endpoint": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.437.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.437.0.tgz", - "integrity": "sha512-MmrqudssOs87JgVg7HGVdvJws/t4kcOrJJd+975ki+DPeSoyK2U4zBDfDkJ+n0tFuZBs3sLwLh0QXE7BV28rRA==", + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.535.0.tgz", + "integrity": "sha512-tqCsEsEj8icW0SAh3NvyhRUq54Gz2pu4NM2tOSrFp7SO55heUUaRLSzYteNZCTOupH//AAaZvbN/UUTO/DrOog==", "dependencies": { - "@aws-sdk/types": "3.433.0", - "@smithy/protocol-http": "^3.0.8", - "@smithy/signature-v4": "^2.0.0", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@aws-sdk/middleware-sdk-s3": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/signature-v4": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.438.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.438.0.tgz", - "integrity": "sha512-G2fUfTtU6/1ayYRMu0Pd9Ln4qYSvwJOWCqJMdkDgvXSwdgcOSOLsnAIk1AHGJDAvgLikdCzuyOsdJiexr9Vnww==", + "version": "3.540.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.540.0.tgz", + "integrity": "sha512-9BvtiVEZe5Ev88Wa4ZIUbtT6BVcPwhxmVInQ6c12MYNb0WNL54BN6wLy/eknAfF05gpX2/NDU2pUDOyMPdm/+g==", "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/middleware-host-header": "3.433.0", - "@aws-sdk/middleware-logger": "3.433.0", - "@aws-sdk/middleware-recursion-detection": "3.433.0", - "@aws-sdk/middleware-user-agent": "3.438.0", - "@aws-sdk/region-config-resolver": "3.433.0", - "@aws-sdk/types": "3.433.0", - "@aws-sdk/util-endpoints": "3.438.0", - "@aws-sdk/util-user-agent-browser": "3.433.0", - "@aws-sdk/util-user-agent-node": "3.437.0", - "@smithy/config-resolver": "^2.0.16", - "@smithy/fetch-http-handler": "^2.2.4", - "@smithy/hash-node": "^2.0.12", - "@smithy/invalid-dependency": "^2.0.12", - "@smithy/middleware-content-length": "^2.0.14", - "@smithy/middleware-endpoint": "^2.1.3", - "@smithy/middleware-retry": "^2.0.18", - "@smithy/middleware-serde": "^2.0.12", - "@smithy/middleware-stack": "^2.0.6", - "@smithy/node-config-provider": "^2.1.3", - "@smithy/node-http-handler": "^2.1.8", - "@smithy/property-provider": "^2.0.0", - "@smithy/protocol-http": "^3.0.8", - "@smithy/shared-ini-file-loader": "^2.0.6", - "@smithy/smithy-client": "^2.1.12", - "@smithy/types": "^2.4.0", - "@smithy/url-parser": "^2.0.12", - "@smithy/util-base64": "^2.0.0", - "@smithy/util-body-length-browser": "^2.0.0", - "@smithy/util-body-length-node": "^2.1.0", - "@smithy/util-defaults-mode-browser": "^2.0.16", - "@smithy/util-defaults-mode-node": "^2.0.21", - "@smithy/util-endpoints": "^1.0.2", - "@smithy/util-retry": "^2.0.5", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.5.0" + "@aws-sdk/client-sso-oidc": "3.540.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/types": { - "version": "3.433.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.433.0.tgz", - "integrity": "sha512-0jEE2mSrNDd8VGFjTc1otYrwYPIkzZJEIK90ZxisKvQ/EURGBhNzWn7ejWB9XCMFT6XumYLBR0V9qq5UPisWtA==", + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.535.0.tgz", + "integrity": "sha512-aY4MYfduNj+sRR37U7XxYR8wemfbKP6lx00ze2M2uubn7mZotuVrWYAafbMSXrdEMSToE5JDhr28vArSOoLcSg==", "dependencies": { - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/util-arn-parser": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.310.0.tgz", - "integrity": "sha512-jL8509owp/xB9+Or0pvn3Fe+b94qfklc2yPowZZIFAkFcCSIdkIglz18cPDWnYAcy9JGewpMS1COXKIUhZkJsA==", + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.535.0.tgz", + "integrity": "sha512-smVo29nUPAOprp8Z5Y3GHuhiOtw6c8/EtLCm5AVMtRsTPw4V414ZXL2H66tzmb5kEeSzQlbfBSBEdIFZoxO9kg==", "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.438.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.438.0.tgz", - "integrity": "sha512-6VyPTq1kN3GWxwFt5DdZfOsr6cJZPLjWh0troY/0uUv3hK74C9o3Y0Xf/z8UAUvQFkVqZse12O0/BgPVMImvfA==", + "version": "3.540.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.540.0.tgz", + "integrity": "sha512-1kMyQFAWx6f8alaI6UT65/5YW/7pDWAKAdNwL6vuJLea03KrZRX3PMoONOSJpAS5m3Ot7HlWZvf3wZDNTLELZw==", "dependencies": { - "@aws-sdk/types": "3.433.0", - "@smithy/util-endpoints": "^1.0.2", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.535.0", + "@smithy/types": "^2.12.0", + "@smithy/util-endpoints": "^1.2.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/util-format-url": { - "version": "3.433.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.433.0.tgz", - "integrity": "sha512-Z6T7I4hELoQ4eeIuKIKx+52B9bc3SCPhjgMcFAFQeesjmHAr0drHyoGNJIat6ckvgI6zzFaeaBZTvWDA2hyDkA==", + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.535.0.tgz", + "integrity": "sha512-ElbNkm0bddu53CuW44Iuux1ZbTV50fydbSh/4ypW3LrmUvHx193ogj0HXQ7X26kmmo9rXcsrLdM92yIeTjidVg==", "dependencies": { - "@aws-sdk/types": "3.433.0", - "@smithy/querystring-builder": "^2.0.12", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.535.0", + "@smithy/querystring-builder": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/util-locate-window": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.310.0.tgz", - "integrity": "sha512-qo2t/vBTnoXpjKxlsC2e1gBrRm80M3bId27r0BRB2VniSSe7bL1mmzM+/HFtujm0iAxtPM+aLEflLJlJeDPg0w==", + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.535.0.tgz", + "integrity": "sha512-PHJ3SL6d2jpcgbqdgiPxkXpu7Drc2PYViwxSIqvvMKhDwzSB1W3mMvtpzwKM4IE7zLFodZo0GKjJ9AsoXndXhA==", "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.433.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.433.0.tgz", - "integrity": "sha512-2Cf/Lwvxbt5RXvWFXrFr49vXv0IddiUwrZoAiwhDYxvsh+BMnh+NUFot+ZQaTrk/8IPZVDeLPWZRdVy00iaVXQ==", + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.535.0.tgz", + "integrity": "sha512-RWMcF/xV5n+nhaA/Ff5P3yNP3Kur/I+VNZngog4TEs92oB/nwOdAg/2JL8bVAhUbMrjTjpwm7PItziYFQoqyig==", "dependencies": { - "@aws-sdk/types": "3.433.0", - "@smithy/types": "^2.4.0", + "@aws-sdk/types": "3.535.0", + "@smithy/types": "^2.12.0", "bowser": "^2.11.0", - "tslib": "^2.5.0" + "tslib": "^2.6.2" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.437.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.437.0.tgz", - "integrity": "sha512-JVEcvWaniamtYVPem4UthtCNoTBCfFTwYj7Y3CrWZ2Qic4TqrwLkAfaBGtI2TGrhIClVr77uzLI6exqMTN7orA==", + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.535.0.tgz", + "integrity": "sha512-dRek0zUuIT25wOWJlsRm97nTkUlh1NDcLsQZIN2Y8KxhwoXXWtJs5vaDPT+qAg+OpcNj80i1zLR/CirqlFg/TQ==", "dependencies": { - "@aws-sdk/types": "3.433.0", - "@smithy/node-config-provider": "^2.1.3", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.535.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" @@ -997,125 +1103,55 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.310.0.tgz", - "integrity": "sha512-TqELu4mOuSIKQCqj63fGVs86Yh+vBx5nHRpWKNUNhB2nPTpfbziTs5c1X358be3peVWA4wPxW7Nt53KIg1tnNw==", + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.535.0.tgz", + "integrity": "sha512-VXAq/Jz8KIrU84+HqsOJhIKZqG0PNTdi6n6PFQ4xJf44ZQHD/5C7ouH4qCFX5XgZXcgbRIcMVVYGC6Jye0dRng==", "dependencies": { - "tslib": "^2.5.0" + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/code-frame/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/compat-data": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz", - "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", + "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", - "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.4.tgz", + "integrity": "sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-module-transforms": "^7.23.0", - "@babel/helpers": "^7.23.2", - "@babel/parser": "^7.23.0", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.4", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.24.4", + "@babel/parser": "^7.24.4", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -1140,14 +1176,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.4.tgz", + "integrity": "sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==", "dev": true, "dependencies": { - "@babel/types": "^7.23.0", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -1155,14 +1191,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", - "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.15", - "browserslist": "^4.21.9", + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -1214,21 +1250,21 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", + "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", "dev": true, "dependencies": { - "@babel/types": "^7.22.15" + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", - "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", @@ -1245,9 +1281,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", + "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", "dev": true, "engines": { "node": ">=6.9.0" @@ -1278,9 +1314,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", "dev": true, "engines": { "node": ">=6.9.0" @@ -1296,37 +1332,38 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", - "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", - "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.4.tgz", + "integrity": "sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==", "dev": true, "dependencies": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0" + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -1404,9 +1441,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz", + "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -1476,12 +1513,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", - "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", + "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1578,12 +1615,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", - "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz", + "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1593,9 +1630,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", - "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", + "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -1604,34 +1641,34 @@ } }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", + "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", + "@babel/code-frame": "^7.24.1", + "@babel/generator": "^7.24.1", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", + "@babel/parser": "^7.24.1", + "@babel/types": "^7.24.0", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -1648,12 +1685,12 @@ } }, "node_modules/@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, @@ -1724,9 +1761,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -1762,16 +1799,38 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/js": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz", - "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1796,23 +1855,20 @@ } }, "node_modules/@fastify/busboy": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.2.1.tgz", - "integrity": "sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q==", - "dependencies": { - "text-decoding": "^1.0.0" - }, + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", "engines": { "node": ">=14" } }, "node_modules/@fastify/cors": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-8.4.1.tgz", - "integrity": "sha512-iYQJtrY3pFiDS5mo5zRaudzg2OcUdJ96PD6xfkKOOEilly5nnrFZx/W6Sce2T79xxlEn2qpU3t5+qS2phS369w==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-9.0.1.tgz", + "integrity": "sha512-YY9Ho3ovI+QHIL2hW+9X4XqQjXLjJqsU+sMV/xFsxZkE8p3GNnYVFpoOxF7SsP5ZL76gwvbo3V9L+FIekBGU4Q==", "dependencies": { "fastify-plugin": "^4.0.0", - "mnemonist": "0.39.5" + "mnemonist": "0.39.6" } }, "node_modules/@fastify/deepmerge": { @@ -1821,9 +1877,9 @@ "integrity": "sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==" }, "node_modules/@fastify/error": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@fastify/error/-/error-3.4.0.tgz", - "integrity": "sha512-e/mafFwbK3MNqxUcFBLgHhgxsF8UT1m8aj0dAlqEa2nJEgPsRtpHTZ3ObgrgkZ2M1eJHPTwgyUl/tXkvabsZdQ==" + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-3.4.1.tgz", + "integrity": "sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ==" }, "node_modules/@fastify/fast-json-stringify-compiler": { "version": "4.3.0", @@ -1851,6 +1907,14 @@ "helmet": "^7.0.0" } }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.1.1.tgz", + "integrity": "sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + } + }, "node_modules/@fastify/middie": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/@fastify/middie/-/middie-8.3.0.tgz", @@ -1868,15 +1932,13 @@ "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" }, "node_modules/@fastify/multipart": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@fastify/multipart/-/multipart-8.0.0.tgz", - "integrity": "sha512-xaH1pGIqYnIJjYs5qG6ryhPSFnWuJIfSXYqEUtzmcyREkMk0SwONd2y+SZ9JXfDmETAC/Ogtc/SRbz+AjZhCkw==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@fastify/multipart/-/multipart-8.2.0.tgz", + "integrity": "sha512-OZ8nsyyoS2TV7Yeu3ZdrdDGsKUTAbfjrKC9jSxGgT2qdgek+BxpWX31ZubTrWMNZyU5xwk4ox6AvTjAbYWjrWg==", "dependencies": { - "@fastify/busboy": "^1.0.0", + "@fastify/busboy": "^2.1.0", "@fastify/deepmerge": "^1.0.0", "@fastify/error": "^3.0.0", - "@fastify/swagger": "^8.3.1", - "@fastify/swagger-ui": "^1.8.0", "fastify-plugin": "^4.0.0", "secure-json-parse": "^2.4.0", "stream-wormhole": "^1.1.0" @@ -1895,40 +1957,16 @@ } }, "node_modules/@fastify/static": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/@fastify/static/-/static-6.12.0.tgz", - "integrity": "sha512-KK1B84E6QD/FcQWxDI2aiUCwHxMJBI1KeCUzm1BwYpPY1b742+jeKruGHP2uOluuM6OkBPI8CIANrXcCRtC2oQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@fastify/static/-/static-7.0.3.tgz", + "integrity": "sha512-2tmTdF+uFCykasutaO6k4/wOt7eXyi7m3dGuCPo5micXzv0qt6ttb/nWnDYL/BlXjYGfp1JI4a1gyluTIylvQA==", "dependencies": { "@fastify/accept-negotiator": "^1.0.0", "@fastify/send": "^2.0.0", "content-disposition": "^0.5.3", "fastify-plugin": "^4.0.0", - "glob": "^8.0.1", - "p-limit": "^3.1.0" - } - }, - "node_modules/@fastify/swagger": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/@fastify/swagger/-/swagger-8.12.0.tgz", - "integrity": "sha512-IMRc0xYuzRvtFDMuaWHyVbvM7CuAi0g3o2jaVgLDvETXPrXWAMWsHYR5niIdWBDPgGUq+soHkag1DKXyhPDB0w==", - "dependencies": { - "fastify-plugin": "^4.0.0", - "json-schema-resolver": "^2.0.0", - "openapi-types": "^12.0.0", - "rfdc": "^1.3.0", - "yaml": "^2.2.2" - } - }, - "node_modules/@fastify/swagger-ui": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@fastify/swagger-ui/-/swagger-ui-1.10.1.tgz", - "integrity": "sha512-u3EJqNKvVr3X+6jY5i6pbs6/tXCrSlqc2Y+PVjnHBTOGh/d36uHMz+z4jPFy9gie2my6iHUrAdM8itlVmoUjog==", - "dependencies": { - "@fastify/static": "^6.0.0", - "fastify-plugin": "^4.0.0", - "openapi-types": "^12.0.2", - "rfdc": "^1.3.0", - "yaml": "^2.2.2" + "fastq": "^1.17.0", + "glob": "^10.3.4" } }, "node_modules/@golevelup/nestjs-testing": { @@ -1939,21 +1977,21 @@ "dev": true }, "node_modules/@grpc/grpc-js": { - "version": "1.9.9", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.9.tgz", - "integrity": "sha512-vQ1qwi/Kiyprt+uhb1+rHMpyk4CVRMTGNUGGPRGS7pLNfWkdCHrGEnT6T3/JyC2VZgoOX/X1KwdoU0WYQAeYcQ==", + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.6.tgz", + "integrity": "sha512-xP58G7wDQ4TCmN/cMUHh00DS7SRDv/+lC+xFLrTkMIN8h55X5NhZMLYbvy7dSELP15qlI6hPhNCRWVMtZMwqLA==", "dependencies": { - "@grpc/proto-loader": "^0.7.8", - "@types/node": ">=12.12.47" + "@grpc/proto-loader": "^0.7.10", + "@js-sdsl/ordered-map": "^4.4.2" }, "engines": { - "node": "^8.13.0 || >=10.10.0" + "node": ">=12.10.0" } }, "node_modules/@grpc/proto-loader": { - "version": "0.7.10", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.10.tgz", - "integrity": "sha512-CAqDfoaQ8ykFd9zqBDn4k6iWT9loLAlc2ETmDFS9JCD70gDcnA4L3AFEo2iV7KyAtAAHFW9ftq1Fz+Vsgq80RQ==", + "version": "0.7.12", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.12.tgz", + "integrity": "sha512-DCVwMxqYzpUCiDMl7hQ384FqP4T3DbNpXU8pt681l3UWCip1WUiD5JrkImUwCB9a7f2cq4CUTmi5r/xIMRPY1Q==", "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", @@ -1967,20 +2005,54 @@ "node": ">=6" } }, + "node_modules/@grpc/reflection": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@grpc/reflection/-/reflection-1.0.3.tgz", + "integrity": "sha512-Fe1F1HpBSbOb2v4DOnZa2TiQkUJrj0/7camKUNoH6OfOXw/GO82e0gA4Eihbsuga8dZxJYNBHsig/c58SG2c/g==", + "dependencies": { + "@grpc/proto-loader": "^0.7.10", + "protobufjs": "^7.2.5" + }, + "peerDependencies": { + "@grpc/grpc-js": "^1.8.21" + } + }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -1995,9 +2067,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, "node_modules/@ioredis/commands": { @@ -2194,6 +2266,12 @@ "node": ">=8" } }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -2382,6 +2460,16 @@ } } }, + "node_modules/@jest/reporters/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/@jest/reporters/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -2402,6 +2490,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@jest/reporters/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -2502,45 +2602,45 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "devOptional": true, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "dev": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/sourcemap-codec": { @@ -2550,15 +2650,36 @@ "devOptional": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@ljharb/through": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.13.tgz", + "integrity": "sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/@lukeed/csprng": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", @@ -2568,13 +2689,18 @@ } }, "node_modules/@lukeed/ms": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.1.tgz", - "integrity": "sha512-Xs/4RZltsAL7pkvaNStUQt7netTkyxrS0K+RILcVr3TRMS/ToOg4I6uNfhB9SlGsnWBym4U+EaXq0f0cEMNkHA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", + "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", "engines": { "node": ">=8" } }, + "node_modules/@microsoft/tsdoc": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz", + "integrity": "sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==" + }, "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.2.tgz", @@ -2654,64 +2780,53 @@ "peer": true }, "node_modules/@nestjs/axios": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-3.0.1.tgz", - "integrity": "sha512-VlOZhAGDmOoFdsmewn8AyClAdGpKXQQaY1+3PGB+g6ceurGIdTxZgRX3VXc1T6Zs60PedWjg3A82TDOB05mrzQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-3.0.2.tgz", + "integrity": "sha512-Z6GuOUdNQjP7FX+OuV2Ybyamse+/e0BFdTWBX5JxpBDKA+YkdLynDgG6HTF04zy6e9zPa19UX0WA2VDoehwhXQ==", "peerDependencies": { "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", "axios": "^1.3.1", - "reflect-metadata": "^0.1.12", "rxjs": "^6.0.0 || ^7.0.0" } }, "node_modules/@nestjs/bull-shared": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@nestjs/bull-shared/-/bull-shared-10.0.1.tgz", - "integrity": "sha512-8Td36l2i5x9+iQWjPB5Bd5+6u5Eangb5DclNcwrdwKqvd28xE92MSW97P4JV52C2kxrTjZwx8ck/wObAwtpQPw==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@nestjs/bull-shared/-/bull-shared-10.1.1.tgz", + "integrity": "sha512-su7eThDrSz1oQagEi8l+1CyZ7N6nMgmyAX0DuZoXqT1KEVEDqGX7x80RlPVF60m/8SYOskckGMjJROSfNQcErw==", "dependencies": { - "tslib": "2.6.0" + "tslib": "2.6.2" }, "peerDependencies": { "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0" } }, - "node_modules/@nestjs/bull-shared/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" - }, "node_modules/@nestjs/bullmq": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@nestjs/bullmq/-/bullmq-10.0.1.tgz", - "integrity": "sha512-YJtfJXfnQinN7OvGx/Qd6jlQFu56zVnI1SppftSS7gkthB2CbJQAjkrfCEPDjp11wbPptBhUnatIL2N+nH/3kA==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@nestjs/bullmq/-/bullmq-10.1.1.tgz", + "integrity": "sha512-afYx1wYCKtXEu1p0S1+qw2o7QaZWr/EQgF7Wkt3YL8RBIECy5S4C450gv/cRGd8EZjlt6bw8hGCLqR2Q5VjHpQ==", "dependencies": { - "@nestjs/bull-shared": "^10.0.1", - "tslib": "2.6.0" + "@nestjs/bull-shared": "^10.1.1", + "tslib": "2.6.2" }, "peerDependencies": { "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0", - "bullmq": "^3.0.0 || ^4.0.0" + "bullmq": "^3.0.0 || ^4.0.0 || ^5.0.0" } }, - "node_modules/@nestjs/bullmq/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" - }, "node_modules/@nestjs/cli": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.2.1.tgz", - "integrity": "sha512-CAJAQwmxFZfB3RTvqz/eaXXWpyU+mZ4QSqfBYzjneTsPgF+uyOAW3yQpaLNn9Dfcv39R9UxSuAhayv6yuFd+Jg==", + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.3.2.tgz", + "integrity": "sha512-aWmD1GLluWrbuC4a1Iz/XBk5p74Uj6nIVZj6Ov03JbTfgtWqGFLtXuMetvzMiHxfrHehx/myt2iKAPRhKdZvTg==", "dev": true, "dependencies": { - "@angular-devkit/core": "16.2.8", - "@angular-devkit/schematics": "16.2.8", - "@angular-devkit/schematics-cli": "16.2.8", + "@angular-devkit/core": "17.1.2", + "@angular-devkit/schematics": "17.1.2", + "@angular-devkit/schematics-cli": "17.1.2", "@nestjs/schematics": "^10.0.1", "chalk": "4.1.2", - "chokidar": "3.5.3", + "chokidar": "3.6.0", "cli-table3": "0.6.3", "commander": "4.1.1", "fork-ts-checker-webpack-plugin": "9.0.2", @@ -2719,15 +2834,14 @@ "inquirer": "8.2.6", "node-emoji": "1.11.0", "ora": "5.4.1", - "os-name": "4.0.1", "rimraf": "4.4.1", "shelljs": "0.8.5", "source-map-support": "0.5.21", "tree-kill": "1.2.2", "tsconfig-paths": "4.2.0", "tsconfig-paths-webpack-plugin": "4.1.0", - "typescript": "5.2.2", - "webpack": "5.89.0", + "typescript": "5.3.3", + "webpack": "5.90.1", "webpack-node-externals": "3.0.0" }, "bin": { @@ -2737,7 +2851,7 @@ "node": ">= 16.14" }, "peerDependencies": { - "@swc/cli": "^0.1.62", + "@swc/cli": "^0.1.62 || ^0.3.0", "@swc/core": "^1.3.62" }, "peerDependenciesMeta": { @@ -2749,13 +2863,26 @@ } } }, - "node_modules/@nestjs/cli/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/@nestjs/cli/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0" + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@nestjs/cli/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" } }, "node_modules/@nestjs/cli/node_modules/glob": { @@ -2780,21 +2907,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@nestjs/cli/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@nestjs/cli/node_modules/rimraf": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.1.tgz", @@ -2855,10 +2967,70 @@ "node": ">=8" } }, + "node_modules/@nestjs/cli/node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@nestjs/cli/node_modules/webpack": { + "version": "5.90.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.1.tgz", + "integrity": "sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.21.10", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, "node_modules/@nestjs/common": { - "version": "10.2.8", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.2.8.tgz", - "integrity": "sha512-rmpwcdvq2IWMmsUVP8rsdKub6uDWk7dwCYo0aif50JTwcvcxzaP3iKVFKoSgvp0RKYu8h15+/AEOfaInmPpl0Q==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.3.7.tgz", + "integrity": "sha512-gKFtFzcJznrwsRYjtNZoPAvSOPYdNgxbTYoAyLTpoy393cIKgLmJTHu6ReH8/qIB9AaZLdGaFLkx98W/tFWFUw==", "dependencies": { "iterare": "1.2.1", "tslib": "2.6.2", @@ -2871,7 +3043,7 @@ "peerDependencies": { "class-transformer": "*", "class-validator": "*", - "reflect-metadata": "^0.1.12", + "reflect-metadata": "^0.1.12 || ^0.2.0", "rxjs": "^7.1.0" }, "peerDependenciesMeta": { @@ -2884,32 +3056,24 @@ } }, "node_modules/@nestjs/config": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-3.1.1.tgz", - "integrity": "sha512-qu5QlNiJdqQtOsnB6lx4JCXPQ96jkKUsOGd+JXfXwqJqZcOSAq6heNFg0opW4pq4J/VZoNwoo87TNnx9wthnqQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-3.2.1.tgz", + "integrity": "sha512-tFZyLJKanSAu51ygQ6ZBSpx95pRcwS6qSpJDW6FFgRQzkOaOUXpL8qD8yMNoYoYxuJCxph+waiBaWKgFWxn3sw==", "dependencies": { - "dotenv": "16.3.1", + "dotenv": "16.4.5", "dotenv-expand": "10.0.0", "lodash": "4.17.21", - "uuid": "9.0.0" + "uuid": "9.0.1" }, "peerDependencies": { "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", - "reflect-metadata": "^0.1.13" - } - }, - "node_modules/@nestjs/config/node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "bin": { - "uuid": "dist/bin/uuid" + "rxjs": "^7.1.0" } }, "node_modules/@nestjs/core": { - "version": "10.2.8", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.2.8.tgz", - "integrity": "sha512-9+MZ2s8ixfY9Bl/M9ofChiyYymcwdK9ZWNH4GDMF7Am7XRAQ1oqde6MYGG05rhQwiVXuTwaYLlXciJKfsrg5qg==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.7.tgz", + "integrity": "sha512-hsdlnfiQ3kgqHL5k7js3CU0PV7hBJVi+LfFMgCkoagRxNMf67z0GFGeOV2jk5d65ssB19qdYsDa1MGVuEaoUpg==", "hasInstallScript": true, "dependencies": { "@nuxtjs/opencollective": "0.3.2", @@ -2928,7 +3092,7 @@ "@nestjs/microservices": "^10.0.0", "@nestjs/platform-express": "^10.0.0", "@nestjs/websockets": "^10.0.0", - "reflect-metadata": "^0.1.12", + "reflect-metadata": "^0.1.12 || ^0.2.0", "rxjs": "^7.1.0" }, "peerDependenciesMeta": { @@ -2944,14 +3108,14 @@ } }, "node_modules/@nestjs/mapped-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.2.tgz", - "integrity": "sha512-V0izw6tWs6fTp9+KiiPUbGHWALy563Frn8X6Bm87ANLRuE46iuBMD5acKBDP5lKL/75QFvrzSJT7HkCbB0jTpg==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.5.tgz", + "integrity": "sha512-bSJv4pd6EY99NX9CjBIyn4TVDoSit82DUZlL4I3bqNfy5Gt+gXTa86i3I/i0iIV9P4hntcGM5GyO+FhZAhxtyg==", "peerDependencies": { "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", "class-transformer": "^0.4.0 || ^0.5.0", "class-validator": "^0.13.0 || ^0.14.0", - "reflect-metadata": "^0.1.12" + "reflect-metadata": "^0.1.12 || ^0.2.0" }, "peerDependenciesMeta": { "class-transformer": { @@ -2963,9 +3127,9 @@ } }, "node_modules/@nestjs/microservices": { - "version": "10.2.8", - "resolved": "https://registry.npmjs.org/@nestjs/microservices/-/microservices-10.2.8.tgz", - "integrity": "sha512-zfrD7hgN3ygrjicASQUVdnsh3V7vTmhZfttZ7ZNjihwqEoweJFgWmqKkpAfbCrIP7z19gT4JQ8hO4W0Alwdt0w==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/microservices/-/microservices-10.3.7.tgz", + "integrity": "sha512-QQ/gygXqPHfGYsr5lWI+HhfE7dvKIqGXJsrGVDsiJt1P0v4bIHcnh8RAQqoN9SLTo6VmIK7JukeV1CFmHUthAA==", "dependencies": { "iterare": "1.2.1", "tslib": "2.6.2" @@ -2986,7 +3150,7 @@ "kafkajs": "*", "mqtt": "*", "nats": "*", - "reflect-metadata": "^0.1.12", + "reflect-metadata": "^0.1.12 || ^0.2.0", "rxjs": "^7.1.0" }, "peerDependenciesMeta": { @@ -3020,15 +3184,15 @@ } }, "node_modules/@nestjs/platform-fastify": { - "version": "10.2.8", - "resolved": "https://registry.npmjs.org/@nestjs/platform-fastify/-/platform-fastify-10.2.8.tgz", - "integrity": "sha512-ZeqIHeGLD7YgJ22K9AkyjcFv/yH/LH+HmujukBq/yDLFlJuurKCgbDPfL0PHq0RRMZu5CeB0dhs8+qihw96yjA==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/platform-fastify/-/platform-fastify-10.3.7.tgz", + "integrity": "sha512-J7ICAOC/zTSfJLTWwvVLK9LON+Dw/NdgPQwPBsi3GNIw3TLLXLrQ4WXnHNER8gnXS3sfKOIngRLusMirLqYjEQ==", "dependencies": { - "@fastify/cors": "8.4.0", + "@fastify/cors": "9.0.1", "@fastify/formbody": "7.4.0", "@fastify/middie": "8.3.0", - "fastify": "4.24.3", - "light-my-request": "5.11.0", + "fastify": "4.26.2", + "light-my-request": "5.12.0", "path-to-regexp": "3.2.0", "tslib": "2.6.2" }, @@ -3037,7 +3201,7 @@ "url": "https://opencollective.com/nest" }, "peerDependencies": { - "@fastify/static": "^6.0.0", + "@fastify/static": "^6.0.0 || ^7.0.0", "@fastify/view": "^7.0.0 || ^8.0.0", "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0" @@ -3051,49 +3215,47 @@ } } }, - "node_modules/@nestjs/platform-fastify/node_modules/@fastify/cors": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-8.4.0.tgz", - "integrity": "sha512-MlVvMTenltToByTpLwlWtO+7dQ3l2J+1OpmGrx9JpSNWo1d+dhfNCOi23zHhxdFhtpDzfwGwCsKu9DTeG7k7nQ==", - "dependencies": { - "fastify-plugin": "^4.0.0", - "mnemonist": "0.39.5" - } - }, "node_modules/@nestjs/schematics": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.0.3.tgz", - "integrity": "sha512-2BRujK0GqGQ7j1Zpz+obVfskDnnOeVKt5aXoSaVngKo8Oczy8uYCY+R547TQB+Kf35epdfFER2pVnQrX3/It5A==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.1.1.tgz", + "integrity": "sha512-o4lfCnEeIkfJhGBbLZxTuVWcGuqDCFwg5OrvpgRUBM7vI/vONvKKiB5riVNpO+JqXoH0I42NNeDb0m4V5RREig==", "dev": true, "dependencies": { - "@angular-devkit/core": "16.2.8", - "@angular-devkit/schematics": "16.2.8", + "@angular-devkit/core": "17.1.2", + "@angular-devkit/schematics": "17.1.2", "comment-json": "4.2.3", - "jsonc-parser": "3.2.0", + "jsonc-parser": "3.2.1", "pluralize": "8.0.0" }, "peerDependencies": { "typescript": ">=4.8.2" } }, + "node_modules/@nestjs/schematics/node_modules/jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "dev": true + }, "node_modules/@nestjs/swagger": { - "version": "7.1.14", - "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.1.14.tgz", - "integrity": "sha512-2Ol4S6qHeYVVmkshkWBM8E/qkmEqEOUj2QIewr0jLSyo30H7f3v81pJyks6pTLy4PK0LGUXojMvIfFIE3mmGQQ==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.3.1.tgz", + "integrity": "sha512-LUC4mr+5oAleEC/a2j8pNRh1S5xhKXJ1Gal5ZdRjt9XebQgbngXCdW7JTA9WOEcwGtFZN9EnKYdquzH971LZfw==", "dependencies": { - "@nestjs/mapped-types": "2.0.2", + "@microsoft/tsdoc": "^0.14.2", + "@nestjs/mapped-types": "2.0.5", "js-yaml": "4.1.0", "lodash": "4.17.21", "path-to-regexp": "3.2.0", - "swagger-ui-dist": "5.9.0" + "swagger-ui-dist": "5.11.2" }, "peerDependencies": { - "@fastify/static": "^6.0.0", + "@fastify/static": "^6.0.0 || ^7.0.0", "@nestjs/common": "^9.0.0 || ^10.0.0", "@nestjs/core": "^9.0.0 || ^10.0.0", "class-transformer": "*", "class-validator": "*", - "reflect-metadata": "^0.1.12" + "reflect-metadata": "^0.1.12 || ^0.2.0" }, "peerDependenciesMeta": { "@fastify/static": { @@ -3108,9 +3270,9 @@ } }, "node_modules/@nestjs/testing": { - "version": "10.2.8", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.2.8.tgz", - "integrity": "sha512-9Kj5IQhM67/nj/MT6Wi2OmWr5YQnCMptwKVFrX1TDaikpY12196v7frk0jVjdT7wms7rV07GZle9I2z0aSjqtQ==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.7.tgz", + "integrity": "sha512-PmwZXyoCC/m3F3IFgpgD+SNN6cDPQa/vi3YQxFruvfX3cuHq+P6ZFvBB7hwaKKsLlhA0so42LsMm41oFBkdouw==", "dev": true, "dependencies": { "tslib": "2.6.2" @@ -3228,19 +3390,11 @@ "node": ">=14" } }, - "node_modules/@pkgr/utils": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz", - "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==", + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "fast-glob": "^3.3.0", - "is-glob": "^4.0.3", - "open": "^9.1.0", - "picocolors": "^1.0.0", - "tslib": "^2.6.0" - }, "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, @@ -3311,9 +3465,9 @@ } }, "node_modules/@redis/client": { - "version": "1.5.11", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.11.tgz", - "integrity": "sha512-cV7yHcOAtNQ5x/yQl7Yw1xf53kO0FNDTdDU6bFIMbW6ljB7U7ns0YRM+QIkpoqTAt6zK5k9Fq0QWlUbLcq9AvA==", + "version": "1.5.14", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.14.tgz", + "integrity": "sha512-YGn0GqsRBFUQxklhY7v562VMOP0DcmlrHHs3IV1mFE3cbxe31IITUkqhBcIhVSI/2JqtWAJXg5mjV4aU+zD0HA==", "dependencies": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", @@ -3329,9 +3483,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/@redis/graph": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.0.tgz", - "integrity": "sha512-16yZWngxyXPd+MJxeSr0dqh2AIOi8j9yXKcKCwVaKDbH3HTuETpDVPcLujhFYVPtYrngSco31BUcSa9TH31Gqg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", + "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", "peerDependencies": { "@redis/client": "^1.0.0" } @@ -3345,9 +3499,9 @@ } }, "node_modules/@redis/search": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.5.tgz", - "integrity": "sha512-hPP8w7GfGsbtYEJdn4n7nXa6xt6hVZnnDktKW4ArMaFQ/m/aR7eFvsLQmG/mn1Upq99btPJk+F27IQ2dYpCoUg==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.6.tgz", + "integrity": "sha512-mZXCxbTYKBQ3M2lZnEddwEAks0Kc7nauire8q20oA0oA/LoA+E/b5Y5KZn232ztPb1FkIGqo12vh3Lf+Vw5iTw==", "peerDependencies": { "@redis/client": "^1.0.0" } @@ -3367,9 +3521,9 @@ "dev": true }, "node_modules/@sinonjs/commons": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", - "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, "dependencies": { "type-detect": "4.0.8" @@ -3385,9 +3539,9 @@ } }, "node_modules/@sinonjs/samsam": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-7.0.1.tgz", - "integrity": "sha512-zsAk2Jkiq89mhZovB2LLOdTCxJF4hqqTToGP0ASWlhp4I1hqOjcfmZGafXntCN7MDC6yySH0mFHrYtHceOeLmw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", "dev": true, "dependencies": { "@sinonjs/commons": "^2.0.0", @@ -3411,624 +3565,638 @@ "dev": true }, "node_modules/@smithy/abort-controller": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.0.12.tgz", - "integrity": "sha512-YIJyefe1mi3GxKdZxEBEuzYOeQ9xpYfqnFmWzojCssRAuR7ycxwpoRQgp965vuW426xUAQhCV5rCaWElQ7XsaA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.2.0.tgz", + "integrity": "sha512-wRlta7GuLWpTqtFfGo+nZyOO1vEvewdNR1R4rTxpC8XU6vG/NDyrFBhwLZsqg1NUoR1noVaXJPC/7ZK47QCySw==", "dependencies": { - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/chunked-blob-reader": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-2.0.0.tgz", - "integrity": "sha512-k+J4GHJsMSAIQPChGBrjEmGS+WbPonCXesoqP9fynIqjn7rdOThdH8FAeCmokP9mxTYKQAKoHCLPzNlm6gh7Wg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-2.2.0.tgz", + "integrity": "sha512-3GJNvRwXBGdkDZZOGiziVYzDpn4j6zfyULHMDKAGIUo72yHALpE9CbhfQp/XcLNVoc1byfMpn6uW5H2BqPjgaQ==", "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" } }, "node_modules/@smithy/chunked-blob-reader-native": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-2.0.0.tgz", - "integrity": "sha512-HM8V2Rp1y8+1343tkZUKZllFhEQPNmpNdgFAncbTsxkZ18/gqjk23XXv3qGyXWp412f3o43ZZ1UZHVcHrpRnCQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-2.2.0.tgz", + "integrity": "sha512-VNB5+1oCgX3Fzs072yuRsUoC2N4Zg/LJ11DTxX3+Qu+Paa6AmbIF0E9sc2wthz9Psrk/zcOlTCyuposlIhPjZQ==", "dependencies": { - "@smithy/util-base64": "^2.0.0", - "tslib": "^2.5.0" + "@smithy/util-base64": "^2.3.0", + "tslib": "^2.6.2" } }, "node_modules/@smithy/config-resolver": { - "version": "2.0.16", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-2.0.16.tgz", - "integrity": "sha512-1k+FWHQDt2pfpXhJsOmNMmlAZ3NUQ98X5tYsjQhVGq+0X6cOBMhfh6Igd0IX3Ut6lEO6DQAdPMI/blNr3JZfMQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-2.2.0.tgz", + "integrity": "sha512-fsiMgd8toyUba6n1WRmr+qACzXltpdDkPTAaDqc8QqPBUzO+/JKwL6bUBseHVi8tu9l+3JOK+tSf7cay+4B3LA==", "dependencies": { - "@smithy/node-config-provider": "^2.1.3", - "@smithy/types": "^2.4.0", - "@smithy/util-config-provider": "^2.0.0", - "@smithy/util-middleware": "^2.0.5", - "tslib": "^2.5.0" + "@smithy/node-config-provider": "^2.3.0", + "@smithy/types": "^2.12.0", + "@smithy/util-config-provider": "^2.3.0", + "@smithy/util-middleware": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-1.4.1.tgz", + "integrity": "sha512-jCnbEQHvTOUQXxXOS110FIMc83dCXUlrqiG/q0QzUSYhglDj9bJVPFjXmxc6qUfARe0mEb8h9LeVoh7FUYHuUg==", + "dependencies": { + "@smithy/middleware-endpoint": "^2.5.0", + "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/util-middleware": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/credential-provider-imds": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-2.0.18.tgz", - "integrity": "sha512-QnPBi6D2zj6AHJdUTo5zXmk8vwHJ2bNevhcVned1y+TZz/OI5cizz5DsYNkqFUIDn8tBuEyKNgbmKVNhBbuY3g==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-2.3.0.tgz", + "integrity": "sha512-BWB9mIukO1wjEOo1Ojgl6LrG4avcaC7T/ZP6ptmAaW4xluhSIPZhY+/PI5YKzlk+jsm+4sQZB45Bt1OfMeQa3w==", "dependencies": { - "@smithy/node-config-provider": "^2.1.3", - "@smithy/property-provider": "^2.0.13", - "@smithy/types": "^2.4.0", - "@smithy/url-parser": "^2.0.12", - "tslib": "^2.5.0" + "@smithy/node-config-provider": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/eventstream-codec": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-2.0.12.tgz", - "integrity": "sha512-ZZQLzHBJkbiAAdj2C5K+lBlYp/XJ+eH2uy+jgJgYIFW/o5AM59Hlj7zyI44/ZTDIQWmBxb3EFv/c5t44V8/g8A==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-2.2.0.tgz", + "integrity": "sha512-8janZoJw85nJmQZc4L8TuePp2pk1nxLgkxIR0TUjKJ5Dkj5oelB9WtiSSGXCQvNsJl0VSTvK/2ueMXxvpa9GVw==", "dependencies": { "@aws-crypto/crc32": "3.0.0", - "@smithy/types": "^2.4.0", - "@smithy/util-hex-encoding": "^2.0.0", - "tslib": "^2.5.0" + "@smithy/types": "^2.12.0", + "@smithy/util-hex-encoding": "^2.2.0", + "tslib": "^2.6.2" } }, "node_modules/@smithy/eventstream-serde-browser": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-2.0.12.tgz", - "integrity": "sha512-0pi8QlU/pwutNshoeJcbKR1p7Ie5STd8UFAMX5xhSoSJjNlxIv/OsHbF023jscMRN2Prrqd6ToGgdCnsZVQjvg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-2.2.0.tgz", + "integrity": "sha512-UaPf8jKbcP71BGiO0CdeLmlg+RhWnlN8ipsMSdwvqBFigl5nil3rHOI/5GE3tfiuX8LvY5Z9N0meuU7Rab7jWw==", "dependencies": { - "@smithy/eventstream-serde-universal": "^2.0.12", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@smithy/eventstream-serde-universal": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-2.0.12.tgz", - "integrity": "sha512-I0XfwQkIX3gAnbrU5rLMkBSjTM9DHttdbLwf12CXmj7SSI5dT87PxtKLRrZGanaCMbdf2yCep+MW5/4M7IbvQA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-2.2.0.tgz", + "integrity": "sha512-RHhbTw/JW3+r8QQH7PrganjNCiuiEZmpi6fYUAetFfPLfZ6EkiA08uN3EFfcyKubXQxOwTeJRZSQmDDCdUshaA==", "dependencies": { - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/eventstream-serde-node": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-2.0.12.tgz", - "integrity": "sha512-vf1vMHGOkG3uqN9x1zKOhnvW/XgvhJXWqjV6zZiT2FMjlEayugQ1mzpSqr7uf89+BzjTzuZKERmOsEAmewLbxw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-2.2.0.tgz", + "integrity": "sha512-zpQMtJVqCUMn+pCSFcl9K/RPNtQE0NuMh8sKpCdEHafhwRsjP50Oq/4kMmvxSRy6d8Jslqd8BLvDngrUtmN9iA==", "dependencies": { - "@smithy/eventstream-serde-universal": "^2.0.12", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@smithy/eventstream-serde-universal": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/eventstream-serde-universal": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-2.0.12.tgz", - "integrity": "sha512-xZ3ZNpCxIND+q+UCy7y1n1/5VQEYicgSTNCcPqsKawX+Vd+6OcFX7gUHMyPzL8cZr+GdmJuxNleqHlH4giK2tw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-2.2.0.tgz", + "integrity": "sha512-pvoe/vvJY0mOpuF84BEtyZoYfbehiFj8KKWk1ds2AT0mTLYFVs+7sBJZmioOFdBXKd48lfrx1vumdPdmGlCLxA==", "dependencies": { - "@smithy/eventstream-codec": "^2.0.12", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@smithy/eventstream-codec": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/fetch-http-handler": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-2.2.4.tgz", - "integrity": "sha512-gIPRFEGi+c6V52eauGKrjDzPWF2Cu7Z1r5F8A3j2wcwz25sPG/t8kjsbEhli/tS/2zJp/ybCZXe4j4ro3yv/HA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-2.5.0.tgz", + "integrity": "sha512-BOWEBeppWhLn/no/JxUL/ghTfANTjT7kg3Ww2rPqTUY9R4yHPXxJ9JhMe3Z03LN3aPwiwlpDIUcVw1xDyHqEhw==", "dependencies": { - "@smithy/protocol-http": "^3.0.8", - "@smithy/querystring-builder": "^2.0.12", - "@smithy/types": "^2.4.0", - "@smithy/util-base64": "^2.0.0", - "tslib": "^2.5.0" + "@smithy/protocol-http": "^3.3.0", + "@smithy/querystring-builder": "^2.2.0", + "@smithy/types": "^2.12.0", + "@smithy/util-base64": "^2.3.0", + "tslib": "^2.6.2" } }, "node_modules/@smithy/hash-blob-browser": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-2.0.12.tgz", - "integrity": "sha512-riLnV16f27yyePX8UF0deRHAeccUK8SrOxyTykSTrnVkgS3DsjNapZtTbd8OGNKEbI60Ncdb5GwN3rHZudXvog==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-2.2.0.tgz", + "integrity": "sha512-SGPoVH8mdXBqrkVCJ1Hd1X7vh1zDXojNN1yZyZTZsCno99hVue9+IYzWDjq/EQDDXxmITB0gBmuyPh8oAZSTcg==", "dependencies": { - "@smithy/chunked-blob-reader": "^2.0.0", - "@smithy/chunked-blob-reader-native": "^2.0.0", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@smithy/chunked-blob-reader": "^2.2.0", + "@smithy/chunked-blob-reader-native": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" } }, "node_modules/@smithy/hash-node": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-2.0.12.tgz", - "integrity": "sha512-fDZnTr5j9t5qcbeJ037aMZXxMka13Znqwrgy3PAqYj6Dm3XHXHftTH3q+NWgayUxl1992GFtQt1RuEzRMy3NnQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-2.2.0.tgz", + "integrity": "sha512-zLWaC/5aWpMrHKpoDF6nqpNtBhlAYKF/7+9yMN7GpdR8CzohnWfGtMznPybnwSS8saaXBMxIGwJqR4HmRp6b3g==", "dependencies": { - "@smithy/types": "^2.4.0", - "@smithy/util-buffer-from": "^2.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.5.0" + "@smithy/types": "^2.12.0", + "@smithy/util-buffer-from": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/hash-stream-node": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-2.0.12.tgz", - "integrity": "sha512-x/DrSynPKrW0k00q7aZ/vy531a3mRw79mOajHp+cIF0TrA1SqEMFoy/B8X0XtoAtlJWt/vvgeDNqt/KAeaAqMw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-2.2.0.tgz", + "integrity": "sha512-aT+HCATOSRMGpPI7bi7NSsTNVZE/La9IaxLXWoVAYMxHT5hGO3ZOGEMZQg8A6nNL+pdFGtZQtND1eoY084HgHQ==", "dependencies": { - "@smithy/types": "^2.4.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.5.0" + "@smithy/types": "^2.12.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/invalid-dependency": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-2.0.12.tgz", - "integrity": "sha512-p5Y+iMHV3SoEpy3VSR7mifbreHQwVSvHSAz/m4GdoXfOzKzaYC8hYv10Ks7Deblkf7lhas8U+lAp9ThbBM+ZXA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-2.2.0.tgz", + "integrity": "sha512-nEDASdbKFKPXN2O6lOlTgrEEOO9NHIeO+HVvZnkqc8h5U9g3BIhWsvzFo+UcUbliMHvKNPD/zVxDrkP1Sbgp8Q==", "dependencies": { - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" } }, "node_modules/@smithy/is-array-buffer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.0.0.tgz", - "integrity": "sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/md5-js": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-2.0.12.tgz", - "integrity": "sha512-OgDt+Xnrw+W5z3MSl5KZZzebqmXrYl9UdbCiBYnnjErmNywwSjV6QB/Oic3/7hnsPniSU81n7Rvlhz2kH4EREQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-2.2.0.tgz", + "integrity": "sha512-M26XTtt9IIusVMOWEAhIvFIr9jYj4ISPPGJROqw6vXngO3IYJCnVVSMFn4Tx1rUTG5BiKJNg9u2nxmBiZC5IlQ==", "dependencies": { - "@smithy/types": "^2.4.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.5.0" + "@smithy/types": "^2.12.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" } }, "node_modules/@smithy/middleware-content-length": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-2.0.14.tgz", - "integrity": "sha512-poUNgKTw9XwPXfX9nEHpVgrMNVpaSMZbshqvPxFVoalF4wp6kRzYKOfdesSVectlQ51VtigoLfbXcdyPwvxgTg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-2.2.0.tgz", + "integrity": "sha512-5bl2LG1Ah/7E5cMSC+q+h3IpVHMeOkG0yLRyQT1p2aMJkSrZG7RlXHPuAgb7EyaFeidKEnnd/fNaLLaKlHGzDQ==", "dependencies": { - "@smithy/protocol-http": "^3.0.8", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@smithy/protocol-http": "^3.3.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/middleware-endpoint": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-2.1.3.tgz", - "integrity": "sha512-ZrQ0/YX6hNVTxqMEHtEaDbDv6pNeEji/a5Vk3HuFC5R3ZY8lfoATyxmOGxBVYnF3NUvZLNC7umEv1WzWGWvCGQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-2.5.0.tgz", + "integrity": "sha512-OBhI9ZEAG8Xen0xsFJwwNOt44WE2CWkfYIxTognC8x42Lfsdf0VN/wCMqpdkySMDio/vts10BiovAxQp0T0faA==", "dependencies": { - "@smithy/middleware-serde": "^2.0.12", - "@smithy/node-config-provider": "^2.1.3", - "@smithy/shared-ini-file-loader": "^2.2.2", - "@smithy/types": "^2.4.0", - "@smithy/url-parser": "^2.0.12", - "@smithy/util-middleware": "^2.0.5", - "tslib": "^2.5.0" + "@smithy/middleware-serde": "^2.3.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-middleware": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/middleware-retry": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-2.0.18.tgz", - "integrity": "sha512-VyrHQRldGSb3v9oFOB5yPxmLT7U2sQic2ytylOnYlnsmVOLlFIaI6sW22c+w2675yq+XZ6HOuzV7x2OBYCWRNA==", - "dependencies": { - "@smithy/node-config-provider": "^2.1.3", - "@smithy/protocol-http": "^3.0.8", - "@smithy/service-error-classification": "^2.0.5", - "@smithy/types": "^2.4.0", - "@smithy/util-middleware": "^2.0.5", - "@smithy/util-retry": "^2.0.5", - "tslib": "^2.5.0", - "uuid": "^8.3.2" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-2.3.0.tgz", + "integrity": "sha512-5H7kD0My2RkZryvYIWA4C9w6t/pdJfbgEdq+fcZhbnZsqHm/4vYFVjDsOzb5pC7PEpksuijoM9fGbM6eN4rLSg==", + "dependencies": { + "@smithy/node-config-provider": "^2.3.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/service-error-classification": "^2.1.5", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@smithy/middleware-retry/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@smithy/middleware-serde": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-2.0.12.tgz", - "integrity": "sha512-IBeco157lIScecq2Z+n0gq56i4MTnfKxS7rbfrAORveDJgnbBAaEQgYqMqp/cYqKrpvEXcyTjwKHrBjCCIZh2A==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-2.3.0.tgz", + "integrity": "sha512-sIADe7ojwqTyvEQBe1nc/GXB9wdHhi9UwyX0lTyttmUWDJLP655ZYE1WngnNyXREme8I27KCaUhyhZWRXL0q7Q==", "dependencies": { - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/middleware-stack": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-2.0.6.tgz", - "integrity": "sha512-YSvNZeOKWLJ0M/ycxwDIe2Ztkp6Qixmcml1ggsSv2fdHKGkBPhGrX5tMzPGMI1yyx55UEYBi2OB4s+RriXX48A==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-2.2.0.tgz", + "integrity": "sha512-Qntc3jrtwwrsAC+X8wms8zhrTr0sFXnyEGhZd9sLtsJ/6gGQKFzNB+wWbOcpJd7BR8ThNCoKt76BuQahfMvpeA==", "dependencies": { - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/node-config-provider": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-2.1.3.tgz", - "integrity": "sha512-J6lXvRHGVnSX3n1PYi+e1L5HN73DkkJpUviV3Ebf+8wSaIjAf+eVNbzyvh/S5EQz7nf4KVfwbD5vdoZMAthAEQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-2.3.0.tgz", + "integrity": "sha512-0elK5/03a1JPWMDPaS726Iw6LpQg80gFut1tNpPfxFuChEEklo2yL823V94SpTZTxmKlXFtFgsP55uh3dErnIg==", "dependencies": { - "@smithy/property-provider": "^2.0.13", - "@smithy/shared-ini-file-loader": "^2.2.2", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/node-http-handler": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-2.1.8.tgz", - "integrity": "sha512-KZylM7Wff/So5SmCiwg2kQNXJ+RXgz34wkxS7WNwIUXuZrZZpY/jKJCK+ZaGyuESDu3TxcaY+zeYGJmnFKbQsA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-2.5.0.tgz", + "integrity": "sha512-mVGyPBzkkGQsPoxQUbxlEfRjrj6FPyA3u3u2VXGr9hT8wilsoQdZdvKpMBFMB8Crfhv5dNkKHIW0Yyuc7eABqA==", "dependencies": { - "@smithy/abort-controller": "^2.0.12", - "@smithy/protocol-http": "^3.0.8", - "@smithy/querystring-builder": "^2.0.12", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@smithy/abort-controller": "^2.2.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/querystring-builder": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/property-provider": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-2.0.13.tgz", - "integrity": "sha512-VJqUf2CbsQX6uUiC5dUPuoEATuFjkbkW3lJHbRnpk9EDC9X+iKqhfTK+WP+lve5EQ9TcCI1Q6R7hrg41FyC54w==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-2.2.0.tgz", + "integrity": "sha512-+xiil2lFhtTRzXkx8F053AV46QnIw6e7MV8od5Mi68E1ICOjCeCHw2XfLnDEUHnT9WGUIkwcqavXjfwuJbGlpg==", "dependencies": { - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/protocol-http": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-3.0.8.tgz", - "integrity": "sha512-SHJvYeWq8q0FK8xHk+xjV9dzDUDjFMT+G1pZbV+XB6OVoac/FSVshlMNPeUJ8AmSkcDKHRu5vASnRqZHgD3qhw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-3.3.0.tgz", + "integrity": "sha512-Xy5XK1AFWW2nlY/biWZXu6/krgbaf2dg0q492D8M5qthsnU2H+UgFeZLbM76FnH7s6RO/xhQRkj+T6KBO3JzgQ==", "dependencies": { - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/querystring-builder": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-2.0.12.tgz", - "integrity": "sha512-cDbF07IuCjiN8CdGvPzfJjXIrmDSelScRfyJYrYBNBbKl2+k7QD/KqiHhtRyEKgID5mmEVrV6KE6L/iPJ98sFw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-2.2.0.tgz", + "integrity": "sha512-L1kSeviUWL+emq3CUVSgdogoM/D9QMFaqxL/dd0X7PCNWmPXqt+ExtrBjqT0V7HLN03Vs9SuiLrG3zy3JGnE5A==", "dependencies": { - "@smithy/types": "^2.4.0", - "@smithy/util-uri-escape": "^2.0.0", - "tslib": "^2.5.0" + "@smithy/types": "^2.12.0", + "@smithy/util-uri-escape": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/querystring-parser": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-2.0.12.tgz", - "integrity": "sha512-fytyTcXaMzPBuNtPlhj5v6dbl4bJAnwKZFyyItAGt4Tgm9HFPZNo7a9r1SKPr/qdxUEBzvL9Rh+B9SkTX3kFxg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-2.2.0.tgz", + "integrity": "sha512-BvHCDrKfbG5Yhbpj4vsbuPV2GgcpHiAkLeIlcA1LtfpMz3jrqizP1+OguSNSj1MwBHEiN+jwNisXLGdajGDQJA==", "dependencies": { - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/service-error-classification": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-2.0.5.tgz", - "integrity": "sha512-M0SeJnEgD2ywJyV99Fb1yKFzmxDe9JfpJiYTVSRMyRLc467BPU0qsuuDPzMCdB1mU8M8u1rVOdkqdoyFN8UFTw==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-2.1.5.tgz", + "integrity": "sha512-uBDTIBBEdAQryvHdc5W8sS5YX7RQzF683XrHePVdFmAgKiMofU15FLSM0/HU03hKTnazdNRFa0YHS7+ArwoUSQ==", "dependencies": { - "@smithy/types": "^2.4.0" + "@smithy/types": "^2.12.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.2.2.tgz", - "integrity": "sha512-noyQUPn7b1M8uB0GEXc/Zyxq+5K2b7aaqWnLp+hgJ7+xu/FCvtyWy5eWLDjQEsHnAet2IZhS5QF8872OR69uNg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.4.0.tgz", + "integrity": "sha512-WyujUJL8e1B6Z4PBfAqC/aGY1+C7T0w20Gih3yrvJSk97gpiVfB+y7c46T4Nunk+ZngLq0rOIdeVeIklk0R3OA==", "dependencies": { - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/signature-v4": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-2.0.12.tgz", - "integrity": "sha512-6Kc2lCZEVmb1nNYngyNbWpq0d82OZwITH11SW/Q0U6PX5fH7B2cIcFe7o6eGEFPkTZTP8itTzmYiGcECL0D0Lw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-2.2.0.tgz", + "integrity": "sha512-+B5TNzj/fRZzVW3z8UUJOkNx15+4E0CLuvJmJUA1JUIZFp3rdJ/M2H5r2SqltaVPXL0oIxv/6YK92T9TsFGbFg==", "dependencies": { - "@smithy/eventstream-codec": "^2.0.12", - "@smithy/is-array-buffer": "^2.0.0", - "@smithy/types": "^2.4.0", - "@smithy/util-hex-encoding": "^2.0.0", - "@smithy/util-middleware": "^2.0.5", - "@smithy/util-uri-escape": "^2.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.5.0" + "@smithy/eventstream-codec": "^2.2.0", + "@smithy/is-array-buffer": "^2.2.0", + "@smithy/types": "^2.12.0", + "@smithy/util-hex-encoding": "^2.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-uri-escape": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/smithy-client": { - "version": "2.1.12", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-2.1.12.tgz", - "integrity": "sha512-XXqhridfkKnpj+lt8vM6HRlZbqUAqBjVC74JIi13F/AYQd/zTj9SOyGfxnbp4mjY9q28LityxIuV8CTinr9r5w==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-2.5.0.tgz", + "integrity": "sha512-DDXWHWdimtS3y/Kw1Jo46KQ0ZYsDKcldFynQERUGBPDpkW1lXOTHy491ALHjwfiBQvzsVKVxl5+ocXNIgJuX4g==", "dependencies": { - "@smithy/middleware-stack": "^2.0.6", - "@smithy/types": "^2.4.0", - "@smithy/util-stream": "^2.0.17", - "tslib": "^2.5.0" + "@smithy/middleware-endpoint": "^2.5.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/types": "^2.12.0", + "@smithy/util-stream": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/types": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.4.0.tgz", - "integrity": "sha512-iH1Xz68FWlmBJ9vvYeHifVMWJf82ONx+OybPW8ZGf5wnEv2S0UXcU4zwlwJkRXuLKpcSLHrraHbn2ucdVXLb4g==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.12.0.tgz", + "integrity": "sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==", "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/url-parser": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-2.0.12.tgz", - "integrity": "sha512-qgkW2mZqRvlNUcBkxYB/gYacRaAdck77Dk3/g2iw0S9F0EYthIS3loGfly8AwoWpIvHKhkTsCXXQfzksgZ4zIA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-2.2.0.tgz", + "integrity": "sha512-hoA4zm61q1mNTpksiSWp2nEl1dt3j726HdRhiNgVJQMj7mLp7dprtF57mOB6JvEk/x9d2bsuL5hlqZbBuHQylQ==", "dependencies": { - "@smithy/querystring-parser": "^2.0.12", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@smithy/querystring-parser": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" } }, "node_modules/@smithy/util-base64": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-2.0.0.tgz", - "integrity": "sha512-Zb1E4xx+m5Lud8bbeYi5FkcMJMnn+1WUnJF3qD7rAdXpaL7UjkFQLdmW5fHadoKbdHpwH9vSR8EyTJFHJs++tA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-2.3.0.tgz", + "integrity": "sha512-s3+eVwNeJuXUwuMbusncZNViuhv2LjVJ1nMwTqSA0XAC7gjKhqqxRdJPhR8+YrkoZ9IiIbFk/yK6ACe/xlF+hw==", "dependencies": { - "@smithy/util-buffer-from": "^2.0.0", - "tslib": "^2.5.0" + "@smithy/util-buffer-from": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/util-body-length-browser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-2.0.0.tgz", - "integrity": "sha512-JdDuS4ircJt+FDnaQj88TzZY3+njZ6O+D3uakS32f2VNnDo3vyEuNdBOh/oFd8Df1zSZOuH1HEChk2AOYDezZg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-2.2.0.tgz", + "integrity": "sha512-dtpw9uQP7W+n3vOtx0CfBD5EWd7EPdIdsQnWTDoFf77e3VUf05uA7R7TGipIo8e4WL2kuPdnsr3hMQn9ziYj5w==", "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" } }, "node_modules/@smithy/util-body-length-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-2.1.0.tgz", - "integrity": "sha512-/li0/kj/y3fQ3vyzn36NTLGmUwAICb7Jbe/CsWCktW363gh1MOcpEcSO3mJ344Gv2dqz8YJCLQpb6hju/0qOWw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-2.3.0.tgz", + "integrity": "sha512-ITWT1Wqjubf2CJthb0BuT9+bpzBfXeMokH/AAa5EJQgbv9aPMVfnM76iFIZVFf50hYXGbtiV71BHAthNWd6+dw==", "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/util-buffer-from": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.0.0.tgz", - "integrity": "sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", "dependencies": { - "@smithy/is-array-buffer": "^2.0.0", - "tslib": "^2.5.0" + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/util-config-provider": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-2.0.0.tgz", - "integrity": "sha512-xCQ6UapcIWKxXHEU4Mcs2s7LcFQRiU3XEluM2WcCjjBtQkUN71Tb+ydGmJFPxMUrW/GWMgQEEGipLym4XG0jZg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-2.3.0.tgz", + "integrity": "sha512-HZkzrRcuFN1k70RLqlNK4FnPXKOpkik1+4JaBoHNJn+RnJGYqaa3c5/+XtLOXhlKzlRgNvyaLieHTW2VwGN0VQ==", "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "2.0.16", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.0.16.tgz", - "integrity": "sha512-Uv5Cu8nVkuvLn0puX+R9zWbSNpLIR3AxUlPoLJ7hC5lvir8B2WVqVEkJLwtixKAncVLasnTVjPDCidtAUTGEQw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.2.0.tgz", + "integrity": "sha512-2okTdZaCBvOJszAPU/KSvlimMe35zLOKbQpHhamFJmR7t95HSe0K3C92jQPjKY3PmDBD+7iMkOnuW05F5OlF4g==", "dependencies": { - "@smithy/property-provider": "^2.0.13", - "@smithy/smithy-client": "^2.1.12", - "@smithy/types": "^2.4.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", "bowser": "^2.11.0", - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { "node": ">= 10.0.0" } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "2.0.21", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.0.21.tgz", - "integrity": "sha512-cUEsttVZ79B7Al2rWK2FW03HBpD9LyuqFtm+1qFty5u9sHSdesr215gS2Ln53fTopNiPgeXpdoM3IgjvIO0rJw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.3.0.tgz", + "integrity": "sha512-hfKXnNLmsW9cmLb/JXKIvtuO6Cf4SuqN5PN1C2Ru/TBIws+m1wSgb+A53vo0r66xzB6E82inKG2J7qtwdi+Kkw==", "dependencies": { - "@smithy/config-resolver": "^2.0.16", - "@smithy/credential-provider-imds": "^2.0.18", - "@smithy/node-config-provider": "^2.1.3", - "@smithy/property-provider": "^2.0.13", - "@smithy/smithy-client": "^2.1.12", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@smithy/config-resolver": "^2.2.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">= 10.0.0" } }, "node_modules/@smithy/util-endpoints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-1.0.2.tgz", - "integrity": "sha512-QEdq+sP68IJHAMVB2ugKVVZEWeKQtZLuf+akHzc8eTVElsZ2ZdVLWC6Cp+uKjJ/t4yOj1qu6ZzyxJQEQ8jdEjg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-1.2.0.tgz", + "integrity": "sha512-BuDHv8zRjsE5zXd3PxFXFknzBG3owCpjq8G3FcsXW3CykYXuEqM3nTSsmLzw5q+T12ZYuDlVUZKBdpNbhVtlrQ==", "dependencies": { - "@smithy/node-config-provider": "^2.1.3", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@smithy/node-config-provider": "^2.3.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@smithy/util-hex-encoding": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.0.0.tgz", - "integrity": "sha512-c5xY+NUnFqG6d7HFh1IFfrm3mGl29lC+vF+geHv4ToiuJCBmIfzx6IeHLg+OgRdPFKDXIw6pvi+p3CsscaMcMA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.2.0.tgz", + "integrity": "sha512-7iKXR+/4TpLK194pVjKiasIyqMtTYJsgKgM242Y9uzt5dhHnUDvMNb+3xIhRJ9QhvqGii/5cRUt4fJn3dtXNHQ==", "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/util-middleware": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-2.0.5.tgz", - "integrity": "sha512-1lyT3TcaMJQe+OFfVI+TlomDkPuVzb27NZYdYtmSTltVmLaUjdCyt4KE+OH1CnhZKsz4/cdCL420Lg9UH5Z2Mw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-2.2.0.tgz", + "integrity": "sha512-L1qpleXf9QD6LwLCJ5jddGkgWyuSvWBkJwWAZ6kFkdifdso+sk3L3O1HdmPvCdnCK3IS4qWyPxev01QMnfHSBw==", "dependencies": { - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/util-retry": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-2.0.5.tgz", - "integrity": "sha512-x3t1+MQAJ6QONk3GTbJNcugCFDVJ+Bkro5YqQQK1EyVesajNDqxFtCx9WdOFNGm/Cbm7tUdwVEmfKQOJoU2Vtw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-2.2.0.tgz", + "integrity": "sha512-q9+pAFPTfftHXRytmZ7GzLFFrEGavqapFc06XxzZFcSIGERXMerXxCitjOG1prVDR9QdjqotF40SWvbqcCpf8g==", "dependencies": { - "@smithy/service-error-classification": "^2.0.5", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@smithy/service-error-classification": "^2.1.5", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@smithy/util-stream": { - "version": "2.0.17", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-2.0.17.tgz", - "integrity": "sha512-fP/ZQ27rRvHsqItds8yB7jerwMpZFTL3QqbQbidUiG0+mttMoKdP0ZqnvM8UK5q0/dfc3/pN7g4XKPXOU7oRWw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-2.2.0.tgz", + "integrity": "sha512-17faEXbYWIRst1aU9SvPZyMdWmqIrduZjVOqCPMIsWFNxs5yQQgFrJL6b2SdiCzyW9mJoDjFtgi53xx7EH+BXA==", "dependencies": { - "@smithy/fetch-http-handler": "^2.2.4", - "@smithy/node-http-handler": "^2.1.8", - "@smithy/types": "^2.4.0", - "@smithy/util-base64": "^2.0.0", - "@smithy/util-buffer-from": "^2.0.0", - "@smithy/util-hex-encoding": "^2.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.5.0" + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-buffer-from": "^2.2.0", + "@smithy/util-hex-encoding": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/util-uri-escape": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.0.0.tgz", - "integrity": "sha512-ebkxsqinSdEooQduuk9CbKcI+wheijxEb3utGXkCoYQkJnwTnLbH1JXGimJtUkQwNQbsbuYwG2+aFVyZf5TLaw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.2.0.tgz", + "integrity": "sha512-jtmJMyt1xMD/d8OtbVJ2gFZOSKc+ueYJZPW20ULW1GOp/q/YIM0wNh+u8ZFao9UaIGz4WoPW8hC64qlWLIfoDA==", "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/util-utf8": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.0.0.tgz", - "integrity": "sha512-rctU1VkziY84n5OXe3bPNpKR001ZCME2JCaBBFgtiM2hfKbHFudc/BkMuPab8hRbLd0j3vbnBTTZ1igBf0wgiQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "dependencies": { - "@smithy/util-buffer-from": "^2.0.0", - "tslib": "^2.5.0" + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@smithy/util-waiter": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-2.0.12.tgz", - "integrity": "sha512-3sENmyVa1NnOPoiT2NCApPmu7ukP7S/v7kL9IxNmnygkDldn7/yK0TP42oPJLwB2k3mospNsSePIlqdXEUyPHA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-2.2.0.tgz", + "integrity": "sha512-IHk53BVw6MPMi2Gsn+hCng8rFA3ZmR3Rk7GllxDUW9qFJl/hiSvskn7XldkECapQVkIg/1dHpMAxI9xSTaLLSA==", "dependencies": { - "@smithy/abort-controller": "^2.0.12", - "@smithy/types": "^2.4.0", - "tslib": "^2.5.0" + "@smithy/abort-controller": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" @@ -4045,10 +4213,16 @@ "integrity": "sha512-kIhULpw9TrGYnHp/8VfdcneIcxKnLixmADtukQRtJUmsVlMg0niMkwV0xZmi8hqa57xqilIHjWFA0GKvEjVU5g==", "optional": true }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "optional": true + }, "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", "devOptional": true }, "node_modules/@tsconfig/node12": { @@ -4070,9 +4244,9 @@ "devOptional": true }, "node_modules/@types/babel__core": { - "version": "7.20.3", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.3.tgz", - "integrity": "sha512-54fjTSeSHwfan8AyHWrKbfBWiEUrNTZsUwPTDSNaaP1QDQIZbeNUg3a59E9D+375MzUw/x1vx2/0F5LBz+AeYA==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, "dependencies": { "@babel/parser": "^7.20.7", @@ -4083,18 +4257,18 @@ } }, "node_modules/@types/babel__generator": { - "version": "7.6.6", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.6.tgz", - "integrity": "sha512-66BXMKb/sUWbMdBNdMvajU7i/44RkrA3z/Yt1c7R5xejt8qh84iU54yUWCtm0QwGJlDcf/gg4zd/x4mpLAlb/w==", + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", "dev": true, "dependencies": { "@babel/types": "^7.0.0" } }, "node_modules/@types/babel__template": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.3.tgz", - "integrity": "sha512-ciwyCLeuRfxboZ4isgdNZi/tkt06m8Tw6uGbBSBgWrnnZGNXiEyM27xc/PjXGQLqlZ6ylbgHMnm7ccF9tCkOeQ==", + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dev": true, "dependencies": { "@babel/parser": "^7.1.0", @@ -4102,18 +4276,18 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.3", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.3.tgz", - "integrity": "sha512-Lsh766rGEFbaxMIDH7Qa+Yha8cMVI3qAK6CHt3OR0YfxOIn5Z54iHiyDRycHrBqeIiqGa20Kpsv1cavfBKkRSw==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", "dev": true, "dependencies": { "@babel/types": "^7.20.7" } }, "node_modules/@types/body-parser": { - "version": "1.19.4", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.4.tgz", - "integrity": "sha512-N7UDG0/xiPQa2D/XrVJXjkWbpqHCd2sBaB32ggRF2l83RhPfamgKGF8gwwqyksS95qUS5ZYF9aF+lLPRlwI2UA==", + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", "dev": true, "dependencies": { "@types/connect": "*", @@ -4121,47 +4295,38 @@ } }, "node_modules/@types/clamscan": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/clamscan/-/clamscan-2.0.6.tgz", - "integrity": "sha512-8Tm9yQq2c3c/jnOf3E49Py8P9m32Ug7ZKR/owtIlxe25EFcGygSs1/gIFFFzBre4bJIkfUQ/Kfx3CUGUyZKBGw==", - "dependencies": { - "@types/node": "*", - "axios": "^0.24.0" - } - }, - "node_modules/@types/clamscan/node_modules/axios": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", - "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@types/clamscan/-/clamscan-2.0.8.tgz", + "integrity": "sha512-HaOKUH+MKgGZAYakboOSHcHga1jGRgD4kpUUslceKtsOqDY16yCLHcURETSF7jOokJOR/Z0k2wk0RL+pN0cbUg==", "dependencies": { - "follow-redirects": "^1.14.4" + "@types/node": "*" } }, "node_modules/@types/config": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@types/config/-/config-3.3.2.tgz", - "integrity": "sha512-NAxsgr73BakGnfpHq5eKmtba/x+JyPcZmSa0DzCTdR4JCxeSC+KpwSQuOMGvACs7axkjGqi44/zYRkhiX7aDew==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@types/config/-/config-3.3.4.tgz", + "integrity": "sha512-qFiTLnWy+TdPSMIXFHP+87lFXFRM4SXjRS+CSB66+56TrpLNw003y1sh7DGaaC1NGesxgKoT5FDy6dyA1Xju/g==", "dev": true }, "node_modules/@types/connect": { - "version": "3.4.37", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.37.tgz", - "integrity": "sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==", + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/cookiejar": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.3.tgz", - "integrity": "sha512-LZ8SD3LpNmLMDLkG2oCBjZg+ETnx6XdCjydUE0HwojDmnDfDUnhMKKbtth1TZh+hzcqb03azrYWoXLS8sMXdqg==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", "dev": true }, "node_modules/@types/eslint": { - "version": "8.44.6", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.6.tgz", - "integrity": "sha512-P6bY56TVmX8y9J87jHNgQh43h6VVU+6H7oN7hgvivV81K2XY8qJZ5vqPy/HdUoVIelii2kChYVzQanlswPWVFw==", + "version": "8.56.7", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.7.tgz", + "integrity": "sha512-SjDvI/x3zsZnOkYZ3lCt9lOZWZLB2jIlNKz+LBgCtDurK0JZcwucxYHn1w2BJkD34dgX9Tjnak0txtq4WTggEA==", "dev": true, "dependencies": { "@types/estree": "*", @@ -4169,9 +4334,9 @@ } }, "node_modules/@types/eslint-scope": { - "version": "3.7.6", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.6.tgz", - "integrity": "sha512-zfM4ipmxVKWdxtDaJ3MP3pBurDXOCoyjvlpE3u6Qzrmw4BPbfm4/ambIeTk/r/J0iq/+2/xp0Fmt+gFvXJY2PQ==", + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "dev": true, "dependencies": { "@types/eslint": "*", @@ -4179,15 +4344,15 @@ } }, "node_modules/@types/estree": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.4.tgz", - "integrity": "sha512-2JwWnHK9H+wUZNorf2Zr6ves96WHoWDJIftkcxPKsS7Djta6Zu519LarhRNljPXkpsZR2ZMwNCPeW7omW07BJw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, "node_modules/@types/express": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.20.tgz", - "integrity": "sha512-rOaqlkgEvOW495xErXMsmyX3WKBInbhG5eqojXYi3cGUaLoRDlXa5d52fkfWZT963AZ3v2eZ4MbKE6WpDAGVsw==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "dev": true, "dependencies": { "@types/body-parser": "*", @@ -4197,9 +4362,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.39", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.39.tgz", - "integrity": "sha512-BiEUfAiGCOllomsRAZOiMFP7LAnrifHpt56pc4Z7l9K6ACyN06Ns1JLMBxwkfLOjJRlSf06NwWsT7yzfpaVpyQ==", + "version": "4.17.43", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz", + "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==", "dev": true, "dependencies": { "@types/node": "*", @@ -4209,48 +4374,48 @@ } }, "node_modules/@types/graceful-fs": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.8.tgz", - "integrity": "sha512-NhRH7YzWq8WiNKVavKPBmtLYZHxNY19Hh+az28O/phfp68CF45pMFud+ZzJ8ewnxnC5smIdF3dqFeiSUQ5I+pw==", + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/http-errors": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.3.tgz", - "integrity": "sha512-pP0P/9BnCj1OVvQR2lF41EkDG/lWWnDyA203b/4Fmi2eTyORnBtcDoKDwjWQthELrBvWkMOrvSOnZ8OVlW6tXA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", "dev": true }, "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-zONci81DZYCZjiLe0r6equvZut0b+dBRPBN5kBDjsONnutYNtJMoWQ9uR2RkL1gLG9NMTzvf+29e5RFfPbeKhQ==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true }, "node_modules/@types/istanbul-lib-report": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.2.tgz", - "integrity": "sha512-8toY6FgdltSdONav1XtUHl4LN1yTmLza+EuDazb/fEmRNCwjyqNVIQWs2IfC74IqjHkREs/nQ2FWq5kZU9IC0w==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "dev": true, "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "node_modules/@types/istanbul-reports": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.3.tgz", - "integrity": "sha512-1nESsePMBlf0RPRffLZi5ujYh7IH1BWL4y9pr+Bn3cJBdxz+RTP8bUFljLz9HvzhhOSWKdyBZ4DIivdL6rvgZg==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dev": true, "dependencies": { "@types/istanbul-lib-report": "*" } }, "node_modules/@types/jest": { - "version": "29.5.7", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.7.tgz", - "integrity": "sha512-HLyetab6KVPSiF+7pFcUyMeLsx25LDNDemw9mGsJBkai/oouwrjTycocSDYopMEwFhN2Y4s9oPyOCZNofgSt2g==", + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", "dev": true, "dependencies": { "expect": "^29.0.0", @@ -4258,56 +4423,62 @@ } }, "node_modules/@types/json-schema": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", - "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/methods": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", "dev": true }, "node_modules/@types/mime": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.4.tgz", - "integrity": "sha512-1Gjee59G25MrQGk8bsNvC6fxNiRgUlGn2wlhGf95a59DrprnnHk80FIMMFG9XHMdrfsuA119ht06QPDXA1Z7tw==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "dev": true }, "node_modules/@types/node": { - "version": "20.8.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.10.tgz", - "integrity": "sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==", + "version": "20.12.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.4.tgz", + "integrity": "sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw==", "dependencies": { "undici-types": "~5.26.4" } }, "node_modules/@types/node-jose": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@types/node-jose/-/node-jose-1.1.12.tgz", - "integrity": "sha512-HtSXbirRMuONr/KSNtBgh631xCt/t3lPz0geQ4pe/FA+yu06TUrJrXEU5y8nJFHNy8KhiZrq6OVlqXD1AtT/dQ==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@types/node-jose/-/node-jose-1.1.13.tgz", + "integrity": "sha512-QjMd4yhwy1EvSToQn0YI3cD29YhyfxFwj7NecuymjLys2/P0FwxWnkgBlFxCai6Y3aBCe7rbwmqwJJawxlgcXw==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/qs": { - "version": "6.9.9", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.9.tgz", - "integrity": "sha512-wYLxw35euwqGvTDx6zfY1vokBFnsK0HNrzc6xNHchxfO2hpuRg74GbkEW7e3sSmPvj0TjCDT1VCa6OtHXnubsg==", + "version": "6.9.14", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz", + "integrity": "sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==", "dev": true }, "node_modules/@types/range-parser": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.6.tgz", - "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "dev": true }, "node_modules/@types/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "dev": true }, "node_modules/@types/send": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.3.tgz", - "integrity": "sha512-/7fKxvKUoETxjFUsuFlPB9YndePpxxRAOfGC/yJdc9kTjTeP5kRCTzfnE8kPUKCeyiyIZu0YQ76s50hCedI1ug==", + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", "dev": true, "dependencies": { "@types/mime": "^1", @@ -4315,14 +4486,14 @@ } }, "node_modules/@types/serve-static": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.4.tgz", - "integrity": "sha512-aqqNfs1XTF0HDrFdlY//+SGUxmdSUbjeRXb5iaZc3x0/vMbYmdw9qvOgHWOyyLFxSSRnUuP5+724zBgfw8/WAw==", + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", "dev": true, "dependencies": { "@types/http-errors": "*", - "@types/mime": "*", - "@types/node": "*" + "@types/node": "*", + "@types/send": "*" } }, "node_modules/@types/sinon": { @@ -4335,15 +4506,15 @@ } }, "node_modules/@types/sinonjs__fake-timers": { - "version": "8.1.4", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.4.tgz", - "integrity": "sha512-GDV68H0mBSN449sa5HEj51E0wfpVQb8xNSMzxf/PrypMFcLTMwJMOM/cgXiv71Mq5drkOQmUGvL1okOZcu6RrQ==", + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", + "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", "dev": true }, "node_modules/@types/source-map-support": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/@types/source-map-support/-/source-map-support-0.5.9.tgz", - "integrity": "sha512-91Jf4LyPAObBTFbpW3bSDK1ncdwXohvlBmzffSj7/44SY+1mD/HhesdfspCMxPIJwllgN2G4eVFatGs4Zw/lnw==", + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@types/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha512-tgVP2H469x9zq34Z0m/fgPewGhg/MLClalNOiPIzQlXrSS2YrKu/xCdSCKnEDwkFha51VKEKB6A9wW26/ZNwzA==", "dev": true, "dependencies": { "source-map": "^0.6.0" @@ -4359,76 +4530,78 @@ } }, "node_modules/@types/stack-utils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.2.tgz", - "integrity": "sha512-g7CK9nHdwjK2n0ymT2CW698FuWJRIx+RP6embAzZ2Qi8/ilIrA1Imt2LVSeHUzKvpoi7BhmmQcXz95eS0f2JXw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, "node_modules/@types/superagent": { - "version": "4.1.20", - "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.20.tgz", - "integrity": "sha512-GfpwJgYSr3yO+nArFkmyqv3i0vZavyEG5xPd/o95RwpKYpsOKJYI5XLdxLpdRbZI3YiGKKdIOFIf/jlP7A0Jxg==", + "version": "8.1.6", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.6.tgz", + "integrity": "sha512-yzBOv+6meEHSzV2NThYYOA6RtqvPr3Hbob9ZLp3i07SH27CrYVfm8CrF7ydTmidtelsFiKx2I4gZAiAOamGgvQ==", "dev": true, "dependencies": { - "@types/cookiejar": "*", + "@types/cookiejar": "^2.1.5", + "@types/methods": "^1.1.4", "@types/node": "*" } }, "node_modules/@types/supertest": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.15.tgz", - "integrity": "sha512-jUCZZ/TMcpGzoSaed9Gjr8HCf3HehExdibyw3OHHEL1als1KmyzcOZZH4MjbObI8TkWsEr7bc7gsW0WTDni+qQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.2.tgz", + "integrity": "sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==", "dev": true, "dependencies": { - "@types/superagent": "*" + "@types/methods": "^1.1.4", + "@types/superagent": "^8.1.0" } }, "node_modules/@types/uuid": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.6.tgz", - "integrity": "sha512-BT2Krtx4xaO6iwzwMFUYvWBWkV2pr37zD68Vmp1CDV196MzczBRxuEpD6Pr395HAgebC/co7hOphs53r8V7jew==", + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", "dev": true }, "node_modules/@types/validator": { - "version": "13.11.5", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.5.tgz", - "integrity": "sha512-xW4qsT4UIYILu+7ZrBnfQdBYniZrMLYYK3wN9M/NdeIHgBN5pZI2/8Q7UfdWIcr5RLJv/OGENsx91JIpUUoC7Q==" + "version": "13.11.9", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.9.tgz", + "integrity": "sha512-FCTsikRozryfayPuiI46QzH3fnrOoctTjvOYZkho9BTFLCOZ2rgZJHMOVgCOfttjPJcgOx52EpkY0CMfy87MIw==" }, "node_modules/@types/yargs": { - "version": "17.0.29", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.29.tgz", - "integrity": "sha512-nacjqA3ee9zRF/++a3FUY1suHTFKZeHba2n8WeDw9cCVdmzmHpIxyzOJBcpHvvEmS8E9KqWlSnWHUkOrkhWcvA==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "dependencies": { "@types/yargs-parser": "*" } }, "node_modules/@types/yargs-parser": { - "version": "21.0.2", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.2.tgz", - "integrity": "sha512-5qcvofLPbfjmBfKaLfj/+f+Sbd6pN4zl7w7VSVI5uz7m9QZTuB2aZAa2uo1wHFBNN2x6g/SoTkXmd8mQnQF2Cw==", + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true }, "node_modules/@types/yauzl": { - "version": "2.10.2", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.2.tgz", - "integrity": "sha512-Km7XAtUIduROw7QPgvcft0lIupeG8a8rdKL8RiSyKvlE7dYY31fEn41HVuQsRFDuROA8tA4K2UVL+WdfFmErBA==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", "optional": true, "dependencies": { "@types/node": "*" } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.9.1.tgz", - "integrity": "sha512-w0tiiRc9I4S5XSXXrMHOWgHgxbrBn1Ro+PmiYhSg2ZVdxrAJtQgzU5o2m1BfP6UOn7Vxcc6152vFjQfmZR4xEg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.5.0.tgz", + "integrity": "sha512-HpqNTH8Du34nLxbKgVMGljZMG0rJd2O9ecvr2QLYp+7512ty1j42KnsFwspPXg1Vh8an9YImf6CokUBltisZFQ==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.9.1", - "@typescript-eslint/type-utils": "6.9.1", - "@typescript-eslint/utils": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1", + "@typescript-eslint/scope-manager": "7.5.0", + "@typescript-eslint/type-utils": "7.5.0", + "@typescript-eslint/utils": "7.5.0", + "@typescript-eslint/visitor-keys": "7.5.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -4437,15 +4610,15 @@ "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -4454,26 +4627,26 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.9.1.tgz", - "integrity": "sha512-C7AK2wn43GSaCUZ9do6Ksgi2g3mwFkMO3Cis96kzmgudoVaKyt62yNzJOktP0HDLb/iO2O0n2lBOzJgr6Q/cyg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.5.0.tgz", + "integrity": "sha512-cj+XGhNujfD2/wzR1tabNsidnYRaFfEkcULdcIyVBYcXjBvBKOes+mpMBP7hMpOyk+gBcfXsrg4NBGAStQyxjQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.9.1", - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/typescript-estree": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1", + "@typescript-eslint/scope-manager": "7.5.0", + "@typescript-eslint/types": "7.5.0", + "@typescript-eslint/typescript-estree": "7.5.0", + "@typescript-eslint/visitor-keys": "7.5.0", "debug": "^4.3.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -4482,16 +4655,16 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.9.1.tgz", - "integrity": "sha512-38IxvKB6NAne3g/+MyXMs2Cda/Sz+CEpmm+KLGEM8hx/CvnSRuw51i8ukfwB/B/sESdeTGet1NH1Wj7I0YXswg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.5.0.tgz", + "integrity": "sha512-Z1r7uJY0MDeUlql9XJ6kRVgk/sP11sr3HKXn268HZyqL7i4cEfrdFuSSY/0tUqT37l5zT0tJOsuDP16kio85iA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1" + "@typescript-eslint/types": "7.5.0", + "@typescript-eslint/visitor-keys": "7.5.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -4499,25 +4672,25 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.9.1.tgz", - "integrity": "sha512-eh2oHaUKCK58qIeYp19F5V5TbpM52680sB4zNSz29VBQPTWIlE/hCj5P5B1AChxECe/fmZlspAWFuRniep1Skg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.5.0.tgz", + "integrity": "sha512-A021Rj33+G8mx2Dqh0nMO9GyjjIBK3MqgVgZ2qlKf6CJy51wY/lkkFqq3TqqnH34XyAHUkq27IjlUkWlQRpLHw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.9.1", - "@typescript-eslint/utils": "6.9.1", + "@typescript-eslint/typescript-estree": "7.5.0", + "@typescript-eslint/utils": "7.5.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -4526,12 +4699,12 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.9.1.tgz", - "integrity": "sha512-BUGslGOb14zUHOUmDB2FfT6SI1CcZEJYfF3qFwBeUrU6srJfzANonwRYHDpLBuzbq3HaoF2XL2hcr01c8f8OaQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.5.0.tgz", + "integrity": "sha512-tv5B4IHeAdhR7uS4+bf8Ov3k793VEVHd45viRRkehIUZxm0WF82VPiLgHzA/Xl4TGPg1ZD49vfxBKFPecD5/mg==", "dev": true, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -4539,21 +4712,22 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.9.1.tgz", - "integrity": "sha512-U+mUylTHfcqeO7mLWVQ5W/tMLXqVpRv61wm9ZtfE5egz7gtnmqVIw9ryh0mgIlkKk9rZLY3UHygsBSdB9/ftyw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.5.0.tgz", + "integrity": "sha512-YklQQfe0Rv2PZEueLTUffiQGKQneiIEKKnfIqPIOxgM9lKSZFCjT5Ad4VqRKj/U4+kQE3fa8YQpskViL7WjdPQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1", + "@typescript-eslint/types": "7.5.0", + "@typescript-eslint/visitor-keys": "7.5.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", + "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -4566,41 +4740,41 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.9.1.tgz", - "integrity": "sha512-L1T0A5nFdQrMVunpZgzqPL6y2wVreSyHhKGZryS6jrEN7bD9NplVAyMryUhXsQ4TWLnZmxc2ekar/lSGIlprCA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.5.0.tgz", + "integrity": "sha512-3vZl9u0R+/FLQcpy2EHyRGNqAS/ofJ3Ji8aebilfJe+fobK8+LbIFmrHciLVDxjDoONmufDcnVSF38KwMEOjzw==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.9.1", - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/typescript-estree": "6.9.1", + "@typescript-eslint/scope-manager": "7.5.0", + "@typescript-eslint/types": "7.5.0", + "@typescript-eslint/typescript-estree": "7.5.0", "semver": "^7.5.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.9.1.tgz", - "integrity": "sha512-MUaPUe/QRLEffARsmNfmpghuQkW436DvESW+h+M52w0coICHRfD6Np9/K6PdACwnrq1HmuLl+cSPZaJmeVPkSw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.5.0.tgz", + "integrity": "sha512-mcuHM/QircmA6O7fy6nn2w/3ditQkj+SgtOc8DW3uQ10Yfj42amm2i+6F2K4YAOPNNTmE6iM1ynM6lrSwdendA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/types": "7.5.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -4614,9 +4788,9 @@ "dev": true }, "node_modules/@webassemblyjs/ast": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", - "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", "dev": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.11.6", @@ -4636,9 +4810,9 @@ "dev": true }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", - "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", "dev": true }, "node_modules/@webassemblyjs/helper-numbers": { @@ -4659,15 +4833,15 @@ "dev": true }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", - "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" + "@webassemblyjs/wasm-gen": "1.12.1" } }, "node_modules/@webassemblyjs/ieee754": { @@ -4695,28 +4869,28 @@ "dev": true }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", - "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", - "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/ieee754": "1.11.6", "@webassemblyjs/leb128": "1.11.6", @@ -4724,24 +4898,24 @@ } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", - "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", - "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-api-error": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/ieee754": "1.11.6", @@ -4750,12 +4924,12 @@ } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", - "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@xtuc/long": "4.2.2" } }, @@ -4772,9 +4946,12 @@ "dev": true }, "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, "node_modules/abort-controller": { "version": "3.0.0", @@ -4793,9 +4970,9 @@ "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==" }, "node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "devOptional": true, "bin": { "acorn": "bin/acorn" @@ -4823,24 +5000,24 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", - "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", "devOptional": true, "engines": { "node": ">=0.4.0" } }, "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "optional": true, "dependencies": { - "debug": "4" + "debug": "^4.3.4" }, "engines": { - "node": ">= 6.0.0" + "node": ">= 14" } }, "node_modules/ajv": { @@ -4948,6 +5125,17 @@ "node": ">= 8" } }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/app-root-path": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", @@ -4962,10 +5150,10 @@ "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==" }, "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "devOptional": true }, "node_modules/argparse": { "version": "2.0.1", @@ -5004,6 +5192,18 @@ "safer-buffer": "^2.1.0" } }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "optional": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -5018,65 +5218,66 @@ } }, "node_modules/automapper-classes": { - "version": "8.7.11", - "resolved": "https://registry.npmjs.org/automapper-classes/-/automapper-classes-8.7.11.tgz", - "integrity": "sha512-FCmSIJzuzZEh0imZ5wm5J6uyODIQPX93DSjZ7qj2GvvwJPcJ3jkt3zG6urNegSgnVs8ujDC27LrGtUL8Hv729A==", + "version": "8.7.12", + "resolved": "https://registry.npmjs.org/automapper-classes/-/automapper-classes-8.7.12.tgz", + "integrity": "sha512-qhuAfh+17Zie5ucw1BZzI5zxgPC7yKw8pBqf9g3OO27RjHwpMhPnylQ97ucVfQ8hjBiwWx8jDlUUu1UliL3yiA==", "engines": { "node": ">=16.0.0" }, "peerDependencies": { - "automapper-core": "8.7.11", + "automapper-core": "8.7.12", "reflect-metadata": "~0.1.13" } }, "node_modules/automapper-core": { - "version": "8.7.11", - "resolved": "https://registry.npmjs.org/automapper-core/-/automapper-core-8.7.11.tgz", - "integrity": "sha512-CY4t9YvFD/zG9/k3fO/1IgGw7+T17wLJGbPEVKzDQ5PfPUu37cWTFV5Fb0T+kmUi3DVxlc6uZ7/5Mrd0l4fWfQ==", + "version": "8.7.12", + "resolved": "https://registry.npmjs.org/automapper-core/-/automapper-core-8.7.12.tgz", + "integrity": "sha512-zqFsY2ZzLyJEXSZ8X0J5ZmNMRkRNXqFj6SkkfS7qcMaSdY9tYRnFHLoWkExcbqiJJG4PzSnXnhanZfiHsDVjZQ==", "engines": { "node": ">=16.0.0" } }, "node_modules/automapper-nestjs": { - "version": "8.7.11", - "resolved": "https://registry.npmjs.org/automapper-nestjs/-/automapper-nestjs-8.7.11.tgz", - "integrity": "sha512-fy6pzwu8AvMeuCFfyO7iAle9PKL6T0z/6STn2C32HkB0MhtNLm4F/qpKHv6xMtvUSkoSNs/ne18JRCjRfIUWng==", + "version": "8.7.12", + "resolved": "https://registry.npmjs.org/automapper-nestjs/-/automapper-nestjs-8.7.12.tgz", + "integrity": "sha512-iP7UzelYNpg4zPPNZcrVoKUc5k+EuG5w7kpQ7zFplgCeOCD8pka3ArUmKA7rQCuJ4F9R3oDagMkds5qkBarnGA==", "engines": { "node": ">=16.0.0" }, "peerDependencies": { "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", "@nestjs/core": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", - "automapper-core": "8.7.11" + "automapper-core": "8.7.12" } }, "node_modules/avvio": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/avvio/-/avvio-8.2.1.tgz", - "integrity": "sha512-TAlMYvOuwGyLK3PfBb5WKBXZmXz2fVCgv23d6zZFdle/q3gPjmxBaeuC0pY0Dzs5PWMSgfqqEZkrye19GlDTgw==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-8.3.0.tgz", + "integrity": "sha512-VBVH0jubFr9LdFASy/vNtm5giTrnbVquWBhT0fyizuNK2rQ7e7ONU2plZQWUNqtE1EmxFEb+kbSkFRkstiaS9Q==", "dependencies": { + "@fastify/error": "^3.3.0", "archy": "^1.0.0", "debug": "^4.0.0", - "fastq": "^1.6.1" + "fastq": "^1.17.1" } }, "node_modules/aws-sdk-client-mock": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/aws-sdk-client-mock/-/aws-sdk-client-mock-2.2.0.tgz", - "integrity": "sha512-Kq2N+6gHRDedbrgTA0NMMfyN1XDWEA5Kbpm9/M/cenSxoNjfvQBOtBawI1lQe5h4UziLl///E7u17K9PBoHEKA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/aws-sdk-client-mock/-/aws-sdk-client-mock-4.0.0.tgz", + "integrity": "sha512-/rxo+pzCFaUozK7TyCqo3GYwzdBGn9Ai6EsT8ytXDoUXlD/Q5hw9hj2lOkCAyubECzGJFHMmQg9GZ1GOGlN/qQ==", "dev": true, "dependencies": { "@types/sinon": "^10.0.10", - "sinon": "^14.0.2", + "sinon": "^16.1.3", "tslib": "^2.1.0" } }, "node_modules/axios": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz", - "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", "dependencies": { - "follow-redirects": "^1.15.0", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -5229,21 +5430,24 @@ "node": ">=6.0.0" } }, - "node_modules/big-integer": { - "version": "1.6.51", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", - "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", - "dev": true, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "optional": true, "engines": { - "node": ">=0.6" + "node": ">=10.0.0" } }, "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/bl": { @@ -5296,25 +5500,12 @@ "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" }, - "node_modules/bplist-parser": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", - "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", - "dev": true, - "dependencies": { - "big-integer": "^1.6.44" - }, - "engines": { - "node": ">= 5.10.0" - } - }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -5334,9 +5525,9 @@ "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" }, "node_modules/browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "dev": true, "funding": [ { @@ -5353,9 +5544,9 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, "bin": { @@ -5423,55 +5614,35 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, - "node_modules/buffer-writer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", - "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", - "engines": { - "node": ">=4" - } - }, "node_modules/bullmq": { - "version": "4.12.7", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-4.12.7.tgz", - "integrity": "sha512-wigDuI8dyzY1jaUZLrwMp0L7t2glp0eErnRCYlVwi56DUWYSrzrOB3Vz8SaAmpc3Ro5dS4mBwt7RDJG3jiuJKA==", + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.5.1.tgz", + "integrity": "sha512-8to4uhjNmyFmwz6OeDiPsUHIX+wmkZ7MRRwG+LX/91tbv+uTTX5BwivQIuw2onBlXaNjiWEg16eyoZD3T5jX8Q==", "peer": true, "dependencies": { "cron-parser": "^4.6.0", - "glob": "^8.0.3", "ioredis": "^5.3.2", - "lodash": "^4.17.21", - "msgpackr": "^1.6.2", + "msgpackr": "^1.10.1", "node-abort-controller": "^3.1.1", "semver": "^7.5.4", "tslib": "^2.0.0", "uuid": "^9.0.0" } }, - "node_modules/bundle-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", - "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==", - "dev": true, - "dependencies": { - "run-applescript": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dev": true, "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5505,9 +5676,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001559", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001559.tgz", - "integrity": "sha512-cPiMKZgqgkg5LY3/ntGeLFUpi6tzddBNS58A4tnTgQw1zON7u2sZMU7SzOeVH4tj20++9ggL+V6FDOFMTaFFYA==", + "version": "1.0.30001605", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001605.tgz", + "integrity": "sha512-nXwGlFWo34uliI9z3n6Qc0wZaf7zaZWA1CPZ169La5mV3I/gem7bst0vr5XQH5TJXZIMfDeZyOrZnSlVzKxxHQ==", "dev": true, "funding": [ { @@ -5602,15 +5773,9 @@ } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -5623,6 +5788,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -5637,19 +5805,19 @@ } }, "node_modules/chromedriver": { - "version": "119.0.0", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-119.0.0.tgz", - "integrity": "sha512-3TmabGT7xg57/Jbsg6B/Kqk3HaSbCP1ZHkR5zNft5vT/IWKjZCAGTH9waMI+i5KHSEiMH0zOw/WF98l+1Npkpw==", + "version": "123.0.1", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-123.0.1.tgz", + "integrity": "sha512-YQUIP/zdlzDIRCZNCv6rEVDSY4RAxo/tDL0OiGPPuai+z8unRNqJr/9V6XTBypVFyDheXNalKt9QxEqdMPuLAQ==", "hasInstallScript": true, "optional": true, "dependencies": { - "@testim/chrome-version": "^1.1.3", - "axios": "^1.4.0", - "compare-versions": "^6.0.0", + "@testim/chrome-version": "^1.1.4", + "axios": "^1.6.7", + "compare-versions": "^6.1.0", "extract-zip": "^2.0.1", - "https-proxy-agent": "^5.0.1", + "proxy-agent": "^6.4.0", "proxy-from-env": "^1.1.0", - "tcp-port-used": "^1.0.1" + "tcp-port-used": "^1.0.2" }, "bin": { "chromedriver": "bin/chromedriver" @@ -5680,11 +5848,11 @@ "dev": true }, "node_modules/clamscan": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clamscan/-/clamscan-2.1.2.tgz", - "integrity": "sha512-pcovgLHcrg3l/mI51Kuk0kN++07pSZdBTskISw0UFvsm8UXda8oNCm0eLeODxFg85Mz+k+TtSS9+XPlriJ8/Fg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/clamscan/-/clamscan-2.2.1.tgz", + "integrity": "sha512-ureXxucH9MfkhyR4nsJMWPnwq/mKlSYHB5RtkuqWltgSF06kET/C36iAeJuGiGXIWc1bi1FMMoptysHLkIRA/g==", "engines": { - "node": ">=12.0.0" + "node": ">=16.0.0" } }, "node_modules/class-transformer": { @@ -5693,13 +5861,13 @@ "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" }, "node_modules/class-validator": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.0.tgz", - "integrity": "sha512-ct3ltplN8I9fOwUd8GrP8UQixwff129BkEtuWDKL5W45cQuLd19xqmTLu5ge78YDm/fdje6FMt0hGOhl0lii3A==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz", + "integrity": "sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==", "dependencies": { - "@types/validator": "^13.7.10", - "libphonenumber-js": "^1.10.14", - "validator": "^13.7.0" + "@types/validator": "^13.11.8", + "libphonenumber-js": "^1.10.53", + "validator": "^13.9.0" } }, "node_modules/clean-css": { @@ -5823,9 +5991,9 @@ } }, "node_modules/cli-spinners": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.1.tgz", - "integrity": "sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "dev": true, "engines": { "node": ">=6" @@ -5985,20 +6153,24 @@ "optional": true }, "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, "node_modules/config": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/config/-/config-3.3.9.tgz", - "integrity": "sha512-G17nfe+cY7kR0wVpc49NCYvNtelm/pPy8czHoFkAgtV1lkmcp7DHtWCdDu+C9Z7gb2WVqa9Tm3uF9aKaPbCfhg==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/config/-/config-3.3.11.tgz", + "integrity": "sha512-Dhn63ZoWCW5EMg4P0Sl/XNsj/7RLiUIA1x1npCy+m2cRwRHzLnt3UtYtxRDMZW/6oOMdWhCzaGYkOcajGgrAOA==", "dependencies": { "json5": "^2.2.3" }, @@ -6038,9 +6210,9 @@ "dev": true }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -6175,6 +6347,15 @@ "node": ">= 10" } }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "optional": true, + "engines": { + "node": ">= 14" + } + }, "node_modules/dateformat": { "version": "4.6.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", @@ -6223,159 +6404,15 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "devOptional": true - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-browser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz", - "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==", - "dev": true, - "dependencies": { - "bundle-name": "^3.0.0", - "default-browser-id": "^3.0.0", - "execa": "^7.1.1", - "titleize": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz", - "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==", - "dev": true, - "dependencies": { - "bplist-parser": "^0.2.0", - "untildify": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/execa": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", - "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^4.3.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^3.0.7", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": "^14.18.0 || ^16.14.0 || >=18.0.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/default-browser/node_modules/human-signals": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", - "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", - "dev": true, - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/default-browser/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/npm-run-path": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", - "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", - "dev": true, - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "devOptional": true }, - "node_modules/default-browser/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, "node_modules/defaults": { @@ -6391,29 +6428,34 @@ } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "dev": true, - "engines": { - "node": ">=12" + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "optional": true, + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 14" } }, "node_modules/delayed-stream": { @@ -6477,9 +6519,9 @@ } }, "node_modules/diff": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", - "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true, "engines": { "node": ">=0.3.1" @@ -6570,14 +6612,14 @@ } }, "node_modules/dotenv": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", - "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" + "url": "https://dotenvx.com" } }, "node_modules/dotenv-expand": { @@ -6618,14 +6660,6 @@ "node": ">=14" } }, - "node_modules/editorconfig/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/editorconfig/node_modules/commander": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", @@ -6649,15 +6683,15 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.574", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.574.tgz", - "integrity": "sha512-bg1m8L0n02xRzx4LsTTMbBPiUd9yIR+74iPtS/Ao65CuXvhVZHP0ym1kSdDG3yHFDXqHQQBKujlN1AQ8qZnyFg==", + "version": "1.4.726", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.726.tgz", + "integrity": "sha512-xtjfBXn53RORwkbyKvDfTajtnTp0OJoPOIBzXvkNbb7+YYvCHJflba3L7Txyx/6Fov3ov2bGPr/n5MTixmPhdQ==", "dev": true }, "node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.5.tgz", + "integrity": "sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw==", "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -6695,9 +6729,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz", + "integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -6727,10 +6761,31 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz", - "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.0.tgz", + "integrity": "sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw==", "dev": true }, "node_modules/es6-promise": { @@ -6739,9 +6794,9 @@ "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "engines": { "node": ">=6" } @@ -6774,17 +6829,47 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "optional": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/eslint": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", - "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.52.0", - "@humanwhocodes/config-array": "^0.11.13", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -6830,9 +6915,9 @@ } }, "node_modules/eslint-config-prettier": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", - "integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" @@ -6842,23 +6927,24 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.1.tgz", - "integrity": "sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", + "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", "dev": true, "dependencies": { "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.8.5" + "synckit": "^0.8.6" }, "engines": { "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://opencollective.com/prettier" + "url": "https://opencollective.com/eslint-plugin-prettier" }, "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", + "eslint-config-prettier": "*", "prettier": ">=3.0.0" }, "peerDependenciesMeta": { @@ -6914,6 +7000,16 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/eslint/node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -6932,6 +7028,18 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -6953,7 +7061,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, + "devOptional": true, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -6990,7 +7098,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=4.0" } @@ -6999,7 +7107,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -7123,9 +7231,9 @@ "integrity": "sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==" }, "node_modules/fast-copy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.1.tgz", - "integrity": "sha512-Knr7NOtK3HWRYGtHoJrjkaWepqT8thIVGAwt0p0aUs1zqkAzXZV4vo9fFNwyb5fcqK1GKYFYxldQdIDVKhUAfA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", "dev": true }, "node_modules/fast-decode-uri-component": { @@ -7145,9 +7253,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -7167,11 +7275,11 @@ "dev": true }, "node_modules/fast-json-stringify": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-5.9.0.tgz", - "integrity": "sha512-hXu5mchOrV7r1wzY2na0VEJZbb2gUAv5Mrlnq1QvdhZHt8k3pYnwZ6YEVbasbvPc1Ki+FyeTRJW8yDN9jN40yQ==", + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-5.13.0.tgz", + "integrity": "sha512-XjTDWKHP3GoMQUOfnjYUbqeHeEt+PvYgvBdG2fRSmYaORILbSr8xTJvZX+w1YSAP5pw2NwKrGRmQleYueZEoxw==", "dependencies": { - "@fastify/deepmerge": "^1.0.0", + "@fastify/merge-json-schemas": "^0.1.0", "ajv": "^8.10.0", "ajv-formats": "^2.1.1", "fast-deep-equal": "^3.1.3", @@ -7195,9 +7303,9 @@ } }, "node_modules/fast-redact": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.3.0.tgz", - "integrity": "sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", "engines": { "node": ">=6" } @@ -7234,21 +7342,31 @@ } }, "node_modules/fastify": { - "version": "4.24.3", - "resolved": "https://registry.npmjs.org/fastify/-/fastify-4.24.3.tgz", - "integrity": "sha512-6HHJ+R2x2LS3y1PqxnwEIjOTZxFl+8h4kSC/TuDPXtA+v2JnV9yEtOsNSKK1RMD7sIR2y1ZsA4BEFaid/cK5pg==", + "version": "4.26.2", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-4.26.2.tgz", + "integrity": "sha512-90pjTuPGrfVKtdpLeLzND5nyC4woXZN5VadiNQCicj/iJU4viNHKhsAnb7jmv1vu2IzkLXyBiCzdWuzeXgQ5Ug==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], "dependencies": { "@fastify/ajv-compiler": "^3.5.0", "@fastify/error": "^3.4.0", "@fastify/fast-json-stringify-compiler": "^4.3.0", "abstract-logging": "^2.0.1", - "avvio": "^8.2.1", + "avvio": "^8.3.0", "fast-content-type-parse": "^1.1.0", "fast-json-stringify": "^5.8.0", - "find-my-way": "^7.7.0", + "find-my-way": "^8.0.0", "light-my-request": "^5.11.0", - "pino": "^8.16.0", - "process-warning": "^2.2.0", + "pino": "^8.17.0", + "process-warning": "^3.0.0", "proxy-addr": "^2.0.7", "rfdc": "^1.3.0", "secure-json-parse": "^2.7.0", @@ -7262,9 +7380,9 @@ "integrity": "sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==" }, "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dependencies": { "reusify": "^1.0.4" } @@ -7335,9 +7453,9 @@ } }, "node_modules/find-my-way": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-7.7.0.tgz", - "integrity": "sha512-+SrHpvQ52Q6W9f3wJoJBbAQULJuNEEQwBvlvYwACDhBTLOTMiQ0HYWh4+vC3OivGP2ENcTI1oKlFA2OepJNjhQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-8.1.0.tgz", + "integrity": "sha512-41QwjCGcVTODUmLLqTMeoHeiozbMXYMAE1CKFiDyi9zVZ2Vjh0yz3MF0WQZoIb+cmzP/XlbFjlF2NtJmvZHznA==", "dependencies": { "fast-deep-equal": "^3.1.3", "fast-querystring": "^1.0.0", @@ -7364,9 +7482,9 @@ } }, "node_modules/flat-cache": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz", - "integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "dependencies": { "flatted": "^3.2.9", @@ -7374,7 +7492,17 @@ "rimraf": "^3.0.2" }, "engines": { - "node": ">=12.0.0" + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flat-cache/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, "node_modules/flat-cache/node_modules/glob": { @@ -7397,6 +7525,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/flat-cache/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/flat-cache/node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -7413,15 +7553,15 @@ } }, "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -7491,6 +7631,16 @@ "webpack": "^5.11.0" } }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/memfs": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", @@ -7503,6 +7653,18 @@ "node": ">= 4.0.0" } }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -7562,7 +7724,8 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "node_modules/fsevents": { "version": "2.3.3", @@ -7612,16 +7775,20 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7647,19 +7814,51 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-uri": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", + "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", + "optional": true, + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4", + "fs-extra": "^11.2.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "optional": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.6", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": ">=12" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -7682,29 +7881,10 @@ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -7757,7 +7937,7 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true + "devOptional": true }, "node_modules/graphemer": { "version": "1.4.0", @@ -7811,21 +7991,21 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "dev": true, "engines": { "node": ">= 0.4" @@ -7856,9 +8036,9 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, "dependencies": { "function-bind": "^1.1.2" @@ -7876,22 +8056,18 @@ } }, "node_modules/helmet": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-7.0.0.tgz", - "integrity": "sha512-MsIgYmdBh460ZZ8cJC81q4XJknjG567wzEmv46WOBblDb6TUd3z8/GhgmsM9pn8g2B80tAJ4m5/d3Bi1KrSUBQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-7.1.0.tgz", + "integrity": "sha512-g+HZqgfbpXdCkme/Cd/mZkV0aV3BZZZSugecH03kl38m/Kmdx8jKjBikpDj2cr+Iynv4KpYEviojNdTJActJAg==", "engines": { "node": ">=16.0.0" } }, "node_modules/help-me": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/help-me/-/help-me-4.2.0.tgz", - "integrity": "sha512-TAOnTB8Tz5Dw8penUuzHVrKNKlCIbwwbHnXraNJxPwf8LRtE2HlM84RYuezMFcwOJmoYOCWVDyJ8TQGxn9PgxA==", - "dev": true, - "dependencies": { - "glob": "^8.0.0", - "readable-stream": "^3.6.0" - } + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "dev": true }, "node_modules/hexoid": { "version": "1.0.0", @@ -7984,17 +8160,30 @@ "node": ">= 0.8" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "optional": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", "optional": true, "dependencies": { - "agent-base": "6", + "agent-base": "^7.0.2", "debug": "4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/human-signals": { @@ -8006,15 +8195,6 @@ "node": ">=10.17.0" } }, - "node_modules/hyperdyperid": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", - "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", - "dev": true, - "engines": { - "node": ">=10.18" - } - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -8047,9 +8227,9 @@ ] }, "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, "engines": { "node": ">= 4" @@ -8103,6 +8283,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -8177,6 +8358,19 @@ "url": "https://opencollective.com/ioredis" } }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "optional": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/ip-regex": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", @@ -8223,21 +8417,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "dev": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -8274,24 +8453,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "dev": true, - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -8348,33 +8509,6 @@ "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", "optional": true }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-wsl/node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is2": { "version": "2.0.9", "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.9.tgz", @@ -8389,35 +8523,29 @@ "node": ">=v0.10.0" } }, - "node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "dev": true - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/istanbul-lib-instrument": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", - "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz", + "integrity": "sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==", "dev": true, "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", "istanbul-lib-coverage": "^3.2.0", "semver": "^7.5.4" }, @@ -8463,9 +8591,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", - "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, "dependencies": { "html-escaper": "^2.0.0", @@ -8649,6 +8777,16 @@ } } }, + "node_modules/jest-config/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/jest-config/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -8669,6 +8807,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/jest-config/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/jest-diff": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", @@ -8968,6 +9118,16 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-runtime/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/jest-runtime/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -8988,6 +9148,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/jest-runtime/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/jest-snapshot": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", @@ -9036,6 +9208,18 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/jest-validate": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", @@ -9124,14 +9308,15 @@ } }, "node_modules/js-beautify": { - "version": "1.14.9", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.9.tgz", - "integrity": "sha512-coM7xq1syLcMyuVGyToxcj2AlzhkDjmfklL8r0JgJ7A76wyGMpJ1oA35mr4APdYNO/o/4YY8H54NQIJzhMbhBg==", + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz", + "integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==", "dependencies": { "config-chain": "^1.1.13", - "editorconfig": "^1.0.3", - "glob": "^8.1.0", - "nopt": "^6.0.0" + "editorconfig": "^1.0.4", + "glob": "^10.3.3", + "js-cookie": "^3.0.5", + "nopt": "^7.2.0" }, "bin": { "css-beautify": "js/bin/css-beautify.js", @@ -9139,7 +9324,15 @@ "js-beautify": "js/bin/js-beautify.js" }, "engines": { - "node": ">=12" + "node": ">=14" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "engines": { + "node": ">=14" } }, "node_modules/js-tokens": { @@ -9159,6 +9352,12 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "optional": true + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -9177,37 +9376,6 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, - "node_modules/json-joy": { - "version": "9.7.0", - "resolved": "https://registry.npmjs.org/json-joy/-/json-joy-9.7.0.tgz", - "integrity": "sha512-TcIkX4xLWgzM+/jIJrfk/1wEXGxRYPBMHRENVZKuDQzOf3Cl+WvI4ykHwobddWhbpI7OLN2IBXwmFv7WTYO5AA==", - "dev": true, - "dependencies": { - "arg": "^5.0.2", - "hyperdyperid": "^1.2.0" - }, - "bin": { - "jj": "bin/jj.js", - "json-pack": "bin/json-pack.js", - "json-pack-test": "bin/json-pack-test.js", - "json-patch": "bin/json-patch.js", - "json-patch-test": "bin/json-patch-test.js", - "json-pointer": "bin/json-pointer.js", - "json-pointer-test": "bin/json-pointer-test.js", - "json-unpack": "bin/json-unpack.js" - }, - "engines": { - "node": ">=10.0" - }, - "funding": { - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "quill-delta": "^5", - "rxjs": "7", - "tslib": "2" - } - }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -9222,22 +9390,6 @@ "fast-deep-equal": "^3.1.3" } }, - "node_modules/json-schema-resolver": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/json-schema-resolver/-/json-schema-resolver-2.0.0.tgz", - "integrity": "sha512-pJ4XLQP4Q9HTxl6RVDLJ8Cyh1uitSs0CzDBAz1uoJ4sRD/Bk7cFSXL1FUXDW3zJ7YnfliJx6eu8Jn283bpZ4Yg==", - "dependencies": { - "debug": "^4.1.1", - "rfdc": "^1.1.4", - "uri-js": "^4.2.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/Eomm/json-schema-resolver?sponsor=1" - } - }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -9270,7 +9422,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, + "devOptional": true, "dependencies": { "universalify": "^2.0.0" }, @@ -9279,9 +9431,9 @@ } }, "node_modules/juice": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/juice/-/juice-9.1.0.tgz", - "integrity": "sha512-odblShmPrUoHUwRuC8EmLji5bPP2MLO1GL+gt4XU3tT2ECmbSrrMjtMQaqg3wgMFP2zvUzdPZGfxc5Trk3Z+fQ==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/juice/-/juice-10.0.0.tgz", + "integrity": "sha512-9f68xmhGrnIi6DBkiiP3rUrQN33SEuaKu1+njX6VgMP+jwZAsnT33WIzlrWICL9matkhYu3OyrqSUP55YTIdGg==", "dependencies": { "cheerio": "^1.0.0-rc.12", "commander": "^6.1.0", @@ -9305,9 +9457,9 @@ } }, "node_modules/just-extend": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", - "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", "dev": true }, "node_modules/jwk-to-pem": { @@ -9321,10 +9473,9 @@ } }, "node_modules/keycloak-connect": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/keycloak-connect/-/keycloak-connect-21.1.2.tgz", - "integrity": "sha512-ydRauaPu3lIlug1xlweAkV4N0InYf8QhkeWz0jzmjEyuPD88DHkGSQ5NcGyySAhMPPvKHISj1dty1Zs2uVu+EQ==", - "deprecated": "This package is deprecated and will be removed in the future. We will shortly provide more details on removal date, and recommended alternatives.", + "version": "24.0.2", + "resolved": "https://registry.npmjs.org/keycloak-connect/-/keycloak-connect-24.0.2.tgz", + "integrity": "sha512-o4hrHAI5SxIVqyD1yY0uIhDM6y9hZyyb3pJc1yYakhfhn2WPqmOGeq8zELPS1XJpj+k/paS+65LW45en1HgebA==", "dependencies": { "jwk-to-pem": "^2.0.0" }, @@ -9376,17 +9527,17 @@ } }, "node_modules/libphonenumber-js": { - "version": "1.10.49", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.49.tgz", - "integrity": "sha512-gvLtyC3tIuqfPzjvYLH9BmVdqzGDiSi4VjtWe2fAgSdBf0yt8yPmbNnRIHNbR5IdtVkm0ayGuzwQKTWmU0hdjQ==" + "version": "1.10.59", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.59.tgz", + "integrity": "sha512-HeTsOrDF/hWhEiKqZVwg9Cqlep5x2T+IYDENvT2VRj3iX8JQ7Y+omENv+AIn0vC8m6GYhivfCed5Cgfw27r5SA==" }, "node_modules/light-my-request": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.11.0.tgz", - "integrity": "sha512-qkFCeloXCOMpmEdZ/MV91P8AT4fjwFXWaAFz3lUeStM8RcoM1ks4J/F8r1b3r6y/H4u3ACEJ1T+Gv5bopj7oDA==", + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.12.0.tgz", + "integrity": "sha512-P526OX6E7aeCIfw/9UyJNsAISfcFETghysaWHQAlQYayynShT08MOj4c6fBCvTWBrHXSvqBAKDp3amUPSCQI4w==", "dependencies": { - "cookie": "^0.5.0", - "process-warning": "^2.0.0", + "cookie": "^0.6.0", + "process-warning": "^3.0.0", "set-cookie-parser": "^2.4.1" } }, @@ -9430,13 +9581,6 @@ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", - "dev": true, - "peer": true - }, "node_modules/lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -9455,13 +9599,6 @@ "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", "peer": true }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "dev": true, - "peer": true - }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -9510,30 +9647,18 @@ } }, "node_modules/luxon": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.3.tgz", - "integrity": "sha512-tFWBiv3h7z+T/tDaoxA8rqTxy1CHV6gHS//QdaH4pulbq/JuBSGgQspQQqcgnwdAx6pNI7cmvz5Sv/addzHmUg==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", + "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", "peer": true, "engines": { "node": ">=12" } }, - "node_modules/macos-release": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.1.tgz", - "integrity": "sha512-DXqXhEM7gW59OjZO8NIjBCz9AQ1BEMrfiOAl4AYByHCtVHRF4KoGNO8mqQeM8lRCtQe/UnJ4imO/d2HdkKsd+A==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/magic-string": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz", - "integrity": "sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==", + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -9573,13 +9698,12 @@ } }, "node_modules/memfs": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.6.0.tgz", - "integrity": "sha512-I6mhA1//KEZfKRQT9LujyW6lRbX7RkC24xKododIDO3AGShcaFAMKElv1yFGWX8fD4UaSiwasr3NeQ5TdtHY1A==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.8.1.tgz", + "integrity": "sha512-7q/AdPzf2WpwPlPL4v1kE2KsJsHl7EF4+hAeVzlyanr2+YnR21NVn9mDqo+7DEaKDRsQy8nvxPlKH4WqMtiO0w==", "dev": true, "dependencies": { - "json-joy": "^9.2.0", - "thingies": "^1.11.1" + "tslib": "^2.0.0" }, "engines": { "node": ">= 4.0.0" @@ -9587,9 +9711,6 @@ "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" } }, "node_modules/mensch": { @@ -9634,6 +9755,18 @@ "node": ">=8.6" } }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/mime": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", @@ -9684,14 +9817,17 @@ "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -9711,502 +9847,395 @@ } }, "node_modules/mjml": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml/-/mjml-4.14.1.tgz", - "integrity": "sha512-f/wnWWIVbeb/ge3ff7c/KYYizI13QbGIp03odwwkCThsJsacw4gpZZAU7V4gXY3HxSXP2/q3jxOfaHVbkfNpOQ==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml/-/mjml-4.15.3.tgz", + "integrity": "sha512-bW2WpJxm6HS+S3Yu6tq1DUPFoTxU9sPviUSmnL7Ua+oVO3WA5ILFWqvujUlz+oeuM+HCwEyMiP5xvKNPENVjYA==", "dependencies": { - "@babel/runtime": "^7.14.6", - "mjml-cli": "4.14.1", - "mjml-core": "4.14.1", - "mjml-migrate": "4.14.1", - "mjml-preset-core": "4.14.1", - "mjml-validator": "4.13.0" + "@babel/runtime": "^7.23.9", + "mjml-cli": "4.15.3", + "mjml-core": "4.15.3", + "mjml-migrate": "4.15.3", + "mjml-preset-core": "4.15.3", + "mjml-validator": "4.15.3" }, "bin": { "mjml": "bin/mjml" } }, "node_modules/mjml-accordion": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-accordion/-/mjml-accordion-4.14.1.tgz", - "integrity": "sha512-dpNXyjnhYwhM75JSjD4wFUa9JgHm86M2pa0CoTzdv1zOQz67ilc4BoK5mc2S0gOjJpjBShM5eOJuCyVIuAPC6w==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-accordion/-/mjml-accordion-4.15.3.tgz", + "integrity": "sha512-LPNVSj1LyUVYT9G1gWwSw3GSuDzDsQCu0tPB2uDsq4VesYNnU6v3iLCQidMiR6azmIt13OEozG700ygAUuA6Ng==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "lodash": "^4.17.21", - "mjml-core": "4.14.1" + "mjml-core": "4.15.3" } }, "node_modules/mjml-body": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-body/-/mjml-body-4.14.1.tgz", - "integrity": "sha512-YpXcK3o2o1U+fhI8f60xahrhXuHmav6BZez9vIN3ZEJOxPFSr+qgr1cT2iyFz50L5+ZsLIVj2ZY+ALQjdsg8ig==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-body/-/mjml-body-4.15.3.tgz", + "integrity": "sha512-7pfUOVPtmb0wC+oUOn4xBsAw4eT5DyD6xqaxj/kssu6RrFXOXgJaVnDPAI9AzIvXJ/5as9QrqRGYAddehwWpHQ==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "lodash": "^4.17.21", - "mjml-core": "4.14.1" + "mjml-core": "4.15.3" } }, "node_modules/mjml-button": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-button/-/mjml-button-4.14.1.tgz", - "integrity": "sha512-V1Tl1vQ3lXYvvqHJHvGcc8URr7V1l/ZOsv7iLV4QRrh7kjKBXaRS7uUJtz6/PzEbNsGQCiNtXrODqcijLWlgaw==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-button/-/mjml-button-4.15.3.tgz", + "integrity": "sha512-79qwn9AgdGjJR1vLnrcm2rq2AsAZkKC5JPwffTMG+Nja6zGYpTDZFZ56ekHWr/r1b5WxkukcPj2PdevUug8c+Q==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "lodash": "^4.17.21", - "mjml-core": "4.14.1" + "mjml-core": "4.15.3" } }, "node_modules/mjml-carousel": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-carousel/-/mjml-carousel-4.14.1.tgz", - "integrity": "sha512-Ku3MUWPk/TwHxVgKEUtzspy/ePaWtN/3z6/qvNik0KIn0ZUIZ4zvR2JtaVL5nd30LHSmUaNj30XMPkCjYiKkFA==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-carousel/-/mjml-carousel-4.15.3.tgz", + "integrity": "sha512-3ju6I4l7uUhPRrJfN3yK9AMsfHvrYbRkcJ1GRphFHzUj37B2J6qJOQUpzA547Y4aeh69TSb7HFVf1t12ejQxVw==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "lodash": "^4.17.21", - "mjml-core": "4.14.1" + "mjml-core": "4.15.3" } }, "node_modules/mjml-cli": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-cli/-/mjml-cli-4.14.1.tgz", - "integrity": "sha512-Gy6MnSygFXs0U1qOXTHqBg2vZX2VL/fAacgQzD4MHq4OuybWaTNSzXRwxBXYCxT3IJB874n2Q0Mxp+Xka+tnZg==", - "dependencies": { - "@babel/runtime": "^7.14.6", - "chokidar": "^3.0.0", - "glob": "^7.1.1", - "html-minifier": "^4.0.0", - "js-beautify": "^1.6.14", - "lodash": "^4.17.21", - "mjml-core": "4.14.1", - "mjml-migrate": "4.14.1", - "mjml-parser-xml": "4.14.1", - "mjml-validator": "4.13.0", - "yargs": "^16.1.0" - }, - "bin": { - "mjml-cli": "bin/mjml" - } - }, - "node_modules/mjml-cli/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/mjml-cli/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mjml-cli/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-cli/-/mjml-cli-4.15.3.tgz", + "integrity": "sha512-+V2TDw3tXUVEptFvLSerz125C2ogYl8klIBRY1m5BHd4JvGVf3yhx8N3PngByCzA6PGcv/eydGQN+wy34SHf0Q==", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/mjml-cli/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mjml-cli/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "engines": { - "node": ">=10" + "@babel/runtime": "^7.23.9", + "chokidar": "^3.0.0", + "glob": "^10.3.10", + "html-minifier": "^4.0.0", + "js-beautify": "^1.6.14", + "lodash": "^4.17.21", + "minimatch": "^9.0.3", + "mjml-core": "4.15.3", + "mjml-migrate": "4.15.3", + "mjml-parser-xml": "4.15.3", + "mjml-validator": "4.15.3", + "yargs": "^17.7.2" + }, + "bin": { + "mjml-cli": "bin/mjml" } }, "node_modules/mjml-column": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-column/-/mjml-column-4.14.1.tgz", - "integrity": "sha512-iixVCIX1YJtpQuwG2WbDr7FqofQrlTtGQ4+YAZXGiLThs0En3xNIJFQX9xJ8sgLEGGltyooHiNICBRlzSp9fDg==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-column/-/mjml-column-4.15.3.tgz", + "integrity": "sha512-hYdEFdJGHPbZJSEysykrevEbB07yhJGSwfDZEYDSbhQQFjV2tXrEgYcFD5EneMaowjb55e3divSJxU4c5q4Qgw==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "lodash": "^4.17.21", - "mjml-core": "4.14.1" + "mjml-core": "4.15.3" } }, "node_modules/mjml-core": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-core/-/mjml-core-4.14.1.tgz", - "integrity": "sha512-di88rSfX+8r4r+cEqlQCO7CRM4mYZrfe2wSCu2je38i+ujjkLpF72cgLnjBlSG5aOUCZgYvlsZ85stqIz9LQfA==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-core/-/mjml-core-4.15.3.tgz", + "integrity": "sha512-Dmwk+2cgSD9L9GmTbEUNd8QxkTZtW9P7FN/ROZW/fGZD6Hq6/4TB0zEspg2Ow9eYjZXO2ofOJ3PaQEEShKV0kQ==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "cheerio": "1.0.0-rc.12", "detect-node": "^2.0.4", "html-minifier": "^4.0.0", "js-beautify": "^1.6.14", - "juice": "^9.0.0", + "juice": "^10.0.0", "lodash": "^4.17.21", - "mjml-migrate": "4.14.1", - "mjml-parser-xml": "4.14.1", - "mjml-validator": "4.13.0" + "mjml-migrate": "4.15.3", + "mjml-parser-xml": "4.15.3", + "mjml-validator": "4.15.3" } }, "node_modules/mjml-divider": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-divider/-/mjml-divider-4.14.1.tgz", - "integrity": "sha512-agqWY0aW2xaMiUOhYKDvcAAfOLalpbbtjKZAl1vWmNkURaoK4L7MgDilKHSJDFUlHGm2ZOArTrq8i6K0iyThBQ==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-divider/-/mjml-divider-4.15.3.tgz", + "integrity": "sha512-vh27LQ9FG/01y0b9ntfqm+GT5AjJnDSDY9hilss2ixIUh0FemvfGRfsGVeV5UBVPBKK7Ffhvfqc7Rciob9Spzw==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "lodash": "^4.17.21", - "mjml-core": "4.14.1" + "mjml-core": "4.15.3" } }, "node_modules/mjml-group": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-group/-/mjml-group-4.14.1.tgz", - "integrity": "sha512-dJt5batgEJ7wxlxzqOfHOI94ABX+8DZBvAlHuddYO4CsLFHYv6XRIArLAMMnAKU76r6p3X8JxYeOjKZXdv49kg==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-group/-/mjml-group-4.15.3.tgz", + "integrity": "sha512-HSu/rKnGZVKFq3ciT46vi1EOy+9mkB0HewO4+P6dP/Y0UerWkN6S3UK11Cxsj0cAp0vFwkPDCdOeEzRdpFEkzA==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "lodash": "^4.17.21", - "mjml-core": "4.14.1" + "mjml-core": "4.15.3" } }, "node_modules/mjml-head": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-head/-/mjml-head-4.14.1.tgz", - "integrity": "sha512-KoCbtSeTAhx05Ugn9TB2UYt5sQinSCb7RGRer5iPQ3CrXj8hT5B5Svn6qvf/GACPkWl4auExHQh+XgLB+r3OEA==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head/-/mjml-head-4.15.3.tgz", + "integrity": "sha512-o3mRuuP/MB5fZycjD3KH/uXsnaPl7Oo8GtdbJTKtH1+O/3pz8GzGMkscTKa97l03DAG2EhGrzzLcU2A6eshwFw==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "lodash": "^4.17.21", - "mjml-core": "4.14.1" + "mjml-core": "4.15.3" } }, "node_modules/mjml-head-attributes": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-head-attributes/-/mjml-head-attributes-4.14.1.tgz", - "integrity": "sha512-XdUNOp2csK28kBDSistInOyzWNwmu5HDNr4y1Z7vSQ1PfkmiuS6jWG7jHUjdoMhs27e6Leuyyc6a8gWSpqSWrg==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-attributes/-/mjml-head-attributes-4.15.3.tgz", + "integrity": "sha512-2ISo0r5ZKwkrvJgDou9xVPxxtXMaETe2AsAA02L89LnbB2KC0N5myNsHV0sEysTw9+CfCmgjAb0GAI5QGpxKkQ==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "lodash": "^4.17.21", - "mjml-core": "4.14.1" + "mjml-core": "4.15.3" } }, "node_modules/mjml-head-breakpoint": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-head-breakpoint/-/mjml-head-breakpoint-4.14.1.tgz", - "integrity": "sha512-Qw9l/W/I5Z9p7I4ShgnEpAL9if4472ejcznbBnp+4Gq+sZoPa7iYoEPsa9UCGutlaCh3N3tIi2qKhl9qD8DFxA==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-breakpoint/-/mjml-head-breakpoint-4.15.3.tgz", + "integrity": "sha512-Eo56FA5C2v6ucmWQL/JBJ2z641pLOom4k0wP6CMZI2utfyiJ+e2Uuinj1KTrgDcEvW4EtU9HrfAqLK9UosLZlg==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "lodash": "^4.17.21", - "mjml-core": "4.14.1" + "mjml-core": "4.15.3" } }, "node_modules/mjml-head-font": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-head-font/-/mjml-head-font-4.14.1.tgz", - "integrity": "sha512-oBYm1gaOdEMjE5BoZouRRD4lCNZ1jcpz92NR/F7xDyMaKCGN6T/+r4S5dq1gOLm9zWqClRHaECdFJNEmrDpZqA==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-font/-/mjml-head-font-4.15.3.tgz", + "integrity": "sha512-CzV2aDPpiNIIgGPHNcBhgyedKY4SX3BJoTwOobSwZVIlEA6TAWB4Z9WwFUmQqZOgo1AkkiTHPZQvGcEhFFXH6g==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "lodash": "^4.17.21", - "mjml-core": "4.14.1" + "mjml-core": "4.15.3" } }, "node_modules/mjml-head-html-attributes": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-head-html-attributes/-/mjml-head-html-attributes-4.14.1.tgz", - "integrity": "sha512-vlJsJc1Sm4Ml2XvLmp01zsdmWmzm6+jNCO7X3eYi9ngEh8LjMCLIQOncnOgjqm9uGpQu2EgUhwvYFZP2luJOVg==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-html-attributes/-/mjml-head-html-attributes-4.15.3.tgz", + "integrity": "sha512-MDNDPMBOgXUZYdxhosyrA2kudiGO8aogT0/cODyi2Ed9o/1S7W+je11JUYskQbncqhWKGxNyaP4VWa+6+vUC/g==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "lodash": "^4.17.21", - "mjml-core": "4.14.1" + "mjml-core": "4.15.3" } }, "node_modules/mjml-head-preview": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-head-preview/-/mjml-head-preview-4.14.1.tgz", - "integrity": "sha512-89gQtt3fhl2dkYpHLF5HDQXz/RLpzecU6wmAIT7Dz6etjLGE1dgq2Ay6Bu/OeHjDcT1gbM131zvBwuXw8OydNw==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-preview/-/mjml-head-preview-4.15.3.tgz", + "integrity": "sha512-J2PxCefUVeFwsAExhrKo4lwxDevc5aKj888HBl/wN4EuWOoOg06iOGCxz4Omd8dqyFsrqvbBuPqRzQ+VycGmaA==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "lodash": "^4.17.21", - "mjml-core": "4.14.1" + "mjml-core": "4.15.3" } }, "node_modules/mjml-head-style": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-head-style/-/mjml-head-style-4.14.1.tgz", - "integrity": "sha512-XryOuf32EDuUCBT2k99C1+H87IOM919oY6IqxKFJCDkmsbywKIum7ibhweJdcxiYGONKTC6xjuibGD3fQTTYNQ==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-style/-/mjml-head-style-4.15.3.tgz", + "integrity": "sha512-9J+JuH+mKrQU65CaJ4KZegACUgNIlYmWQYx3VOBR/tyz+8kDYX7xBhKJCjQ1I4wj2Tvga3bykd89Oc2kFZ5WOw==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "lodash": "^4.17.21", - "mjml-core": "4.14.1" + "mjml-core": "4.15.3" } }, "node_modules/mjml-head-title": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-head-title/-/mjml-head-title-4.14.1.tgz", - "integrity": "sha512-aIfpmlQdf1eJZSSrFodmlC4g5GudBti2eMyG42M7/3NeLM6anEWoe+UkF/6OG4Zy0tCQ40BDJ5iBZlMsjQICzw==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-title/-/mjml-head-title-4.15.3.tgz", + "integrity": "sha512-IM59xRtsxID4DubQ0iLmoCGXguEe+9BFG4z6y2xQDrscIa4QY3KlfqgKGT69ojW+AVbXXJPEVqrAi4/eCsLItQ==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "lodash": "^4.17.21", - "mjml-core": "4.14.1" + "mjml-core": "4.15.3" } }, "node_modules/mjml-hero": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-hero/-/mjml-hero-4.14.1.tgz", - "integrity": "sha512-TQJ3yfjrKYGkdEWjHLHhL99u/meKFYgnfJvlo9xeBvRjSM696jIjdqaPHaunfw4CP6d2OpCIMuacgOsvqQMWOA==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-hero/-/mjml-hero-4.15.3.tgz", + "integrity": "sha512-9cLAPuc69yiuzNrMZIN58j+HMK1UWPaq2i3/Fg2ZpimfcGFKRcPGCbEVh0v+Pb6/J0+kf8yIO0leH20opu3AyQ==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "lodash": "^4.17.21", - "mjml-core": "4.14.1" + "mjml-core": "4.15.3" } }, "node_modules/mjml-image": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-image/-/mjml-image-4.14.1.tgz", - "integrity": "sha512-jfKLPHXuFq83okwlNM1Um/AEWeVDgs2JXIOsWp2TtvXosnRvGGMzA5stKLYdy1x6UfKF4c1ovpMS162aYGp+xQ==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-image/-/mjml-image-4.15.3.tgz", + "integrity": "sha512-g1OhSdofIytE9qaOGdTPmRIp7JsCtgO0zbsn1Fk6wQh2gEL55Z40j/VoghslWAWTgT2OHFdBKnMvWtN6U5+d2Q==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "lodash": "^4.17.21", - "mjml-core": "4.14.1" + "mjml-core": "4.15.3" } }, "node_modules/mjml-migrate": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-migrate/-/mjml-migrate-4.14.1.tgz", - "integrity": "sha512-d+9HKQOhZi3ZFAaFSDdjzJX9eDQGjMf3BArLWNm2okC4ZgfJSpOc77kgCyFV8ugvwc8fFegPnSV60Jl4xtvK2A==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-migrate/-/mjml-migrate-4.15.3.tgz", + "integrity": "sha512-sr/+35RdxZroNQVegjpfRHJ5hda9XCgaS4mK2FGO+Mb1IUevKfeEPII3F/cHDpNwFeYH3kAgyqQ22ClhGLWNBA==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "js-beautify": "^1.6.14", "lodash": "^4.17.21", - "mjml-core": "4.14.1", - "mjml-parser-xml": "4.14.1", - "yargs": "^16.1.0" + "mjml-core": "4.15.3", + "mjml-parser-xml": "4.15.3", + "yargs": "^17.7.2" }, "bin": { "migrate": "lib/cli.js" } }, - "node_modules/mjml-migrate/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/mjml-migrate/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/mjml-migrate/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mjml-migrate/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "engines": { - "node": ">=10" - } - }, "node_modules/mjml-navbar": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-navbar/-/mjml-navbar-4.14.1.tgz", - "integrity": "sha512-rNy1Kw8CR3WQ+M55PFBAUDz2VEOjz+sk06OFnsnmNjoMVCjo1EV7OFLDAkmxAwqkC8h4zQWEOFY0MBqqoAg7+A==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-navbar/-/mjml-navbar-4.15.3.tgz", + "integrity": "sha512-VsKH/Jdlf8Yu3y7GpzQV5n7JMdpqvZvTSpF6UQXL0PWOm7k6+LX+sCZimOfpHJ+wCaaybpxokjWZ71mxOoCWoA==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "lodash": "^4.17.21", - "mjml-core": "4.14.1" + "mjml-core": "4.15.3" } }, "node_modules/mjml-parser-xml": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-parser-xml/-/mjml-parser-xml-4.14.1.tgz", - "integrity": "sha512-9WQVeukbXfq9DUcZ8wOsHC6BTdhaVwTAJDYMIQglXLwKwN7I4pTCguDDHy5d0kbbzK5OCVxCdZe+bfVI6XANOQ==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-parser-xml/-/mjml-parser-xml-4.15.3.tgz", + "integrity": "sha512-Tz0UX8/JVYICLjT+U8J1f/TFxIYVYjzZHeh4/Oyta0pLpRLeZlxEd71f3u3kdnulCKMP4i37pFRDmyLXAlEuLw==", "dependencies": { - "@babel/runtime": "^7.14.6", - "detect-node": "2.0.4", - "htmlparser2": "^8.0.1", + "@babel/runtime": "^7.23.9", + "detect-node": "2.1.0", + "htmlparser2": "^9.1.0", "lodash": "^4.17.15" } }, - "node_modules/mjml-parser-xml/node_modules/detect-node": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", - "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==" + "node_modules/mjml-parser-xml/node_modules/htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } }, "node_modules/mjml-preset-core": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-preset-core/-/mjml-preset-core-4.14.1.tgz", - "integrity": "sha512-uUCqK9Z9d39rwB/+JDV2KWSZGB46W7rPQpc9Xnw1DRP7wD7qAfJwK6AZFCwfTgWdSxw0PwquVNcrUS9yBa9uhw==", - "dependencies": { - "@babel/runtime": "^7.14.6", - "mjml-accordion": "4.14.1", - "mjml-body": "4.14.1", - "mjml-button": "4.14.1", - "mjml-carousel": "4.14.1", - "mjml-column": "4.14.1", - "mjml-divider": "4.14.1", - "mjml-group": "4.14.1", - "mjml-head": "4.14.1", - "mjml-head-attributes": "4.14.1", - "mjml-head-breakpoint": "4.14.1", - "mjml-head-font": "4.14.1", - "mjml-head-html-attributes": "4.14.1", - "mjml-head-preview": "4.14.1", - "mjml-head-style": "4.14.1", - "mjml-head-title": "4.14.1", - "mjml-hero": "4.14.1", - "mjml-image": "4.14.1", - "mjml-navbar": "4.14.1", - "mjml-raw": "4.14.1", - "mjml-section": "4.14.1", - "mjml-social": "4.14.1", - "mjml-spacer": "4.14.1", - "mjml-table": "4.14.1", - "mjml-text": "4.14.1", - "mjml-wrapper": "4.14.1" + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-preset-core/-/mjml-preset-core-4.15.3.tgz", + "integrity": "sha512-1zZS8P4O0KweWUqNS655+oNnVMPQ1Rq1GaZq5S9JfwT1Vh/m516lSmiTW9oko6gGHytt5s6Yj6oOeu5Zm8FoLw==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "mjml-accordion": "4.15.3", + "mjml-body": "4.15.3", + "mjml-button": "4.15.3", + "mjml-carousel": "4.15.3", + "mjml-column": "4.15.3", + "mjml-divider": "4.15.3", + "mjml-group": "4.15.3", + "mjml-head": "4.15.3", + "mjml-head-attributes": "4.15.3", + "mjml-head-breakpoint": "4.15.3", + "mjml-head-font": "4.15.3", + "mjml-head-html-attributes": "4.15.3", + "mjml-head-preview": "4.15.3", + "mjml-head-style": "4.15.3", + "mjml-head-title": "4.15.3", + "mjml-hero": "4.15.3", + "mjml-image": "4.15.3", + "mjml-navbar": "4.15.3", + "mjml-raw": "4.15.3", + "mjml-section": "4.15.3", + "mjml-social": "4.15.3", + "mjml-spacer": "4.15.3", + "mjml-table": "4.15.3", + "mjml-text": "4.15.3", + "mjml-wrapper": "4.15.3" } }, "node_modules/mjml-raw": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-raw/-/mjml-raw-4.14.1.tgz", - "integrity": "sha512-9+4wzoXnCtfV6QPmjfJkZ50hxFB4Z8QZnl2Ac0D1Cn3dUF46UkmO5NLMu7UDIlm5DdFyycZrMOwvZS4wv9ksPw==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-raw/-/mjml-raw-4.15.3.tgz", + "integrity": "sha512-IGyHheOYyRchBLiAEgw3UM11kFNmBSMupu2BDdejC6ZiDhEAdG+tyERlsCwDPYtXanvFpGWULIu3XlsUPc+RZw==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "lodash": "^4.17.21", - "mjml-core": "4.14.1" + "mjml-core": "4.15.3" } }, "node_modules/mjml-section": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-section/-/mjml-section-4.14.1.tgz", - "integrity": "sha512-Ik5pTUhpT3DOfB3hEmAWp8rZ0ilWtIivnL8XdUJRfgYE9D+MCRn+reIO+DAoJHxiQoI6gyeKkIP4B9OrQ7cHQw==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-section/-/mjml-section-4.15.3.tgz", + "integrity": "sha512-JfVPRXH++Hd933gmQfG8JXXCBCR6fIzC3DwiYycvanL/aW1cEQ2EnebUfQkt5QzlYjOkJEH+JpccAsq3ln6FZQ==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "lodash": "^4.17.21", - "mjml-core": "4.14.1" + "mjml-core": "4.15.3" } }, "node_modules/mjml-social": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-social/-/mjml-social-4.14.1.tgz", - "integrity": "sha512-G44aOZXgZHukirjkeQWTTV36UywtE2YvSwWGNfo/8d+k5JdJJhCIrlwaahyKEAyH63G1B0Zt8b2lEWx0jigYUw==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-social/-/mjml-social-4.15.3.tgz", + "integrity": "sha512-7sD5FXrESOxpT9Z4Oh36bS6u/geuUrMP1aCg2sjyAwbPcF1aWa2k9OcatQfpRf6pJEhUZ18y6/WBBXmMVmSzXg==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "lodash": "^4.17.21", - "mjml-core": "4.14.1" + "mjml-core": "4.15.3" } }, "node_modules/mjml-spacer": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-spacer/-/mjml-spacer-4.14.1.tgz", - "integrity": "sha512-5SfQCXTd3JBgRH1pUy6NVZ0lXBiRqFJPVHBdtC3OFvUS3q1w16eaAXlIUWMKTfy8CKhQrCiE6m65kc662ZpYxA==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-spacer/-/mjml-spacer-4.15.3.tgz", + "integrity": "sha512-3B7Qj+17EgDdAtZ3NAdMyOwLTX1jfmJuY7gjyhS2HtcZAmppW+cxqHUBwCKfvSRgTQiccmEvtNxaQK+tfyrZqA==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "lodash": "^4.17.21", - "mjml-core": "4.14.1" + "mjml-core": "4.15.3" } }, "node_modules/mjml-table": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-table/-/mjml-table-4.14.1.tgz", - "integrity": "sha512-aVBdX3WpyKVGh/PZNn2KgRem+PQhWlvnD00DKxDejRBsBSKYSwZ0t3EfFvZOoJ9DzfHsN0dHuwd6Z18Ps44NFQ==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-table/-/mjml-table-4.15.3.tgz", + "integrity": "sha512-FLx7DcRKTdKdcOCbMyBaeudeHaHpwPveRrBm6WyQe3LXx6FfdmOh59i71/16LFQMgBOD3N4/UJkzxLzlTJzMqQ==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "lodash": "^4.17.21", - "mjml-core": "4.14.1" + "mjml-core": "4.15.3" } }, "node_modules/mjml-text": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-text/-/mjml-text-4.14.1.tgz", - "integrity": "sha512-yZuvf5z6qUxEo5CqOhCUltJlR6oySKVcQNHwoV5sneMaKdmBiaU4VDnlYFera9gMD9o3KBHIX6kUg7EHnCwBRQ==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-text/-/mjml-text-4.15.3.tgz", + "integrity": "sha512-+C0hxCmw9kg0XzT6vhE5mFkK6y225nC8UEQcN94K0fBCjPKkM+HqZMwGX205fzdGRi+Bxa55b/VhrIVwdv+8vw==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "lodash": "^4.17.21", - "mjml-core": "4.14.1" + "mjml-core": "4.15.3" } }, "node_modules/mjml-validator": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-validator/-/mjml-validator-4.13.0.tgz", - "integrity": "sha512-uURYfyQYtHJ6Qz/1A7/+E9ezfcoISoLZhYK3olsxKRViwaA2Mm8gy/J3yggZXnsUXWUns7Qymycm5LglLEIiQg==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-validator/-/mjml-validator-4.15.3.tgz", + "integrity": "sha512-Xb72KdqRwjv/qM2rJpV22syyP2N3cRQ9VVDrN6u2FSzLq02buFNxmSPJ7CKhat3PrUNdVHU75KZwOf/tz4UEhA==", "dependencies": { - "@babel/runtime": "^7.14.6" + "@babel/runtime": "^7.23.9" } }, "node_modules/mjml-wrapper": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mjml-wrapper/-/mjml-wrapper-4.14.1.tgz", - "integrity": "sha512-aA5Xlq6d0hZ5LY+RvSaBqmVcLkvPvdhyAv3vQf3G41Gfhel4oIPmkLnVpHselWhV14A0KwIOIAKVxHtSAxyOTQ==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-wrapper/-/mjml-wrapper-4.15.3.tgz", + "integrity": "sha512-ditsCijeHJrmBmObtJmQ18ddLxv5oPyMTdPU8Di8APOnD2zPk7Z4UAuJSl7HXB45oFiivr3MJf4koFzMUSZ6Gg==", "dependencies": { - "@babel/runtime": "^7.14.6", + "@babel/runtime": "^7.23.9", "lodash": "^4.17.21", - "mjml-core": "4.14.1", - "mjml-section": "4.14.1" + "mjml-core": "4.15.3", + "mjml-section": "4.15.3" } }, "node_modules/mkdirp": { @@ -10224,9 +10253,9 @@ } }, "node_modules/mnemonist": { - "version": "0.39.5", - "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.5.tgz", - "integrity": "sha512-FPUtkhtJ0efmEFGpU14x7jGbTB+s18LrzRL2KgoWz9YvcY3cPomz8tih01GbHwnGk/OmkOKfqd/RAQoc8Lm7DQ==", + "version": "0.39.6", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.6.tgz", + "integrity": "sha512-A/0v5Z59y63US00cRSLiloEIw3t5G+MiKz4BhX21FI+YBJXBOGW0ohFxTxO08dsOYlzxo87T7vGfZKYp2bcAWA==", "dependencies": { "obliterator": "^2.0.1" } @@ -10237,9 +10266,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/msgpackr": { - "version": "1.9.9", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.9.9.tgz", - "integrity": "sha512-sbn6mioS2w0lq1O6PpGtsv6Gy8roWM+o3o4Sqjd6DudrL/nOugY+KyJUimoWzHnf9OkO0T6broHFnYE/R05t9A==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.10.1.tgz", + "integrity": "sha512-r5VRLv9qouXuLiIBrLpl2d5ZvPt8svdQTl5/vMvE4nzDMyEX4sgW5yWhuBBj5UmgwOTWj8CIdSXn5sAfsHAWIQ==", "peer": true, "optionalDependencies": { "msgpackr-extract": "^3.0.2" @@ -10295,14 +10324,14 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "node_modules/nest-keycloak-connect": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/nest-keycloak-connect/-/nest-keycloak-connect-1.9.2.tgz", - "integrity": "sha512-O3dq2PL2AVk9zohMgAFL8IfFzpS6wvr24m30rit6eg34nmAI/zJIkfvVjmd/KSZ1Z685ADlI3GCOeHe7CArizA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/nest-keycloak-connect/-/nest-keycloak-connect-1.10.0.tgz", + "integrity": "sha512-OhuYZEKud/YEhmyiZhPn+8Cse9NhWZkCT0elCqn3xqVTdDjqVFiRPcVUHrD6/IkuAXpe8M1AzbQFc1KooX6Xpg==", "peerDependencies": { "@nestjs/common": ">=6.0.0 <11.0.0", "@nestjs/core": ">=6.0.0 <11.0.0", "@nestjs/graphql": ">=6", - "keycloak-connect": ">=10.0.0 <22.0.0" + "keycloak-connect": ">=10.0.0 <25.0.0" }, "peerDependenciesMeta": { "@nestjs/graphql": { @@ -10311,11 +10340,11 @@ } }, "node_modules/nestjs-cls": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/nestjs-cls/-/nestjs-cls-3.6.0.tgz", - "integrity": "sha512-hAu8Pyhl0okB7LgQFoxP8kq26izBh0UCnDLfE2Ze9eJAz2A8XdFkBNIVca868T1Yf8bsLzvfsKPTxCQZ0hTMDQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/nestjs-cls/-/nestjs-cls-4.3.0.tgz", + "integrity": "sha512-MVTun6tqCZih8AJXRj8uBuuFyJhQrIA9m9fStiQjbBXUkE3BrlMRvmLzyw8UcneB3xtFFTfwkAh5PYKRulyaOg==", "engines": { - "node": ">=12.17.0" + "node": ">=16" }, "peerDependencies": { "@nestjs/common": "> 7.0.0 < 11", @@ -10325,35 +10354,36 @@ } }, "node_modules/nestjs-grpc-reflection": { - "version": "0.0.18", - "resolved": "https://registry.npmjs.org/nestjs-grpc-reflection/-/nestjs-grpc-reflection-0.0.18.tgz", - "integrity": "sha512-l3JsOqqHPSfJIEjW8s8wNVnsk9apPSH3al0kHQFpHJysOJdBPzyoXjbLNAr8aYHIabG3IPUl3Q2emKRpj5aTfA==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/nestjs-grpc-reflection/-/nestjs-grpc-reflection-0.2.2.tgz", + "integrity": "sha512-aXmBXJMWdyK2lDR5T5Up83aCnbQypxF53EsiixMTnPZI77Dhh5jmnGJK9qCjMy8K92LAGcOPNoGowv73p5w/AQ==", "dependencies": { - "google-protobuf": "^3.21.0", - "protobufjs": "^7.0.0", + "@grpc/reflection": "^1.0.0", + "google-protobuf": "^3.21.2", + "protobufjs": "^7.2.5", "reflect-metadata": "^0.1.13", - "rxjs": "^7.5.6", "ts-case-convert": "^2.0.2" }, "peerDependencies": { "@grpc/grpc-js": ">=1.5.4", "@grpc/proto-loader": ">=0.6.9", - "@nestjs/common": ">=8.0.0", - "@nestjs/core": ">=8.0.0", - "@nestjs/microservices": ">=8.0.0" + "@nestjs/common": ">=8.4.0", + "@nestjs/core": ">=8.4.0", + "@nestjs/microservices": ">=8.4.0", + "rxjs": ">=7.0.0" } }, "node_modules/nestjs-pino": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/nestjs-pino/-/nestjs-pino-3.5.0.tgz", - "integrity": "sha512-IWJ3dzLVjg5istcd3Cz3rVO+gmvabfVAT1YmQgzL1HnC2hkc0H6qA6k6SZ7OIwQfewuRejYfPu3TlkxwRrqxHQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nestjs-pino/-/nestjs-pino-4.0.0.tgz", + "integrity": "sha512-XhCg/R+l3w0BFP6MHyR6lU/BHVEV0tV9z24G0vuA9FD3sv+TQNvnO9uVsF1l/oVspgGfQ9Qulmb2UbsfYlI0+g==", "hasInstallScript": true, "engines": { "node": ">= 14" }, "peerDependencies": { "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", - "pino-http": "^6.4.0 || ^7.0.0 || ^8.0.0" + "pino-http": "^6.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" } }, "node_modules/nestjs-spelunker": { @@ -10369,36 +10399,42 @@ "reflect-metadata": "^0.1.0" } }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "optional": true, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/nise": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.5.tgz", - "integrity": "sha512-VJuPIfUFaXNRzETTQEEItTOP8Y171ijr+JLq42wHes3DiryR8vT+1TXQW/Rx8JNUhyYYWyIvjXTU6dOhJcs9Nw==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", "dev": true, "dependencies": { - "@sinonjs/commons": "^2.0.0", - "@sinonjs/fake-timers": "^10.0.2", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "path-to-regexp": "^1.7.0" + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" } }, - "node_modules/nise/node_modules/@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "node_modules/nise/node_modules/@sinonjs/fake-timers": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", "dev": true, "dependencies": { - "type-detect": "4.0.8" + "@sinonjs/commons": "^3.0.0" } }, "node_modules/nise/node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, - "dependencies": { - "isarray": "0.0.1" - } + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", + "dev": true }, "node_modules/no-case": { "version": "2.3.2", @@ -10484,23 +10520,23 @@ } }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, "node_modules/nopt": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", - "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz", + "integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==", "dependencies": { - "abbrev": "^1.0.0" + "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/normalize-path": { @@ -10568,6 +10604,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "devOptional": true, "dependencies": { "wrappy": "1" } @@ -10587,29 +10624,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/open": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", - "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", - "dev": true, - "dependencies": { - "default-browser": "^4.0.0", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/openapi-types": { - "version": "12.1.3", - "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", - "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==" - }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -10650,22 +10664,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/os-name": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/os-name/-/os-name-4.0.1.tgz", - "integrity": "sha512-xl9MAoU97MH1Xt5K9ERft2YfCAoaO6msy1OBA0ozxEC0x0TmIoE6K3QvgJMMZA9yKGLmHXNY/YZoDbiGDj4zYw==", - "dev": true, - "dependencies": { - "macos-release": "^2.5.0", - "windows-release": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -10679,6 +10677,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -10713,10 +10712,37 @@ "node": ">=6" } }, - "node_modules/packet-reader": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", - "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + "node_modules/pac-proxy-agent": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.1.tgz", + "integrity": "sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A==", + "optional": true, + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "pac-resolver": "^7.0.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "optional": true, + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } }, "node_modules/pako": { "version": "2.1.0", @@ -10797,6 +10823,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -10816,11 +10843,11 @@ "dev": true }, "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", + "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { @@ -10831,9 +10858,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", - "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", "engines": { "node": "14 || >=16.14" } @@ -10859,15 +10886,13 @@ "optional": true }, "node_modules/pg": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.3.tgz", - "integrity": "sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==", - "dependencies": { - "buffer-writer": "2.0.0", - "packet-reader": "1.0.0", - "pg-connection-string": "^2.6.2", - "pg-pool": "^3.6.1", - "pg-protocol": "^1.6.0", + "version": "8.11.5", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.5.tgz", + "integrity": "sha512-jqgNHSKL5cbDjFlHyYsCXmQDrfIX/3RsNwYqpd4N0Kt8niLuNoRNH+aazv6cOd43gPh9Y4DjQCtb+X0MH0Hvnw==", + "dependencies": { + "pg-connection-string": "^2.6.4", + "pg-pool": "^3.6.2", + "pg-protocol": "^1.6.1", "pg-types": "^2.1.0", "pgpass": "1.x" }, @@ -10893,9 +10918,9 @@ "optional": true }, "node_modules/pg-connection-string": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz", - "integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==" + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.4.tgz", + "integrity": "sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==" }, "node_modules/pg-int8": { "version": "1.0.1", @@ -10906,17 +10931,17 @@ } }, "node_modules/pg-pool": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.1.tgz", - "integrity": "sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.2.tgz", + "integrity": "sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==", "peerDependencies": { "pg": ">=8.0" } }, "node_modules/pg-protocol": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz", - "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==" + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.1.tgz", + "integrity": "sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==" }, "node_modules/pg-types": { "version": "2.2.0", @@ -10948,27 +10973,28 @@ "dev": true }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz", + "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==", + "dev": true, "engines": { - "node": ">=8.6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/pino": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/pino/-/pino-8.16.1.tgz", - "integrity": "sha512-3bKsVhBmgPjGV9pyn4fO/8RtoVDR8ssW1ev819FsRXlRNgW8gR/9Kx+gCK4UPWd4JjrRDLWpzd/pb1AyWm3MGA==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.19.0.tgz", + "integrity": "sha512-oswmokxkav9bADfJ2ifrvfHUwad6MLp73Uat0IkQWY3iAw5xTRoznXbXksZs8oaOUMpmhVWD+PZogNzllWpJaA==", "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "v1.1.0", "pino-std-serializers": "^6.0.0", - "process-warning": "^2.0.0", + "process-warning": "^3.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", @@ -10989,9 +11015,9 @@ } }, "node_modules/pino-abstract-transport/node_modules/readable-stream": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz", - "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", @@ -11004,28 +11030,28 @@ } }, "node_modules/pino-http": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/pino-http/-/pino-http-8.5.1.tgz", - "integrity": "sha512-T/3d9YHKBYpv/QHjNy73P5BNYYkRrC2/D6CxKMecG4fKFLN+B2iC6LsKYzGRTRV+Ld3fjxFC1ca4TUGbPdzk+Q==", + "version": "8.6.1", + "resolved": "https://registry.npmjs.org/pino-http/-/pino-http-8.6.1.tgz", + "integrity": "sha512-J0hiJgUExtBXP2BjrK4VB305tHXS31sCmWJ9XJo2wPkLHa1NFPuW4V9wjG27PAc2fmBCigiNhQKpvrx+kntBPA==", "peer": true, "dependencies": { "get-caller-file": "^2.0.5", - "pino": "^8.0.0", - "pino-std-serializers": "^6.0.0", - "process-warning": "^2.0.0" + "pino": "^8.17.1", + "pino-std-serializers": "^6.2.2", + "process-warning": "^3.0.0" } }, "node_modules/pino-pretty": { - "version": "10.2.3", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-10.2.3.tgz", - "integrity": "sha512-4jfIUc8TC1GPUfDyMSlW1STeORqkoxec71yhxIpLDQapUu8WOuoz2TTCoidrIssyz78LZC69whBMPIKCMbi3cw==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-11.0.0.tgz", + "integrity": "sha512-YFJZqw59mHIY72wBnBs7XhLGG6qpJMa4pEQTRgEPEbjIYbng2LXEZZF1DoyDg9CfejEy8uZCyzpcBXXG0oOCwQ==", "dev": true, "dependencies": { "colorette": "^2.0.7", "dateformat": "^4.6.3", "fast-copy": "^3.0.0", "fast-safe-stringify": "^2.1.1", - "help-me": "^4.0.1", + "help-me": "^5.0.0", "joycon": "^3.1.1", "minimist": "^1.2.6", "on-exit-leak-free": "^2.1.0", @@ -11041,9 +11067,9 @@ } }, "node_modules/pino-pretty/node_modules/readable-stream": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz", - "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "dependencies": { "abort-controller": "^3.0.0", @@ -11188,9 +11214,9 @@ } }, "node_modules/prettier": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", - "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -11249,9 +11275,9 @@ } }, "node_modules/process-warning": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.3.0.tgz", - "integrity": "sha512-N6mp1+2jpQr3oCFMz6SeHRGbv6Slb20bRhj4v3xR99HqNToAcOe1MFOp4tytyzOfJn+QtN8Rf7U/h2KAn4kC6g==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==" }, "node_modules/prompts": { "version": "2.4.2", @@ -11272,9 +11298,9 @@ "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" }, "node_modules/protobufjs": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.5.tgz", - "integrity": "sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==", + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.6.tgz", + "integrity": "sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw==", "hasInstallScript": true, "dependencies": { "@protobufjs/aspromise": "^1.1.2", @@ -11306,6 +11332,34 @@ "node": ">= 0.10" } }, + "node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "optional": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "optional": true, + "engines": { + "node": ">=12" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -11330,9 +11384,9 @@ } }, "node_modules/pure-rand": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", - "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", "dev": true, "funding": [ { @@ -11346,12 +11400,12 @@ ] }, "node_modules/qs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.0.tgz", + "integrity": "sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg==", "dev": true, "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -11385,21 +11439,6 @@ "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" }, - "node_modules/quill-delta": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz", - "integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==", - "dev": true, - "peer": true, - "dependencies": { - "fast-diff": "^1.3.0", - "lodash.clonedeep": "^4.5.0", - "lodash.isequal": "^4.5.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -11440,6 +11479,17 @@ "node": ">=8.10.0" } }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/real-require": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", @@ -11461,15 +11511,15 @@ } }, "node_modules/redis": { - "version": "4.6.10", - "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.10.tgz", - "integrity": "sha512-mmbyhuKgDiJ5TWUhiKhBssz+mjsuSI/lSZNPI9QvZOYzWvYGejtb+W3RlDDf8LD6Bdl5/mZeG8O1feUGhXTxEg==", + "version": "4.6.13", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.13.tgz", + "integrity": "sha512-MHgkS4B+sPjCXpf+HfdetBwbRz6vCtsceTmw1pHNYJAsYxrfpOP6dz+piJWGos8wqG7qb3vj/Rrc5qOlmInUuA==", "dependencies": { "@redis/bloom": "1.2.0", - "@redis/client": "1.5.11", - "@redis/graph": "1.1.0", + "@redis/client": "1.5.14", + "@redis/graph": "1.1.1", "@redis/json": "1.0.6", - "@redis/search": "1.1.5", + "@redis/search": "1.1.6", "@redis/time-series": "1.0.5" } }, @@ -11495,14 +11545,14 @@ } }, "node_modules/reflect-metadata": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", + "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==" }, "node_modules/regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/relateurl": { "version": "0.2.7", @@ -11623,84 +11673,26 @@ "node": ">=0.10.0" } }, - "node_modules/rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" - }, - "node_modules/rimraf": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", - "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==", - "dependencies": { - "glob": "^10.3.7" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-applescript": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", - "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", - "dev": true, + "node_modules/rfdc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", + "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==" + }, + "node_modules/rimraf": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", + "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==", "dependencies": { - "execa": "^5.0.0" + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" }, "engines": { - "node": ">=12" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/run-async": { @@ -11838,9 +11830,9 @@ "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -11868,9 +11860,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "dependencies": { "randombytes": "^2.1.0" @@ -11882,15 +11874,17 @@ "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==" }, "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dev": true, "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -11949,6 +11943,16 @@ "node": ">=4" } }, + "node_modules/shelljs/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/shelljs/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -11969,15 +11973,31 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/shelljs/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dev": true, "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -11990,17 +12010,16 @@ "dev": true }, "node_modules/sinon": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-14.0.2.tgz", - "integrity": "sha512-PDpV0ZI3ZCS3pEqx0vpNp6kzPhHrLx72wA0G+ZLaaJjLIYeE0n8INlgaohKuGy7hP0as5tbUd23QWu5U233t+w==", - "deprecated": "16.1.1", + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-16.1.3.tgz", + "integrity": "sha512-mjnWWeyxcAf9nC0bXcPmiDut+oE8HYridTNzBbF98AYVLmWwGRp2ISEpyhYflG1ifILT+eNn3BmKUJPxjXUPlA==", "dev": true, "dependencies": { - "@sinonjs/commons": "^2.0.0", - "@sinonjs/fake-timers": "^9.1.2", - "@sinonjs/samsam": "^7.0.1", - "diff": "^5.0.0", - "nise": "^5.1.2", + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^10.3.0", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.4", "supports-color": "^7.2.0" }, "funding": { @@ -12008,33 +12027,6 @@ "url": "https://opencollective.com/sinon" } }, - "node_modules/sinon/node_modules/@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/sinon/node_modules/@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/sinon/node_modules/@sinonjs/fake-timers/node_modules/@sinonjs/commons": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", - "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -12058,10 +12050,48 @@ "node": "*" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "optional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.1.tgz", + "integrity": "sha512-B6w7tkwNid7ToxjZ08rQMT8M9BJAf8DKx8Ft4NivzH0zBUfd6jldGcisJn/RLgxcX3FPNDdNQCUEMMT79b+oCQ==", + "optional": true, + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz", + "integrity": "sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==", + "optional": true, + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/sonic-boom": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.7.0.tgz", - "integrity": "sha512-IudtNvSqA/ObjN97tfgNmOKyDOs4dNcg4cUUsHDebqsgb8wGBBwb31LIgShNO8fye0dFI52X1+tFoKKI6Rq1Gg==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", + "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==", "dependencies": { "atomic-sleep": "^1.0.0" } @@ -12101,10 +12131,10 @@ } }, "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "optional": true }, "node_modules/stack-utils": { "version": "2.0.6", @@ -12289,13 +12319,13 @@ } }, "node_modules/supertest": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.3.tgz", - "integrity": "sha512-EMCG6G8gDu5qEqRQ3JjjPs6+FYT1a7Hv5ApHvtSghmOFJYtsU5S+pSb6Y2EUeCEY3CmEL3mmQ8YWlPOzQomabA==", + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.4.tgz", + "integrity": "sha512-erY3HFDG0dPnhw4U+udPfrzXa4xhSG+n4rxfRuZWCUvjFWwKl+OxWf/7zk50s84/fAAs7vf5QAb9uRa0cCykxw==", "dev": true, "dependencies": { "methods": "^1.1.2", - "superagent": "^8.0.5" + "superagent": "^8.1.2" }, "engines": { "node": ">=6.4.0" @@ -12325,9 +12355,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.9.0.tgz", - "integrity": "sha512-NUHSYoe5XRTk/Are8jPJ6phzBh3l9l33nEyXosM17QInoV95/jng8+PuSGtbD407QoPf93MH3Bkh773OgesJpA==" + "version": "5.11.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.11.2.tgz", + "integrity": "sha512-jQG0cRgJNMZ7aCoiFofnoojeSaa/+KgWaDlfgs8QN+BXoGMpxeMVY5OEnjq4OlNvF3yjftO8c9GRAgcHlO+u7A==" }, "node_modules/symbol-observable": { "version": "4.0.0", @@ -12339,13 +12369,13 @@ } }, "node_modules/synckit": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", - "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", "dev": true, "dependencies": { - "@pkgr/utils": "^2.3.1", - "tslib": "^2.5.0" + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -12391,9 +12421,9 @@ } }, "node_modules/terser": { - "version": "5.24.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.24.0.tgz", - "integrity": "sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==", + "version": "5.30.3", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.3.tgz", + "integrity": "sha512-STdUgOUx8rLbMGO9IOwHLpCqolkDITFFQSMYYwKE1N2lY6MVSaeoi10z/EhWxRc6ybqoVmKSkhKYH/XUpl7vSA==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -12409,16 +12439,16 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", - "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", + "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", "serialize-javascript": "^6.0.1", - "terser": "^5.16.8" + "terser": "^5.26.0" }, "engines": { "node": ">= 10.13.0" @@ -12491,6 +12521,16 @@ "node": ">=8" } }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/test-exclude/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -12511,10 +12551,17 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/text-decoding": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", - "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==" + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } }, "node_modules/text-table": { "version": "0.2.0", @@ -12541,18 +12588,6 @@ "node": ">=0.8" } }, - "node_modules/thingies": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.12.0.tgz", - "integrity": "sha512-AiGqfYC1jLmJagbzQGuoZRM48JPsr9yB734a7K6wzr34NMhjUPrWSQrkF7ZBybf3yCerCL2Gcr02kMv4NmaZfA==", - "dev": true, - "engines": { - "node": ">=10.18" - }, - "peerDependencies": { - "tslib": "^2" - } - }, "node_modules/thread-stream": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.4.1.tgz", @@ -12567,18 +12602,6 @@ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, - "node_modules/titleize": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", - "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -12618,9 +12641,9 @@ } }, "node_modules/toad-cache": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.3.0.tgz", - "integrity": "sha512-3oDzcogWGHZdkwrHyvJVpPjA7oNzY6ENOV3PsWJY9XYPZ6INo94Yd47s5may1U+nleBPwDhrRiTPMIvKaa3MQg==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", + "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", "engines": { "node": ">=12" } @@ -12648,26 +12671,26 @@ } }, "node_modules/ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", "dev": true, "engines": { - "node": ">=16.13.0" + "node": ">=16" }, "peerDependencies": { "typescript": ">=4.2.0" } }, "node_modules/ts-case-convert": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ts-case-convert/-/ts-case-convert-2.0.2.tgz", - "integrity": "sha512-vdKfx1VAdpvEBOBv5OpVu5ZFqRg9HdTI4sYt6qqMeICBeNyXvitrarCnFWNDAki51IKwCyx+ZssY46Q9jH5otA==" + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/ts-case-convert/-/ts-case-convert-2.0.7.tgz", + "integrity": "sha512-Kqj8wrkuduWsKUOUNRczrkdHCDt4ZNNd6HKjVw42EnMIGHQUABS4pqfy0acETVLwUTppc1fzo/yi11+uMTaqzw==" }, "node_modules/ts-jest": { - "version": "29.1.1", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", - "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", + "version": "29.1.2", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", + "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==", "dev": true, "dependencies": { "bs-logger": "0.x", @@ -12683,7 +12706,7 @@ "ts-jest": "cli.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^16.10.0 || ^18.0.0 || >=20.0.0" }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", @@ -12708,9 +12731,9 @@ } }, "node_modules/ts-loader": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.0.tgz", - "integrity": "sha512-LLlB/pkB4q9mW2yLdFMnK3dEHbrBjeZTYguaaIfusyojBgAGf5kF+O6KcWqiGzWqHk0LBsoolrp4VftEURhybg==", + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", + "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", "dev": true, "dependencies": { "chalk": "^4.1.0", @@ -12728,9 +12751,9 @@ } }, "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "devOptional": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", @@ -12770,12 +12793,6 @@ } } }, - "node_modules/ts-node/node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "devOptional": true - }, "node_modules/ts-node/node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -12786,21 +12803,21 @@ } }, "node_modules/ts-poet": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/ts-poet/-/ts-poet-6.6.0.tgz", - "integrity": "sha512-4vEH/wkhcjRPFOdBwIh9ItO6jOoumVLRF4aABDX5JSNEubSqwOulihxQPqai+OkuygJm3WYMInxXQX4QwVNMuw==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/ts-poet/-/ts-poet-6.7.0.tgz", + "integrity": "sha512-A0wvFtpkTCWPw7ftTIwbEH+L+7ul4CU0x3jXKQ+kCnmEQIAOwhpUaBmcAYKxZCxHae9/MUl4LbyTqw25BpzW5Q==", "dependencies": { - "dprint-node": "^1.0.7" + "dprint-node": "^1.0.8" } }, "node_modules/ts-proto": { - "version": "1.163.0", - "resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-1.163.0.tgz", - "integrity": "sha512-jpC4oA86NkN015R2YHGj9bhOJEYOuop4JddmADixRxBcBBcfCgkh7mhxA10BWrmk3/oyczI3T//UNK4x3P9/Sw==", + "version": "1.171.0", + "resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-1.171.0.tgz", + "integrity": "sha512-NMwiqCixlk6MZ3TAIpVQUuWwUktCXhoP+07V7D7Eo2IlEwulZucOGrU82toHhPL/QTd+oMBM2RDeQk9qs8+ZEQ==", "dependencies": { "case-anything": "^2.1.13", "protobufjs": "^7.2.4", - "ts-poet": "^6.5.0", + "ts-poet": "^6.7.0", "ts-proto-descriptors": "1.15.0" }, "bin": { @@ -13015,58 +13032,15 @@ "typeorm": "^0.2.0 || ^0.3.0" } }, - "node_modules/typeorm/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/typeorm/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/typeorm/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/typeorm/node_modules/reflect-metadata": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", - "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==" + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==" }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", + "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", "devOptional": true, "bin": { "tsc": "bin/tsc", @@ -13104,9 +13078,9 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/unionfs": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/unionfs/-/unionfs-4.5.1.tgz", - "integrity": "sha512-hn8pzkh0/80mpsIT/YBJKa4+BF/9pNh0IgysBi0CjL95Uok8Hus69TNfgeJckoUNwfTpBq26+F7edO1oBINaIw==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/unionfs/-/unionfs-4.5.4.tgz", + "integrity": "sha512-qI3RvJwwdFcWUdZz1dWgAyLSfGlY2fS2pstvwkZBUTnkxjcnIvzriBLtqJTKz9FtArAvJeiVCqHlxhOw8Syfyw==", "dev": true, "dependencies": { "fs-monkey": "^1.0.0" @@ -13116,20 +13090,11 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 10.0.0" } }, - "node_modules/untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", @@ -13198,9 +13163,9 @@ "devOptional": true }, "node_modules/v8-to-istanbul": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz", - "integrity": "sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", + "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", @@ -13237,9 +13202,9 @@ } }, "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", + "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", "dev": true, "dependencies": { "glob-to-regexp": "^0.4.1", @@ -13381,34 +13346,35 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "node_modules/webpack": { - "version": "5.89.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", - "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", + "version": "5.91.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", + "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==", "dev": true, + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", + "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.15.0", + "enhanced-resolve": "^5.16.0", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", + "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", - "watchpack": "^2.4.0", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "bin": { @@ -13450,6 +13416,7 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, + "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -13463,6 +13430,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, + "peer": true, "engines": { "node": ">=4.0" } @@ -13490,68 +13458,6 @@ "node": ">= 8" } }, - "node_modules/windows-release": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-4.0.0.tgz", - "integrity": "sha512-OxmV4wzDKB1x7AZaZgXMVsdJ1qER1ed83ZrTYd5Bwq2HfJVg3DJS8nqlAG4sMoJ7mu8cuRmLEYyU13BKwctRAg==", - "dev": true, - "dependencies": { - "execa": "^4.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/windows-release/node_modules/execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/windows-release/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/windows-release/node_modules/human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true, - "engines": { - "node": ">=8.12.0" - } - }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -13591,7 +13497,8 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "devOptional": true }, "node_modules/write-file-atomic": { "version": "4.0.2", @@ -13628,14 +13535,6 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, - "node_modules/yaml": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.3.tgz", - "integrity": "sha512-zw0VAJxgeZ6+++/su5AFoqBbZbrEakwu+X0M5HmcwUiBL7AzcuPKjj5we4xfQLp78LkEMpD0cOnUhmgOVy3KdQ==", - "engines": { - "node": ">= 14" - } - }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -13684,6 +13583,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, "engines": { "node": ">=10" }, diff --git a/services/package.json b/services/package.json index f3c4826831..df7d546f2e 100644 --- a/services/package.json +++ b/services/package.json @@ -28,49 +28,49 @@ "alcs:migration:run": "npm run alcs:typeorm migration:run" }, "dependencies": { - "@aws-sdk/client-s3": "^3.441.0", - "@aws-sdk/s3-request-presigner": "^3.441.0", - "@fastify/cors": "^8.4.1", + "@aws-sdk/client-s3": "^3.540.0", + "@aws-sdk/s3-request-presigner": "^3.540.0", + "@fastify/cors": "^9.0.1", "@fastify/helmet": "^11.1.1", - "@fastify/multipart": "^8.0.0", - "@fastify/static": "^6.12.0", - "@grpc/grpc-js": "^1.8.7", - "@grpc/proto-loader": "^0.7.4", - "@nestjs/axios": "^3.0.1", - "@nestjs/bullmq": "^10.0.1", - "@nestjs/common": "^10.2.8", - "@nestjs/config": "^3.1.1", - "@nestjs/core": "^10.2.8", - "@nestjs/microservices": "^10.2.8", - "@nestjs/platform-fastify": "^10.2.8", - "@nestjs/swagger": "^7.1.14", + "@fastify/multipart": "^8.2.0", + "@fastify/static": "^7.0.3", + "@grpc/grpc-js": "^1.10.6", + "@grpc/proto-loader": "^0.7.12", + "@nestjs/axios": "^3.0.2", + "@nestjs/bullmq": "^10.1.1", + "@nestjs/common": "^10.3.7", + "@nestjs/config": "^3.2.1", + "@nestjs/core": "^10.3.7", + "@nestjs/microservices": "^10.3.7", + "@nestjs/platform-fastify": "^10.3.7", + "@nestjs/swagger": "^7.3.1", "@nestjs/typeorm": "^10.0.2", - "@types/clamscan": "^2.0.6", - "automapper-classes": "^8.7.11", - "automapper-core": "^8.7.11", - "automapper-nestjs": "^8.7.11", - "clamscan": "^2.1.2", + "@types/clamscan": "^2.0.8", + "automapper-classes": "^8.7.12", + "automapper-core": "^8.7.12", + "automapper-nestjs": "^8.7.12", + "clamscan": "^2.2.1", "class-transformer": "^0.5.1", - "class-validator": "^0.14.0", - "config": "^3.3.9", + "class-validator": "^0.14.1", + "config": "^3.3.11", "csv-parser": "^3.0.0", "dayjs": "^1.11.10", "handlebars": "^4.7.8", - "keycloak-connect": "^21.1.2", - "mjml": "^4.14.1", - "nest-keycloak-connect": "^1.9.2", - "nestjs-cls": "^3.6.0", - "nestjs-grpc-reflection": "^0.0.18", - "nestjs-pino": "^3.5.0", + "keycloak-connect": "^24.0.2", + "mjml": "^4.15.3", + "nest-keycloak-connect": "^1.10.0", + "nestjs-cls": "^4.3.0", + "nestjs-grpc-reflection": "^0.2.2", + "nestjs-pino": "^4.0.0", "nestjs-spelunker": "^1.3.0", "node-jose": "^2.2.0", - "pg": "^8.11.3", - "redis": "^4.6.10", - "reflect-metadata": "^0.1.13", + "pg": "^8.11.5", + "redis": "^4.6.13", + "reflect-metadata": "^0.1.14", "rimraf": "^5.0.5", "rxjs": "^7.8.1", "source-map-support": "^0.5.21", - "ts-proto": "^1.163.0", + "ts-proto": "^1.171.0", "ts-protoc-gen": "^0.15.0", "typeorm": "^0.3.20", "typeorm-naming-strategies": "^4.1.0", @@ -78,34 +78,34 @@ }, "devDependencies": { "@golevelup/nestjs-testing": "^0.1.2", - "@nestjs/cli": "^10.2.1", - "@nestjs/schematics": "^10.0.3", - "@nestjs/testing": "^10.2.8", - "@types/config": "^3.3.2", - "@types/express": "^4.17.20", - "@types/jest": "^29.5.7", - "@types/node": "^20.8.10", - "@types/node-jose": "^1.1.12", - "@types/source-map-support": "^0.5.9", - "@types/supertest": "^2.0.15", - "@types/uuid": "^9.0.6", - "@typescript-eslint/eslint-plugin": "^6.9.1", - "@typescript-eslint/parser": "^6.9.1", - "aws-sdk-client-mock": "^2.0.1", - "eslint": "^8.52.0", - "eslint-config-prettier": "^9.0.0", - "eslint-plugin-prettier": "^5.0.1", + "@nestjs/cli": "^10.3.2", + "@nestjs/schematics": "^10.1.1", + "@nestjs/testing": "^10.3.7", + "@types/config": "^3.3.4", + "@types/express": "^4.17.21", + "@types/jest": "^29.5.12", + "@types/node": "^20.12.4", + "@types/node-jose": "^1.1.13", + "@types/source-map-support": "^0.5.10", + "@types/supertest": "^6.0.2", + "@types/uuid": "^9.0.8", + "@typescript-eslint/eslint-plugin": "^7.5.0", + "@typescript-eslint/parser": "^7.5.0", + "aws-sdk-client-mock": "^4.0.0", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", "jest": "^29.7.0", - "memfs": "^4.6.0", - "pino-pretty": "^10.2.3", - "prettier": "^3.0.3", - "supertest": "^6.3.3", - "ts-jest": "^29.1.1", - "ts-loader": "^9.5.0", - "ts-node": "^10.9.1", + "memfs": "^4.8.1", + "pino-pretty": "^11.0.0", + "prettier": "^3.2.5", + "supertest": "^6.3.4", + "ts-jest": "^29.1.2", + "ts-loader": "^9.5.1", + "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", - "typescript": "^5.2.2", - "unionfs": "^4.5.1" + "typescript": "^5.4.3", + "unionfs": "^4.5.4" }, "jest": { "moduleFileExtensions": [ From d73c3c29b1b7bcc8d8c0d6f314f9f63b4630bf36 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Thu, 4 Apr 2024 10:41:30 -0700 Subject: [PATCH 099/153] ALCS Frontend --- alcs-frontend/angular.json | 48 +- alcs-frontend/package-lock.json | 8473 +++++++++-------- alcs-frontend/package.json | 67 +- .../application-details.component.scss | 2 +- .../additional-information.component.scss | 4 +- .../notice-of-intent-details.component.scss | 2 +- .../notification-details.component.scss | 2 +- alcs-frontend/src/styles.scss | 30 +- 8 files changed, 4336 insertions(+), 4292 deletions(-) diff --git a/alcs-frontend/angular.json b/alcs-frontend/angular.json index d215aa81e1..0388e34ea3 100644 --- a/alcs-frontend/angular.json +++ b/alcs-frontend/angular.json @@ -23,10 +23,19 @@ "polyfills": "src/polyfills.ts", "tsConfig": "tsconfig.app.json", "inlineStyleLanguage": "scss", - "assets": ["src/favicon.ico", "src/assets", "src/maintenance.html"], - "styles": ["./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "src/styles.scss"], + "assets": [ + "src/favicon.ico", + "src/assets", + "src/maintenance.html" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], "stylePreprocessorOptions": { - "includePaths": ["node_modules"] + "includePaths": [ + "node_modules" + ] }, "scripts": [] }, @@ -73,10 +82,10 @@ "builder": "@angular-devkit/build-angular:dev-server", "configurations": { "production": { - "browserTarget": "alcs-frontend:build:production" + "buildTarget": "alcs-frontend:build:production" }, "development": { - "browserTarget": "alcs-frontend:build:development" + "buildTarget": "alcs-frontend:build:development" } }, "defaultConfiguration": "development" @@ -84,17 +93,27 @@ "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { - "browserTarget": "alcs-frontend:build" + "buildTarget": "alcs-frontend:build" } }, "test": { "builder": "@angular-builders/jest:run", "options": { - "polyfills": ["src/polyfills.ts"], + "polyfills": [ + "src/polyfills.ts" + ], "tsConfig": "tsconfig.spec.json", - "inlineStyleLanguage": ["scss"], - "assets": ["src/favicon.ico", "src/assets"], - "styles": ["./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "src/styles.scss"], + "inlineStyleLanguage": [ + "scss" + ], + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], "scripts": [], "detectOpenHandles": true, "forceExit": true @@ -103,7 +122,10 @@ "lint": { "builder": "@angular-eslint/builder:lint", "options": { - "lintFilePatterns": ["src/**/*.ts", "src/**/*.html"] + "lintFilePatterns": [ + "src/**/*.ts", + "src/**/*.html" + ] } } } @@ -111,6 +133,8 @@ }, "cli": { "analytics": "d29c8019-b84b-4fdc-9690-1ee8ea441897", - "schematicCollections": ["@angular-eslint/schematics"] + "schematicCollections": [ + "@angular-eslint/schematics" + ] } } diff --git a/alcs-frontend/package-lock.json b/alcs-frontend/package-lock.json index 3d94614839..61716f8598 100644 --- a/alcs-frontend/package-lock.json +++ b/alcs-frontend/package-lock.json @@ -9,48 +9,49 @@ "version": "0.0.0", "license": "Apache-2.0", "dependencies": { - "@angular/animations": "^16.2.11", - "@angular/cdk": "^16.2.11", - "@angular/common": "^16.2.11", - "@angular/compiler": "^16.2.11", - "@angular/core": "^16.2.11", - "@angular/forms": "^16.2.11", - "@angular/material": "^16.2.11", - "@angular/material-moment-adapter": "^16.2.11", - "@angular/platform-browser": "^16.2.11", - "@angular/platform-browser-dynamic": "^16.2.11", - "@angular/router": "^16.2.11", + "@angular/animations": "^17.3.3", + "@angular/cdk": "^17.3.3", + "@angular/common": "^17.3.3", + "@angular/compiler": "^17.3.3", + "@angular/core": "^17.3.3", + "@angular/forms": "^17.3.3", + "@angular/material": "^17.3.3", + "@angular/material-experimental": "^17.3.3", + "@angular/material-moment-adapter": "^17.3.3", + "@angular/platform-browser": "^17.3.3", + "@angular/platform-browser-dynamic": "^17.3.3", + "@angular/router": "^17.3.3", "@bcgov/bc-sans": "^2.1.0", - "@ng-matero/extensions": "^16.1.2", - "@ng-select/ng-option-highlight": "^11.2.0", + "@ng-matero/extensions": "^17.2.0", + "@ng-select/ng-option-highlight": "^12.0.6", "angular-mentions": "^1.5.0", - "jwt-decode": "^3.1.2", - "moment": "^2.29.4", - "moment-timezone": "^0.5.43", - "ngx-mask": "^16.3.9", + "jwt-decode": "^4.0.0", + "moment": "^2.30.1", + "moment-timezone": "^0.5.45", + "ngx-mask": "^17.0.7", "rxjs": "~7.8.1", "source-map-support": "^0.5.21", "tslib": "^2.6.2", - "zone.js": "~0.13.3" + "zone.js": "~0.14.4" }, "devDependencies": { - "@angular-builders/jest": "^16.0.1", - "@angular-devkit/build-angular": "^16.2.9", - "@angular-eslint/builder": "^16.2.0", - "@angular-eslint/eslint-plugin": "^16.2.0", - "@angular-eslint/eslint-plugin-template": "^16.2.0", - "@angular-eslint/schematics": "^16.2.0", - "@angular-eslint/template-parser": "^16.2.0", - "@angular/cli": "~16.2.9", - "@angular/compiler-cli": "^16.2.11", + "@angular-builders/jest": "^17.0.3", + "@angular-devkit/build-angular": "^17.3.3", + "@angular-eslint/builder": "^17.3.0", + "@angular-eslint/eslint-plugin": "^17.3.0", + "@angular-eslint/eslint-plugin-template": "^17.3.0", + "@angular-eslint/schematics": "^17.3.0", + "@angular-eslint/template-parser": "^17.3.0", + "@angular/cli": "~17.3.3", + "@angular/compiler-cli": "^17.3.3", "@golevelup/ts-jest": "^0.4.0", - "@types/jest": "^29.5.7", - "@typescript-eslint/eslint-plugin": "6.9.1", - "@typescript-eslint/parser": "6.9.1", - "eslint": "^8.52.0", + "@types/jest": "^29.5.12", + "@typescript-eslint/eslint-plugin": "7.5.0", + "@typescript-eslint/parser": "7.5.0", + "eslint": "^8.57.0", "jest": "^29.7.0", - "prettier": "^3.0.3", - "typescript": "4.9.5" + "prettier": "^3.2.5", + "typescript": "5.4.3" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -63,147 +64,162 @@ } }, "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, - "node_modules/@angular-builders/jest": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/@angular-builders/jest/-/jest-16.0.1.tgz", - "integrity": "sha512-FuYkfy8JwdfTHevjgs8z18sXt0egcWbSSkefyM/QsGVkMHs+b4N4xzV20MQtyx0Yc6nJzuknIH5ZvDwLYUDPyQ==", + "node_modules/@angular-builders/common": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@angular-builders/common/-/common-1.0.2.tgz", + "integrity": "sha512-lUusRq6jN1It5LcUTLS6Q+AYAYGTo/EEN8hV0M6Ek9qXzweAouJaSEnwv7p04/pD7yJTl0YOCbN79u+wGm3x4g==", "dev": true, "dependencies": { - "@angular-devkit/architect": ">=0.1600.0 < 0.1700.0", - "@angular-devkit/core": "^16.0.0", - "jest-preset-angular": "13.1.1", - "lodash": "^4.17.15", + "@angular-devkit/core": "^17.1.0", + "ts-node": "^10.0.0", "tsconfig-paths": "^4.1.0" }, + "engines": { + "node": "^14.20.0 || ^16.13.0 || >=18.10.0" + } + }, + "node_modules/@angular-builders/jest": { + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/@angular-builders/jest/-/jest-17.0.3.tgz", + "integrity": "sha512-LW4s8t+NLnWR7Aud+EZup8dOBfQF8rfOIncsarDtP/48rz/Ucnzvum7xEt/NYAlZ6y/Dpk7wO6SlqAsaOPf8mA==", + "dev": true, + "dependencies": { + "@angular-builders/common": "1.0.2", + "@angular-devkit/architect": ">=0.1700.0 < 0.1800.0", + "@angular-devkit/core": "^17.0.0", + "jest-preset-angular": "14.0.3", + "lodash": "^4.17.15" + }, "engines": { "node": "^14.20.0 || ^16.13.0 || >=18.10.0" }, "peerDependencies": { - "@angular-devkit/build-angular": "^16.0.0", - "@angular/compiler-cli": "^16.0.0", - "@angular/core": "^16.0.0", - "@angular/platform-browser-dynamic": "^16.0.0", + "@angular-devkit/build-angular": "^17.0.0", + "@angular/compiler-cli": "^17.0.0", + "@angular/core": "^17.0.0", + "@angular/platform-browser-dynamic": "^17.0.0", "jest": ">=29" } }, "node_modules/@angular-devkit/architect": { - "version": "0.1602.9", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1602.9.tgz", - "integrity": "sha512-U3vfb/e2sFfg0D9FyyRBXRPP7g4FBFtGK8Q3JPmvAVsHHwi5AUFRNR7YBChB/T5TMNY077HcTyEirVh2FeUpdA==", + "version": "0.1703.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1703.3.tgz", + "integrity": "sha512-BKbdigCjmspqxOxSIQuWgPZzpyuKqZoTBDh0jDeLcAmvPsuxCgIWbsExI4OQ0CyusnQ+XT0IT39q8B9rvF56cg==", "dev": true, "dependencies": { - "@angular-devkit/core": "16.2.9", + "@angular-devkit/core": "17.3.3", "rxjs": "7.8.1" }, "engines": { - "node": "^16.14.0 || >=18.10.0", + "node": "^18.13.0 || >=20.9.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, "node_modules/@angular-devkit/build-angular": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-16.2.9.tgz", - "integrity": "sha512-S1C4UYxRVyNt3C0wCxbT2jZ1dN5i37kS0mol3PQjbR8gQ0GQzHmzhjTBl1oImo8aouET9yhrk9etk65oat4mBQ==", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-17.3.3.tgz", + "integrity": "sha512-E/6Z1MIMhEB1I2sN+Pw4/zinwAFj4vLDh6dEuj856WWEPndgPiUB6fGX4EbCTsyIUzboXI5ysdNyt2Eq56bllA==", "dev": true, "dependencies": { - "@ampproject/remapping": "2.2.1", - "@angular-devkit/architect": "0.1602.9", - "@angular-devkit/build-webpack": "0.1602.9", - "@angular-devkit/core": "16.2.9", - "@babel/core": "7.22.9", - "@babel/generator": "7.22.9", + "@ampproject/remapping": "2.3.0", + "@angular-devkit/architect": "0.1703.3", + "@angular-devkit/build-webpack": "0.1703.3", + "@angular-devkit/core": "17.3.3", + "@babel/core": "7.24.0", + "@babel/generator": "7.23.6", "@babel/helper-annotate-as-pure": "7.22.5", "@babel/helper-split-export-declaration": "7.22.6", - "@babel/plugin-proposal-async-generator-functions": "7.20.7", - "@babel/plugin-transform-async-to-generator": "7.22.5", - "@babel/plugin-transform-runtime": "7.22.9", - "@babel/preset-env": "7.22.9", - "@babel/runtime": "7.22.6", - "@babel/template": "7.22.5", + "@babel/plugin-transform-async-generator-functions": "7.23.9", + "@babel/plugin-transform-async-to-generator": "7.23.3", + "@babel/plugin-transform-runtime": "7.24.0", + "@babel/preset-env": "7.24.0", + "@babel/runtime": "7.24.0", "@discoveryjs/json-ext": "0.5.7", - "@ngtools/webpack": "16.2.9", - "@vitejs/plugin-basic-ssl": "1.0.1", + "@ngtools/webpack": "17.3.3", + "@vitejs/plugin-basic-ssl": "1.1.0", "ansi-colors": "4.1.3", - "autoprefixer": "10.4.14", + "autoprefixer": "10.4.18", "babel-loader": "9.1.3", "babel-plugin-istanbul": "6.1.1", "browserslist": "^4.21.5", - "chokidar": "3.5.3", "copy-webpack-plugin": "11.0.0", - "critters": "0.0.20", - "css-loader": "6.8.1", - "esbuild-wasm": "0.18.17", - "fast-glob": "3.3.1", - "guess-parser": "0.4.22", - "https-proxy-agent": "5.0.1", - "inquirer": "8.2.4", - "jsonc-parser": "3.2.0", + "critters": "0.0.22", + "css-loader": "6.10.0", + "esbuild-wasm": "0.20.1", + "fast-glob": "3.3.2", + "http-proxy-middleware": "2.0.6", + "https-proxy-agent": "7.0.4", + "inquirer": "9.2.15", + "jsonc-parser": "3.2.1", "karma-source-map-support": "1.4.0", - "less": "4.1.3", + "less": "4.2.0", "less-loader": "11.1.0", "license-webpack-plugin": "4.0.2", "loader-utils": "3.2.1", - "magic-string": "0.30.1", - "mini-css-extract-plugin": "2.7.6", - "mrmime": "1.0.1", + "magic-string": "0.30.8", + "mini-css-extract-plugin": "2.8.1", + "mrmime": "2.0.0", "open": "8.4.2", "ora": "5.4.1", "parse5-html-rewriting-stream": "7.0.0", - "picomatch": "2.3.1", - "piscina": "4.0.0", - "postcss": "8.4.31", - "postcss-loader": "7.3.3", + "picomatch": "4.0.1", + "piscina": "4.4.0", + "postcss": "8.4.35", + "postcss-loader": "8.1.1", "resolve-url-loader": "5.0.0", "rxjs": "7.8.1", - "sass": "1.64.1", - "sass-loader": "13.3.2", - "semver": "7.5.4", - "source-map-loader": "4.0.1", + "sass": "1.71.1", + "sass-loader": "14.1.1", + "semver": "7.6.0", + "source-map-loader": "5.0.0", "source-map-support": "0.5.21", - "terser": "5.19.2", - "text-table": "0.2.0", + "terser": "5.29.1", "tree-kill": "1.2.2", - "tslib": "2.6.1", - "vite": "4.4.7", - "webpack": "5.88.2", - "webpack-dev-middleware": "6.1.1", + "tslib": "2.6.2", + "undici": "6.7.1", + "vite": "5.1.5", + "watchpack": "2.4.0", + "webpack": "5.90.3", + "webpack-dev-middleware": "6.1.2", "webpack-dev-server": "4.15.1", - "webpack-merge": "5.9.0", + "webpack-merge": "5.10.0", "webpack-subresource-integrity": "5.1.0" }, "engines": { - "node": "^16.14.0 || >=18.10.0", + "node": "^18.13.0 || >=20.9.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, "optionalDependencies": { - "esbuild": "0.18.17" + "esbuild": "0.20.1" }, "peerDependencies": { - "@angular/compiler-cli": "^16.0.0", - "@angular/localize": "^16.0.0", - "@angular/platform-server": "^16.0.0", - "@angular/service-worker": "^16.0.0", + "@angular/compiler-cli": "^17.0.0", + "@angular/localize": "^17.0.0", + "@angular/platform-server": "^17.0.0", + "@angular/service-worker": "^17.0.0", + "@web/test-runner": "^0.18.0", + "browser-sync": "^3.0.2", "jest": "^29.5.0", "jest-environment-jsdom": "^29.5.0", "karma": "^6.3.0", - "ng-packagr": "^16.0.0", + "ng-packagr": "^17.0.0", "protractor": "^7.0.0", "tailwindcss": "^2.0.0 || ^3.0.0", - "typescript": ">=4.9.3 <5.2" + "typescript": ">=5.2 <5.5" }, "peerDependenciesMeta": { "@angular/localize": { @@ -215,6 +231,12 @@ "@angular/service-worker": { "optional": true }, + "@web/test-runner": { + "optional": true + }, + "browser-sync": { + "optional": true + }, "jest": { "optional": true }, @@ -235,23 +257,107 @@ } } }, - "node_modules/@angular-devkit/build-angular/node_modules/tslib": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", - "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==", + "node_modules/@angular-devkit/build-angular/node_modules/@babel/core": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz", + "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.24.0", + "@babel/parser": "^7.24.0", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.0", + "@babel/types": "^7.24.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@angular-devkit/build-angular/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/picomatch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", + "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1602.9", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1602.9.tgz", - "integrity": "sha512-+3IxovfBPR2Vy730mGa0SVKkd5LQVom85gjXOs7WcnnnZmfc1q/BtFlqTgW1UWvTxP8IQdm7UYWVclQfL/WExw==", + "version": "0.1703.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1703.3.tgz", + "integrity": "sha512-d0JjE8MaGVNphlJfeP1OZKhNT4wCXkEZKdSdwE0+W+vDHNUuZiUBB1czO48sb7T4xBrdjRWlV/9CzMNJ7n3ydA==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1602.9", + "@angular-devkit/architect": "0.1703.3", "rxjs": "7.8.1" }, "engines": { - "node": "^16.14.0 || >=18.10.0", + "node": "^18.13.0 || >=20.9.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, @@ -261,20 +367,20 @@ } }, "node_modules/@angular-devkit/core": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.9.tgz", - "integrity": "sha512-dcHWjHBNGm3yCeNz19y8A1At4KgyC6XHNnbFL0y+nnZYiaESXjUoXJYKASedI6A+Bpl0HNq2URhH6bL6Af3+4w==", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.3.tgz", + "integrity": "sha512-J22Sh3M7rj8Ar3iEs20ko5wgC3DE7vWfYZNdimt2IJiS4J7BEX8R3Awf+TRt+6AN3NFm3/xe1Sz4yvDh3FvNFg==", "dev": true, "dependencies": { "ajv": "8.12.0", "ajv-formats": "2.1.1", - "jsonc-parser": "3.2.0", - "picomatch": "2.3.1", + "jsonc-parser": "3.2.1", + "picomatch": "4.0.1", "rxjs": "7.8.1", "source-map": "0.7.4" }, "engines": { - "node": "^16.14.0 || >=18.10.0", + "node": "^18.13.0 || >=20.9.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, @@ -287,32 +393,44 @@ } } }, + "node_modules/@angular-devkit/core/node_modules/picomatch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", + "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@angular-devkit/schematics": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.2.9.tgz", - "integrity": "sha512-lB51CGCILpcSI37CwKUAGDLxMqh7zmuRbiPo9s9mSkCM4ccqxFlaL+VFTq2/laneARD6aikpOHnkVm5myNzQPw==", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.3.3.tgz", + "integrity": "sha512-SABqTtj2im4PJhQjNaAsSypbNkpZFW8YozJ3P748tlh5a9XoHpgiqXv5JhRbyKElLDAyk5i9fe2++JmSudPG/Q==", "dev": true, "dependencies": { - "@angular-devkit/core": "16.2.9", - "jsonc-parser": "3.2.0", - "magic-string": "0.30.1", + "@angular-devkit/core": "17.3.3", + "jsonc-parser": "3.2.1", + "magic-string": "0.30.8", "ora": "5.4.1", "rxjs": "7.8.1" }, "engines": { - "node": "^16.14.0 || >=18.10.0", + "node": "^18.13.0 || >=20.9.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, "node_modules/@angular-eslint/builder": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-16.2.0.tgz", - "integrity": "sha512-SZjXOi3YIjuX2CocuRsR2QH6k1ca9lRO6IMm0YIYMmBPFCRP2KFHkL6aQnXM6DSaymQNN2TXfpuvUd45NxhU1w==", + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-17.3.0.tgz", + "integrity": "sha512-JXSZE7+KA3UGU6jwc0v9lwOIMptosrvLIOXGlXqrhHWEXfkfu3ENPq1Lm3K8jLndQ57XueEhC+Nab/AuUiWA/Q==", "dev": true, "dependencies": { - "@nx/devkit": "16.5.1", - "nx": "16.5.1" + "@nx/devkit": "^17.2.8 || ^18.0.0", + "nx": "^17.2.8 || ^18.0.0" }, "peerDependencies": { "eslint": "^7.20.0 || ^8.0.0", @@ -320,19 +438,19 @@ } }, "node_modules/@angular-eslint/bundled-angular-compiler": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-16.2.0.tgz", - "integrity": "sha512-ct9orDYxkMl2+uvM7UBfgV28Dq57V4dEs+Drh7cD673JIMa6sXbgmd0QEtm8W3cmyK/jcTzmuoufxbH7hOxd6g==", + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-17.3.0.tgz", + "integrity": "sha512-ejfNzRuBeHUV8m2fkgs+M809rj5STuCuQo4fdfc6ccQpzXDI6Ha7BKpTznWfg5g529q/wrkoGSGgFxU9Yc2/dQ==", "dev": true }, "node_modules/@angular-eslint/eslint-plugin": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-16.2.0.tgz", - "integrity": "sha512-zdiAIox1T+B71HL+A8m+1jWdU34nvPGLhCRw/uZNwHzknsF4tYzNQ9W7T/SC/g/2s1yT2yNosEVNJSGSFvunJg==", + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-17.3.0.tgz", + "integrity": "sha512-81cQbOEPoQupFX8WmpqZn+y8VA7JdVRGBtt+uJNKBXcJknTpPWdLBZRFlgVakmC24iEZ0Fint/N3NBBQI3mz2A==", "dev": true, "dependencies": { - "@angular-eslint/utils": "16.2.0", - "@typescript-eslint/utils": "5.62.0" + "@angular-eslint/utils": "17.3.0", + "@typescript-eslint/utils": "7.2.0" }, "peerDependencies": { "eslint": "^7.20.0 || ^8.0.0", @@ -340,17 +458,17 @@ } }, "node_modules/@angular-eslint/eslint-plugin-template": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-16.2.0.tgz", - "integrity": "sha512-YFdQ6hHX6NlQj0lfogZwfyKjU8pqkJU+Zsk0ehjlXP8VfKFVmDeQT5/Xr6Df9C8pveC3hvq6Jgd8vo67S9Enxg==", + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-17.3.0.tgz", + "integrity": "sha512-9l/aRfpE9MCRVDWRb+rSB9Zei0paep1vqV6M/87VUnzBnzqeMRnVuPvQowilh2zweVSGKBF25Vp4HkwOL6ExDQ==", "dev": true, "dependencies": { - "@angular-eslint/bundled-angular-compiler": "16.2.0", - "@angular-eslint/utils": "16.2.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", + "@angular-eslint/bundled-angular-compiler": "17.3.0", + "@angular-eslint/utils": "17.3.0", + "@typescript-eslint/type-utils": "7.2.0", + "@typescript-eslint/utils": "7.2.0", "aria-query": "5.3.0", - "axobject-query": "3.2.1" + "axobject-query": "4.0.0" }, "peerDependencies": { "eslint": "^7.20.0 || ^8.0.0", @@ -358,45 +476,61 @@ } }, "node_modules/@angular-eslint/schematics": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-16.2.0.tgz", - "integrity": "sha512-2JUVR7hAKx37mgWeDjvyWEMH5uSeeksYuaQT5wwlgIzgrO4BNFuqs6Rgyp2jiYa7BFMX/qHULSa+bSq5J5ceEA==", + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-17.3.0.tgz", + "integrity": "sha512-5yssd5EOomxlKt9vN/OXXCTCuI3Pmfj16pkjBDoW0wzC8/M2l5zlXIEfoKumHYv2wtF553LhaMXVYVU35e0lTw==", "dev": true, "dependencies": { - "@angular-eslint/eslint-plugin": "16.2.0", - "@angular-eslint/eslint-plugin-template": "16.2.0", - "@nx/devkit": "16.5.1", - "ignore": "5.2.4", - "nx": "16.5.1", + "@angular-eslint/eslint-plugin": "17.3.0", + "@angular-eslint/eslint-plugin-template": "17.3.0", + "@nx/devkit": "^17.2.8 || ^18.0.0", + "ignore": "5.3.1", + "nx": "^17.2.8 || ^18.0.0", "strip-json-comments": "3.1.1", - "tmp": "0.2.1" + "tmp": "0.2.3" }, "peerDependencies": { - "@angular/cli": ">= 16.0.0 < 17.0.0" + "@angular/cli": ">= 17.0.0 < 18.0.0" } }, "node_modules/@angular-eslint/template-parser": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-16.2.0.tgz", - "integrity": "sha512-v2jVKTy2wN7iM9nHpBkxLn2wfL8jSl4IlPrXThIqj8No2VHtpLQZPKuXbGPUXQX05VS2Mj5feScQ36ZVGS8Rbw==", + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-17.3.0.tgz", + "integrity": "sha512-m+UzAnWgtjeS0x6skSmR0eXltD/p7HZA+c8pPyAkiHQzkxE7ohhfyZc03yWGuYJvWQUqQAKKdO/nQop14TP0bg==", "dev": true, "dependencies": { - "@angular-eslint/bundled-angular-compiler": "16.2.0", - "eslint-scope": "^7.0.0" + "@angular-eslint/bundled-angular-compiler": "17.3.0", + "eslint-scope": "^8.0.0" }, "peerDependencies": { "eslint": "^7.20.0 || ^8.0.0", "typescript": "*" } }, + "node_modules/@angular-eslint/template-parser/node_modules/eslint-scope": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz", + "integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@angular-eslint/utils": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-16.2.0.tgz", - "integrity": "sha512-NxMRwnlIgzmbJQfWkfd9y3Sz0hzjFdK5LH44i+3D5NhpPdZ6SzwHAjMYWoYsmmNQX5tlDXoicYF9Mz9Wz8DJ/A==", + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-17.3.0.tgz", + "integrity": "sha512-PJT9pxWqpvI9OXO+7L5SIVhvMW+RFjeafC7PYjtvSbNFpz+kF644BiAcfMJ0YqBnkrw3JXt+RAX25CT4mXIoXw==", "dev": true, "dependencies": { - "@angular-eslint/bundled-angular-compiler": "16.2.0", - "@typescript-eslint/utils": "5.62.0" + "@angular-eslint/bundled-angular-compiler": "17.3.0", + "@typescript-eslint/utils": "7.2.0" }, "peerDependencies": { "eslint": "^7.20.0 || ^8.0.0", @@ -404,23 +538,23 @@ } }, "node_modules/@angular/animations": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-16.2.11.tgz", - "integrity": "sha512-xdLYXsGi7OuJawhiVIppl2VkPHhPdxUP/nR6+ETR3TdAscVruCWJs4z9XKval4fbik/brekbFNFuYtlx6csDhQ==", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-17.3.3.tgz", + "integrity": "sha512-poLW3FHe5wkxmTIsQ3em2vq4obgQHyZJz6biF+4hCqQSNMbMBS0e5ZycAiJLkUD/WLc88lQZ20muRO7qjVuMLA==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^16.14.0 || >=18.10.0" + "node": "^18.13.0 || >=20.9.0" }, "peerDependencies": { - "@angular/core": "16.2.11" + "@angular/core": "17.3.3" } }, "node_modules/@angular/cdk": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-16.2.11.tgz", - "integrity": "sha512-FcJ9xd9ptjULdScnBNg7YkVnY9NKePFfmvvs2zt841Hd489L8BUkTUdbvtCLhMJTTSN+k+D+RYFhevZuhPKVVg==", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-17.3.3.tgz", + "integrity": "sha512-hfS9pwaNE6CTZqP3FBh9tZPbuf//bDqZ5IpMzscfDFrwX8ycxBiI3znH/rFSf9l1rL0OQGoqWWNVfJCT+RrukA==", "dependencies": { "tslib": "^2.3.0" }, @@ -428,33 +562,33 @@ "parse5": "^7.1.2" }, "peerDependencies": { - "@angular/common": "^16.0.0 || ^17.0.0", - "@angular/core": "^16.0.0 || ^17.0.0", + "@angular/common": "^17.0.0 || ^18.0.0", + "@angular/core": "^17.0.0 || ^18.0.0", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/cli": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-16.2.9.tgz", - "integrity": "sha512-wkpV/Ni26LUeDmhee2TPXXEq3feEdZMSG8+nkfUK9kqIcxm0IjI1GLPeiVOX7aQobuKNe2cCAFNwsrXWjj+2og==", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-17.3.3.tgz", + "integrity": "sha512-veIGK2sRm0SfiLHeftx0W0xC3N8uxoqxXiSG57V6W2wIFN/fKm3aRq3sa8phz7vxUzoKGqyZh6hsT7ybkjgkGA==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1602.9", - "@angular-devkit/core": "16.2.9", - "@angular-devkit/schematics": "16.2.9", - "@schematics/angular": "16.2.9", + "@angular-devkit/architect": "0.1703.3", + "@angular-devkit/core": "17.3.3", + "@angular-devkit/schematics": "17.3.3", + "@schematics/angular": "17.3.3", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.3", - "ini": "4.1.1", - "inquirer": "8.2.4", - "jsonc-parser": "3.2.0", - "npm-package-arg": "10.1.0", - "npm-pick-manifest": "8.0.1", + "ini": "4.1.2", + "inquirer": "9.2.15", + "jsonc-parser": "3.2.1", + "npm-package-arg": "11.0.1", + "npm-pick-manifest": "9.0.0", "open": "8.4.2", "ora": "5.4.1", - "pacote": "15.2.0", - "resolve": "1.22.2", - "semver": "7.5.4", + "pacote": "17.0.6", + "resolve": "1.22.8", + "semver": "7.6.0", "symbol-observable": "4.0.0", "yargs": "17.7.2" }, @@ -462,38 +596,71 @@ "ng": "bin/ng.js" }, "engines": { - "node": "^16.14.0 || >=18.10.0", + "node": "^18.13.0 || >=20.9.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, + "node_modules/@angular/cli/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular/cli/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular/cli/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/@angular/common": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-16.2.11.tgz", - "integrity": "sha512-h80WUR2OYlqxQy+4XgNtWT2vB+vZ6oCrFX/q8cU5jAvbvGQfJuH0zfcbSlUflStmAhk5/OT25F0mt96cqapEAw==", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-17.3.3.tgz", + "integrity": "sha512-GwlKetNpfWKiG2j4S6bYTi6PA2iT4+eln7o8owo44xZWdQnWQjfxnH39vQuCyhi6OOQL1dozmae+fVXgQsV6jQ==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^16.14.0 || >=18.10.0" + "node": "^18.13.0 || >=20.9.0" }, "peerDependencies": { - "@angular/core": "16.2.11", + "@angular/core": "17.3.3", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-16.2.11.tgz", - "integrity": "sha512-9q/E3uurvoQbdTTWDyWCLpzmfJ4+et7SUca1/EljD/X7Xg2FNU5GpTMutBtWFL7wDyWk1oswivuq9/C4GVW7fA==", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-17.3.3.tgz", + "integrity": "sha512-ZNMRfagMxMjk1KW5H3ssCg5QL0J6ZW1JAZ1mrTXixqS7gbdwl60bTGE+EfuEwbjvovEYaj4l9cga47eMaxZTbQ==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^16.14.0 || >=18.10.0" + "node": "^18.13.0 || >=20.9.0" }, "peerDependencies": { - "@angular/core": "16.2.11" + "@angular/core": "17.3.3" }, "peerDependenciesMeta": { "@angular/core": { @@ -502,16 +669,16 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-16.2.11.tgz", - "integrity": "sha512-ZtZCXfkVBH78HUm2Byf+WX3Y6WzQK9EXYXNU/ni1rvSZ1vLNwieLDfWb/xwiO7QojrHZTym1RJ10jTMinTguqw==", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.3.3.tgz", + "integrity": "sha512-vM0lqwuXQZ912HbLnIuvUblvIz2WEUsU7a5Z2ieNey6famH4zxPH12vCbVwXgicB6GLHorhOfcWC5443wD2mJw==", "dev": true, "dependencies": { - "@babel/core": "7.23.2", + "@babel/core": "7.23.9", "@jridgewell/sourcemap-codec": "^1.4.14", "chokidar": "^3.0.0", "convert-source-map": "^1.5.1", - "reflect-metadata": "^0.1.2", + "reflect-metadata": "^0.2.0", "semver": "^7.0.0", "tslib": "^2.3.0", "yargs": "^17.2.1" @@ -522,210 +689,200 @@ "ngcc": "bundles/ngcc/index.js" }, "engines": { - "node": "^16.14.0 || >=18.10.0" + "node": "^18.13.0 || >=20.9.0" }, "peerDependencies": { - "@angular/compiler": "16.2.11", - "typescript": ">=4.9.3 <5.2" - } - }, - "node_modules/@angular/compiler-cli/node_modules/@babel/core": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", - "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-module-transforms": "^7.23.0", - "@babel/helpers": "^7.23.2", - "@babel/parser": "^7.23.0", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@angular/compiler-cli/node_modules/@babel/core/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "node_modules/@angular/compiler-cli/node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@angular/compiler-cli/node_modules/@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.23.0", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@angular/compiler-cli/node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" - }, - "engines": { - "node": ">=6.9.0" + "@angular/compiler": "17.3.3", + "typescript": ">=5.2 <5.5" } }, "node_modules/@angular/core": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-16.2.11.tgz", - "integrity": "sha512-Jb+7/p1vczQRQ3iC1QxUS5cE4X1hPVAvbrFnyMpSx6Pq5o274v/lK6PvhUZrfKrp9FxFp9pN+WHjUqNFqOuJZg==", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-17.3.3.tgz", + "integrity": "sha512-O/jr3aFJMCxF6Jmymjx4jIigRHJfqM/ALIi60y2LVznBVFkk9xyMTsAjgWQIEHX+2muEIzgfKuXzpL0y30y+wA==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^16.14.0 || >=18.10.0" + "node": "^18.13.0 || >=20.9.0" }, "peerDependencies": { "rxjs": "^6.5.3 || ^7.4.0", - "zone.js": "~0.13.0" + "zone.js": "~0.14.0" } }, "node_modules/@angular/forms": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-16.2.11.tgz", - "integrity": "sha512-2powweUorehB1opfev6/sUeb3Bdey+Txq4gjI1Qdeo9c9OgtaKu6wK0KXgoism8HXXRFcGHMfS0dUVoDPVrtiQ==", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-17.3.3.tgz", + "integrity": "sha512-wqn+eAggbOZY91hr7oDjv5qdflszVOC9SZMcWJUoZTGn+8eoV6v6728GDFuDDwYkKQ9G9eQbX4IZmYoVw3TVjQ==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^16.14.0 || >=18.10.0" + "node": "^18.13.0 || >=20.9.0" }, "peerDependencies": { - "@angular/common": "16.2.11", - "@angular/core": "16.2.11", - "@angular/platform-browser": "16.2.11", + "@angular/common": "17.3.3", + "@angular/core": "17.3.3", + "@angular/platform-browser": "17.3.3", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/material": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-16.2.11.tgz", - "integrity": "sha512-hrkRD9/38++nIyo3k/KQpxsIaWm+FOJVmoJa83qvwZZt+fHKfT7xaNvRPZ+L2oqFaAvH5ivnL1u1nDuDyWz/0w==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/auto-init": "15.0.0-canary.bc9ae6c9c.0", - "@material/banner": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/button": "15.0.0-canary.bc9ae6c9c.0", - "@material/card": "15.0.0-canary.bc9ae6c9c.0", - "@material/checkbox": "15.0.0-canary.bc9ae6c9c.0", - "@material/chips": "15.0.0-canary.bc9ae6c9c.0", - "@material/circular-progress": "15.0.0-canary.bc9ae6c9c.0", - "@material/data-table": "15.0.0-canary.bc9ae6c9c.0", - "@material/density": "15.0.0-canary.bc9ae6c9c.0", - "@material/dialog": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/drawer": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/fab": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/floating-label": "15.0.0-canary.bc9ae6c9c.0", - "@material/form-field": "15.0.0-canary.bc9ae6c9c.0", - "@material/icon-button": "15.0.0-canary.bc9ae6c9c.0", - "@material/image-list": "15.0.0-canary.bc9ae6c9c.0", - "@material/layout-grid": "15.0.0-canary.bc9ae6c9c.0", - "@material/line-ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/linear-progress": "15.0.0-canary.bc9ae6c9c.0", - "@material/list": "15.0.0-canary.bc9ae6c9c.0", - "@material/menu": "15.0.0-canary.bc9ae6c9c.0", - "@material/menu-surface": "15.0.0-canary.bc9ae6c9c.0", - "@material/notched-outline": "15.0.0-canary.bc9ae6c9c.0", - "@material/radio": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/segmented-button": "15.0.0-canary.bc9ae6c9c.0", - "@material/select": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/slider": "15.0.0-canary.bc9ae6c9c.0", - "@material/snackbar": "15.0.0-canary.bc9ae6c9c.0", - "@material/switch": "15.0.0-canary.bc9ae6c9c.0", - "@material/tab": "15.0.0-canary.bc9ae6c9c.0", - "@material/tab-bar": "15.0.0-canary.bc9ae6c9c.0", - "@material/tab-indicator": "15.0.0-canary.bc9ae6c9c.0", - "@material/tab-scroller": "15.0.0-canary.bc9ae6c9c.0", - "@material/textfield": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tooltip": "15.0.0-canary.bc9ae6c9c.0", - "@material/top-app-bar": "15.0.0-canary.bc9ae6c9c.0", - "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-17.3.3.tgz", + "integrity": "sha512-cb3PYY+Lf3FvXxXIRmOBcTn5QS9Ghr5Eq0aiJiiYV6YVohr0YGWsndMCZ/5a2j8fxpboDo9THeTnOuuAOJv7AA==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/auto-init": "15.0.0-canary.7f224ddd4.0", + "@material/banner": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/button": "15.0.0-canary.7f224ddd4.0", + "@material/card": "15.0.0-canary.7f224ddd4.0", + "@material/checkbox": "15.0.0-canary.7f224ddd4.0", + "@material/chips": "15.0.0-canary.7f224ddd4.0", + "@material/circular-progress": "15.0.0-canary.7f224ddd4.0", + "@material/data-table": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dialog": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/drawer": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/fab": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/floating-label": "15.0.0-canary.7f224ddd4.0", + "@material/form-field": "15.0.0-canary.7f224ddd4.0", + "@material/icon-button": "15.0.0-canary.7f224ddd4.0", + "@material/image-list": "15.0.0-canary.7f224ddd4.0", + "@material/layout-grid": "15.0.0-canary.7f224ddd4.0", + "@material/line-ripple": "15.0.0-canary.7f224ddd4.0", + "@material/linear-progress": "15.0.0-canary.7f224ddd4.0", + "@material/list": "15.0.0-canary.7f224ddd4.0", + "@material/menu": "15.0.0-canary.7f224ddd4.0", + "@material/menu-surface": "15.0.0-canary.7f224ddd4.0", + "@material/notched-outline": "15.0.0-canary.7f224ddd4.0", + "@material/radio": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/segmented-button": "15.0.0-canary.7f224ddd4.0", + "@material/select": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/slider": "15.0.0-canary.7f224ddd4.0", + "@material/snackbar": "15.0.0-canary.7f224ddd4.0", + "@material/switch": "15.0.0-canary.7f224ddd4.0", + "@material/tab": "15.0.0-canary.7f224ddd4.0", + "@material/tab-bar": "15.0.0-canary.7f224ddd4.0", + "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0", + "@material/tab-scroller": "15.0.0-canary.7f224ddd4.0", + "@material/textfield": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tooltip": "15.0.0-canary.7f224ddd4.0", + "@material/top-app-bar": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.3.0" }, "peerDependencies": { - "@angular/animations": "^16.0.0 || ^17.0.0", - "@angular/cdk": "16.2.11", - "@angular/common": "^16.0.0 || ^17.0.0", - "@angular/core": "^16.0.0 || ^17.0.0", - "@angular/forms": "^16.0.0 || ^17.0.0", - "@angular/platform-browser": "^16.0.0 || ^17.0.0", + "@angular/animations": "^17.0.0 || ^18.0.0", + "@angular/cdk": "17.3.3", + "@angular/common": "^17.0.0 || ^18.0.0", + "@angular/core": "^17.0.0 || ^18.0.0", + "@angular/forms": "^17.0.0 || ^18.0.0", + "@angular/platform-browser": "^17.0.0 || ^18.0.0", "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/@angular/material-experimental": { + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/material-experimental/-/material-experimental-17.3.3.tgz", + "integrity": "sha512-K36GZPqfsfVHNHNFR7p9a7MAvgjmnA7mPXMGBJbtVFksHGcdQG237k+ei8fjMvAxXGoa/jObhEnBtDwZ2FhQfg==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/auto-init": "15.0.0-canary.7f224ddd4.0", + "@material/banner": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/button": "15.0.0-canary.7f224ddd4.0", + "@material/card": "15.0.0-canary.7f224ddd4.0", + "@material/checkbox": "15.0.0-canary.7f224ddd4.0", + "@material/chips": "15.0.0-canary.7f224ddd4.0", + "@material/circular-progress": "15.0.0-canary.7f224ddd4.0", + "@material/data-table": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dialog": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/drawer": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/fab": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/floating-label": "15.0.0-canary.7f224ddd4.0", + "@material/form-field": "15.0.0-canary.7f224ddd4.0", + "@material/icon-button": "15.0.0-canary.7f224ddd4.0", + "@material/image-list": "15.0.0-canary.7f224ddd4.0", + "@material/layout-grid": "15.0.0-canary.7f224ddd4.0", + "@material/line-ripple": "15.0.0-canary.7f224ddd4.0", + "@material/linear-progress": "15.0.0-canary.7f224ddd4.0", + "@material/list": "15.0.0-canary.7f224ddd4.0", + "@material/menu": "15.0.0-canary.7f224ddd4.0", + "@material/menu-surface": "15.0.0-canary.7f224ddd4.0", + "@material/notched-outline": "15.0.0-canary.7f224ddd4.0", + "@material/radio": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/segmented-button": "15.0.0-canary.7f224ddd4.0", + "@material/select": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/slider": "15.0.0-canary.7f224ddd4.0", + "@material/snackbar": "15.0.0-canary.7f224ddd4.0", + "@material/switch": "15.0.0-canary.7f224ddd4.0", + "@material/tab": "15.0.0-canary.7f224ddd4.0", + "@material/tab-bar": "15.0.0-canary.7f224ddd4.0", + "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0", + "@material/tab-scroller": "15.0.0-canary.7f224ddd4.0", + "@material/textfield": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tooltip": "15.0.0-canary.7f224ddd4.0", + "@material/top-app-bar": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/animations": "^17.0.0 || ^18.0.0", + "@angular/cdk": "17.3.3", + "@angular/common": "^17.0.0 || ^18.0.0", + "@angular/core": "^17.0.0 || ^18.0.0", + "@angular/forms": "^17.0.0 || ^18.0.0", + "@angular/material": "17.3.3", + "@angular/platform-browser": "^17.0.0 || ^18.0.0" + } + }, "node_modules/@angular/material-moment-adapter": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-16.2.11.tgz", - "integrity": "sha512-vAwWi1GehmqrgOcaAnL6C6EAfr6q6liZqyNyrGC+xaNChqLD2TrzOo9qljIGiYdkRm1p36Fe4BOdBFjh+73EwQ==", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-17.3.3.tgz", + "integrity": "sha512-KjUurtvBNhq8XOAGtBJLGhuOlgNKimA7m0WnFm5ShTNHbb5o2ogN5iNdJWxsWZC3qrq0RCwJs/YIrONfS3Znbg==", "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { - "@angular/core": "^16.0.0 || ^17.0.0", - "@angular/material": "16.2.11", + "@angular/core": "^17.0.0 || ^18.0.0", + "@angular/material": "17.3.3", "moment": "^2.18.1" } }, "node_modules/@angular/platform-browser": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-16.2.11.tgz", - "integrity": "sha512-gUptbI3lbaRg+L8rcTlxKtFunYmR/M/mm9/l9uRd+5qk2mnFI0+s/tzRoaq7K0XaRGKZiWLNTz6FTkviO1zo2g==", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.3.tgz", + "integrity": "sha512-XFWjquD+Pr9VszRzrDlT6uaf57TsY9XhL9iHCNok6Op5DpVQpIAuw1vFt2t5ZoQ0gv+lY8mVWnxgqe3CgTdYxw==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^16.14.0 || >=18.10.0" + "node": "^18.13.0 || >=20.9.0" }, "peerDependencies": { - "@angular/animations": "16.2.11", - "@angular/common": "16.2.11", - "@angular/core": "16.2.11" + "@angular/animations": "17.3.3", + "@angular/common": "17.3.3", + "@angular/core": "17.3.3" }, "peerDependenciesMeta": { "@angular/animations": { @@ -734,87 +891,81 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-16.2.11.tgz", - "integrity": "sha512-e+A7z6MUJaqC4Fdq7XQfIhAox3ZPM1lczM6G08fUKPbFDEe+c9i7C8YRLL+69BXDuG790btugIeOQcn5lnJcFg==", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-17.3.3.tgz", + "integrity": "sha512-jSgSNHRTXCIat20I+4tLm/e8qOvrIE3Zv7S/DtYZEiAth84uoznvo1kXnN+KREse2vP/WoNgSDKQ2JLzkwYXSQ==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^16.14.0 || >=18.10.0" + "node": "^18.13.0 || >=20.9.0" }, "peerDependencies": { - "@angular/common": "16.2.11", - "@angular/compiler": "16.2.11", - "@angular/core": "16.2.11", - "@angular/platform-browser": "16.2.11" + "@angular/common": "17.3.3", + "@angular/compiler": "17.3.3", + "@angular/core": "17.3.3", + "@angular/platform-browser": "17.3.3" } }, "node_modules/@angular/router": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-16.2.11.tgz", - "integrity": "sha512-QTssqJue+xQ8M1gzmfJcIHPIpPOijVwGnXQjt7cnFggNe/CedOckLEzk2j7/6aC1b5aQKuZePPw6XMvk8ciilQ==", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-17.3.3.tgz", + "integrity": "sha512-kj42+TtwvET7MFqxB3pkKyob0VNmspASlv8Y29vSpzzaOHn8J1fDf6H+8opoIC+Gmvo5NqXUDwq7nxI5aQ0mUQ==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^16.14.0 || >=18.10.0" + "node": "^18.13.0 || >=20.9.0" }, "peerDependencies": { - "@angular/common": "16.2.11", - "@angular/core": "16.2.11", - "@angular/platform-browser": "16.2.11", + "@angular/common": "17.3.3", + "@angular/core": "17.3.3", + "@angular/platform-browser": "17.3.3", "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@assemblyscript/loader": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.10.1.tgz", - "integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==", - "dev": true - }, "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz", - "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", + "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.9.tgz", - "integrity": "sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", + "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.9", - "@babel/helper-compilation-targets": "^7.22.9", - "@babel/helper-module-transforms": "^7.22.9", - "@babel/helpers": "^7.22.6", - "@babel/parser": "^7.22.7", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.8", - "@babel/types": "^7.22.5", - "convert-source-map": "^1.7.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.9", + "@babel/parser": "^7.23.9", + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", + "json5": "^2.2.3", "semver": "^6.3.1" }, "engines": { @@ -825,6 +976,12 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -835,12 +992,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.9.tgz", - "integrity": "sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5", + "@babel/types": "^7.23.6", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -874,14 +1031,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", - "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.15", - "browserslist": "^4.21.9", + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -899,17 +1056,17 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz", - "integrity": "sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.4.tgz", + "integrity": "sha512-lG75yeuUSVu0pIcbhiYMXBXANHrpUPaOfu7ryAzskCgKUHuAxRQI5ssrtmF0X9UXldPlvT0XM/A4F44OXRt6iQ==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-member-expression-to-functions": "^7.23.0", "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.9", + "@babel/helper-replace-supers": "^7.24.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", "semver": "^6.3.1" @@ -957,9 +1114,9 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz", - "integrity": "sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.1.tgz", + "integrity": "sha512-o7SDgTJuvx5vLKD6SFvkydkSMBvahDKGiNJzG22IZYXhiqoe9efY7zocICBgzHV4IRg5wdgl2nEL/tulKIEIbA==", "dev": true, "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", @@ -994,20 +1151,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-function-name/node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-hoist-variables": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", @@ -1045,9 +1188,9 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", - "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", @@ -1076,9 +1219,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", + "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", "dev": true, "engines": { "node": ">=6.9.0" @@ -1102,13 +1245,13 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", - "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz", + "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-member-expression-to-functions": "^7.23.0", "@babel/helper-optimise-call-expression": "^7.22.5" }, "engines": { @@ -1155,9 +1298,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", "dev": true, "engines": { "node": ">=6.9.0" @@ -1173,9 +1316,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", - "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", "dev": true, "engines": { "node": ">=6.9.0" @@ -1195,66 +1338,39 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-wrap-function/node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helpers": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", - "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers/node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.4.tgz", + "integrity": "sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz", + "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -1264,12 +1380,12 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.15.tgz", - "integrity": "sha512-FB9iYlz7rURmRJyXRKEnalYPPdn87H5no108cyuQQyMwlpJ2SJtpIUBI27kdTin956pz+LPypkPVPUTlxOmrsg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz", + "integrity": "sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1279,14 +1395,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.15.tgz", - "integrity": "sha512-Hyph9LseGvAeeXzikV88bczhsrLrIZqDPxO+sSmAunMPaGrBGhfMWzCPYTtiW9t+HzSE2wtV8e5cc5P6r1xMDQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz", + "integrity": "sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.15" + "@babel/plugin-transform-optional-chaining": "^7.24.1" }, "engines": { "node": ">=6.9.0" @@ -1295,23 +1411,20 @@ "@babel/core": "^7.13.0" } }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", - "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead.", + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.1.tgz", + "integrity": "sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, "node_modules/@babel/plugin-proposal-private-property-in-object": { @@ -1326,23 +1439,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-unicode-property-regex instead.", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", @@ -1419,12 +1515,12 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", - "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz", + "integrity": "sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1434,12 +1530,12 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz", - "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.1.tgz", + "integrity": "sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1621,12 +1717,12 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", - "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz", + "integrity": "sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1636,9 +1732,9 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.2.tgz", - "integrity": "sha512-BBYVGxbDVHfoeXbOwcagAkOQAm9NxoTdMGfTqghu1GrvadSaw6iW3Je6IcL5PNOw8VwjxqBECXy50/iCQSY/lQ==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.9.tgz", + "integrity": "sha512-8Q3veQEDGe14dTYuwagbRtwxQDnytyg1JFu4/HwEMETeofocrB0U0ejBJIXoeG/t2oXZ8kzCyI0ZZfbT80VFNQ==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", @@ -1654,14 +1750,14 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", - "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", + "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-module-imports": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.5" + "@babel/helper-remap-async-to-generator": "^7.22.20" }, "engines": { "node": ">=6.9.0" @@ -1671,12 +1767,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", - "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz", + "integrity": "sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1686,12 +1782,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.0.tgz", - "integrity": "sha512-cOsrbmIOXmf+5YbL99/S49Y3j46k/T16b9ml8bm9lP6N9US5iQ2yBK7gpui1pg0V/WMcXdkfKbTb7HXq9u+v4g==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.4.tgz", + "integrity": "sha512-nIFUZIpGKDf9O9ttyRXpHFpKC+X3Y5mtshZONuEUYBomAKoM4y029Jr+uB1bHGPhNmK8YXHevDtKDOLmtRrp6g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1701,13 +1797,13 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz", - "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz", + "integrity": "sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1717,13 +1813,13 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.11.tgz", - "integrity": "sha512-GMM8gGmqI7guS/llMFk1bJDkKfn3v3C4KHK9Yg1ey5qcHcOlKb0QvcMrgzvxo+T03/4szNh5lghY+fEC98Kq9g==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz", + "integrity": "sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.11", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.24.4", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-class-static-block": "^7.14.5" }, "engines": { @@ -1734,18 +1830,17 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.15.tgz", - "integrity": "sha512-VbbC3PGjBdE0wAWDdHM9G8Gm977pnYI0XpqMd6LrKISj8/DJXEsWqgRuTYaNE9Bv0JGhTZUzHDlMk18IpOuoqw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.1.tgz", + "integrity": "sha512-ZTIe3W7UejJd3/3R4p7ScyyOoafetUShSf4kCqV0O7F/RiHxVj/wRaRnQlrGwflvcehNA8M42HkAiEDYZu2F1Q==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.9", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-replace-supers": "^7.24.1", "@babel/helper-split-export-declaration": "^7.22.6", "globals": "^11.1.0" }, @@ -1757,13 +1852,13 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", - "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz", + "integrity": "sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/template": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/template": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1773,12 +1868,12 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.0.tgz", - "integrity": "sha512-vaMdgNXFkYrB+8lbgniSYWHsgqK5gjaMNcc84bMIOMRLH0L9AqYq3hwMdvnyqj1OPqea8UtjPEuS/DCenah1wg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.1.tgz", + "integrity": "sha512-ow8jciWqNxR3RYbSNVuF4U2Jx130nwnBnhRw6N6h1bOejNkABmcI5X5oz29K4alWX7vf1C+o6gtKXikzRKkVdw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1788,13 +1883,13 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz", - "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz", + "integrity": "sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1804,12 +1899,12 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz", - "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz", + "integrity": "sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1819,12 +1914,12 @@ } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.11.tgz", - "integrity": "sha512-g/21plo58sfteWjaO0ZNVb+uEOkJNjAaHhbejrnBmu011l/eNDScmkbjCC3l4FKb10ViaGU4aOkFznSu2zRHgA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz", + "integrity": "sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-dynamic-import": "^7.8.3" }, "engines": { @@ -1835,13 +1930,13 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz", - "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz", + "integrity": "sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw==", "dev": true, "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1851,12 +1946,12 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.11.tgz", - "integrity": "sha512-xa7aad7q7OiT8oNZ1mU7NrISjlSkVdMbNxn9IuLZyL9AJEhs1Apba3I+u5riX1dIkdptP5EKDG5XDPByWxtehw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz", + "integrity": "sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" }, "engines": { @@ -1867,12 +1962,13 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.15.tgz", - "integrity": "sha512-me6VGeHsx30+xh9fbDLLPi0J1HzmeIIyenoOQHuw2D4m2SAU3NrspX5XxJLBpqn5yrLzrlw2Iy3RA//Bx27iOA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz", + "integrity": "sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1882,14 +1978,14 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", - "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz", + "integrity": "sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1899,12 +1995,12 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.11.tgz", - "integrity": "sha512-CxT5tCqpA9/jXFlme9xIBCc5RPtdDq3JpkkhgHQqtDdiTnTI0jtZ0QzXhr5DILeYifDPp2wvY2ad+7+hLMW5Pw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.1.tgz", + "integrity": "sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-json-strings": "^7.8.3" }, "engines": { @@ -1915,12 +2011,12 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", - "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz", + "integrity": "sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1930,12 +2026,12 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.11.tgz", - "integrity": "sha512-qQwRTP4+6xFCDV5k7gZBF3C31K34ut0tbEcTKxlX/0KXxm9GLcO14p570aWxFvVzx6QAfPgq7gaeIHXJC8LswQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.1.tgz", + "integrity": "sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" }, "engines": { @@ -1946,12 +2042,12 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", - "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz", + "integrity": "sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1961,13 +2057,13 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.0.tgz", - "integrity": "sha512-xWT5gefv2HGSm4QHtgc1sYPbseOyf+FFDo2JbpE25GWl5BqTGO9IMwTYJRoIdjsF85GE+VegHxSCUt5EvoYTAw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz", + "integrity": "sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1977,13 +2073,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.0.tgz", - "integrity": "sha512-32Xzss14/UVc7k9g775yMIvkVK8xwKE0DPdP5JTapr3+Z9w4tzeOuLNY6BXDQR6BdnzIlXnCGAzsk/ICHBLVWQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz", + "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-simple-access": "^7.22.5" }, "engines": { @@ -1994,14 +2090,14 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.0.tgz", - "integrity": "sha512-qBej6ctXZD2f+DhlOC9yO47yEYgUh5CZNz/aBoH4j/3NOlRfJXJbY7xDQCqQVf9KbrqGzIWER1f23doHGrIHFg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz", + "integrity": "sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA==", "dev": true, "dependencies": { "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-validator-identifier": "^7.22.20" }, "engines": { @@ -2012,13 +2108,13 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz", - "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz", + "integrity": "sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2044,12 +2140,12 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz", - "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz", + "integrity": "sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2059,12 +2155,12 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.11.tgz", - "integrity": "sha512-YZWOw4HxXrotb5xsjMJUDlLgcDXSfO9eCmdl1bgW4+/lAGdkjaEvOnQ4p5WKKdUgSzO39dgPl0pTnfxm0OAXcg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz", + "integrity": "sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" }, "engines": { @@ -2075,12 +2171,12 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.11.tgz", - "integrity": "sha512-3dzU4QGPsILdJbASKhF/V2TVP+gJya1PsueQCxIPCEcerqF21oEcrob4mzjsp2Py/1nLfF5m+xYNMDpmA8vffg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.1.tgz", + "integrity": "sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-numeric-separator": "^7.10.4" }, "engines": { @@ -2091,16 +2187,15 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.15.tgz", - "integrity": "sha512-fEB+I1+gAmfAyxZcX1+ZUwLeAuuf8VIg67CTznZE0MqVFumWkh8xWtn58I4dxdVf080wn7gzWoF8vndOViJe9Q==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.1.tgz", + "integrity": "sha512-XjD5f0YqOtebto4HGISLNfiNMTTs6tbkFf2TOqJlYKYmbo+mN9Dnpl4SRoofiziuOWMIyq3sZEUqLo3hLITFEA==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.22.15" + "@babel/plugin-transform-parameters": "^7.24.1" }, "engines": { "node": ">=6.9.0" @@ -2110,13 +2205,13 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", - "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz", + "integrity": "sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-replace-supers": "^7.24.1" }, "engines": { "node": ">=6.9.0" @@ -2126,12 +2221,12 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.11.tgz", - "integrity": "sha512-rli0WxesXUeCJnMYhzAglEjLWVDF6ahb45HuprcmQuLidBJFWjNnOzssk2kuc6e33FlLaiZhG/kUIzUMWdBKaQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.1.tgz", + "integrity": "sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" }, "engines": { @@ -2142,12 +2237,12 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.0.tgz", - "integrity": "sha512-sBBGXbLJjxTzLBF5rFWaikMnOGOk/BmK6vVByIdEggZ7Vn6CvWXZyRkkLFK6WE0IF8jSliyOkUN6SScFgzCM0g==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.1.tgz", + "integrity": "sha512-n03wmDt+987qXwAgcBlnUUivrZBPZ8z1plL0YvgQalLm+ZE5BMhGm94jhxXtA1wzv1Cu2aaOv1BM9vbVttrzSg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, @@ -2159,12 +2254,12 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.15.tgz", - "integrity": "sha512-hjk7qKIqhyzhhUvRT683TYQOFa/4cQKwQy7ALvTpODswN40MljzNDa0YldevS6tGbxwaEKVn502JmY0dP7qEtQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.1.tgz", + "integrity": "sha512-8Jl6V24g+Uw5OGPeWNKrKqXPDw2YDjLc53ojwfMcKwlEoETKU9rU0mHUtcg9JntWI/QYzGAXNWEcVHZ+fR+XXg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2174,13 +2269,13 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz", - "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz", + "integrity": "sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2190,14 +2285,14 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.11.tgz", - "integrity": "sha512-sSCbqZDBKHetvjSwpyWzhuHkmW5RummxJBVbYLkGkaiTOWGxml7SXt0iWa03bzxFIx7wOj3g/ILRd0RcJKBeSQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.1.tgz", + "integrity": "sha512-pTHxDVa0BpUbvAgX3Gat+7cSciXqUcY9j2VZKTbSB6+VQGpNgNO9ailxTGHSXlqOnX1Hcx1Enme2+yv7VqP9bg==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.11", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, "engines": { @@ -2208,12 +2303,12 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", - "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz", + "integrity": "sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2223,12 +2318,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz", - "integrity": "sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz", + "integrity": "sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "regenerator-transform": "^0.15.2" }, "engines": { @@ -2239,12 +2334,12 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz", - "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz", + "integrity": "sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2254,16 +2349,16 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.22.9.tgz", - "integrity": "sha512-9KjBH61AGJetCPYp/IEyLEp47SyybZb0nDRpBvmtEkm+rUIwxdlKpyNHI1TmsGkeuLclJdleQHRZ8XLBnnh8CQ==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.0.tgz", + "integrity": "sha512-zc0GA5IitLKJrSfXlXmp8KDqLrnGECK7YRfQBmEKg1NmBOQ7e+KuclBEKJgzifQeUYLdNiAw4B4bjyvzWVLiSA==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "babel-plugin-polyfill-corejs2": "^0.4.4", - "babel-plugin-polyfill-corejs3": "^0.8.2", - "babel-plugin-polyfill-regenerator": "^0.5.1", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0", + "babel-plugin-polyfill-corejs2": "^0.4.8", + "babel-plugin-polyfill-corejs3": "^0.9.0", + "babel-plugin-polyfill-regenerator": "^0.5.5", "semver": "^6.3.1" }, "engines": { @@ -2283,12 +2378,12 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", - "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz", + "integrity": "sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2298,12 +2393,12 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", - "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz", + "integrity": "sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" }, "engines": { @@ -2314,12 +2409,12 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz", - "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz", + "integrity": "sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2329,12 +2424,12 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", - "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz", + "integrity": "sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2344,12 +2439,12 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz", - "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.1.tgz", + "integrity": "sha512-CBfU4l/A+KruSUoW+vTQthwcAdwuqbpRNB8HQKlZABwHRhsdHZ9fezp4Sn18PeAlYxTNiLMlx4xUBV3AWfg1BA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2359,12 +2454,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz", - "integrity": "sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz", + "integrity": "sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2374,13 +2469,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz", - "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.1.tgz", + "integrity": "sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2390,13 +2485,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz", - "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz", + "integrity": "sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2406,13 +2501,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz", - "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.1.tgz", + "integrity": "sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2422,25 +2517,26 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.9.tgz", - "integrity": "sha512-wNi5H/Emkhll/bqPjsjQorSykrlfY5OWakd6AulLvMEytpKasMVUpVy8RL4qBIBs5Ac6/5i0/Rv0b/Fg6Eag/g==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-compilation-targets": "^7.22.9", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.5", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.5", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.5", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.0.tgz", + "integrity": "sha512-ZxPEzV9IgvGn73iK0E6VB9/95Nd7aMFpbE0l8KQFDG70cOV9IxRP7Y2FUPmlK0v6ImlLqYX50iuZ3ZTVhOF2lA==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.22.5", - "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-assertions": "^7.23.3", + "@babel/plugin-syntax-import-attributes": "^7.23.3", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", @@ -2452,59 +2548,58 @@ "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.22.5", - "@babel/plugin-transform-async-generator-functions": "^7.22.7", - "@babel/plugin-transform-async-to-generator": "^7.22.5", - "@babel/plugin-transform-block-scoped-functions": "^7.22.5", - "@babel/plugin-transform-block-scoping": "^7.22.5", - "@babel/plugin-transform-class-properties": "^7.22.5", - "@babel/plugin-transform-class-static-block": "^7.22.5", - "@babel/plugin-transform-classes": "^7.22.6", - "@babel/plugin-transform-computed-properties": "^7.22.5", - "@babel/plugin-transform-destructuring": "^7.22.5", - "@babel/plugin-transform-dotall-regex": "^7.22.5", - "@babel/plugin-transform-duplicate-keys": "^7.22.5", - "@babel/plugin-transform-dynamic-import": "^7.22.5", - "@babel/plugin-transform-exponentiation-operator": "^7.22.5", - "@babel/plugin-transform-export-namespace-from": "^7.22.5", - "@babel/plugin-transform-for-of": "^7.22.5", - "@babel/plugin-transform-function-name": "^7.22.5", - "@babel/plugin-transform-json-strings": "^7.22.5", - "@babel/plugin-transform-literals": "^7.22.5", - "@babel/plugin-transform-logical-assignment-operators": "^7.22.5", - "@babel/plugin-transform-member-expression-literals": "^7.22.5", - "@babel/plugin-transform-modules-amd": "^7.22.5", - "@babel/plugin-transform-modules-commonjs": "^7.22.5", - "@babel/plugin-transform-modules-systemjs": "^7.22.5", - "@babel/plugin-transform-modules-umd": "^7.22.5", + "@babel/plugin-transform-arrow-functions": "^7.23.3", + "@babel/plugin-transform-async-generator-functions": "^7.23.9", + "@babel/plugin-transform-async-to-generator": "^7.23.3", + "@babel/plugin-transform-block-scoped-functions": "^7.23.3", + "@babel/plugin-transform-block-scoping": "^7.23.4", + "@babel/plugin-transform-class-properties": "^7.23.3", + "@babel/plugin-transform-class-static-block": "^7.23.4", + "@babel/plugin-transform-classes": "^7.23.8", + "@babel/plugin-transform-computed-properties": "^7.23.3", + "@babel/plugin-transform-destructuring": "^7.23.3", + "@babel/plugin-transform-dotall-regex": "^7.23.3", + "@babel/plugin-transform-duplicate-keys": "^7.23.3", + "@babel/plugin-transform-dynamic-import": "^7.23.4", + "@babel/plugin-transform-exponentiation-operator": "^7.23.3", + "@babel/plugin-transform-export-namespace-from": "^7.23.4", + "@babel/plugin-transform-for-of": "^7.23.6", + "@babel/plugin-transform-function-name": "^7.23.3", + "@babel/plugin-transform-json-strings": "^7.23.4", + "@babel/plugin-transform-literals": "^7.23.3", + "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", + "@babel/plugin-transform-member-expression-literals": "^7.23.3", + "@babel/plugin-transform-modules-amd": "^7.23.3", + "@babel/plugin-transform-modules-commonjs": "^7.23.3", + "@babel/plugin-transform-modules-systemjs": "^7.23.9", + "@babel/plugin-transform-modules-umd": "^7.23.3", "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.22.5", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.5", - "@babel/plugin-transform-numeric-separator": "^7.22.5", - "@babel/plugin-transform-object-rest-spread": "^7.22.5", - "@babel/plugin-transform-object-super": "^7.22.5", - "@babel/plugin-transform-optional-catch-binding": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.6", - "@babel/plugin-transform-parameters": "^7.22.5", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/plugin-transform-private-property-in-object": "^7.22.5", - "@babel/plugin-transform-property-literals": "^7.22.5", - "@babel/plugin-transform-regenerator": "^7.22.5", - "@babel/plugin-transform-reserved-words": "^7.22.5", - "@babel/plugin-transform-shorthand-properties": "^7.22.5", - "@babel/plugin-transform-spread": "^7.22.5", - "@babel/plugin-transform-sticky-regex": "^7.22.5", - "@babel/plugin-transform-template-literals": "^7.22.5", - "@babel/plugin-transform-typeof-symbol": "^7.22.5", - "@babel/plugin-transform-unicode-escapes": "^7.22.5", - "@babel/plugin-transform-unicode-property-regex": "^7.22.5", - "@babel/plugin-transform-unicode-regex": "^7.22.5", - "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.22.5", - "babel-plugin-polyfill-corejs2": "^0.4.4", - "babel-plugin-polyfill-corejs3": "^0.8.2", - "babel-plugin-polyfill-regenerator": "^0.5.1", + "@babel/plugin-transform-new-target": "^7.23.3", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", + "@babel/plugin-transform-numeric-separator": "^7.23.4", + "@babel/plugin-transform-object-rest-spread": "^7.24.0", + "@babel/plugin-transform-object-super": "^7.23.3", + "@babel/plugin-transform-optional-catch-binding": "^7.23.4", + "@babel/plugin-transform-optional-chaining": "^7.23.4", + "@babel/plugin-transform-parameters": "^7.23.3", + "@babel/plugin-transform-private-methods": "^7.23.3", + "@babel/plugin-transform-private-property-in-object": "^7.23.4", + "@babel/plugin-transform-property-literals": "^7.23.3", + "@babel/plugin-transform-regenerator": "^7.23.3", + "@babel/plugin-transform-reserved-words": "^7.23.3", + "@babel/plugin-transform-shorthand-properties": "^7.23.3", + "@babel/plugin-transform-spread": "^7.23.3", + "@babel/plugin-transform-sticky-regex": "^7.23.3", + "@babel/plugin-transform-template-literals": "^7.23.3", + "@babel/plugin-transform-typeof-symbol": "^7.23.3", + "@babel/plugin-transform-unicode-escapes": "^7.23.3", + "@babel/plugin-transform-unicode-property-regex": "^7.23.3", + "@babel/plugin-transform-unicode-regex": "^7.23.3", + "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.8", + "babel-plugin-polyfill-corejs3": "^0.9.0", + "babel-plugin-polyfill-regenerator": "^0.5.5", "core-js-compat": "^3.31.0", "semver": "^6.3.1" }, @@ -2525,14 +2620,12 @@ } }, "node_modules/@babel/preset-modules": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6.tgz", - "integrity": "sha512-ID2yj6K/4lKfhuU3+EX4UvNbIt7eACFbHmNUjzA+ep+B5971CknnA/9DEWKbRokfbbtblxxxXFJJrH47UEAMVg==", + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", "@babel/types": "^7.4.4", "esutils": "^2.0.2" }, @@ -2547,46 +2640,46 @@ "dev": true }, "node_modules/@babel/runtime": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", - "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz", + "integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==", "dev": true, "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", + "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", + "@babel/code-frame": "^7.24.1", + "@babel/generator": "^7.24.1", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", + "@babel/parser": "^7.24.1", + "@babel/types": "^7.24.0", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -2594,14 +2687,14 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.4.tgz", + "integrity": "sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==", "dev": true, "dependencies": { - "@babel/types": "^7.23.0", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -2609,12 +2702,12 @@ } }, "node_modules/@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, @@ -2633,6 +2726,28 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@ctrl/tinycolor": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", @@ -2650,10 +2765,26 @@ "node": ">=10.0.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.1.tgz", + "integrity": "sha512-m55cpeupQ2DbuRGQMMZDzbv9J9PgVelPjlcmM5kxHnrBdBx6REaEd7LamYV7Dm8N7rCyR/XwU6rVP8ploKtIkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/android-arm": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.17.tgz", - "integrity": "sha512-wHsmJG/dnL3OkpAcwbgoBTTMHVi4Uyou3F5mf58ZtmUyIKfcdA7TROav/6tCzET4A3QW2Q2FC+eFneMU+iyOxg==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.1.tgz", + "integrity": "sha512-4j0+G27/2ZXGWR5okcJi7pQYhmkVgb4D7UKwxcqrjhvp5TKWx3cUjgB1CGj1mfdmJBQ9VnUGgUhign+FPF2Zgw==", "cpu": [ "arm" ], @@ -2667,9 +2798,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.17.tgz", - "integrity": "sha512-9np+YYdNDed5+Jgr1TdWBsozZ85U1Oa3xW0c7TWqH0y2aGghXtZsuT8nYRbzOMcl0bXZXjOGbksoTtVOlWrRZg==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.1.tgz", + "integrity": "sha512-hCnXNF0HM6AjowP+Zou0ZJMWWa1VkD77BXe959zERgGJBBxB+sV+J9f/rcjeg2c5bsukD/n17RKWXGFCO5dD5A==", "cpu": [ "arm64" ], @@ -2683,9 +2814,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.17.tgz", - "integrity": "sha512-O+FeWB/+xya0aLg23hHEM2E3hbfwZzjqumKMSIqcHbNvDa+dza2D0yLuymRBQQnC34CWrsJUXyH2MG5VnLd6uw==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.1.tgz", + "integrity": "sha512-MSfZMBoAsnhpS+2yMFYIQUPs8Z19ajwfuaSZx+tSl09xrHZCjbeXXMsUF/0oq7ojxYEpsSo4c0SfjxOYXRbpaA==", "cpu": [ "x64" ], @@ -2699,9 +2830,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.17.tgz", - "integrity": "sha512-M9uJ9VSB1oli2BE/dJs3zVr9kcCBBsE883prage1NWz6pBS++1oNn/7soPNS3+1DGj0FrkSvnED4Bmlu1VAE9g==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.1.tgz", + "integrity": "sha512-Ylk6rzgMD8klUklGPzS414UQLa5NPXZD5tf8JmQU8GQrj6BrFA/Ic9tb2zRe1kOZyCbGl+e8VMbDRazCEBqPvA==", "cpu": [ "arm64" ], @@ -2715,9 +2846,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.17.tgz", - "integrity": "sha512-XDre+J5YeIJDMfp3n0279DFNrGCXlxOuGsWIkRb1NThMZ0BsrWXoTg23Jer7fEXQ9Ye5QjrvXpxnhzl3bHtk0g==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.1.tgz", + "integrity": "sha512-pFIfj7U2w5sMp52wTY1XVOdoxw+GDwy9FsK3OFz4BpMAjvZVs0dT1VXs8aQm22nhwoIWUmIRaE+4xow8xfIDZA==", "cpu": [ "x64" ], @@ -2731,9 +2862,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.17.tgz", - "integrity": "sha512-cjTzGa3QlNfERa0+ptykyxs5A6FEUQQF0MuilYXYBGdBxD3vxJcKnzDlhDCa1VAJCmAxed6mYhA2KaJIbtiNuQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.1.tgz", + "integrity": "sha512-UyW1WZvHDuM4xDz0jWun4qtQFauNdXjXOtIy7SYdf7pbxSWWVlqhnR/T2TpX6LX5NI62spt0a3ldIIEkPM6RHw==", "cpu": [ "arm64" ], @@ -2747,9 +2878,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.17.tgz", - "integrity": "sha512-sOxEvR8d7V7Kw8QqzxWc7bFfnWnGdaFBut1dRUYtu+EIRXefBc/eIsiUiShnW0hM3FmQ5Zf27suDuHsKgZ5QrA==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.1.tgz", + "integrity": "sha512-itPwCw5C+Jh/c624vcDd9kRCCZVpzpQn8dtwoYIt2TJF3S9xJLiRohnnNrKwREvcZYx0n8sCSbvGH349XkcQeg==", "cpu": [ "x64" ], @@ -2763,9 +2894,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.17.tgz", - "integrity": "sha512-2d3Lw6wkwgSLC2fIvXKoMNGVaeY8qdN0IC3rfuVxJp89CRfA3e3VqWifGDfuakPmp90+ZirmTfye1n4ncjv2lg==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.1.tgz", + "integrity": "sha512-LojC28v3+IhIbfQ+Vu4Ut5n3wKcgTu6POKIHN9Wpt0HnfgUGlBuyDDQR4jWZUZFyYLiz4RBBBmfU6sNfn6RhLw==", "cpu": [ "arm" ], @@ -2779,9 +2910,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.17.tgz", - "integrity": "sha512-c9w3tE7qA3CYWjT+M3BMbwMt+0JYOp3vCMKgVBrCl1nwjAlOMYzEo+gG7QaZ9AtqZFj5MbUc885wuBBmu6aADQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.1.tgz", + "integrity": "sha512-cX8WdlF6Cnvw/DO9/X7XLH2J6CkBnz7Twjpk56cshk9sjYVcuh4sXQBy5bmTwzBjNVZze2yaV1vtcJS04LbN8w==", "cpu": [ "arm64" ], @@ -2795,9 +2926,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.17.tgz", - "integrity": "sha512-1DS9F966pn5pPnqXYz16dQqWIB0dmDfAQZd6jSSpiT9eX1NzKh07J6VKR3AoXXXEk6CqZMojiVDSZi1SlmKVdg==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.1.tgz", + "integrity": "sha512-4H/sQCy1mnnGkUt/xszaLlYJVTz3W9ep52xEefGtd6yXDQbz/5fZE5dFLUgsPdbUOQANcVUa5iO6g3nyy5BJiw==", "cpu": [ "ia32" ], @@ -2811,9 +2942,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.17.tgz", - "integrity": "sha512-EvLsxCk6ZF0fpCB6w6eOI2Fc8KW5N6sHlIovNe8uOFObL2O+Mr0bflPHyHwLT6rwMg9r77WOAWb2FqCQrVnwFg==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.1.tgz", + "integrity": "sha512-c0jgtB+sRHCciVXlyjDcWb2FUuzlGVRwGXgI+3WqKOIuoo8AmZAddzeOHeYLtD+dmtHw3B4Xo9wAUdjlfW5yYA==", "cpu": [ "loong64" ], @@ -2827,9 +2958,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.17.tgz", - "integrity": "sha512-e0bIdHA5p6l+lwqTE36NAW5hHtw2tNRmHlGBygZC14QObsA3bD4C6sXLJjvnDIjSKhW1/0S3eDy+QmX/uZWEYQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.1.tgz", + "integrity": "sha512-TgFyCfIxSujyuqdZKDZ3yTwWiGv+KnlOeXXitCQ+trDODJ+ZtGOzLkSWngynP0HZnTsDyBbPy7GWVXWaEl6lhA==", "cpu": [ "mips64el" ], @@ -2843,9 +2974,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.17.tgz", - "integrity": "sha512-BAAilJ0M5O2uMxHYGjFKn4nJKF6fNCdP1E0o5t5fvMYYzeIqy2JdAP88Az5LHt9qBoUa4tDaRpfWt21ep5/WqQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.1.tgz", + "integrity": "sha512-b+yuD1IUeL+Y93PmFZDZFIElwbmFfIKLKlYI8M6tRyzE6u7oEP7onGk0vZRh8wfVGC2dZoy0EqX1V8qok4qHaw==", "cpu": [ "ppc64" ], @@ -2859,9 +2990,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.17.tgz", - "integrity": "sha512-Wh/HW2MPnC3b8BqRSIme/9Zhab36PPH+3zam5pqGRH4pE+4xTrVLx2+XdGp6fVS3L2x+DrsIcsbMleex8fbE6g==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.1.tgz", + "integrity": "sha512-wpDlpE0oRKZwX+GfomcALcouqjjV8MIX8DyTrxfyCfXxoKQSDm45CZr9fanJ4F6ckD4yDEPT98SrjvLwIqUCgg==", "cpu": [ "riscv64" ], @@ -2875,9 +3006,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.17.tgz", - "integrity": "sha512-j/34jAl3ul3PNcK3pfI0NSlBANduT2UO5kZ7FCaK33XFv3chDhICLY8wJJWIhiQ+YNdQ9dxqQctRg2bvrMlYgg==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.1.tgz", + "integrity": "sha512-5BepC2Au80EohQ2dBpyTquqGCES7++p7G+7lXe1bAIvMdXm4YYcEfZtQrP4gaoZ96Wv1Ute61CEHFU7h4FMueQ==", "cpu": [ "s390x" ], @@ -2891,9 +3022,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.17.tgz", - "integrity": "sha512-QM50vJ/y+8I60qEmFxMoxIx4de03pGo2HwxdBeFd4nMh364X6TIBZ6VQ5UQmPbQWUVWHWws5MmJXlHAXvJEmpQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.1.tgz", + "integrity": "sha512-5gRPk7pKuaIB+tmH+yKd2aQTRpqlf1E4f/mC+tawIm/CGJemZcHZpp2ic8oD83nKgUPMEd0fNanrnFljiruuyA==", "cpu": [ "x64" ], @@ -2907,9 +3038,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.17.tgz", - "integrity": "sha512-/jGlhWR7Sj9JPZHzXyyMZ1RFMkNPjC6QIAan0sDOtIo2TYk3tZn5UDrkE0XgsTQCxWTTOcMPf9p6Rh2hXtl5TQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.1.tgz", + "integrity": "sha512-4fL68JdrLV2nVW2AaWZBv3XEm3Ae3NZn/7qy2KGAt3dexAgSVT+Hc97JKSZnqezgMlv9x6KV0ZkZY7UO5cNLCg==", "cpu": [ "x64" ], @@ -2923,9 +3054,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.17.tgz", - "integrity": "sha512-rSEeYaGgyGGf4qZM2NonMhMOP/5EHp4u9ehFiBrg7stH6BYEEjlkVREuDEcQ0LfIl53OXLxNbfuIj7mr5m29TA==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.1.tgz", + "integrity": "sha512-GhRuXlvRE+twf2ES+8REbeCb/zeikNqwD3+6S5y5/x+DYbAQUNl0HNBs4RQJqrechS4v4MruEr8ZtAin/hK5iw==", "cpu": [ "x64" ], @@ -2939,9 +3070,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.17.tgz", - "integrity": "sha512-Y7ZBbkLqlSgn4+zot4KUNYst0bFoO68tRgI6mY2FIM+b7ZbyNVtNbDP5y8qlu4/knZZ73fgJDlXID+ohY5zt5g==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.1.tgz", + "integrity": "sha512-ZnWEyCM0G1Ex6JtsygvC3KUUrlDXqOihw8RicRuQAzw+c4f1D66YlPNNV3rkjVW90zXVsHwZYWbJh3v+oQFM9Q==", "cpu": [ "x64" ], @@ -2955,9 +3086,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.17.tgz", - "integrity": "sha512-bwPmTJsEQcbZk26oYpc4c/8PvTY3J5/QK8jM19DVlEsAB41M39aWovWoHtNm78sd6ip6prilxeHosPADXtEJFw==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.1.tgz", + "integrity": "sha512-QZ6gXue0vVQY2Oon9WyLFCdSuYbXSoxaZrPuJ4c20j6ICedfsDilNPYfHLlMH7vGfU5DQR0czHLmJvH4Nzis/A==", "cpu": [ "arm64" ], @@ -2971,9 +3102,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.17.tgz", - "integrity": "sha512-H/XaPtPKli2MhW+3CQueo6Ni3Avggi6hP/YvgkEe1aSaxw+AeO8MFjq8DlgfTd9Iz4Yih3QCZI6YLMoyccnPRg==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.1.tgz", + "integrity": "sha512-HzcJa1NcSWTAU0MJIxOho8JftNp9YALui3o+Ny7hCh0v5f90nprly1U3Sj1Ldj/CvKKdvvFsCRvDkpsEMp4DNw==", "cpu": [ "ia32" ], @@ -2987,9 +3118,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.17.tgz", - "integrity": "sha512-fGEb8f2BSA3CW7riJVurug65ACLuQAzKq0SSqkY2b2yHHH0MzDfbLyKIGzHwOI/gkHcxM/leuSW6D5w/LMNitA==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.1.tgz", + "integrity": "sha512-0MBh53o6XtI6ctDnRMeQ+xoCN8kD2qI1rY1KgF/xdWQwoFeKou7puvDfV8/Wv4Ctx2rRpET/gGdz3YlNtNACSA==", "cpu": [ "x64" ], @@ -3027,9 +3158,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -3072,9 +3203,9 @@ "dev": true }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -3117,20 +3248,14 @@ } }, "node_modules/@eslint/js": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz", - "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", - "dev": true - }, "node_modules/@golevelup/ts-jest": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@golevelup/ts-jest/-/ts-jest-0.4.0.tgz", @@ -3138,13 +3263,13 @@ "dev": true }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { @@ -3165,9 +3290,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, "node_modules/@isaacs/cliui": { @@ -3942,14 +4067,14 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -3965,22 +4090,22 @@ } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "dev": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/sourcemap-codec": { @@ -3990,9 +4115,9 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -4000,797 +4125,809 @@ } }, "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", - "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", "dev": true }, + "node_modules/@ljharb/through": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.13.tgz", + "integrity": "sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/@material/animation": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/animation/-/animation-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-leRf+BcZTfC/iSigLXnYgcHAGvFVQveoJT5+2PIRdyPI/bIG7hhciRgacHRsCKC0sGya81dDblLgdkjSUemYLw==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/animation/-/animation-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-1GSJaPKef+7HRuV+HusVZHps64cmZuOItDbt40tjJVaikcaZvwmHlcTxRIqzcRoCdt5ZKHh3NoO7GB9Khg4Jnw==", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@material/auto-init": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/auto-init/-/auto-init-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-uxzDq7q3c0Bu1pAsMugc1Ik9ftQYQqZY+5e2ybNplT8gTImJhNt4M2mMiMHbMANk2l3UgICmUyRSomgPBWCPIA==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/auto-init/-/auto-init-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-t7ZGpRJ3ec0QDUO0nJu/SMgLW7qcuG2KqIsEYD1Ej8qhI2xpdR2ydSDQOkVEitXmKoGol1oq4nYSBjTlB65GqA==", "dependencies": { - "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/banner": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/banner/-/banner-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-SHeVoidCUFVhXANN6MNWxK9SZoTSgpIP8GZB7kAl52BywLxtV+FirTtLXkg/8RUkxZRyRWl7HvQ0ZFZa7QQAyA==", - "dependencies": { - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/button": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/banner/-/banner-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-g9wBUZzYBizyBcBQXTIafnRUUPi7efU9gPJfzeGgkynXiccP/vh5XMmH+PBxl5v+4MlP/d4cZ2NUYoAN7UTqSA==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/button": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/base": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/base/-/base-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-Fc3vGuOf+duGo22HTRP6dHdc+MUe0VqQfWOuKrn/wXKD62m0QQR2TqJd3rRhCumH557T5QUyheW943M3E+IGfg==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/base/-/base-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-I9KQOKXpLfJkP8MqZyr8wZIzdPHrwPjFvGd9zSK91/vPyE4hzHRJc/0njsh9g8Lm9PRYLbifXX+719uTbHxx+A==", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@material/button": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/button/-/button-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-3AQgwrPZCTWHDJvwgKq7Cj+BurQ4wTjDdGL+FEnIGUAjJDskwi1yzx5tW2Wf/NxIi7IoPFyOY3UB41jwMiOrnw==", - "dependencies": { - "@material/density": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/button/-/button-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-BHB7iyHgRVH+JF16+iscR+Qaic+p7LU1FOLgP8KucRlpF9tTwIxQA6mJwGRi5gUtcG+vyCmzVS+hIQ6DqT/7BA==", + "dependencies": { + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/card": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/card/-/card-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-nPlhiWvbLmooTnBmV5gmzB0eLWSgLKsSRBYAbIBmO76Okgz1y+fQNLag+lpm/TDaHVsn5fmQJH8e0zIg0rYsQA==", - "dependencies": { - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/card/-/card-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-kt7y9/IWOtJTr3Z/AoWJT3ZLN7CLlzXhx2udCLP9ootZU2bfGK0lzNwmo80bv/pJfrY9ihQKCtuGTtNxUy+vIw==", + "dependencies": { + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/checkbox": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/checkbox/-/checkbox-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-4tpNnO1L0IppoMF3oeQn8F17t2n0WHB0D7mdJK9rhrujen/fLbekkIC82APB3fdGtLGg3qeNqDqPsJm1YnmrwA==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/density": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/checkbox/-/checkbox-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-rURcrL5O1u6hzWR+dNgiQ/n89vk6tdmdP3mZgnxJx61q4I/k1yijKqNJSLrkXH7Rto3bM5NRKMOlgvMvVd7UMQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/chips": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/chips/-/chips-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-fqHKvE5bSWK0bXVkf57MWxZtytGqYBZvvHIOs4JI9HPHEhaJy4CpSw562BEtbm3yFxxALoQknvPW2KYzvADnmA==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/checkbox": "15.0.0-canary.bc9ae6c9c.0", - "@material/density": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/chips/-/chips-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-AYAivV3GSk/T/nRIpH27sOHFPaSMrE3L0WYbnb5Wa93FgY8a0fbsFYtSH2QmtwnzXveg+B1zGTt7/xIIcynKdQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/checkbox": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "safevalues": "^0.3.4", "tslib": "^2.1.0" } }, "node_modules/@material/circular-progress": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/circular-progress/-/circular-progress-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-Lxe8BGAxQwCQqrLhrYrIP0Uok10h7aYS3RBXP41ph+5GmwJd5zdyE2t93qm2dyThvU6qKuXw9726Dtq/N+wvZQ==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/progress-indicator": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/circular-progress/-/circular-progress-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-DJrqCKb+LuGtjNvKl8XigvyK02y36GRkfhMUYTcJEi3PrOE00bwXtyj7ilhzEVshQiXg6AHGWXtf5UqwNrx3Ow==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/progress-indicator": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/data-table": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/data-table/-/data-table-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-j/7qplT9+sUpfe4pyWhPbl01qJA+OoNAG3VMJruBBR461ZBKyTi7ssKH9yksFGZ8eCEPkOsk/+kDxsiZvRWkeQ==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/checkbox": "15.0.0-canary.bc9ae6c9c.0", - "@material/density": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/icon-button": "15.0.0-canary.bc9ae6c9c.0", - "@material/linear-progress": "15.0.0-canary.bc9ae6c9c.0", - "@material/list": "15.0.0-canary.bc9ae6c9c.0", - "@material/menu": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/select": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/data-table/-/data-table-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-/2WZsuBIq9z9RWYF5Jo6b7P6u0fwit+29/mN7rmAZ6akqUR54nXyNfoSNiyydMkzPlZZsep5KrSHododDhBZbA==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/checkbox": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/icon-button": "15.0.0-canary.7f224ddd4.0", + "@material/linear-progress": "15.0.0-canary.7f224ddd4.0", + "@material/list": "15.0.0-canary.7f224ddd4.0", + "@material/menu": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/select": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/density": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/density/-/density-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-Zt3u07fXrBWLW06Tl5fgvjicxNQMkFdawLyNTzZ5TvbXfVkErILLePwwGaw8LNcvzqJP6ABLA8jiR+sKNoJQCg==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/density/-/density-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-o9EXmGKVpiQ6mHhyV3oDDzc78Ow3E7v8dlaOhgaDSXgmqaE8v5sIlLNa/LKSyUga83/fpGk3QViSGXotpQx0jA==", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@material/dialog": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/dialog/-/dialog-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-o+9a/fmwJ9+gY3Z/uhj/PMVJDq7it1NTWKJn2GwAKdB+fDkT4hb9qEdcxMPyvJJ5ups+XiKZo03+tZrD+38c1w==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/button": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/icon-button": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/dialog/-/dialog-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-u0XpTlv1JqWC/bQ3DavJ1JguofTelLT2wloj59l3/1b60jv42JQ6Am7jU3I8/SIUB1MKaW7dYocXjDWtWJakLA==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/button": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/icon-button": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/dom": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/dom/-/dom-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-ly78R7aoCJtundSUu0UROU+5pQD5Piae0Y1MkN6bs0724azeazX1KeXFeaf06JOXnlr5/41ol+fSUPowjoqnOg==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/dom/-/dom-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-mQ1HT186GPQSkRg5S18i70typ5ZytfjL09R0gJ2Qg5/G+MLCGi7TAjZZSH65tuD/QGOjel4rDdWOTmYbPYV6HA==", "dependencies": { - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/drawer": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/drawer/-/drawer-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-PFL4cEFnt7VTxDsuspFVNhsFDYyumjU0VWfj3PWB7XudsEfQ3lo85D3HCEtTTbRsCainGN8bgYNDNafLBqiigw==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/list": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/drawer/-/drawer-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-qyO0W0KBftfH8dlLR0gVAgv7ZHNvU8ae11Ao6zJif/YxcvK4+gph1z8AO4H410YmC2kZiwpSKyxM1iQCCzbb4g==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/list": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/elevation": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/elevation/-/elevation-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-Ro+Pk8jFuap+T0B0shA3xI1hs2b89dNQ2EIPCNjNMp87emHKAzJfhKb7EZGIwv3+gFLlVaLyIVkb94I89KLsyg==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/elevation/-/elevation-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-tV6s4/pUBECedaI36Yj18KmRCk1vfue/JP/5yYRlFNnLMRVISePbZaKkn/BHXVf+26I3W879+XqIGlDVdmOoMA==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/fab": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/fab/-/fab-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-dvU0KWMRglwJEQwmQtFAmJcAjzg9VFF6Aqj78bJYu/DAIGFJ1VTTTSgoXM/XCm1YyQEZ7kZRvxBO37CH54rSDg==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/fab/-/fab-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-4h76QrzfZTcPdd+awDPZ4Q0YdSqsXQnS540TPtyXUJ/5G99V6VwGpjMPIxAsW0y+pmI9UkLL/srrMaJec+7r4Q==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/feature-targeting": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-wkDjVcoVEYYaJvun28IXdln/foLgPD7n9ZC9TY76GErGCwTq+HWpU6wBAAk+ePmpRFDayw4vI4wBlaWGxLtysQ==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-SAjtxYh6YlKZriU83diDEQ7jNSP2MnxKsER0TvFeyG1vX/DWsUyYDOIJTOEa9K1N+fgJEBkNK8hY55QhQaspew==", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@material/floating-label": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/floating-label/-/floating-label-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-bUWPtXzZITOD/2mkvLkEPO1ngDWmb74y0Kgbz6llHLOQBtycyJIpuoQJ1q2Ez0NM/tFLwPphhAgRqmL3YQ/Kzw==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/floating-label/-/floating-label-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-0KMo5ijjYaEHPiZ2pCVIcbaTS2LycvH9zEhEMKwPPGssBCX7iz5ffYQFk7e5yrQand1r3jnQQgYfHAwtykArnQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/focus-ring": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/focus-ring/-/focus-ring-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-cZHThVose3GvAlJzpJoBI1iqL6d1/Jj9hXrR+r8Mwtb1hBIUEG3hxfsRd4vGREuzROPlf0OgNf/V+YHoSwgR5w==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/focus-ring/-/focus-ring-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-Jmg1nltq4J6S6A10EGMZnvufrvU3YTi+8R8ZD9lkSbun0Fm2TVdICQt/Auyi6An9zP66oQN6c31eqO6KfIPsDg==", "dependencies": { - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0" + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0" } }, "node_modules/@material/form-field": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/form-field/-/form-field-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-+JFXy5X44Gue1CbZZAQ6YejnI203lebYwL0i6k0ylDpWHEOdD5xkF2PyHR28r9/65Ebcbwbff6q7kI1SGoT7MA==", - "dependencies": { - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/form-field/-/form-field-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-fEPWgDQEPJ6WF7hNnIStxucHR9LE4DoDSMqCsGWS2Yu+NLZYLuCEecgR0UqQsl1EQdNRaFh8VH93KuxGd2hiPg==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/icon-button": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/icon-button/-/icon-button-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-1a0MHgyIwOs4RzxrVljsqSizGYFlM1zY2AZaLDsgT4G3kzsplTx8HZQ022GpUCjAygW+WLvg4z1qAhQHvsbqlw==", - "dependencies": { - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/density": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/icon-button/-/icon-button-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-DcK7IL4ICY/DW+48YQZZs9g0U1kRaW0Wb0BxhvppDMYziHo/CTpFdle4gjyuTyRxPOdHQz5a97ru48Z9O4muTw==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/image-list": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/image-list/-/image-list-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-WKWmiYap2iu4QdqmeUSliLlN4O2Ueqa0OuVAYHn/TCzmQ2xmnhZ1pvDLbs6TplpOmlki7vFfe+aSt5SU9gwfOQ==", - "dependencies": { - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/image-list/-/image-list-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-voMjG2p80XbjL1B2lmF65zO5gEgJOVKClLdqh4wbYzYfwY/SR9c8eLvlYG7DLdFaFBl/7gGxD8TvvZ329HUFPw==", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/layout-grid": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/layout-grid/-/layout-grid-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-5GqmT6oTZhUGWIb+CLD0ZNyDyTiJsr/rm9oRIi3+vCujACwxFkON9tzBlZohdtFS16nuzUusthN6Jt9UrJcN6Q==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/layout-grid/-/layout-grid-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-veDABLxMn2RmvfnUO2RUmC1OFfWr4cU+MrxKPoDD2hl3l3eDYv5fxws6r5T1JoSyXoaN+oEZpheS0+M9Ure8Pg==", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@material/line-ripple": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/line-ripple/-/line-ripple-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-8S30WXEuUdgDdBulzUDlPXD6qMzwCX9SxYb5mGDYLwl199cpSGdXHtGgEcCjokvnpLhdZhcT1Dsxeo1g2Evh5Q==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/line-ripple/-/line-ripple-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-f60hVJhIU6I3/17Tqqzch1emUKEcfVVgHVqADbU14JD+oEIz429ZX9ksZ3VChoU3+eejFl+jVdZMLE/LrAuwpg==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/linear-progress": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/linear-progress/-/linear-progress-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-6EJpjrz6aoH2/gXLg9iMe0yF2C42hpQyZoHpmcgTLKeci85ktDvJIjwup8tnk8ULQyFiGiIrhXw2v2RSsiFjvQ==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/progress-indicator": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/linear-progress/-/linear-progress-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-pRDEwPQielDiC9Sc5XhCXrGxP8wWOnAO8sQlMebfBYHYqy5hhiIzibezS8CSaW4MFQFyXmCmpmqWlbqGYRmiyg==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/progress-indicator": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/list": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/list/-/list-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-TQ1ppqiCMQj/P7bGD4edbIIv4goczZUoiUAaPq/feb1dflvrFMzYqJ7tQRRCyBL8nRhJoI2x99tk8Q2RXvlGUQ==", - "dependencies": { - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/density": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/list/-/list-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-Is0NV91sJlXF5pOebYAtWLF4wU2MJDbYqztML/zQNENkQxDOvEXu3nWNb3YScMIYJJXvARO0Liur5K4yPagS1Q==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/menu": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/menu/-/menu-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-IlAh61xzrzxXs38QZlt74UYt8J431zGznSzDtB1Fqs6YFNd11QPKoiRXn1J2Qu/lUxbFV7i8NBKMCKtia0n6/Q==", - "dependencies": { - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/list": "15.0.0-canary.bc9ae6c9c.0", - "@material/menu-surface": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/menu/-/menu-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-D11QU1dXqLbh5X1zKlEhS3QWh0b5BPNXlafc5MXfkdJHhOiieb7LC9hMJhbrHtj24FadJ7evaFW/T2ugJbJNnQ==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/list": "15.0.0-canary.7f224ddd4.0", + "@material/menu-surface": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/menu-surface": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/menu-surface/-/menu-surface-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-dMtSPN+olTWE+08M5qe4ea1IZOhVryYqzK0Gyb2u1G75rSArUxCOB5rr6OC/ST3Mq3RS6zGuYo7srZt4534K9Q==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/menu-surface/-/menu-surface-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-7RZHvw0gbwppaAJ/Oh5SWmfAKJ62aw1IMB3+3MRwsb5PLoV666wInYa+zJfE4i7qBeOn904xqT2Nko5hY0ssrg==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/notched-outline": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/notched-outline/-/notched-outline-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-WuurMg44xexkvLTBTnsO0A+qnzFjpcPdvgWBGstBepYozsvSF9zJGdb1x7Zv1MmqbpYh/Ohnuxtb/Y3jOh6irg==", - "dependencies": { - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/floating-label": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/notched-outline/-/notched-outline-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-Yg2usuKB2DKlKIBISbie9BFsOVuffF71xjbxPbybvqemxqUBd+bD5/t6H1fLE+F8/NCu5JMigho4ewUU+0RCiw==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/floating-label": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/progress-indicator": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/progress-indicator/-/progress-indicator-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-uOnsvqw5F2fkeTnTl4MrYzjI7KCLmmLyZaM0cgLNuLsWVlddQE+SGMl28tENx7DUK3HebWq0FxCP8f25LuDD+w==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/progress-indicator/-/progress-indicator-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-UPbDjE5CqT+SqTs0mNFG6uFEw7wBlgYmh+noSkQ6ty/EURm8lF125dmi4dv4kW0+octonMXqkGtAoZwLIHKf/w==", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@material/radio": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/radio/-/radio-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-ehzOK+U1IxQN+OQjgD2lsnf1t7t7RAwQzeO6Czkiuid29ookYbQynWuLWk7NW8H8ohl7lnmfqTP1xSNkkL/F0g==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/density": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/radio/-/radio-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-wR1X0Sr0KmQLu6+YOFKAI84G3L6psqd7Kys5kfb8WKBM36zxO5HQXC5nJm/Y0rdn22ixzsIz2GBo0MNU4V4k1A==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/ripple": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-JfLW+g3GMVDv4cruQ19+HUxpKVdWCldFlIPw1UYezz2h3WTNDy05S3uP2zUdXzZ01C3dkBFviv4nqZ0GCT16MA==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-JqOsWM1f4aGdotP0rh1vZlPZTg6lZgh39FIYHFMfOwfhR+LAikUJ+37ciqZuewgzXB6iiRO6a8aUH6HR5SJYPg==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/rtl": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-SkKLNLFp5QtG7/JEFg9R92qq4MzTcZ5As6sWbH7rRg6ahTHoJEuqE+pOb9Vrtbj84k5gtX+vCYPvCILtSlr2uw==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-UVf14qAtmPiaaZjuJtmN36HETyoKWmsZM/qn1L5ciR2URb8O035dFWnz4ZWFMmAYBno/L7JiZaCkPurv2ZNrGA==", "dependencies": { - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/segmented-button": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/segmented-button/-/segmented-button-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-YDwkCWP9l5mIZJ7pZJZ2hMDxfBlIGVJ+deNzr8O+Z7/xC5LGXbl4R5aPtUVHygvXAXxpf5096ZD+dSXzYzvWlw==", - "dependencies": { - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/segmented-button/-/segmented-button-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-LCnVRUSAhELTKI/9hSvyvIvQIpPpqF29BV+O9yM4WoNNmNWqTulvuiv7grHZl6Z+kJuxSg4BGbsPxxb9dXozPg==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/select": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/select/-/select-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-unfOWVf7T0sixVG+3k3RTuATfzqvCF6QAzA6J9rlCh/Tq4HuIBNDdV4z19IVu4zwmgWYxY0iSvqWUvdJJYwakQ==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/density": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/floating-label": "15.0.0-canary.bc9ae6c9c.0", - "@material/line-ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/list": "15.0.0-canary.bc9ae6c9c.0", - "@material/menu": "15.0.0-canary.bc9ae6c9c.0", - "@material/menu-surface": "15.0.0-canary.bc9ae6c9c.0", - "@material/notched-outline": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/select/-/select-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-WioZtQEXRpglum0cMSzSqocnhsGRr+ZIhvKb3FlaNrTaK8H3Y4QA7rVjv3emRtrLOOjaT6/RiIaUMTo9AGzWQQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/floating-label": "15.0.0-canary.7f224ddd4.0", + "@material/line-ripple": "15.0.0-canary.7f224ddd4.0", + "@material/list": "15.0.0-canary.7f224ddd4.0", + "@material/menu": "15.0.0-canary.7f224ddd4.0", + "@material/menu-surface": "15.0.0-canary.7f224ddd4.0", + "@material/notched-outline": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/shape": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/shape/-/shape-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-Dsvr771ZKC46ODzoixLdGwlLEQLfxfLrtnRojXABoZf5G3o9KtJU+J+5Ld5aa960OAsCzzANuaub4iR88b1guA==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/shape/-/shape-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-8z8l1W3+cymObunJoRhwFPKZ+FyECfJ4MJykNiaZq7XJFZkV6xNmqAVrrbQj93FtLsECn9g4PjjIomguVn/OEw==", "dependencies": { - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/slider": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/slider/-/slider-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-3AEu+7PwW4DSNLndue47dh2u7ga4hDJRYmuu7wnJCIWJBnLCkp6C92kNc4Rj5iQY2ftJio5aj1gqryluh5tlYg==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/slider/-/slider-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-QU/WSaSWlLKQRqOhJrPgm29wqvvzRusMqwAcrCh1JTrCl+xwJ43q5WLDfjYhubeKtrEEgGu9tekkAiYfMG7EBw==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/snackbar": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/snackbar/-/snackbar-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-TwwQSYxfGK6mc03/rdDamycND6o+1p61WNd7ElZv1F1CLxB4ihRjbCoH7Qo+oVDaP8CTpjeclka+24RLhQq0mA==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/button": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/icon-button": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/snackbar/-/snackbar-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-sm7EbVKddaXpT/aXAYBdPoN0k8yeg9+dprgBUkrdqGzWJAeCkxb4fv2B3He88YiCtvkTz2KLY4CThPQBSEsMFQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/button": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/icon-button": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/switch": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/switch/-/switch-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-OjUjtT0kRz1ASAsOS+dNzwMwvsjmqy5edK57692qmrP6bL4GblFfBDoiNJ6t0AN4OaKcmL5Hy/xNrTdOZW7Qqw==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/density": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/switch/-/switch-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-lEDJfRvkVyyeHWIBfoxYjJVl+WlEAE2kZ/+6OqB1FW0OV8ftTODZGhHRSzjVBA1/p4FPuhAtKtoK9jTpa4AZjA==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", "safevalues": "^0.3.4", "tslib": "^2.1.0" } }, "node_modules/@material/tab": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/tab/-/tab-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-s/L9otAwn/pZwVQZBRQJmPqYeNbjoEbzbjMpDQf/VBG/6dJ+aP03ilIBEkqo8NVnCoChqcdtVCoDNRtbU+yp6w==", - "dependencies": { - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/tab-indicator": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/tab/-/tab-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-E1xGACImyCLurhnizyOTCgOiVezce4HlBFAI6YhJo/AyVwjN2Dtas4ZLQMvvWWqpyhITNkeYdOchwCC1mrz3AQ==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/tab-bar": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/tab-bar/-/tab-bar-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-Xmtq0wJGfu5k+zQeFeNsr4bUKv7L+feCmUp/gsapJ655LQKMXOUQZtSv9ZqWOfrCMy55hoF1CzGFV+oN3tyWWQ==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/density": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/tab": "15.0.0-canary.bc9ae6c9c.0", - "@material/tab-indicator": "15.0.0-canary.bc9ae6c9c.0", - "@material/tab-scroller": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/tab-bar/-/tab-bar-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-p1Asb2NzrcECvAQU3b2SYrpyJGyJLQWR+nXTYzDKE8WOpLIRCXap2audNqD7fvN/A20UJ1J8U01ptrvCkwJ4eA==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/tab": "15.0.0-canary.7f224ddd4.0", + "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0", + "@material/tab-scroller": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/tab-indicator": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/tab-indicator/-/tab-indicator-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-despCJYi1GrDDq7F2hvLQkObHnSLZPPDxnOzU16zJ6FNYvIdszgfzn2HgAZ6pl5hLOexQ8cla6cAqjTDuaJBhQ==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/tab-indicator/-/tab-indicator-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-h9Td3MPqbs33spcPS7ecByRHraYgU4tNCZpZzZXw31RypjKvISDv/PS5wcA4RmWqNGih78T7xg4QIGsZg4Pk4w==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/tab-scroller": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/tab-scroller/-/tab-scroller-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-QWHG/EWxirj4V9u2IHz+OSY9XCWrnNrPnNgEufxAJVUKV/A8ma1DYeFSQqxhX709R8wKGdycJksg0Flkl7Gq7w==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/tab": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/tab-scroller/-/tab-scroller-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-LFeYNjQpdXecwECd8UaqHYbhscDCwhGln5Yh+3ctvcEgvmDPNjhKn/DL3sWprWvG8NAhP6sHMrsGhQFVdCWtTg==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/tab": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/textfield": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/textfield/-/textfield-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-R3qRex9kCaZIAK8DuxPnVC42R0OaW7AB7fsFknDKeTeVQvRcbnV8E+iWSdqTiGdsi6QQHifX8idUrXw+O45zPw==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/density": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/floating-label": "15.0.0-canary.bc9ae6c9c.0", - "@material/line-ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/notched-outline": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/textfield/-/textfield-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-AExmFvgE5nNF0UA4l2cSzPghtxSUQeeoyRjFLHLy+oAaE4eKZFrSy0zEpqPeWPQpEMDZk+6Y+6T3cOFYBeSvsw==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/floating-label": "15.0.0-canary.7f224ddd4.0", + "@material/line-ripple": "15.0.0-canary.7f224ddd4.0", + "@material/notched-outline": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/theme": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/theme/-/theme-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-CpUwXGE0dbhxQ45Hu9r9wbJtO/MAlv5ER4tBHA9tp/K+SU+lDgurBE2touFMg5INmdfVNtdumxb0nPPLaNQcUg==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/theme/-/theme-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-hs45hJoE9yVnoVOcsN1jklyOa51U4lzWsEnQEuJTPOk2+0HqCQ0yv/q0InpSnm2i69fNSyZC60+8HADZGF8ugQ==", "dependencies": { - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/tokens": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/tokens/-/tokens-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-nbEuGj05txWz6ZMUanpM47SaAD7soyjKILR+XwDell9Zg3bGhsnexCNXPEz2fD+YgomS+jM5XmIcaJJHg/H93Q==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/tokens/-/tokens-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-r9TDoicmcT7FhUXC4eYMFnt9TZsz0G8T3wXvkKncLppYvZ517gPyD/1+yhuGfGOxAzxTrM66S/oEc1fFE2q4hw==", "dependencies": { - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0" + "@material/elevation": "15.0.0-canary.7f224ddd4.0" } }, "node_modules/@material/tooltip": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/tooltip/-/tooltip-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-UzuXp0b9NuWuYLYpPguxrjbJnCmT/Cco8CkjI/6JajxaeA3o2XEBbQfRMTq8PTafuBjCHTc0b0mQY7rtxUp1Gg==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/button": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/tooltip/-/tooltip-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-8qNk3pmPLTnam3XYC1sZuplQXW9xLn4Z4MI3D+U17Q7pfNZfoOugGr+d2cLA9yWAEjVJYB0mj8Yu86+udo4N9w==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/button": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "safevalues": "^0.3.4", "tslib": "^2.1.0" } }, "node_modules/@material/top-app-bar": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/top-app-bar/-/top-app-bar-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-vJWjsvqtdSD5+yQ/9vgoBtBSCvPJ5uF/DVssv8Hdhgs1PYaAcODUi77kdi0+sy/TaWyOsTkQixqmwnFS16zesA==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/top-app-bar/-/top-app-bar-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-SARR5/ClYT4CLe9qAXakbr0i0cMY0V3V4pe3ElIJPfL2Z2c4wGR1mTR8m2LxU1MfGKK8aRoUdtfKaxWejp+eNA==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/touch-target": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/touch-target/-/touch-target-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-AqYh9fjt+tv4ZE0C6MeYHblS2H+XwLbDl2mtyrK0DOEnCVQk5/l5ImKDfhrUdFWHvS4a5nBM4AA+sa7KaroLoA==", - "dependencies": { - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/touch-target/-/touch-target-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-BJo/wFKHPYLGsRaIpd7vsQwKr02LtO2e89Psv0on/p0OephlNIgeB9dD9W+bQmaeZsZ6liKSKRl6wJWDiK71PA==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/typography": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/typography/-/typography-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-CKsG1zyv34AKPNyZC8olER2OdPII64iR2SzQjpqh1UUvmIFiMPk23LvQ1OnC5aCB14pOXzmVgvJt31r9eNdZ6Q==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/typography/-/typography-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-kBaZeCGD50iq1DeRRH5OM5Jl7Gdk+/NOfKArkY4ksBZvJiStJ7ACAhpvb8MEGm4s3jvDInQFLsDq3hL+SA79sQ==", "dependencies": { - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@ng-matero/extensions": { - "version": "16.1.2", - "resolved": "https://registry.npmjs.org/@ng-matero/extensions/-/extensions-16.1.2.tgz", - "integrity": "sha512-GKpKwSZTzSPkFrMB99cSXlwxfnZIbjT+SsqOhQqLBKIJddtzj4Pxx7wIqzfb/q3SROWlQpg72kGeUfiR5Z5FCA==", + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/@ng-matero/extensions/-/extensions-17.2.0.tgz", + "integrity": "sha512-/6us4HDFOrk0OIZDTZ8R/yNj5i8E9bpaY3AouGlq6B+yj2LG0Pfzj2yxzEevaLEdcQN0RGNgSZ8A2KfCYmZ6Iw==", "dependencies": { - "@ng-select/ng-select": "^11.0.0", + "@ng-select/ng-select": "^12.0.0", "ngx-color": "^9.0.0", - "photoviewer": "^3.8.0", + "photoviewer": "^3.9.0", "tslib": "^2.4.0" }, "peerDependencies": { - "@angular/animations": ">=16.0.0", - "@angular/cdk": ">=16.0.0", - "@angular/common": ">=16.0.0", - "@angular/core": ">=16.0.0", - "@angular/material": ">=16.0.0" + "@angular/animations": ">=17.1.0", + "@angular/cdk": ">=17.1.0", + "@angular/common": ">=17.1.0", + "@angular/core": ">=17.1.0", + "@angular/material": ">=17.1.0" } }, "node_modules/@ng-select/ng-option-highlight": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/@ng-select/ng-option-highlight/-/ng-option-highlight-11.2.0.tgz", - "integrity": "sha512-kgUs+riATO/lbQIXo8yDIvdJV+ntXX8soT8yRIBxsIov6QaKLhRnn9nebkkfreeS8lxnriu9oXfKZFhikVXPLg==", + "version": "12.0.6", + "resolved": "https://registry.npmjs.org/@ng-select/ng-option-highlight/-/ng-option-highlight-12.0.6.tgz", + "integrity": "sha512-c1ZoQZZSlxfo8M55hWPuifkUPdGn3iobXtkzC9YzXooOWpfW0OxVlowqbb+qYupGqfdlF+FH3TlihxWD9HragA==", "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { - "@angular/common": "^16.0.0", - "@angular/core": "^16.0.0" + "@angular/common": "^17.0.0-rc.0", + "@angular/core": "^17.0.0-rc.0" } }, "node_modules/@ng-select/ng-select": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-11.2.0.tgz", - "integrity": "sha512-lTyw93kFdKGecp9eKmOP0PQSCaAJS8DCt4D60ns055+ixvRSp2fuXAuJUvn1e3gAsvpZor37osmYlOJ4LYwYIA==", + "version": "12.0.7", + "resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-12.0.7.tgz", + "integrity": "sha512-Eht1zlLP0DJxiXcKnq3aY/EJ8odomgU0hM0BJoPY6oX3XFHndtFtdPxlZfhVtQn+FwyDEh7306rRx6digxVssA==", "dependencies": { "tslib": "^2.3.1" }, @@ -4799,24 +4936,24 @@ "npm": ">= 8" }, "peerDependencies": { - "@angular/common": "^16.0.0", - "@angular/core": "^16.0.0", - "@angular/forms": "^16.0.0" + "@angular/common": "^17.0.0-rc.0", + "@angular/core": "^17.0.0-rc.0", + "@angular/forms": "^17.0.0-rc.0" } }, "node_modules/@ngtools/webpack": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-16.2.9.tgz", - "integrity": "sha512-rOclD7FfT4OSwVA0nDnULbJS6TORJ0+sQiuT2ebaNFErYr3LOm6Zut05tnmzFw8q1cePrILbG+xpnbggNr9Pyw==", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-17.3.3.tgz", + "integrity": "sha512-053KMbg1Tb+Mmg4Htsv8yTpI7ABghguoxhwosQXKB0CjO6M0oexuvdaxbRDQ1vd5xYNOW9LcOfxOMPIwyU4BBA==", "dev": true, "engines": { - "node": "^16.14.0 || >=18.10.0", + "node": "^18.13.0 || >=20.9.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, "peerDependencies": { - "@angular/compiler-cli": "^16.0.0", - "typescript": ">=4.9.3 <5.2", + "@angular/compiler-cli": "^17.0.0", + "typescript": ">=5.2 <5.5", "webpack": "^5.54.0" } }, @@ -4855,6 +4992,44 @@ "node": ">= 8" } }, + "node_modules/@npmcli/agent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", + "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/agent/node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/@npmcli/fs": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.0.tgz", @@ -4868,46 +5043,55 @@ } }, "node_modules/@npmcli/git": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.1.0.tgz", - "integrity": "sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.4.tgz", + "integrity": "sha512-nr6/WezNzuYUppzXRaYu/W4aT5rLxdXqEFupbh6e/ovlYFQ8hpu1UUPV3Ir/YTl+74iXl2ZOMlGzudh9ZPUchQ==", "dev": true, "dependencies": { - "@npmcli/promise-spawn": "^6.0.0", - "lru-cache": "^7.4.4", - "npm-pick-manifest": "^8.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", "proc-log": "^3.0.0", "promise-inflight": "^1.0.1", "promise-retry": "^2.0.1", "semver": "^7.3.5", - "which": "^3.0.0" + "which": "^4.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" } }, "node_modules/@npmcli/git/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", "dev": true, "engines": { - "node": ">=12" + "node": "14 || >=16.14" } }, "node_modules/@npmcli/git/node_modules/which": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", - "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, "dependencies": { - "isexe": "^2.0.0" + "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.13.0 || >=18.0.0" } }, "node_modules/@npmcli/installed-package-contents": { @@ -4926,20 +5110,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@npmcli/move-file": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", - "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", - "deprecated": "This functionality has been moved to @npmcli/fs", - "dev": true, - "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/@npmcli/node-gyp": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", @@ -4949,139 +5119,209 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@npmcli/promise-spawn": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz", - "integrity": "sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==", + "node_modules/@npmcli/package-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.0.0.tgz", + "integrity": "sha512-OI2zdYBLhQ7kpNPaJxiflofYIpkNLi+lnGdzqUOfRmCF3r2l1nadcjtCYMJKv/Utm/ZtlffaUuTiAktPHbc17g==", "dev": true, "dependencies": { - "which": "^3.0.0" + "@npmcli/git": "^5.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^7.0.0", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "proc-log": "^3.0.0", + "semver": "^7.5.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@npmcli/promise-spawn/node_modules/which": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", - "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "node_modules/@npmcli/package-json/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "balanced-match": "^1.0.0" } }, - "node_modules/@npmcli/run-script": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-6.0.2.tgz", - "integrity": "sha512-NCcr1uQo1k5U+SYlnIrbAh3cxy+OQT1VtqiAbxdymSlptbzBb62AjH2xXgjNCoP073hoa1CfCAcwoZ8k96C4nA==", + "node_modules/@npmcli/package-json/node_modules/glob": { + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", "dev": true, "dependencies": { - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/promise-spawn": "^6.0.0", - "node-gyp": "^9.0.0", - "read-package-json-fast": "^3.0.0", - "which": "^3.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.6", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@npmcli/run-script/node_modules/which": { + "node_modules/@npmcli/package-json/node_modules/json-parse-even-better-errors": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", - "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz", + "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg==", "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@nrwl/devkit": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-16.5.1.tgz", - "integrity": "sha512-NB+DE/+AFJ7lKH/WBFyatJEhcZGj25F24ncDkwjZ6MzEiSOGOJS0LaV/R+VUsmS5EHTPXYOpn3zHWWAcJhyOmA==", + "node_modules/@npmcli/package-json/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "dependencies": { - "@nx/devkit": "16.5.1" - } + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "node_modules/@nrwl/tao": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/@nrwl/tao/-/tao-16.5.1.tgz", - "integrity": "sha512-x+gi/fKdM6uQNIti9exFlm3V5LBP3Y8vOEziO42HdOigyrXa0S0HD2WMpccmp6PclYKhwEDUjKJ39xh5sdh4Ig==", + "node_modules/@npmcli/promise-spawn": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.1.tgz", + "integrity": "sha512-P4KkF9jX3y+7yFUxgcUdDtLy+t4OlDGuEBLNs57AZsfSfg+uV6MLndqGpnl4831ggaEdXwR50XFoZP4VFtHolg==", "dev": true, "dependencies": { - "nx": "16.5.1" + "which": "^4.0.0" }, - "bin": { - "tao": "index.js" + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@nx/devkit": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-16.5.1.tgz", - "integrity": "sha512-T1acZrVVmJw/sJ4PIGidCBYBiBqlg/jT9e8nIGXLSDS20xcLvfo4zBQf8UZLrmHglnwwpDpOWuVJCp2rYA5aDg==", + "node_modules/@npmcli/promise-spawn/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, "dependencies": { - "@nrwl/devkit": "16.5.1", - "ejs": "^3.1.7", - "ignore": "^5.0.4", - "semver": "7.5.3", - "tmp": "~0.2.1", - "tslib": "^2.3.0" + "isexe": "^3.1.1" }, - "peerDependencies": { - "nx": ">= 15 <= 17" + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" } }, - "node_modules/@nx/devkit/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/@npmcli/redact": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-1.1.0.tgz", + "integrity": "sha512-PfnWuOkQgu7gCbnSsAisaX7hKOdZ4wSAhAzH3/ph5dSGau52kCRrMMGbiSQLwyTZpgldkZ49b0brkOr1AzGBHQ==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-7.0.4.tgz", + "integrity": "sha512-9ApYM/3+rBt9V80aYg6tZfzj3UWdiYyCt7gJUD1VJKvWF5nwKDSICXbYIQbspFTq6TOpbsEtIC0LArB8d9PFmg==", "dev": true, "dependencies": { - "yallist": "^4.0.0" + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "node-gyp": "^10.0.0", + "which": "^4.0.0" }, "engines": { - "node": ">=10" + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" } }, - "node_modules/@nx/devkit/node_modules/semver": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", - "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "node_modules/@npmcli/run-script/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, "dependencies": { - "lru-cache": "^6.0.0" + "isexe": "^3.1.1" }, "bin": { - "semver": "bin/semver.js" + "node-which": "bin/which.js" }, "engines": { - "node": ">=10" + "node": "^16.13.0 || >=18.0.0" } }, - "node_modules/@nx/devkit/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "node_modules/@nrwl/devkit": { + "version": "18.2.2", + "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-18.2.2.tgz", + "integrity": "sha512-6RBr1aMzrpY0kk9L9buqT9H7Nv8+QujJPo4ASr6jp/5d5gPBsebeTn6qSvv1xJSB0GhB1ACOeq1nVkbwRQoQCw==", + "dev": true, + "dependencies": { + "@nx/devkit": "18.2.2" + } + }, + "node_modules/@nrwl/tao": { + "version": "18.2.2", + "resolved": "https://registry.npmjs.org/@nrwl/tao/-/tao-18.2.2.tgz", + "integrity": "sha512-tXjAbbw8Ir3cY/PQVHiC7q10jsU43r5kkEVwa2vzd1rfPtPFvj9WtgwISd+GstuppYtsbNi+UgTNmHX8dRKPYQ==", + "dev": true, + "dependencies": { + "nx": "18.2.2", + "tslib": "^2.3.0" + }, + "bin": { + "tao": "index.js" + } + }, + "node_modules/@nx/devkit": { + "version": "18.2.2", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-18.2.2.tgz", + "integrity": "sha512-Yz/uLYfy2QLeeCJecgKXuT4z0eGx/yBw3VxkgD0zSvpSIg8p1OGSK/rUQ47n/FibsLRdXa1Me5uE57rNt/FKvA==", + "dev": true, + "dependencies": { + "@nrwl/devkit": "18.2.2", + "ejs": "^3.1.7", + "enquirer": "~2.3.6", + "ignore": "^5.0.4", + "semver": "^7.5.3", + "tmp": "~0.2.1", + "tslib": "^2.3.0", + "yargs-parser": "21.1.1" + }, + "peerDependencies": { + "nx": ">= 16 <= 18" + } }, "node_modules/@nx/nx-darwin-arm64": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-16.5.1.tgz", - "integrity": "sha512-q98TFI4B/9N9PmKUr1jcbtD4yAFs1HfYd9jUXXTQOlfO9SbDjnrYJgZ4Fp9rMNfrBhgIQ4x1qx0AukZccKmH9Q==", + "version": "18.2.2", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-18.2.2.tgz", + "integrity": "sha512-mZ5X2rmtzmEGnt5ddpKlyQDGRd1wh0HSJtWvjruj6fYLNNpoosnXefI0PQLZUw13hf8OpJNo8J6xKfjIViSa8g==", "cpu": [ "arm64" ], @@ -5095,9 +5335,9 @@ } }, "node_modules/@nx/nx-darwin-x64": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-16.5.1.tgz", - "integrity": "sha512-j9HmL1l8k7EVJ3eOM5y8COF93gqrydpxCDoz23ZEtsY+JHY77VAiRQsmqBgEx9GGA2dXi9VEdS67B0+1vKariw==", + "version": "18.2.2", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-18.2.2.tgz", + "integrity": "sha512-FeYvbr0OOIdn9xvuNZlLHQKwdAPN9KcWnmIysJTQZeanvUf6tifkhBUU1cXDduAkdut5iibnnA91JhcEj4x9yg==", "cpu": [ "x64" ], @@ -5111,9 +5351,9 @@ } }, "node_modules/@nx/nx-freebsd-x64": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-16.5.1.tgz", - "integrity": "sha512-CXSPT01aVS869tvCCF2tZ7LnCa8l41wJ3mTVtWBkjmRde68E5Up093hklRMyXb3kfiDYlfIKWGwrV4r0eH6x1A==", + "version": "18.2.2", + "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-18.2.2.tgz", + "integrity": "sha512-Owt/5jT8IG5I6eRbs8en+bHvi2St+k1Z1S1CLArlnfTzkTgVGz/R39HD4OouEVnr2dQPkfc7ms6+XkhlYx5NLg==", "cpu": [ "x64" ], @@ -5127,9 +5367,9 @@ } }, "node_modules/@nx/nx-linux-arm-gnueabihf": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-16.5.1.tgz", - "integrity": "sha512-BhrumqJSZCWFfLFUKl4CAUwR0Y0G2H5EfFVGKivVecEQbb+INAek1aa6c89evg2/OvetQYsJ+51QknskwqvLsA==", + "version": "18.2.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-18.2.2.tgz", + "integrity": "sha512-6D6ZC4EdBjAE0QgLYXuk7AC5r/LM+XUUOa5tFAV6fsAKn+GjVFsmP8dl/HEHfg+vx619+o+IrVrOA+h6ztmNJA==", "cpu": [ "arm" ], @@ -5143,9 +5383,9 @@ } }, "node_modules/@nx/nx-linux-arm64-gnu": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-16.5.1.tgz", - "integrity": "sha512-x7MsSG0W+X43WVv7JhiSq2eKvH2suNKdlUHEG09Yt0vm3z0bhtym1UCMUg3IUAK7jy9hhLeDaFVFkC6zo+H/XQ==", + "version": "18.2.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-18.2.2.tgz", + "integrity": "sha512-RHZ9nPZ4ivv9p+djO9WqoilMhjlR8/rj7P4sog5OpeRE5EWc65Rb7SFwjek1IovS2gbbK+3P2y8Q4G7lyvbe5w==", "cpu": [ "arm64" ], @@ -5159,9 +5399,9 @@ } }, "node_modules/@nx/nx-linux-arm64-musl": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-16.5.1.tgz", - "integrity": "sha512-J+/v/mFjOm74I0PNtH5Ka+fDd+/dWbKhpcZ2R1/6b9agzZk+Ff/SrwJcSYFXXWKbPX+uQ4RcJoytT06Zs3s0ow==", + "version": "18.2.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-18.2.2.tgz", + "integrity": "sha512-WginA4UHdrRhK40pDV8sv3Izho5aOzWf3iC8WVXi8r850mVbOE88JaWnO7TJ7zNWgiM32/OZeVyaYQ/Wv8pYjw==", "cpu": [ "arm64" ], @@ -5175,9 +5415,9 @@ } }, "node_modules/@nx/nx-linux-x64-gnu": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-16.5.1.tgz", - "integrity": "sha512-igooWJ5YxQ94Zft7IqgL+Lw0qHaY15Btw4gfK756g/YTYLZEt4tTvR1y6RnK/wdpE3sa68bFTLVBNCGTyiTiDQ==", + "version": "18.2.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-18.2.2.tgz", + "integrity": "sha512-Fekq6TWZAN7T1Yi+IVAPQ3wUmsmtvu3WyvXiVBjVKh8C1H/PKPcNi+4kaG9Ys1BhBZhqiEfTgc44RF9xLM9IAQ==", "cpu": [ "x64" ], @@ -5191,9 +5431,9 @@ } }, "node_modules/@nx/nx-linux-x64-musl": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-16.5.1.tgz", - "integrity": "sha512-zF/exnPqFYbrLAduGhTmZ7zNEyADid2bzNQiIjJkh8Y6NpDwrQIwVIyvIxqynsjMrIs51kBH+8TUjKjj2Jgf5A==", + "version": "18.2.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-18.2.2.tgz", + "integrity": "sha512-3Uk7x2/giczRCva7RsWd/KjgeYH9kOQFiqzE4heMrjBEuJQfACDlasjIrTRv9bwLrZ6otkBVeX/zmE9kBo3tOA==", "cpu": [ "x64" ], @@ -5207,9 +5447,9 @@ } }, "node_modules/@nx/nx-win32-arm64-msvc": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-16.5.1.tgz", - "integrity": "sha512-qtqiLS9Y9TYyAbbpq58kRoOroko4ZXg5oWVqIWFHoxc5bGPweQSJCROEqd1AOl2ZDC6BxfuVHfhDDop1kK05WA==", + "version": "18.2.2", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-18.2.2.tgz", + "integrity": "sha512-y0d79+FYtSEI96KGAjIUrD7/xybAp7aSjqqesM0WP2+DIJBYkdjK6maTKxkB5gb3FBJyhfNYr4A1NqDnvbPtvA==", "cpu": [ "arm64" ], @@ -5223,9 +5463,9 @@ } }, "node_modules/@nx/nx-win32-x64-msvc": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-16.5.1.tgz", - "integrity": "sha512-kUJBLakK7iyA9WfsGGQBVennA4jwf5XIgm0lu35oMOphtZIluvzItMt0EYBmylEROpmpEIhHq0P6J9FA+WH0Rg==", + "version": "18.2.2", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-18.2.2.tgz", + "integrity": "sha512-17b7hh8VMGWHq0yQDxevLUM0K4ZoNUah3oYVbYe46tp1w7D4u44vDkOOE2SpV2E/alllcDES1etcVsYQSMTGig==", "cpu": [ "x64" ], @@ -5238,24 +5478,6 @@ "node": ">= 10" } }, - "node_modules/@parcel/watcher": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.0.4.tgz", - "integrity": "sha512-cTDi+FUDBIUOBKEtj+nhiJ71AZVlkAsQFuGQTun5tV9mwQBQgZvhCzG+URPQc8myeN32yRVZEfVAPCs1RW+Jvg==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "node-addon-api": "^3.2.1", - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -5266,152 +5488,287 @@ "node": ">=14" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.0.tgz", + "integrity": "sha512-jwXtxYbRt1V+CdQSy6Z+uZti7JF5irRKF8hlKfEnF/xJpcNGuuiZMBvuoYM+x9sr9iWGnzrlM0+9hvQ1kgkf1w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.0.tgz", + "integrity": "sha512-fI9nduZhCccjzlsA/OuAwtFGWocxA4gqXGTLvOyiF8d+8o0fZUeSztixkYjcGq1fGZY3Tkq4yRvHPFxU+jdZ9Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.0.tgz", + "integrity": "sha512-BcnSPRM76/cD2gQC+rQNGBN6GStBs2pl/FpweW8JYuz5J/IEa0Fr4AtrPv766DB/6b2MZ/AfSIOSGw3nEIP8SA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.0.tgz", + "integrity": "sha512-LDyFB9GRolGN7XI6955aFeI3wCdCUszFWumWU0deHA8VpR3nWRrjG6GtGjBrQxQKFevnUTHKCfPR4IvrW3kCgQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.0.tgz", + "integrity": "sha512-ygrGVhQP47mRh0AAD0zl6QqCbNsf0eTo+vgwkY6LunBcg0f2Jv365GXlDUECIyoXp1kKwL5WW6rsO429DBY/bA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.0.tgz", + "integrity": "sha512-x+uJ6MAYRlHGe9wi4HQjxpaKHPM3d3JjqqCkeC5gpnnI6OWovLdXTpfa8trjxPLnWKyBsSi5kne+146GAxFt4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.0.tgz", + "integrity": "sha512-nrRw8ZTQKg6+Lttwqo6a2VxR9tOroa2m91XbdQ2sUUzHoedXlsyvY1fN4xWdqz8PKmf4orDwejxXHjh7YBGUCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.0.tgz", + "integrity": "sha512-xV0d5jDb4aFu84XKr+lcUJ9y3qpIWhttO3Qev97z8DKLXR62LC3cXT/bMZXrjLF9X+P5oSmJTzAhqwUbY96PnA==", + "cpu": [ + "ppc64le" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.0.tgz", + "integrity": "sha512-SDDhBQwZX6LPRoPYjAZWyL27LbcBo7WdBFWJi5PI9RPCzU8ijzkQn7tt8NXiXRiFMJCVpkuMkBf4OxSxVMizAw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.0.tgz", + "integrity": "sha512-RxB/qez8zIDshNJDufYlTT0ZTVut5eCpAZ3bdXDU9yTxBzui3KhbGjROK2OYTTor7alM7XBhssgoO3CZ0XD3qA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.0.tgz", + "integrity": "sha512-C6y6z2eCNCfhZxT9u+jAM2Fup89ZjiG5pIzZIDycs1IwESviLxwkQcFRGLjnDrP+PT+v5i4YFvlcfAs+LnreXg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.0.tgz", + "integrity": "sha512-i0QwbHYfnOMYsBEyjxcwGu5SMIi9sImDVjDg087hpzXqhBSosxkE7gyIYFHgfFl4mr7RrXksIBZ4DoLoP4FhJg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.0.tgz", + "integrity": "sha512-Fq52EYb0riNHLBTAcL0cun+rRwyZ10S9vKzhGKKgeD+XbwunszSY0rVMco5KbOsTlwovP2rTOkiII/fQ4ih/zQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.0.tgz", + "integrity": "sha512-e/PBHxPdJ00O9p5Ui43+vixSgVf4NlLsmV6QneGERJ3lnjIua/kim6PRFe3iDueT1rQcgSkYP8ZBBXa/h4iPvw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.0.tgz", + "integrity": "sha512-aGg7iToJjdklmxlUlJh/PaPNa4PmqHfyRMLunbL3eaMO0gp656+q1zOKkpJ/CVe9CryJv6tAN1HDoR8cNGzkag==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@schematics/angular": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-16.2.9.tgz", - "integrity": "sha512-uiU2YbZRVHgk1N1DDsek/5CKhfpZ8myJYNJk8eHV5LswnXOP3aqvH23VhneaAgOYwK5fISC7eMG0pLVKMvFfZQ==", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-17.3.3.tgz", + "integrity": "sha512-kNlyjIKTBhfi8Jab3MCkxNRbbpErbzdu0lZNSL8Nidmqs6Tk23Dc1bZe4t/gPNOCkCvQlwYa6X88SjC/ntyVng==", "dev": true, "dependencies": { - "@angular-devkit/core": "16.2.9", - "@angular-devkit/schematics": "16.2.9", - "jsonc-parser": "3.2.0" + "@angular-devkit/core": "17.3.3", + "@angular-devkit/schematics": "17.3.3", + "jsonc-parser": "3.2.1" }, "engines": { - "node": "^16.14.0 || >=18.10.0", + "node": "^18.13.0 || >=20.9.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, "node_modules/@sigstore/bundle": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-1.1.0.tgz", - "integrity": "sha512-PFutXEy0SmQxYI4texPw3dd2KewuNqv7OuK1ZFtY2fM754yhvG2KdgwIhRnoEE2uHdtdGNQ8s0lb94dW9sELog==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.1.tgz", + "integrity": "sha512-eqV17lO3EIFqCWK3969Rz+J8MYrRZKw9IBHpSo6DEcEX2c+uzDFOgHE9f2MnyDpfs48LFO4hXmk9KhQ74JzU1g==", "dev": true, "dependencies": { - "@sigstore/protobuf-specs": "^0.2.0" + "@sigstore/protobuf-specs": "^0.3.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-1.1.0.tgz", + "integrity": "sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/@sigstore/protobuf-specs": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.2.1.tgz", - "integrity": "sha512-XTWVxnWJu+c1oCshMLwnKvz8ZQJJDVOlciMfgpJBQbThVjKTCG8dwyhgLngBD2KN0ap9F/gOV8rFDEx8uh7R2A==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.1.tgz", + "integrity": "sha512-aIL8Z9NsMr3C64jyQzE0XlkEyBLpgEJJFDHLVVStkFV5Q3Il/r/YtY6NJWKQ4cy4AE7spP1IX5Jq7VCAxHHMfQ==", "dev": true, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/@sigstore/sign": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-1.0.0.tgz", - "integrity": "sha512-INxFVNQteLtcfGmcoldzV6Je0sbbfh9I16DM4yJPw3j5+TFP8X6uIiA18mvpEa9yyeycAKgPmOA3X9hVdVTPUA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.0.tgz", + "integrity": "sha512-tsAyV6FC3R3pHmKS880IXcDJuiFJiKITO1jxR1qbplcsBkZLBmjrEw5GbC7ikD6f5RU1hr7WnmxB/2kKc1qUWQ==", "dev": true, "dependencies": { - "@sigstore/bundle": "^1.1.0", - "@sigstore/protobuf-specs": "^0.2.0", - "make-fetch-happen": "^11.0.1" + "@sigstore/bundle": "^2.3.0", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.1", + "make-fetch-happen": "^13.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@sigstore/sign/node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "node_modules/@sigstore/tuf": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.2.tgz", + "integrity": "sha512-mwbY1VrEGU4CO55t+Kl6I7WZzIl+ysSzEYdA1Nv/FTrl2bkeaPXo5PnWZAVfcY2zSdhOpsUTJW67/M2zHXGn5w==", "dev": true, + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.0", + "tuf-js": "^2.2.0" + }, "engines": { - "node": ">= 10" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@sigstore/sign/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "node_modules/@sigstore/verify": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.1.1.tgz", + "integrity": "sha512-BNANJms49rw9Q5J+fJjrDqOQSzjXDcOq/pgKDaVdDoIvQwqIfaoUriy+fQfh8sBX04hr4bkkrwu3EbhQqoQH7A==", "dev": true, "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@sigstore/sign/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@sigstore/sign/node_modules/make-fetch-happen": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", - "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", - "dev": true, - "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^17.0.0", - "http-cache-semantics": "^4.1.1", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^5.0.0", - "minipass-fetch": "^3.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^10.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@sigstore/sign/node_modules/minipass-fetch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz", - "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==", - "dev": true, - "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "node_modules/@sigstore/sign/node_modules/minipass-fetch/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/@sigstore/tuf": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-1.0.3.tgz", - "integrity": "sha512-2bRovzs0nJZFlCN3rXirE4gwxCn97JNjMmwpecqlbgV9WcxX7WRuIrgzx/X7Ib7MYRbyUTpBYE0s2x6AmZXnlg==", - "dev": true, - "dependencies": { - "@sigstore/protobuf-specs": "^0.2.0", - "tuf-js": "^1.1.7" + "@sigstore/bundle": "^2.2.0", + "@sigstore/core": "^1.1.0", + "@sigstore/protobuf-specs": "^0.3.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/@sinclair/typebox": { @@ -5439,34 +5796,58 @@ } }, "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", "dev": true, "engines": { - "node": ">= 6" + "node": ">= 10" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, "node_modules/@tufjs/canonical-json": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-1.0.0.tgz", - "integrity": "sha512-QTnf++uxunWvG2z3UFNzAoQPHxnSXOwtaI3iJ+AohhV+5vONuArPjJE7aPXPVXfXJsqrVbZBu9b81AJoSd09IQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", "dev": true, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/@tufjs/models": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-1.0.4.tgz", - "integrity": "sha512-qaGV9ltJP0EO25YfFUPhxRVK0evXFIAGicsVXuRim4Ed9cjPxYhNnNJ49SFmbeLgtxpslIkX317IgpfcHPVj/A==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.0.tgz", + "integrity": "sha512-c8nj8BaOExmZKO2DXhDfegyhSGcG9E/mPN3U13L+/PsoWm1uaGiHHjxqSHQiasDBQwDA3aHuw9+9spYAP1qvvg==", "dev": true, "dependencies": { - "@tufjs/canonical-json": "1.0.0", - "minimatch": "^9.0.0" + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/@tufjs/models/node_modules/brace-expansion": { @@ -5479,9 +5860,9 @@ } }, "node_modules/@tufjs/models/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -5535,9 +5916,9 @@ } }, "node_modules/@types/body-parser": { - "version": "1.19.4", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.4.tgz", - "integrity": "sha512-N7UDG0/xiPQa2D/XrVJXjkWbpqHCd2sBaB32ggRF2l83RhPfamgKGF8gwwqyksS95qUS5ZYF9aF+lLPRlwI2UA==", + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", "dev": true, "dependencies": { "@types/connect": "*", @@ -5545,27 +5926,27 @@ } }, "node_modules/@types/bonjour": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.12.tgz", - "integrity": "sha512-ky0kWSqXVxSqgqJvPIkgFkcn4C8MnRog308Ou8xBBIVo39OmUFy+jqNe0nPwLCDFxUpmT9EvT91YzOJgkDRcFg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/connect": { - "version": "3.4.37", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.37.tgz", - "integrity": "sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==", + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.2.tgz", - "integrity": "sha512-gX2j9x+NzSh4zOhnRPSdPPmTepS4DfxES0AvIFv3jGv5QyeAJf6u6dY5/BAoAJU9Qq1uTvwOku8SSC2GnCRl6Q==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", "dev": true, "dependencies": { "@types/express-serve-static-core": "*", @@ -5573,9 +5954,9 @@ } }, "node_modules/@types/eslint": { - "version": "8.44.6", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.6.tgz", - "integrity": "sha512-P6bY56TVmX8y9J87jHNgQh43h6VVU+6H7oN7hgvivV81K2XY8qJZ5vqPy/HdUoVIelii2kChYVzQanlswPWVFw==", + "version": "8.56.7", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.7.tgz", + "integrity": "sha512-SjDvI/x3zsZnOkYZ3lCt9lOZWZLB2jIlNKz+LBgCtDurK0JZcwucxYHn1w2BJkD34dgX9Tjnak0txtq4WTggEA==", "dev": true, "dependencies": { "@types/estree": "*", @@ -5583,9 +5964,9 @@ } }, "node_modules/@types/eslint-scope": { - "version": "3.7.6", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.6.tgz", - "integrity": "sha512-zfM4ipmxVKWdxtDaJ3MP3pBurDXOCoyjvlpE3u6Qzrmw4BPbfm4/ambIeTk/r/J0iq/+2/xp0Fmt+gFvXJY2PQ==", + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "dev": true, "dependencies": { "@types/eslint": "*", @@ -5593,15 +5974,15 @@ } }, "node_modules/@types/estree": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.4.tgz", - "integrity": "sha512-2JwWnHK9H+wUZNorf2Zr6ves96WHoWDJIftkcxPKsS7Djta6Zu519LarhRNljPXkpsZR2ZMwNCPeW7omW07BJw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, "node_modules/@types/express": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.20.tgz", - "integrity": "sha512-rOaqlkgEvOW495xErXMsmyX3WKBInbhG5eqojXYi3cGUaLoRDlXa5d52fkfWZT963AZ3v2eZ4MbKE6WpDAGVsw==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "dev": true, "dependencies": { "@types/body-parser": "*", @@ -5611,9 +5992,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.39", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.39.tgz", - "integrity": "sha512-BiEUfAiGCOllomsRAZOiMFP7LAnrifHpt56pc4Z7l9K6ACyN06Ns1JLMBxwkfLOjJRlSf06NwWsT7yzfpaVpyQ==", + "version": "4.17.43", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz", + "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==", "dev": true, "dependencies": { "@types/node": "*", @@ -5632,15 +6013,15 @@ } }, "node_modules/@types/http-errors": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.3.tgz", - "integrity": "sha512-pP0P/9BnCj1OVvQR2lF41EkDG/lWWnDyA203b/4Fmi2eTyORnBtcDoKDwjWQthELrBvWkMOrvSOnZ8OVlW6tXA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", "dev": true }, "node_modules/@types/http-proxy": { - "version": "1.17.13", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.13.tgz", - "integrity": "sha512-GkhdWcMNiR5QSQRYnJ+/oXzu0+7JJEPC8vkWXK351BkhjraZF+1W13CUYARUvX9+NqIU2n6YHA4iwywsc/M6Sw==", + "version": "1.17.14", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", + "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", "dev": true, "dependencies": { "@types/node": "*" @@ -5671,9 +6052,9 @@ } }, "node_modules/@types/jest": { - "version": "29.5.7", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.7.tgz", - "integrity": "sha512-HLyetab6KVPSiF+7pFcUyMeLsx25LDNDemw9mGsJBkai/oouwrjTycocSDYopMEwFhN2Y4s9oPyOCZNofgSt2g==", + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", "dev": true, "dependencies": { "expect": "^29.0.0", @@ -5692,15 +6073,15 @@ } }, "node_modules/@types/json-schema": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", - "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "node_modules/@types/mime": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.4.tgz", - "integrity": "sha512-1Gjee59G25MrQGk8bsNvC6fxNiRgUlGn2wlhGf95a59DrprnnHk80FIMMFG9XHMdrfsuA119ht06QPDXA1Z7tw==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "dev": true }, "node_modules/@types/node": { @@ -5713,24 +6094,24 @@ } }, "node_modules/@types/node-forge": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.8.tgz", - "integrity": "sha512-vGXshY9vim9CJjrpcS5raqSjEfKlJcWy2HNdgUasR66fAnVEYarrf1ULV4nfvpC1nZq/moA9qyqBcu83x+Jlrg==", + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/qs": { - "version": "6.9.9", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.9.tgz", - "integrity": "sha512-wYLxw35euwqGvTDx6zfY1vokBFnsK0HNrzc6xNHchxfO2hpuRg74GbkEW7e3sSmPvj0TjCDT1VCa6OtHXnubsg==", + "version": "6.9.14", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz", + "integrity": "sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==", "dev": true }, "node_modules/@types/range-parser": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.6.tgz", - "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "dev": true }, "node_modules/@types/retry": { @@ -5740,15 +6121,15 @@ "dev": true }, "node_modules/@types/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "dev": true }, "node_modules/@types/send": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.3.tgz", - "integrity": "sha512-/7fKxvKUoETxjFUsuFlPB9YndePpxxRAOfGC/yJdc9kTjTeP5kRCTzfnE8kPUKCeyiyIZu0YQ76s50hCedI1ug==", + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", "dev": true, "dependencies": { "@types/mime": "^1", @@ -5756,29 +6137,29 @@ } }, "node_modules/@types/serve-index": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.3.tgz", - "integrity": "sha512-4KG+yMEuvDPRrYq5fyVm/I2uqAJSAwZK9VSa+Zf+zUq9/oxSSvy3kkIqyL+jjStv6UCVi8/Aho0NHtB1Fwosrg==", + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", "dev": true, "dependencies": { "@types/express": "*" } }, "node_modules/@types/serve-static": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.4.tgz", - "integrity": "sha512-aqqNfs1XTF0HDrFdlY//+SGUxmdSUbjeRXb5iaZc3x0/vMbYmdw9qvOgHWOyyLFxSSRnUuP5+724zBgfw8/WAw==", + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", "dev": true, "dependencies": { "@types/http-errors": "*", - "@types/mime": "*", - "@types/node": "*" + "@types/node": "*", + "@types/send": "*" } }, "node_modules/@types/sockjs": { - "version": "0.3.35", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.35.tgz", - "integrity": "sha512-tIF57KB+ZvOBpAQwSaACfEu7htponHXaFzP7RfKYgsOS0NoYnn+9+jzp7bbq4fWerizI3dTB4NfAZoyeQKWJLw==", + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", "dev": true, "dependencies": { "@types/node": "*" @@ -5791,15 +6172,15 @@ "dev": true }, "node_modules/@types/tough-cookie": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.4.tgz", - "integrity": "sha512-95Sfz4nvMAb0Nl9DTxN3j64adfwfbBPEYq14VN7zT5J5O2M9V6iZMIIQU1U+pJyl9agHYHNCqhCXgyEtIRRa5A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", "dev": true }, "node_modules/@types/ws": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.8.tgz", - "integrity": "sha512-flUksGIQCnJd6sZ1l5dqCEG/ksaoAg/eUwiLAGTJQcfgvZJKF++Ta4bJA6A5aPSJmsr+xlseHn4KLgVlNnvPTg==", + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", "dev": true, "dependencies": { "@types/node": "*" @@ -5821,16 +6202,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.9.1.tgz", - "integrity": "sha512-w0tiiRc9I4S5XSXXrMHOWgHgxbrBn1Ro+PmiYhSg2ZVdxrAJtQgzU5o2m1BfP6UOn7Vxcc6152vFjQfmZR4xEg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.5.0.tgz", + "integrity": "sha512-HpqNTH8Du34nLxbKgVMGljZMG0rJd2O9ecvr2QLYp+7512ty1j42KnsFwspPXg1Vh8an9YImf6CokUBltisZFQ==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.9.1", - "@typescript-eslint/type-utils": "6.9.1", - "@typescript-eslint/utils": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1", + "@typescript-eslint/scope-manager": "7.5.0", + "@typescript-eslint/type-utils": "7.5.0", + "@typescript-eslint/utils": "7.5.0", + "@typescript-eslint/visitor-keys": "7.5.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -5839,15 +6220,15 @@ "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -5856,25 +6237,25 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.9.1.tgz", - "integrity": "sha512-eh2oHaUKCK58qIeYp19F5V5TbpM52680sB4zNSz29VBQPTWIlE/hCj5P5B1AChxECe/fmZlspAWFuRniep1Skg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.5.0.tgz", + "integrity": "sha512-A021Rj33+G8mx2Dqh0nMO9GyjjIBK3MqgVgZ2qlKf6CJy51wY/lkkFqq3TqqnH34XyAHUkq27IjlUkWlQRpLHw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.9.1", - "@typescript-eslint/utils": "6.9.1", + "@typescript-eslint/typescript-estree": "7.5.0", + "@typescript-eslint/utils": "7.5.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -5883,51 +6264,51 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.9.1.tgz", - "integrity": "sha512-L1T0A5nFdQrMVunpZgzqPL6y2wVreSyHhKGZryS6jrEN7bD9NplVAyMryUhXsQ4TWLnZmxc2ekar/lSGIlprCA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.5.0.tgz", + "integrity": "sha512-3vZl9u0R+/FLQcpy2EHyRGNqAS/ofJ3Ji8aebilfJe+fobK8+LbIFmrHciLVDxjDoONmufDcnVSF38KwMEOjzw==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.9.1", - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/typescript-estree": "6.9.1", + "@typescript-eslint/scope-manager": "7.5.0", + "@typescript-eslint/types": "7.5.0", + "@typescript-eslint/typescript-estree": "7.5.0", "semver": "^7.5.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.9.1.tgz", - "integrity": "sha512-C7AK2wn43GSaCUZ9do6Ksgi2g3mwFkMO3Cis96kzmgudoVaKyt62yNzJOktP0HDLb/iO2O0n2lBOzJgr6Q/cyg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.5.0.tgz", + "integrity": "sha512-cj+XGhNujfD2/wzR1tabNsidnYRaFfEkcULdcIyVBYcXjBvBKOes+mpMBP7hMpOyk+gBcfXsrg4NBGAStQyxjQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.9.1", - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/typescript-estree": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1", + "@typescript-eslint/scope-manager": "7.5.0", + "@typescript-eslint/types": "7.5.0", + "@typescript-eslint/typescript-estree": "7.5.0", + "@typescript-eslint/visitor-keys": "7.5.0", "debug": "^4.3.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -5936,16 +6317,16 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.9.1.tgz", - "integrity": "sha512-38IxvKB6NAne3g/+MyXMs2Cda/Sz+CEpmm+KLGEM8hx/CvnSRuw51i8ukfwB/B/sESdeTGet1NH1Wj7I0YXswg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.5.0.tgz", + "integrity": "sha512-Z1r7uJY0MDeUlql9XJ6kRVgk/sP11sr3HKXn268HZyqL7i4cEfrdFuSSY/0tUqT37l5zT0tJOsuDP16kio85iA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1" + "@typescript-eslint/types": "7.5.0", + "@typescript-eslint/visitor-keys": "7.5.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -5953,25 +6334,25 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.2.0.tgz", + "integrity": "sha512-xHi51adBHo9O9330J8GQYQwrKBqbIPJGZZVQTHHmy200hvkLZFWJIFtAG/7IYTWUyun6DE6w5InDReePJYJlJA==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", + "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/utils": "7.2.0", "debug": "^4.3.4", - "tsutils": "^3.21.0" + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -5980,12 +6361,12 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz", + "integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==", "dev": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -5993,21 +6374,22 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz", + "integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -6020,29 +6402,53 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz", + "integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" + "@typescript-eslint/types": "7.2.0", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/type-utils/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/types": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.9.1.tgz", - "integrity": "sha512-BUGslGOb14zUHOUmDB2FfT6SI1CcZEJYfF3qFwBeUrU6srJfzANonwRYHDpLBuzbq3HaoF2XL2hcr01c8f8OaQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.5.0.tgz", + "integrity": "sha512-tv5B4IHeAdhR7uS4+bf8Ov3k793VEVHd45viRRkehIUZxm0WF82VPiLgHzA/Xl4TGPg1ZD49vfxBKFPecD5/mg==", "dev": true, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -6050,21 +6456,22 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.9.1.tgz", - "integrity": "sha512-U+mUylTHfcqeO7mLWVQ5W/tMLXqVpRv61wm9ZtfE5egz7gtnmqVIw9ryh0mgIlkKk9rZLY3UHygsBSdB9/ftyw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.5.0.tgz", + "integrity": "sha512-YklQQfe0Rv2PZEueLTUffiQGKQneiIEKKnfIqPIOxgM9lKSZFCjT5Ad4VqRKj/U4+kQE3fa8YQpskViL7WjdPQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1", + "@typescript-eslint/types": "7.5.0", + "@typescript-eslint/visitor-keys": "7.5.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", + "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -6076,43 +6483,66 @@ } } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.2.0.tgz", + "integrity": "sha512-YfHpnMAGb1Eekpm3XRK8hcMwGLGsnT6L+7b2XyRv6ouDuJU1tZir1GS2i0+VXRatMwSI1/UfcyPe53ADkU+IuA==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/typescript-estree": "7.2.0", + "semver": "^7.5.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz", + "integrity": "sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -6120,12 +6550,12 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz", + "integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==", "dev": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -6133,21 +6563,22 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz", + "integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -6160,55 +6591,57 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz", + "integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" + "@typescript-eslint/types": "7.2.0", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "node_modules/@typescript-eslint/utils/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" + "balanced-match": "^1.0.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "node_modules/@typescript-eslint/utils/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, "engines": { - "node": ">=4.0" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.9.1.tgz", - "integrity": "sha512-MUaPUe/QRLEffARsmNfmpghuQkW436DvESW+h+M52w0coICHRfD6Np9/K6PdACwnrq1HmuLl+cSPZaJmeVPkSw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.5.0.tgz", + "integrity": "sha512-mcuHM/QircmA6O7fy6nn2w/3ditQkj+SgtOc8DW3uQ10Yfj42amm2i+6F2K4YAOPNNTmE6iM1ynM6lrSwdendA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/types": "7.5.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -6222,21 +6655,21 @@ "dev": true }, "node_modules/@vitejs/plugin-basic-ssl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.0.1.tgz", - "integrity": "sha512-pcub+YbFtFhaGRTo1832FQHQSHvMrlb43974e2eS8EKleR3p1cDdkJFPci1UhwkEf1J9Bz+wKBSzqpKp7nNj2A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.1.0.tgz", + "integrity": "sha512-wO4Dk/rm8u7RNhOf95ZzcEmC9rYOncYgvq4z3duaJrCgjN8BxAnDVyndanfcJZ0O6XZzHz6Q0hTimxTg8Y9g/A==", "dev": true, "engines": { "node": ">=14.6.0" }, "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0" + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" } }, "node_modules/@webassemblyjs/ast": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", - "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", "dev": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.11.6", @@ -6256,9 +6689,9 @@ "dev": true }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", - "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", "dev": true }, "node_modules/@webassemblyjs/helper-numbers": { @@ -6279,15 +6712,15 @@ "dev": true }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", - "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" + "@webassemblyjs/wasm-gen": "1.12.1" } }, "node_modules/@webassemblyjs/ieee754": { @@ -6315,28 +6748,28 @@ "dev": true }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", - "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", - "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/ieee754": "1.11.6", "@webassemblyjs/leb128": "1.11.6", @@ -6344,24 +6777,24 @@ } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", - "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", - "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-api-error": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/ieee754": "1.11.6", @@ -6370,134 +6803,41 @@ } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", - "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@xtuc/long": "4.2.2" } }, - "node_modules/@wessberg/ts-evaluator": { - "version": "0.0.27", - "resolved": "https://registry.npmjs.org/@wessberg/ts-evaluator/-/ts-evaluator-0.0.27.tgz", - "integrity": "sha512-7gOpVm3yYojUp/Yn7F4ZybJRxyqfMNf0LXK5KJiawbPfL0XTsJV+0mgrEDjOIR6Bi0OYk2Cyg4tjFu1r8MCZaA==", - "deprecated": "this package has been renamed to ts-evaluator. Please install ts-evaluator instead", + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true + }, + "node_modules/@yarnpkg/parsers": { + "version": "3.0.0-rc.46", + "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.0-rc.46.tgz", + "integrity": "sha512-aiATs7pSutzda/rq8fnuPwTglyVwjM22bNnK2ZgjrpAjQHSSl3lztd2f9evst1W/qnC58DRz7T7QndUDumAR4Q==", "dev": true, "dependencies": { - "chalk": "^4.1.0", - "jsdom": "^16.4.0", - "object-path": "^0.11.5", - "tslib": "^2.0.3" - }, - "engines": { - "node": ">=10.1.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/wessberg/ts-evaluator?sponsor=1" - }, - "peerDependencies": { - "typescript": ">=3.2.x || >= 4.x" - } - }, - "node_modules/@wessberg/ts-evaluator/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@wessberg/ts-evaluator/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@wessberg/ts-evaluator/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@wessberg/ts-evaluator/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@wessberg/ts-evaluator/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@wessberg/ts-evaluator/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "node_modules/@yarnpkg/lockfile": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", - "dev": true - }, - "node_modules/@yarnpkg/parsers": { - "version": "3.0.0-rc.46", - "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.0-rc.46.tgz", - "integrity": "sha512-aiATs7pSutzda/rq8fnuPwTglyVwjM22bNnK2ZgjrpAjQHSSl3lztd2f9evst1W/qnC58DRz7T7QndUDumAR4Q==", - "dev": true, - "dependencies": { - "js-yaml": "^3.10.0", - "tslib": "^2.4.0" + "js-yaml": "^3.10.0", + "tslib": "^2.4.0" }, "engines": { "node": ">=14.15.0" @@ -6525,13 +6865,17 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", "dev": true }, "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, "node_modules/accepts": { "version": "1.3.8", @@ -6547,9 +6891,9 @@ } }, "node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -6559,25 +6903,13 @@ } }, "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", "dev": true, "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "node_modules/acorn-globals/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" } }, "node_modules/acorn-import-assertions": { @@ -6599,9 +6931,9 @@ } }, "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", "dev": true, "engines": { "node": ">=0.4.0" @@ -6635,27 +6967,15 @@ } }, "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/agentkeepalive": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", - "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "dependencies": { - "humanize-ms": "^1.2.1" + "debug": "^4.3.4" }, "engines": { - "node": ">= 8.0.0" + "node": ">= 14" } }, "node_modules/aggregate-error": { @@ -6798,25 +7118,12 @@ "node": ">= 8" } }, - "node_modules/aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true }, - "node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", - "dev": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -6836,9 +7143,9 @@ } }, "node_modules/array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "dev": true }, "node_modules/array-union": { @@ -6851,9 +7158,9 @@ } }, "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", "dev": true }, "node_modules/asynckit": { @@ -6863,9 +7170,9 @@ "dev": true }, "node_modules/autoprefixer": { - "version": "10.4.14", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", - "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==", + "version": "10.4.18", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.18.tgz", + "integrity": "sha512-1DKbDfsr6KUElM6wg+0zRNkB/Q7WcKYAaK+pzXn+Xqmszm/5Xa9coeNdtP88Vi+dPzZnMjhge8GIV49ZQkDa+g==", "dev": true, "funding": [ { @@ -6875,12 +7182,16 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "browserslist": "^4.21.5", - "caniuse-lite": "^1.0.30001464", - "fraction.js": "^4.2.0", + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001591", + "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", "postcss-value-parser": "^4.2.0" @@ -6896,34 +7207,20 @@ } }, "node_modules/axios": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz", - "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", "dev": true, "dependencies": { - "follow-redirects": "^1.15.0", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, - "node_modules/axios/node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/axobject-query": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", - "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.0.0.tgz", + "integrity": "sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==", "dev": true, "dependencies": { "dequal": "^2.0.3" @@ -7069,13 +7366,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz", - "integrity": "sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q==", + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz", + "integrity": "sha512-rpIuu//y5OX6jVU+a5BCn1R5RSZYWAl2Nar76iwaOdycqb6JPxediskWFMMl7stfwNJR4b7eiQvh5fB5TEQJTQ==", "dev": true, "dependencies": { "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.4.3", + "@babel/helper-define-polyfill-provider": "^0.6.1", "semver": "^6.3.1" }, "peerDependencies": { @@ -7092,25 +7389,57 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.8.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.6.tgz", - "integrity": "sha512-leDIc4l4tUgU7str5BWLS2h8q2N4Nf6lGZP6UrNDxdtfF2g69eJ5L0H7S8A5Ln/arfFAfHor5InAdZuIOwZdgQ==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.9.0.tgz", + "integrity": "sha512-7nZPG1uzK2Ymhy/NbaOWTg3uibM2BmGASS4vHS4szRZAIR8R6GwA/xAujpdrXU5iyklrimWnLWU+BLF9suPTqg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.5.0", + "core-js-compat": "^3.34.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz", + "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==", "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.3", - "core-js-compat": "^3.33.1" + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.3.tgz", - "integrity": "sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.5.tgz", + "integrity": "sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.5.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator/node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz", + "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==", "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.3" + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -7217,13 +7546,13 @@ } }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dev": true, "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -7231,7 +7560,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -7265,13 +7594,11 @@ "dev": true }, "node_modules/bonjour-service": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", - "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", + "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", "dev": true, "dependencies": { - "array-flatten": "^2.1.2", - "dns-equal": "^1.0.0", "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" } @@ -7304,16 +7631,10 @@ "node": ">=8" } }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, "node_modules/browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "dev": true, "funding": [ { @@ -7330,9 +7651,9 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, "bin": { @@ -7411,17 +7732,17 @@ } }, "node_modules/cacache": { - "version": "17.1.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.1.4.tgz", - "integrity": "sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==", + "version": "18.0.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.2.tgz", + "integrity": "sha512-r3NU8h/P+4lVUHfeRw1dtgQYar3DZMm4/cm2bZgOvrFC/su7budSOeqh52VJIC4U4iG1WWwV6vRW0znqBvxNuw==", "dev": true, "dependencies": { "@npmcli/fs": "^3.1.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", - "lru-cache": "^7.7.1", + "lru-cache": "^10.0.1", "minipass": "^7.0.3", - "minipass-collect": "^1.0.2", + "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^4.0.0", @@ -7430,7 +7751,7 @@ "unique-filename": "^3.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/cacache/node_modules/brace-expansion": { @@ -7443,16 +7764,16 @@ } }, "node_modules/cacache/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", + "jackspeak": "^2.3.6", "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" }, "bin": { "glob": "dist/esm/bin.mjs" @@ -7465,18 +7786,18 @@ } }, "node_modules/cacache/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", "dev": true, "engines": { - "node": ">=12" + "node": "14 || >=16.14" } }, "node_modules/cacache/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -7488,24 +7809,20 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/cacache/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dev": true, "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7530,9 +7847,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001559", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001559.tgz", - "integrity": "sha512-cPiMKZgqgkg5LY3/ntGeLFUpi6tzddBNS58A4tnTgQw1zON7u2sZMU7SzOeVH4tj20++9ggL+V6FDOFMTaFFYA==", + "version": "1.0.30001605", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001605.tgz", + "integrity": "sha512-nXwGlFWo34uliI9z3n6Qc0wZaf7zaZWA1CPZ169La5mV3I/gem7bst0vr5XQH5TJXZIMfDeZyOrZnSlVzKxxHQ==", "dev": true, "funding": [ { @@ -7678,23 +7995,26 @@ } }, "node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", "dev": true, "engines": { - "node": ">= 10" + "node": ">= 12" } }, "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "dependencies": { "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", + "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/clone": { @@ -7751,15 +8071,6 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, - "bin": { - "color-support": "bin.js" - } - }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", @@ -7856,12 +8167,6 @@ "node": ">=0.8" } }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "dev": true - }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -7890,9 +8195,9 @@ "dev": true }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "dev": true, "engines": { "node": ">= 0.6" @@ -7984,12 +8289,12 @@ } }, "node_modules/core-js-compat": { - "version": "3.33.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.2.tgz", - "integrity": "sha512-axfo+wxFVxnqf8RvxTzoAlzW4gRoacrHeoFlc9n0x50+7BEyZL/Rt3hicaED1/CEd7I6tPCPVUYcJwCMO5XUYw==", + "version": "3.36.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz", + "integrity": "sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==", "dev": true, "dependencies": { - "browserslist": "^4.22.1" + "browserslist": "^4.23.0" }, "funding": { "type": "opencollective", @@ -8003,15 +8308,15 @@ "dev": true }, "node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "dependencies": { + "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" + "parse-json": "^5.2.0" }, "engines": { "node": ">=14" @@ -8137,10 +8442,16 @@ "node": ">=8" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "node_modules/critters": { - "version": "0.0.20", - "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.20.tgz", - "integrity": "sha512-CImNRorKOl5d8TWcnAz5n5izQ6HFsvz29k327/ELy6UFcmbiZNOsinaKvzv16WZR0P6etfSWYzE47C4/56B3Uw==", + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.22.tgz", + "integrity": "sha512-NU7DEcQZM2Dy8XTKFHxtdnIM/drE312j2T4PCVaSUcS0oBeyT/NImpRw/Ap0zOr/1SE7SgPK9tGPg1WK/sVakw==", "dev": true, "dependencies": { "chalk": "^4.1.0", @@ -8149,7 +8460,7 @@ "domhandler": "^5.0.2", "htmlparser2": "^8.0.2", "postcss": "^8.4.23", - "pretty-bytes": "^5.3.0" + "postcss-media-query-parser": "^0.2.3" } }, "node_modules/critters/node_modules/ansi-styles": { @@ -8237,19 +8548,19 @@ } }, "node_modules/css-loader": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz", - "integrity": "sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.10.0.tgz", + "integrity": "sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==", "dev": true, "dependencies": { "icss-utils": "^5.1.0", - "postcss": "^8.4.21", + "postcss": "^8.4.33", "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.3", - "postcss-modules-scope": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.4", + "postcss-modules-scope": "^3.1.1", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", - "semver": "^7.3.8" + "semver": "^7.5.4" }, "engines": { "node": ">= 12.13.0" @@ -8259,11 +8570,20 @@ "url": "https://opencollective.com/webpack" }, "peerDependencies": { + "@rspack/core": "0.x || 1.x", "webpack": "^5.0.0" - } - }, - "node_modules/css-select": { - "version": "5.1.0", + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-select": { + "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", "dev": true, @@ -8303,9 +8623,9 @@ } }, "node_modules/cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", "dev": true }, "node_modules/cssstyle": { @@ -8327,17 +8647,17 @@ "dev": true }, "node_modules/data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", "dev": true, "dependencies": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/debug": { @@ -8417,17 +8737,20 @@ } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-lazy-prop": { @@ -8448,12 +8771,6 @@ "node": ">=0.4.0" } }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -8497,6 +8814,15 @@ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "dev": true }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -8518,12 +8844,6 @@ "node": ">=8" } }, - "node_modules/dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", - "dev": true - }, "node_modules/dns-packet": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", @@ -8575,24 +8895,16 @@ ] }, "node_modules/domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "deprecated": "Use your platform's native DOMException instead", "dev": true, "dependencies": { - "webidl-conversions": "^5.0.0" + "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/domexception/node_modules/webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true, - "engines": { - "node": ">=8" + "node": ">=12" } }, "node_modules/domhandler": { @@ -8611,9 +8923,9 @@ } }, "node_modules/domq.js": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/domq.js/-/domq.js-0.6.8.tgz", - "integrity": "sha512-G9kHE7/dUr9VhtczUEPVQpQQvH9N599a9usD+Q+eHwVbAptVKGSodKdEnVBgW3PPeOAnL0rFaZlasGjFlbaBwA==" + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/domq.js/-/domq.js-0.7.1.tgz", + "integrity": "sha512-SQjubdV+FsTa8pRPrhygohvraS6NS5XdH2wK0V8nzg3vQW5+h3+LDn5XCE5RYejbtZE4aAUz2UTxE1B9oSl55w==" }, "node_modules/domutils": { "version": "3.1.0", @@ -8630,12 +8942,24 @@ } }, "node_modules/dotenv": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.2.tgz", + "integrity": "sha512-HTlk5nmhkm8F6JcdXvHIzaorzCoziNQT9mGxLPVXW8wJF1TiGSL60ZGB4gHWabHOaMmWmhvk2/lPHfnBiT78AQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/dotenv-expand": { "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", - "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", + "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", "dev": true, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/duplexer": { @@ -8672,9 +8996,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.572", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.572.tgz", - "integrity": "sha512-RlFobl4D3ieetbnR+2EpxdzFl9h0RAJkPK3pfiwMug2nhBin2ZCsGIAJWdpNniLz43sgXam/CgipOmvTA+rUiA==", + "version": "1.4.726", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.726.tgz", + "integrity": "sha512-xtjfBXn53RORwkbyKvDfTajtnTp0OJoPOIBzXvkNbb7+YYvCHJflba3L7Txyx/6Fov3ov2bGPr/n5MTixmPhdQ==", "dev": true }, "node_modules/emittery": { @@ -8746,9 +9070,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz", + "integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -8819,18 +9143,40 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz", - "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.0.tgz", + "integrity": "sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw==", "dev": true }, "node_modules/esbuild": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.17.tgz", - "integrity": "sha512-1GJtYnUxsJreHYA0Y+iQz2UEykonY66HNWOb0yXYZi9/kNrORUEHVg87eQsCtqh59PEJ5YVZJO98JHznMJSWjg==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.1.tgz", + "integrity": "sha512-OJwEgrpWm/PCMsLVWXKqvcjme3bHNpOgN7Tb6cQnR5n0TPbQx1/Xrn7rqM+wn17bYeT6MGB5sn1Bh5YiGi70nA==", "dev": true, "hasInstallScript": true, + "optional": true, "bin": { "esbuild": "bin/esbuild" }, @@ -8838,34 +9184,35 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.18.17", - "@esbuild/android-arm64": "0.18.17", - "@esbuild/android-x64": "0.18.17", - "@esbuild/darwin-arm64": "0.18.17", - "@esbuild/darwin-x64": "0.18.17", - "@esbuild/freebsd-arm64": "0.18.17", - "@esbuild/freebsd-x64": "0.18.17", - "@esbuild/linux-arm": "0.18.17", - "@esbuild/linux-arm64": "0.18.17", - "@esbuild/linux-ia32": "0.18.17", - "@esbuild/linux-loong64": "0.18.17", - "@esbuild/linux-mips64el": "0.18.17", - "@esbuild/linux-ppc64": "0.18.17", - "@esbuild/linux-riscv64": "0.18.17", - "@esbuild/linux-s390x": "0.18.17", - "@esbuild/linux-x64": "0.18.17", - "@esbuild/netbsd-x64": "0.18.17", - "@esbuild/openbsd-x64": "0.18.17", - "@esbuild/sunos-x64": "0.18.17", - "@esbuild/win32-arm64": "0.18.17", - "@esbuild/win32-ia32": "0.18.17", - "@esbuild/win32-x64": "0.18.17" + "@esbuild/aix-ppc64": "0.20.1", + "@esbuild/android-arm": "0.20.1", + "@esbuild/android-arm64": "0.20.1", + "@esbuild/android-x64": "0.20.1", + "@esbuild/darwin-arm64": "0.20.1", + "@esbuild/darwin-x64": "0.20.1", + "@esbuild/freebsd-arm64": "0.20.1", + "@esbuild/freebsd-x64": "0.20.1", + "@esbuild/linux-arm": "0.20.1", + "@esbuild/linux-arm64": "0.20.1", + "@esbuild/linux-ia32": "0.20.1", + "@esbuild/linux-loong64": "0.20.1", + "@esbuild/linux-mips64el": "0.20.1", + "@esbuild/linux-ppc64": "0.20.1", + "@esbuild/linux-riscv64": "0.20.1", + "@esbuild/linux-s390x": "0.20.1", + "@esbuild/linux-x64": "0.20.1", + "@esbuild/netbsd-x64": "0.20.1", + "@esbuild/openbsd-x64": "0.20.1", + "@esbuild/sunos-x64": "0.20.1", + "@esbuild/win32-arm64": "0.20.1", + "@esbuild/win32-ia32": "0.20.1", + "@esbuild/win32-x64": "0.20.1" } }, "node_modules/esbuild-wasm": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.18.17.tgz", - "integrity": "sha512-9OHGcuRzy+I8ziF9FzjfKLWAPbvi0e/metACVg9k6bK+SI4FFxeV6PcZsz8RIVaMD4YNehw+qj6UMR3+qj/EuQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.20.1.tgz", + "integrity": "sha512-6v/WJubRsjxBbQdz6izgvx7LsVFvVaGmSdwrFHmEzoVgfXL89hkKPoQHsnVI2ngOkcBUQT9kmAM1hVL1k/Av4A==", "dev": true, "bin": { "esbuild": "bin/esbuild" @@ -8930,16 +9277,16 @@ } }, "node_modules/eslint": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", - "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.52.0", - "@humanwhocodes/config-array": "^0.11.13", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -9300,12 +9647,6 @@ "node": ">= 0.6" } }, - "node_modules/eventemitter-asyncresource": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/eventemitter-asyncresource/-/eventemitter-asyncresource-1.0.0.tgz", - "integrity": "sha512-39F7TBIV0G7gTelxwbEqnwhp90eqCPON1k0NwNfwhgKn4Co4ybUbj2pECcXT0B3ztRKZ7Pw1JujUUgmQJHcVAQ==", - "dev": true - }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -9376,17 +9717,17 @@ "dev": true }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dev": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -9417,12 +9758,6 @@ "node": ">= 0.10.0" } }, - "node_modules/express/node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true - }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -9471,9 +9806,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -9689,9 +10024,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "dev": true, "funding": [ { @@ -9737,9 +10072,9 @@ } }, "node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dev": true, "dependencies": { "asynckit": "^0.4.0", @@ -9788,9 +10123,9 @@ "dev": true }, "node_modules/fs-extra": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", - "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", "dev": true, "dependencies": { "graceful-fs": "^4.2.0", @@ -9813,15 +10148,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/fs-monkey": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz", @@ -9857,25 +10183,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", - "dev": true, - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -9895,16 +10202,20 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -10021,18 +10332,6 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, - "node_modules/guess-parser": { - "version": "0.4.22", - "resolved": "https://registry.npmjs.org/guess-parser/-/guess-parser-0.4.22.tgz", - "integrity": "sha512-KcUWZ5ACGaBM69SbqwVIuWGoSAgD+9iJnchR9j/IarVI1jHVeXv+bUXBIMeqVMSKt3zrn0Dgf9UpcOEpPBLbSg==", - "dev": true, - "dependencies": { - "@wessberg/ts-evaluator": "0.0.27" - }, - "peerDependencies": { - "typescript": ">=3.7.5" - } - }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -10049,21 +10348,21 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "dev": true, "engines": { "node": ">= 0.4" @@ -10084,12 +10383,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "dev": true - }, "node_modules/hasown": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", @@ -10102,42 +10395,25 @@ "node": ">= 0.4" } }, - "node_modules/hdr-histogram-js": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz", - "integrity": "sha512-Hkn78wwzWHNCp2uarhzQ2SGFLU3JY8SBDDd3TAABK4fc30wm+MuPOrg5QVFVfkKOQd6Bfz3ukJEI+q9sXEkK1g==", - "dev": true, - "dependencies": { - "@assemblyscript/loader": "^0.10.1", - "base64-js": "^1.2.0", - "pako": "^1.0.3" - } - }, - "node_modules/hdr-histogram-percentiles-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hdr-histogram-percentiles-obj/-/hdr-histogram-percentiles-obj-3.0.0.tgz", - "integrity": "sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==", - "dev": true - }, "node_modules/hosted-git-info": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", - "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.1.tgz", + "integrity": "sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA==", "dev": true, "dependencies": { - "lru-cache": "^7.5.1" + "lru-cache": "^10.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", "dev": true, "engines": { - "node": ">=12" + "node": "14 || >=16.14" } }, "node_modules/hpack.js": { @@ -10183,21 +10459,21 @@ } }, "node_modules/html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", "dev": true, "dependencies": { - "whatwg-encoding": "^1.0.5" + "whatwg-encoding": "^2.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/html-entities": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", - "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", "dev": true, "funding": [ { @@ -10284,12 +10560,12 @@ } }, "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "dev": true, "dependencies": { - "@tootallnate/once": "1", + "@tootallnate/once": "2", "agent-base": "6", "debug": "4" }, @@ -10297,6 +10573,18 @@ "node": ">= 6" } }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/http-proxy-middleware": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", @@ -10322,16 +10610,16 @@ } }, "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", "dev": true, "dependencies": { - "agent-base": "6", + "agent-base": "^7.0.2", "debug": "4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/human-signals": { @@ -10343,15 +10631,6 @@ "node": ">=10.17.0" } }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dev": true, - "dependencies": { - "ms": "^2.0.0" - } - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -10397,18 +10676,18 @@ ] }, "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, "engines": { "node": ">= 4" } }, "node_modules/ignore-walk": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.3.tgz", - "integrity": "sha512-C7FfFoTA+bI10qfeydT8aZbvr91vAEU+2W5BZUlzPec47oNb07SsOfwYrtxuvOYdUApPP/Qlh4DtAO51Ekk2QA==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.4.tgz", + "integrity": "sha512-t7sv42WkwFkyKbivUCglsQW5YWMskWtbEf4MNKX5u/CCWHKSPzN4FtBQGsQZgCLbxOzpVlcbWVK5KB3auIOjSw==", "dev": true, "dependencies": { "minimatch": "^9.0.0" @@ -10427,9 +10706,9 @@ } }, "node_modules/ignore-walk/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -10455,9 +10734,9 @@ } }, "node_modules/immutable": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", - "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", + "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==", "dev": true }, "node_modules/import-fresh": { @@ -10534,12 +10813,6 @@ "node": ">=8" } }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -10557,38 +10830,38 @@ "dev": true }, "node_modules/ini": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", - "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", + "integrity": "sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==", "dev": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/inquirer": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz", - "integrity": "sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==", + "version": "9.2.15", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.15.tgz", + "integrity": "sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg==", "dev": true, "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", + "@ljharb/through": "^2.3.12", + "ansi-escapes": "^4.3.2", + "chalk": "^5.3.0", "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", + "cli-width": "^4.1.0", + "external-editor": "^3.1.0", + "figures": "^3.2.0", "lodash": "^4.17.21", - "mute-stream": "0.0.8", + "mute-stream": "1.0.0", "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^7.0.0" + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=18" } }, "node_modules/inquirer/node_modules/ansi-styles": { @@ -10607,16 +10880,12 @@ } }, "node_modules/inquirer/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" @@ -10640,31 +10909,37 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/inquirer/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/inquirer/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, "engines": { "node": ">=8" } }, - "node_modules/inquirer/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" }, "engines": { - "node": ">=8" + "node": ">= 12" } }, - "node_modules/ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "dev": true }, "node_modules/ipaddr.js": { @@ -11666,296 +11941,30 @@ } } }, - "node_modules/jest-environment-jsdom/node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/jest-environment-jsdom/node_modules/acorn-globals": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", - "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, "dependencies": { - "acorn": "^8.1.0", - "acorn-walk": "^8.0.2" + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-environment-jsdom/node_modules/acorn-walk": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", - "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/jest-environment-jsdom/node_modules/cssom": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", - "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", - "dev": true - }, - "node_modules/jest-environment-jsdom/node_modules/data-urls": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", - "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", - "dev": true, - "dependencies": { - "abab": "^2.0.6", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/jest-environment-jsdom/node_modules/domexception": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", - "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", - "dev": true, - "dependencies": { - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/jest-environment-jsdom/node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/jest-environment-jsdom/node_modules/html-encoding-sniffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", - "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", - "dev": true, - "dependencies": { - "whatwg-encoding": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/jest-environment-jsdom/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/jest-environment-jsdom/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jest-environment-jsdom/node_modules/jsdom": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", - "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", - "dev": true, - "dependencies": { - "abab": "^2.0.6", - "acorn": "^8.8.1", - "acorn-globals": "^7.0.0", - "cssom": "^0.5.0", - "cssstyle": "^2.3.0", - "data-urls": "^3.0.2", - "decimal.js": "^10.4.2", - "domexception": "^4.0.0", - "escodegen": "^2.0.0", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^3.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.1", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.2", - "parse5": "^7.1.1", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.2", - "w3c-xmlserializer": "^4.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0", - "ws": "^8.11.0", - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jest-environment-jsdom/node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=v12.22.7" - } - }, - "node_modules/jest-environment-jsdom/node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "dev": true, - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/jest-environment-jsdom/node_modules/w3c-xmlserializer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", - "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", - "dev": true, - "dependencies": { - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/jest-environment-jsdom/node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/jest-environment-jsdom/node_modules/whatwg-encoding": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", - "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", - "dev": true, - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/jest-environment-jsdom/node_modules/whatwg-mimetype": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", - "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/jest-environment-jsdom/node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "dev": true, - "dependencies": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/jest-environment-jsdom/node_modules/ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/jest-environment-jsdom/node_modules/xml-name-validator": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", - "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-haste-map": { @@ -12203,13 +12212,13 @@ } }, "node_modules/jest-preset-angular": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/jest-preset-angular/-/jest-preset-angular-13.1.1.tgz", - "integrity": "sha512-X8i7icKt9U5uhj7YKqdEZm7ZZPvNFRxfBnU+9SALdIkHYJhwtlJ5/MUk9wo4f3lX2smOkIl9LPJUu1APO+11Jg==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/jest-preset-angular/-/jest-preset-angular-14.0.3.tgz", + "integrity": "sha512-usgBL7x0rXMnMSx8iEFeOozj50W6fp+YAmQcQBUdAXhN+PAXRy4UXL6I/rfcAOU09rnnq7RKsLsmhpp/fFEuag==", "dev": true, "dependencies": { "bs-logger": "^0.2.6", - "esbuild-wasm": ">=0.13.8", + "esbuild-wasm": ">=0.15.13", "jest-environment-jsdom": "^29.0.0", "jest-util": "^29.0.0", "pretty-format": "^29.0.0", @@ -12219,15 +12228,15 @@ "node": "^14.15.0 || >=16.10.0" }, "optionalDependencies": { - "esbuild": ">=0.13.8" + "esbuild": ">=0.15.13" }, "peerDependencies": { - "@angular-devkit/build-angular": ">=13.0.0 <17.0.0", - "@angular/compiler-cli": ">=13.0.0 <17.0.0", - "@angular/core": ">=13.0.0 <17.0.0", - "@angular/platform-browser-dynamic": ">=13.0.0 <17.0.0", + "@angular-devkit/build-angular": ">=15.0.0 <18.0.0", + "@angular/compiler-cli": ">=15.0.0 <18.0.0", + "@angular/core": ">=15.0.0 <18.0.0", + "@angular/platform-browser-dynamic": ">=15.0.0 <18.0.0", "jest": "^29.0.0", - "typescript": ">=4.4" + "typescript": ">=4.8" } }, "node_modules/jest-regex-util": { @@ -13009,42 +13018,47 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true + }, "node_modules/jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", "dev": true, "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=14" }, "peerDependencies": { "canvas": "^2.5.0" @@ -13055,11 +13069,30 @@ } } }, - "node_modules/jsdom/node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true + "node_modules/jsdom/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/jsdom/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } }, "node_modules/jsesc": { "version": "2.5.2", @@ -13110,9 +13143,9 @@ } }, "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", "dev": true }, "node_modules/jsonfile": { @@ -13137,9 +13170,12 @@ ] }, "node_modules/jwt-decode": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", - "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } }, "node_modules/karma-source-map-support": { "version": "1.4.0", @@ -13197,9 +13233,9 @@ } }, "node_modules/less": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", - "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz", + "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==", "dev": true, "dependencies": { "copy-anything": "^2.0.1", @@ -13316,9 +13352,9 @@ } }, "node_modules/lines-and-columns": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.3.tgz", - "integrity": "sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", + "integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==", "dev": true, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" @@ -13474,9 +13510,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz", - "integrity": "sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==", + "version": "0.30.8", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", + "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -13507,212 +13543,27 @@ "dev": true }, "node_modules/make-fetch-happen": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", - "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.0.tgz", + "integrity": "sha512-7ThobcL8brtGo9CavByQrQi+23aIfgYU++wg4B87AIS8Rb2ZBt/MEaDqzA00Xwv/jUjAjYkLHjVolYuTLKda2A==", "dev": true, "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^16.1.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", + "http-cache-semantics": "^4.1.1", "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^2.0.3", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.3", "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^9.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/make-fetch-happen/node_modules/@npmcli/fs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", - "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", - "dev": true, - "dependencies": { - "@gar/promisify": "^1.1.3", - "semver": "^7.3.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/make-fetch-happen/node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/make-fetch-happen/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/make-fetch-happen/node_modules/cacache": { - "version": "16.1.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", - "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", - "dev": true, - "dependencies": { - "@npmcli/fs": "^2.1.0", - "@npmcli/move-file": "^2.0.0", - "chownr": "^2.0.0", - "fs-minipass": "^2.1.0", - "glob": "^8.0.1", - "infer-owner": "^1.0.4", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "mkdirp": "^1.0.4", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^9.0.0", - "tar": "^6.1.11", - "unique-filename": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/make-fetch-happen/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/make-fetch-happen/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/make-fetch-happen/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/make-fetch-happen/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/make-fetch-happen/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-fetch-happen/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/make-fetch-happen/node_modules/ssri": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", - "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", - "dev": true, - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/make-fetch-happen/node_modules/unique-filename": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", - "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", - "dev": true, - "dependencies": { - "unique-slug": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/make-fetch-happen/node_modules/unique-slug": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", - "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4" + "ssri": "^10.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/make-fetch-happen/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -13834,12 +13685,13 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.7.6", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz", - "integrity": "sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.1.tgz", + "integrity": "sha512-/1HDlyFRxWIZPI1ZpgqlZ8jMw/1Dp/dl3P0L1jtZ+zVcHqwPhGwaJwKL00WVgfnBy6PWCde9W65or7IIETImuA==", "dev": true, "dependencies": { - "schema-utils": "^4.0.0" + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" }, "engines": { "node": ">= 12.13.0" @@ -13880,79 +13732,43 @@ } }, "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", "dev": true, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-collect/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", "dev": true, "dependencies": { - "yallist": "^4.0.0" + "minipass": "^7.0.3" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/minipass-collect/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/minipass-fetch": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", - "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz", + "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==", "dev": true, "dependencies": { - "minipass": "^3.1.6", + "minipass": "^7.0.3", "minipass-sized": "^1.0.3", "minizlib": "^2.1.2" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" }, "optionalDependencies": { "encoding": "^0.1.13" } }, - "node_modules/minipass-fetch/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-fetch/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/minipass-flush": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", @@ -14115,17 +13931,17 @@ } }, "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", "engines": { "node": "*" } }, "node_modules/moment-timezone": { - "version": "0.5.43", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.43.tgz", - "integrity": "sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ==", + "version": "0.5.45", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.45.tgz", + "integrity": "sha512-HIWmqA86KcmCAhnMAN0wuDOARV/525R2+lOLotuGFzn4HO+FH+/645z2wx0Dt3iDv6/p61SIvKnDstISainhLQ==", "dependencies": { "moment": "^2.29.4" }, @@ -14134,9 +13950,9 @@ } }, "node_modules/mrmime": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", - "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", "dev": true, "engines": { "node": ">=10" @@ -14162,15 +13978,18 @@ } }, "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true, "funding": [ { @@ -14192,13 +14011,12 @@ "dev": true }, "node_modules/needle": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-3.2.0.tgz", - "integrity": "sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", + "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", "dev": true, "optional": true, "dependencies": { - "debug": "^3.2.6", "iconv-lite": "^0.6.3", "sax": "^1.2.4" }, @@ -14209,16 +14027,6 @@ "node": ">= 4.4.x" } }, - "node_modules/needle/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "optional": true, - "dependencies": { - "ms": "^2.1.1" - } - }, "node_modules/needle/node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -14262,9 +14070,9 @@ } }, "node_modules/ngx-mask": { - "version": "16.3.9", - "resolved": "https://registry.npmjs.org/ngx-mask/-/ngx-mask-16.3.9.tgz", - "integrity": "sha512-cptsvlI4OLI8Tpj23ZgSQDKz5jksyWGzAuEEn5pd58cq2oFGeeHZS2i1SQQi8kp+a+Dh/2RvDsfFmDWmI5Ln9w==", + "version": "17.0.7", + "resolved": "https://registry.npmjs.org/ngx-mask/-/ngx-mask-17.0.7.tgz", + "integrity": "sha512-pmiCkAIDi5HqWwicHsOThPsJALF3fdMR21aoDJd/boJoEo1HoS34NJNUkqJ11qB2ZBVu5PX8R9KB617OCMkXXQ==", "dependencies": { "tslib": "^2.3.0" }, @@ -14293,7 +14101,8 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", - "dev": true + "dev": true, + "optional": true }, "node_modules/node-forge": { "version": "1.3.1", @@ -14305,81 +14114,157 @@ } }, "node_modules/node-gyp": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz", - "integrity": "sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.1.0.tgz", + "integrity": "sha512-B4J5M1cABxPc5PwfjhbV5hoy2DP9p8lFXASnEN6hugXOa61416tnTZ29x9sSwAd0o99XNIcpvDDy1swAExsVKA==", "dev": true, "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", - "glob": "^7.1.4", + "glob": "^10.3.10", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^10.0.3", - "nopt": "^6.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", + "make-fetch-happen": "^13.0.0", + "nopt": "^7.0.0", + "proc-log": "^3.0.0", "semver": "^7.3.5", "tar": "^6.1.2", - "which": "^2.0.2" + "which": "^4.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": "^12.13 || ^14.13 || >=16" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/node-gyp-build": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.1.tgz", - "integrity": "sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", + "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==", "dev": true, + "optional": true, "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, + "node_modules/node-gyp/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/node-gyp/node_modules/glob": { + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.6", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/node-gyp/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true }, + "node_modules/node-machine-id": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz", + "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==", + "dev": true + }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, "node_modules/nopt": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", - "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz", + "integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==", "dev": true, "dependencies": { - "abbrev": "^1.0.0" + "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/normalize-package-data": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", - "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.0.tgz", + "integrity": "sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg==", "dev": true, "dependencies": { - "hosted-git-info": "^6.0.0", + "hosted-git-info": "^7.0.0", "is-core-module": "^2.8.1", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/normalize-path": { @@ -14434,147 +14319,64 @@ } }, "node_modules/npm-package-arg": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", - "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.1.tgz", + "integrity": "sha512-M7s1BD4NxdAvBKUPqqRW957Xwcl/4Zvo8Aj+ANrzvIPzGJZElrH7Z//rSaec2ORcND6FHHLnZeY8qgTpXDMFQQ==", "dev": true, "dependencies": { - "hosted-git-info": "^6.0.0", + "hosted-git-info": "^7.0.0", "proc-log": "^3.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm-packlist": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-7.0.4.tgz", - "integrity": "sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", + "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", "dev": true, "dependencies": { - "ignore-walk": "^6.0.0" + "ignore-walk": "^6.0.4" }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/npm-pick-manifest": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.1.tgz", - "integrity": "sha512-mRtvlBjTsJvfCCdmPtiu2bdlx8d/KXtF7yNXNWe7G0Z36qWA9Ny5zXsI2PfBZEv7SXgoxTmNaTzGSbbzDZChoA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.0.0.tgz", + "integrity": "sha512-VfvRSs/b6n9ol4Qb+bDwNGUXutpy76x6MARw/XssevE0TnctIKcmklJZM5Z7nqs5z5aW+0S63pgCNbpkUNNXBg==", "dev": true, "dependencies": { "npm-install-checks": "^6.0.0", "npm-normalize-package-bin": "^3.0.0", - "npm-package-arg": "^10.0.0", + "npm-package-arg": "^11.0.0", "semver": "^7.3.5" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm-registry-fetch": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-14.0.5.tgz", - "integrity": "sha512-kIDMIo4aBm6xg7jOttupWZamsZRkAqMqwqqbVXnUqstY5+tapvv6bkH/qMR76jdgV+YljEUCyWx3hRYMrJiAgA==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-16.2.0.tgz", + "integrity": "sha512-zVH+G0q1O2hqgQBUvQ2LWp6ujr6VJAeDnmWxqiMlCguvLexEzBnuQIwC70r04vcvCMAcYEIpA/rO9YyVi+fmJQ==", "dev": true, "dependencies": { - "make-fetch-happen": "^11.0.0", - "minipass": "^5.0.0", + "@npmcli/redact": "^1.1.0", + "make-fetch-happen": "^13.0.0", + "minipass": "^7.0.2", "minipass-fetch": "^3.0.0", "minipass-json-stream": "^1.0.1", "minizlib": "^2.1.2", - "npm-package-arg": "^10.0.0", + "npm-package-arg": "^11.0.0", "proc-log": "^3.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm-registry-fetch/node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/npm-registry-fetch/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/npm-registry-fetch/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/npm-registry-fetch/node_modules/make-fetch-happen": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", - "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", - "dev": true, - "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^17.0.0", - "http-cache-semantics": "^4.1.1", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^5.0.0", - "minipass-fetch": "^3.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^10.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm-registry-fetch/node_modules/minipass-fetch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz", - "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==", - "dev": true, - "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "node_modules/npm-registry-fetch/node_modules/minipass-fetch/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm-run-path": { @@ -14589,21 +14391,6 @@ "node": ">=8" } }, - "node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "dev": true, - "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -14623,65 +14410,66 @@ "dev": true }, "node_modules/nx": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/nx/-/nx-16.5.1.tgz", - "integrity": "sha512-I3hJRE4hG7JWAtncWwDEO3GVeGPpN0TtM8xH5ArZXyDuVeTth/i3TtJzdDzqXO1HHtIoAQN0xeq4n9cLuMil5g==", + "version": "18.2.2", + "resolved": "https://registry.npmjs.org/nx/-/nx-18.2.2.tgz", + "integrity": "sha512-ZEnN+2XV6QWI3q6N/I9byjSK2ErxAJJjKIWFQ45RW7+KCFbiwF0zeGnn5zruSHY7nbTrUf5C7MDA80eXam5DTg==", "dev": true, "hasInstallScript": true, "dependencies": { - "@nrwl/tao": "16.5.1", - "@parcel/watcher": "2.0.4", + "@nrwl/tao": "18.2.2", "@yarnpkg/lockfile": "^1.1.0", "@yarnpkg/parsers": "3.0.0-rc.46", "@zkochan/js-yaml": "0.0.6", - "axios": "^1.0.0", + "axios": "^1.6.0", "chalk": "^4.1.0", "cli-cursor": "3.1.0", "cli-spinners": "2.6.1", - "cliui": "^7.0.2", - "dotenv": "~10.0.0", + "cliui": "^8.0.1", + "dotenv": "~16.3.1", + "dotenv-expand": "~10.0.0", "enquirer": "~2.3.6", - "fast-glob": "3.2.7", "figures": "3.2.0", "flat": "^5.0.2", "fs-extra": "^11.1.0", - "glob": "7.1.4", "ignore": "^5.0.4", + "jest-diff": "^29.4.1", "js-yaml": "4.1.0", "jsonc-parser": "3.2.0", "lines-and-columns": "~2.0.3", - "minimatch": "3.0.5", + "minimatch": "9.0.3", + "node-machine-id": "1.1.12", "npm-run-path": "^4.0.1", "open": "^8.4.0", - "semver": "7.5.3", + "ora": "5.3.0", + "semver": "^7.5.3", "string-width": "^4.2.3", "strong-log-transformer": "^2.1.0", "tar-stream": "~2.2.0", "tmp": "~0.2.1", "tsconfig-paths": "^4.1.2", "tslib": "^2.3.0", - "v8-compile-cache": "2.3.0", "yargs": "^17.6.2", "yargs-parser": "21.1.1" }, "bin": { - "nx": "bin/nx.js" + "nx": "bin/nx.js", + "nx-cloud": "bin/nx-cloud.js" }, "optionalDependencies": { - "@nx/nx-darwin-arm64": "16.5.1", - "@nx/nx-darwin-x64": "16.5.1", - "@nx/nx-freebsd-x64": "16.5.1", - "@nx/nx-linux-arm-gnueabihf": "16.5.1", - "@nx/nx-linux-arm64-gnu": "16.5.1", - "@nx/nx-linux-arm64-musl": "16.5.1", - "@nx/nx-linux-x64-gnu": "16.5.1", - "@nx/nx-linux-x64-musl": "16.5.1", - "@nx/nx-win32-arm64-msvc": "16.5.1", - "@nx/nx-win32-x64-msvc": "16.5.1" + "@nx/nx-darwin-arm64": "18.2.2", + "@nx/nx-darwin-x64": "18.2.2", + "@nx/nx-freebsd-x64": "18.2.2", + "@nx/nx-linux-arm-gnueabihf": "18.2.2", + "@nx/nx-linux-arm64-gnu": "18.2.2", + "@nx/nx-linux-arm64-musl": "18.2.2", + "@nx/nx-linux-x64-gnu": "18.2.2", + "@nx/nx-linux-x64-musl": "18.2.2", + "@nx/nx-win32-arm64-msvc": "18.2.2", + "@nx/nx-win32-x64-msvc": "18.2.2" }, "peerDependencies": { - "@swc-node/register": "^1.4.2", - "@swc/core": "^1.2.173" + "@swc-node/register": "^1.8.0", + "@swc/core": "^1.3.85" }, "peerDependenciesMeta": { "@swc-node/register": { @@ -14713,6 +14501,15 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/nx/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/nx/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -14747,39 +14544,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/nx/node_modules/fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nx/node_modules/glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, "node_modules/nx/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -14801,43 +14565,47 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/nx/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } + "node_modules/nx/node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true }, "node_modules/nx/node_modules/minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/nx/node_modules/semver": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", - "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "node_modules/nx/node_modules/ora": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.3.0.tgz", + "integrity": "sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g==", "dev": true, "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "bl": "^4.0.3", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "log-symbols": "^4.0.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/nx/node_modules/supports-color": { @@ -14852,12 +14620,6 @@ "node": ">=8" } }, - "node_modules/nx/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -14867,15 +14629,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object-path": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.8.tgz", - "integrity": "sha512-YJjNZrlXJFM42wTBn6zgOJVar9KFJvzx6sTWDte8sWZF//cnjl0BxHNpfZx+ZffXX63A9q0b1zsFiBX4g4X5KA==", - "dev": true, - "engines": { - "node": ">= 10.12.0" - } - }, "node_modules/obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -15152,27 +14905,27 @@ } }, "node_modules/pacote": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-15.2.0.tgz", - "integrity": "sha512-rJVZeIwHTUta23sIZgEIM62WYwbmGbThdbnkt81ravBplQv+HjyroqnLRNH2+sLJHcGZmLRmhPwACqhfTcOmnA==", + "version": "17.0.6", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-17.0.6.tgz", + "integrity": "sha512-cJKrW21VRE8vVTRskJo78c/RCvwJCn1f4qgfxL4w77SOWrTCRcmfkYHlHtS0gqpgjv3zhXflRtgsrUCX5xwNnQ==", "dev": true, "dependencies": { - "@npmcli/git": "^4.0.0", + "@npmcli/git": "^5.0.0", "@npmcli/installed-package-contents": "^2.0.1", - "@npmcli/promise-spawn": "^6.0.1", - "@npmcli/run-script": "^6.0.0", - "cacache": "^17.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "@npmcli/run-script": "^7.0.0", + "cacache": "^18.0.0", "fs-minipass": "^3.0.0", - "minipass": "^5.0.0", - "npm-package-arg": "^10.0.0", - "npm-packlist": "^7.0.0", - "npm-pick-manifest": "^8.0.0", - "npm-registry-fetch": "^14.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^11.0.0", + "npm-packlist": "^8.0.0", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^16.0.0", "proc-log": "^3.0.0", "promise-retry": "^2.0.1", - "read-package-json": "^6.0.0", + "read-package-json": "^7.0.0", "read-package-json-fast": "^3.0.0", - "sigstore": "^1.3.0", + "sigstore": "^2.2.0", "ssri": "^10.0.0", "tar": "^6.1.11" }, @@ -15180,15 +14933,9 @@ "pacote": "lib/bin.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -15315,12 +15062,12 @@ "dev": true }, "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", "dev": true, "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", + "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { @@ -15331,9 +15078,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", - "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", "dev": true, "engines": { "node": "14 || >=16.14" @@ -15355,11 +15102,11 @@ } }, "node_modules/photoviewer": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/photoviewer/-/photoviewer-3.8.1.tgz", - "integrity": "sha512-JkG1j0n/24y3HZ4v+1JEcHOqPwPV/ltRMw3Z0TAUQzgeorMghbqE0IGQneljkc0qkNCjgiNhwG1ApWvnxoQMWg==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/photoviewer/-/photoviewer-3.10.0.tgz", + "integrity": "sha512-TRv8h8dNjLdvkzRVyQu2rENTk0jUrvWtmX6crKp2wB2sw8Jnsie9SFJgvil4iu4QKvGRZnPmj53L/DPExYHJig==", "dependencies": { - "domq.js": "^0.6.8" + "domq.js": "^0.7.0" } }, "node_modules/picocolors": { @@ -15400,15 +15147,10 @@ } }, "node_modules/piscina": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.0.0.tgz", - "integrity": "sha512-641nAmJS4k4iqpNUqfggqUBUMmlw0ZoM5VZKdQkV2e970Inn3Tk9kroCc1wpsYLD07vCwpys5iY0d3xI/9WkTg==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.4.0.tgz", + "integrity": "sha512-+AQduEJefrOApE4bV7KRmp3N2JnnyErlVqq4P/jmko4FPz9Z877BCccl/iB3FdrWSUkvbGV9Kan/KllJgat3Vg==", "dev": true, - "dependencies": { - "eventemitter-asyncresource": "^1.0.0", - "hdr-histogram-js": "^2.0.1", - "hdr-histogram-percentiles-obj": "^3.0.0" - }, "optionalDependencies": { "nice-napi": "^1.0.2" } @@ -15511,9 +15253,9 @@ } }, "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", "dev": true, "funding": [ { @@ -15530,7 +15272,7 @@ } ], "dependencies": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -15539,31 +15281,46 @@ } }, "node_modules/postcss-loader": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.3.tgz", - "integrity": "sha512-YgO/yhtevGO/vJePCQmTxiaEwER94LABZN0ZMT4A0vsak9TpO+RvKRs7EmJ8peIlB9xfXCsS7M8LjqncsUZ5HA==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", + "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", "dev": true, "dependencies": { - "cosmiconfig": "^8.2.0", - "jiti": "^1.18.2", - "semver": "^7.3.8" + "cosmiconfig": "^9.0.0", + "jiti": "^1.20.0", + "semver": "^7.5.4" }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { + "@rspack/core": "0.x || 1.x", "postcss": "^7.0.0 || ^8.0.1", "webpack": "^5.0.0" - } - }, - "node_modules/postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", + "dev": true + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", "dev": true, "engines": { "node": "^10 || ^12 || >= 14" @@ -15573,9 +15330,9 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", - "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", "dev": true, "dependencies": { "icss-utils": "^5.0.0", @@ -15590,9 +15347,9 @@ } }, "node_modules/postcss-modules-scope": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", - "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.4" @@ -15620,9 +15377,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", - "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", + "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -15648,9 +15405,9 @@ } }, "node_modules/prettier": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", - "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -15662,18 +15419,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -15873,9 +15618,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dev": true, "dependencies": { "bytes": "3.1.2", @@ -15903,18 +15648,18 @@ "dev": true }, "node_modules/read-package-json": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.4.tgz", - "integrity": "sha512-AEtWXYfopBj2z5N5PbkAOeNHRPUg5q+Nen7QLxV8M2zJq1ym6/lCz3fYNTCXe19puu2d06jfHhrP7v/S2PtMMw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-7.0.0.tgz", + "integrity": "sha512-uL4Z10OKV4p6vbdvIXB+OzhInYtIozl/VxUBPgNkBuUi2DeRonnuspmaVAMcrkmfjKGNmRndyQAbE7/AmzGwFg==", "dev": true, "dependencies": { "glob": "^10.2.2", "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^5.0.0", + "normalize-package-data": "^6.0.0", "npm-normalize-package-bin": "^3.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/read-package-json-fast": { @@ -15931,9 +15676,9 @@ } }, "node_modules/read-package-json-fast/node_modules/json-parse-even-better-errors": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", - "integrity": "sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz", + "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg==", "dev": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -15949,16 +15694,16 @@ } }, "node_modules/read-package-json/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", + "jackspeak": "^2.3.6", "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" }, "bin": { "glob": "dist/esm/bin.mjs" @@ -15971,18 +15716,18 @@ } }, "node_modules/read-package-json/node_modules/json-parse-even-better-errors": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", - "integrity": "sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz", + "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg==", "dev": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/read-package-json/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -16021,9 +15766,9 @@ } }, "node_modules/reflect-metadata": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", "dev": true }, "node_modules/regenerate": { @@ -16045,9 +15790,9 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "dev": true }, "node_modules/regenerator-transform": { @@ -16060,9 +15805,9 @@ } }, "node_modules/regex-parser": { - "version": "2.2.11", - "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", - "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.0.tgz", + "integrity": "sha512-TVILVSz2jY5D47F4mA4MppkBrafEaiUWJO/TcZHEIuI13AqoZMkK1WMA4Om1YkYbTx+9Ki1/tSUXbceyr9saRg==", "dev": true }, "node_modules/regexpu-core": { @@ -16128,12 +15873,12 @@ "dev": true }, "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, "dependencies": { - "is-core-module": "^2.11.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -16261,25 +16006,43 @@ } }, "node_modules/rollup": { - "version": "3.29.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", - "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.0.tgz", + "integrity": "sha512-Qe7w62TyawbDzB4yt32R0+AbIo6m1/sqO7UPzFS8Z/ksL5mrfhA0v4CavfdmFav3D+ub4QeAgsGEe84DoWe/nQ==", "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, "bin": { "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=14.18.0", + "node": ">=18.0.0", "npm": ">=8.0.0" }, "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.14.0", + "@rollup/rollup-android-arm64": "4.14.0", + "@rollup/rollup-darwin-arm64": "4.14.0", + "@rollup/rollup-darwin-x64": "4.14.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.14.0", + "@rollup/rollup-linux-arm64-gnu": "4.14.0", + "@rollup/rollup-linux-arm64-musl": "4.14.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.14.0", + "@rollup/rollup-linux-riscv64-gnu": "4.14.0", + "@rollup/rollup-linux-s390x-gnu": "4.14.0", + "@rollup/rollup-linux-x64-gnu": "4.14.0", + "@rollup/rollup-linux-x64-musl": "4.14.0", + "@rollup/rollup-win32-arm64-msvc": "4.14.0", + "@rollup/rollup-win32-ia32-msvc": "4.14.0", + "@rollup/rollup-win32-x64-msvc": "4.14.0", "fsevents": "~2.3.2" } }, "node_modules/run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", "dev": true, "engines": { "node": ">=0.12.0" @@ -16348,9 +16111,9 @@ "integrity": "sha512-LRneZZRXNgjzwG4bDQdOTSbze3fHm1EAKN/8bePxnlEZiBmkYEDggaHbuvHI9/hoqHbGfsEA7tWS9GhYHZBBsw==" }, "node_modules/sass": { - "version": "1.64.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.64.1.tgz", - "integrity": "sha512-16rRACSOFEE8VN7SCgBu1MpYCyN7urj9At898tyzdXFhC+a+yOX5dXwAR7L8/IdPJ1NB8OYoXmD55DM30B2kEQ==", + "version": "1.71.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz", + "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -16365,29 +16128,29 @@ } }, "node_modules/sass-loader": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.2.tgz", - "integrity": "sha512-CQbKl57kdEv+KDLquhC+gE3pXt74LEAzm+tzywcA0/aHZuub8wTErbjAoNI57rPUWRYRNC5WUnNl8eGJNbDdwg==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.1.1.tgz", + "integrity": "sha512-QX8AasDg75monlybel38BZ49JP5Z+uSKfKwF2rO7S74BywaRmGQMUBw9dtkS+ekyM/QnP+NOrRYq8ABMZ9G8jw==", "dev": true, "dependencies": { "neo-async": "^2.6.2" }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "fibers": ">= 3.1.0", + "@rspack/core": "0.x || 1.x", "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", "sass": "^1.3.0", "sass-embedded": "*", "webpack": "^5.0.0" }, "peerDependenciesMeta": { - "fibers": { + "@rspack/core": { "optional": true }, "node-sass": { @@ -16398,6 +16161,9 @@ }, "sass-embedded": { "optional": true + }, + "webpack": { + "optional": true } } }, @@ -16409,15 +16175,15 @@ "optional": true }, "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", "dev": true, "dependencies": { "xmlchars": "^2.2.0" }, "engines": { - "node": ">=10" + "node": ">=v12.22.7" } }, "node_modules/schema-utils": { @@ -16537,9 +16303,9 @@ "dev": true }, "node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "dependencies": { "randombytes": "^2.1.0" @@ -16638,22 +16404,18 @@ "node": ">= 0.8.0" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dev": true, "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -16708,14 +16470,18 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dev": true, "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -16728,106 +16494,20 @@ "dev": true }, "node_modules/sigstore": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-1.9.0.tgz", - "integrity": "sha512-0Zjz0oe37d08VeOtBIuB6cRriqXse2e8w+7yIy2XSXjshRKxbc2KkhXjL229jXSxEm7UbcjS76wcJDGQddVI9A==", - "dev": true, - "dependencies": { - "@sigstore/bundle": "^1.1.0", - "@sigstore/protobuf-specs": "^0.2.0", - "@sigstore/sign": "^1.0.0", - "@sigstore/tuf": "^1.0.3", - "make-fetch-happen": "^11.0.1" - }, - "bin": { - "sigstore": "bin/sigstore.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/sigstore/node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/sigstore/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/sigstore/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/sigstore/node_modules/make-fetch-happen": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", - "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", - "dev": true, - "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^17.0.0", - "http-cache-semantics": "^4.1.1", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^5.0.0", - "minipass-fetch": "^3.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^10.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/sigstore/node_modules/minipass-fetch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz", - "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.2.2.tgz", + "integrity": "sha512-2A3WvXkQurhuMgORgT60r6pOWiCOO5LlEqY2ADxGBDGVYLSo5HN0uLtb68YpVpuL/Vi8mLTe7+0Dx2Fq8lLqEg==", "dev": true, "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "@sigstore/bundle": "^2.2.0", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.0", + "@sigstore/sign": "^2.2.3", + "@sigstore/tuf": "^2.3.1", + "@sigstore/verify": "^1.1.0" }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "node_modules/sigstore/node_modules/minipass-fetch/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", - "dev": true, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/sisteransi": { @@ -16867,31 +16547,31 @@ } }, "node_modules/socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.1.tgz", + "integrity": "sha512-B6w7tkwNid7ToxjZ08rQMT8M9BJAf8DKx8Ft4NivzH0zBUfd6jldGcisJn/RLgxcX3FPNDdNQCUEMMT79b+oCQ==", "dev": true, "dependencies": { - "ip": "^2.0.0", + "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" }, "engines": { - "node": ">= 10.13.0", + "node": ">= 10.0.0", "npm": ">= 3.0.0" } }, "node_modules/socks-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", - "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz", + "integrity": "sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==", "dev": true, "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.7.1" }, "engines": { - "node": ">= 10" + "node": ">= 14" } }, "node_modules/source-map": { @@ -16904,26 +16584,25 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/source-map-loader": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.1.tgz", - "integrity": "sha512-oqXpzDIByKONVY8g1NUPOTQhe0UTU5bWUl32GSkqK2LjJj0HmwTMVKxcUip0RgAYhY1mqgOxjbQM48a0mmeNfA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz", + "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", "dev": true, "dependencies": { - "abab": "^2.0.6", "iconv-lite": "^0.6.3", "source-map-js": "^1.0.2" }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", @@ -16973,9 +16652,9 @@ } }, "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", "dev": true }, "node_modules/spdx-expression-parse": { @@ -16989,9 +16668,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", - "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==", + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", + "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", "dev": true }, "node_modules/spdy": { @@ -17042,15 +16721,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/ssri/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -17253,9 +16923,9 @@ } }, "node_modules/tar": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", - "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "dev": true, "dependencies": { "chownr": "^2.0.0", @@ -17309,6 +16979,15 @@ "node": ">=8" } }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/tar/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -17316,9 +16995,9 @@ "dev": true }, "node_modules/terser": { - "version": "5.19.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz", - "integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==", + "version": "5.29.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.29.1.tgz", + "integrity": "sha512-lZQ/fyaIGxsbGxApKmoPTODIzELy3++mXhS5hOqaAWZjQtpq/hFHAc+rm29NND1rYRxRWKcjuARNwULNXa5RtQ==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -17334,16 +17013,16 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", - "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", + "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", "serialize-javascript": "^6.0.1", - "terser": "^5.16.8" + "terser": "^5.26.0" }, "engines": { "node": ">= 10.13.0" @@ -17487,15 +17166,12 @@ "dev": true }, "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", "dev": true, - "dependencies": { - "rimraf": "^3.0.0" - }, "engines": { - "node": ">=8.17.0" + "node": ">=14.14" } }, "node_modules/tmpl": { @@ -17559,15 +17235,15 @@ } }, "node_modules/tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", "dev": true, "dependencies": { "punycode": "^2.1.1" }, "engines": { - "node": ">=8" + "node": ">=12" } }, "node_modules/tree-kill": { @@ -17580,21 +17256,21 @@ } }, "node_modules/ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", "dev": true, "engines": { - "node": ">=16.13.0" + "node": ">=16" }, "peerDependencies": { "typescript": ">=4.2.0" } }, "node_modules/ts-jest": { - "version": "29.1.1", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", - "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", + "version": "29.1.2", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", + "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==", "dev": true, "dependencies": { "bs-logger": "0.x", @@ -17610,7 +17286,7 @@ "ts-jest": "cli.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^16.10.0 || ^18.0.0 || >=20.0.0" }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", @@ -17634,6 +17310,49 @@ } } }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/tsconfig-paths": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", @@ -17662,169 +17381,64 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "node_modules/tuf-js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.0.tgz", + "integrity": "sha512-ZSDngmP1z6zw+FIkIBjvOp/II/mIub/O7Pp12j1WNsiCpg5R5wAc//i555bBQsE44O94btLt0xM/Zr2LQjwdCg==", "dev": true, "dependencies": { - "tslib": "^1.8.1" + "@tufjs/models": "2.0.0", + "debug": "^4.3.4", + "make-fetch-happen": "^13.0.0" }, "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/tuf-js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-1.1.7.tgz", - "integrity": "sha512-i3P9Kgw3ytjELUfpuKVDNBJvk4u5bXL6gskv572mcevPbSKCV3zt3djhmlEQ65yERjIbOSncy7U4cQJaB1CBCg==", + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "dependencies": { - "@tufjs/models": "1.0.4", - "debug": "^4.3.4", - "make-fetch-happen": "^11.1.1" + "prelude-ls": "^1.2.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">= 0.8.0" } }, - "node_modules/tuf-js/node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true, "engines": { - "node": ">= 10" + "node": ">=4" } }, - "node_modules/tuf-js/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, "engines": { - "node": ">= 6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/tuf-js/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, "engines": { - "node": ">=12" - } - }, - "node_modules/tuf-js/node_modules/make-fetch-happen": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", - "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", - "dev": true, - "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^17.0.0", - "http-cache-semantics": "^4.1.1", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^5.0.0", - "minipass-fetch": "^3.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^10.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/tuf-js/node_modules/minipass-fetch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz", - "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==", - "dev": true, - "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "node_modules/tuf-js/node_modules/minipass-fetch/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" + "node": ">= 0.6" } }, "node_modules/typed-assert": { @@ -17834,16 +17448,25 @@ "dev": true }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", + "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.7.1.tgz", + "integrity": "sha512-+Wtb9bAQw6HYWzCnxrPTMVEV3Q1QjYanI0E4q02ehReMuquQdLTEFEYbfs7hcImVYKcQkWSwT6buEmSVIiDDtQ==", + "dev": true, + "engines": { + "node": ">=18.0" } }, "node_modules/undici-types": { @@ -17983,162 +17606,558 @@ "requires-port": "^1.0.0" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/v8-to-istanbul": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz", + "integrity": "sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz", + "integrity": "sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==", + "dev": true, + "dependencies": { + "builtins": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.5.tgz", + "integrity": "sha512-BdN1xh0Of/oQafhU+FvopafUp6WaYenLU/NFoL5WyJL++GxkNfieKzBhM24H3HVsPQrlAqB7iJYTHabzaRed5Q==", + "dev": true, + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.35", + "rollup": "^4.2.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], "dev": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4.0" + "node": ">=12" } }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], "dev": true, - "bin": { - "uuid": "dist/bin/uuid" + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" } }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "node_modules/v8-to-istanbul": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz", - "integrity": "sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==", + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=10.12.0" + "node": ">=12" } }, - "node_modules/v8-to-istanbul/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" } }, - "node_modules/validate-npm-package-name": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz", - "integrity": "sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==", + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], "dev": true, - "dependencies": { - "builtins": "^5.0.0" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=12" } }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], "dev": true, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.8" + "node": ">=12" } }, - "node_modules/vite": { - "version": "4.4.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.7.tgz", - "integrity": "sha512-6pYf9QJ1mHylfVh39HpuSfMPojPSKVxZvnclX1K1FyZ1PXDOcLBibdq5t1qxJSnL63ca8Wf4zts6mD8u8oc9Fw==", + "node_modules/vite/node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", "dev": true, - "dependencies": { - "esbuild": "^0.18.10", - "postcss": "^8.4.26", - "rollup": "^3.25.2" - }, + "hasInstallScript": true, "bin": { - "vite": "bin/vite.js" + "esbuild": "bin/esbuild" }, "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" + "node": ">=12" }, "optionalDependencies": { - "fsevents": "~2.3.2" - }, - "peerDependencies": { - "@types/node": ">= 14", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", - "dev": true, - "dependencies": { - "browser-process-hrtime": "^1.0.0" + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" } }, "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", "dev": true, "dependencies": { - "xml-name-validator": "^3.0.0" + "xml-name-validator": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=14" } }, "node_modules/walker": { @@ -18182,28 +18201,28 @@ } }, "node_modules/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", "dev": true, "engines": { - "node": ">=10.4" + "node": ">=12" } }, "node_modules/webpack": { - "version": "5.88.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", - "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", + "version": "5.90.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.3.tgz", + "integrity": "sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", + "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.11.5", "@webassemblyjs/wasm-edit": "^1.11.5", "@webassemblyjs/wasm-parser": "^1.11.5", "acorn": "^8.7.1", "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", + "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.15.0", "es-module-lexer": "^1.2.1", @@ -18217,7 +18236,7 @@ "neo-async": "^2.6.2", "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", + "terser-webpack-plugin": "^5.3.10", "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, @@ -18238,9 +18257,9 @@ } }, "node_modules/webpack-dev-middleware": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-6.1.1.tgz", - "integrity": "sha512-y51HrHaFeeWir0YO4f0g+9GwZawuigzcAdRNon6jErXy/SqV/+O6eaVAzDqE6t3e3NpGeR5CS+cCDaTC+V3yEQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-6.1.2.tgz", + "integrity": "sha512-Wu+EHmX326YPYUpQLKmKbTyZZJIB8/n6R09pTmB03kJmnMsVPTo9COzHZFr01txwaCAuZvfBJE4ZCHRcKs5JaQ==", "dev": true, "dependencies": { "colorette": "^2.0.10", @@ -18325,9 +18344,9 @@ } }, "node_modules/webpack-dev-server/node_modules/webpack-dev-middleware": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", - "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", "dev": true, "dependencies": { "colorette": "^2.0.10", @@ -18347,34 +18366,14 @@ "webpack": "^4.0.0 || ^5.0.0" } }, - "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/webpack-merge": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.9.0.tgz", - "integrity": "sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", "dev": true, "dependencies": { "clone-deep": "^4.0.1", + "flat": "^5.0.2", "wildcard": "^2.0.0" }, "engines": { @@ -18506,32 +18505,49 @@ } }, "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "dependencies": { - "iconv-lite": "0.4.24" + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "engines": { + "node": ">=12" + } }, "node_modules/whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", "dev": true, "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/which": { @@ -18549,15 +18565,6 @@ "node": ">= 8" } }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, "node_modules/wildcard": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", @@ -18685,16 +18692,16 @@ } }, "node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "dev": true, "engines": { - "node": ">=8.3.0" + "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -18706,10 +18713,13 @@ } }, "node_modules/xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "engines": { + "node": ">=12" + } }, "node_modules/xmlchars": { "version": "2.2.0", @@ -18759,18 +18769,13 @@ "node": ">=12" } }, - "node_modules/yargs/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, "engines": { - "node": ">=12" + "node": ">=6" } }, "node_modules/yocto-queue": { @@ -18786,9 +18791,9 @@ } }, "node_modules/zone.js": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.13.3.tgz", - "integrity": "sha512-MKPbmZie6fASC/ps4dkmIhaT5eonHkEt6eAy80K42tAm0G2W+AahLJjbfi6X9NPdciOE9GRFTTM8u2IiF6O3ww==", + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.4.tgz", + "integrity": "sha512-NtTUvIlNELez7Q1DzKVIFZBzNb646boQMgpATo9z3Ftuu/gWvzxCW7jdjcUDoRGxRikrhVHB/zLXh1hxeJawvw==", "dependencies": { "tslib": "^2.3.0" } diff --git a/alcs-frontend/package.json b/alcs-frontend/package.json index e6b4ed3838..34270baef8 100644 --- a/alcs-frontend/package.json +++ b/alcs-frontend/package.json @@ -15,47 +15,48 @@ }, "private": true, "dependencies": { - "@angular/animations": "^16.2.11", - "@angular/cdk": "^16.2.11", - "@angular/common": "^16.2.11", - "@angular/compiler": "^16.2.11", - "@angular/core": "^16.2.11", - "@angular/forms": "^16.2.11", - "@angular/material": "^16.2.11", - "@angular/material-moment-adapter": "^16.2.11", - "@angular/platform-browser": "^16.2.11", - "@angular/platform-browser-dynamic": "^16.2.11", - "@angular/router": "^16.2.11", + "@angular/animations": "^17.3.3", + "@angular/cdk": "^17.3.3", + "@angular/common": "^17.3.3", + "@angular/compiler": "^17.3.3", + "@angular/core": "^17.3.3", + "@angular/forms": "^17.3.3", + "@angular/material": "^17.3.3", + "@angular/material-experimental": "^17.3.3", + "@angular/material-moment-adapter": "^17.3.3", + "@angular/platform-browser": "^17.3.3", + "@angular/platform-browser-dynamic": "^17.3.3", + "@angular/router": "^17.3.3", "@bcgov/bc-sans": "^2.1.0", - "@ng-matero/extensions": "^16.1.2", - "@ng-select/ng-option-highlight": "^11.2.0", + "@ng-matero/extensions": "^17.2.0", + "@ng-select/ng-option-highlight": "^12.0.6", "angular-mentions": "^1.5.0", - "jwt-decode": "^3.1.2", - "moment": "^2.29.4", - "moment-timezone": "^0.5.43", - "ngx-mask": "^16.3.9", + "jwt-decode": "^4.0.0", + "moment": "^2.30.1", + "moment-timezone": "^0.5.45", + "ngx-mask": "^17.0.7", "rxjs": "~7.8.1", "source-map-support": "^0.5.21", "tslib": "^2.6.2", - "zone.js": "~0.13.3" + "zone.js": "~0.14.4" }, "devDependencies": { - "@angular-builders/jest": "^16.0.1", - "@angular-devkit/build-angular": "^16.2.9", - "@angular-eslint/builder": "^16.2.0", - "@angular-eslint/eslint-plugin": "^16.2.0", - "@angular-eslint/eslint-plugin-template": "^16.2.0", - "@angular-eslint/schematics": "^16.2.0", - "@angular-eslint/template-parser": "^16.2.0", - "@angular/cli": "~16.2.9", - "@angular/compiler-cli": "^16.2.11", + "@angular-builders/jest": "^17.0.3", + "@angular-devkit/build-angular": "^17.3.3", + "@angular-eslint/builder": "^17.3.0", + "@angular-eslint/eslint-plugin": "^17.3.0", + "@angular-eslint/eslint-plugin-template": "^17.3.0", + "@angular-eslint/schematics": "^17.3.0", + "@angular-eslint/template-parser": "^17.3.0", + "@angular/cli": "~17.3.3", + "@angular/compiler-cli": "^17.3.3", "@golevelup/ts-jest": "^0.4.0", - "@types/jest": "^29.5.7", - "@typescript-eslint/eslint-plugin": "6.9.1", - "@typescript-eslint/parser": "6.9.1", - "eslint": "^8.52.0", + "@types/jest": "^29.5.12", + "@typescript-eslint/eslint-plugin": "7.5.0", + "@typescript-eslint/parser": "7.5.0", + "eslint": "^8.57.0", "jest": "^29.7.0", - "prettier": "^3.0.3", - "typescript": "4.9.5" + "prettier": "^3.2.5", + "typescript": "5.4.3" } } diff --git a/alcs-frontend/src/app/features/application/applicant-info/application-details/application-details.component.scss b/alcs-frontend/src/app/features/application/applicant-info/application-details/application-details.component.scss index c94d23fe3e..8ac50e0594 100644 --- a/alcs-frontend/src/app/features/application/applicant-info/application-details/application-details.component.scss +++ b/alcs-frontend/src/app/features/application/applicant-info/application-details/application-details.component.scss @@ -34,7 +34,7 @@ .no-data-text { text-align: center; color: colors.$grey; - padding-top: rem(12); + padding-top: 12px; .error { justify-content: center; diff --git a/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/additional-information/additional-information.component.scss b/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/additional-information/additional-information.component.scss index 68f6ea8546..71a4bcfa01 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/additional-information/additional-information.component.scss +++ b/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/additional-information/additional-information.component.scss @@ -2,6 +2,6 @@ display: grid; grid-template-columns: max-content max-content max-content; grid-template-columns: 1fr 1fr 2fr; - grid-column-gap: rem(36); - grid-row-gap: rem(12); + grid-column-gap: 36px; + grid-row-gap: 36px; } diff --git a/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/notice-of-intent-details.component.scss b/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/notice-of-intent-details.component.scss index f1242f912c..1e5282d2c5 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/notice-of-intent-details.component.scss +++ b/alcs-frontend/src/app/features/notice-of-intent/applicant-info/notice-of-intent-details/notice-of-intent-details.component.scss @@ -34,7 +34,7 @@ .no-data-text { text-align: center; color: colors.$grey; - padding-top: rem(12); + padding-top: 12px; .error { justify-content: center; diff --git a/alcs-frontend/src/app/features/notification/applicant-info/notification-details/notification-details.component.scss b/alcs-frontend/src/app/features/notification/applicant-info/notification-details/notification-details.component.scss index 14c855f966..eb20ba937e 100644 --- a/alcs-frontend/src/app/features/notification/applicant-info/notification-details/notification-details.component.scss +++ b/alcs-frontend/src/app/features/notification/applicant-info/notification-details/notification-details.component.scss @@ -28,7 +28,7 @@ .no-data-text { text-align: center; color: colors.$grey; - padding-top: rem(12); + padding-top: 12px; .error { justify-content: center; diff --git a/alcs-frontend/src/styles.scss b/alcs-frontend/src/styles.scss index 349eeebe37..2063f0258d 100644 --- a/alcs-frontend/src/styles.scss +++ b/alcs-frontend/src/styles.scss @@ -1,6 +1,7 @@ // Custom Theming for Angular Material // For more information: https://material.angular.io/guide/theming @use '@angular/material' as mat; +@use '@angular/material-experimental' as matx; @use '@ng-matero/extensions' as mtx; // Plus imports for other components in your app. @@ -132,20 +133,20 @@ $alcs-warn: mat.define-palette($mat-custom-warn); // Create the theme object. A theme consists of configurations for individual // theming systems such as "color" or "typography". $alcs-theme: mat.define-light-theme( - ( - color: ( - primary: $alcs-primary, - accent: $alcs-accent, - warn: $alcs-warn, - ), - ) + ( + color: ( + primary: $alcs-primary, + accent: $alcs-accent, + warn: $alcs-warn, + ), + ) ); // Include theme styles for core and each component used in your app. // Alternatively, you can import and @include the theme mixins for each component // that you are using. @include mat.all-component-themes($alcs-theme); - +@include mtx.all-component-themes($alcs-theme); /* You can add global styles to this file, and also import other style files */ html, @@ -321,3 +322,16 @@ mat-button-toggle-group { .ellipsis-3 { @include text-ellipsis($lines: 3); } + +.mat-primary { + &.mat-mdc-button-base { + --mat-fab-foreground-color: #fff; + --mdc-filled-button-label-text-color: #fff; + --mdc-protected-button-label-text-color: #fff; + } + + &.mat-mdc-fab, + &.mat-mdc-mini-fab { + --mat-icon-color: #fff; + } +} From d317b8144425a0322e0d536f33a2e6673465bd56 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Thu, 4 Apr 2024 10:54:04 -0700 Subject: [PATCH 100/153] Portal Frontend Convert uses of @ in html to @ --- .../authentication.service.spec.ts | 12 +- .../authentication/authentication.service.ts | 29 +- portal-frontend/angular.json | 41 +- portal-frontend/package-lock.json | 7333 ++++++++--------- portal-frontend/package.json | 48 +- .../application-details.component.html | 157 +- .../select-government.component.html | 26 +- .../select-government.component.spec.ts | 5 +- .../create-submission-dialog.component.html | 96 +- .../select-government.component.html | 26 +- .../select-government.component.spec.ts | 5 +- .../notice-of-intent-details.component.html | 91 +- .../select-government.component.html | 24 +- .../select-government.component.spec.ts | 7 +- .../success/success.component.html | 6 +- .../notification-details.component.html | 54 +- .../authentication/authentication.service.ts | 38 +- portal-frontend/src/styles.scss | 13 + .../apps/alcs/src/main.controller.spec.ts | 9 +- 19 files changed, 4032 insertions(+), 3988 deletions(-) diff --git a/alcs-frontend/src/app/services/authentication/authentication.service.spec.ts b/alcs-frontend/src/app/services/authentication/authentication.service.spec.ts index ec7a7b11b9..1559a81587 100644 --- a/alcs-frontend/src/app/services/authentication/authentication.service.spec.ts +++ b/alcs-frontend/src/app/services/authentication/authentication.service.spec.ts @@ -9,7 +9,9 @@ const mockToken = { exp: 1, }; -jest.mock('jwt-decode', () => (token: string) => mockToken); +jest.mock('jwt-decode', () => ({ + jwtDecode: (token: string) => mockToken, +})); describe('AuthenticationService', () => { let service: AuthenticationService; @@ -65,7 +67,7 @@ describe('AuthenticationService', () => { of({ refresh_token: 'newRefreshToken', token: 'newToken', - }) + }), ); await service.getToken(); @@ -78,7 +80,7 @@ describe('AuthenticationService', () => { httpClient.get.mockReturnValue( of({ url: fakeUrl, - }) + }), ); window = Object.create(window); @@ -118,8 +120,8 @@ describe('AuthenticationService', () => { new HttpErrorResponse({ error: fakeLoginUrl, status: 401, - }) - ) + }), + ), ); await service.getToken(); diff --git a/alcs-frontend/src/app/services/authentication/authentication.service.ts b/alcs-frontend/src/app/services/authentication/authentication.service.ts index 6d125a0a26..28ca19e9eb 100644 --- a/alcs-frontend/src/app/services/authentication/authentication.service.ts +++ b/alcs-frontend/src/app/services/authentication/authentication.service.ts @@ -1,7 +1,7 @@ import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; -import jwtDecode, { JwtPayload } from 'jwt-decode'; +import { jwtDecode, JwtPayload } from 'jwt-decode'; import { BehaviorSubject, firstValueFrom } from 'rxjs'; import { environment } from '../../../environments/environment'; @@ -30,15 +30,14 @@ export const ALL_ROLES = Object.values(ROLES); providedIn: 'root', }) export class AuthenticationService { + isInitialized = false; + $currentUser = new BehaviorSubject(undefined); + currentUser: ICurrentUser | undefined; private token: string | undefined; private refreshToken: string | undefined; private expires: number | undefined; private refreshExpires: number | undefined; - isInitialized = false; - $currentUser = new BehaviorSubject(undefined); - currentUser: ICurrentUser | undefined; - constructor( private http: HttpClient, private router: Router, @@ -92,6 +91,16 @@ export class AuthenticationService { } } + async logout() { + const logoutUrl = await this.getLogoutUrl(); + if (logoutUrl) { + this.clearTokens(); + window.location.href = logoutUrl.url; + } + } + + getCurrentUser = () => this.currentUser; + private async loadTokenFromStorage() { const existingToken = localStorage.getItem(JWT_TOKEN_KEY); const refreshToken = localStorage.getItem(REFRESH_TOKEN_KEY); @@ -102,14 +111,6 @@ export class AuthenticationService { } } - async logout() { - const logoutUrl = await this.getLogoutUrl(); - if (logoutUrl) { - this.clearTokens(); - window.location.href = logoutUrl.url; - } - } - private async isTokenValid(token: string) { try { await firstValueFrom( @@ -146,6 +147,4 @@ export class AuthenticationService { private async getLogoutUrl() { return firstValueFrom(this.http.get<{ url: string }>(`${environment.authUrl}/logout/alcs`)); } - - getCurrentUser = () => this.currentUser; } diff --git a/portal-frontend/angular.json b/portal-frontend/angular.json index f3564cd961..a3237670f5 100644 --- a/portal-frontend/angular.json +++ b/portal-frontend/angular.json @@ -23,10 +23,17 @@ "polyfills": "src/polyfills.ts", "tsConfig": "tsconfig.app.json", "inlineStyleLanguage": "scss", - "assets": ["src/favicon.ico", "src/assets"], - "styles": ["src/styles.scss"], + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "src/styles.scss" + ], "stylePreprocessorOptions": { - "includePaths": ["node_modules"] + "includePaths": [ + "node_modules" + ] }, "scripts": [] }, @@ -72,10 +79,10 @@ "builder": "@angular-devkit/build-angular:dev-server", "configurations": { "production": { - "browserTarget": "portal-frontend:build:production" + "buildTarget": "portal-frontend:build:production" }, "development": { - "browserTarget": "portal-frontend:build:development" + "buildTarget": "portal-frontend:build:development" } }, "defaultConfiguration": "development" @@ -83,17 +90,26 @@ "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { - "browserTarget": "portal-frontend:build" + "buildTarget": "portal-frontend:build" } }, "test": { "builder": "@angular-builders/jest:run", "options": { - "polyfills": ["src/polyfills.ts"], + "polyfills": [ + "src/polyfills.ts" + ], "tsConfig": "tsconfig.spec.json", - "inlineStyleLanguage": ["scss"], - "assets": ["src/favicon.ico", "src/assets"], - "styles": ["src/styles.scss"], + "inlineStyleLanguage": [ + "scss" + ], + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "src/styles.scss" + ], "scripts": [], "detectOpenHandles": true, "forceExit": true @@ -102,7 +118,10 @@ "lint": { "builder": "@angular-eslint/builder:lint", "options": { - "lintFilePatterns": ["src/**/*.ts", "src/**/*.html"] + "lintFilePatterns": [ + "src/**/*.ts", + "src/**/*.html" + ] } } } diff --git a/portal-frontend/package-lock.json b/portal-frontend/package-lock.json index a781f50aa8..aef166c5f5 100644 --- a/portal-frontend/package-lock.json +++ b/portal-frontend/package-lock.json @@ -8,38 +8,38 @@ "name": "portal-frontend", "version": "0.0.0", "dependencies": { - "@angular/animations": "^16.2.11", - "@angular/common": "^16.2.11", - "@angular/compiler": "^16.2.11", - "@angular/core": "^16.2.11", - "@angular/forms": "^16.2.11", - "@angular/material": "^16.2.11", - "@angular/material-moment-adapter": "^16.2.11", - "@angular/platform-browser": "^16.2.11", - "@angular/platform-browser-dynamic": "^16.2.11", - "@angular/router": "^16.2.11", + "@angular/animations": "^17.3.3", + "@angular/common": "^17.3.3", + "@angular/compiler": "^17.3.3", + "@angular/core": "^17.3.3", + "@angular/forms": "^17.3.3", + "@angular/material": "^17.3.3", + "@angular/material-moment-adapter": "^17.3.3", + "@angular/platform-browser": "^17.3.3", + "@angular/platform-browser-dynamic": "^17.3.3", + "@angular/router": "^17.3.3", "@bcgov/bc-sans": "^2.1.0", - "jwt-decode": "^3.1.2", - "ngx-mask": "^16.3.9", + "jwt-decode": "^4.0.0", + "ngx-mask": "^17.0.7", "rxjs": "~7.8.1", "source-map-support": "^0.5.21", "tslib": "^2.6.2", - "zone.js": "~0.13.3" + "zone.js": "~0.14.4" }, "devDependencies": { - "@angular-builders/jest": "^16.0.1", - "@angular-devkit/build-angular": "^16.2.9", - "@angular/cli": "~16.2.9", - "@angular/compiler-cli": "^16.2.11", + "@angular-builders/jest": "^17.0.3", + "@angular-devkit/build-angular": "^17.3.3", + "@angular/cli": "~17.3.3", + "@angular/compiler-cli": "^17.3.3", "@golevelup/ts-jest": "^0.4.0", - "@types/jest": "^29.5.7", - "@typescript-eslint/eslint-plugin": "5.62.0", - "@typescript-eslint/parser": "5.62.0", - "eslint": "^8.52.0", + "@types/jest": "^29.5.12", + "@typescript-eslint/eslint-plugin": "7.5.0", + "@typescript-eslint/parser": "7.5.0", + "eslint": "^8.57.0", "jest": "^29.7.0", - "jest-preset-angular": "^13.1.2", - "prettier": "^2.8.8", - "typescript": "4.9.4" + "jest-preset-angular": "^14.0.3", + "prettier": "^3.2.5", + "typescript": "5.4.3" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -52,175 +52,162 @@ } }, "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, - "node_modules/@angular-builders/jest": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/@angular-builders/jest/-/jest-16.0.1.tgz", - "integrity": "sha512-FuYkfy8JwdfTHevjgs8z18sXt0egcWbSSkefyM/QsGVkMHs+b4N4xzV20MQtyx0Yc6nJzuknIH5ZvDwLYUDPyQ==", + "node_modules/@angular-builders/common": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@angular-builders/common/-/common-1.0.2.tgz", + "integrity": "sha512-lUusRq6jN1It5LcUTLS6Q+AYAYGTo/EEN8hV0M6Ek9qXzweAouJaSEnwv7p04/pD7yJTl0YOCbN79u+wGm3x4g==", "dev": true, "dependencies": { - "@angular-devkit/architect": ">=0.1600.0 < 0.1700.0", - "@angular-devkit/core": "^16.0.0", - "jest-preset-angular": "13.1.1", - "lodash": "^4.17.15", + "@angular-devkit/core": "^17.1.0", + "ts-node": "^10.0.0", "tsconfig-paths": "^4.1.0" }, "engines": { "node": "^14.20.0 || ^16.13.0 || >=18.10.0" - }, - "peerDependencies": { - "@angular-devkit/build-angular": "^16.0.0", - "@angular/compiler-cli": "^16.0.0", - "@angular/core": "^16.0.0", - "@angular/platform-browser-dynamic": "^16.0.0", - "jest": ">=29" } }, - "node_modules/@angular-builders/jest/node_modules/jest-preset-angular": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/jest-preset-angular/-/jest-preset-angular-13.1.1.tgz", - "integrity": "sha512-X8i7icKt9U5uhj7YKqdEZm7ZZPvNFRxfBnU+9SALdIkHYJhwtlJ5/MUk9wo4f3lX2smOkIl9LPJUu1APO+11Jg==", + "node_modules/@angular-builders/jest": { + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/@angular-builders/jest/-/jest-17.0.3.tgz", + "integrity": "sha512-LW4s8t+NLnWR7Aud+EZup8dOBfQF8rfOIncsarDtP/48rz/Ucnzvum7xEt/NYAlZ6y/Dpk7wO6SlqAsaOPf8mA==", "dev": true, "dependencies": { - "bs-logger": "^0.2.6", - "esbuild-wasm": ">=0.13.8", - "jest-environment-jsdom": "^29.0.0", - "jest-util": "^29.0.0", - "pretty-format": "^29.0.0", - "ts-jest": "^29.0.0" + "@angular-builders/common": "1.0.2", + "@angular-devkit/architect": ">=0.1700.0 < 0.1800.0", + "@angular-devkit/core": "^17.0.0", + "jest-preset-angular": "14.0.3", + "lodash": "^4.17.15" }, "engines": { - "node": "^14.15.0 || >=16.10.0" - }, - "optionalDependencies": { - "esbuild": ">=0.13.8" + "node": "^14.20.0 || ^16.13.0 || >=18.10.0" }, "peerDependencies": { - "@angular-devkit/build-angular": ">=13.0.0 <17.0.0", - "@angular/compiler-cli": ">=13.0.0 <17.0.0", - "@angular/core": ">=13.0.0 <17.0.0", - "@angular/platform-browser-dynamic": ">=13.0.0 <17.0.0", - "jest": "^29.0.0", - "typescript": ">=4.4" + "@angular-devkit/build-angular": "^17.0.0", + "@angular/compiler-cli": "^17.0.0", + "@angular/core": "^17.0.0", + "@angular/platform-browser-dynamic": "^17.0.0", + "jest": ">=29" } }, "node_modules/@angular-devkit/architect": { - "version": "0.1602.9", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1602.9.tgz", - "integrity": "sha512-U3vfb/e2sFfg0D9FyyRBXRPP7g4FBFtGK8Q3JPmvAVsHHwi5AUFRNR7YBChB/T5TMNY077HcTyEirVh2FeUpdA==", + "version": "0.1703.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1703.3.tgz", + "integrity": "sha512-BKbdigCjmspqxOxSIQuWgPZzpyuKqZoTBDh0jDeLcAmvPsuxCgIWbsExI4OQ0CyusnQ+XT0IT39q8B9rvF56cg==", "dev": true, "dependencies": { - "@angular-devkit/core": "16.2.9", + "@angular-devkit/core": "17.3.3", "rxjs": "7.8.1" }, "engines": { - "node": "^16.14.0 || >=18.10.0", + "node": "^18.13.0 || >=20.9.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, "node_modules/@angular-devkit/build-angular": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-16.2.9.tgz", - "integrity": "sha512-S1C4UYxRVyNt3C0wCxbT2jZ1dN5i37kS0mol3PQjbR8gQ0GQzHmzhjTBl1oImo8aouET9yhrk9etk65oat4mBQ==", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-17.3.3.tgz", + "integrity": "sha512-E/6Z1MIMhEB1I2sN+Pw4/zinwAFj4vLDh6dEuj856WWEPndgPiUB6fGX4EbCTsyIUzboXI5ysdNyt2Eq56bllA==", "dev": true, "dependencies": { - "@ampproject/remapping": "2.2.1", - "@angular-devkit/architect": "0.1602.9", - "@angular-devkit/build-webpack": "0.1602.9", - "@angular-devkit/core": "16.2.9", - "@babel/core": "7.22.9", - "@babel/generator": "7.22.9", + "@ampproject/remapping": "2.3.0", + "@angular-devkit/architect": "0.1703.3", + "@angular-devkit/build-webpack": "0.1703.3", + "@angular-devkit/core": "17.3.3", + "@babel/core": "7.24.0", + "@babel/generator": "7.23.6", "@babel/helper-annotate-as-pure": "7.22.5", "@babel/helper-split-export-declaration": "7.22.6", - "@babel/plugin-proposal-async-generator-functions": "7.20.7", - "@babel/plugin-transform-async-to-generator": "7.22.5", - "@babel/plugin-transform-runtime": "7.22.9", - "@babel/preset-env": "7.22.9", - "@babel/runtime": "7.22.6", - "@babel/template": "7.22.5", + "@babel/plugin-transform-async-generator-functions": "7.23.9", + "@babel/plugin-transform-async-to-generator": "7.23.3", + "@babel/plugin-transform-runtime": "7.24.0", + "@babel/preset-env": "7.24.0", + "@babel/runtime": "7.24.0", "@discoveryjs/json-ext": "0.5.7", - "@ngtools/webpack": "16.2.9", - "@vitejs/plugin-basic-ssl": "1.0.1", + "@ngtools/webpack": "17.3.3", + "@vitejs/plugin-basic-ssl": "1.1.0", "ansi-colors": "4.1.3", - "autoprefixer": "10.4.14", + "autoprefixer": "10.4.18", "babel-loader": "9.1.3", "babel-plugin-istanbul": "6.1.1", "browserslist": "^4.21.5", - "chokidar": "3.5.3", "copy-webpack-plugin": "11.0.0", - "critters": "0.0.20", - "css-loader": "6.8.1", - "esbuild-wasm": "0.18.17", - "fast-glob": "3.3.1", - "guess-parser": "0.4.22", - "https-proxy-agent": "5.0.1", - "inquirer": "8.2.4", - "jsonc-parser": "3.2.0", + "critters": "0.0.22", + "css-loader": "6.10.0", + "esbuild-wasm": "0.20.1", + "fast-glob": "3.3.2", + "http-proxy-middleware": "2.0.6", + "https-proxy-agent": "7.0.4", + "inquirer": "9.2.15", + "jsonc-parser": "3.2.1", "karma-source-map-support": "1.4.0", - "less": "4.1.3", + "less": "4.2.0", "less-loader": "11.1.0", "license-webpack-plugin": "4.0.2", "loader-utils": "3.2.1", - "magic-string": "0.30.1", - "mini-css-extract-plugin": "2.7.6", - "mrmime": "1.0.1", + "magic-string": "0.30.8", + "mini-css-extract-plugin": "2.8.1", + "mrmime": "2.0.0", "open": "8.4.2", "ora": "5.4.1", "parse5-html-rewriting-stream": "7.0.0", - "picomatch": "2.3.1", - "piscina": "4.0.0", - "postcss": "8.4.31", - "postcss-loader": "7.3.3", + "picomatch": "4.0.1", + "piscina": "4.4.0", + "postcss": "8.4.35", + "postcss-loader": "8.1.1", "resolve-url-loader": "5.0.0", "rxjs": "7.8.1", - "sass": "1.64.1", - "sass-loader": "13.3.2", - "semver": "7.5.4", - "source-map-loader": "4.0.1", + "sass": "1.71.1", + "sass-loader": "14.1.1", + "semver": "7.6.0", + "source-map-loader": "5.0.0", "source-map-support": "0.5.21", - "terser": "5.19.2", - "text-table": "0.2.0", + "terser": "5.29.1", "tree-kill": "1.2.2", - "tslib": "2.6.1", - "vite": "4.4.7", - "webpack": "5.88.2", - "webpack-dev-middleware": "6.1.1", + "tslib": "2.6.2", + "undici": "6.7.1", + "vite": "5.1.5", + "watchpack": "2.4.0", + "webpack": "5.90.3", + "webpack-dev-middleware": "6.1.2", "webpack-dev-server": "4.15.1", - "webpack-merge": "5.9.0", + "webpack-merge": "5.10.0", "webpack-subresource-integrity": "5.1.0" }, "engines": { - "node": "^16.14.0 || >=18.10.0", + "node": "^18.13.0 || >=20.9.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, "optionalDependencies": { - "esbuild": "0.18.17" + "esbuild": "0.20.1" }, "peerDependencies": { - "@angular/compiler-cli": "^16.0.0", - "@angular/localize": "^16.0.0", - "@angular/platform-server": "^16.0.0", - "@angular/service-worker": "^16.0.0", + "@angular/compiler-cli": "^17.0.0", + "@angular/localize": "^17.0.0", + "@angular/platform-server": "^17.0.0", + "@angular/service-worker": "^17.0.0", + "@web/test-runner": "^0.18.0", + "browser-sync": "^3.0.2", "jest": "^29.5.0", "jest-environment-jsdom": "^29.5.0", "karma": "^6.3.0", - "ng-packagr": "^16.0.0", + "ng-packagr": "^17.0.0", "protractor": "^7.0.0", "tailwindcss": "^2.0.0 || ^3.0.0", - "typescript": ">=4.9.3 <5.2" + "typescript": ">=5.2 <5.5" }, "peerDependenciesMeta": { "@angular/localize": { @@ -232,6 +219,12 @@ "@angular/service-worker": { "optional": true }, + "@web/test-runner": { + "optional": true + }, + "browser-sync": { + "optional": true + }, "jest": { "optional": true }, @@ -252,400 +245,887 @@ } } }, - "node_modules/@angular-devkit/build-angular/node_modules/tslib": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", - "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==", - "dev": true - }, - "node_modules/@angular-devkit/build-webpack": { - "version": "0.1602.9", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1602.9.tgz", - "integrity": "sha512-+3IxovfBPR2Vy730mGa0SVKkd5LQVom85gjXOs7WcnnnZmfc1q/BtFlqTgW1UWvTxP8IQdm7UYWVclQfL/WExw==", + "node_modules/@angular-devkit/build-angular/node_modules/@babel/core": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz", + "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1602.9", - "rxjs": "7.8.1" + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.24.0", + "@babel/parser": "^7.24.0", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.0", + "@babel/types": "^7.24.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { - "node": "^16.14.0 || >=18.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "node": ">=6.9.0" }, - "peerDependencies": { - "webpack": "^5.30.0", - "webpack-dev-server": "^4.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@angular-devkit/core": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.9.tgz", - "integrity": "sha512-dcHWjHBNGm3yCeNz19y8A1At4KgyC6XHNnbFL0y+nnZYiaESXjUoXJYKASedI6A+Bpl0HNq2URhH6bL6Af3+4w==", + "node_modules/@angular-devkit/build-angular/node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "dependencies": { - "ajv": "8.12.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.2.0", - "picomatch": "2.3.1", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^16.14.0 || >=18.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@angular-devkit/schematics": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.2.9.tgz", - "integrity": "sha512-lB51CGCILpcSI37CwKUAGDLxMqh7zmuRbiPo9s9mSkCM4ccqxFlaL+VFTq2/laneARD6aikpOHnkVm5myNzQPw==", + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/aix-ppc64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.1.tgz", + "integrity": "sha512-m55cpeupQ2DbuRGQMMZDzbv9J9PgVelPjlcmM5kxHnrBdBx6REaEd7LamYV7Dm8N7rCyR/XwU6rVP8ploKtIkA==", + "cpu": [ + "ppc64" + ], "dev": true, - "dependencies": { - "@angular-devkit/core": "16.2.9", - "jsonc-parser": "3.2.0", - "magic-string": "0.30.1", - "ora": "5.4.1", - "rxjs": "7.8.1" - }, + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": "^16.14.0 || >=18.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "node": ">=12" } }, - "node_modules/@angular/animations": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-16.2.11.tgz", - "integrity": "sha512-xdLYXsGi7OuJawhiVIppl2VkPHhPdxUP/nR6+ETR3TdAscVruCWJs4z9XKval4fbik/brekbFNFuYtlx6csDhQ==", - "dependencies": { - "tslib": "^2.3.0" - }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/android-arm": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.1.tgz", + "integrity": "sha512-4j0+G27/2ZXGWR5okcJi7pQYhmkVgb4D7UKwxcqrjhvp5TKWx3cUjgB1CGj1mfdmJBQ9VnUGgUhign+FPF2Zgw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^16.14.0 || >=18.10.0" - }, - "peerDependencies": { - "@angular/core": "16.2.11" + "node": ">=12" } }, - "node_modules/@angular/cdk": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-16.2.11.tgz", - "integrity": "sha512-FcJ9xd9ptjULdScnBNg7YkVnY9NKePFfmvvs2zt841Hd489L8BUkTUdbvtCLhMJTTSN+k+D+RYFhevZuhPKVVg==", - "peer": true, - "dependencies": { - "tslib": "^2.3.0" - }, - "optionalDependencies": { - "parse5": "^7.1.2" - }, - "peerDependencies": { - "@angular/common": "^16.0.0 || ^17.0.0", - "@angular/core": "^16.0.0 || ^17.0.0", - "rxjs": "^6.5.3 || ^7.4.0" + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/android-arm64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.1.tgz", + "integrity": "sha512-hCnXNF0HM6AjowP+Zou0ZJMWWa1VkD77BXe959zERgGJBBxB+sV+J9f/rcjeg2c5bsukD/n17RKWXGFCO5dD5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@angular/cli": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-16.2.9.tgz", - "integrity": "sha512-wkpV/Ni26LUeDmhee2TPXXEq3feEdZMSG8+nkfUK9kqIcxm0IjI1GLPeiVOX7aQobuKNe2cCAFNwsrXWjj+2og==", + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/android-x64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.1.tgz", + "integrity": "sha512-MSfZMBoAsnhpS+2yMFYIQUPs8Z19ajwfuaSZx+tSl09xrHZCjbeXXMsUF/0oq7ojxYEpsSo4c0SfjxOYXRbpaA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@angular-devkit/architect": "0.1602.9", - "@angular-devkit/core": "16.2.9", - "@angular-devkit/schematics": "16.2.9", - "@schematics/angular": "16.2.9", - "@yarnpkg/lockfile": "1.1.0", - "ansi-colors": "4.1.3", - "ini": "4.1.1", - "inquirer": "8.2.4", - "jsonc-parser": "3.2.0", - "npm-package-arg": "10.1.0", - "npm-pick-manifest": "8.0.1", - "open": "8.4.2", - "ora": "5.4.1", - "pacote": "15.2.0", - "resolve": "1.22.2", - "semver": "7.5.4", - "symbol-observable": "4.0.0", - "yargs": "17.7.2" - }, - "bin": { - "ng": "bin/ng.js" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^16.14.0 || >=18.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "node": ">=12" } }, - "node_modules/@angular/common": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-16.2.11.tgz", - "integrity": "sha512-h80WUR2OYlqxQy+4XgNtWT2vB+vZ6oCrFX/q8cU5jAvbvGQfJuH0zfcbSlUflStmAhk5/OT25F0mt96cqapEAw==", - "dependencies": { - "tslib": "^2.3.0" - }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/darwin-arm64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.1.tgz", + "integrity": "sha512-Ylk6rzgMD8klUklGPzS414UQLa5NPXZD5tf8JmQU8GQrj6BrFA/Ic9tb2zRe1kOZyCbGl+e8VMbDRazCEBqPvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^16.14.0 || >=18.10.0" - }, - "peerDependencies": { - "@angular/core": "16.2.11", - "rxjs": "^6.5.3 || ^7.4.0" + "node": ">=12" } }, - "node_modules/@angular/compiler": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-16.2.11.tgz", - "integrity": "sha512-9q/E3uurvoQbdTTWDyWCLpzmfJ4+et7SUca1/EljD/X7Xg2FNU5GpTMutBtWFL7wDyWk1oswivuq9/C4GVW7fA==", - "dependencies": { - "tslib": "^2.3.0" - }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/darwin-x64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.1.tgz", + "integrity": "sha512-pFIfj7U2w5sMp52wTY1XVOdoxw+GDwy9FsK3OFz4BpMAjvZVs0dT1VXs8aQm22nhwoIWUmIRaE+4xow8xfIDZA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^16.14.0 || >=18.10.0" - }, - "peerDependencies": { - "@angular/core": "16.2.11" - }, - "peerDependenciesMeta": { - "@angular/core": { - "optional": true - } + "node": ">=12" } }, - "node_modules/@angular/compiler-cli": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-16.2.11.tgz", - "integrity": "sha512-ZtZCXfkVBH78HUm2Byf+WX3Y6WzQK9EXYXNU/ni1rvSZ1vLNwieLDfWb/xwiO7QojrHZTym1RJ10jTMinTguqw==", + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.1.tgz", + "integrity": "sha512-UyW1WZvHDuM4xDz0jWun4qtQFauNdXjXOtIy7SYdf7pbxSWWVlqhnR/T2TpX6LX5NI62spt0a3ldIIEkPM6RHw==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@babel/core": "7.23.2", - "@jridgewell/sourcemap-codec": "^1.4.14", - "chokidar": "^3.0.0", - "convert-source-map": "^1.5.1", - "reflect-metadata": "^0.1.2", - "semver": "^7.0.0", - "tslib": "^2.3.0", - "yargs": "^17.2.1" - }, - "bin": { - "ng-xi18n": "bundles/src/bin/ng_xi18n.js", - "ngc": "bundles/src/bin/ngc.js", - "ngcc": "bundles/ngcc/index.js" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "^16.14.0 || >=18.10.0" - }, - "peerDependencies": { - "@angular/compiler": "16.2.11", - "typescript": ">=4.9.3 <5.2" + "node": ">=12" } }, - "node_modules/@angular/compiler-cli/node_modules/@babel/core": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", - "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/freebsd-x64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.1.tgz", + "integrity": "sha512-itPwCw5C+Jh/c624vcDd9kRCCZVpzpQn8dtwoYIt2TJF3S9xJLiRohnnNrKwREvcZYx0n8sCSbvGH349XkcQeg==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-module-transforms": "^7.23.0", - "@babel/helpers": "^7.23.2", - "@babel/parser": "^7.23.0", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" + "node": ">=12" } }, - "node_modules/@angular/compiler-cli/node_modules/@babel/core/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "node_modules/@angular/compiler-cli/node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-arm": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.1.tgz", + "integrity": "sha512-LojC28v3+IhIbfQ+Vu4Ut5n3wKcgTu6POKIHN9Wpt0HnfgUGlBuyDDQR4jWZUZFyYLiz4RBBBmfU6sNfn6RhLw==", + "cpu": [ + "arm" + ], "dev": true, - "bin": { - "semver": "bin/semver.js" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@angular/compiler-cli/node_modules/@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-arm64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.1.tgz", + "integrity": "sha512-cX8WdlF6Cnvw/DO9/X7XLH2J6CkBnz7Twjpk56cshk9sjYVcuh4sXQBy5bmTwzBjNVZze2yaV1vtcJS04LbN8w==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@babel/types": "^7.23.0", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" + "node": ">=12" } }, - "node_modules/@angular/compiler-cli/node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-ia32": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.1.tgz", + "integrity": "sha512-4H/sQCy1mnnGkUt/xszaLlYJVTz3W9ep52xEefGtd6yXDQbz/5fZE5dFLUgsPdbUOQANcVUa5iO6g3nyy5BJiw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-loong64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.1.tgz", + "integrity": "sha512-c0jgtB+sRHCciVXlyjDcWb2FUuzlGVRwGXgI+3WqKOIuoo8AmZAddzeOHeYLtD+dmtHw3B4Xo9wAUdjlfW5yYA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-mips64el": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.1.tgz", + "integrity": "sha512-TgFyCfIxSujyuqdZKDZ3yTwWiGv+KnlOeXXitCQ+trDODJ+ZtGOzLkSWngynP0HZnTsDyBbPy7GWVXWaEl6lhA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-ppc64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.1.tgz", + "integrity": "sha512-b+yuD1IUeL+Y93PmFZDZFIElwbmFfIKLKlYI8M6tRyzE6u7oEP7onGk0vZRh8wfVGC2dZoy0EqX1V8qok4qHaw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-riscv64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.1.tgz", + "integrity": "sha512-wpDlpE0oRKZwX+GfomcALcouqjjV8MIX8DyTrxfyCfXxoKQSDm45CZr9fanJ4F6ckD4yDEPT98SrjvLwIqUCgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-s390x": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.1.tgz", + "integrity": "sha512-5BepC2Au80EohQ2dBpyTquqGCES7++p7G+7lXe1bAIvMdXm4YYcEfZtQrP4gaoZ96Wv1Ute61CEHFU7h4FMueQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-x64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.1.tgz", + "integrity": "sha512-5gRPk7pKuaIB+tmH+yKd2aQTRpqlf1E4f/mC+tawIm/CGJemZcHZpp2ic8oD83nKgUPMEd0fNanrnFljiruuyA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/netbsd-x64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.1.tgz", + "integrity": "sha512-4fL68JdrLV2nVW2AaWZBv3XEm3Ae3NZn/7qy2KGAt3dexAgSVT+Hc97JKSZnqezgMlv9x6KV0ZkZY7UO5cNLCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/openbsd-x64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.1.tgz", + "integrity": "sha512-GhRuXlvRE+twf2ES+8REbeCb/zeikNqwD3+6S5y5/x+DYbAQUNl0HNBs4RQJqrechS4v4MruEr8ZtAin/hK5iw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/sunos-x64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.1.tgz", + "integrity": "sha512-ZnWEyCM0G1Ex6JtsygvC3KUUrlDXqOihw8RicRuQAzw+c4f1D66YlPNNV3rkjVW90zXVsHwZYWbJh3v+oQFM9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/win32-arm64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.1.tgz", + "integrity": "sha512-QZ6gXue0vVQY2Oon9WyLFCdSuYbXSoxaZrPuJ4c20j6ICedfsDilNPYfHLlMH7vGfU5DQR0czHLmJvH4Nzis/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/win32-ia32": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.1.tgz", + "integrity": "sha512-HzcJa1NcSWTAU0MJIxOho8JftNp9YALui3o+Ny7hCh0v5f90nprly1U3Sj1Ldj/CvKKdvvFsCRvDkpsEMp4DNw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/win32-x64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.1.tgz", + "integrity": "sha512-0MBh53o6XtI6ctDnRMeQ+xoCN8kD2qI1rY1KgF/xdWQwoFeKou7puvDfV8/Wv4Ctx2rRpET/gGdz3YlNtNACSA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@angular-devkit/build-angular/node_modules/esbuild": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.1.tgz", + "integrity": "sha512-OJwEgrpWm/PCMsLVWXKqvcjme3bHNpOgN7Tb6cQnR5n0TPbQx1/Xrn7rqM+wn17bYeT6MGB5sn1Bh5YiGi70nA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.1", + "@esbuild/android-arm": "0.20.1", + "@esbuild/android-arm64": "0.20.1", + "@esbuild/android-x64": "0.20.1", + "@esbuild/darwin-arm64": "0.20.1", + "@esbuild/darwin-x64": "0.20.1", + "@esbuild/freebsd-arm64": "0.20.1", + "@esbuild/freebsd-x64": "0.20.1", + "@esbuild/linux-arm": "0.20.1", + "@esbuild/linux-arm64": "0.20.1", + "@esbuild/linux-ia32": "0.20.1", + "@esbuild/linux-loong64": "0.20.1", + "@esbuild/linux-mips64el": "0.20.1", + "@esbuild/linux-ppc64": "0.20.1", + "@esbuild/linux-riscv64": "0.20.1", + "@esbuild/linux-s390x": "0.20.1", + "@esbuild/linux-x64": "0.20.1", + "@esbuild/netbsd-x64": "0.20.1", + "@esbuild/openbsd-x64": "0.20.1", + "@esbuild/sunos-x64": "0.20.1", + "@esbuild/win32-arm64": "0.20.1", + "@esbuild/win32-ia32": "0.20.1", + "@esbuild/win32-x64": "0.20.1" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/picomatch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", + "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@angular-devkit/build-webpack": { + "version": "0.1703.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1703.3.tgz", + "integrity": "sha512-d0JjE8MaGVNphlJfeP1OZKhNT4wCXkEZKdSdwE0+W+vDHNUuZiUBB1czO48sb7T4xBrdjRWlV/9CzMNJ7n3ydA==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "0.1703.3", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "webpack": "^5.30.0", + "webpack-dev-server": "^4.0.0" + } + }, + "node_modules/@angular-devkit/core": { + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.3.tgz", + "integrity": "sha512-J22Sh3M7rj8Ar3iEs20ko5wgC3DE7vWfYZNdimt2IJiS4J7BEX8R3Awf+TRt+6AN3NFm3/xe1Sz4yvDh3FvNFg==", + "dev": true, + "dependencies": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.1", + "picomatch": "4.0.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/core/node_modules/picomatch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", + "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@angular-devkit/schematics": { + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.3.3.tgz", + "integrity": "sha512-SABqTtj2im4PJhQjNaAsSypbNkpZFW8YozJ3P748tlh5a9XoHpgiqXv5JhRbyKElLDAyk5i9fe2++JmSudPG/Q==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "17.3.3", + "jsonc-parser": "3.2.1", + "magic-string": "0.30.8", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/animations": { + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-17.3.3.tgz", + "integrity": "sha512-poLW3FHe5wkxmTIsQ3em2vq4obgQHyZJz6biF+4hCqQSNMbMBS0e5ZycAiJLkUD/WLc88lQZ20muRO7qjVuMLA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/core": "17.3.3" + } + }, + "node_modules/@angular/cdk": { + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-17.3.3.tgz", + "integrity": "sha512-hfS9pwaNE6CTZqP3FBh9tZPbuf//bDqZ5IpMzscfDFrwX8ycxBiI3znH/rFSf9l1rL0OQGoqWWNVfJCT+RrukA==", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "optionalDependencies": { + "parse5": "^7.1.2" + }, + "peerDependencies": { + "@angular/common": "^17.0.0 || ^18.0.0", + "@angular/core": "^17.0.0 || ^18.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/cli": { + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-17.3.3.tgz", + "integrity": "sha512-veIGK2sRm0SfiLHeftx0W0xC3N8uxoqxXiSG57V6W2wIFN/fKm3aRq3sa8phz7vxUzoKGqyZh6hsT7ybkjgkGA==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "0.1703.3", + "@angular-devkit/core": "17.3.3", + "@angular-devkit/schematics": "17.3.3", + "@schematics/angular": "17.3.3", + "@yarnpkg/lockfile": "1.1.0", + "ansi-colors": "4.1.3", + "ini": "4.1.2", + "inquirer": "9.2.15", + "jsonc-parser": "3.2.1", + "npm-package-arg": "11.0.1", + "npm-pick-manifest": "9.0.0", + "open": "8.4.2", + "ora": "5.4.1", + "pacote": "17.0.6", + "resolve": "1.22.8", + "semver": "7.6.0", + "symbol-observable": "4.0.0", + "yargs": "17.7.2" + }, + "bin": { + "ng": "bin/ng.js" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/cli/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular/cli/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular/cli/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@angular/common": { + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-17.3.3.tgz", + "integrity": "sha512-GwlKetNpfWKiG2j4S6bYTi6PA2iT4+eln7o8owo44xZWdQnWQjfxnH39vQuCyhi6OOQL1dozmae+fVXgQsV6jQ==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/core": "17.3.3", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/compiler": { + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-17.3.3.tgz", + "integrity": "sha512-ZNMRfagMxMjk1KW5H3ssCg5QL0J6ZW1JAZ1mrTXixqS7gbdwl60bTGE+EfuEwbjvovEYaj4l9cga47eMaxZTbQ==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/core": "17.3.3" + }, + "peerDependenciesMeta": { + "@angular/core": { + "optional": true + } + } + }, + "node_modules/@angular/compiler-cli": { + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.3.3.tgz", + "integrity": "sha512-vM0lqwuXQZ912HbLnIuvUblvIz2WEUsU7a5Z2ieNey6famH4zxPH12vCbVwXgicB6GLHorhOfcWC5443wD2mJw==", + "dev": true, + "dependencies": { + "@babel/core": "7.23.9", + "@jridgewell/sourcemap-codec": "^1.4.14", + "chokidar": "^3.0.0", + "convert-source-map": "^1.5.1", + "reflect-metadata": "^0.2.0", + "semver": "^7.0.0", + "tslib": "^2.3.0", + "yargs": "^17.2.1" + }, + "bin": { + "ng-xi18n": "bundles/src/bin/ng_xi18n.js", + "ngc": "bundles/src/bin/ngc.js", + "ngcc": "bundles/ngcc/index.js" }, "engines": { - "node": ">=6.9.0" + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/compiler": "17.3.3", + "typescript": ">=5.2 <5.5" } }, "node_modules/@angular/core": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-16.2.11.tgz", - "integrity": "sha512-Jb+7/p1vczQRQ3iC1QxUS5cE4X1hPVAvbrFnyMpSx6Pq5o274v/lK6PvhUZrfKrp9FxFp9pN+WHjUqNFqOuJZg==", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-17.3.3.tgz", + "integrity": "sha512-O/jr3aFJMCxF6Jmymjx4jIigRHJfqM/ALIi60y2LVznBVFkk9xyMTsAjgWQIEHX+2muEIzgfKuXzpL0y30y+wA==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^16.14.0 || >=18.10.0" + "node": "^18.13.0 || >=20.9.0" }, "peerDependencies": { "rxjs": "^6.5.3 || ^7.4.0", - "zone.js": "~0.13.0" + "zone.js": "~0.14.0" } }, "node_modules/@angular/forms": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-16.2.11.tgz", - "integrity": "sha512-2powweUorehB1opfev6/sUeb3Bdey+Txq4gjI1Qdeo9c9OgtaKu6wK0KXgoism8HXXRFcGHMfS0dUVoDPVrtiQ==", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-17.3.3.tgz", + "integrity": "sha512-wqn+eAggbOZY91hr7oDjv5qdflszVOC9SZMcWJUoZTGn+8eoV6v6728GDFuDDwYkKQ9G9eQbX4IZmYoVw3TVjQ==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^16.14.0 || >=18.10.0" + "node": "^18.13.0 || >=20.9.0" }, "peerDependencies": { - "@angular/common": "16.2.11", - "@angular/core": "16.2.11", - "@angular/platform-browser": "16.2.11", + "@angular/common": "17.3.3", + "@angular/core": "17.3.3", + "@angular/platform-browser": "17.3.3", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/material": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-16.2.11.tgz", - "integrity": "sha512-hrkRD9/38++nIyo3k/KQpxsIaWm+FOJVmoJa83qvwZZt+fHKfT7xaNvRPZ+L2oqFaAvH5ivnL1u1nDuDyWz/0w==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/auto-init": "15.0.0-canary.bc9ae6c9c.0", - "@material/banner": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/button": "15.0.0-canary.bc9ae6c9c.0", - "@material/card": "15.0.0-canary.bc9ae6c9c.0", - "@material/checkbox": "15.0.0-canary.bc9ae6c9c.0", - "@material/chips": "15.0.0-canary.bc9ae6c9c.0", - "@material/circular-progress": "15.0.0-canary.bc9ae6c9c.0", - "@material/data-table": "15.0.0-canary.bc9ae6c9c.0", - "@material/density": "15.0.0-canary.bc9ae6c9c.0", - "@material/dialog": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/drawer": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/fab": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/floating-label": "15.0.0-canary.bc9ae6c9c.0", - "@material/form-field": "15.0.0-canary.bc9ae6c9c.0", - "@material/icon-button": "15.0.0-canary.bc9ae6c9c.0", - "@material/image-list": "15.0.0-canary.bc9ae6c9c.0", - "@material/layout-grid": "15.0.0-canary.bc9ae6c9c.0", - "@material/line-ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/linear-progress": "15.0.0-canary.bc9ae6c9c.0", - "@material/list": "15.0.0-canary.bc9ae6c9c.0", - "@material/menu": "15.0.0-canary.bc9ae6c9c.0", - "@material/menu-surface": "15.0.0-canary.bc9ae6c9c.0", - "@material/notched-outline": "15.0.0-canary.bc9ae6c9c.0", - "@material/radio": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/segmented-button": "15.0.0-canary.bc9ae6c9c.0", - "@material/select": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/slider": "15.0.0-canary.bc9ae6c9c.0", - "@material/snackbar": "15.0.0-canary.bc9ae6c9c.0", - "@material/switch": "15.0.0-canary.bc9ae6c9c.0", - "@material/tab": "15.0.0-canary.bc9ae6c9c.0", - "@material/tab-bar": "15.0.0-canary.bc9ae6c9c.0", - "@material/tab-indicator": "15.0.0-canary.bc9ae6c9c.0", - "@material/tab-scroller": "15.0.0-canary.bc9ae6c9c.0", - "@material/textfield": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tooltip": "15.0.0-canary.bc9ae6c9c.0", - "@material/top-app-bar": "15.0.0-canary.bc9ae6c9c.0", - "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-17.3.3.tgz", + "integrity": "sha512-cb3PYY+Lf3FvXxXIRmOBcTn5QS9Ghr5Eq0aiJiiYV6YVohr0YGWsndMCZ/5a2j8fxpboDo9THeTnOuuAOJv7AA==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/auto-init": "15.0.0-canary.7f224ddd4.0", + "@material/banner": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/button": "15.0.0-canary.7f224ddd4.0", + "@material/card": "15.0.0-canary.7f224ddd4.0", + "@material/checkbox": "15.0.0-canary.7f224ddd4.0", + "@material/chips": "15.0.0-canary.7f224ddd4.0", + "@material/circular-progress": "15.0.0-canary.7f224ddd4.0", + "@material/data-table": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dialog": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/drawer": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/fab": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/floating-label": "15.0.0-canary.7f224ddd4.0", + "@material/form-field": "15.0.0-canary.7f224ddd4.0", + "@material/icon-button": "15.0.0-canary.7f224ddd4.0", + "@material/image-list": "15.0.0-canary.7f224ddd4.0", + "@material/layout-grid": "15.0.0-canary.7f224ddd4.0", + "@material/line-ripple": "15.0.0-canary.7f224ddd4.0", + "@material/linear-progress": "15.0.0-canary.7f224ddd4.0", + "@material/list": "15.0.0-canary.7f224ddd4.0", + "@material/menu": "15.0.0-canary.7f224ddd4.0", + "@material/menu-surface": "15.0.0-canary.7f224ddd4.0", + "@material/notched-outline": "15.0.0-canary.7f224ddd4.0", + "@material/radio": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/segmented-button": "15.0.0-canary.7f224ddd4.0", + "@material/select": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/slider": "15.0.0-canary.7f224ddd4.0", + "@material/snackbar": "15.0.0-canary.7f224ddd4.0", + "@material/switch": "15.0.0-canary.7f224ddd4.0", + "@material/tab": "15.0.0-canary.7f224ddd4.0", + "@material/tab-bar": "15.0.0-canary.7f224ddd4.0", + "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0", + "@material/tab-scroller": "15.0.0-canary.7f224ddd4.0", + "@material/textfield": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tooltip": "15.0.0-canary.7f224ddd4.0", + "@material/top-app-bar": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.3.0" }, "peerDependencies": { - "@angular/animations": "^16.0.0 || ^17.0.0", - "@angular/cdk": "16.2.11", - "@angular/common": "^16.0.0 || ^17.0.0", - "@angular/core": "^16.0.0 || ^17.0.0", - "@angular/forms": "^16.0.0 || ^17.0.0", - "@angular/platform-browser": "^16.0.0 || ^17.0.0", + "@angular/animations": "^17.0.0 || ^18.0.0", + "@angular/cdk": "17.3.3", + "@angular/common": "^17.0.0 || ^18.0.0", + "@angular/core": "^17.0.0 || ^18.0.0", + "@angular/forms": "^17.0.0 || ^18.0.0", + "@angular/platform-browser": "^17.0.0 || ^18.0.0", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/material-moment-adapter": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-16.2.11.tgz", - "integrity": "sha512-vAwWi1GehmqrgOcaAnL6C6EAfr6q6liZqyNyrGC+xaNChqLD2TrzOo9qljIGiYdkRm1p36Fe4BOdBFjh+73EwQ==", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-17.3.3.tgz", + "integrity": "sha512-KjUurtvBNhq8XOAGtBJLGhuOlgNKimA7m0WnFm5ShTNHbb5o2ogN5iNdJWxsWZC3qrq0RCwJs/YIrONfS3Znbg==", "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { - "@angular/core": "^16.0.0 || ^17.0.0", - "@angular/material": "16.2.11", + "@angular/core": "^17.0.0 || ^18.0.0", + "@angular/material": "17.3.3", "moment": "^2.18.1" } }, "node_modules/@angular/platform-browser": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-16.2.11.tgz", - "integrity": "sha512-gUptbI3lbaRg+L8rcTlxKtFunYmR/M/mm9/l9uRd+5qk2mnFI0+s/tzRoaq7K0XaRGKZiWLNTz6FTkviO1zo2g==", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.3.tgz", + "integrity": "sha512-XFWjquD+Pr9VszRzrDlT6uaf57TsY9XhL9iHCNok6Op5DpVQpIAuw1vFt2t5ZoQ0gv+lY8mVWnxgqe3CgTdYxw==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^16.14.0 || >=18.10.0" + "node": "^18.13.0 || >=20.9.0" }, "peerDependencies": { - "@angular/animations": "16.2.11", - "@angular/common": "16.2.11", - "@angular/core": "16.2.11" + "@angular/animations": "17.3.3", + "@angular/common": "17.3.3", + "@angular/core": "17.3.3" }, "peerDependenciesMeta": { "@angular/animations": { @@ -654,87 +1134,81 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-16.2.11.tgz", - "integrity": "sha512-e+A7z6MUJaqC4Fdq7XQfIhAox3ZPM1lczM6G08fUKPbFDEe+c9i7C8YRLL+69BXDuG790btugIeOQcn5lnJcFg==", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-17.3.3.tgz", + "integrity": "sha512-jSgSNHRTXCIat20I+4tLm/e8qOvrIE3Zv7S/DtYZEiAth84uoznvo1kXnN+KREse2vP/WoNgSDKQ2JLzkwYXSQ==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^16.14.0 || >=18.10.0" + "node": "^18.13.0 || >=20.9.0" }, "peerDependencies": { - "@angular/common": "16.2.11", - "@angular/compiler": "16.2.11", - "@angular/core": "16.2.11", - "@angular/platform-browser": "16.2.11" + "@angular/common": "17.3.3", + "@angular/compiler": "17.3.3", + "@angular/core": "17.3.3", + "@angular/platform-browser": "17.3.3" } }, "node_modules/@angular/router": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-16.2.11.tgz", - "integrity": "sha512-QTssqJue+xQ8M1gzmfJcIHPIpPOijVwGnXQjt7cnFggNe/CedOckLEzk2j7/6aC1b5aQKuZePPw6XMvk8ciilQ==", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-17.3.3.tgz", + "integrity": "sha512-kj42+TtwvET7MFqxB3pkKyob0VNmspASlv8Y29vSpzzaOHn8J1fDf6H+8opoIC+Gmvo5NqXUDwq7nxI5aQ0mUQ==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^16.14.0 || >=18.10.0" + "node": "^18.13.0 || >=20.9.0" }, "peerDependencies": { - "@angular/common": "16.2.11", - "@angular/core": "16.2.11", - "@angular/platform-browser": "16.2.11", + "@angular/common": "17.3.3", + "@angular/core": "17.3.3", + "@angular/platform-browser": "17.3.3", "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@assemblyscript/loader": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.10.1.tgz", - "integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==", - "dev": true - }, "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz", - "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", + "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.9.tgz", - "integrity": "sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", + "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.9", - "@babel/helper-compilation-targets": "^7.22.9", - "@babel/helper-module-transforms": "^7.22.9", - "@babel/helpers": "^7.22.6", - "@babel/parser": "^7.22.7", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.8", - "@babel/types": "^7.22.5", - "convert-source-map": "^1.7.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.9", + "@babel/parser": "^7.23.9", + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", + "json5": "^2.2.3", "semver": "^6.3.1" }, "engines": { @@ -745,6 +1219,12 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -755,12 +1235,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.9.tgz", - "integrity": "sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5", + "@babel/types": "^7.23.6", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -794,14 +1274,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", - "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.15", - "browserslist": "^4.21.9", + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -819,17 +1299,17 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz", - "integrity": "sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.4.tgz", + "integrity": "sha512-lG75yeuUSVu0pIcbhiYMXBXANHrpUPaOfu7ryAzskCgKUHuAxRQI5ssrtmF0X9UXldPlvT0XM/A4F44OXRt6iQ==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-member-expression-to-functions": "^7.23.0", "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.9", + "@babel/helper-replace-supers": "^7.24.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", "semver": "^6.3.1" @@ -877,9 +1357,9 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz", - "integrity": "sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.1.tgz", + "integrity": "sha512-o7SDgTJuvx5vLKD6SFvkydkSMBvahDKGiNJzG22IZYXhiqoe9efY7zocICBgzHV4IRg5wdgl2nEL/tulKIEIbA==", "dev": true, "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", @@ -914,20 +1394,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-function-name/node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-hoist-variables": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", @@ -965,9 +1431,9 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", - "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", @@ -996,9 +1462,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", + "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", "dev": true, "engines": { "node": ">=6.9.0" @@ -1022,13 +1488,13 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", - "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz", + "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-member-expression-to-functions": "^7.23.0", "@babel/helper-optimise-call-expression": "^7.22.5" }, "engines": { @@ -1075,9 +1541,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", "dev": true, "engines": { "node": ">=6.9.0" @@ -1093,9 +1559,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", - "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", "dev": true, "engines": { "node": ">=6.9.0" @@ -1115,66 +1581,39 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-wrap-function/node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helpers": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", - "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers/node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.4.tgz", + "integrity": "sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz", + "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -1184,12 +1623,12 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.15.tgz", - "integrity": "sha512-FB9iYlz7rURmRJyXRKEnalYPPdn87H5no108cyuQQyMwlpJ2SJtpIUBI27kdTin956pz+LPypkPVPUTlxOmrsg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz", + "integrity": "sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1199,14 +1638,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.15.tgz", - "integrity": "sha512-Hyph9LseGvAeeXzikV88bczhsrLrIZqDPxO+sSmAunMPaGrBGhfMWzCPYTtiW9t+HzSE2wtV8e5cc5P6r1xMDQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz", + "integrity": "sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.15" + "@babel/plugin-transform-optional-chaining": "^7.24.1" }, "engines": { "node": ">=6.9.0" @@ -1215,23 +1654,20 @@ "@babel/core": "^7.13.0" } }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", - "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead.", + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.1.tgz", + "integrity": "sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, "node_modules/@babel/plugin-proposal-private-property-in-object": { @@ -1246,23 +1682,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-unicode-property-regex instead.", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", @@ -1339,12 +1758,12 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", - "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz", + "integrity": "sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1354,12 +1773,12 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz", - "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.1.tgz", + "integrity": "sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1541,12 +1960,12 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", - "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz", + "integrity": "sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1556,9 +1975,9 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.2.tgz", - "integrity": "sha512-BBYVGxbDVHfoeXbOwcagAkOQAm9NxoTdMGfTqghu1GrvadSaw6iW3Je6IcL5PNOw8VwjxqBECXy50/iCQSY/lQ==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.9.tgz", + "integrity": "sha512-8Q3veQEDGe14dTYuwagbRtwxQDnytyg1JFu4/HwEMETeofocrB0U0ejBJIXoeG/t2oXZ8kzCyI0ZZfbT80VFNQ==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", @@ -1574,14 +1993,14 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", - "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", + "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-module-imports": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.5" + "@babel/helper-remap-async-to-generator": "^7.22.20" }, "engines": { "node": ">=6.9.0" @@ -1591,12 +2010,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", - "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz", + "integrity": "sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1606,12 +2025,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.0.tgz", - "integrity": "sha512-cOsrbmIOXmf+5YbL99/S49Y3j46k/T16b9ml8bm9lP6N9US5iQ2yBK7gpui1pg0V/WMcXdkfKbTb7HXq9u+v4g==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.4.tgz", + "integrity": "sha512-nIFUZIpGKDf9O9ttyRXpHFpKC+X3Y5mtshZONuEUYBomAKoM4y029Jr+uB1bHGPhNmK8YXHevDtKDOLmtRrp6g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1621,13 +2040,13 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz", - "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz", + "integrity": "sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1637,13 +2056,13 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.11.tgz", - "integrity": "sha512-GMM8gGmqI7guS/llMFk1bJDkKfn3v3C4KHK9Yg1ey5qcHcOlKb0QvcMrgzvxo+T03/4szNh5lghY+fEC98Kq9g==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz", + "integrity": "sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.11", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.24.4", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-class-static-block": "^7.14.5" }, "engines": { @@ -1654,18 +2073,17 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.15.tgz", - "integrity": "sha512-VbbC3PGjBdE0wAWDdHM9G8Gm977pnYI0XpqMd6LrKISj8/DJXEsWqgRuTYaNE9Bv0JGhTZUzHDlMk18IpOuoqw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.1.tgz", + "integrity": "sha512-ZTIe3W7UejJd3/3R4p7ScyyOoafetUShSf4kCqV0O7F/RiHxVj/wRaRnQlrGwflvcehNA8M42HkAiEDYZu2F1Q==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.9", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-replace-supers": "^7.24.1", "@babel/helper-split-export-declaration": "^7.22.6", "globals": "^11.1.0" }, @@ -1677,13 +2095,13 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", - "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz", + "integrity": "sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/template": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/template": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1693,12 +2111,12 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.0.tgz", - "integrity": "sha512-vaMdgNXFkYrB+8lbgniSYWHsgqK5gjaMNcc84bMIOMRLH0L9AqYq3hwMdvnyqj1OPqea8UtjPEuS/DCenah1wg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.1.tgz", + "integrity": "sha512-ow8jciWqNxR3RYbSNVuF4U2Jx130nwnBnhRw6N6h1bOejNkABmcI5X5oz29K4alWX7vf1C+o6gtKXikzRKkVdw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1708,13 +2126,13 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz", - "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz", + "integrity": "sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1724,12 +2142,12 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz", - "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz", + "integrity": "sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1739,12 +2157,12 @@ } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.11.tgz", - "integrity": "sha512-g/21plo58sfteWjaO0ZNVb+uEOkJNjAaHhbejrnBmu011l/eNDScmkbjCC3l4FKb10ViaGU4aOkFznSu2zRHgA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz", + "integrity": "sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-dynamic-import": "^7.8.3" }, "engines": { @@ -1755,13 +2173,13 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz", - "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz", + "integrity": "sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw==", "dev": true, "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1771,12 +2189,12 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.11.tgz", - "integrity": "sha512-xa7aad7q7OiT8oNZ1mU7NrISjlSkVdMbNxn9IuLZyL9AJEhs1Apba3I+u5riX1dIkdptP5EKDG5XDPByWxtehw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz", + "integrity": "sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" }, "engines": { @@ -1787,12 +2205,13 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.15.tgz", - "integrity": "sha512-me6VGeHsx30+xh9fbDLLPi0J1HzmeIIyenoOQHuw2D4m2SAU3NrspX5XxJLBpqn5yrLzrlw2Iy3RA//Bx27iOA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz", + "integrity": "sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1802,14 +2221,14 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", - "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz", + "integrity": "sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1819,12 +2238,12 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.11.tgz", - "integrity": "sha512-CxT5tCqpA9/jXFlme9xIBCc5RPtdDq3JpkkhgHQqtDdiTnTI0jtZ0QzXhr5DILeYifDPp2wvY2ad+7+hLMW5Pw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.1.tgz", + "integrity": "sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-json-strings": "^7.8.3" }, "engines": { @@ -1835,12 +2254,12 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", - "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz", + "integrity": "sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1850,12 +2269,12 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.11.tgz", - "integrity": "sha512-qQwRTP4+6xFCDV5k7gZBF3C31K34ut0tbEcTKxlX/0KXxm9GLcO14p570aWxFvVzx6QAfPgq7gaeIHXJC8LswQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.1.tgz", + "integrity": "sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" }, "engines": { @@ -1866,12 +2285,12 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", - "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz", + "integrity": "sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1881,13 +2300,13 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.0.tgz", - "integrity": "sha512-xWT5gefv2HGSm4QHtgc1sYPbseOyf+FFDo2JbpE25GWl5BqTGO9IMwTYJRoIdjsF85GE+VegHxSCUt5EvoYTAw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz", + "integrity": "sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1897,13 +2316,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.0.tgz", - "integrity": "sha512-32Xzss14/UVc7k9g775yMIvkVK8xwKE0DPdP5JTapr3+Z9w4tzeOuLNY6BXDQR6BdnzIlXnCGAzsk/ICHBLVWQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz", + "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-simple-access": "^7.22.5" }, "engines": { @@ -1914,14 +2333,14 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.0.tgz", - "integrity": "sha512-qBej6ctXZD2f+DhlOC9yO47yEYgUh5CZNz/aBoH4j/3NOlRfJXJbY7xDQCqQVf9KbrqGzIWER1f23doHGrIHFg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz", + "integrity": "sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA==", "dev": true, "dependencies": { "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-validator-identifier": "^7.22.20" }, "engines": { @@ -1932,13 +2351,13 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz", - "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz", + "integrity": "sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1964,12 +2383,12 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz", - "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz", + "integrity": "sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1979,12 +2398,12 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.11.tgz", - "integrity": "sha512-YZWOw4HxXrotb5xsjMJUDlLgcDXSfO9eCmdl1bgW4+/lAGdkjaEvOnQ4p5WKKdUgSzO39dgPl0pTnfxm0OAXcg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz", + "integrity": "sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" }, "engines": { @@ -1995,12 +2414,12 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.11.tgz", - "integrity": "sha512-3dzU4QGPsILdJbASKhF/V2TVP+gJya1PsueQCxIPCEcerqF21oEcrob4mzjsp2Py/1nLfF5m+xYNMDpmA8vffg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.1.tgz", + "integrity": "sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-numeric-separator": "^7.10.4" }, "engines": { @@ -2011,16 +2430,15 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.15.tgz", - "integrity": "sha512-fEB+I1+gAmfAyxZcX1+ZUwLeAuuf8VIg67CTznZE0MqVFumWkh8xWtn58I4dxdVf080wn7gzWoF8vndOViJe9Q==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.1.tgz", + "integrity": "sha512-XjD5f0YqOtebto4HGISLNfiNMTTs6tbkFf2TOqJlYKYmbo+mN9Dnpl4SRoofiziuOWMIyq3sZEUqLo3hLITFEA==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.22.15" + "@babel/plugin-transform-parameters": "^7.24.1" }, "engines": { "node": ">=6.9.0" @@ -2030,13 +2448,13 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", - "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz", + "integrity": "sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-replace-supers": "^7.24.1" }, "engines": { "node": ">=6.9.0" @@ -2046,12 +2464,12 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.11.tgz", - "integrity": "sha512-rli0WxesXUeCJnMYhzAglEjLWVDF6ahb45HuprcmQuLidBJFWjNnOzssk2kuc6e33FlLaiZhG/kUIzUMWdBKaQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.1.tgz", + "integrity": "sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" }, "engines": { @@ -2062,12 +2480,12 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.0.tgz", - "integrity": "sha512-sBBGXbLJjxTzLBF5rFWaikMnOGOk/BmK6vVByIdEggZ7Vn6CvWXZyRkkLFK6WE0IF8jSliyOkUN6SScFgzCM0g==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.1.tgz", + "integrity": "sha512-n03wmDt+987qXwAgcBlnUUivrZBPZ8z1plL0YvgQalLm+ZE5BMhGm94jhxXtA1wzv1Cu2aaOv1BM9vbVttrzSg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, @@ -2079,12 +2497,12 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.15.tgz", - "integrity": "sha512-hjk7qKIqhyzhhUvRT683TYQOFa/4cQKwQy7ALvTpODswN40MljzNDa0YldevS6tGbxwaEKVn502JmY0dP7qEtQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.1.tgz", + "integrity": "sha512-8Jl6V24g+Uw5OGPeWNKrKqXPDw2YDjLc53ojwfMcKwlEoETKU9rU0mHUtcg9JntWI/QYzGAXNWEcVHZ+fR+XXg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2094,13 +2512,13 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz", - "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz", + "integrity": "sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2110,14 +2528,14 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.11.tgz", - "integrity": "sha512-sSCbqZDBKHetvjSwpyWzhuHkmW5RummxJBVbYLkGkaiTOWGxml7SXt0iWa03bzxFIx7wOj3g/ILRd0RcJKBeSQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.1.tgz", + "integrity": "sha512-pTHxDVa0BpUbvAgX3Gat+7cSciXqUcY9j2VZKTbSB6+VQGpNgNO9ailxTGHSXlqOnX1Hcx1Enme2+yv7VqP9bg==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.11", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, "engines": { @@ -2128,12 +2546,12 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", - "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz", + "integrity": "sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2143,12 +2561,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz", - "integrity": "sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz", + "integrity": "sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "regenerator-transform": "^0.15.2" }, "engines": { @@ -2159,12 +2577,12 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz", - "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz", + "integrity": "sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2174,16 +2592,16 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.22.9.tgz", - "integrity": "sha512-9KjBH61AGJetCPYp/IEyLEp47SyybZb0nDRpBvmtEkm+rUIwxdlKpyNHI1TmsGkeuLclJdleQHRZ8XLBnnh8CQ==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.0.tgz", + "integrity": "sha512-zc0GA5IitLKJrSfXlXmp8KDqLrnGECK7YRfQBmEKg1NmBOQ7e+KuclBEKJgzifQeUYLdNiAw4B4bjyvzWVLiSA==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "babel-plugin-polyfill-corejs2": "^0.4.4", - "babel-plugin-polyfill-corejs3": "^0.8.2", - "babel-plugin-polyfill-regenerator": "^0.5.1", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0", + "babel-plugin-polyfill-corejs2": "^0.4.8", + "babel-plugin-polyfill-corejs3": "^0.9.0", + "babel-plugin-polyfill-regenerator": "^0.5.5", "semver": "^6.3.1" }, "engines": { @@ -2203,12 +2621,12 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", - "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz", + "integrity": "sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2218,12 +2636,12 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", - "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz", + "integrity": "sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" }, "engines": { @@ -2234,12 +2652,12 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz", - "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz", + "integrity": "sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2249,12 +2667,12 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", - "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz", + "integrity": "sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2264,12 +2682,12 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz", - "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.1.tgz", + "integrity": "sha512-CBfU4l/A+KruSUoW+vTQthwcAdwuqbpRNB8HQKlZABwHRhsdHZ9fezp4Sn18PeAlYxTNiLMlx4xUBV3AWfg1BA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2279,12 +2697,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz", - "integrity": "sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz", + "integrity": "sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2294,13 +2712,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz", - "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.1.tgz", + "integrity": "sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2310,13 +2728,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz", - "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz", + "integrity": "sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2326,13 +2744,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz", - "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.1.tgz", + "integrity": "sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2342,25 +2760,26 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.9.tgz", - "integrity": "sha512-wNi5H/Emkhll/bqPjsjQorSykrlfY5OWakd6AulLvMEytpKasMVUpVy8RL4qBIBs5Ac6/5i0/Rv0b/Fg6Eag/g==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-compilation-targets": "^7.22.9", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.5", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.5", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.5", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.0.tgz", + "integrity": "sha512-ZxPEzV9IgvGn73iK0E6VB9/95Nd7aMFpbE0l8KQFDG70cOV9IxRP7Y2FUPmlK0v6ImlLqYX50iuZ3ZTVhOF2lA==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.22.5", - "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-assertions": "^7.23.3", + "@babel/plugin-syntax-import-attributes": "^7.23.3", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", @@ -2372,59 +2791,58 @@ "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.22.5", - "@babel/plugin-transform-async-generator-functions": "^7.22.7", - "@babel/plugin-transform-async-to-generator": "^7.22.5", - "@babel/plugin-transform-block-scoped-functions": "^7.22.5", - "@babel/plugin-transform-block-scoping": "^7.22.5", - "@babel/plugin-transform-class-properties": "^7.22.5", - "@babel/plugin-transform-class-static-block": "^7.22.5", - "@babel/plugin-transform-classes": "^7.22.6", - "@babel/plugin-transform-computed-properties": "^7.22.5", - "@babel/plugin-transform-destructuring": "^7.22.5", - "@babel/plugin-transform-dotall-regex": "^7.22.5", - "@babel/plugin-transform-duplicate-keys": "^7.22.5", - "@babel/plugin-transform-dynamic-import": "^7.22.5", - "@babel/plugin-transform-exponentiation-operator": "^7.22.5", - "@babel/plugin-transform-export-namespace-from": "^7.22.5", - "@babel/plugin-transform-for-of": "^7.22.5", - "@babel/plugin-transform-function-name": "^7.22.5", - "@babel/plugin-transform-json-strings": "^7.22.5", - "@babel/plugin-transform-literals": "^7.22.5", - "@babel/plugin-transform-logical-assignment-operators": "^7.22.5", - "@babel/plugin-transform-member-expression-literals": "^7.22.5", - "@babel/plugin-transform-modules-amd": "^7.22.5", - "@babel/plugin-transform-modules-commonjs": "^7.22.5", - "@babel/plugin-transform-modules-systemjs": "^7.22.5", - "@babel/plugin-transform-modules-umd": "^7.22.5", + "@babel/plugin-transform-arrow-functions": "^7.23.3", + "@babel/plugin-transform-async-generator-functions": "^7.23.9", + "@babel/plugin-transform-async-to-generator": "^7.23.3", + "@babel/plugin-transform-block-scoped-functions": "^7.23.3", + "@babel/plugin-transform-block-scoping": "^7.23.4", + "@babel/plugin-transform-class-properties": "^7.23.3", + "@babel/plugin-transform-class-static-block": "^7.23.4", + "@babel/plugin-transform-classes": "^7.23.8", + "@babel/plugin-transform-computed-properties": "^7.23.3", + "@babel/plugin-transform-destructuring": "^7.23.3", + "@babel/plugin-transform-dotall-regex": "^7.23.3", + "@babel/plugin-transform-duplicate-keys": "^7.23.3", + "@babel/plugin-transform-dynamic-import": "^7.23.4", + "@babel/plugin-transform-exponentiation-operator": "^7.23.3", + "@babel/plugin-transform-export-namespace-from": "^7.23.4", + "@babel/plugin-transform-for-of": "^7.23.6", + "@babel/plugin-transform-function-name": "^7.23.3", + "@babel/plugin-transform-json-strings": "^7.23.4", + "@babel/plugin-transform-literals": "^7.23.3", + "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", + "@babel/plugin-transform-member-expression-literals": "^7.23.3", + "@babel/plugin-transform-modules-amd": "^7.23.3", + "@babel/plugin-transform-modules-commonjs": "^7.23.3", + "@babel/plugin-transform-modules-systemjs": "^7.23.9", + "@babel/plugin-transform-modules-umd": "^7.23.3", "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.22.5", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.5", - "@babel/plugin-transform-numeric-separator": "^7.22.5", - "@babel/plugin-transform-object-rest-spread": "^7.22.5", - "@babel/plugin-transform-object-super": "^7.22.5", - "@babel/plugin-transform-optional-catch-binding": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.6", - "@babel/plugin-transform-parameters": "^7.22.5", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/plugin-transform-private-property-in-object": "^7.22.5", - "@babel/plugin-transform-property-literals": "^7.22.5", - "@babel/plugin-transform-regenerator": "^7.22.5", - "@babel/plugin-transform-reserved-words": "^7.22.5", - "@babel/plugin-transform-shorthand-properties": "^7.22.5", - "@babel/plugin-transform-spread": "^7.22.5", - "@babel/plugin-transform-sticky-regex": "^7.22.5", - "@babel/plugin-transform-template-literals": "^7.22.5", - "@babel/plugin-transform-typeof-symbol": "^7.22.5", - "@babel/plugin-transform-unicode-escapes": "^7.22.5", - "@babel/plugin-transform-unicode-property-regex": "^7.22.5", - "@babel/plugin-transform-unicode-regex": "^7.22.5", - "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.22.5", - "babel-plugin-polyfill-corejs2": "^0.4.4", - "babel-plugin-polyfill-corejs3": "^0.8.2", - "babel-plugin-polyfill-regenerator": "^0.5.1", + "@babel/plugin-transform-new-target": "^7.23.3", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", + "@babel/plugin-transform-numeric-separator": "^7.23.4", + "@babel/plugin-transform-object-rest-spread": "^7.24.0", + "@babel/plugin-transform-object-super": "^7.23.3", + "@babel/plugin-transform-optional-catch-binding": "^7.23.4", + "@babel/plugin-transform-optional-chaining": "^7.23.4", + "@babel/plugin-transform-parameters": "^7.23.3", + "@babel/plugin-transform-private-methods": "^7.23.3", + "@babel/plugin-transform-private-property-in-object": "^7.23.4", + "@babel/plugin-transform-property-literals": "^7.23.3", + "@babel/plugin-transform-regenerator": "^7.23.3", + "@babel/plugin-transform-reserved-words": "^7.23.3", + "@babel/plugin-transform-shorthand-properties": "^7.23.3", + "@babel/plugin-transform-spread": "^7.23.3", + "@babel/plugin-transform-sticky-regex": "^7.23.3", + "@babel/plugin-transform-template-literals": "^7.23.3", + "@babel/plugin-transform-typeof-symbol": "^7.23.3", + "@babel/plugin-transform-unicode-escapes": "^7.23.3", + "@babel/plugin-transform-unicode-property-regex": "^7.23.3", + "@babel/plugin-transform-unicode-regex": "^7.23.3", + "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.8", + "babel-plugin-polyfill-corejs3": "^0.9.0", + "babel-plugin-polyfill-regenerator": "^0.5.5", "core-js-compat": "^3.31.0", "semver": "^6.3.1" }, @@ -2445,14 +2863,12 @@ } }, "node_modules/@babel/preset-modules": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6.tgz", - "integrity": "sha512-ID2yj6K/4lKfhuU3+EX4UvNbIt7eACFbHmNUjzA+ep+B5971CknnA/9DEWKbRokfbbtblxxxXFJJrH47UEAMVg==", + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", "@babel/types": "^7.4.4", "esutils": "^2.0.2" }, @@ -2467,46 +2883,46 @@ "dev": true }, "node_modules/@babel/runtime": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", - "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz", + "integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==", "dev": true, "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", + "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", + "@babel/code-frame": "^7.24.1", + "@babel/generator": "^7.24.1", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", + "@babel/parser": "^7.24.1", + "@babel/types": "^7.24.0", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -2514,14 +2930,14 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.4.tgz", + "integrity": "sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==", "dev": true, "dependencies": { - "@babel/types": "^7.23.0", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -2529,12 +2945,12 @@ } }, "node_modules/@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, @@ -2553,6 +2969,28 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -2562,10 +3000,26 @@ "node": ">=10.0.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/android-arm": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.17.tgz", - "integrity": "sha512-wHsmJG/dnL3OkpAcwbgoBTTMHVi4Uyou3F5mf58ZtmUyIKfcdA7TROav/6tCzET4A3QW2Q2FC+eFneMU+iyOxg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", "cpu": [ "arm" ], @@ -2579,9 +3033,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.17.tgz", - "integrity": "sha512-9np+YYdNDed5+Jgr1TdWBsozZ85U1Oa3xW0c7TWqH0y2aGghXtZsuT8nYRbzOMcl0bXZXjOGbksoTtVOlWrRZg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", "cpu": [ "arm64" ], @@ -2595,9 +3049,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.17.tgz", - "integrity": "sha512-O+FeWB/+xya0aLg23hHEM2E3hbfwZzjqumKMSIqcHbNvDa+dza2D0yLuymRBQQnC34CWrsJUXyH2MG5VnLd6uw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", "cpu": [ "x64" ], @@ -2611,9 +3065,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.17.tgz", - "integrity": "sha512-M9uJ9VSB1oli2BE/dJs3zVr9kcCBBsE883prage1NWz6pBS++1oNn/7soPNS3+1DGj0FrkSvnED4Bmlu1VAE9g==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", "cpu": [ "arm64" ], @@ -2627,9 +3081,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.17.tgz", - "integrity": "sha512-XDre+J5YeIJDMfp3n0279DFNrGCXlxOuGsWIkRb1NThMZ0BsrWXoTg23Jer7fEXQ9Ye5QjrvXpxnhzl3bHtk0g==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", "cpu": [ "x64" ], @@ -2643,9 +3097,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.17.tgz", - "integrity": "sha512-cjTzGa3QlNfERa0+ptykyxs5A6FEUQQF0MuilYXYBGdBxD3vxJcKnzDlhDCa1VAJCmAxed6mYhA2KaJIbtiNuQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", "cpu": [ "arm64" ], @@ -2659,9 +3113,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.17.tgz", - "integrity": "sha512-sOxEvR8d7V7Kw8QqzxWc7bFfnWnGdaFBut1dRUYtu+EIRXefBc/eIsiUiShnW0hM3FmQ5Zf27suDuHsKgZ5QrA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", "cpu": [ "x64" ], @@ -2675,9 +3129,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.17.tgz", - "integrity": "sha512-2d3Lw6wkwgSLC2fIvXKoMNGVaeY8qdN0IC3rfuVxJp89CRfA3e3VqWifGDfuakPmp90+ZirmTfye1n4ncjv2lg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", "cpu": [ "arm" ], @@ -2691,9 +3145,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.17.tgz", - "integrity": "sha512-c9w3tE7qA3CYWjT+M3BMbwMt+0JYOp3vCMKgVBrCl1nwjAlOMYzEo+gG7QaZ9AtqZFj5MbUc885wuBBmu6aADQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", "cpu": [ "arm64" ], @@ -2707,9 +3161,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.17.tgz", - "integrity": "sha512-1DS9F966pn5pPnqXYz16dQqWIB0dmDfAQZd6jSSpiT9eX1NzKh07J6VKR3AoXXXEk6CqZMojiVDSZi1SlmKVdg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", "cpu": [ "ia32" ], @@ -2723,9 +3177,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.17.tgz", - "integrity": "sha512-EvLsxCk6ZF0fpCB6w6eOI2Fc8KW5N6sHlIovNe8uOFObL2O+Mr0bflPHyHwLT6rwMg9r77WOAWb2FqCQrVnwFg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", "cpu": [ "loong64" ], @@ -2739,9 +3193,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.17.tgz", - "integrity": "sha512-e0bIdHA5p6l+lwqTE36NAW5hHtw2tNRmHlGBygZC14QObsA3bD4C6sXLJjvnDIjSKhW1/0S3eDy+QmX/uZWEYQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", "cpu": [ "mips64el" ], @@ -2755,9 +3209,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.17.tgz", - "integrity": "sha512-BAAilJ0M5O2uMxHYGjFKn4nJKF6fNCdP1E0o5t5fvMYYzeIqy2JdAP88Az5LHt9qBoUa4tDaRpfWt21ep5/WqQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", "cpu": [ "ppc64" ], @@ -2771,9 +3225,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.17.tgz", - "integrity": "sha512-Wh/HW2MPnC3b8BqRSIme/9Zhab36PPH+3zam5pqGRH4pE+4xTrVLx2+XdGp6fVS3L2x+DrsIcsbMleex8fbE6g==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", "cpu": [ "riscv64" ], @@ -2787,9 +3241,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.17.tgz", - "integrity": "sha512-j/34jAl3ul3PNcK3pfI0NSlBANduT2UO5kZ7FCaK33XFv3chDhICLY8wJJWIhiQ+YNdQ9dxqQctRg2bvrMlYgg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", "cpu": [ "s390x" ], @@ -2803,9 +3257,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.17.tgz", - "integrity": "sha512-QM50vJ/y+8I60qEmFxMoxIx4de03pGo2HwxdBeFd4nMh364X6TIBZ6VQ5UQmPbQWUVWHWws5MmJXlHAXvJEmpQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", "cpu": [ "x64" ], @@ -2819,9 +3273,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.17.tgz", - "integrity": "sha512-/jGlhWR7Sj9JPZHzXyyMZ1RFMkNPjC6QIAan0sDOtIo2TYk3tZn5UDrkE0XgsTQCxWTTOcMPf9p6Rh2hXtl5TQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", "cpu": [ "x64" ], @@ -2835,9 +3289,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.17.tgz", - "integrity": "sha512-rSEeYaGgyGGf4qZM2NonMhMOP/5EHp4u9ehFiBrg7stH6BYEEjlkVREuDEcQ0LfIl53OXLxNbfuIj7mr5m29TA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", "cpu": [ "x64" ], @@ -2851,9 +3305,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.17.tgz", - "integrity": "sha512-Y7ZBbkLqlSgn4+zot4KUNYst0bFoO68tRgI6mY2FIM+b7ZbyNVtNbDP5y8qlu4/knZZ73fgJDlXID+ohY5zt5g==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", "cpu": [ "x64" ], @@ -2867,9 +3321,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.17.tgz", - "integrity": "sha512-bwPmTJsEQcbZk26oYpc4c/8PvTY3J5/QK8jM19DVlEsAB41M39aWovWoHtNm78sd6ip6prilxeHosPADXtEJFw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", "cpu": [ "arm64" ], @@ -2883,9 +3337,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.17.tgz", - "integrity": "sha512-H/XaPtPKli2MhW+3CQueo6Ni3Avggi6hP/YvgkEe1aSaxw+AeO8MFjq8DlgfTd9Iz4Yih3QCZI6YLMoyccnPRg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", "cpu": [ "ia32" ], @@ -2899,9 +3353,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.17.tgz", - "integrity": "sha512-fGEb8f2BSA3CW7riJVurug65ACLuQAzKq0SSqkY2b2yHHH0MzDfbLyKIGzHwOI/gkHcxM/leuSW6D5w/LMNitA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", "cpu": [ "x64" ], @@ -2939,9 +3393,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -2984,9 +3438,9 @@ "dev": true }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -3029,20 +3483,14 @@ } }, "node_modules/@eslint/js": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz", - "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", - "dev": true - }, "node_modules/@golevelup/ts-jest": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@golevelup/ts-jest/-/ts-jest-0.4.0.tgz", @@ -3050,13 +3498,13 @@ "dev": true }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { @@ -3077,9 +3525,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, "node_modules/@isaacs/cliui": { @@ -3854,14 +4302,14 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -3877,9 +4325,9 @@ } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, "engines": { "node": ">=6.0.0" @@ -3902,9 +4350,9 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -3912,776 +4360,788 @@ } }, "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", - "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", "dev": true }, + "node_modules/@ljharb/through": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.13.tgz", + "integrity": "sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/@material/animation": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/animation/-/animation-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-leRf+BcZTfC/iSigLXnYgcHAGvFVQveoJT5+2PIRdyPI/bIG7hhciRgacHRsCKC0sGya81dDblLgdkjSUemYLw==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/animation/-/animation-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-1GSJaPKef+7HRuV+HusVZHps64cmZuOItDbt40tjJVaikcaZvwmHlcTxRIqzcRoCdt5ZKHh3NoO7GB9Khg4Jnw==", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@material/auto-init": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/auto-init/-/auto-init-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-uxzDq7q3c0Bu1pAsMugc1Ik9ftQYQqZY+5e2ybNplT8gTImJhNt4M2mMiMHbMANk2l3UgICmUyRSomgPBWCPIA==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/auto-init/-/auto-init-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-t7ZGpRJ3ec0QDUO0nJu/SMgLW7qcuG2KqIsEYD1Ej8qhI2xpdR2ydSDQOkVEitXmKoGol1oq4nYSBjTlB65GqA==", "dependencies": { - "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/banner": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/banner/-/banner-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-SHeVoidCUFVhXANN6MNWxK9SZoTSgpIP8GZB7kAl52BywLxtV+FirTtLXkg/8RUkxZRyRWl7HvQ0ZFZa7QQAyA==", - "dependencies": { - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/button": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/banner/-/banner-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-g9wBUZzYBizyBcBQXTIafnRUUPi7efU9gPJfzeGgkynXiccP/vh5XMmH+PBxl5v+4MlP/d4cZ2NUYoAN7UTqSA==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/button": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/base": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/base/-/base-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-Fc3vGuOf+duGo22HTRP6dHdc+MUe0VqQfWOuKrn/wXKD62m0QQR2TqJd3rRhCumH557T5QUyheW943M3E+IGfg==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/base/-/base-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-I9KQOKXpLfJkP8MqZyr8wZIzdPHrwPjFvGd9zSK91/vPyE4hzHRJc/0njsh9g8Lm9PRYLbifXX+719uTbHxx+A==", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@material/button": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/button/-/button-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-3AQgwrPZCTWHDJvwgKq7Cj+BurQ4wTjDdGL+FEnIGUAjJDskwi1yzx5tW2Wf/NxIi7IoPFyOY3UB41jwMiOrnw==", - "dependencies": { - "@material/density": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/button/-/button-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-BHB7iyHgRVH+JF16+iscR+Qaic+p7LU1FOLgP8KucRlpF9tTwIxQA6mJwGRi5gUtcG+vyCmzVS+hIQ6DqT/7BA==", + "dependencies": { + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/card": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/card/-/card-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-nPlhiWvbLmooTnBmV5gmzB0eLWSgLKsSRBYAbIBmO76Okgz1y+fQNLag+lpm/TDaHVsn5fmQJH8e0zIg0rYsQA==", - "dependencies": { - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/card/-/card-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-kt7y9/IWOtJTr3Z/AoWJT3ZLN7CLlzXhx2udCLP9ootZU2bfGK0lzNwmo80bv/pJfrY9ihQKCtuGTtNxUy+vIw==", + "dependencies": { + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/checkbox": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/checkbox/-/checkbox-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-4tpNnO1L0IppoMF3oeQn8F17t2n0WHB0D7mdJK9rhrujen/fLbekkIC82APB3fdGtLGg3qeNqDqPsJm1YnmrwA==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/density": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/checkbox/-/checkbox-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-rURcrL5O1u6hzWR+dNgiQ/n89vk6tdmdP3mZgnxJx61q4I/k1yijKqNJSLrkXH7Rto3bM5NRKMOlgvMvVd7UMQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/chips": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/chips/-/chips-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-fqHKvE5bSWK0bXVkf57MWxZtytGqYBZvvHIOs4JI9HPHEhaJy4CpSw562BEtbm3yFxxALoQknvPW2KYzvADnmA==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/checkbox": "15.0.0-canary.bc9ae6c9c.0", - "@material/density": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/chips/-/chips-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-AYAivV3GSk/T/nRIpH27sOHFPaSMrE3L0WYbnb5Wa93FgY8a0fbsFYtSH2QmtwnzXveg+B1zGTt7/xIIcynKdQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/checkbox": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "safevalues": "^0.3.4", "tslib": "^2.1.0" } }, "node_modules/@material/circular-progress": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/circular-progress/-/circular-progress-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-Lxe8BGAxQwCQqrLhrYrIP0Uok10h7aYS3RBXP41ph+5GmwJd5zdyE2t93qm2dyThvU6qKuXw9726Dtq/N+wvZQ==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/progress-indicator": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/circular-progress/-/circular-progress-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-DJrqCKb+LuGtjNvKl8XigvyK02y36GRkfhMUYTcJEi3PrOE00bwXtyj7ilhzEVshQiXg6AHGWXtf5UqwNrx3Ow==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/progress-indicator": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/data-table": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/data-table/-/data-table-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-j/7qplT9+sUpfe4pyWhPbl01qJA+OoNAG3VMJruBBR461ZBKyTi7ssKH9yksFGZ8eCEPkOsk/+kDxsiZvRWkeQ==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/checkbox": "15.0.0-canary.bc9ae6c9c.0", - "@material/density": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/icon-button": "15.0.0-canary.bc9ae6c9c.0", - "@material/linear-progress": "15.0.0-canary.bc9ae6c9c.0", - "@material/list": "15.0.0-canary.bc9ae6c9c.0", - "@material/menu": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/select": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/data-table/-/data-table-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-/2WZsuBIq9z9RWYF5Jo6b7P6u0fwit+29/mN7rmAZ6akqUR54nXyNfoSNiyydMkzPlZZsep5KrSHododDhBZbA==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/checkbox": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/icon-button": "15.0.0-canary.7f224ddd4.0", + "@material/linear-progress": "15.0.0-canary.7f224ddd4.0", + "@material/list": "15.0.0-canary.7f224ddd4.0", + "@material/menu": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/select": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/density": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/density/-/density-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-Zt3u07fXrBWLW06Tl5fgvjicxNQMkFdawLyNTzZ5TvbXfVkErILLePwwGaw8LNcvzqJP6ABLA8jiR+sKNoJQCg==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/density/-/density-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-o9EXmGKVpiQ6mHhyV3oDDzc78Ow3E7v8dlaOhgaDSXgmqaE8v5sIlLNa/LKSyUga83/fpGk3QViSGXotpQx0jA==", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@material/dialog": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/dialog/-/dialog-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-o+9a/fmwJ9+gY3Z/uhj/PMVJDq7it1NTWKJn2GwAKdB+fDkT4hb9qEdcxMPyvJJ5ups+XiKZo03+tZrD+38c1w==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/button": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/icon-button": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/dialog/-/dialog-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-u0XpTlv1JqWC/bQ3DavJ1JguofTelLT2wloj59l3/1b60jv42JQ6Am7jU3I8/SIUB1MKaW7dYocXjDWtWJakLA==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/button": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/icon-button": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/dom": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/dom/-/dom-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-ly78R7aoCJtundSUu0UROU+5pQD5Piae0Y1MkN6bs0724azeazX1KeXFeaf06JOXnlr5/41ol+fSUPowjoqnOg==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/dom/-/dom-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-mQ1HT186GPQSkRg5S18i70typ5ZytfjL09R0gJ2Qg5/G+MLCGi7TAjZZSH65tuD/QGOjel4rDdWOTmYbPYV6HA==", "dependencies": { - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/drawer": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/drawer/-/drawer-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-PFL4cEFnt7VTxDsuspFVNhsFDYyumjU0VWfj3PWB7XudsEfQ3lo85D3HCEtTTbRsCainGN8bgYNDNafLBqiigw==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/list": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/drawer/-/drawer-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-qyO0W0KBftfH8dlLR0gVAgv7ZHNvU8ae11Ao6zJif/YxcvK4+gph1z8AO4H410YmC2kZiwpSKyxM1iQCCzbb4g==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/list": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/elevation": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/elevation/-/elevation-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-Ro+Pk8jFuap+T0B0shA3xI1hs2b89dNQ2EIPCNjNMp87emHKAzJfhKb7EZGIwv3+gFLlVaLyIVkb94I89KLsyg==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/elevation/-/elevation-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-tV6s4/pUBECedaI36Yj18KmRCk1vfue/JP/5yYRlFNnLMRVISePbZaKkn/BHXVf+26I3W879+XqIGlDVdmOoMA==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/fab": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/fab/-/fab-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-dvU0KWMRglwJEQwmQtFAmJcAjzg9VFF6Aqj78bJYu/DAIGFJ1VTTTSgoXM/XCm1YyQEZ7kZRvxBO37CH54rSDg==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/fab/-/fab-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-4h76QrzfZTcPdd+awDPZ4Q0YdSqsXQnS540TPtyXUJ/5G99V6VwGpjMPIxAsW0y+pmI9UkLL/srrMaJec+7r4Q==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/feature-targeting": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-wkDjVcoVEYYaJvun28IXdln/foLgPD7n9ZC9TY76GErGCwTq+HWpU6wBAAk+ePmpRFDayw4vI4wBlaWGxLtysQ==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-SAjtxYh6YlKZriU83diDEQ7jNSP2MnxKsER0TvFeyG1vX/DWsUyYDOIJTOEa9K1N+fgJEBkNK8hY55QhQaspew==", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@material/floating-label": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/floating-label/-/floating-label-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-bUWPtXzZITOD/2mkvLkEPO1ngDWmb74y0Kgbz6llHLOQBtycyJIpuoQJ1q2Ez0NM/tFLwPphhAgRqmL3YQ/Kzw==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/floating-label/-/floating-label-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-0KMo5ijjYaEHPiZ2pCVIcbaTS2LycvH9zEhEMKwPPGssBCX7iz5ffYQFk7e5yrQand1r3jnQQgYfHAwtykArnQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/focus-ring": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/focus-ring/-/focus-ring-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-cZHThVose3GvAlJzpJoBI1iqL6d1/Jj9hXrR+r8Mwtb1hBIUEG3hxfsRd4vGREuzROPlf0OgNf/V+YHoSwgR5w==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/focus-ring/-/focus-ring-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-Jmg1nltq4J6S6A10EGMZnvufrvU3YTi+8R8ZD9lkSbun0Fm2TVdICQt/Auyi6An9zP66oQN6c31eqO6KfIPsDg==", "dependencies": { - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0" + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0" } }, "node_modules/@material/form-field": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/form-field/-/form-field-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-+JFXy5X44Gue1CbZZAQ6YejnI203lebYwL0i6k0ylDpWHEOdD5xkF2PyHR28r9/65Ebcbwbff6q7kI1SGoT7MA==", - "dependencies": { - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/form-field/-/form-field-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-fEPWgDQEPJ6WF7hNnIStxucHR9LE4DoDSMqCsGWS2Yu+NLZYLuCEecgR0UqQsl1EQdNRaFh8VH93KuxGd2hiPg==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/icon-button": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/icon-button/-/icon-button-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-1a0MHgyIwOs4RzxrVljsqSizGYFlM1zY2AZaLDsgT4G3kzsplTx8HZQ022GpUCjAygW+WLvg4z1qAhQHvsbqlw==", - "dependencies": { - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/density": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/icon-button/-/icon-button-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-DcK7IL4ICY/DW+48YQZZs9g0U1kRaW0Wb0BxhvppDMYziHo/CTpFdle4gjyuTyRxPOdHQz5a97ru48Z9O4muTw==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/image-list": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/image-list/-/image-list-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-WKWmiYap2iu4QdqmeUSliLlN4O2Ueqa0OuVAYHn/TCzmQ2xmnhZ1pvDLbs6TplpOmlki7vFfe+aSt5SU9gwfOQ==", - "dependencies": { - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/image-list/-/image-list-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-voMjG2p80XbjL1B2lmF65zO5gEgJOVKClLdqh4wbYzYfwY/SR9c8eLvlYG7DLdFaFBl/7gGxD8TvvZ329HUFPw==", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/layout-grid": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/layout-grid/-/layout-grid-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-5GqmT6oTZhUGWIb+CLD0ZNyDyTiJsr/rm9oRIi3+vCujACwxFkON9tzBlZohdtFS16nuzUusthN6Jt9UrJcN6Q==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/layout-grid/-/layout-grid-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-veDABLxMn2RmvfnUO2RUmC1OFfWr4cU+MrxKPoDD2hl3l3eDYv5fxws6r5T1JoSyXoaN+oEZpheS0+M9Ure8Pg==", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@material/line-ripple": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/line-ripple/-/line-ripple-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-8S30WXEuUdgDdBulzUDlPXD6qMzwCX9SxYb5mGDYLwl199cpSGdXHtGgEcCjokvnpLhdZhcT1Dsxeo1g2Evh5Q==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/line-ripple/-/line-ripple-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-f60hVJhIU6I3/17Tqqzch1emUKEcfVVgHVqADbU14JD+oEIz429ZX9ksZ3VChoU3+eejFl+jVdZMLE/LrAuwpg==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/linear-progress": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/linear-progress/-/linear-progress-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-6EJpjrz6aoH2/gXLg9iMe0yF2C42hpQyZoHpmcgTLKeci85ktDvJIjwup8tnk8ULQyFiGiIrhXw2v2RSsiFjvQ==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/progress-indicator": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/linear-progress/-/linear-progress-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-pRDEwPQielDiC9Sc5XhCXrGxP8wWOnAO8sQlMebfBYHYqy5hhiIzibezS8CSaW4MFQFyXmCmpmqWlbqGYRmiyg==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/progress-indicator": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/list": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/list/-/list-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-TQ1ppqiCMQj/P7bGD4edbIIv4goczZUoiUAaPq/feb1dflvrFMzYqJ7tQRRCyBL8nRhJoI2x99tk8Q2RXvlGUQ==", - "dependencies": { - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/density": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/list/-/list-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-Is0NV91sJlXF5pOebYAtWLF4wU2MJDbYqztML/zQNENkQxDOvEXu3nWNb3YScMIYJJXvARO0Liur5K4yPagS1Q==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/menu": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/menu/-/menu-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-IlAh61xzrzxXs38QZlt74UYt8J431zGznSzDtB1Fqs6YFNd11QPKoiRXn1J2Qu/lUxbFV7i8NBKMCKtia0n6/Q==", - "dependencies": { - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/list": "15.0.0-canary.bc9ae6c9c.0", - "@material/menu-surface": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/menu/-/menu-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-D11QU1dXqLbh5X1zKlEhS3QWh0b5BPNXlafc5MXfkdJHhOiieb7LC9hMJhbrHtj24FadJ7evaFW/T2ugJbJNnQ==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/list": "15.0.0-canary.7f224ddd4.0", + "@material/menu-surface": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/menu-surface": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/menu-surface/-/menu-surface-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-dMtSPN+olTWE+08M5qe4ea1IZOhVryYqzK0Gyb2u1G75rSArUxCOB5rr6OC/ST3Mq3RS6zGuYo7srZt4534K9Q==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/menu-surface/-/menu-surface-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-7RZHvw0gbwppaAJ/Oh5SWmfAKJ62aw1IMB3+3MRwsb5PLoV666wInYa+zJfE4i7qBeOn904xqT2Nko5hY0ssrg==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/notched-outline": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/notched-outline/-/notched-outline-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-WuurMg44xexkvLTBTnsO0A+qnzFjpcPdvgWBGstBepYozsvSF9zJGdb1x7Zv1MmqbpYh/Ohnuxtb/Y3jOh6irg==", - "dependencies": { - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/floating-label": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/notched-outline/-/notched-outline-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-Yg2usuKB2DKlKIBISbie9BFsOVuffF71xjbxPbybvqemxqUBd+bD5/t6H1fLE+F8/NCu5JMigho4ewUU+0RCiw==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/floating-label": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/progress-indicator": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/progress-indicator/-/progress-indicator-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-uOnsvqw5F2fkeTnTl4MrYzjI7KCLmmLyZaM0cgLNuLsWVlddQE+SGMl28tENx7DUK3HebWq0FxCP8f25LuDD+w==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/progress-indicator/-/progress-indicator-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-UPbDjE5CqT+SqTs0mNFG6uFEw7wBlgYmh+noSkQ6ty/EURm8lF125dmi4dv4kW0+octonMXqkGtAoZwLIHKf/w==", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@material/radio": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/radio/-/radio-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-ehzOK+U1IxQN+OQjgD2lsnf1t7t7RAwQzeO6Czkiuid29ookYbQynWuLWk7NW8H8ohl7lnmfqTP1xSNkkL/F0g==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/density": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/radio/-/radio-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-wR1X0Sr0KmQLu6+YOFKAI84G3L6psqd7Kys5kfb8WKBM36zxO5HQXC5nJm/Y0rdn22ixzsIz2GBo0MNU4V4k1A==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/ripple": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-JfLW+g3GMVDv4cruQ19+HUxpKVdWCldFlIPw1UYezz2h3WTNDy05S3uP2zUdXzZ01C3dkBFviv4nqZ0GCT16MA==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-JqOsWM1f4aGdotP0rh1vZlPZTg6lZgh39FIYHFMfOwfhR+LAikUJ+37ciqZuewgzXB6iiRO6a8aUH6HR5SJYPg==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/rtl": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-SkKLNLFp5QtG7/JEFg9R92qq4MzTcZ5As6sWbH7rRg6ahTHoJEuqE+pOb9Vrtbj84k5gtX+vCYPvCILtSlr2uw==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-UVf14qAtmPiaaZjuJtmN36HETyoKWmsZM/qn1L5ciR2URb8O035dFWnz4ZWFMmAYBno/L7JiZaCkPurv2ZNrGA==", "dependencies": { - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/segmented-button": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/segmented-button/-/segmented-button-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-YDwkCWP9l5mIZJ7pZJZ2hMDxfBlIGVJ+deNzr8O+Z7/xC5LGXbl4R5aPtUVHygvXAXxpf5096ZD+dSXzYzvWlw==", - "dependencies": { - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/segmented-button/-/segmented-button-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-LCnVRUSAhELTKI/9hSvyvIvQIpPpqF29BV+O9yM4WoNNmNWqTulvuiv7grHZl6Z+kJuxSg4BGbsPxxb9dXozPg==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/select": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/select/-/select-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-unfOWVf7T0sixVG+3k3RTuATfzqvCF6QAzA6J9rlCh/Tq4HuIBNDdV4z19IVu4zwmgWYxY0iSvqWUvdJJYwakQ==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/density": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/floating-label": "15.0.0-canary.bc9ae6c9c.0", - "@material/line-ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/list": "15.0.0-canary.bc9ae6c9c.0", - "@material/menu": "15.0.0-canary.bc9ae6c9c.0", - "@material/menu-surface": "15.0.0-canary.bc9ae6c9c.0", - "@material/notched-outline": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/select/-/select-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-WioZtQEXRpglum0cMSzSqocnhsGRr+ZIhvKb3FlaNrTaK8H3Y4QA7rVjv3emRtrLOOjaT6/RiIaUMTo9AGzWQQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/floating-label": "15.0.0-canary.7f224ddd4.0", + "@material/line-ripple": "15.0.0-canary.7f224ddd4.0", + "@material/list": "15.0.0-canary.7f224ddd4.0", + "@material/menu": "15.0.0-canary.7f224ddd4.0", + "@material/menu-surface": "15.0.0-canary.7f224ddd4.0", + "@material/notched-outline": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/shape": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/shape/-/shape-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-Dsvr771ZKC46ODzoixLdGwlLEQLfxfLrtnRojXABoZf5G3o9KtJU+J+5Ld5aa960OAsCzzANuaub4iR88b1guA==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/shape/-/shape-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-8z8l1W3+cymObunJoRhwFPKZ+FyECfJ4MJykNiaZq7XJFZkV6xNmqAVrrbQj93FtLsECn9g4PjjIomguVn/OEw==", "dependencies": { - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/slider": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/slider/-/slider-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-3AEu+7PwW4DSNLndue47dh2u7ga4hDJRYmuu7wnJCIWJBnLCkp6C92kNc4Rj5iQY2ftJio5aj1gqryluh5tlYg==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/slider/-/slider-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-QU/WSaSWlLKQRqOhJrPgm29wqvvzRusMqwAcrCh1JTrCl+xwJ43q5WLDfjYhubeKtrEEgGu9tekkAiYfMG7EBw==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/snackbar": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/snackbar/-/snackbar-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-TwwQSYxfGK6mc03/rdDamycND6o+1p61WNd7ElZv1F1CLxB4ihRjbCoH7Qo+oVDaP8CTpjeclka+24RLhQq0mA==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/button": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/icon-button": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/snackbar/-/snackbar-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-sm7EbVKddaXpT/aXAYBdPoN0k8yeg9+dprgBUkrdqGzWJAeCkxb4fv2B3He88YiCtvkTz2KLY4CThPQBSEsMFQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/button": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/icon-button": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/switch": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/switch/-/switch-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-OjUjtT0kRz1ASAsOS+dNzwMwvsjmqy5edK57692qmrP6bL4GblFfBDoiNJ6t0AN4OaKcmL5Hy/xNrTdOZW7Qqw==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/density": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/switch/-/switch-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-lEDJfRvkVyyeHWIBfoxYjJVl+WlEAE2kZ/+6OqB1FW0OV8ftTODZGhHRSzjVBA1/p4FPuhAtKtoK9jTpa4AZjA==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", "safevalues": "^0.3.4", "tslib": "^2.1.0" } }, "node_modules/@material/tab": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/tab/-/tab-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-s/L9otAwn/pZwVQZBRQJmPqYeNbjoEbzbjMpDQf/VBG/6dJ+aP03ilIBEkqo8NVnCoChqcdtVCoDNRtbU+yp6w==", - "dependencies": { - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/tab-indicator": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/tab/-/tab-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-E1xGACImyCLurhnizyOTCgOiVezce4HlBFAI6YhJo/AyVwjN2Dtas4ZLQMvvWWqpyhITNkeYdOchwCC1mrz3AQ==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/tab-bar": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/tab-bar/-/tab-bar-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-Xmtq0wJGfu5k+zQeFeNsr4bUKv7L+feCmUp/gsapJ655LQKMXOUQZtSv9ZqWOfrCMy55hoF1CzGFV+oN3tyWWQ==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/density": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/tab": "15.0.0-canary.bc9ae6c9c.0", - "@material/tab-indicator": "15.0.0-canary.bc9ae6c9c.0", - "@material/tab-scroller": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/tab-bar/-/tab-bar-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-p1Asb2NzrcECvAQU3b2SYrpyJGyJLQWR+nXTYzDKE8WOpLIRCXap2audNqD7fvN/A20UJ1J8U01ptrvCkwJ4eA==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/tab": "15.0.0-canary.7f224ddd4.0", + "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0", + "@material/tab-scroller": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/tab-indicator": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/tab-indicator/-/tab-indicator-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-despCJYi1GrDDq7F2hvLQkObHnSLZPPDxnOzU16zJ6FNYvIdszgfzn2HgAZ6pl5hLOexQ8cla6cAqjTDuaJBhQ==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/tab-indicator/-/tab-indicator-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-h9Td3MPqbs33spcPS7ecByRHraYgU4tNCZpZzZXw31RypjKvISDv/PS5wcA4RmWqNGih78T7xg4QIGsZg4Pk4w==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/tab-scroller": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/tab-scroller/-/tab-scroller-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-QWHG/EWxirj4V9u2IHz+OSY9XCWrnNrPnNgEufxAJVUKV/A8ma1DYeFSQqxhX709R8wKGdycJksg0Flkl7Gq7w==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/tab": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/tab-scroller/-/tab-scroller-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-LFeYNjQpdXecwECd8UaqHYbhscDCwhGln5Yh+3ctvcEgvmDPNjhKn/DL3sWprWvG8NAhP6sHMrsGhQFVdCWtTg==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/tab": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/textfield": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/textfield/-/textfield-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-R3qRex9kCaZIAK8DuxPnVC42R0OaW7AB7fsFknDKeTeVQvRcbnV8E+iWSdqTiGdsi6QQHifX8idUrXw+O45zPw==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/density": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/floating-label": "15.0.0-canary.bc9ae6c9c.0", - "@material/line-ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/notched-outline": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/textfield/-/textfield-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-AExmFvgE5nNF0UA4l2cSzPghtxSUQeeoyRjFLHLy+oAaE4eKZFrSy0zEpqPeWPQpEMDZk+6Y+6T3cOFYBeSvsw==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/floating-label": "15.0.0-canary.7f224ddd4.0", + "@material/line-ripple": "15.0.0-canary.7f224ddd4.0", + "@material/notched-outline": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/theme": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/theme/-/theme-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-CpUwXGE0dbhxQ45Hu9r9wbJtO/MAlv5ER4tBHA9tp/K+SU+lDgurBE2touFMg5INmdfVNtdumxb0nPPLaNQcUg==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/theme/-/theme-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-hs45hJoE9yVnoVOcsN1jklyOa51U4lzWsEnQEuJTPOk2+0HqCQ0yv/q0InpSnm2i69fNSyZC60+8HADZGF8ugQ==", "dependencies": { - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/tokens": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/tokens/-/tokens-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-nbEuGj05txWz6ZMUanpM47SaAD7soyjKILR+XwDell9Zg3bGhsnexCNXPEz2fD+YgomS+jM5XmIcaJJHg/H93Q==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/tokens/-/tokens-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-r9TDoicmcT7FhUXC4eYMFnt9TZsz0G8T3wXvkKncLppYvZ517gPyD/1+yhuGfGOxAzxTrM66S/oEc1fFE2q4hw==", "dependencies": { - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0" + "@material/elevation": "15.0.0-canary.7f224ddd4.0" } }, "node_modules/@material/tooltip": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/tooltip/-/tooltip-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-UzuXp0b9NuWuYLYpPguxrjbJnCmT/Cco8CkjI/6JajxaeA3o2XEBbQfRMTq8PTafuBjCHTc0b0mQY7rtxUp1Gg==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/button": "15.0.0-canary.bc9ae6c9c.0", - "@material/dom": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/tooltip/-/tooltip-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-8qNk3pmPLTnam3XYC1sZuplQXW9xLn4Z4MI3D+U17Q7pfNZfoOugGr+d2cLA9yWAEjVJYB0mj8Yu86+udo4N9w==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/button": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "safevalues": "^0.3.4", "tslib": "^2.1.0" } }, "node_modules/@material/top-app-bar": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/top-app-bar/-/top-app-bar-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-vJWjsvqtdSD5+yQ/9vgoBtBSCvPJ5uF/DVssv8Hdhgs1PYaAcODUi77kdi0+sy/TaWyOsTkQixqmwnFS16zesA==", - "dependencies": { - "@material/animation": "15.0.0-canary.bc9ae6c9c.0", - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", - "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/shape": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", - "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/top-app-bar/-/top-app-bar-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-SARR5/ClYT4CLe9qAXakbr0i0cMY0V3V4pe3ElIJPfL2Z2c4wGR1mTR8m2LxU1MfGKK8aRoUdtfKaxWejp+eNA==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/touch-target": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/touch-target/-/touch-target-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-AqYh9fjt+tv4ZE0C6MeYHblS2H+XwLbDl2mtyrK0DOEnCVQk5/l5ImKDfhrUdFWHvS4a5nBM4AA+sa7KaroLoA==", - "dependencies": { - "@material/base": "15.0.0-canary.bc9ae6c9c.0", - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/touch-target/-/touch-target-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-BJo/wFKHPYLGsRaIpd7vsQwKr02LtO2e89Psv0on/p0OephlNIgeB9dD9W+bQmaeZsZ6liKSKRl6wJWDiK71PA==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@material/typography": { - "version": "15.0.0-canary.bc9ae6c9c.0", - "resolved": "https://registry.npmjs.org/@material/typography/-/typography-15.0.0-canary.bc9ae6c9c.0.tgz", - "integrity": "sha512-CKsG1zyv34AKPNyZC8olER2OdPII64iR2SzQjpqh1UUvmIFiMPk23LvQ1OnC5aCB14pOXzmVgvJt31r9eNdZ6Q==", + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/typography/-/typography-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-kBaZeCGD50iq1DeRRH5OM5Jl7Gdk+/NOfKArkY4ksBZvJiStJ7ACAhpvb8MEGm4s3jvDInQFLsDq3hL+SA79sQ==", "dependencies": { - "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", - "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", "tslib": "^2.1.0" } }, "node_modules/@ngtools/webpack": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-16.2.9.tgz", - "integrity": "sha512-rOclD7FfT4OSwVA0nDnULbJS6TORJ0+sQiuT2ebaNFErYr3LOm6Zut05tnmzFw8q1cePrILbG+xpnbggNr9Pyw==", + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-17.3.3.tgz", + "integrity": "sha512-053KMbg1Tb+Mmg4Htsv8yTpI7ABghguoxhwosQXKB0CjO6M0oexuvdaxbRDQ1vd5xYNOW9LcOfxOMPIwyU4BBA==", "dev": true, "engines": { - "node": "^16.14.0 || >=18.10.0", + "node": "^18.13.0 || >=20.9.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, "peerDependencies": { - "@angular/compiler-cli": "^16.0.0", - "typescript": ">=4.9.3 <5.2", + "@angular/compiler-cli": "^17.0.0", + "typescript": ">=5.2 <5.5", "webpack": "^5.54.0" } }, @@ -4717,7 +5177,57 @@ "fastq": "^1.6.0" }, "engines": { - "node": ">= 8" + "node": ">= 8" + } + }, + "node_modules/@npmcli/agent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", + "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/agent/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@npmcli/agent/node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "dev": true, + "engines": { + "node": "14 || >=16.14" } }, "node_modules/@npmcli/fs": { @@ -4733,46 +5243,55 @@ } }, "node_modules/@npmcli/git": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.1.0.tgz", - "integrity": "sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.4.tgz", + "integrity": "sha512-nr6/WezNzuYUppzXRaYu/W4aT5rLxdXqEFupbh6e/ovlYFQ8hpu1UUPV3Ir/YTl+74iXl2ZOMlGzudh9ZPUchQ==", "dev": true, "dependencies": { - "@npmcli/promise-spawn": "^6.0.0", - "lru-cache": "^7.4.4", - "npm-pick-manifest": "^8.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", "proc-log": "^3.0.0", "promise-inflight": "^1.0.1", "promise-retry": "^2.0.1", "semver": "^7.3.5", - "which": "^3.0.0" + "which": "^4.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" } }, "node_modules/@npmcli/git/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", "dev": true, "engines": { - "node": ">=12" + "node": "14 || >=16.14" } }, "node_modules/@npmcli/git/node_modules/which": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", - "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, "dependencies": { - "isexe": "^2.0.0" + "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.13.0 || >=18.0.0" } }, "node_modules/@npmcli/installed-package-contents": { @@ -4791,85 +5310,171 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@npmcli/move-file": { + "node_modules/@npmcli/node-gyp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", + "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.0.0.tgz", + "integrity": "sha512-OI2zdYBLhQ7kpNPaJxiflofYIpkNLi+lnGdzqUOfRmCF3r2l1nadcjtCYMJKv/Utm/ZtlffaUuTiAktPHbc17g==", + "dev": true, + "dependencies": { + "@npmcli/git": "^5.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^7.0.0", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "proc-log": "^3.0.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/brace-expansion": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", - "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", - "deprecated": "This functionality has been moved to @npmcli/fs", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" + "balanced-match": "^1.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/glob": { + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.6", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@npmcli/node-gyp": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", - "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "node_modules/@npmcli/package-json/node_modules/json-parse-even-better-errors": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz", + "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg==", "dev": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/@npmcli/package-json/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@npmcli/promise-spawn": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz", - "integrity": "sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.1.tgz", + "integrity": "sha512-P4KkF9jX3y+7yFUxgcUdDtLy+t4OlDGuEBLNs57AZsfSfg+uV6MLndqGpnl4831ggaEdXwR50XFoZP4VFtHolg==", "dev": true, "dependencies": { - "which": "^3.0.0" + "which": "^4.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" } }, "node_modules/@npmcli/promise-spawn/node_modules/which": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", - "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, "dependencies": { - "isexe": "^2.0.0" + "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/redact": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-1.1.0.tgz", + "integrity": "sha512-PfnWuOkQgu7gCbnSsAisaX7hKOdZ4wSAhAzH3/ph5dSGau52kCRrMMGbiSQLwyTZpgldkZ49b0brkOr1AzGBHQ==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/@npmcli/run-script": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-6.0.2.tgz", - "integrity": "sha512-NCcr1uQo1k5U+SYlnIrbAh3cxy+OQT1VtqiAbxdymSlptbzBb62AjH2xXgjNCoP073hoa1CfCAcwoZ8k96C4nA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-7.0.4.tgz", + "integrity": "sha512-9ApYM/3+rBt9V80aYg6tZfzj3UWdiYyCt7gJUD1VJKvWF5nwKDSICXbYIQbspFTq6TOpbsEtIC0LArB8d9PFmg==", "dev": true, "dependencies": { "@npmcli/node-gyp": "^3.0.0", - "@npmcli/promise-spawn": "^6.0.0", - "node-gyp": "^9.0.0", - "read-package-json-fast": "^3.0.0", - "which": "^3.0.0" + "@npmcli/package-json": "^5.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "node-gyp": "^10.0.0", + "which": "^4.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" } }, "node_modules/@npmcli/run-script/node_modules/which": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", - "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, "dependencies": { - "isexe": "^2.0.0" + "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.13.0 || >=18.0.0" } }, "node_modules/@pkgjs/parseargs": { @@ -4882,152 +5487,287 @@ "node": ">=14" } }, - "node_modules/@schematics/angular": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-16.2.9.tgz", - "integrity": "sha512-uiU2YbZRVHgk1N1DDsek/5CKhfpZ8myJYNJk8eHV5LswnXOP3aqvH23VhneaAgOYwK5fISC7eMG0pLVKMvFfZQ==", + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.0.tgz", + "integrity": "sha512-jwXtxYbRt1V+CdQSy6Z+uZti7JF5irRKF8hlKfEnF/xJpcNGuuiZMBvuoYM+x9sr9iWGnzrlM0+9hvQ1kgkf1w==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "@angular-devkit/core": "16.2.9", - "@angular-devkit/schematics": "16.2.9", - "jsonc-parser": "3.2.0" - }, - "engines": { - "node": "^16.14.0 || >=18.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@sigstore/bundle": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-1.1.0.tgz", - "integrity": "sha512-PFutXEy0SmQxYI4texPw3dd2KewuNqv7OuK1ZFtY2fM754yhvG2KdgwIhRnoEE2uHdtdGNQ8s0lb94dW9sELog==", + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.0.tgz", + "integrity": "sha512-fI9nduZhCccjzlsA/OuAwtFGWocxA4gqXGTLvOyiF8d+8o0fZUeSztixkYjcGq1fGZY3Tkq4yRvHPFxU+jdZ9Q==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@sigstore/protobuf-specs": "^0.2.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@sigstore/protobuf-specs": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.2.1.tgz", - "integrity": "sha512-XTWVxnWJu+c1oCshMLwnKvz8ZQJJDVOlciMfgpJBQbThVjKTCG8dwyhgLngBD2KN0ap9F/gOV8rFDEx8uh7R2A==", + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.0.tgz", + "integrity": "sha512-BcnSPRM76/cD2gQC+rQNGBN6GStBs2pl/FpweW8JYuz5J/IEa0Fr4AtrPv766DB/6b2MZ/AfSIOSGw3nEIP8SA==", + "cpu": [ + "arm64" + ], "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@sigstore/sign": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-1.0.0.tgz", - "integrity": "sha512-INxFVNQteLtcfGmcoldzV6Je0sbbfh9I16DM4yJPw3j5+TFP8X6uIiA18mvpEa9yyeycAKgPmOA3X9hVdVTPUA==", + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.0.tgz", + "integrity": "sha512-LDyFB9GRolGN7XI6955aFeI3wCdCUszFWumWU0deHA8VpR3nWRrjG6GtGjBrQxQKFevnUTHKCfPR4IvrW3kCgQ==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@sigstore/bundle": "^1.1.0", - "@sigstore/protobuf-specs": "^0.2.0", - "make-fetch-happen": "^11.0.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@sigstore/sign/node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.0.tgz", + "integrity": "sha512-ygrGVhQP47mRh0AAD0zl6QqCbNsf0eTo+vgwkY6LunBcg0f2Jv365GXlDUECIyoXp1kKwL5WW6rsO429DBY/bA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.0.tgz", + "integrity": "sha512-x+uJ6MAYRlHGe9wi4HQjxpaKHPM3d3JjqqCkeC5gpnnI6OWovLdXTpfa8trjxPLnWKyBsSi5kne+146GAxFt4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.0.tgz", + "integrity": "sha512-nrRw8ZTQKg6+Lttwqo6a2VxR9tOroa2m91XbdQ2sUUzHoedXlsyvY1fN4xWdqz8PKmf4orDwejxXHjh7YBGUCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.0.tgz", + "integrity": "sha512-xV0d5jDb4aFu84XKr+lcUJ9y3qpIWhttO3Qev97z8DKLXR62LC3cXT/bMZXrjLF9X+P5oSmJTzAhqwUbY96PnA==", + "cpu": [ + "ppc64le" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.0.tgz", + "integrity": "sha512-SDDhBQwZX6LPRoPYjAZWyL27LbcBo7WdBFWJi5PI9RPCzU8ijzkQn7tt8NXiXRiFMJCVpkuMkBf4OxSxVMizAw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.0.tgz", + "integrity": "sha512-RxB/qez8zIDshNJDufYlTT0ZTVut5eCpAZ3bdXDU9yTxBzui3KhbGjROK2OYTTor7alM7XBhssgoO3CZ0XD3qA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.0.tgz", + "integrity": "sha512-C6y6z2eCNCfhZxT9u+jAM2Fup89ZjiG5pIzZIDycs1IwESviLxwkQcFRGLjnDrP+PT+v5i4YFvlcfAs+LnreXg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.0.tgz", + "integrity": "sha512-i0QwbHYfnOMYsBEyjxcwGu5SMIi9sImDVjDg087hpzXqhBSosxkE7gyIYFHgfFl4mr7RrXksIBZ4DoLoP4FhJg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.0.tgz", + "integrity": "sha512-Fq52EYb0riNHLBTAcL0cun+rRwyZ10S9vKzhGKKgeD+XbwunszSY0rVMco5KbOsTlwovP2rTOkiII/fQ4ih/zQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.0.tgz", + "integrity": "sha512-e/PBHxPdJ00O9p5Ui43+vixSgVf4NlLsmV6QneGERJ3lnjIua/kim6PRFe3iDueT1rQcgSkYP8ZBBXa/h4iPvw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.0.tgz", + "integrity": "sha512-aGg7iToJjdklmxlUlJh/PaPNa4PmqHfyRMLunbL3eaMO0gp656+q1zOKkpJ/CVe9CryJv6tAN1HDoR8cNGzkag==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@schematics/angular": { + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-17.3.3.tgz", + "integrity": "sha512-kNlyjIKTBhfi8Jab3MCkxNRbbpErbzdu0lZNSL8Nidmqs6Tk23Dc1bZe4t/gPNOCkCvQlwYa6X88SjC/ntyVng==", "dev": true, + "dependencies": { + "@angular-devkit/core": "17.3.3", + "@angular-devkit/schematics": "17.3.3", + "jsonc-parser": "3.2.1" + }, "engines": { - "node": ">= 10" + "node": "^18.13.0 || >=20.9.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" } }, - "node_modules/@sigstore/sign/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "node_modules/@sigstore/bundle": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.1.tgz", + "integrity": "sha512-eqV17lO3EIFqCWK3969Rz+J8MYrRZKw9IBHpSo6DEcEX2c+uzDFOgHE9f2MnyDpfs48LFO4hXmk9KhQ74JzU1g==", "dev": true, "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" + "@sigstore/protobuf-specs": "^0.3.1" }, "engines": { - "node": ">= 6" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@sigstore/sign/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "node_modules/@sigstore/core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-1.1.0.tgz", + "integrity": "sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==", "dev": true, "engines": { - "node": ">=12" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@sigstore/sign/node_modules/make-fetch-happen": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", - "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", + "node_modules/@sigstore/protobuf-specs": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.1.tgz", + "integrity": "sha512-aIL8Z9NsMr3C64jyQzE0XlkEyBLpgEJJFDHLVVStkFV5Q3Il/r/YtY6NJWKQ4cy4AE7spP1IX5Jq7VCAxHHMfQ==", "dev": true, - "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^17.0.0", - "http-cache-semantics": "^4.1.1", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^5.0.0", - "minipass-fetch": "^3.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^10.0.0" - }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@sigstore/sign/node_modules/minipass-fetch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz", - "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==", + "node_modules/@sigstore/sign": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.0.tgz", + "integrity": "sha512-tsAyV6FC3R3pHmKS880IXcDJuiFJiKITO1jxR1qbplcsBkZLBmjrEw5GbC7ikD6f5RU1hr7WnmxB/2kKc1qUWQ==", "dev": true, "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" + "@sigstore/bundle": "^2.3.0", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.1", + "make-fetch-happen": "^13.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@sigstore/sign/node_modules/minipass-fetch/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "node_modules/@sigstore/tuf": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.2.tgz", + "integrity": "sha512-mwbY1VrEGU4CO55t+Kl6I7WZzIl+ysSzEYdA1Nv/FTrl2bkeaPXo5PnWZAVfcY2zSdhOpsUTJW67/M2zHXGn5w==", "dev": true, + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.0", + "tuf-js": "^2.2.0" + }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@sigstore/tuf": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-1.0.3.tgz", - "integrity": "sha512-2bRovzs0nJZFlCN3rXirE4gwxCn97JNjMmwpecqlbgV9WcxX7WRuIrgzx/X7Ib7MYRbyUTpBYE0s2x6AmZXnlg==", + "node_modules/@sigstore/verify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.0.tgz", + "integrity": "sha512-hQF60nc9yab+Csi4AyoAmilGNfpXT+EXdBgFkP9OgPwIBPwyqVf7JAWPtmqrrrneTmAT6ojv7OlH1f6Ix5BG4Q==", "dev": true, "dependencies": { - "@sigstore/protobuf-specs": "^0.2.0", - "tuf-js": "^1.1.7" + "@sigstore/bundle": "^2.3.1", + "@sigstore/core": "^1.1.0", + "@sigstore/protobuf-specs": "^0.3.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/@sinclair/typebox": { @@ -5054,35 +5794,50 @@ "@sinonjs/commons": "^3.0.0" } }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true, - "engines": { - "node": ">= 6" - } + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true }, "node_modules/@tufjs/canonical-json": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-1.0.0.tgz", - "integrity": "sha512-QTnf++uxunWvG2z3UFNzAoQPHxnSXOwtaI3iJ+AohhV+5vONuArPjJE7aPXPVXfXJsqrVbZBu9b81AJoSd09IQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", "dev": true, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/@tufjs/models": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-1.0.4.tgz", - "integrity": "sha512-qaGV9ltJP0EO25YfFUPhxRVK0evXFIAGicsVXuRim4Ed9cjPxYhNnNJ49SFmbeLgtxpslIkX317IgpfcHPVj/A==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.0.tgz", + "integrity": "sha512-c8nj8BaOExmZKO2DXhDfegyhSGcG9E/mPN3U13L+/PsoWm1uaGiHHjxqSHQiasDBQwDA3aHuw9+9spYAP1qvvg==", "dev": true, "dependencies": { - "@tufjs/canonical-json": "1.0.0", - "minimatch": "^9.0.0" + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/@tufjs/models/node_modules/brace-expansion": { @@ -5095,9 +5850,9 @@ } }, "node_modules/@tufjs/models/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -5151,9 +5906,9 @@ } }, "node_modules/@types/body-parser": { - "version": "1.19.4", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.4.tgz", - "integrity": "sha512-N7UDG0/xiPQa2D/XrVJXjkWbpqHCd2sBaB32ggRF2l83RhPfamgKGF8gwwqyksS95qUS5ZYF9aF+lLPRlwI2UA==", + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", "dev": true, "dependencies": { "@types/connect": "*", @@ -5161,27 +5916,27 @@ } }, "node_modules/@types/bonjour": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.12.tgz", - "integrity": "sha512-ky0kWSqXVxSqgqJvPIkgFkcn4C8MnRog308Ou8xBBIVo39OmUFy+jqNe0nPwLCDFxUpmT9EvT91YzOJgkDRcFg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/connect": { - "version": "3.4.37", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.37.tgz", - "integrity": "sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==", + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.2.tgz", - "integrity": "sha512-gX2j9x+NzSh4zOhnRPSdPPmTepS4DfxES0AvIFv3jGv5QyeAJf6u6dY5/BAoAJU9Qq1uTvwOku8SSC2GnCRl6Q==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", "dev": true, "dependencies": { "@types/express-serve-static-core": "*", @@ -5209,15 +5964,15 @@ } }, "node_modules/@types/estree": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.4.tgz", - "integrity": "sha512-2JwWnHK9H+wUZNorf2Zr6ves96WHoWDJIftkcxPKsS7Djta6Zu519LarhRNljPXkpsZR2ZMwNCPeW7omW07BJw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, "node_modules/@types/express": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.20.tgz", - "integrity": "sha512-rOaqlkgEvOW495xErXMsmyX3WKBInbhG5eqojXYi3cGUaLoRDlXa5d52fkfWZT963AZ3v2eZ4MbKE6WpDAGVsw==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "dev": true, "dependencies": { "@types/body-parser": "*", @@ -5227,9 +5982,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.39", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.39.tgz", - "integrity": "sha512-BiEUfAiGCOllomsRAZOiMFP7LAnrifHpt56pc4Z7l9K6ACyN06Ns1JLMBxwkfLOjJRlSf06NwWsT7yzfpaVpyQ==", + "version": "4.17.43", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz", + "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==", "dev": true, "dependencies": { "@types/node": "*", @@ -5248,15 +6003,15 @@ } }, "node_modules/@types/http-errors": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.3.tgz", - "integrity": "sha512-pP0P/9BnCj1OVvQR2lF41EkDG/lWWnDyA203b/4Fmi2eTyORnBtcDoKDwjWQthELrBvWkMOrvSOnZ8OVlW6tXA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", "dev": true }, "node_modules/@types/http-proxy": { - "version": "1.17.13", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.13.tgz", - "integrity": "sha512-GkhdWcMNiR5QSQRYnJ+/oXzu0+7JJEPC8vkWXK351BkhjraZF+1W13CUYARUvX9+NqIU2n6YHA4iwywsc/M6Sw==", + "version": "1.17.14", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", + "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", "dev": true, "dependencies": { "@types/node": "*" @@ -5287,9 +6042,9 @@ } }, "node_modules/@types/jest": { - "version": "29.5.7", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.7.tgz", - "integrity": "sha512-HLyetab6KVPSiF+7pFcUyMeLsx25LDNDemw9mGsJBkai/oouwrjTycocSDYopMEwFhN2Y4s9oPyOCZNofgSt2g==", + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", "dev": true, "dependencies": { "expect": "^29.0.0", @@ -5314,9 +6069,9 @@ "dev": true }, "node_modules/@types/mime": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.4.tgz", - "integrity": "sha512-1Gjee59G25MrQGk8bsNvC6fxNiRgUlGn2wlhGf95a59DrprnnHk80FIMMFG9XHMdrfsuA119ht06QPDXA1Z7tw==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "dev": true }, "node_modules/@types/node": { @@ -5329,24 +6084,24 @@ } }, "node_modules/@types/node-forge": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.8.tgz", - "integrity": "sha512-vGXshY9vim9CJjrpcS5raqSjEfKlJcWy2HNdgUasR66fAnVEYarrf1ULV4nfvpC1nZq/moA9qyqBcu83x+Jlrg==", + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/qs": { - "version": "6.9.9", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.9.tgz", - "integrity": "sha512-wYLxw35euwqGvTDx6zfY1vokBFnsK0HNrzc6xNHchxfO2hpuRg74GbkEW7e3sSmPvj0TjCDT1VCa6OtHXnubsg==", + "version": "6.9.14", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz", + "integrity": "sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==", "dev": true }, "node_modules/@types/range-parser": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.6.tgz", - "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "dev": true }, "node_modules/@types/retry": { @@ -5356,15 +6111,15 @@ "dev": true }, "node_modules/@types/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "dev": true }, "node_modules/@types/send": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.3.tgz", - "integrity": "sha512-/7fKxvKUoETxjFUsuFlPB9YndePpxxRAOfGC/yJdc9kTjTeP5kRCTzfnE8kPUKCeyiyIZu0YQ76s50hCedI1ug==", + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", "dev": true, "dependencies": { "@types/mime": "^1", @@ -5372,29 +6127,29 @@ } }, "node_modules/@types/serve-index": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.3.tgz", - "integrity": "sha512-4KG+yMEuvDPRrYq5fyVm/I2uqAJSAwZK9VSa+Zf+zUq9/oxSSvy3kkIqyL+jjStv6UCVi8/Aho0NHtB1Fwosrg==", + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", "dev": true, "dependencies": { "@types/express": "*" } }, "node_modules/@types/serve-static": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.4.tgz", - "integrity": "sha512-aqqNfs1XTF0HDrFdlY//+SGUxmdSUbjeRXb5iaZc3x0/vMbYmdw9qvOgHWOyyLFxSSRnUuP5+724zBgfw8/WAw==", + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", "dev": true, "dependencies": { "@types/http-errors": "*", - "@types/mime": "*", - "@types/node": "*" + "@types/node": "*", + "@types/send": "*" } }, "node_modules/@types/sockjs": { - "version": "0.3.35", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.35.tgz", - "integrity": "sha512-tIF57KB+ZvOBpAQwSaACfEu7htponHXaFzP7RfKYgsOS0NoYnn+9+jzp7bbq4fWerizI3dTB4NfAZoyeQKWJLw==", + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", "dev": true, "dependencies": { "@types/node": "*" @@ -5413,9 +6168,9 @@ "dev": true }, "node_modules/@types/ws": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.8.tgz", - "integrity": "sha512-flUksGIQCnJd6sZ1l5dqCEG/ksaoAg/eUwiLAGTJQcfgvZJKF++Ta4bJA6A5aPSJmsr+xlseHn4KLgVlNnvPTg==", + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", "dev": true, "dependencies": { "@types/node": "*" @@ -5437,32 +6192,33 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", - "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.5.0.tgz", + "integrity": "sha512-HpqNTH8Du34nLxbKgVMGljZMG0rJd2O9ecvr2QLYp+7512ty1j42KnsFwspPXg1Vh8an9YImf6CokUBltisZFQ==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "7.5.0", + "@typescript-eslint/type-utils": "7.5.0", + "@typescript-eslint/utils": "7.5.0", + "@typescript-eslint/visitor-keys": "7.5.0", "debug": "^4.3.4", "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -5471,25 +6227,26 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", - "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.5.0.tgz", + "integrity": "sha512-cj+XGhNujfD2/wzR1tabNsidnYRaFfEkcULdcIyVBYcXjBvBKOes+mpMBP7hMpOyk+gBcfXsrg4NBGAStQyxjQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/scope-manager": "7.5.0", + "@typescript-eslint/types": "7.5.0", + "@typescript-eslint/typescript-estree": "7.5.0", + "@typescript-eslint/visitor-keys": "7.5.0", "debug": "^4.3.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -5498,16 +6255,16 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.5.0.tgz", + "integrity": "sha512-Z1r7uJY0MDeUlql9XJ6kRVgk/sP11sr3HKXn268HZyqL7i4cEfrdFuSSY/0tUqT37l5zT0tJOsuDP16kio85iA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" + "@typescript-eslint/types": "7.5.0", + "@typescript-eslint/visitor-keys": "7.5.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -5515,25 +6272,25 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.5.0.tgz", + "integrity": "sha512-A021Rj33+G8mx2Dqh0nMO9GyjjIBK3MqgVgZ2qlKf6CJy51wY/lkkFqq3TqqnH34XyAHUkq27IjlUkWlQRpLHw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", + "@typescript-eslint/typescript-estree": "7.5.0", + "@typescript-eslint/utils": "7.5.0", "debug": "^4.3.4", - "tsutils": "^3.21.0" + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -5542,12 +6299,12 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.5.0.tgz", + "integrity": "sha512-tv5B4IHeAdhR7uS4+bf8Ov3k793VEVHd45viRRkehIUZxm0WF82VPiLgHzA/Xl4TGPg1ZD49vfxBKFPecD5/mg==", "dev": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -5555,21 +6312,22 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.5.0.tgz", + "integrity": "sha512-YklQQfe0Rv2PZEueLTUffiQGKQneiIEKKnfIqPIOxgM9lKSZFCjT5Ad4VqRKj/U4+kQE3fa8YQpskViL7WjdPQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", + "@typescript-eslint/types": "7.5.0", + "@typescript-eslint/visitor-keys": "7.5.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -5581,43 +6339,66 @@ } } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.5.0.tgz", + "integrity": "sha512-3vZl9u0R+/FLQcpy2EHyRGNqAS/ofJ3Ji8aebilfJe+fobK8+LbIFmrHciLVDxjDoONmufDcnVSF38KwMEOjzw==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "7.5.0", + "@typescript-eslint/types": "7.5.0", + "@typescript-eslint/typescript-estree": "7.5.0", + "semver": "^7.5.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.5.0.tgz", + "integrity": "sha512-mcuHM/QircmA6O7fy6nn2w/3ditQkj+SgtOc8DW3uQ10Yfj42amm2i+6F2K4YAOPNNTmE6iM1ynM6lrSwdendA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" + "@typescript-eslint/types": "7.5.0", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -5631,15 +6412,15 @@ "dev": true }, "node_modules/@vitejs/plugin-basic-ssl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.0.1.tgz", - "integrity": "sha512-pcub+YbFtFhaGRTo1832FQHQSHvMrlb43974e2eS8EKleR3p1cDdkJFPci1UhwkEf1J9Bz+wKBSzqpKp7nNj2A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.1.0.tgz", + "integrity": "sha512-wO4Dk/rm8u7RNhOf95ZzcEmC9rYOncYgvq4z3duaJrCgjN8BxAnDVyndanfcJZ0O6XZzHz6Q0hTimxTg8Y9g/A==", "dev": true, "engines": { "node": ">=14.6.0" }, "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0" + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" } }, "node_modules/@webassemblyjs/ast": { @@ -5788,99 +6569,6 @@ "@xtuc/long": "4.2.2" } }, - "node_modules/@wessberg/ts-evaluator": { - "version": "0.0.27", - "resolved": "https://registry.npmjs.org/@wessberg/ts-evaluator/-/ts-evaluator-0.0.27.tgz", - "integrity": "sha512-7gOpVm3yYojUp/Yn7F4ZybJRxyqfMNf0LXK5KJiawbPfL0XTsJV+0mgrEDjOIR6Bi0OYk2Cyg4tjFu1r8MCZaA==", - "deprecated": "this package has been renamed to ts-evaluator. Please install ts-evaluator instead", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "jsdom": "^16.4.0", - "object-path": "^0.11.5", - "tslib": "^2.0.3" - }, - "engines": { - "node": ">=10.1.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/wessberg/ts-evaluator?sponsor=1" - }, - "peerDependencies": { - "typescript": ">=3.2.x || >= 4.x" - } - }, - "node_modules/@wessberg/ts-evaluator/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@wessberg/ts-evaluator/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@wessberg/ts-evaluator/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@wessberg/ts-evaluator/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@wessberg/ts-evaluator/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@wessberg/ts-evaluator/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -5906,10 +6594,13 @@ "dev": true }, "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, "node_modules/accepts": { "version": "1.3.8", @@ -5936,28 +6627,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "node_modules/acorn-globals/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/acorn-import-assertions": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", @@ -5977,9 +6646,9 @@ } }, "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", "dev": true, "engines": { "node": ">=0.4.0" @@ -6024,18 +6693,6 @@ "node": ">= 6.0.0" } }, - "node_modules/agentkeepalive": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", - "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", - "dev": true, - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -6164,25 +6821,12 @@ "node": ">= 8" } }, - "node_modules/aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true }, - "node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", - "dev": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -6193,9 +6837,9 @@ } }, "node_modules/array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "dev": true }, "node_modules/array-union": { @@ -6214,9 +6858,9 @@ "dev": true }, "node_modules/autoprefixer": { - "version": "10.4.14", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", - "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==", + "version": "10.4.18", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.18.tgz", + "integrity": "sha512-1DKbDfsr6KUElM6wg+0zRNkB/Q7WcKYAaK+pzXn+Xqmszm/5Xa9coeNdtP88Vi+dPzZnMjhge8GIV49ZQkDa+g==", "dev": true, "funding": [ { @@ -6226,12 +6870,16 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "browserslist": "^4.21.5", - "caniuse-lite": "^1.0.30001464", - "fraction.js": "^4.2.0", + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001591", + "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", "postcss-value-parser": "^4.2.0" @@ -6386,13 +7034,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz", - "integrity": "sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q==", + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz", + "integrity": "sha512-rpIuu//y5OX6jVU+a5BCn1R5RSZYWAl2Nar76iwaOdycqb6JPxediskWFMMl7stfwNJR4b7eiQvh5fB5TEQJTQ==", "dev": true, "dependencies": { "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.4.3", + "@babel/helper-define-polyfill-provider": "^0.6.1", "semver": "^6.3.1" }, "peerDependencies": { @@ -6409,25 +7057,57 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.8.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.6.tgz", - "integrity": "sha512-leDIc4l4tUgU7str5BWLS2h8q2N4Nf6lGZP6UrNDxdtfF2g69eJ5L0H7S8A5Ln/arfFAfHor5InAdZuIOwZdgQ==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.9.0.tgz", + "integrity": "sha512-7nZPG1uzK2Ymhy/NbaOWTg3uibM2BmGASS4vHS4szRZAIR8R6GwA/xAujpdrXU5iyklrimWnLWU+BLF9suPTqg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.5.0", + "core-js-compat": "^3.34.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz", + "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==", "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.3", - "core-js-compat": "^3.33.1" + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.3.tgz", - "integrity": "sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.5.tgz", + "integrity": "sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg==", "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.3" + "@babel/helper-define-polyfill-provider": "^0.5.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator/node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz", + "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -6534,13 +7214,13 @@ } }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dev": true, "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -6548,7 +7228,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -6582,13 +7262,11 @@ "dev": true }, "node_modules/bonjour-service": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", - "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", + "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", "dev": true, "dependencies": { - "array-flatten": "^2.1.2", - "dns-equal": "^1.0.0", "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" } @@ -6621,16 +7299,10 @@ "node": ">=8" } }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, "node_modules/browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "dev": true, "funding": [ { @@ -6647,9 +7319,9 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, "bin": { @@ -6728,17 +7400,17 @@ } }, "node_modules/cacache": { - "version": "17.1.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.1.4.tgz", - "integrity": "sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==", + "version": "18.0.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.2.tgz", + "integrity": "sha512-r3NU8h/P+4lVUHfeRw1dtgQYar3DZMm4/cm2bZgOvrFC/su7budSOeqh52VJIC4U4iG1WWwV6vRW0znqBvxNuw==", "dev": true, "dependencies": { "@npmcli/fs": "^3.1.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", - "lru-cache": "^7.7.1", + "lru-cache": "^10.0.1", "minipass": "^7.0.3", - "minipass-collect": "^1.0.2", + "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^4.0.0", @@ -6747,7 +7419,7 @@ "unique-filename": "^3.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/cacache/node_modules/brace-expansion": { @@ -6760,16 +7432,16 @@ } }, "node_modules/cacache/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", + "jackspeak": "^2.3.6", "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" }, "bin": { "glob": "dist/esm/bin.mjs" @@ -6782,18 +7454,18 @@ } }, "node_modules/cacache/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", "dev": true, "engines": { - "node": ">=12" + "node": "14 || >=16.14" } }, "node_modules/cacache/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -6805,24 +7477,20 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/cacache/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dev": true, "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6847,9 +7515,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001559", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001559.tgz", - "integrity": "sha512-cPiMKZgqgkg5LY3/ntGeLFUpi6tzddBNS58A4tnTgQw1zON7u2sZMU7SzOeVH4tj20++9ggL+V6FDOFMTaFFYA==", + "version": "1.0.30001605", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001605.tgz", + "integrity": "sha512-nXwGlFWo34uliI9z3n6Qc0wZaf7zaZWA1CPZ169La5mV3I/gem7bst0vr5XQH5TJXZIMfDeZyOrZnSlVzKxxHQ==", "dev": true, "funding": [ { @@ -6995,12 +7663,12 @@ } }, "node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", "dev": true, "engines": { - "node": ">= 10" + "node": ">= 12" } }, "node_modules/cliui": { @@ -7071,15 +7739,6 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, - "bin": { - "color-support": "bin.js" - } - }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", @@ -7176,12 +7835,6 @@ "node": ">=0.8" } }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "dev": true - }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -7210,9 +7863,9 @@ "dev": true }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "dev": true, "engines": { "node": ">= 0.6" @@ -7304,12 +7957,12 @@ } }, "node_modules/core-js-compat": { - "version": "3.33.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.2.tgz", - "integrity": "sha512-axfo+wxFVxnqf8RvxTzoAlzW4gRoacrHeoFlc9n0x50+7BEyZL/Rt3hicaED1/CEd7I6tPCPVUYcJwCMO5XUYw==", + "version": "3.36.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz", + "integrity": "sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==", "dev": true, "dependencies": { - "browserslist": "^4.22.1" + "browserslist": "^4.23.0" }, "funding": { "type": "opencollective", @@ -7322,6 +7975,50 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/cosmiconfig/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -7413,10 +8110,16 @@ "node": ">=8" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "node_modules/critters": { - "version": "0.0.20", - "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.20.tgz", - "integrity": "sha512-CImNRorKOl5d8TWcnAz5n5izQ6HFsvz29k327/ELy6UFcmbiZNOsinaKvzv16WZR0P6etfSWYzE47C4/56B3Uw==", + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.22.tgz", + "integrity": "sha512-NU7DEcQZM2Dy8XTKFHxtdnIM/drE312j2T4PCVaSUcS0oBeyT/NImpRw/Ap0zOr/1SE7SgPK9tGPg1WK/sVakw==", "dev": true, "dependencies": { "chalk": "^4.1.0", @@ -7425,7 +8128,7 @@ "domhandler": "^5.0.2", "htmlparser2": "^8.0.2", "postcss": "^8.4.23", - "pretty-bytes": "^5.3.0" + "postcss-media-query-parser": "^0.2.3" } }, "node_modules/critters/node_modules/ansi-styles": { @@ -7513,19 +8216,19 @@ } }, "node_modules/css-loader": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz", - "integrity": "sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.10.0.tgz", + "integrity": "sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==", "dev": true, "dependencies": { "icss-utils": "^5.1.0", - "postcss": "^8.4.21", + "postcss": "^8.4.33", "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.3", - "postcss-modules-scope": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.4", + "postcss-modules-scope": "^3.1.1", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", - "semver": "^7.3.8" + "semver": "^7.5.4" }, "engines": { "node": ">= 12.13.0" @@ -7535,7 +8238,16 @@ "url": "https://opencollective.com/webpack" }, "peerDependencies": { + "@rspack/core": "0.x || 1.x", "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, "node_modules/css-select": { @@ -7578,12 +8290,6 @@ "node": ">=4" } }, - "node_modules/cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, "node_modules/cssstyle": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", @@ -7602,20 +8308,6 @@ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", "dev": true }, - "node_modules/data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "dependencies": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -7693,17 +8385,20 @@ } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-lazy-prop": { @@ -7724,12 +8419,6 @@ "node": ">=0.4.0" } }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -7764,6 +8453,15 @@ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "dev": true }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -7785,12 +8483,6 @@ "node": ">=8" } }, - "node_modules/dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", - "dev": true - }, "node_modules/dns-packet": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", @@ -7841,27 +8533,6 @@ } ] }, - "node_modules/domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dev": true, - "dependencies": { - "webidl-conversions": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/domexception/node_modules/webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/domhandler": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", @@ -7904,9 +8575,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.572", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.572.tgz", - "integrity": "sha512-RlFobl4D3ieetbnR+2EpxdzFl9h0RAJkPK3pfiwMug2nhBin2ZCsGIAJWdpNniLz43sgXam/CgipOmvTA+rUiA==", + "version": "1.4.726", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.726.tgz", + "integrity": "sha512-xtjfBXn53RORwkbyKvDfTajtnTp0OJoPOIBzXvkNbb7+YYvCHJflba3L7Txyx/6Fov3ov2bGPr/n5MTixmPhdQ==", "dev": true }, "node_modules/emittery": { @@ -8030,6 +8701,27 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz", @@ -8037,9 +8729,9 @@ "dev": true }, "node_modules/esbuild": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.17.tgz", - "integrity": "sha512-1GJtYnUxsJreHYA0Y+iQz2UEykonY66HNWOb0yXYZi9/kNrORUEHVg87eQsCtqh59PEJ5YVZJO98JHznMJSWjg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", "dev": true, "hasInstallScript": true, "bin": { @@ -8049,34 +8741,35 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.18.17", - "@esbuild/android-arm64": "0.18.17", - "@esbuild/android-x64": "0.18.17", - "@esbuild/darwin-arm64": "0.18.17", - "@esbuild/darwin-x64": "0.18.17", - "@esbuild/freebsd-arm64": "0.18.17", - "@esbuild/freebsd-x64": "0.18.17", - "@esbuild/linux-arm": "0.18.17", - "@esbuild/linux-arm64": "0.18.17", - "@esbuild/linux-ia32": "0.18.17", - "@esbuild/linux-loong64": "0.18.17", - "@esbuild/linux-mips64el": "0.18.17", - "@esbuild/linux-ppc64": "0.18.17", - "@esbuild/linux-riscv64": "0.18.17", - "@esbuild/linux-s390x": "0.18.17", - "@esbuild/linux-x64": "0.18.17", - "@esbuild/netbsd-x64": "0.18.17", - "@esbuild/openbsd-x64": "0.18.17", - "@esbuild/sunos-x64": "0.18.17", - "@esbuild/win32-arm64": "0.18.17", - "@esbuild/win32-ia32": "0.18.17", - "@esbuild/win32-x64": "0.18.17" + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" } }, "node_modules/esbuild-wasm": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.18.17.tgz", - "integrity": "sha512-9OHGcuRzy+I8ziF9FzjfKLWAPbvi0e/metACVg9k6bK+SI4FFxeV6PcZsz8RIVaMD4YNehw+qj6UMR3+qj/EuQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.20.1.tgz", + "integrity": "sha512-6v/WJubRsjxBbQdz6izgvx7LsVFvVaGmSdwrFHmEzoVgfXL89hkKPoQHsnVI2ngOkcBUQT9kmAM1hVL1k/Av4A==", "dev": true, "bin": { "esbuild": "bin/esbuild" @@ -8150,16 +8843,16 @@ } }, "node_modules/eslint": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", - "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.52.0", - "@humanwhocodes/config-array": "^0.11.13", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -8366,9 +9059,9 @@ } }, "node_modules/eslint/node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -8560,12 +9253,6 @@ "node": ">= 0.6" } }, - "node_modules/eventemitter-asyncresource": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/eventemitter-asyncresource/-/eventemitter-asyncresource-1.0.0.tgz", - "integrity": "sha512-39F7TBIV0G7gTelxwbEqnwhp90eqCPON1k0NwNfwhgKn4Co4ybUbj2pECcXT0B3ztRKZ7Pw1JujUUgmQJHcVAQ==", - "dev": true - }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -8636,17 +9323,17 @@ "dev": true }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dev": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -8677,12 +9364,6 @@ "node": ">= 0.10.0" } }, - "node_modules/express/node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true - }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -8719,9 +9400,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -8877,10 +9558,19 @@ "node": ">=8" } }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, "node_modules/flat-cache": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz", - "integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "dependencies": { "flatted": "^3.2.9", @@ -8888,19 +9578,19 @@ "rimraf": "^3.0.2" }, "engines": { - "node": ">=12.0.0" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "dev": true, "funding": [ { @@ -8945,20 +9635,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -9002,15 +9678,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/fs-monkey": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz", @@ -9046,25 +9713,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", - "dev": true, - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -9084,16 +9732,20 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -9210,18 +9862,6 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, - "node_modules/guess-parser": { - "version": "0.4.22", - "resolved": "https://registry.npmjs.org/guess-parser/-/guess-parser-0.4.22.tgz", - "integrity": "sha512-KcUWZ5ACGaBM69SbqwVIuWGoSAgD+9iJnchR9j/IarVI1jHVeXv+bUXBIMeqVMSKt3zrn0Dgf9UpcOEpPBLbSg==", - "dev": true, - "dependencies": { - "@wessberg/ts-evaluator": "0.0.27" - }, - "peerDependencies": { - "typescript": ">=3.7.5" - } - }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -9238,21 +9878,21 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "dev": true, "engines": { "node": ">= 0.4" @@ -9273,12 +9913,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "dev": true - }, "node_modules/hasown": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", @@ -9291,42 +9925,25 @@ "node": ">= 0.4" } }, - "node_modules/hdr-histogram-js": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz", - "integrity": "sha512-Hkn78wwzWHNCp2uarhzQ2SGFLU3JY8SBDDd3TAABK4fc30wm+MuPOrg5QVFVfkKOQd6Bfz3ukJEI+q9sXEkK1g==", - "dev": true, - "dependencies": { - "@assemblyscript/loader": "^0.10.1", - "base64-js": "^1.2.0", - "pako": "^1.0.3" - } - }, - "node_modules/hdr-histogram-percentiles-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hdr-histogram-percentiles-obj/-/hdr-histogram-percentiles-obj-3.0.0.tgz", - "integrity": "sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==", - "dev": true - }, "node_modules/hosted-git-info": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", - "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.1.tgz", + "integrity": "sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA==", "dev": true, "dependencies": { - "lru-cache": "^7.5.1" + "lru-cache": "^10.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", "dev": true, "engines": { - "node": ">=12" + "node": "14 || >=16.14" } }, "node_modules/hpack.js": { @@ -9371,22 +9988,10 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dev": true, - "dependencies": { - "whatwg-encoding": "^1.0.5" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/html-entities": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", - "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", "dev": true, "funding": [ { @@ -9473,17 +10078,28 @@ } }, "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">= 6" + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" } }, "node_modules/http-proxy-middleware": { @@ -9532,15 +10148,6 @@ "node": ">=10.17.0" } }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dev": true, - "dependencies": { - "ms": "^2.0.0" - } - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -9595,9 +10202,9 @@ } }, "node_modules/ignore-walk": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.3.tgz", - "integrity": "sha512-C7FfFoTA+bI10qfeydT8aZbvr91vAEU+2W5BZUlzPec47oNb07SsOfwYrtxuvOYdUApPP/Qlh4DtAO51Ekk2QA==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.4.tgz", + "integrity": "sha512-t7sv42WkwFkyKbivUCglsQW5YWMskWtbEf4MNKX5u/CCWHKSPzN4FtBQGsQZgCLbxOzpVlcbWVK5KB3auIOjSw==", "dev": true, "dependencies": { "minimatch": "^9.0.0" @@ -9616,9 +10223,9 @@ } }, "node_modules/ignore-walk/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -9644,9 +10251,9 @@ } }, "node_modules/immutable": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", - "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", + "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==", "dev": true }, "node_modules/import-fresh": { @@ -9723,12 +10330,6 @@ "node": ">=8" } }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -9746,38 +10347,38 @@ "dev": true }, "node_modules/ini": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", - "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", + "integrity": "sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==", "dev": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/inquirer": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz", - "integrity": "sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==", + "version": "9.2.15", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.15.tgz", + "integrity": "sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg==", "dev": true, "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", + "@ljharb/through": "^2.3.12", + "ansi-escapes": "^4.3.2", + "chalk": "^5.3.0", "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", + "cli-width": "^4.1.0", + "external-editor": "^3.1.0", + "figures": "^3.2.0", "lodash": "^4.17.21", - "mute-stream": "0.0.8", + "mute-stream": "1.0.0", "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^7.0.0" + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=18" } }, "node_modules/inquirer/node_modules/ansi-styles": { @@ -9796,16 +10397,12 @@ } }, "node_modules/inquirer/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" @@ -9829,31 +10426,37 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/inquirer/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/inquirer/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, "engines": { "node": ">=8" } }, - "node_modules/inquirer/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" }, "engines": { - "node": ">=8" + "node": ">= 12" } }, - "node_modules/ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "dev": true }, "node_modules/ipaddr.js": { @@ -10786,15 +11389,6 @@ "acorn-walk": "^8.0.2" } }, - "node_modules/jest-environment-jsdom/node_modules/acorn-walk": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", - "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/jest-environment-jsdom/node_modules/cssom": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", @@ -11003,27 +11597,6 @@ "node": ">=12" } }, - "node_modules/jest-environment-jsdom/node_modules/ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/jest-environment-jsdom/node_modules/xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", @@ -11304,13 +11877,13 @@ } }, "node_modules/jest-preset-angular": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/jest-preset-angular/-/jest-preset-angular-13.1.2.tgz", - "integrity": "sha512-kuzuIch/YYTMaMnuGDaiZEu++Bjc5WskOAmMwqWO0Grpcd0SulqTOV70Gz6Q/ZOQuMye+LS4KPyIVfqnJr2e3g==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/jest-preset-angular/-/jest-preset-angular-14.0.3.tgz", + "integrity": "sha512-usgBL7x0rXMnMSx8iEFeOozj50W6fp+YAmQcQBUdAXhN+PAXRy4UXL6I/rfcAOU09rnnq7RKsLsmhpp/fFEuag==", "dev": true, "dependencies": { "bs-logger": "^0.2.6", - "esbuild-wasm": ">=0.13.8", + "esbuild-wasm": ">=0.15.13", "jest-environment-jsdom": "^29.0.0", "jest-util": "^29.0.0", "pretty-format": "^29.0.0", @@ -11320,15 +11893,15 @@ "node": "^14.15.0 || >=16.10.0" }, "optionalDependencies": { - "esbuild": ">=0.13.8" + "esbuild": ">=0.15.13" }, "peerDependencies": { - "@angular-devkit/build-angular": ">=13.0.0 <17.0.0", - "@angular/compiler-cli": ">=13.0.0 <17.0.0", - "@angular/core": ">=13.0.0 <17.0.0", - "@angular/platform-browser-dynamic": ">=13.0.0 <17.0.0", + "@angular-devkit/build-angular": ">=15.0.0 <18.0.0", + "@angular/compiler-cli": ">=15.0.0 <18.0.0", + "@angular/core": ">=15.0.0 <18.0.0", + "@angular/platform-browser-dynamic": ">=15.0.0 <18.0.0", "jest": "^29.0.0", - "typescript": ">=4.4" + "typescript": ">=4.8" } }, "node_modules/jest-regex-util": { @@ -12110,56 +12683,10 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", - "dev": true, - "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", "dev": true }, "node_modules/jsesc": { @@ -12211,9 +12738,9 @@ } }, "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", "dev": true }, "node_modules/jsonparse": { @@ -12226,9 +12753,12 @@ ] }, "node_modules/jwt-decode": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", - "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } }, "node_modules/karma-source-map-support": { "version": "1.4.0", @@ -12286,9 +12816,9 @@ } }, "node_modules/less": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", - "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz", + "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==", "dev": true, "dependencies": { "copy-anything": "^2.0.1", @@ -12544,261 +13074,76 @@ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/magic-string": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz", - "integrity": "sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/make-fetch-happen": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", - "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", - "dev": true, - "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^16.1.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^2.0.3", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^9.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/make-fetch-happen/node_modules/@npmcli/fs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", - "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", - "dev": true, - "dependencies": { - "@gar/promisify": "^1.1.3", - "semver": "^7.3.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/make-fetch-happen/node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/make-fetch-happen/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/make-fetch-happen/node_modules/cacache": { - "version": "16.1.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", - "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", - "dev": true, - "dependencies": { - "@npmcli/fs": "^2.1.0", - "@npmcli/move-file": "^2.0.0", - "chownr": "^2.0.0", - "fs-minipass": "^2.1.0", - "glob": "^8.0.1", - "infer-owner": "^1.0.4", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "mkdirp": "^1.0.4", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^9.0.0", - "tar": "^6.1.11", - "unique-filename": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/make-fetch-happen/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/make-fetch-happen/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/make-fetch-happen/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/make-fetch-happen/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/make-fetch-happen/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/make-fetch-happen/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" + "yallist": "^3.0.2" } }, - "node_modules/make-fetch-happen/node_modules/ssri": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", - "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "node_modules/magic-string": { + "version": "0.30.8", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", + "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", "dev": true, "dependencies": { - "minipass": "^3.1.1" + "@jridgewell/sourcemap-codec": "^1.4.15" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=12" } }, - "node_modules/make-fetch-happen/node_modules/unique-filename": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", - "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "dependencies": { - "unique-slug": "^3.0.0" + "semver": "^7.5.3" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-fetch-happen/node_modules/unique-slug": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", - "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/make-fetch-happen": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.0.tgz", + "integrity": "sha512-7ThobcL8brtGo9CavByQrQi+23aIfgYU++wg4B87AIS8Rb2ZBt/MEaDqzA00Xwv/jUjAjYkLHjVolYuTLKda2A==", "dev": true, "dependencies": { - "imurmurhash": "^0.1.4" + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", + "http-cache-semantics": "^4.1.1", + "is-lambda": "^1.0.1", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "ssri": "^10.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/make-fetch-happen/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -12915,12 +13260,13 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.7.6", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz", - "integrity": "sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.1.tgz", + "integrity": "sha512-/1HDlyFRxWIZPI1ZpgqlZ8jMw/1Dp/dl3P0L1jtZ+zVcHqwPhGwaJwKL00WVgfnBy6PWCde9W65or7IIETImuA==", "dev": true, "dependencies": { - "schema-utils": "^4.0.0" + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" }, "engines": { "node": ">= 12.13.0" @@ -12961,79 +13307,43 @@ } }, "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", "dev": true, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-collect/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", "dev": true, "dependencies": { - "yallist": "^4.0.0" + "minipass": "^7.0.3" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/minipass-collect/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/minipass-fetch": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", - "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz", + "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==", "dev": true, "dependencies": { - "minipass": "^3.1.6", + "minipass": "^7.0.3", "minipass-sized": "^1.0.3", "minizlib": "^2.1.2" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" }, "optionalDependencies": { "encoding": "^0.1.13" } }, - "node_modules/minipass-fetch/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-fetch/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/minipass-flush": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", @@ -13205,9 +13515,9 @@ } }, "node_modules/mrmime": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", - "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", "dev": true, "engines": { "node": ">=10" @@ -13233,15 +13543,18 @@ } }, "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true, "funding": [ { @@ -13262,12 +13575,6 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, "node_modules/needle": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/needle/-/needle-3.2.0.tgz", @@ -13325,9 +13632,9 @@ "dev": true }, "node_modules/ngx-mask": { - "version": "16.3.9", - "resolved": "https://registry.npmjs.org/ngx-mask/-/ngx-mask-16.3.9.tgz", - "integrity": "sha512-cptsvlI4OLI8Tpj23ZgSQDKz5jksyWGzAuEEn5pd58cq2oFGeeHZS2i1SQQi8kp+a+Dh/2RvDsfFmDWmI5Ln9w==", + "version": "17.0.7", + "resolved": "https://registry.npmjs.org/ngx-mask/-/ngx-mask-17.0.7.tgz", + "integrity": "sha512-pmiCkAIDi5HqWwicHsOThPsJALF3fdMR21aoDJd/boJoEo1HoS34NJNUkqJ11qB2ZBVu5PX8R9KB617OCMkXXQ==", "dependencies": { "tslib": "^2.3.0" }, @@ -13369,34 +13676,33 @@ } }, "node_modules/node-gyp": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz", - "integrity": "sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.1.0.tgz", + "integrity": "sha512-B4J5M1cABxPc5PwfjhbV5hoy2DP9p8lFXASnEN6hugXOa61416tnTZ29x9sSwAd0o99XNIcpvDDy1swAExsVKA==", "dev": true, "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", - "glob": "^7.1.4", + "glob": "^10.3.10", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^10.0.3", - "nopt": "^6.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", + "make-fetch-happen": "^13.0.0", + "nopt": "^7.0.0", + "proc-log": "^3.0.0", "semver": "^7.3.5", "tar": "^6.1.2", - "which": "^2.0.2" + "which": "^4.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": "^12.13 || ^14.13 || >=16" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/node-gyp-build": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.1.tgz", - "integrity": "sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", + "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==", "dev": true, "optional": true, "bin": { @@ -13405,6 +13711,76 @@ "node-gyp-build-test": "build-test.js" } }, + "node_modules/node-gyp/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/node-gyp/node_modules/glob": { + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.6", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/node-gyp/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -13412,39 +13788,39 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, "node_modules/nopt": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", - "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz", + "integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==", "dev": true, "dependencies": { - "abbrev": "^1.0.0" + "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/normalize-package-data": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", - "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.0.tgz", + "integrity": "sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg==", "dev": true, "dependencies": { - "hosted-git-info": "^6.0.0", + "hosted-git-info": "^7.0.0", "is-core-module": "^2.8.1", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/normalize-path": { @@ -13483,163 +13859,80 @@ "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", "dev": true, "dependencies": { - "semver": "^7.1.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm-normalize-package-bin": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", - "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm-package-arg": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", - "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^6.0.0", - "proc-log": "^3.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^5.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm-packlist": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-7.0.4.tgz", - "integrity": "sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==", - "dev": true, - "dependencies": { - "ignore-walk": "^6.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm-pick-manifest": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.1.tgz", - "integrity": "sha512-mRtvlBjTsJvfCCdmPtiu2bdlx8d/KXtF7yNXNWe7G0Z36qWA9Ny5zXsI2PfBZEv7SXgoxTmNaTzGSbbzDZChoA==", - "dev": true, - "dependencies": { - "npm-install-checks": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0", - "npm-package-arg": "^10.0.0", - "semver": "^7.3.5" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm-registry-fetch": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-14.0.5.tgz", - "integrity": "sha512-kIDMIo4aBm6xg7jOttupWZamsZRkAqMqwqqbVXnUqstY5+tapvv6bkH/qMR76jdgV+YljEUCyWx3hRYMrJiAgA==", - "dev": true, - "dependencies": { - "make-fetch-happen": "^11.0.0", - "minipass": "^5.0.0", - "minipass-fetch": "^3.0.0", - "minipass-json-stream": "^1.0.1", - "minizlib": "^2.1.2", - "npm-package-arg": "^10.0.0", - "proc-log": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm-registry-fetch/node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/npm-registry-fetch/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" + "semver": "^7.1.1" }, "engines": { - "node": ">= 6" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm-registry-fetch/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", "dev": true, "engines": { - "node": ">=12" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm-registry-fetch/node_modules/make-fetch-happen": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", - "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", + "node_modules/npm-package-arg": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.1.tgz", + "integrity": "sha512-M7s1BD4NxdAvBKUPqqRW957Xwcl/4Zvo8Aj+ANrzvIPzGJZElrH7Z//rSaec2ORcND6FHHLnZeY8qgTpXDMFQQ==", "dev": true, "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^17.0.0", - "http-cache-semantics": "^4.1.1", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^5.0.0", - "minipass-fetch": "^3.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^10.0.0" + "hosted-git-info": "^7.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/npm-registry-fetch/node_modules/minipass-fetch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz", - "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==", + "node_modules/npm-packlist": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", + "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", "dev": true, "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" + "ignore-walk": "^6.0.4" }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.0.0.tgz", + "integrity": "sha512-VfvRSs/b6n9ol4Qb+bDwNGUXutpy76x6MARw/XssevE0TnctIKcmklJZM5Z7nqs5z5aW+0S63pgCNbpkUNNXBg==", + "dev": true, + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^11.0.0", + "semver": "^7.3.5" }, - "optionalDependencies": { - "encoding": "^0.1.13" + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/npm-registry-fetch/node_modules/minipass-fetch/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "node_modules/npm-registry-fetch": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-16.2.0.tgz", + "integrity": "sha512-zVH+G0q1O2hqgQBUvQ2LWp6ujr6VJAeDnmWxqiMlCguvLexEzBnuQIwC70r04vcvCMAcYEIpA/rO9YyVi+fmJQ==", "dev": true, + "dependencies": { + "@npmcli/redact": "^1.1.0", + "make-fetch-happen": "^13.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^11.0.0", + "proc-log": "^3.0.0" + }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm-run-path": { @@ -13654,21 +13947,6 @@ "node": ">=8" } }, - "node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "dev": true, - "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -13696,15 +13974,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object-path": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.8.tgz", - "integrity": "sha512-YJjNZrlXJFM42wTBn6zgOJVar9KFJvzx6sTWDte8sWZF//cnjl0BxHNpfZx+ZffXX63A9q0b1zsFiBX4g4X5KA==", - "dev": true, - "engines": { - "node": ">= 10.12.0" - } - }, "node_modules/obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -13981,27 +14250,27 @@ } }, "node_modules/pacote": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-15.2.0.tgz", - "integrity": "sha512-rJVZeIwHTUta23sIZgEIM62WYwbmGbThdbnkt81ravBplQv+HjyroqnLRNH2+sLJHcGZmLRmhPwACqhfTcOmnA==", + "version": "17.0.6", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-17.0.6.tgz", + "integrity": "sha512-cJKrW21VRE8vVTRskJo78c/RCvwJCn1f4qgfxL4w77SOWrTCRcmfkYHlHtS0gqpgjv3zhXflRtgsrUCX5xwNnQ==", "dev": true, "dependencies": { - "@npmcli/git": "^4.0.0", + "@npmcli/git": "^5.0.0", "@npmcli/installed-package-contents": "^2.0.1", - "@npmcli/promise-spawn": "^6.0.1", - "@npmcli/run-script": "^6.0.0", - "cacache": "^17.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "@npmcli/run-script": "^7.0.0", + "cacache": "^18.0.0", "fs-minipass": "^3.0.0", - "minipass": "^5.0.0", - "npm-package-arg": "^10.0.0", - "npm-packlist": "^7.0.0", - "npm-pick-manifest": "^8.0.0", - "npm-registry-fetch": "^14.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^11.0.0", + "npm-packlist": "^8.0.0", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^16.0.0", "proc-log": "^3.0.0", "promise-retry": "^2.0.1", - "read-package-json": "^6.0.0", + "read-package-json": "^7.0.0", "read-package-json-fast": "^3.0.0", - "sigstore": "^1.3.0", + "sigstore": "^2.2.0", "ssri": "^10.0.0", "tar": "^6.1.11" }, @@ -14009,15 +14278,9 @@ "pacote": "lib/bin.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -14138,12 +14401,12 @@ "dev": true }, "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", "dev": true, "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", + "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { @@ -14154,9 +14417,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", - "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", "dev": true, "engines": { "node": "14 || >=16.14" @@ -14215,15 +14478,10 @@ } }, "node_modules/piscina": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.0.0.tgz", - "integrity": "sha512-641nAmJS4k4iqpNUqfggqUBUMmlw0ZoM5VZKdQkV2e970Inn3Tk9kroCc1wpsYLD07vCwpys5iY0d3xI/9WkTg==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.4.0.tgz", + "integrity": "sha512-+AQduEJefrOApE4bV7KRmp3N2JnnyErlVqq4P/jmko4FPz9Z877BCccl/iB3FdrWSUkvbGV9Kan/KllJgat3Vg==", "dev": true, - "dependencies": { - "eventemitter-asyncresource": "^1.0.0", - "hdr-histogram-js": "^2.0.1", - "hdr-histogram-percentiles-obj": "^3.0.0" - }, "optionalDependencies": { "nice-napi": "^1.0.2" } @@ -14326,9 +14584,9 @@ } }, "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", "dev": true, "funding": [ { @@ -14345,7 +14603,7 @@ } ], "dependencies": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -14354,90 +14612,46 @@ } }, "node_modules/postcss-loader": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.3.tgz", - "integrity": "sha512-YgO/yhtevGO/vJePCQmTxiaEwER94LABZN0ZMT4A0vsak9TpO+RvKRs7EmJ8peIlB9xfXCsS7M8LjqncsUZ5HA==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", + "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", "dev": true, "dependencies": { - "cosmiconfig": "^8.2.0", - "jiti": "^1.18.2", - "semver": "^7.3.8" + "cosmiconfig": "^9.0.0", + "jiti": "^1.20.0", + "semver": "^7.5.4" }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { + "@rspack/core": "0.x || 1.x", "postcss": "^7.0.0 || ^8.0.1", "webpack": "^5.0.0" - } - }, - "node_modules/postcss-loader/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/postcss-loader/node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", - "dev": true, - "dependencies": { - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" }, "peerDependenciesMeta": { - "typescript": { + "@rspack/core": { + "optional": true + }, + "webpack": { "optional": true } } }, - "node_modules/postcss-loader/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/postcss-loader/node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", - "dev": true, - "optional": true, - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } + "node_modules/postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", + "dev": true }, "node_modules/postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", "dev": true, "engines": { "node": "^10 || ^12 || >= 14" @@ -14447,9 +14661,9 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", - "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", "dev": true, "dependencies": { "icss-utils": "^5.0.0", @@ -14464,9 +14678,9 @@ } }, "node_modules/postcss-modules-scope": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", - "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.4" @@ -14494,9 +14708,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", - "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", + "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -14522,32 +14736,20 @@ } }, "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -14741,9 +14943,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dev": true, "dependencies": { "bytes": "3.1.2", @@ -14771,18 +14973,18 @@ "dev": true }, "node_modules/read-package-json": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.4.tgz", - "integrity": "sha512-AEtWXYfopBj2z5N5PbkAOeNHRPUg5q+Nen7QLxV8M2zJq1ym6/lCz3fYNTCXe19puu2d06jfHhrP7v/S2PtMMw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-7.0.0.tgz", + "integrity": "sha512-uL4Z10OKV4p6vbdvIXB+OzhInYtIozl/VxUBPgNkBuUi2DeRonnuspmaVAMcrkmfjKGNmRndyQAbE7/AmzGwFg==", "dev": true, "dependencies": { "glob": "^10.2.2", "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^5.0.0", + "normalize-package-data": "^6.0.0", "npm-normalize-package-bin": "^3.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/read-package-json-fast": { @@ -14799,9 +15001,9 @@ } }, "node_modules/read-package-json-fast/node_modules/json-parse-even-better-errors": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", - "integrity": "sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz", + "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg==", "dev": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -14817,16 +15019,16 @@ } }, "node_modules/read-package-json/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", + "jackspeak": "^2.3.6", "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" }, "bin": { "glob": "dist/esm/bin.mjs" @@ -14839,18 +15041,18 @@ } }, "node_modules/read-package-json/node_modules/json-parse-even-better-errors": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", - "integrity": "sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz", + "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg==", "dev": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/read-package-json/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -14889,9 +15091,9 @@ } }, "node_modules/reflect-metadata": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", "dev": true }, "node_modules/regenerate": { @@ -14913,9 +15115,9 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "dev": true }, "node_modules/regenerator-transform": { @@ -14996,12 +15198,12 @@ "dev": true }, "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, "dependencies": { - "is-core-module": "^2.11.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -15129,25 +15331,43 @@ } }, "node_modules/rollup": { - "version": "3.29.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", - "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.0.tgz", + "integrity": "sha512-Qe7w62TyawbDzB4yt32R0+AbIo6m1/sqO7UPzFS8Z/ksL5mrfhA0v4CavfdmFav3D+ub4QeAgsGEe84DoWe/nQ==", "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, "bin": { "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=14.18.0", + "node": ">=18.0.0", "npm": ">=8.0.0" }, "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.14.0", + "@rollup/rollup-android-arm64": "4.14.0", + "@rollup/rollup-darwin-arm64": "4.14.0", + "@rollup/rollup-darwin-x64": "4.14.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.14.0", + "@rollup/rollup-linux-arm64-gnu": "4.14.0", + "@rollup/rollup-linux-arm64-musl": "4.14.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.14.0", + "@rollup/rollup-linux-riscv64-gnu": "4.14.0", + "@rollup/rollup-linux-s390x-gnu": "4.14.0", + "@rollup/rollup-linux-x64-gnu": "4.14.0", + "@rollup/rollup-linux-x64-musl": "4.14.0", + "@rollup/rollup-win32-arm64-msvc": "4.14.0", + "@rollup/rollup-win32-ia32-msvc": "4.14.0", + "@rollup/rollup-win32-x64-msvc": "4.14.0", "fsevents": "~2.3.2" } }, "node_modules/run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", "dev": true, "engines": { "node": ">=0.12.0" @@ -15216,9 +15436,9 @@ "integrity": "sha512-LRneZZRXNgjzwG4bDQdOTSbze3fHm1EAKN/8bePxnlEZiBmkYEDggaHbuvHI9/hoqHbGfsEA7tWS9GhYHZBBsw==" }, "node_modules/sass": { - "version": "1.64.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.64.1.tgz", - "integrity": "sha512-16rRACSOFEE8VN7SCgBu1MpYCyN7urj9At898tyzdXFhC+a+yOX5dXwAR7L8/IdPJ1NB8OYoXmD55DM30B2kEQ==", + "version": "1.71.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz", + "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -15233,29 +15453,29 @@ } }, "node_modules/sass-loader": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.2.tgz", - "integrity": "sha512-CQbKl57kdEv+KDLquhC+gE3pXt74LEAzm+tzywcA0/aHZuub8wTErbjAoNI57rPUWRYRNC5WUnNl8eGJNbDdwg==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.1.1.tgz", + "integrity": "sha512-QX8AasDg75monlybel38BZ49JP5Z+uSKfKwF2rO7S74BywaRmGQMUBw9dtkS+ekyM/QnP+NOrRYq8ABMZ9G8jw==", "dev": true, "dependencies": { "neo-async": "^2.6.2" }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "fibers": ">= 3.1.0", + "@rspack/core": "0.x || 1.x", "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", "sass": "^1.3.0", "sass-embedded": "*", "webpack": "^5.0.0" }, "peerDependenciesMeta": { - "fibers": { + "@rspack/core": { "optional": true }, "node-sass": { @@ -15266,6 +15486,9 @@ }, "sass-embedded": { "optional": true + }, + "webpack": { + "optional": true } } }, @@ -15276,18 +15499,6 @@ "dev": true, "optional": true }, - "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/schema-utils": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", @@ -15506,22 +15717,18 @@ "node": ">= 0.8.0" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dev": true, "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -15568,134 +15775,52 @@ }, "node_modules/shell-quote": { "version": "1.8.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", - "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/sigstore": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-1.9.0.tgz", - "integrity": "sha512-0Zjz0oe37d08VeOtBIuB6cRriqXse2e8w+7yIy2XSXjshRKxbc2KkhXjL229jXSxEm7UbcjS76wcJDGQddVI9A==", - "dev": true, - "dependencies": { - "@sigstore/bundle": "^1.1.0", - "@sigstore/protobuf-specs": "^0.2.0", - "@sigstore/sign": "^1.0.0", - "@sigstore/tuf": "^1.0.3", - "make-fetch-happen": "^11.0.1" - }, - "bin": { - "sigstore": "bin/sigstore.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/sigstore/node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/sigstore/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/sigstore/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/sigstore/node_modules/make-fetch-happen": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", - "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", - "dev": true, - "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^17.0.0", - "http-cache-semantics": "^4.1.1", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^5.0.0", - "minipass-fetch": "^3.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^10.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/sigstore/node_modules/minipass-fetch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz", - "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==", + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dev": true, "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">= 0.4" }, - "optionalDependencies": { - "encoding": "^0.1.13" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/sigstore/node_modules/minipass-fetch/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sigstore": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.2.2.tgz", + "integrity": "sha512-2A3WvXkQurhuMgORgT60r6pOWiCOO5LlEqY2ADxGBDGVYLSo5HN0uLtb68YpVpuL/Vi8mLTe7+0Dx2Fq8lLqEg==", "dev": true, + "dependencies": { + "@sigstore/bundle": "^2.2.0", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.0", + "@sigstore/sign": "^2.2.3", + "@sigstore/tuf": "^2.3.1", + "@sigstore/verify": "^1.1.0" + }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/sisteransi": { @@ -15735,31 +15860,43 @@ } }, "node_modules/socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.1.tgz", + "integrity": "sha512-B6w7tkwNid7ToxjZ08rQMT8M9BJAf8DKx8Ft4NivzH0zBUfd6jldGcisJn/RLgxcX3FPNDdNQCUEMMT79b+oCQ==", "dev": true, "dependencies": { - "ip": "^2.0.0", + "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" }, "engines": { - "node": ">= 10.13.0", + "node": ">= 10.0.0", "npm": ">= 3.0.0" } }, "node_modules/socks-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", - "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz", + "integrity": "sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==", "dev": true, "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.7.1" }, "engines": { - "node": ">= 10" + "node": ">= 14" + } + }, + "node_modules/socks-proxy-agent/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" } }, "node_modules/source-map": { @@ -15781,17 +15918,16 @@ } }, "node_modules/source-map-loader": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.1.tgz", - "integrity": "sha512-oqXpzDIByKONVY8g1NUPOTQhe0UTU5bWUl32GSkqK2LjJj0HmwTMVKxcUip0RgAYhY1mqgOxjbQM48a0mmeNfA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz", + "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", "dev": true, "dependencies": { - "abab": "^2.0.6", "iconv-lite": "^0.6.3", "source-map-js": "^1.0.2" }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", @@ -15841,9 +15977,9 @@ } }, "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", "dev": true }, "node_modules/spdx-expression-parse": { @@ -15857,9 +15993,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", - "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==", + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", + "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", "dev": true }, "node_modules/spdy": { @@ -15910,15 +16046,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/ssri/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -16104,9 +16231,9 @@ } }, "node_modules/tar": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", - "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "dev": true, "dependencies": { "chownr": "^2.0.0", @@ -16144,6 +16271,15 @@ "node": ">=8" } }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/tar/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -16151,9 +16287,9 @@ "dev": true }, "node_modules/terser": { - "version": "5.19.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz", - "integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==", + "version": "5.29.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.29.1.tgz", + "integrity": "sha512-lZQ/fyaIGxsbGxApKmoPTODIzELy3++mXhS5hOqaAWZjQtpq/hFHAc+rm29NND1rYRxRWKcjuARNwULNXa5RtQ==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -16169,16 +16305,16 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", - "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", + "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", "serialize-javascript": "^6.0.1", - "terser": "^5.16.8" + "terser": "^5.26.0" }, "engines": { "node": ">= 10.13.0" @@ -16309,12 +16445,6 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, "node_modules/thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", @@ -16384,18 +16514,6 @@ "node": ">=6" } }, - "node_modules/tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -16405,6 +16523,18 @@ "tree-kill": "cli.js" } }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/ts-jest": { "version": "29.1.1", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", @@ -16448,6 +16578,49 @@ } } }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/tsconfig-paths": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", @@ -16476,123 +16649,18 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/tuf-js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-1.1.7.tgz", - "integrity": "sha512-i3P9Kgw3ytjELUfpuKVDNBJvk4u5bXL6gskv572mcevPbSKCV3zt3djhmlEQ65yERjIbOSncy7U4cQJaB1CBCg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.0.tgz", + "integrity": "sha512-ZSDngmP1z6zw+FIkIBjvOp/II/mIub/O7Pp12j1WNsiCpg5R5wAc//i555bBQsE44O94btLt0xM/Zr2LQjwdCg==", "dev": true, "dependencies": { - "@tufjs/models": "1.0.4", + "@tufjs/models": "2.0.0", "debug": "^4.3.4", - "make-fetch-happen": "^11.1.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/tuf-js/node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/tuf-js/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/tuf-js/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/tuf-js/node_modules/make-fetch-happen": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", - "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", - "dev": true, - "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^17.0.0", - "http-cache-semantics": "^4.1.1", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^5.0.0", - "minipass-fetch": "^3.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^10.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/tuf-js/node_modules/minipass-fetch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz", - "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==", - "dev": true, - "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "make-fetch-happen": "^13.0.0" }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "node_modules/tuf-js/node_modules/minipass-fetch/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", - "dev": true, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/type-check": { @@ -16648,16 +16716,25 @@ "dev": true }, "node_modules/typescript": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", - "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", + "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.7.1.tgz", + "integrity": "sha512-+Wtb9bAQw6HYWzCnxrPTMVEV3Q1QjYanI0E4q02ehReMuquQdLTEFEYbfs7hcImVYKcQkWSwT6buEmSVIiDDtQ==", + "dev": true, + "engines": { + "node": ">=18.0" } }, "node_modules/undici-types": { @@ -16821,6 +16898,12 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "node_modules/v8-to-istanbul": { "version": "9.1.3", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz", @@ -16873,29 +16956,29 @@ } }, "node_modules/vite": { - "version": "4.4.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.7.tgz", - "integrity": "sha512-6pYf9QJ1mHylfVh39HpuSfMPojPSKVxZvnclX1K1FyZ1PXDOcLBibdq5t1qxJSnL63ca8Wf4zts6mD8u8oc9Fw==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.5.tgz", + "integrity": "sha512-BdN1xh0Of/oQafhU+FvopafUp6WaYenLU/NFoL5WyJL++GxkNfieKzBhM24H3HVsPQrlAqB7iJYTHabzaRed5Q==", "dev": true, "dependencies": { - "esbuild": "^0.18.10", - "postcss": "^8.4.26", - "rollup": "^3.25.2" + "esbuild": "^0.19.3", + "postcss": "^8.4.35", + "rollup": "^4.2.0" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^18.0.0 || >=20.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" }, "optionalDependencies": { - "fsevents": "~2.3.2" + "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": ">= 14", + "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", @@ -16927,28 +17010,6 @@ } } }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", - "dev": true, - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, - "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "dependencies": { - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -16989,29 +17050,20 @@ "defaults": "^1.0.3" } }, - "node_modules/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true, - "engines": { - "node": ">=10.4" - } - }, "node_modules/webpack": { - "version": "5.88.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", - "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", + "version": "5.90.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.3.tgz", + "integrity": "sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", + "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.11.5", "@webassemblyjs/wasm-edit": "^1.11.5", "@webassemblyjs/wasm-parser": "^1.11.5", "acorn": "^8.7.1", "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", + "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.15.0", "es-module-lexer": "^1.2.1", @@ -17025,7 +17077,7 @@ "neo-async": "^2.6.2", "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", + "terser-webpack-plugin": "^5.3.10", "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, @@ -17046,9 +17098,9 @@ } }, "node_modules/webpack-dev-middleware": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-6.1.1.tgz", - "integrity": "sha512-y51HrHaFeeWir0YO4f0g+9GwZawuigzcAdRNon6jErXy/SqV/+O6eaVAzDqE6t3e3NpGeR5CS+cCDaTC+V3yEQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-6.1.2.tgz", + "integrity": "sha512-Wu+EHmX326YPYUpQLKmKbTyZZJIB8/n6R09pTmB03kJmnMsVPTo9COzHZFr01txwaCAuZvfBJE4ZCHRcKs5JaQ==", "dev": true, "dependencies": { "colorette": "^2.0.10", @@ -17133,9 +17185,9 @@ } }, "node_modules/webpack-dev-server/node_modules/webpack-dev-middleware": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", - "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", "dev": true, "dependencies": { "colorette": "^2.0.10", @@ -17155,34 +17207,14 @@ "webpack": "^4.0.0 || ^5.0.0" } }, - "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/webpack-merge": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.9.0.tgz", - "integrity": "sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", "dev": true, "dependencies": { "clone-deep": "^4.0.1", + "flat": "^5.0.2", "wildcard": "^2.0.0" }, "engines": { @@ -17291,35 +17323,6 @@ "node": ">=0.8.0" } }, - "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "dependencies": { - "iconv-lite": "0.4.24" - } - }, - "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dev": true, - "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -17335,15 +17338,6 @@ "node": ">= 8" } }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, "node_modules/wildcard": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", @@ -17471,16 +17465,16 @@ } }, "node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "dev": true, "engines": { - "node": ">=8.3.0" + "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -17491,12 +17485,6 @@ } } }, - "node_modules/xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, "node_modules/xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", @@ -17545,6 +17533,15 @@ "node": ">=12" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -17558,9 +17555,9 @@ } }, "node_modules/zone.js": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.13.3.tgz", - "integrity": "sha512-MKPbmZie6fASC/ps4dkmIhaT5eonHkEt6eAy80K42tAm0G2W+AahLJjbfi6X9NPdciOE9GRFTTM8u2IiF6O3ww==", + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.4.tgz", + "integrity": "sha512-NtTUvIlNELez7Q1DzKVIFZBzNb646boQMgpATo9z3Ftuu/gWvzxCW7jdjcUDoRGxRikrhVHB/zLXh1hxeJawvw==", "dependencies": { "tslib": "^2.3.0" } diff --git a/portal-frontend/package.json b/portal-frontend/package.json index e1add12804..b342a7c751 100644 --- a/portal-frontend/package.json +++ b/portal-frontend/package.json @@ -13,37 +13,37 @@ }, "private": true, "dependencies": { - "@angular/animations": "^16.2.11", - "@angular/common": "^16.2.11", - "@angular/compiler": "^16.2.11", - "@angular/core": "^16.2.11", - "@angular/forms": "^16.2.11", - "@angular/material": "^16.2.11", - "@angular/material-moment-adapter": "^16.2.11", - "@angular/platform-browser": "^16.2.11", - "@angular/platform-browser-dynamic": "^16.2.11", - "@angular/router": "^16.2.11", + "@angular/animations": "^17.3.3", + "@angular/common": "^17.3.3", + "@angular/compiler": "^17.3.3", + "@angular/core": "^17.3.3", + "@angular/forms": "^17.3.3", + "@angular/material": "^17.3.3", + "@angular/material-moment-adapter": "^17.3.3", + "@angular/platform-browser": "^17.3.3", + "@angular/platform-browser-dynamic": "^17.3.3", + "@angular/router": "^17.3.3", "@bcgov/bc-sans": "^2.1.0", - "jwt-decode": "^3.1.2", - "ngx-mask": "^16.3.9", + "jwt-decode": "^4.0.0", + "ngx-mask": "^17.0.7", "rxjs": "~7.8.1", "source-map-support": "^0.5.21", "tslib": "^2.6.2", - "zone.js": "~0.13.3" + "zone.js": "~0.14.4" }, "devDependencies": { - "@angular-builders/jest": "^16.0.1", - "@angular-devkit/build-angular": "^16.2.9", - "@angular/cli": "~16.2.9", - "@angular/compiler-cli": "^16.2.11", + "@angular-builders/jest": "^17.0.3", + "@angular-devkit/build-angular": "^17.3.3", + "@angular/cli": "~17.3.3", + "@angular/compiler-cli": "^17.3.3", "@golevelup/ts-jest": "^0.4.0", - "@types/jest": "^29.5.7", - "@typescript-eslint/eslint-plugin": "5.62.0", - "@typescript-eslint/parser": "5.62.0", - "eslint": "^8.52.0", + "@types/jest": "^29.5.12", + "@typescript-eslint/eslint-plugin": "7.5.0", + "@typescript-eslint/parser": "7.5.0", + "eslint": "^8.57.0", "jest": "^29.7.0", - "jest-preset-angular": "^13.1.2", - "prettier": "^2.8.8", - "typescript": "4.9.4" + "jest-preset-angular": "^14.0.3", + "prettier": "^3.2.5", + "typescript": "5.4.3" } } diff --git a/portal-frontend/src/app/features/applications/application-details/application-details.component.html b/portal-frontend/src/app/features/applications/application-details/application-details.component.html index 542065b950..5e3ba672d1 100644 --- a/portal-frontend/src/app/features/applications/application-details/application-details.component.html +++ b/portal-frontend/src/app/features/applications/application-details/application-details.component.html @@ -1,10 +1,10 @@

Review & Submit

@@ -15,11 +15,11 @@

2. Other Owned Parcels

{{ - applicationSubmission.hasOtherParcelsInCommunity ? 'Yes' : 'No' - }} + applicationSubmission.hasOtherParcelsInCommunity ? 'Yes' : 'No' + }}
@@ -28,11 +28,11 @@

2. Other Owned Parcels

{{ applicationSubmission.otherParcelsDescription }} - +
- +
@@ -42,22 +42,22 @@

3. Primary Contact

Type
{{ primaryContact?.type?.label }} - +
First Name
{{ primaryContact?.firstName }} - +
Last Name
{{ primaryContact?.lastName }} - +
Organization (optional)Organization (optional) Ministry/Department Responsible Department @@ -65,30 +65,31 @@

3. Primary Contact

{{ primaryContact?.organizationName }}
Phone
{{ primaryContact?.phoneNumber ?? '' | mask : '(000) 000-0000' }} - + Invalid FormatInvalid Format +
Email
{{ primaryContact?.email }} - + Invalid Format
Authorization Letter(s)
{{ file.fileName }} @@ -99,7 +100,7 @@

3. Primary Contact

- +
@@ -109,13 +110,13 @@

4. Government

Local or First Nation Government
{{ localGovernment?.name }} - + This local/First Nation government is not set up with the ALC Portal to receive submissions. You can continue to fill out the form but you will be unable to submit. Please contact the ALC directly as soon as possible: -  ALC.Portal@gov.bc.ca / 236-468-3342 +  ALC.Portal@gov.bc.ca / 236-468-3342 - +
@@ -146,24 +147,24 @@

Land Use of Parcel(s) under Application

{{ applicationSubmission.parcelsAgricultureDescription }}
Describe all agricultural improvements made to the parcel(s).
{{ applicationSubmission.parcelsAgricultureImprovementDescription }}
Describe all other uses that currently take place on the parcel(s).
{{ applicationSubmission.parcelsNonAgricultureUseDescription }}
@@ -176,54 +177,54 @@

Land Use of Adjacent Parcels

North
{{ applicationSubmission.northLandUseType }} - +
{{ applicationSubmission.northLandUseTypeDescription }}
East
{{ applicationSubmission.eastLandUseType }} - +
{{ applicationSubmission.eastLandUseTypeDescription }}
South
{{ applicationSubmission.southLandUseType }} - +
{{ applicationSubmission.southLandUseTypeDescription }}
West
{{ applicationSubmission.westLandUseType }} - +
{{ applicationSubmission.westLandUseTypeDescription }}
- +
@@ -231,83 +232,83 @@

Land Use of Adjacent Parcels

6. Proposal

@@ -324,19 +325,19 @@

7. Optional Documents

{{ file.type?.label }} - +
{{ file.description }} - +
- +
- +
diff --git a/portal-frontend/src/app/features/applications/edit-submission/select-government/select-government.component.html b/portal-frontend/src/app/features/applications/edit-submission/select-government/select-government.component.html index 1869412bb1..cf6dd82d22 100644 --- a/portal-frontend/src/app/features/applications/edit-submission/select-government/select-government.component.html +++ b/portal-frontend/src/app/features/applications/edit-submission/select-government/select-government.component.html @@ -7,17 +7,17 @@

Government

- + - + {{ option.name }} @@ -40,9 +40,9 @@

Government

This local/First Nation government is not set up with the ALC Portal to receive submissions. You can continue to fill out the form but you will be unable to submit. Please contact the ALC directly as soon as possible:  ALC.Portal@gov.bc.ca / 236-468-3342 + href="mailto:ALC.Portal@gov.bc.ca" +>ALC.Portal@gov.bc.ca / 236-468-3342 You're logged in with a Business BCeID that is associated with the government selected above. You will have the @@ -51,16 +51,16 @@

Government

Please Note: If your Local or First Nation Government is not listed, please contact the ALC directly. - ALC.Portal@gov.bc.ca / 236-468-3342 + ALC.Portal@gov.bc.ca / 236-468-3342

- - + +
- -
diff --git a/portal-frontend/src/app/features/applications/edit-submission/select-government/select-government.component.spec.ts b/portal-frontend/src/app/features/applications/edit-submission/select-government/select-government.component.spec.ts index a2b9669ba9..7626dabbad 100644 --- a/portal-frontend/src/app/features/applications/edit-submission/select-government/select-government.component.spec.ts +++ b/portal-frontend/src/app/features/applications/edit-submission/select-government/select-government.component.spec.ts @@ -1,6 +1,6 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MatAutocomplete } from '@angular/material/autocomplete'; +import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { BehaviorSubject } from 'rxjs'; import { ApplicationSubmissionDetailedDto } from '../../../../services/application-submission/application-submission.dto'; @@ -20,7 +20,8 @@ describe('SelectGovernmentComponent', () => { mockAppService = createMock(); await TestBed.configureTestingModule({ - declarations: [SelectGovernmentComponent, MatAutocomplete], + imports: [MatAutocompleteModule], + declarations: [SelectGovernmentComponent], providers: [ { provide: CodeService, diff --git a/portal-frontend/src/app/features/create-submission-dialog/create-submission-dialog.component.html b/portal-frontend/src/app/features/create-submission-dialog/create-submission-dialog.component.html index e9af24188d..968634d68f 100644 --- a/portal-frontend/src/app/features/create-submission-dialog/create-submission-dialog.component.html +++ b/portal-frontend/src/app/features/create-submission-dialog/create-submission-dialog.component.html @@ -6,13 +6,13 @@

-
+
Select an option to learn more about the submission type.
-
+
{{ selectedSubmissionType.label }}
Read {{ readMoreClicked ? 'Less' : 'More' }}Read {{ readMoreClicked ? 'Less' : 'More' }}
@@ -47,53 +47,53 @@

Select an option to learn more about the application type.
- + {{ appType.portalLabel }}
-
+
{{ selectedAppType.portalLabel }}
- Read {{ readMoreClicked ? 'Less' : 'More' }}Read {{ readMoreClicked ? 'Less' : 'More' }}
Select an option to learn more about the notice of intent type.
- - + + {{ type.portalLabel }}
-
+
{{ selectedNoiType.portalLabel }}
- Read {{ readMoreClicked ? 'Less' : 'More' }}Read {{ readMoreClicked ? 'Less' : 'More' }}
@@ -104,16 +104,16 @@

Submitting a notification of Statutory Right of Way (SRW) only applies to a SRW described in s. 218 of the Land Title Act.s. 218 of the Land Title Act.
Before you create a notification of SRW:
-
+
  1. Permitted Uses: If you intend to use, construct works, or remove soil or place fill (including gravel) @@ -136,58 +136,58 @@

    your Land Title Survey Authority SRW package.

    If you have any questions, please contact the ALC.
    - Email: ALC.Portal@gov.bc.ca
    + Email: ALC.Portal@gov.bc.ca
    Phone: 236-468-3342 or 1-800-663-7867

- Read {{ readMoreClicked ? 'Less' : 'More' }}Read {{ readMoreClicked ? 'Less' : 'More' }}

-
-
- - +
-
- +
+
-
- - +
+ +
-
- +
+
-
- +
+ diff --git a/portal-frontend/src/app/features/notice-of-intents/edit-submission/select-government/select-government.component.html b/portal-frontend/src/app/features/notice-of-intents/edit-submission/select-government/select-government.component.html index c06c49dd51..79e0d49275 100644 --- a/portal-frontend/src/app/features/notice-of-intents/edit-submission/select-government/select-government.component.html +++ b/portal-frontend/src/app/features/notice-of-intents/edit-submission/select-government/select-government.component.html @@ -7,17 +7,17 @@

Government

- + - + {{ option.name }} @@ -40,22 +40,22 @@

Government

This local/First Nation government is not set up with the ALC Portal to receive submissions. You can continue to fill out the form but you will be unable to submit. Please contact the ALC directly as soon as possible:  ALC.Portal@gov.bc.ca / 236-468-3342 + href="mailto:ALC.Portal@gov.bc.ca" +>ALC.Portal@gov.bc.ca / 236-468-3342

Please Note: If your Local or First Nation Government is not listed, please contact the ALC directly. - ALC.Portal@gov.bc.ca / 236-468-3342 + ALC.Portal@gov.bc.ca / 236-468-3342

- - + +
- -
diff --git a/portal-frontend/src/app/features/notice-of-intents/edit-submission/select-government/select-government.component.spec.ts b/portal-frontend/src/app/features/notice-of-intents/edit-submission/select-government/select-government.component.spec.ts index fd9c1abe6d..fdd1a60bbb 100644 --- a/portal-frontend/src/app/features/notice-of-intents/edit-submission/select-government/select-government.component.spec.ts +++ b/portal-frontend/src/app/features/notice-of-intents/edit-submission/select-government/select-government.component.spec.ts @@ -1,6 +1,6 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MatAutocomplete } from '@angular/material/autocomplete'; +import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { BehaviorSubject } from 'rxjs'; import { CodeService } from '../../../../services/code/code.service'; @@ -20,7 +20,8 @@ describe('SelectGovernmentComponent', () => { mockAppService = createMock(); await TestBed.configureTestingModule({ - declarations: [SelectGovernmentComponent, MatAutocomplete], + imports: [MatAutocompleteModule], + declarations: [SelectGovernmentComponent], providers: [ { provide: CodeService, diff --git a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/notice-of-intent-details.component.html b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/notice-of-intent-details.component.html index 5fefa1418d..db20614a6d 100644 --- a/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/notice-of-intent-details.component.html +++ b/portal-frontend/src/app/features/notice-of-intents/notice-of-intent-details/notice-of-intent-details.component.html @@ -1,10 +1,10 @@

Review & Submit

@@ -13,22 +13,22 @@

2. Primary Contact

Type
{{ primaryContact?.type?.label }} - +
First Name
{{ primaryContact?.firstName }} - +
Last Name
{{ primaryContact?.lastName }} - +
Organization (optional)Organization (optional) Ministry/Department Responsible Department @@ -36,30 +36,31 @@

2. Primary Contact

{{ primaryContact?.organizationName }}
Phone
{{ primaryContact?.phoneNumber ?? '' | mask : '(000) 000-0000' }} - + Invalid FormatInvalid Format +
Email
{{ primaryContact?.email }} - + Invalid Format
Authorization Letter(s)
{{ file.fileName }} @@ -70,7 +71,7 @@

2. Primary Contact

- +
@@ -80,17 +81,17 @@

3. Government

Local or First Nation Government
{{ localGovernment?.name }} - + This local/First Nation government is not set up with the ALC Portal to receive submissions. You can continue to fill out the form but you will be unable to submit. Please contact the ALC directly as soon as possible: -  ALC.Portal@gov.bc.ca / 236-468-3342 +  ALC.Portal@gov.bc.ca / 236-468-3342
- +
@@ -103,20 +104,20 @@

Land Use of Parcel(s) under Notice of Intent

Describe all agriculture that currently takes place on the parcel(s).
{{ noiSubmission.parcelsAgricultureDescription }} - +
Describe all agricultural improvements made to the parcel(s).
{{ noiSubmission.parcelsAgricultureImprovementDescription }}
Describe all other uses that currently take place on the parcel(s).
{{ noiSubmission.parcelsNonAgricultureUseDescription }} - +

Land Use of Adjacent Parcels

@@ -128,42 +129,42 @@

Land Use of Adjacent Parcels

North
{{ noiSubmission.northLandUseType }} - +
{{ noiSubmission.northLandUseTypeDescription }} - +
East
{{ noiSubmission.eastLandUseType }} - +
{{ noiSubmission.eastLandUseTypeDescription }} - +
South
{{ noiSubmission.southLandUseType }} - +
{{ noiSubmission.southLandUseTypeDescription }} - +
West
{{ noiSubmission.westLandUseType }} - +
{{ noiSubmission.westLandUseTypeDescription }} - +
- +
@@ -171,37 +172,37 @@

Land Use of Adjacent Parcels

5. Proposal

6. Additional Proposal Information

@@ -219,19 +220,19 @@

7. Optional Documents

{{ file.type?.label }} - +
{{ file.description }} - +
- +
- +
diff --git a/portal-frontend/src/app/features/notifications/edit-submission/select-government/select-government.component.html b/portal-frontend/src/app/features/notifications/edit-submission/select-government/select-government.component.html index b4c99f5560..6b7436b6f9 100644 --- a/portal-frontend/src/app/features/notifications/edit-submission/select-government/select-government.component.html +++ b/portal-frontend/src/app/features/notifications/edit-submission/select-government/select-government.component.html @@ -7,17 +7,17 @@

Government

- + - + {{ option.name }} @@ -40,21 +40,21 @@

Government

This local/First Nation government is not set up with the ALC Portal to receive submissions. You can continue to fill out the form but you will be unable to submit. Please contact the ALC directly as soon as possible:  ALC.Portal@gov.bc.ca / 236-468-3342 + href="mailto:ALC.Portal@gov.bc.ca" +>ALC.Portal@gov.bc.ca / 236-468-3342

Please Note: If your Local or First Nation Government is not listed, please contact the ALC directly. - ALC.Portal@gov.bc.ca / 236-468-3342 + ALC.Portal@gov.bc.ca / 236-468-3342

- +
- -
diff --git a/portal-frontend/src/app/features/notifications/edit-submission/select-government/select-government.component.spec.ts b/portal-frontend/src/app/features/notifications/edit-submission/select-government/select-government.component.spec.ts index 087b3b5700..efd7805824 100644 --- a/portal-frontend/src/app/features/notifications/edit-submission/select-government/select-government.component.spec.ts +++ b/portal-frontend/src/app/features/notifications/edit-submission/select-government/select-government.component.spec.ts @@ -1,7 +1,7 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MatAutocomplete } from '@angular/material/autocomplete'; -import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { MatAutocompleteModule } from '@angular/material/autocomplete'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { BehaviorSubject } from 'rxjs'; import { CodeService } from '../../../../services/code/code.service'; import { NotificationSubmissionDetailedDto } from '../../../../services/notification-submission/notification-submission.dto'; @@ -20,7 +20,8 @@ describe('SelectGovernmentComponent', () => { mockNotificationSubmissionService = createMock(); await TestBed.configureTestingModule({ - declarations: [SelectGovernmentComponent, MatAutocomplete], + imports: [MatAutocompleteModule], + declarations: [SelectGovernmentComponent], providers: [ { provide: CodeService, diff --git a/portal-frontend/src/app/features/notifications/edit-submission/success/success.component.html b/portal-frontend/src/app/features/notifications/edit-submission/success/success.component.html index 52f7d97bba..329fadb098 100644 --- a/portal-frontend/src/app/features/notifications/edit-submission/success/success.component.html +++ b/portal-frontend/src/app/features/notifications/edit-submission/success/success.component.html @@ -8,9 +8,9 @@

Notification ID: {{ fileId }} Submitted!

The Primary Contact will receive an automated email shortly containing an attached PDF response which must be appended as an additional document to the LTSA SRW application package.

If the email is not received within an hour, please email your SRW ID to - ALC.Portal@gov.bc.ca request assistance. + ALC.Portal@gov.bc.ca request assistance.

- - + +
diff --git a/portal-frontend/src/app/features/notifications/notification-details/notification-details.component.html b/portal-frontend/src/app/features/notifications/notification-details/notification-details.component.html index 85bcff7e34..c61e82670b 100644 --- a/portal-frontend/src/app/features/notifications/notification-details/notification-details.component.html +++ b/portal-frontend/src/app/features/notifications/notification-details/notification-details.component.html @@ -2,8 +2,8 @@

Review & Submit

@@ -20,7 +20,7 @@

2. Identify Transferee(s)

{{ transferee.displayName }}
{{ transferee.organizationName }} - +
{{ transferee.phoneNumber | mask : '(000) 000-0000' }} @@ -29,12 +29,12 @@

2. Identify Transferee(s)

- +
@@ -44,42 +44,44 @@

3. Primary Contact

First Name
{{ notificationSubmission.contactFirstName }} - +
Last Name
{{ notificationSubmission.contactLastName }} - +
Organization (optional)
{{ notificationSubmission.contactOrganization }} - +
Phone
{{ notificationSubmission.contactPhone ?? '' | mask : '(000) 000-0000' }} - + Invalid FormatInvalid Format +
Email
{{ notificationSubmission.contactEmail }} Invalid FormatInvalid Format +
- +
@@ -89,27 +91,27 @@

4. Government

Local or First Nation Government
{{ localGovernment?.name }} - + This local/First Nation government is not set up with the ALC Portal to receive submissions. You can continue to fill out the form but you will be unable to submit. Please contact the ALC directly as soon as possible: -  ALC.Portal@gov.bc.ca / 236-468-3342 +  ALC.Portal@gov.bc.ca / 236-468-3342
- +

5. Purpose of SRW

@@ -126,19 +128,19 @@

6. Optional Attachments

{{ file.type?.label }} - +
{{ file.description }} - +
- +
- +
diff --git a/portal-frontend/src/app/services/authentication/authentication.service.ts b/portal-frontend/src/app/services/authentication/authentication.service.ts index 8b7c9dc393..368a16cd37 100644 --- a/portal-frontend/src/app/services/authentication/authentication.service.ts +++ b/portal-frontend/src/app/services/authentication/authentication.service.ts @@ -1,7 +1,7 @@ import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; -import jwtDecode, { JwtPayload } from 'jwt-decode'; +import { jwtDecode, JwtPayload } from 'jwt-decode'; import { BehaviorSubject, firstValueFrom } from 'rxjs'; import { environment } from '../../../environments/environment'; import { UserDto } from './authentication.dto'; @@ -18,17 +18,19 @@ export interface ICurrentUser { providedIn: 'root', }) export class AuthenticationService { - private token: string | undefined; - private refreshToken: string | undefined; - private expires: number | undefined; - private refreshExpires: number | undefined; - isInitialized = false; $currentTokenUser = new BehaviorSubject(undefined); $currentProfile = new BehaviorSubject(undefined); currentUser: ICurrentUser | undefined; + private token: string | undefined; + private refreshToken: string | undefined; + private expires: number | undefined; + private refreshExpires: number | undefined; - constructor(private http: HttpClient, private router: Router) {} + constructor( + private http: HttpClient, + private router: Router, + ) {} async setTokens(token: string, refreshToken: string) { this.token = token; @@ -46,7 +48,7 @@ export class AuthenticationService { this.$currentTokenUser.next(this.currentUser); console.debug( - `Token Successfully Refreshed, Expires at: ${new Date(this.expires)} Refresh at: ${new Date(this.refreshExpires)}` + `Token Successfully Refreshed, Expires at: ${new Date(this.expires)} Refresh at: ${new Date(this.refreshExpires)}`, ); this.loadUser(); @@ -90,6 +92,14 @@ export class AuthenticationService { } } + async logout() { + const logoutUrl = await this.getLogoutUrl(); + if (logoutUrl) { + this.clearTokens(); + window.location.href = logoutUrl.url; + } + } + private async loadTokenFromStorage(redirect = true) { const existingToken = localStorage.getItem(JWT_TOKEN_KEY); const refreshToken = localStorage.getItem(REFRESH_TOKEN_KEY); @@ -104,14 +114,6 @@ export class AuthenticationService { } } - async logout() { - const logoutUrl = await this.getLogoutUrl(); - if (logoutUrl) { - this.clearTokens(); - window.location.href = logoutUrl.url; - } - } - private async isTokenValid(token: string) { try { await firstValueFrom( @@ -120,7 +122,7 @@ export class AuthenticationService { headers: { Authorization: `Bearer ${token}`, }, - }) + }), ); return true; } catch (e) { @@ -136,7 +138,7 @@ export class AuthenticationService { this.http.get<{ refresh_token: string; token: string; - }>(`${environment.apiUrl}/authorize/refresh?r=${refreshToken}`) + }>(`${environment.apiUrl}/authorize/refresh?r=${refreshToken}`), ); } diff --git a/portal-frontend/src/styles.scss b/portal-frontend/src/styles.scss index ea762bae8c..a77b1f3a0f 100644 --- a/portal-frontend/src/styles.scss +++ b/portal-frontend/src/styles.scss @@ -109,3 +109,16 @@ a { .backdrop { background-color: rgba(255, 255, 255, 0.7); } + +.mat-primary { + &.mat-mdc-button-base { + --mat-fab-foreground-color: #fff; + --mdc-filled-button-label-text-color: #fff; + --mdc-protected-button-label-text-color: #fff; + } + + &.mat-mdc-fab, + &.mat-mdc-mini-fab { + --mat-icon-color: #fff; + } +} diff --git a/services/apps/alcs/src/main.controller.spec.ts b/services/apps/alcs/src/main.controller.spec.ts index 767763e691..38ebd042b6 100644 --- a/services/apps/alcs/src/main.controller.spec.ts +++ b/services/apps/alcs/src/main.controller.spec.ts @@ -1,13 +1,14 @@ import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; import { Test, TestingModule } from '@nestjs/testing'; +import { KEYCLOAK_MULTITENANT_SERVICE } from 'nest-keycloak-connect'; import { ClsService } from 'nestjs-cls'; -import { MainController } from './main.controller'; -import { MainService } from './main.service'; import { mockKeyCloakProviders } from '../test/mocks/mockTypes'; import { HealthCheckDbDto, HealthCheckDto, } from './healthcheck/healthcheck.dto'; +import { MainController } from './main.controller'; +import { MainService } from './main.service'; describe('AlcsController', () => { let alcsController: MainController; @@ -27,6 +28,10 @@ describe('AlcsController', () => { provide: ClsService, useValue: {}, }, + { + provide: KEYCLOAK_MULTITENANT_SERVICE, + useValue: {}, + }, ...mockKeyCloakProviders, ], }).compile(); From 42b90df0d1805d938920f0094ca22e73c285eefb Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Thu, 4 Apr 2024 14:08:13 -0700 Subject: [PATCH 101/153] Populate Notification and Inquiry AppType for Home Page * Remove hardcoded notification type --- .../src/app/features/home/assigned/assigned.component.ts | 3 +-- .../subtask/subtask-table/subtask-table.component.html | 6 ------ .../home/subtask/subtask-table/subtask-table.component.ts | 4 +--- .../application-type-pill.constants.ts | 8 -------- services/apps/alcs/src/alcs/home/home.controller.ts | 2 ++ services/apps/alcs/src/alcs/inquiry/inquiry.service.ts | 1 + .../alcs/src/alcs/notification/notification.service.ts | 1 + 7 files changed, 6 insertions(+), 19 deletions(-) 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 2545c29ecc..c956801892 100644 --- a/alcs-frontend/src/app/features/home/assigned/assigned.component.ts +++ b/alcs-frontend/src/app/features/home/assigned/assigned.component.ts @@ -11,7 +11,6 @@ import { NotificationDto } from '../../../services/notification/notification.dto import { PlanningReferralDto } from '../../../services/planning-review/planning-review.dto'; import { MODIFICATION_TYPE_LABEL, - NOTIFICATION_LABEL, RECON_TYPE_LABEL, RETROACTIVE_TYPE_LABEL, } from '../../../shared/application-type-pill/application-type-pill.constants'; @@ -214,7 +213,7 @@ export class AssignedComponent implements OnInit { card: a.card, date: a.dateSubmittedToAlc, highPriority: a.card!.highPriority, - labels: [NOTIFICATION_LABEL], + labels: [a.type], }; } diff --git a/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.html b/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.html index 768a2af105..e623f740c6 100644 --- a/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.html +++ b/alcs-frontend/src/app/features/home/subtask/subtask-table/subtask-table.component.html @@ -30,12 +30,6 @@ [type]="RECON_TYPE_LABEL" > - -

@@ -76,13 +76,13 @@

Additional Proposal Information

- - - @@ -209,7 +208,7 @@

Additional Proposal Information

Describe how the structure is necessary for residential use
# Type + {{ type }} @@ -91,20 +91,23 @@

Additional Proposal Information

warning -
This field is required
+
+ This field is required +
Total Floor Area + Additional Proposal Information separatorLimit="9999999999" min="0.01" placeholder="Type area" - [formControlName]="i + '-area'" - attr.aria-label="{{ i + '-area' }}" + [formControlName]="element.id + '-area'" /> m2
warning -
This field is required
+
+ This field is required +
Action - +