diff --git a/alcs-frontend/src/app/features/search/application-search-table/application-search-table.component.html b/alcs-frontend/src/app/features/search/application-search-table/application-search-table.component.html index 2ee164f4c6..c6e17a4846 100644 --- a/alcs-frontend/src/app/features/search/application-search-table/application-search-table.component.html +++ b/alcs-frontend/src/app/features/search/application-search-table/application-search-table.component.html @@ -46,13 +46,14 @@ - + Portal Status + diff --git a/alcs-frontend/src/app/features/search/application-search-table/application-search-table.component.ts b/alcs-frontend/src/app/features/search/application-search-table/application-search-table.component.ts index a45e8128e3..ae4c2cf1a7 100644 --- a/alcs-frontend/src/app/features/search/application-search-table/application-search-table.component.ts +++ b/alcs-frontend/src/app/features/search/application-search-table/application-search-table.component.ts @@ -19,7 +19,7 @@ interface SearchResult { referenceId: string; board?: string; class: string; - status?: ApplicationSubmissionStatusPill; + status?: ApplicationSubmissionStatusPill | null; } @Component({ @@ -42,7 +42,7 @@ export class ApplicationSearchTableComponent { @Output() tableChange = new EventEmitter(); - displayedColumns = ['fileId', 'dateSubmitted', 'ownerName', 'type', 'government', 'portalStatus']; + displayedColumns = ['fileId', 'dateSubmitted', 'ownerName', 'type', 'government', 'status']; dataSource: SearchResult[] = []; itemsPerPage = 20; total = 0; @@ -88,24 +88,22 @@ export class ApplicationSearchTableComponent { private mapApplications(applications: ApplicationSearchResultDto[]): SearchResult[] { return applications.map((e) => { const status = this.statuses.find((st) => st.code === e.status); - return { fileNumber: e.fileNumber, dateSubmitted: e.dateSubmitted, ownerName: e.ownerName, type: e.type, localGovernmentName: e.localGovernmentName, - portalStatus: e.portalStatus, referenceId: e.referenceId, board: e.boardCode, class: e.class, - status: { - backgroundColor: status?.portalBackgroundColor ?? defaultStatusBackgroundColour, - textColor: status?.portalColor ?? defaultStatusColour, - borderColor: status?.portalBackgroundColor, - label: status?.label, - shortLabel: status?.label, - }, + status: status ? { + backgroundColor: status.portalBackgroundColor ?? defaultStatusBackgroundColour, + textColor: status.portalColor ?? defaultStatusColour, + borderColor: status.portalBackgroundColor, + label: status.label, + shortLabel: status.label, + } : null, }; }); } diff --git a/alcs-frontend/src/app/features/search/notice-of-intent-search-table/notice-of-intent-search-table.component.html b/alcs-frontend/src/app/features/search/notice-of-intent-search-table/notice-of-intent-search-table.component.html index ef901b754b..38f446c755 100644 --- a/alcs-frontend/src/app/features/search/notice-of-intent-search-table/notice-of-intent-search-table.component.html +++ b/alcs-frontend/src/app/features/search/notice-of-intent-search-table/notice-of-intent-search-table.component.html @@ -46,13 +46,14 @@ - + Portal Status + 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 b32404ce25..b6cdd0a68b 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 @@ -19,7 +19,7 @@ interface SearchResult { referenceId: string; board?: string; class: string; - status?: ApplicationSubmissionStatusPill; + status?: ApplicationSubmissionStatusPill | null; } @Component({ @@ -41,7 +41,7 @@ export class NoticeOfIntentSearchTableComponent { @Output() tableChange = new EventEmitter(); - displayedColumns = ['fileId', 'dateSubmitted', 'ownerName', 'type', 'government', 'portalStatus']; + displayedColumns = ['fileId', 'dateSubmitted', 'ownerName', 'type', 'government', 'status']; dataSource: SearchResult[] = []; itemsPerPage = 20; @@ -98,13 +98,13 @@ export class NoticeOfIntentSearchTableComponent { referenceId: e.referenceId, board: e.boardCode, class: e.class, - status: { + status: status ? { backgroundColor: status!.alcsBackgroundColor, textColor: status!.alcsColor, borderColor: status!.alcsBackgroundColor, label: status!.label, shortLabel: status!.label, - }, + } : null, }; }); } diff --git a/alcs-frontend/src/app/features/search/notification-search-table/notification-search-table.component.html b/alcs-frontend/src/app/features/search/notification-search-table/notification-search-table.component.html index fae38aa644..885029dc2f 100644 --- a/alcs-frontend/src/app/features/search/notification-search-table/notification-search-table.component.html +++ b/alcs-frontend/src/app/features/search/notification-search-table/notification-search-table.component.html @@ -46,13 +46,14 @@ - + Portal Status + diff --git a/alcs-frontend/src/app/features/search/notification-search-table/notification-search-table.component.ts b/alcs-frontend/src/app/features/search/notification-search-table/notification-search-table.component.ts index 040428f3f2..8cece56bf5 100644 --- a/alcs-frontend/src/app/features/search/notification-search-table/notification-search-table.component.ts +++ b/alcs-frontend/src/app/features/search/notification-search-table/notification-search-table.component.ts @@ -18,7 +18,7 @@ interface SearchResult { referenceId: string; board?: string; class: string; - status?: ApplicationSubmissionStatusPill; + status?: ApplicationSubmissionStatusPill | null; } @Component({ @@ -41,7 +41,7 @@ export class NotificationSearchTableComponent { @Output() tableChange = new EventEmitter(); - displayedColumns = ['fileId', 'dateSubmitted', 'ownerName', 'type', 'government', 'portalStatus']; + displayedColumns = ['fileId', 'dateSubmitted', 'ownerName', 'type', 'government', 'status']; dataSource: SearchResult[] = []; itemsPerPage = 20; total = 0; @@ -97,13 +97,13 @@ export class NotificationSearchTableComponent { referenceId: e.referenceId, board: e.boardCode, class: e.class, - status: { + status: status ? { backgroundColor: status ? status!.alcsBackgroundColor : '', textColor: status ? status!.alcsColor : '', borderColor: status ? status!.alcsBackgroundColor : '', label: status ? status!.label : '', shortLabel: status ? status!.label : '', - }, + } : null, }; }); } diff --git a/alcs-frontend/src/app/features/search/search.component.html b/alcs-frontend/src/app/features/search/search.component.html index 051424f118..de78eb202a 100644 --- a/alcs-frontend/src/app/features/search/search.component.html +++ b/alcs-frontend/src/app/features/search/search.component.html @@ -331,7 +331,7 @@

Date Range

Search Results:

- + Applications: {{ applicationTotal }} (a.label > b.label ? 1 : -1)); } + + private async updateApplicationStatuses() { + const needsUpdate = this.applications.filter((a) => a.status === null).length > 0; + if (!needsUpdate) return; + const statusUpdates = await this.searchService.advancedSearchApplicationStatusFetch( + this.applications.map((a) => a.fileNumber) + ); + this.applications = this.applications.map((a) => { + const updatedStatus = statusUpdates ? statusUpdates.find((s) => s.fileNumber === a.fileNumber) : null; + return { + ...a, + status: updatedStatus ? updatedStatus.status : '', + } + }); + } + + private async updateNoiStatuses() { + const needsUpdate = this.noticeOfIntents.filter((a) => a.status === null).length > 0; + if (!needsUpdate) return; + const statusUpdates = await this.searchService.advancedSearchNoiStatusFetch( + this.noticeOfIntents.map((a) => a.fileNumber) + ); + this.noticeOfIntents = this.noticeOfIntents.map((a) => { + const updatedStatus = statusUpdates ? statusUpdates.find((s) => s.fileNumber === a.fileNumber) : null; + return { + ...a, + status: updatedStatus ? updatedStatus.status : '', + } + }); + } + + private async updateNotificationStatuses() { + const needsUpdate = this.notifications.filter((a) => a.status === null).length > 0; + if (!needsUpdate) return; + const statusUpdates = await this.searchService.advancedSearchNotificationStatusFetch( + this.notifications.map((a) => a.fileNumber) + ); + this.notifications = this.notifications.map((a) => { + const updatedStatus = statusUpdates ? statusUpdates.find((s) => s.fileNumber === a.fileNumber) : null; + return { + ...a, + status: updatedStatus ? updatedStatus.status : '', + } + }); + } } diff --git a/alcs-frontend/src/app/features/search/search.module.ts b/alcs-frontend/src/app/features/search/search.module.ts index 5484fb5684..ef3edde8d0 100644 --- a/alcs-frontend/src/app/features/search/search.module.ts +++ b/alcs-frontend/src/app/features/search/search.module.ts @@ -13,6 +13,7 @@ import { NotificationSearchTableComponent } from './notification-search-table/no import { SearchComponent } from './search.component'; import { InquirySearchTableComponent } from './inquiry-search-table/inquiry-search-table.component'; import { MatChipsModule } from '@angular/material/chips'; +import { SpinnerStatusComponent } from './spinner-status/spinner-status.component'; const routes: Routes = [ { @@ -30,6 +31,7 @@ const routes: Routes = [ NotificationSearchTableComponent, FileTypeFilterDropDownComponent, InquirySearchTableComponent, + SpinnerStatusComponent, ], imports: [ CommonModule, diff --git a/alcs-frontend/src/app/features/search/spinner-status/spinner-status.component.html b/alcs-frontend/src/app/features/search/spinner-status/spinner-status.component.html new file mode 100644 index 0000000000..8110a6a901 --- /dev/null +++ b/alcs-frontend/src/app/features/search/spinner-status/spinner-status.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/alcs-frontend/src/app/features/search/spinner-status/spinner-status.component.scss b/alcs-frontend/src/app/features/search/spinner-status/spinner-status.component.scss new file mode 100644 index 0000000000..51e336567d --- /dev/null +++ b/alcs-frontend/src/app/features/search/spinner-status/spinner-status.component.scss @@ -0,0 +1,12 @@ +.spinner-container { + width: 25px; + height: 25px; + display: flex; + align-items: center; + justify-content: center; +} + +.spinner-overlay { + z-index: 2; + opacity: 0.4; +} diff --git a/alcs-frontend/src/app/features/search/spinner-status/spinner-status.component.ts b/alcs-frontend/src/app/features/search/spinner-status/spinner-status.component.ts new file mode 100644 index 0000000000..7a5e71245d --- /dev/null +++ b/alcs-frontend/src/app/features/search/spinner-status/spinner-status.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-spinner-status', + templateUrl: './spinner-status.component.html', + styleUrls: ['./spinner-status.component.scss'], +}) +export class SpinnerStatusComponent {} diff --git a/alcs-frontend/src/app/services/search/search.dto.ts b/alcs-frontend/src/app/services/search/search.dto.ts index c5f2d6ea8a..a99e3fe84a 100644 --- a/alcs-frontend/src/app/services/search/search.dto.ts +++ b/alcs-frontend/src/app/services/search/search.dto.ts @@ -11,7 +11,7 @@ export interface ApplicationSearchResultDto { dateSubmitted: number; portalStatus?: string; class: string; - status: string; + status?: string | null; } export interface NoticeOfIntentSearchResultDto extends ApplicationSearchResultDto {} @@ -93,3 +93,8 @@ export interface SearchResultDto { boardCode?: string; label?: ApplicationTypeDto; } + +export interface StatusUpdateSearchResultDto { + fileNumber: string; + status: string; +} diff --git a/alcs-frontend/src/app/services/search/search.service.ts b/alcs-frontend/src/app/services/search/search.service.ts index 9a019340bf..3e0dcd44a3 100644 --- a/alcs-frontend/src/app/services/search/search.service.ts +++ b/alcs-frontend/src/app/services/search/search.service.ts @@ -13,6 +13,7 @@ import { PlanningReviewSearchResultDto, SearchRequestDto, SearchResultDto, + StatusUpdateSearchResultDto, } from './search.dto'; @Injectable({ @@ -45,7 +46,7 @@ export class SearchService { return undefined; } } - + async advancedSearchApplicationsFetch(searchDto: SearchRequestDto) { try { return await firstValueFrom( @@ -61,6 +62,51 @@ export class SearchService { } } + async advancedSearchApplicationStatusFetch(fileNumbers: string[]) { + try { + return await firstValueFrom( + this.http.post( + `${this.baseUrl}/advanced/application-status`, + fileNumbers, + ), + ); + } catch (e) { + console.error(e); + this.toastService.showErrorToast(`Application Status search failed. Please refresh the page and try again`); + return undefined; + } + } + + async advancedSearchNoiStatusFetch(fileNumbers: string[]) { + try { + return await firstValueFrom( + this.http.post( + `${this.baseUrl}/advanced/noi-status`, + fileNumbers, + ), + ); + } catch (e) { + console.error(e); + this.toastService.showErrorToast(`Notice of Intent search failed. Please refresh the page and try again`); + return undefined; + } + } + + async advancedSearchNotificationStatusFetch(fileNumbers: string[]) { + try { + return await firstValueFrom( + this.http.post( + `${this.baseUrl}/advanced/notification-status`, + fileNumbers, + ), + ); + } catch (e) { + console.error(e); + this.toastService.showErrorToast(`Notification search failed. Please refresh the page and try again`); + return undefined; + } + } + async advancedSearchNoticeOfIntentsFetch(searchDto: SearchRequestDto) { try { return await firstValueFrom( diff --git a/services/apps/alcs/src/alcs/search/application/application-advanced-search.service.ts b/services/apps/alcs/src/alcs/search/application/application-advanced-search.service.ts index a10a45509d..80a08ebe80 100644 --- a/services/apps/alcs/src/alcs/search/application/application-advanced-search.service.ts +++ b/services/apps/alcs/src/alcs/search/application/application-advanced-search.service.ts @@ -13,6 +13,7 @@ import { LocalGovernment } from '../../local-government/local-government.entity' import { SEARCH_CACHE_TIME } from '../search.config'; import { AdvancedSearchResultDto, SearchRequestDto } from '../search.dto'; import { ApplicationSubmissionSearchView } from './application-search-view.entity'; +import { ApplicationSubmissionStatusSearchView } from '../status/application-search-status-view.entity'; @Injectable() export class ApplicationAdvancedSearchService { @@ -62,13 +63,20 @@ export class ApplicationAdvancedSearchService { fileNumbers: [...fileNumbers.values()], }); + if (searchDto.sortField === 'status') { + query = query.innerJoin( + ApplicationSubmissionStatusSearchView, + 'app_status', + 'app_status.file_number = "appSearch"."file_number"', + ); + } + 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 t0 = performance.now(); const results = await Promise.all([query.getMany(), query.getCount()]); const t1 = performance.now(); @@ -94,8 +102,8 @@ export class ApplicationAdvancedSearchService { case 'government': return '"appSearch"."local_government_name"'; - case 'portalStatus': - return `"appSearch"."status" ->> 'label' `; + case 'status': + return `"app_status"."status" ->> 'label' `; default: case 'dateSubmitted': diff --git a/services/apps/alcs/src/alcs/search/application/application-search-view.entity.ts b/services/apps/alcs/src/alcs/search/application/application-search-view.entity.ts index e5657ecd3e..fb92d32959 100644 --- a/services/apps/alcs/src/alcs/search/application/application-search-view.entity.ts +++ b/services/apps/alcs/src/alcs/search/application/application-search-view.entity.ts @@ -27,17 +27,10 @@ export class SearchApplicationSubmissionStatusType { .addSelect('app.decision_date', 'decision_date') .addSelect('app.uuid', 'application_uuid') .addSelect('app.region_code', 'application_region_code') - .addSelect( - 'alcs.get_current_status_for_application_submission_by_uuid(app_sub.uuid)', - 'status', - ) + .addSelect('null', 'status') .from(ApplicationSubmission, 'app_sub') .innerJoin(Application, 'app', 'app.file_number = app_sub.file_number') - .leftJoin( - LocalGovernment, - 'localGovernment', - 'app_sub.local_government_uuid = localGovernment.uuid', - ) + .leftJoin(LocalGovernment, 'localGovernment', 'app_sub.local_government_uuid = localGovernment.uuid') .where(`app_sub.is_draft IS NOT TRUE`), }) export class ApplicationSubmissionSearchView { diff --git a/services/apps/alcs/src/alcs/search/notice-of-intent/notice-of-intent-advanced-search.service.ts b/services/apps/alcs/src/alcs/search/notice-of-intent/notice-of-intent-advanced-search.service.ts index da276d0ebf..1677d33c14 100644 --- a/services/apps/alcs/src/alcs/search/notice-of-intent/notice-of-intent-advanced-search.service.ts +++ b/services/apps/alcs/src/alcs/search/notice-of-intent/notice-of-intent-advanced-search.service.ts @@ -13,6 +13,7 @@ import { NoticeOfIntent } from '../../notice-of-intent/notice-of-intent.entity'; import { SEARCH_CACHE_TIME } from '../search.config'; import { AdvancedSearchResultDto, SearchRequestDto } from '../search.dto'; import { NoticeOfIntentSubmissionSearchView } from './notice-of-intent-search-view.entity'; +import { NoiSubmissionStatusSearchView } from '../status/noi-search-status-view.entity'; @Injectable() export class NoticeOfIntentAdvancedSearchService { @@ -63,13 +64,20 @@ export class NoticeOfIntentAdvancedSearchService { fileNumbers: [...fileNumbers.values()], }); + if (searchDto.sortField === 'status') { + query = query.innerJoin( + NoiSubmissionStatusSearchView, + 'noi_status', + 'noi_status.file_number = "noiSearch"."file_number"', + ); + } + 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 t0 = performance.now(); const results = await Promise.all([query.getMany(), query.getCount()]); const t1 = performance.now(); @@ -95,8 +103,8 @@ export class NoticeOfIntentAdvancedSearchService { case 'government': return '"noiSearch"."local_government_name"'; - case 'portalStatus': - return `"noiSearch"."status" ->> 'label' `; + case 'status': + return `"noi_status"."status" ->> 'label' `; default: case 'dateSubmitted': diff --git a/services/apps/alcs/src/alcs/search/notice-of-intent/notice-of-intent-search-view.entity.ts b/services/apps/alcs/src/alcs/search/notice-of-intent/notice-of-intent-search-view.entity.ts index d65c2ca7c9..de5352a1af 100644 --- a/services/apps/alcs/src/alcs/search/notice-of-intent/notice-of-intent-search-view.entity.ts +++ b/services/apps/alcs/src/alcs/search/notice-of-intent/notice-of-intent-search-view.entity.ts @@ -1,11 +1,4 @@ -import { - DataSource, - JoinColumn, - ManyToOne, - PrimaryColumn, - ViewColumn, - ViewEntity, -} from 'typeorm'; +import { DataSource, JoinColumn, ManyToOne, PrimaryColumn, ViewColumn, ViewEntity } from 'typeorm'; import { NoticeOfIntentSubmission } from '../../../portal/notice-of-intent-submission/notice-of-intent-submission.entity'; import { NoticeOfIntentType } from '../../notice-of-intent/notice-of-intent-type/notice-of-intent-type.entity'; import { LocalGovernment } from '../../local-government/local-government.entity'; @@ -38,23 +31,12 @@ export class SearchNoticeOfIntentSubmissionStatusType { .addSelect('noi.decision_date', 'decision_date') .addSelect('noi.uuid', 'notice_of_intent_uuid') .addSelect('noi.region_code', 'notice_of_intent_region_code') - .addSelect( - 'alcs.get_current_status_for_notice_of_intent_submission_by_uuid(nois.uuid)', - 'status', - ) + .addSelect('null', 'status') .from(NoticeOfIntentSubmission, 'nois') .innerJoin(NoticeOfIntent, 'noi', 'noi.file_number = nois.file_number') .withDeleted() - .innerJoinAndSelect( - NoticeOfIntentType, - 'noticeOfIntentType', - 'nois.type_code = noticeOfIntentType.code', - ) - .leftJoin( - LocalGovernment, - 'localGovernment', - 'nois.local_government_uuid = localGovernment.uuid', - ) + .innerJoinAndSelect(NoticeOfIntentType, 'noticeOfIntentType', 'nois.type_code = noticeOfIntentType.code') + .leftJoin(LocalGovernment, 'localGovernment', 'nois.local_government_uuid = localGovernment.uuid') .where(`nois.is_draft IS NOT TRUE`), }) export class NoticeOfIntentSubmissionSearchView { diff --git a/services/apps/alcs/src/alcs/search/notification/notification-advanced-search.service.ts b/services/apps/alcs/src/alcs/search/notification/notification-advanced-search.service.ts index 6e0c3b0b36..3f9c9a90a9 100644 --- a/services/apps/alcs/src/alcs/search/notification/notification-advanced-search.service.ts +++ b/services/apps/alcs/src/alcs/search/notification/notification-advanced-search.service.ts @@ -12,6 +12,7 @@ import { SEARCH_CACHE_TIME } from '../search.config'; import { AdvancedSearchResultDto, SearchRequestDto } from '../search.dto'; import { NotificationSubmissionSearchView } from './notification-search-view.entity'; import { getNextDayToPacific, getStartOfDayToPacific } from '../../../utils/pacific-date-time-helper'; +import { NotificationSubmissionStatusSearchView } from '../status/notification-search-status-view.entity'; @Injectable() export class NotificationAdvancedSearchService { @@ -45,11 +46,7 @@ export class NotificationAdvancedSearchService { fileNumbers = new Set(cachedNumbers); } else { fileNumbers = await this.searchForFileNumbers(searchDto); - await client.setEx( - searchKey, - SEARCH_CACHE_TIME, - JSON.stringify([...fileNumbers.values()]), - ); + await client.setEx(searchKey, SEARCH_CACHE_TIME, JSON.stringify([...fileNumbers.values()])); } if (fileNumbers.size === 0) { @@ -70,17 +67,20 @@ export class NotificationAdvancedSearchService { fileNumbers: [...fileNumbers.values()], }); + if (searchDto.sortField === 'status') { + query = query.innerJoin( + NotificationSubmissionStatusSearchView, + 'not_status', + 'not_status.file_number = "notificationSearch"."file_number"', + ); + } + const sortQuery = this.compileSortQuery(searchDto); query = query - .orderBy( - sortQuery, - searchDto.sortDirection, - searchDto.sortDirection === 'ASC' ? 'NULLS FIRST' : 'NULLS LAST', - ) + .orderBy(sortQuery, searchDto.sortDirection, searchDto.sortDirection === 'ASC' ? 'NULLS FIRST' : 'NULLS LAST') .offset((searchDto.page - 1) * searchDto.pageSize) .limit(searchDto.pageSize); - const t0 = performance.now(); const results = await Promise.all([query.getMany(), query.getCount()]); const t1 = performance.now(); @@ -106,8 +106,8 @@ export class NotificationAdvancedSearchService { case 'government': return '"notificationSearch"."local_government_name"'; - case 'portalStatus': - return `"notificationSearch"."status" ->> 'label' `; + case 'status': + return `"not_status"."status" ->> 'label' `; default: case 'dateSubmitted': @@ -119,18 +119,12 @@ export class NotificationAdvancedSearchService { const promises: Promise<{ fileNumber: string }[]>[] = []; if (searchDto.fileNumber) { - const promise = NOTIFICATION_SEARCH_FILTERS.addFileNumberResults( - searchDto, - this.notificationRepository, - ); + const promise = NOTIFICATION_SEARCH_FILTERS.addFileNumberResults(searchDto, this.notificationRepository); promises.push(promise); } if (searchDto.portalStatusCodes && searchDto.portalStatusCodes.length > 0) { - const promise = NOTIFICATION_SEARCH_FILTERS.addPortalStatusResults( - searchDto, - this.notificationSubRepository, - ); + const promise = NOTIFICATION_SEARCH_FILTERS.addPortalStatusResults(searchDto, this.notificationSubRepository); promises.push(promise); } @@ -148,26 +142,17 @@ export class NotificationAdvancedSearchService { } if (searchDto.name) { - const promise = NOTIFICATION_SEARCH_FILTERS.addNameResults( - searchDto, - this.notificationSubRepository, - ); + const promise = NOTIFICATION_SEARCH_FILTERS.addNameResults(searchDto, this.notificationSubRepository); promises.push(promise); } if (searchDto.pid || searchDto.civicAddress) { - const promise = NOTIFICATION_SEARCH_FILTERS.addParcelResults( - searchDto, - this.notificationSubRepository, - ); + const promise = NOTIFICATION_SEARCH_FILTERS.addParcelResults(searchDto, this.notificationSubRepository); promises.push(promise); } if (searchDto.fileTypes.includes('SRW')) { - const promise = NOTIFICATION_SEARCH_FILTERS.addFileTypeResults( - searchDto, - this.notificationRepository, - ); + const promise = NOTIFICATION_SEARCH_FILTERS.addFileTypeResults(searchDto, this.notificationRepository); promises.push(promise); } @@ -178,16 +163,11 @@ export class NotificationAdvancedSearchService { const t0 = performance.now(); const finalResult = await processSearchPromises(promises); const t1 = performance.now(); - this.logger.debug( - `ALCS Application pre-search search took ${t1 - t0} milliseconds.`, - ); + this.logger.debug(`ALCS Application pre-search search took ${t1 - t0} milliseconds.`); return finalResult; } - private addRegionResults( - searchDto: SearchRequestDto, - promises: Promise<{ fileNumber: string }[]>[], - ) { + private addRegionResults(searchDto: SearchRequestDto, promises: Promise<{ fileNumber: string }[]>[]) { const promise = this.notificationRepository.find({ where: { regionCode: searchDto.regionCode, @@ -199,34 +179,19 @@ export class NotificationAdvancedSearchService { promises.push(promise); } - private addSubmittedDateResults( - searchDto: SearchRequestDto, - promises: Promise<{ fileNumber: string }[]>[], - ) { - let query = this.notificationRepository - .createQueryBuilder('notification') - .select('notification.fileNumber'); + private addSubmittedDateResults(searchDto: SearchRequestDto, promises: Promise<{ fileNumber: string }[]>[]) { + let query = this.notificationRepository.createQueryBuilder('notification').select('notification.fileNumber'); if (searchDto.dateSubmittedFrom !== undefined) { - query = query.andWhere( - 'notification.date_submitted_to_alc >= :date_submitted_from', - { - date_submitted_from: getStartOfDayToPacific( - searchDto.dateSubmittedFrom - ).toISOString(), - }, - ); + query = query.andWhere('notification.date_submitted_to_alc >= :date_submitted_from', { + date_submitted_from: getStartOfDayToPacific(searchDto.dateSubmittedFrom).toISOString(), + }); } if (searchDto.dateSubmittedTo !== undefined) { - query = query.andWhere( - 'notification.date_submitted_to_alc < :date_submitted_to', - { - date_submitted_to: getNextDayToPacific( - searchDto.dateSubmittedTo - ).toISOString(), - }, - ); + query = query.andWhere('notification.date_submitted_to_alc < :date_submitted_to', { + date_submitted_to: getNextDayToPacific(searchDto.dateSubmittedTo).toISOString(), + }); } promises.push(query.getMany()); } diff --git a/services/apps/alcs/src/alcs/search/notification/notification-search-view.entity.ts b/services/apps/alcs/src/alcs/search/notification/notification-search-view.entity.ts index 33e8e61fd2..054593f98d 100644 --- a/services/apps/alcs/src/alcs/search/notification/notification-search-view.entity.ts +++ b/services/apps/alcs/src/alcs/search/notification/notification-search-view.entity.ts @@ -1,11 +1,4 @@ -import { - DataSource, - JoinColumn, - ManyToOne, - PrimaryColumn, - ViewColumn, - ViewEntity, -} from 'typeorm'; +import { DataSource, JoinColumn, ManyToOne, PrimaryColumn, ViewColumn, ViewEntity } from 'typeorm'; import { NotificationSubmission } from '../../../portal/notification-submission/notification-submission.entity'; import { LocalGovernment } from '../../local-government/local-government.entity'; import { NotificationType } from '../../notification/notification-type/notification-type.entity'; @@ -35,27 +28,12 @@ export class SearchNotificationSubmissionStatusType { .addSelect('noti.date_submitted_to_alc', 'date_submitted_to_alc') .addSelect('noti.uuid', 'notification_uuid') .addSelect('noti.region_code', 'notification_region_code') - .addSelect( - 'alcs.get_current_status_for_notification_submission_by_uuid(noti_sub.uuid)', - 'status', - ) + .addSelect('null', 'status') .from(NotificationSubmission, 'noti_sub') - .innerJoin( - Notification, - 'noti', - 'noti.file_number = noti_sub.file_number', - ) + .innerJoin(Notification, 'noti', 'noti.file_number = noti_sub.file_number') .withDeleted() - .innerJoinAndSelect( - NotificationType, - 'notificationType', - 'noti_sub.type_code = notificationType.code', - ) - .leftJoin( - LocalGovernment, - 'localGovernment', - 'noti.local_government_uuid = localGovernment.uuid', - ), + .innerJoinAndSelect(NotificationType, 'notificationType', 'noti_sub.type_code = notificationType.code') + .leftJoin(LocalGovernment, 'localGovernment', 'noti.local_government_uuid = localGovernment.uuid'), }) export class NotificationSubmissionSearchView { @ViewColumn() 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 c991110101..b947e09a6e 100644 --- a/services/apps/alcs/src/alcs/search/search.controller.spec.ts +++ b/services/apps/alcs/src/alcs/search/search.controller.spec.ts @@ -20,6 +20,7 @@ import { PlanningReviewAdvancedSearchService } from './planning-review/planning- import { SearchController } from './search.controller'; import { SearchRequestDto } from './search.dto'; import { SearchService } from './search.service'; +import { SearchStatusService } from './status/search-status.service'; describe('SearchController', () => { let controller: SearchController; @@ -29,10 +30,22 @@ describe('SearchController', () => { let mockNotificationAdvancedSearchService: DeepMocked; let mockPlanningReviewAdvancedSearchService: DeepMocked; let mockInquiryAdvancedSearchService: DeepMocked; + let mockSearchStatusService: DeepMocked; let mockDataSource: DeepMocked; let mockQueryRunner: DeepMocked; let mockAppTypeRepo: DeepMocked>; + const statusSearchMockedResult = [ + { + fileNumber: 'file1', + status: 'status', + }, + { + fileNumber: 'file2', + status: 'status', + }, + ]; + beforeEach(async () => { mockSearchService = createMock(); mockNoticeOfIntentAdvancedSearchService = createMock(); @@ -40,6 +53,7 @@ describe('SearchController', () => { mockNotificationAdvancedSearchService = createMock(); mockPlanningReviewAdvancedSearchService = createMock(); mockInquiryAdvancedSearchService = createMock(); + mockSearchStatusService = createMock(); mockDataSource = createMock(); mockAppTypeRepo = createMock(); @@ -74,6 +88,10 @@ describe('SearchController', () => { provide: InquiryAdvancedSearchService, useValue: mockInquiryAdvancedSearchService, }, + { + provide: SearchStatusService, + useValue: mockSearchStatusService, + }, { provide: DataSource, useValue: mockDataSource, @@ -103,12 +121,10 @@ describe('SearchController', () => { mockSearchService.getPlanningReview.mockResolvedValue(new PlanningReview()); mockSearchService.getInquiry.mockResolvedValue(new Inquiry()); - mockNoticeOfIntentAdvancedSearchService.searchNoticeOfIntents.mockResolvedValue( - { - data: [], - total: 0, - }, - ); + mockNoticeOfIntentAdvancedSearchService.searchNoticeOfIntents.mockResolvedValue({ + data: [], + total: 0, + }); mockApplicationAdvancedSearchService.searchApplications.mockResolvedValue({ data: [], @@ -129,6 +145,10 @@ describe('SearchController', () => { data: [], total: 0, }); + + mockSearchStatusService.searchApplicationStatus.mockResolvedValue(statusSearchMockedResult); + mockSearchStatusService.searchNoiStatus.mockResolvedValue(statusSearchMockedResult); + mockSearchStatusService.searchNotificationStatus.mockResolvedValue(statusSearchMockedResult); }); it('should be defined', () => { @@ -144,13 +164,9 @@ describe('SearchController', () => { expect(mockSearchService.getNoi).toHaveBeenCalledTimes(1); expect(mockSearchService.getNoi).toHaveBeenCalledWith(searchString); expect(mockSearchService.getPlanningReview).toHaveBeenCalledTimes(1); - expect(mockSearchService.getPlanningReview).toHaveBeenCalledWith( - searchString, - ); + expect(mockSearchService.getPlanningReview).toHaveBeenCalledWith(searchString); expect(mockSearchService.getNotification).toHaveBeenCalledTimes(1); - expect(mockSearchService.getNotification).toHaveBeenCalledWith( - searchString, - ); + expect(mockSearchService.getNotification).toHaveBeenCalledWith(searchString); expect(result).toBeDefined(); expect(result.length).toBe(5); }); @@ -168,29 +184,21 @@ describe('SearchController', () => { const result = await controller.advancedSearch(mockSearchRequestDto); - expect( - mockApplicationAdvancedSearchService.searchApplications, - ).toHaveBeenCalledTimes(1); - expect( - mockApplicationAdvancedSearchService.searchApplications, - ).toHaveBeenCalledWith(mockSearchRequestDto, {}); + expect(mockApplicationAdvancedSearchService.searchApplications).toHaveBeenCalledTimes(1); + expect(mockApplicationAdvancedSearchService.searchApplications).toHaveBeenCalledWith(mockSearchRequestDto, {}); expect(result.applications).toBeDefined(); expect(result.totalApplications).toBe(0); - expect( - mockNoticeOfIntentAdvancedSearchService.searchNoticeOfIntents, - ).toHaveBeenCalledTimes(1); - expect( - mockNoticeOfIntentAdvancedSearchService.searchNoticeOfIntents, - ).toHaveBeenCalledWith(mockSearchRequestDto, {}); + expect(mockNoticeOfIntentAdvancedSearchService.searchNoticeOfIntents).toHaveBeenCalledTimes(1); + expect(mockNoticeOfIntentAdvancedSearchService.searchNoticeOfIntents).toHaveBeenCalledWith( + mockSearchRequestDto, + {}, + ); expect(result.noticeOfIntents).toBeDefined(); expect(result.totalNoticeOfIntents).toBe(0); expect(mockInquiryAdvancedSearchService.search).toHaveBeenCalledTimes(1); - expect(mockInquiryAdvancedSearchService.search).toHaveBeenCalledWith( - mockSearchRequestDto, - {}, - ); + expect(mockInquiryAdvancedSearchService.search).toHaveBeenCalledWith(mockSearchRequestDto, {}); expect(result.noticeOfIntents).toBeDefined(); expect(result.totalNoticeOfIntents).toBe(0); }); @@ -205,16 +213,11 @@ describe('SearchController', () => { portalStatusCodes: [], }; - const result = - await controller.advancedSearchApplications(mockSearchRequestDto); + const result = await controller.advancedSearchApplications(mockSearchRequestDto); expect(mockDataSource.createQueryRunner).toHaveBeenCalledTimes(1); - expect( - mockApplicationAdvancedSearchService.searchApplications, - ).toHaveBeenCalledTimes(1); - expect( - mockApplicationAdvancedSearchService.searchApplications, - ).toHaveBeenCalledWith(mockSearchRequestDto, {}); + expect(mockApplicationAdvancedSearchService.searchApplications).toHaveBeenCalledTimes(1); + expect(mockApplicationAdvancedSearchService.searchApplications).toHaveBeenCalledWith(mockSearchRequestDto, {}); expect(result.data).toBeDefined(); expect(result.total).toBe(0); expect(mockQueryRunner.release).toHaveBeenCalledTimes(1); @@ -230,15 +233,13 @@ describe('SearchController', () => { portalStatusCodes: [], }; - const result = - await controller.advancedSearchNoticeOfIntents(mockSearchRequestDto); + const result = await controller.advancedSearchNoticeOfIntents(mockSearchRequestDto); - expect( - mockNoticeOfIntentAdvancedSearchService.searchNoticeOfIntents, - ).toHaveBeenCalledTimes(1); - expect( - mockNoticeOfIntentAdvancedSearchService.searchNoticeOfIntents, - ).toHaveBeenCalledWith(mockSearchRequestDto, {}); + expect(mockNoticeOfIntentAdvancedSearchService.searchNoticeOfIntents).toHaveBeenCalledTimes(1); + expect(mockNoticeOfIntentAdvancedSearchService.searchNoticeOfIntents).toHaveBeenCalledWith( + mockSearchRequestDto, + {}, + ); expect(result.data).toBeDefined(); expect(result.total).toBe(0); }); @@ -256,12 +257,8 @@ describe('SearchController', () => { const result = await controller.advancedSearch(mockSearchRequestDto); expect(mockDataSource.createQueryRunner).toHaveBeenCalledTimes(1); - expect( - mockApplicationAdvancedSearchService.searchApplications, - ).toHaveBeenCalledTimes(1); - expect( - mockApplicationAdvancedSearchService.searchApplications, - ).toHaveBeenCalledWith(mockSearchRequestDto, {}); + expect(mockApplicationAdvancedSearchService.searchApplications).toHaveBeenCalledTimes(1); + expect(mockApplicationAdvancedSearchService.searchApplications).toHaveBeenCalledWith(mockSearchRequestDto, {}); expect(result.applications).toBeDefined(); expect(result.totalApplications).toBe(0); expect(mockQueryRunner.release).toHaveBeenCalledTimes(1); @@ -279,12 +276,11 @@ describe('SearchController', () => { const result = await controller.advancedSearch(mockSearchRequestDto); - expect( - mockNoticeOfIntentAdvancedSearchService.searchNoticeOfIntents, - ).toHaveBeenCalledTimes(1); - expect( - mockNoticeOfIntentAdvancedSearchService.searchNoticeOfIntents, - ).toHaveBeenCalledWith(mockSearchRequestDto, {}); + expect(mockNoticeOfIntentAdvancedSearchService.searchNoticeOfIntents).toHaveBeenCalledTimes(1); + expect(mockNoticeOfIntentAdvancedSearchService.searchNoticeOfIntents).toHaveBeenCalledWith( + mockSearchRequestDto, + {}, + ); expect(result.noticeOfIntents).toBeDefined(); expect(result.totalNoticeOfIntents).toBe(0); }); @@ -302,10 +298,7 @@ describe('SearchController', () => { const result = await controller.advancedSearch(mockSearchRequestDto); expect(mockInquiryAdvancedSearchService.search).toHaveBeenCalledTimes(1); - expect(mockInquiryAdvancedSearchService.search).toHaveBeenCalledWith( - mockSearchRequestDto, - {}, - ); + expect(mockInquiryAdvancedSearchService.search).toHaveBeenCalledWith(mockSearchRequestDto, {}); expect(result.inquiries).toBeDefined(); expect(result.totalInquiries).toBe(0); }); @@ -322,14 +315,36 @@ describe('SearchController', () => { const result = await controller.advancedSearch(mockSearchRequestDto); - expect( - mockPlanningReviewAdvancedSearchService.search, - ).toHaveBeenCalledTimes(1); - expect(mockPlanningReviewAdvancedSearchService.search).toHaveBeenCalledWith( - mockSearchRequestDto, - {}, - ); + expect(mockPlanningReviewAdvancedSearchService.search).toHaveBeenCalledTimes(1); + expect(mockPlanningReviewAdvancedSearchService.search).toHaveBeenCalledWith(mockSearchRequestDto, {}); expect(result.inquiries).toBeDefined(); expect(result.totalInquiries).toBe(0); }); + + it('should call application status search', async () => { + const fileNumbers = ['file1', 'file2']; + + const result = await controller.advancedSearchApplicationStatus(fileNumbers); + + expect(mockSearchStatusService.searchApplicationStatus).toHaveBeenCalledTimes(1); + expect(result).toBeDefined(); + }); + + it('should call noi status search', async () => { + const fileNumbers = ['file1', 'file2']; + + const result = await controller.advancedSearchNoiStatus(fileNumbers); + + expect(mockSearchStatusService.searchNoiStatus).toHaveBeenCalledTimes(1); + expect(result).toBeDefined(); + }); + + it('should call notification status search', async () => { + const fileNumbers = ['file1', 'file2']; + + const result = await controller.advancedSearchNotificationStatus(fileNumbers); + + expect(mockSearchStatusService.searchNotificationStatus).toHaveBeenCalledTimes(1); + expect(result).toBeDefined(); + }); }); diff --git a/services/apps/alcs/src/alcs/search/search.controller.ts b/services/apps/alcs/src/alcs/search/search.controller.ts index be0a7b6549..475ff7cc19 100644 --- a/services/apps/alcs/src/alcs/search/search.controller.ts +++ b/services/apps/alcs/src/alcs/search/search.controller.ts @@ -40,8 +40,10 @@ import { PlanningReviewSearchResultDto, SearchRequestDto, SearchResultDto, + StatusUpdateSearchResultDto, } from './search.dto'; import { SearchService } from './search.service'; +import { SearchStatusService } from './status/search-status.service'; @ApiOAuth2(config.get('KEYCLOAK.SCOPES')) @UseGuards(RolesGuard) @@ -54,6 +56,7 @@ export class SearchController { private applicationSearchService: ApplicationAdvancedSearchService, private notificationSearchService: NotificationAdvancedSearchService, private planningReviewSearchService: PlanningReviewAdvancedSearchService, + private searchStatusService: SearchStatusService, private inquirySearchService: InquiryAdvancedSearchService, @InjectRepository(ApplicationType) private appTypeRepo: Repository, @@ -183,14 +186,18 @@ export class SearchController { ): Promise> { const queryRunner = this.dataSource.createQueryRunner('slave'); - const noticeOfIntents = await this.noticeOfIntentSearchService.searchNoticeOfIntents(searchDto, queryRunner); + try { + const noticeOfIntents = await this.noticeOfIntentSearchService.searchNoticeOfIntents(searchDto, queryRunner); - const mappedSearchResult = await this.mapAdvancedSearchResults(null, noticeOfIntents, null, null, null); + const mappedSearchResult = await this.mapAdvancedSearchResults(null, noticeOfIntents, null, null, null); - return { - total: mappedSearchResult.totalNoticeOfIntents, - data: mappedSearchResult.noticeOfIntents, - }; + return { + total: mappedSearchResult.totalNoticeOfIntents, + data: mappedSearchResult.noticeOfIntents, + }; + } finally { + await queryRunner.release(); + } } @Post('/advanced/notifications') @@ -200,14 +207,75 @@ export class SearchController { ): Promise> { const queryRunner = this.dataSource.createQueryRunner('slave'); - const notifications = await this.notificationSearchService.search(searchDto, queryRunner); + try { + const notifications = await this.notificationSearchService.search(searchDto, queryRunner); - const mappedSearchResult = await this.mapAdvancedSearchResults(null, null, null, notifications, null); + const mappedSearchResult = await this.mapAdvancedSearchResults(null, null, null, notifications, null); - return { - total: mappedSearchResult.totalNotifications, - data: mappedSearchResult.notifications, - }; + return { + total: mappedSearchResult.totalNotifications, + data: mappedSearchResult.notifications, + }; + } finally { + await queryRunner.release(); + } + } + + @Post('/advanced/application-status') + @UserRoles(...ROLES_ALLOWED_APPLICATIONS) + async advancedSearchApplicationStatus(@Body() fileNumbers: string[]): Promise { + const queryRunner = this.dataSource.createQueryRunner('slave'); + + try { + const statuses = await this.searchStatusService.searchApplicationStatus(fileNumbers, queryRunner); + + return statuses.map((s) => { + return { + fileNumber: s.fileNumber, + status: s.status, + }; + }); + } finally { + await queryRunner.release(); + } + } + + @Post('/advanced/noi-status') + @UserRoles(...ROLES_ALLOWED_APPLICATIONS) + async advancedSearchNoiStatus(@Body() fileNumbers: string[]): Promise { + const queryRunner = this.dataSource.createQueryRunner('slave'); + + try { + const statuses = await this.searchStatusService.searchNoiStatus(fileNumbers, queryRunner); + + return statuses.map((s) => { + return { + fileNumber: s.fileNumber, + status: s.status, + }; + }); + } finally { + await queryRunner.release(); + } + } + + @Post('/advanced/notification-status') + @UserRoles(...ROLES_ALLOWED_APPLICATIONS) + async advancedSearchNotificationStatus(@Body() fileNumbers: string[]): Promise { + const queryRunner = this.dataSource.createQueryRunner('slave'); + + try { + const statuses = await this.searchStatusService.searchNotificationStatus(fileNumbers, queryRunner); + + return statuses.map((s) => { + return { + fileNumber: s.fileNumber, + status: s.status, + }; + }); + } finally { + await queryRunner.release(); + } } @Post('/advanced/planning-reviews') @@ -217,14 +285,18 @@ export class SearchController { ): Promise> { const queryRunner = this.dataSource.createQueryRunner('slave'); - const planningReviews = await this.planningReviewSearchService.search(searchDto, queryRunner); + try { + const planningReviews = await this.planningReviewSearchService.search(searchDto, queryRunner); - const mappedSearchResult = await this.mapAdvancedSearchResults(null, null, planningReviews, null, null); + const mappedSearchResult = await this.mapAdvancedSearchResults(null, null, planningReviews, null, null); - return { - total: mappedSearchResult.totalPlanningReviews, - data: mappedSearchResult.planningReviews, - }; + return { + total: mappedSearchResult.totalPlanningReviews, + data: mappedSearchResult.planningReviews, + }; + } finally { + await queryRunner.release(); + } } @Post('/advanced/inquiries') @@ -234,14 +306,18 @@ export class SearchController { ): Promise> { const queryRunner = this.dataSource.createQueryRunner('slave'); - const inquiries = await this.inquirySearchService.search(searchDto, queryRunner); + try { + const inquiries = await this.inquirySearchService.search(searchDto, queryRunner); - const mappedSearchResult = await this.mapAdvancedSearchResults(null, null, null, null, inquiries); + const mappedSearchResult = await this.mapAdvancedSearchResults(null, null, null, null, inquiries); - return { - total: mappedSearchResult.totalInquiries, - data: mappedSearchResult.inquiries, - }; + return { + total: mappedSearchResult.totalInquiries, + data: mappedSearchResult.inquiries, + }; + } finally { + await queryRunner.release(); + } } private getEntitiesTypeToSearch(searchDto: SearchRequestDto) { @@ -434,7 +510,7 @@ export class SearchController { localGovernmentName: application.localGovernmentName, ownerName: application.applicant, class: 'APP', - status: application.status.status_type_code, + status: application.status && application.status.status_type_code, }; } @@ -449,7 +525,7 @@ export class SearchController { localGovernmentName: noi.localGovernmentName, ownerName: noi.applicant, class: 'NOI', - status: noi.status.status_type_code, + status: noi.status && noi.status.status_type_code, }; } @@ -464,7 +540,7 @@ export class SearchController { localGovernmentName: notification.localGovernmentName, ownerName: notification.applicant, class: 'NOTI', - status: notification.status.status_type_code, + status: notification.status && notification.status.status_type_code, }; } diff --git a/services/apps/alcs/src/alcs/search/search.dto.ts b/services/apps/alcs/src/alcs/search/search.dto.ts index 842eb1418f..401dcf8ac6 100644 --- a/services/apps/alcs/src/alcs/search/search.dto.ts +++ b/services/apps/alcs/src/alcs/search/search.dto.ts @@ -1,10 +1,4 @@ -import { - IsArray, - IsNumber, - IsOptional, - IsString, - MinLength, -} from 'class-validator'; +import { IsArray, 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'; @@ -19,13 +13,7 @@ export class SearchResultDto { label?: ApplicationTypeDto; } -export type SearchEntityClass = - | 'APP' - | 'NOI' - | 'PLAN' - | 'COV' - | 'NOTI' - | 'INQR'; +export type SearchEntityClass = 'APP' | 'NOI' | 'PLAN' | 'COV' | 'NOTI' | 'INQR'; export class ApplicationSearchResultDto { type: ApplicationTypeDto; @@ -34,7 +22,7 @@ export class ApplicationSearchResultDto { localGovernmentName?: string; fileNumber: string; boardCode?: string; - status: string; + status?: string; dateSubmitted?: number; class: SearchEntityClass; } @@ -46,7 +34,7 @@ export class NoticeOfIntentSearchResultDto { localGovernmentName?: string; fileNumber: string; boardCode?: string; - status: string; + status?: string; dateSubmitted?: number; class: SearchEntityClass; } @@ -69,7 +57,7 @@ export class NotificationSearchResultDto { localGovernmentName?: string; fileNumber: string; boardCode?: string; - status: string; + status?: string; dateSubmitted?: number; class: SearchEntityClass; } @@ -100,6 +88,11 @@ export class AdvancedSearchResponseDto { totalInquiries: number; } +export class StatusUpdateSearchResultDto { + fileNumber: string; + status: string; +} + export class AdvancedSearchResultDto { data: T; total: number; diff --git a/services/apps/alcs/src/alcs/search/search.module.ts b/services/apps/alcs/src/alcs/search/search.module.ts index 70b12f1261..f26630bb8f 100644 --- a/services/apps/alcs/src/alcs/search/search.module.ts +++ b/services/apps/alcs/src/alcs/search/search.module.ts @@ -23,6 +23,10 @@ import { PlanningReviewAdvancedSearchService } from './planning-review/planning- import { PlanningReviewSearchView } from './planning-review/planning-review-search-view.entity'; import { SearchController } from './search.controller'; import { SearchService } from './search.service'; +import { SearchStatusService } from './status/search-status.service'; +import { ApplicationSubmissionStatusSearchView } from './status/application-search-status-view.entity'; +import { NoiSubmissionStatusSearchView } from './status/noi-search-status-view.entity'; +import { NotificationSubmissionStatusSearchView } from './status/notification-search-status-view.entity'; @Module({ imports: [ @@ -42,6 +46,9 @@ import { SearchService } from './search.service'; Inquiry, InquirySearchView, LocalGovernment, + ApplicationSubmissionStatusSearchView, + NoiSubmissionStatusSearchView, + NotificationSubmissionStatusSearchView, ]), ], providers: [ @@ -52,6 +59,7 @@ import { SearchService } from './search.service'; NotificationAdvancedSearchService, PlanningReviewAdvancedSearchService, InquiryAdvancedSearchService, + SearchStatusService, ], controllers: [SearchController], }) diff --git a/services/apps/alcs/src/alcs/search/status/application-search-status-view.entity.ts b/services/apps/alcs/src/alcs/search/status/application-search-status-view.entity.ts new file mode 100644 index 0000000000..ed315f9183 --- /dev/null +++ b/services/apps/alcs/src/alcs/search/status/application-search-status-view.entity.ts @@ -0,0 +1,30 @@ +import { DataSource, ViewColumn, ViewEntity } from 'typeorm'; +import { ApplicationSubmission } from '../../../portal/application-submission/application-submission.entity'; + +// typeorm does not transform property names for the status +export class SearchApplicationSubmissionStatusType { + submission_uuid: string; + + status_type_code: string; + + effective_date: Date; + + label: string; +} + +@ViewEntity({ + expression: (datasource: DataSource) => + datasource + .createQueryBuilder() + .select('app_sub.file_number', 'file_number') + .addSelect('alcs.get_current_status_for_application_submission_by_uuid(app_sub.uuid)', 'status') + .from(ApplicationSubmission, 'app_sub') + .where(`app_sub.is_draft IS NOT TRUE`), +}) +export class ApplicationSubmissionStatusSearchView { + @ViewColumn() + fileNumber: string; + + @ViewColumn() + status: SearchApplicationSubmissionStatusType; +} diff --git a/services/apps/alcs/src/alcs/search/status/noi-search-status-view.entity.ts b/services/apps/alcs/src/alcs/search/status/noi-search-status-view.entity.ts new file mode 100644 index 0000000000..ec8e74aa7c --- /dev/null +++ b/services/apps/alcs/src/alcs/search/status/noi-search-status-view.entity.ts @@ -0,0 +1,30 @@ +import { DataSource, ViewColumn, ViewEntity } from 'typeorm'; +import { NoticeOfIntentSubmission } from '../../../portal/notice-of-intent-submission/notice-of-intent-submission.entity'; + +// typeorm does not transform property names for the status +export class SearchNoiSubmissionStatusType { + submission_uuid: string; + + status_type_code: string; + + effective_date: Date; + + label: string; +} + +@ViewEntity({ + expression: (datasource: DataSource) => + datasource + .createQueryBuilder() + .select('noi_sub.file_number', 'file_number') + .addSelect('alcs.get_current_status_for_notice_of_intent_submission_by_uuid(noi_sub.uuid)', 'status') + .from(NoticeOfIntentSubmission, 'noi_sub') + .where(`noi_sub.is_draft IS NOT TRUE`), +}) +export class NoiSubmissionStatusSearchView { + @ViewColumn() + fileNumber: string; + + @ViewColumn() + status: SearchNoiSubmissionStatusType; +} diff --git a/services/apps/alcs/src/alcs/search/status/notification-search-status-view.entity.ts b/services/apps/alcs/src/alcs/search/status/notification-search-status-view.entity.ts new file mode 100644 index 0000000000..ea5ef33bdb --- /dev/null +++ b/services/apps/alcs/src/alcs/search/status/notification-search-status-view.entity.ts @@ -0,0 +1,29 @@ +import { DataSource, ViewColumn, ViewEntity } from 'typeorm'; +import { NotificationSubmission } from '../../../portal/notification-submission/notification-submission.entity'; + +// typeorm does not transform property names for the status +export class SearchNotificationSubmissionStatusType { + submission_uuid: string; + + status_type_code: string; + + effective_date: Date; + + label: string; +} + +@ViewEntity({ + expression: (datasource: DataSource) => + datasource + .createQueryBuilder() + .select('not_sub.file_number', 'file_number') + .addSelect('alcs.get_current_status_for_notification_submission_by_uuid(not_sub.uuid)', 'status') + .from(NotificationSubmission, 'not_sub'), +}) +export class NotificationSubmissionStatusSearchView { + @ViewColumn() + fileNumber: string; + + @ViewColumn() + status: SearchNotificationSubmissionStatusType; +} diff --git a/services/apps/alcs/src/alcs/search/status/search-status.service.spec.ts b/services/apps/alcs/src/alcs/search/status/search-status.service.spec.ts new file mode 100644 index 0000000000..fe16bf9783 --- /dev/null +++ b/services/apps/alcs/src/alcs/search/status/search-status.service.spec.ts @@ -0,0 +1,89 @@ +import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; +import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { QueryRunner, Repository } from 'typeorm'; +import { SearchStatusService } from './search-status.service'; +import { ApplicationSubmissionStatusSearchView } from './application-search-status-view.entity'; +import { NoiSubmissionStatusSearchView } from './noi-search-status-view.entity'; +import { NotificationSubmissionStatusSearchView } from './notification-search-status-view.entity'; +import { createMockQuery } from '../../../../test/mocks/mockTypes'; + +describe('SearchStatusService', () => { + let service: SearchStatusService; + let mockApplicationRepository: DeepMocked>; + let mockNoiRepository: DeepMocked>; + let mockNotificationRepository: DeepMocked>; + + let mockQuery: any = {}; + + beforeEach(async () => { + mockApplicationRepository = createMock(); + mockNoiRepository = createMock(); + mockNotificationRepository = createMock(); + mockQuery = createMockQuery(); + + const module: TestingModule = await Test.createTestingModule({ + providers: [ + SearchStatusService, + { + provide: getRepositoryToken(ApplicationSubmissionStatusSearchView), + useValue: mockApplicationRepository, + }, + { + provide: getRepositoryToken(NoiSubmissionStatusSearchView), + useValue: mockNoiRepository, + }, + { + provide: getRepositoryToken(NotificationSubmissionStatusSearchView), + useValue: mockNotificationRepository, + }, + ], + }).compile(); + + service = module.get(SearchStatusService); + + mockApplicationRepository.createQueryBuilder.mockReturnValue(mockQuery as any); + mockNoiRepository.createQueryBuilder.mockReturnValue(mockQuery as any); + mockNotificationRepository.createQueryBuilder.mockReturnValue(mockQuery as any); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + it('should call repository to get aplication status', async () => { + mockApplicationRepository.createQueryBuilder.mockReturnValue(mockQuery); + + const mockQueryRunner = createMock(); + + const result = await service.searchApplicationStatus(['file1', 'file2'], mockQueryRunner); + + expect(result).toEqual([]); + expect(mockApplicationRepository.createQueryBuilder).toHaveBeenCalledTimes(1); + expect(mockQuery.andWhere).toHaveBeenCalledTimes(1); + }); + + it('should call repository to get noi status', async () => { + mockNoiRepository.createQueryBuilder.mockReturnValue(mockQuery); + + const mockQueryRunner = createMock(); + + const result = await service.searchNoiStatus(['file1', 'file2'], mockQueryRunner); + + expect(result).toEqual([]); + expect(mockNoiRepository.createQueryBuilder).toHaveBeenCalledTimes(1); + expect(mockQuery.andWhere).toHaveBeenCalledTimes(1); + }); + + it('should call repository to get notification status', async () => { + mockNotificationRepository.createQueryBuilder.mockReturnValue(mockQuery); + + const mockQueryRunner = createMock(); + + const result = await service.searchNotificationStatus(['file1', 'file2'], mockQueryRunner); + + expect(result).toEqual([]); + expect(mockNotificationRepository.createQueryBuilder).toHaveBeenCalledTimes(1); + expect(mockQuery.andWhere).toHaveBeenCalledTimes(1); + }); +}); diff --git a/services/apps/alcs/src/alcs/search/status/search-status.service.ts b/services/apps/alcs/src/alcs/search/status/search-status.service.ts new file mode 100644 index 0000000000..37fc08dde1 --- /dev/null +++ b/services/apps/alcs/src/alcs/search/status/search-status.service.ts @@ -0,0 +1,91 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { QueryRunner, Repository } from 'typeorm'; +import { StatusUpdateSearchResultDto } from '../search.dto'; +import { ApplicationSubmissionStatusSearchView } from './application-search-status-view.entity'; +import { NoiSubmissionStatusSearchView } from './noi-search-status-view.entity'; +import { NotificationSubmissionStatusSearchView } from './notification-search-status-view.entity'; + +@Injectable() +export class SearchStatusService { + private logger: Logger = new Logger(SearchStatusService.name); + + constructor( + @InjectRepository(ApplicationSubmissionStatusSearchView) + private applicationStatusSearchRepository: Repository, + @InjectRepository(NoiSubmissionStatusSearchView) + private noiStatusSearchRepository: Repository, + @InjectRepository(NotificationSubmissionStatusSearchView) + private notificationStatusSearchRepository: Repository, + ) {} + + async searchApplicationStatus( + fileNumbers: string[], + queryRunner: QueryRunner, + ): Promise { + const query = this.applicationStatusSearchRepository + .createQueryBuilder('appSearch', queryRunner) + .andWhere('appSearch.fileNumber IN(:...fileNumbers)', { + fileNumbers: [...fileNumbers], + }); + + const t0 = performance.now(); + const results = await Promise.all([query.getMany()]); + const t1 = performance.now(); + this.logger.debug(`ALCS Application status search took ${t1 - t0} milliseconds.`); + + const statusArray: StatusUpdateSearchResultDto[] = results[0].map((r) => { + return { + fileNumber: r.fileNumber, + status: r.status.status_type_code, + }; + }); + + return statusArray; + } + + async searchNoiStatus(fileNumbers: string[], queryRunner: QueryRunner): Promise { + const query = this.noiStatusSearchRepository + .createQueryBuilder('noiSearch', queryRunner) + .andWhere('noiSearch.fileNumber IN(:...fileNumbers)', { + fileNumbers: [...fileNumbers], + }); + const t0 = performance.now(); + const results = await Promise.all([query.getMany()]); + const t1 = performance.now(); + this.logger.debug(`ALCS Noi status search took ${t1 - t0} milliseconds.`); + + const statusArray: StatusUpdateSearchResultDto[] = results[0].map((r) => { + return { + fileNumber: r.fileNumber, + status: r.status.status_type_code, + }; + }); + + return statusArray; + } + + async searchNotificationStatus( + fileNumbers: string[], + queryRunner: QueryRunner, + ): Promise { + const query = this.notificationStatusSearchRepository + .createQueryBuilder('notSearch', queryRunner) + .andWhere('notSearch.fileNumber IN(:...fileNumbers)', { + fileNumbers: [...fileNumbers], + }); + const t0 = performance.now(); + const results = await Promise.all([query.getMany()]); + const t1 = performance.now(); + this.logger.debug(`ALCS Notification status search took ${t1 - t0} milliseconds.`); + + const statusArray: StatusUpdateSearchResultDto[] = results[0].map((r) => { + return { + fileNumber: r.fileNumber, + status: r.status.status_type_code, + }; + }); + + return statusArray; + } +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1731103073215-application_status_update.ts b/services/apps/alcs/src/providers/typeorm/migrations/1731103073215-application_status_update.ts new file mode 100644 index 0000000000..6326731c03 --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1731103073215-application_status_update.ts @@ -0,0 +1,16 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class ApplicationStatusUpdate1731103073215 implements MigrationInterface { + name = 'ApplicationStatusUpdate1731103073215' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE VIEW "alcs"."application_submission_status_search_view" AS SELECT "app_sub"."file_number" AS "file_number", alcs.get_current_status_for_application_submission_by_uuid("app_sub"."uuid") AS "status" FROM "alcs"."application_submission" "app_sub" WHERE ( "app_sub"."is_draft" IS NOT TRUE ) AND ( "app_sub"."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","application_submission_status_search_view","SELECT \"app_sub\".\"file_number\" AS \"file_number\", alcs.get_current_status_for_application_submission_by_uuid(\"app_sub\".\"uuid\") AS \"status\" FROM \"alcs\".\"application_submission\" \"app_sub\" WHERE ( \"app_sub\".\"is_draft\" IS NOT TRUE ) AND ( \"app_sub\".\"audit_deleted_date_at\" IS NULL )"]); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DELETE FROM "alcs"."typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, ["VIEW","application_submission_status_search_view","alcs"]); + await queryRunner.query(`DROP VIEW "alcs"."application_submission_status_search_view"`); + } + +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1731441606374-noi_status_update.ts b/services/apps/alcs/src/providers/typeorm/migrations/1731441606374-noi_status_update.ts new file mode 100644 index 0000000000..f233f3ab24 --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1731441606374-noi_status_update.ts @@ -0,0 +1,16 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class NoiStatusUpdate1731441606374 implements MigrationInterface { + name = 'NoiStatusUpdate1731441606374' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE VIEW "alcs"."noi_submission_status_search_view" AS SELECT "noi_sub"."file_number" AS "file_number", alcs.get_current_status_for_notice_of_intent_submission_by_uuid("noi_sub"."uuid") AS "status" FROM "alcs"."notice_of_intent_submission" "noi_sub" WHERE ( "noi_sub"."is_draft" IS NOT TRUE ) AND ( "noi_sub"."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","noi_submission_status_search_view","SELECT \"noi_sub\".\"file_number\" AS \"file_number\", alcs.get_current_status_for_notice_of_intent_submission_by_uuid(\"noi_sub\".\"uuid\") AS \"status\" FROM \"alcs\".\"notice_of_intent_submission\" \"noi_sub\" WHERE ( \"noi_sub\".\"is_draft\" IS NOT TRUE ) AND ( \"noi_sub\".\"audit_deleted_date_at\" IS NULL )"]); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DELETE FROM "alcs"."typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, ["VIEW","noi_submission_status_search_view","alcs"]); + await queryRunner.query(`DROP VIEW "alcs"."noi_submission_status_search_view"`); + } + +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1731444339841-remove_app_noi_status.ts b/services/apps/alcs/src/providers/typeorm/migrations/1731444339841-remove_app_noi_status.ts new file mode 100644 index 0000000000..c52857577a --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1731444339841-remove_app_noi_status.ts @@ -0,0 +1,64 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class RemoveAppNoiStatus1731444339841 implements MigrationInterface { + name = 'RemoveAppNoiStatus1731444339841' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP FUNCTION IF EXISTS alcs.get_nois(character varying[]);`); + await queryRunner.query(`DELETE FROM "alcs"."typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, ["VIEW","application_submission_search_view","alcs"]); + await queryRunner.query(`DROP VIEW "alcs"."application_submission_search_view"`); + await queryRunner.query(`DELETE FROM "alcs"."typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, ["VIEW","notice_of_intent_submission_search_view","alcs"]); + await queryRunner.query(`DROP VIEW "alcs"."notice_of_intent_submission_search_view"`); + await queryRunner.query(`CREATE VIEW "alcs"."application_submission_search_view" AS SELECT "app_sub"."uuid" AS "uuid", "app_sub"."applicant" AS "applicant", "app"."uuid" AS "application_uuid", "localGovernment"."name" AS "local_government_name", "app_sub"."file_number" AS "file_number", "app"."type_code" AS "application_type_code", "app"."date_submitted_to_alc" AS "date_submitted_to_alc", "app"."decision_date" AS "decision_date", "app"."region_code" AS "application_region_code", null AS "status" FROM "alcs"."application_submission" "app_sub" INNER JOIN "alcs"."application" "app" ON "app"."file_number" = "app_sub"."file_number" AND "app"."audit_deleted_date_at" IS NULL LEFT JOIN "alcs"."local_government" "localGovernment" ON "app_sub"."local_government_uuid" = "localGovernment"."uuid" AND "localGovernment"."audit_deleted_date_at" IS NULL WHERE ( "app_sub"."is_draft" IS NOT TRUE ) AND ( "app_sub"."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","application_submission_search_view","SELECT \"app_sub\".\"uuid\" AS \"uuid\", \"app_sub\".\"applicant\" AS \"applicant\", \"app\".\"uuid\" AS \"application_uuid\", \"localGovernment\".\"name\" AS \"local_government_name\", \"app_sub\".\"file_number\" AS \"file_number\", \"app\".\"type_code\" AS \"application_type_code\", \"app\".\"date_submitted_to_alc\" AS \"date_submitted_to_alc\", \"app\".\"decision_date\" AS \"decision_date\", \"app\".\"region_code\" AS \"application_region_code\", null AS \"status\" FROM \"alcs\".\"application_submission\" \"app_sub\" INNER JOIN \"alcs\".\"application\" \"app\" ON \"app\".\"file_number\" = \"app_sub\".\"file_number\" AND \"app\".\"audit_deleted_date_at\" IS NULL LEFT JOIN \"alcs\".\"local_government\" \"localGovernment\" ON \"app_sub\".\"local_government_uuid\" = \"localGovernment\".\"uuid\" AND \"localGovernment\".\"audit_deleted_date_at\" IS NULL WHERE ( \"app_sub\".\"is_draft\" IS NOT TRUE ) AND ( \"app_sub\".\"audit_deleted_date_at\" IS NULL )"]); + await queryRunner.query(`CREATE VIEW "alcs"."notice_of_intent_submission_search_view" AS SELECT "nois"."uuid" AS "uuid", "nois"."applicant" AS "applicant", "noi"."uuid" AS "notice_of_intent_uuid", "noticeOfIntentType"."audit_deleted_date_at" AS "noticeOfIntentType_audit_deleted_date_at", "noticeOfIntentType"."audit_created_at" AS "noticeOfIntentType_audit_created_at", "noticeOfIntentType"."audit_updated_at" AS "noticeOfIntentType_audit_updated_at", "noticeOfIntentType"."audit_created_by" AS "noticeOfIntentType_audit_created_by", "noticeOfIntentType"."audit_updated_by" AS "noticeOfIntentType_audit_updated_by", "noticeOfIntentType"."label" AS "noticeOfIntentType_label", "noticeOfIntentType"."code" AS "noticeOfIntentType_code", "noticeOfIntentType"."description" AS "noticeOfIntentType_description", "noticeOfIntentType"."short_label" AS "noticeOfIntentType_short_label", "noticeOfIntentType"."background_color" AS "noticeOfIntentType_background_color", "noticeOfIntentType"."text_color" AS "noticeOfIntentType_text_color", "noticeOfIntentType"."html_description" AS "noticeOfIntentType_html_description", "noticeOfIntentType"."portal_label" AS "noticeOfIntentType_portal_label", "noticeOfIntentType"."alc_fee_amount" AS "noticeOfIntentType_alc_fee_amount", "noticeOfIntentType"."government_fee_amount" AS "noticeOfIntentType_government_fee_amount", "localGovernment"."name" AS "local_government_name", "nois"."file_number" AS "file_number", "nois"."local_government_uuid" AS "local_government_uuid", "nois"."type_code" AS "notice_of_intent_type_code", "nois"."is_draft" AS "is_draft", "noi"."date_submitted_to_alc" AS "date_submitted_to_alc", "noi"."legacy_id" AS "legacy_id", "noi"."decision_date" AS "decision_date", "noi"."region_code" AS "notice_of_intent_region_code", null AS "status" FROM "alcs"."notice_of_intent_submission" "nois" INNER JOIN "alcs"."notice_of_intent" "noi" ON "noi"."file_number" = "nois"."file_number" AND "noi"."audit_deleted_date_at" IS NULL INNER JOIN "alcs"."notice_of_intent_type" "noticeOfIntentType" ON "nois"."type_code" = "noticeOfIntentType"."code" LEFT JOIN "alcs"."local_government" "localGovernment" ON "nois"."local_government_uuid" = "localGovernment"."uuid" WHERE "nois"."is_draft" IS NOT TRUE`); + await queryRunner.query(`INSERT INTO "alcs"."typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES (DEFAULT, $1, DEFAULT, $2, $3, $4)`, ["alcs","VIEW","notice_of_intent_submission_search_view","SELECT \"nois\".\"uuid\" AS \"uuid\", \"nois\".\"applicant\" AS \"applicant\", \"noi\".\"uuid\" AS \"notice_of_intent_uuid\", \"noticeOfIntentType\".\"audit_deleted_date_at\" AS \"noticeOfIntentType_audit_deleted_date_at\", \"noticeOfIntentType\".\"audit_created_at\" AS \"noticeOfIntentType_audit_created_at\", \"noticeOfIntentType\".\"audit_updated_at\" AS \"noticeOfIntentType_audit_updated_at\", \"noticeOfIntentType\".\"audit_created_by\" AS \"noticeOfIntentType_audit_created_by\", \"noticeOfIntentType\".\"audit_updated_by\" AS \"noticeOfIntentType_audit_updated_by\", \"noticeOfIntentType\".\"label\" AS \"noticeOfIntentType_label\", \"noticeOfIntentType\".\"code\" AS \"noticeOfIntentType_code\", \"noticeOfIntentType\".\"description\" AS \"noticeOfIntentType_description\", \"noticeOfIntentType\".\"short_label\" AS \"noticeOfIntentType_short_label\", \"noticeOfIntentType\".\"background_color\" AS \"noticeOfIntentType_background_color\", \"noticeOfIntentType\".\"text_color\" AS \"noticeOfIntentType_text_color\", \"noticeOfIntentType\".\"html_description\" AS \"noticeOfIntentType_html_description\", \"noticeOfIntentType\".\"portal_label\" AS \"noticeOfIntentType_portal_label\", \"noticeOfIntentType\".\"alc_fee_amount\" AS \"noticeOfIntentType_alc_fee_amount\", \"noticeOfIntentType\".\"government_fee_amount\" AS \"noticeOfIntentType_government_fee_amount\", \"localGovernment\".\"name\" AS \"local_government_name\", \"nois\".\"file_number\" AS \"file_number\", \"nois\".\"local_government_uuid\" AS \"local_government_uuid\", \"nois\".\"type_code\" AS \"notice_of_intent_type_code\", \"nois\".\"is_draft\" AS \"is_draft\", \"noi\".\"date_submitted_to_alc\" AS \"date_submitted_to_alc\", \"noi\".\"legacy_id\" AS \"legacy_id\", \"noi\".\"decision_date\" AS \"decision_date\", \"noi\".\"region_code\" AS \"notice_of_intent_region_code\", null AS \"status\" FROM \"alcs\".\"notice_of_intent_submission\" \"nois\" INNER JOIN \"alcs\".\"notice_of_intent\" \"noi\" ON \"noi\".\"file_number\" = \"nois\".\"file_number\" AND \"noi\".\"audit_deleted_date_at\" IS NULL INNER JOIN \"alcs\".\"notice_of_intent_type\" \"noticeOfIntentType\" ON \"nois\".\"type_code\" = \"noticeOfIntentType\".\"code\" LEFT JOIN \"alcs\".\"local_government\" \"localGovernment\" ON \"nois\".\"local_government_uuid\" = \"localGovernment\".\"uuid\" WHERE \"nois\".\"is_draft\" IS NOT TRUE"]); + await queryRunner.query(` + CREATE OR REPLACE FUNCTION alcs.get_nois( + file_numbers_arr character varying[]) + RETURNS SETOF alcs.notice_of_intent_submission_search_view + LANGUAGE 'sql' + COST 100 + VOLATILE PARALLEL UNSAFE + ROWS 1000 + + AS $BODY$ + + SELECT "noi".* FROM "alcs"."notice_of_intent_submission_search_view" AS "noi" + JOIN unnest(file_numbers_arr) AS fnum ON "noi"."file_number" = fnum + $BODY$; + ALTER FUNCTION alcs.get_nois(character varying[]) + OWNER TO postgres; + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP FUNCTION IF EXISTS alcs.get_nois(character varying[]);`); + await queryRunner.query(`DELETE FROM "alcs"."typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, ["VIEW","notice_of_intent_submission_search_view","alcs"]); + await queryRunner.query(`DROP VIEW "alcs"."notice_of_intent_submission_search_view"`); + await queryRunner.query(`DELETE FROM "alcs"."typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, ["VIEW","application_submission_search_view","alcs"]); + await queryRunner.query(`DROP VIEW "alcs"."application_submission_search_view"`); + await queryRunner.query(`CREATE VIEW "alcs"."notice_of_intent_submission_search_view" AS SELECT "nois"."uuid" AS "uuid", "nois"."applicant" AS "applicant", "noi"."uuid" AS "notice_of_intent_uuid", "noticeOfIntentType"."audit_deleted_date_at" AS "noticeOfIntentType_audit_deleted_date_at", "noticeOfIntentType"."audit_created_at" AS "noticeOfIntentType_audit_created_at", "noticeOfIntentType"."audit_updated_at" AS "noticeOfIntentType_audit_updated_at", "noticeOfIntentType"."audit_created_by" AS "noticeOfIntentType_audit_created_by", "noticeOfIntentType"."audit_updated_by" AS "noticeOfIntentType_audit_updated_by", "noticeOfIntentType"."label" AS "noticeOfIntentType_label", "noticeOfIntentType"."code" AS "noticeOfIntentType_code", "noticeOfIntentType"."description" AS "noticeOfIntentType_description", "noticeOfIntentType"."short_label" AS "noticeOfIntentType_short_label", "noticeOfIntentType"."background_color" AS "noticeOfIntentType_background_color", "noticeOfIntentType"."text_color" AS "noticeOfIntentType_text_color", "noticeOfIntentType"."html_description" AS "noticeOfIntentType_html_description", "noticeOfIntentType"."portal_label" AS "noticeOfIntentType_portal_label", "noticeOfIntentType"."alc_fee_amount" AS "noticeOfIntentType_alc_fee_amount", "noticeOfIntentType"."government_fee_amount" AS "noticeOfIntentType_government_fee_amount", "localGovernment"."name" AS "local_government_name", "nois"."file_number" AS "file_number", "nois"."local_government_uuid" AS "local_government_uuid", "nois"."type_code" AS "notice_of_intent_type_code", "nois"."is_draft" AS "is_draft", "noi"."date_submitted_to_alc" AS "date_submitted_to_alc", "noi"."legacy_id" AS "legacy_id", "noi"."decision_date" AS "decision_date", "noi"."region_code" AS "notice_of_intent_region_code", alcs.get_current_status_for_notice_of_intent_submission_by_uuid("nois"."uuid") AS "status" FROM "alcs"."notice_of_intent_submission" "nois" INNER JOIN "alcs"."notice_of_intent" "noi" ON "noi"."file_number" = "nois"."file_number" AND "noi"."audit_deleted_date_at" IS NULL INNER JOIN "alcs"."notice_of_intent_type" "noticeOfIntentType" ON "nois"."type_code" = "noticeOfIntentType"."code" LEFT JOIN "alcs"."local_government" "localGovernment" ON "nois"."local_government_uuid" = "localGovernment"."uuid" WHERE "nois"."is_draft" IS NOT TRUE`); + await queryRunner.query(`INSERT INTO "alcs"."typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES (DEFAULT, $1, DEFAULT, $2, $3, $4)`, ["alcs","VIEW","notice_of_intent_submission_search_view","SELECT \"nois\".\"uuid\" AS \"uuid\", \"nois\".\"applicant\" AS \"applicant\", \"noi\".\"uuid\" AS \"notice_of_intent_uuid\", \"noticeOfIntentType\".\"audit_deleted_date_at\" AS \"noticeOfIntentType_audit_deleted_date_at\", \"noticeOfIntentType\".\"audit_created_at\" AS \"noticeOfIntentType_audit_created_at\", \"noticeOfIntentType\".\"audit_updated_at\" AS \"noticeOfIntentType_audit_updated_at\", \"noticeOfIntentType\".\"audit_created_by\" AS \"noticeOfIntentType_audit_created_by\", \"noticeOfIntentType\".\"audit_updated_by\" AS \"noticeOfIntentType_audit_updated_by\", \"noticeOfIntentType\".\"label\" AS \"noticeOfIntentType_label\", \"noticeOfIntentType\".\"code\" AS \"noticeOfIntentType_code\", \"noticeOfIntentType\".\"description\" AS \"noticeOfIntentType_description\", \"noticeOfIntentType\".\"short_label\" AS \"noticeOfIntentType_short_label\", \"noticeOfIntentType\".\"background_color\" AS \"noticeOfIntentType_background_color\", \"noticeOfIntentType\".\"text_color\" AS \"noticeOfIntentType_text_color\", \"noticeOfIntentType\".\"html_description\" AS \"noticeOfIntentType_html_description\", \"noticeOfIntentType\".\"portal_label\" AS \"noticeOfIntentType_portal_label\", \"noticeOfIntentType\".\"alc_fee_amount\" AS \"noticeOfIntentType_alc_fee_amount\", \"noticeOfIntentType\".\"government_fee_amount\" AS \"noticeOfIntentType_government_fee_amount\", \"localGovernment\".\"name\" AS \"local_government_name\", \"nois\".\"file_number\" AS \"file_number\", \"nois\".\"local_government_uuid\" AS \"local_government_uuid\", \"nois\".\"type_code\" AS \"notice_of_intent_type_code\", \"nois\".\"is_draft\" AS \"is_draft\", \"noi\".\"date_submitted_to_alc\" AS \"date_submitted_to_alc\", \"noi\".\"legacy_id\" AS \"legacy_id\", \"noi\".\"decision_date\" AS \"decision_date\", \"noi\".\"region_code\" AS \"notice_of_intent_region_code\", alcs.get_current_status_for_notice_of_intent_submission_by_uuid(\"nois\".\"uuid\") AS \"status\" FROM \"alcs\".\"notice_of_intent_submission\" \"nois\" INNER JOIN \"alcs\".\"notice_of_intent\" \"noi\" ON \"noi\".\"file_number\" = \"nois\".\"file_number\" AND \"noi\".\"audit_deleted_date_at\" IS NULL INNER JOIN \"alcs\".\"notice_of_intent_type\" \"noticeOfIntentType\" ON \"nois\".\"type_code\" = \"noticeOfIntentType\".\"code\" LEFT JOIN \"alcs\".\"local_government\" \"localGovernment\" ON \"nois\".\"local_government_uuid\" = \"localGovernment\".\"uuid\" WHERE \"nois\".\"is_draft\" IS NOT TRUE"]); + await queryRunner.query(`CREATE VIEW "alcs"."application_submission_search_view" AS SELECT "app_sub"."uuid" AS "uuid", "app_sub"."applicant" AS "applicant", "app"."uuid" AS "application_uuid", "localGovernment"."name" AS "local_government_name", "app_sub"."file_number" AS "file_number", "app"."type_code" AS "application_type_code", "app"."date_submitted_to_alc" AS "date_submitted_to_alc", "app"."decision_date" AS "decision_date", "app"."region_code" AS "application_region_code", alcs.get_current_status_for_application_submission_by_uuid("app_sub"."uuid") AS "status" FROM "alcs"."application_submission" "app_sub" INNER JOIN "alcs"."application" "app" ON "app"."file_number" = "app_sub"."file_number" AND "app"."audit_deleted_date_at" IS NULL LEFT JOIN "alcs"."local_government" "localGovernment" ON "app_sub"."local_government_uuid" = "localGovernment"."uuid" AND "localGovernment"."audit_deleted_date_at" IS NULL WHERE ( "app_sub"."is_draft" IS NOT TRUE ) AND ( "app_sub"."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","application_submission_search_view","SELECT \"app_sub\".\"uuid\" AS \"uuid\", \"app_sub\".\"applicant\" AS \"applicant\", \"app\".\"uuid\" AS \"application_uuid\", \"localGovernment\".\"name\" AS \"local_government_name\", \"app_sub\".\"file_number\" AS \"file_number\", \"app\".\"type_code\" AS \"application_type_code\", \"app\".\"date_submitted_to_alc\" AS \"date_submitted_to_alc\", \"app\".\"decision_date\" AS \"decision_date\", \"app\".\"region_code\" AS \"application_region_code\", alcs.get_current_status_for_application_submission_by_uuid(\"app_sub\".\"uuid\") AS \"status\" FROM \"alcs\".\"application_submission\" \"app_sub\" INNER JOIN \"alcs\".\"application\" \"app\" ON \"app\".\"file_number\" = \"app_sub\".\"file_number\" AND \"app\".\"audit_deleted_date_at\" IS NULL LEFT JOIN \"alcs\".\"local_government\" \"localGovernment\" ON \"app_sub\".\"local_government_uuid\" = \"localGovernment\".\"uuid\" AND \"localGovernment\".\"audit_deleted_date_at\" IS NULL WHERE ( \"app_sub\".\"is_draft\" IS NOT TRUE ) AND ( \"app_sub\".\"audit_deleted_date_at\" IS NULL )"]); + await queryRunner.query(` + CREATE OR REPLACE FUNCTION alcs.get_nois( + file_numbers_arr character varying[]) + RETURNS SETOF alcs.notice_of_intent_submission_search_view + LANGUAGE 'sql' + COST 100 + VOLATILE PARALLEL UNSAFE + ROWS 1000 + + AS $BODY$ + + SELECT "noi".* FROM "alcs"."notice_of_intent_submission_search_view" AS "noi" + JOIN unnest(file_numbers_arr) AS fnum ON "noi"."file_number" = fnum + $BODY$; + ALTER FUNCTION alcs.get_nois(character varying[]) + OWNER TO postgres; + `); + } + +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1731453091938-notification_status_changes.ts b/services/apps/alcs/src/providers/typeorm/migrations/1731453091938-notification_status_changes.ts new file mode 100644 index 0000000000..7978f73c73 --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1731453091938-notification_status_changes.ts @@ -0,0 +1,24 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class NotificationStatusChanges1731453091938 implements MigrationInterface { + name = 'NotificationStatusChanges1731453091938' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DELETE FROM "alcs"."typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, ["VIEW","notification_submission_search_view","alcs"]); + await queryRunner.query(`DROP VIEW "alcs"."notification_submission_search_view"`); + await queryRunner.query(`CREATE VIEW "alcs"."notification_submission_status_search_view" AS SELECT "not_sub"."file_number" AS "file_number", alcs.get_current_status_for_notification_submission_by_uuid("not_sub"."uuid") AS "status" FROM "alcs"."notification_submission" "not_sub" WHERE "not_sub"."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","notification_submission_status_search_view","SELECT \"not_sub\".\"file_number\" AS \"file_number\", alcs.get_current_status_for_notification_submission_by_uuid(\"not_sub\".\"uuid\") AS \"status\" FROM \"alcs\".\"notification_submission\" \"not_sub\" WHERE \"not_sub\".\"audit_deleted_date_at\" IS NULL"]); + await queryRunner.query(`CREATE VIEW "alcs"."notification_submission_search_view" AS SELECT "noti_sub"."uuid" AS "uuid", "noti_sub"."applicant" AS "applicant", "noti"."uuid" AS "notification_uuid", "notificationType"."audit_deleted_date_at" AS "notificationType_audit_deleted_date_at", "notificationType"."audit_created_at" AS "notificationType_audit_created_at", "notificationType"."audit_updated_at" AS "notificationType_audit_updated_at", "notificationType"."audit_created_by" AS "notificationType_audit_created_by", "notificationType"."audit_updated_by" AS "notificationType_audit_updated_by", "notificationType"."label" AS "notificationType_label", "notificationType"."code" AS "notificationType_code", "notificationType"."description" AS "notificationType_description", "notificationType"."short_label" AS "notificationType_short_label", "notificationType"."background_color" AS "notificationType_background_color", "notificationType"."text_color" AS "notificationType_text_color", "notificationType"."html_description" AS "notificationType_html_description", "notificationType"."portal_label" AS "notificationType_portal_label", "localGovernment"."name" AS "local_government_name", "noti_sub"."file_number" AS "file_number", "noti_sub"."local_government_uuid" AS "local_government_uuid", "noti"."type_code" AS "notification_type_code", "noti"."date_submitted_to_alc" AS "date_submitted_to_alc", "noti"."region_code" AS "notification_region_code", null AS "status" FROM "alcs"."notification_submission" "noti_sub" INNER JOIN "alcs"."notification" "noti" ON "noti"."file_number" = "noti_sub"."file_number" AND "noti"."audit_deleted_date_at" IS NULL INNER JOIN "alcs"."notification_type" "notificationType" ON "noti_sub"."type_code" = "notificationType"."code" LEFT JOIN "alcs"."local_government" "localGovernment" ON "noti"."local_government_uuid" = "localGovernment"."uuid"`); + await queryRunner.query(`INSERT INTO "alcs"."typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES (DEFAULT, $1, DEFAULT, $2, $3, $4)`, ["alcs","VIEW","notification_submission_search_view","SELECT \"noti_sub\".\"uuid\" AS \"uuid\", \"noti_sub\".\"applicant\" AS \"applicant\", \"noti\".\"uuid\" AS \"notification_uuid\", \"notificationType\".\"audit_deleted_date_at\" AS \"notificationType_audit_deleted_date_at\", \"notificationType\".\"audit_created_at\" AS \"notificationType_audit_created_at\", \"notificationType\".\"audit_updated_at\" AS \"notificationType_audit_updated_at\", \"notificationType\".\"audit_created_by\" AS \"notificationType_audit_created_by\", \"notificationType\".\"audit_updated_by\" AS \"notificationType_audit_updated_by\", \"notificationType\".\"label\" AS \"notificationType_label\", \"notificationType\".\"code\" AS \"notificationType_code\", \"notificationType\".\"description\" AS \"notificationType_description\", \"notificationType\".\"short_label\" AS \"notificationType_short_label\", \"notificationType\".\"background_color\" AS \"notificationType_background_color\", \"notificationType\".\"text_color\" AS \"notificationType_text_color\", \"notificationType\".\"html_description\" AS \"notificationType_html_description\", \"notificationType\".\"portal_label\" AS \"notificationType_portal_label\", \"localGovernment\".\"name\" AS \"local_government_name\", \"noti_sub\".\"file_number\" AS \"file_number\", \"noti_sub\".\"local_government_uuid\" AS \"local_government_uuid\", \"noti\".\"type_code\" AS \"notification_type_code\", \"noti\".\"date_submitted_to_alc\" AS \"date_submitted_to_alc\", \"noti\".\"region_code\" AS \"notification_region_code\", null AS \"status\" FROM \"alcs\".\"notification_submission\" \"noti_sub\" INNER JOIN \"alcs\".\"notification\" \"noti\" ON \"noti\".\"file_number\" = \"noti_sub\".\"file_number\" AND \"noti\".\"audit_deleted_date_at\" IS NULL INNER JOIN \"alcs\".\"notification_type\" \"notificationType\" ON \"noti_sub\".\"type_code\" = \"notificationType\".\"code\" LEFT JOIN \"alcs\".\"local_government\" \"localGovernment\" ON \"noti\".\"local_government_uuid\" = \"localGovernment\".\"uuid\""]); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DELETE FROM "alcs"."typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, ["VIEW","notification_submission_search_view","alcs"]); + await queryRunner.query(`DROP VIEW "alcs"."notification_submission_search_view"`); + await queryRunner.query(`DELETE FROM "alcs"."typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, ["VIEW","notification_submission_status_search_view","alcs"]); + await queryRunner.query(`DROP VIEW "alcs"."notification_submission_status_search_view"`); + await queryRunner.query(`CREATE VIEW "alcs"."notification_submission_search_view" AS SELECT "noti_sub"."uuid" AS "uuid", "noti_sub"."applicant" AS "applicant", "noti"."uuid" AS "notification_uuid", "notificationType"."audit_deleted_date_at" AS "notificationType_audit_deleted_date_at", "notificationType"."audit_created_at" AS "notificationType_audit_created_at", "notificationType"."audit_updated_at" AS "notificationType_audit_updated_at", "notificationType"."audit_created_by" AS "notificationType_audit_created_by", "notificationType"."audit_updated_by" AS "notificationType_audit_updated_by", "notificationType"."label" AS "notificationType_label", "notificationType"."code" AS "notificationType_code", "notificationType"."description" AS "notificationType_description", "notificationType"."short_label" AS "notificationType_short_label", "notificationType"."background_color" AS "notificationType_background_color", "notificationType"."text_color" AS "notificationType_text_color", "notificationType"."html_description" AS "notificationType_html_description", "notificationType"."portal_label" AS "notificationType_portal_label", "localGovernment"."name" AS "local_government_name", "noti_sub"."file_number" AS "file_number", "noti_sub"."local_government_uuid" AS "local_government_uuid", "noti"."type_code" AS "notification_type_code", "noti"."date_submitted_to_alc" AS "date_submitted_to_alc", "noti"."region_code" AS "notification_region_code", alcs.get_current_status_for_notification_submission_by_uuid("noti_sub"."uuid") AS "status" FROM "alcs"."notification_submission" "noti_sub" INNER JOIN "alcs"."notification" "noti" ON "noti"."file_number" = "noti_sub"."file_number" AND "noti"."audit_deleted_date_at" IS NULL INNER JOIN "alcs"."notification_type" "notificationType" ON "noti_sub"."type_code" = "notificationType"."code" LEFT JOIN "alcs"."local_government" "localGovernment" ON "noti"."local_government_uuid" = "localGovernment"."uuid"`); + await queryRunner.query(`INSERT INTO "alcs"."typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES (DEFAULT, $1, DEFAULT, $2, $3, $4)`, ["alcs","VIEW","notification_submission_search_view","SELECT \"noti_sub\".\"uuid\" AS \"uuid\", \"noti_sub\".\"applicant\" AS \"applicant\", \"noti\".\"uuid\" AS \"notification_uuid\", \"notificationType\".\"audit_deleted_date_at\" AS \"notificationType_audit_deleted_date_at\", \"notificationType\".\"audit_created_at\" AS \"notificationType_audit_created_at\", \"notificationType\".\"audit_updated_at\" AS \"notificationType_audit_updated_at\", \"notificationType\".\"audit_created_by\" AS \"notificationType_audit_created_by\", \"notificationType\".\"audit_updated_by\" AS \"notificationType_audit_updated_by\", \"notificationType\".\"label\" AS \"notificationType_label\", \"notificationType\".\"code\" AS \"notificationType_code\", \"notificationType\".\"description\" AS \"notificationType_description\", \"notificationType\".\"short_label\" AS \"notificationType_short_label\", \"notificationType\".\"background_color\" AS \"notificationType_background_color\", \"notificationType\".\"text_color\" AS \"notificationType_text_color\", \"notificationType\".\"html_description\" AS \"notificationType_html_description\", \"notificationType\".\"portal_label\" AS \"notificationType_portal_label\", \"localGovernment\".\"name\" AS \"local_government_name\", \"noti_sub\".\"file_number\" AS \"file_number\", \"noti_sub\".\"local_government_uuid\" AS \"local_government_uuid\", \"noti\".\"type_code\" AS \"notification_type_code\", \"noti\".\"date_submitted_to_alc\" AS \"date_submitted_to_alc\", \"noti\".\"region_code\" AS \"notification_region_code\", alcs.get_current_status_for_notification_submission_by_uuid(\"noti_sub\".\"uuid\") AS \"status\" FROM \"alcs\".\"notification_submission\" \"noti_sub\" INNER JOIN \"alcs\".\"notification\" \"noti\" ON \"noti\".\"file_number\" = \"noti_sub\".\"file_number\" AND \"noti\".\"audit_deleted_date_at\" IS NULL INNER JOIN \"alcs\".\"notification_type\" \"notificationType\" ON \"noti_sub\".\"type_code\" = \"notificationType\".\"code\" LEFT JOIN \"alcs\".\"local_government\" \"localGovernment\" ON \"noti\".\"local_government_uuid\" = \"localGovernment\".\"uuid\""]); + } + +}