-
Search Results
+
Search Results
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 0c83cc7057..9848c8f051 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
@@ -22,6 +22,7 @@ import { ToastService } from '../../../services/toast/toast.service';
import { MOBILE_BREAKPOINT } from '../../../shared/utils/breakpoints';
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',
@@ -193,6 +194,7 @@ export class PublicSearchComponent implements OnInit, OnDestroy {
sessionStorage.setItem(SEARCH_SESSION_STORAGE_KEY, searchDto);
this.isLoading = true;
+ scrollToElement({ id: `searchResults`, center: false });
const result = await this.searchService.search(searchParams);
this.searchResultsHidden = false;
this.isLoading = false;
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 b7b69eab01..e5dd88c9ae 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,7 +51,9 @@ export class ApplicationDocumentService {
async openFile(fileUuid: string) {
try {
- return await firstValueFrom(this.httpClient.get<{ url: string }>(`${this.serviceUrl}/${fileUuid}/open`));
+ return await firstValueFrom(
+ this.httpClient.get<{ url: string; fileName: 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/authentication/authentication.service.ts b/portal-frontend/src/app/services/authentication/authentication.service.ts
index cb5f636e2c..8b7c9dc393 100644
--- a/portal-frontend/src/app/services/authentication/authentication.service.ts
+++ b/portal-frontend/src/app/services/authentication/authentication.service.ts
@@ -79,13 +79,12 @@ export class AuthenticationService {
async refreshTokens(redirect = true) {
if (this.refreshToken) {
- if (this.expires && this.expires < Date.now()) {
+ if (this.refreshExpires && this.refreshExpires < Date.now()) {
if (redirect) {
await this.router.navigateByUrl('/login');
}
return;
}
-
const newTokens = await this.getNewTokens(this.refreshToken);
await this.setTokens(newTokens.token, newTokens.refresh_token);
}
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 40461754d6..1860044eb3 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,7 +51,9 @@ export class NoticeOfIntentDocumentService {
async openFile(fileUuid: string) {
try {
- return await firstValueFrom(this.httpClient.get<{ url: string }>(`${this.serviceUrl}/${fileUuid}/open`));
+ return await firstValueFrom(
+ this.httpClient.get<{ url: string; fileName: 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 698ecbae19..e94af3b6d5 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,7 +51,9 @@ export class NotificationDocumentService {
async openFile(fileUuid: string) {
try {
- return await firstValueFrom(this.httpClient.get<{ url: string }>(`${this.serviceUrl}/${fileUuid}/open`));
+ return await firstValueFrom(
+ this.httpClient.get<{ url: string; fileName: 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/owner-dialogs/parcel-owners/parcel-owners.component.ts b/portal-frontend/src/app/shared/owner-dialogs/parcel-owners/parcel-owners.component.ts
index 8cadb3bc21..bddf22ec3c 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,6 +11,7 @@ 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';
@Component({
selector: 'app-parcel-owners[owners][fileId][submissionUuid][ownerService]',
@@ -107,7 +108,7 @@ export class ParcelOwnersComponent {
async onOpenFile(uuid: string) {
const res = await this.documentService.openFile(uuid);
if (res) {
- window.open(res.url, '_blank');
+ openFileIframe(res);
}
}
}
diff --git a/portal-frontend/src/app/shared/utils/file.ts b/portal-frontend/src/app/shared/utils/file.ts
index e13fc14fcc..5aaee70e01 100644
--- a/portal-frontend/src/app/shared/utils/file.ts
+++ b/portal-frontend/src/app/shared/utils/file.ts
@@ -11,3 +11,23 @@ export const openPdfFile = (fileName: string, data: any) => {
}
downloadLink.click();
};
+
+export const openFileIframe = (data: { url: string; fileName: string }) => {
+ const newWindow = window.open('', '_blank');
+ if (newWindow) {
+ newWindow.document.title = data.fileName;
+
+ const object = newWindow.document.createElement('object');
+ object.data = 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.height = '100%';
+ newWindow.document.body.style.width = '100%';
+ newWindow.document.body.style.margin = '0';
+ newWindow.document.body.style.overflow = 'hidden';
+ }
+};
diff --git a/portal-frontend/test/README.md b/portal-frontend/test/README.md
deleted file mode 100644
index 460e39b2c3..0000000000
--- a/portal-frontend/test/README.md
+++ /dev/null
@@ -1,13 +0,0 @@
-!THIS IS NOT A FULL AUTOMATION. IT IS JUST TO REDUCE AMOUNT OF MANUAL CLICKS FOR DEVELOPERS WHEN THEY ARE WORKING ON LOCAL ENVIRONMENTS!
-
-The Automation is implemented using the playwright. https://playwright.dev/
-
-Note: make sure you do not commit any credentials to the repo
-
-How to run:
-Navigate to portal/test folder and from there perform following commands
-
-```bash
-$ npm i
-$ npx playwright test --headed --project=chromium
-```
diff --git a/portal-frontend/test/config.ts b/portal-frontend/test/config.ts
deleted file mode 100644
index 01d701114d..0000000000
--- a/portal-frontend/test/config.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export const baseUrl = 'http://localhost:4201/login';
-export const userName = 'MekhtiHuseinov';
-export const password = 'UQ-ZCDHi.FiF6c';
-export const filePathToUseAsUpload = '/Users/mekhti/Desktop/test_upload_1.png';
diff --git a/portal-frontend/test/github.workflow.playwright.yml.template b/portal-frontend/test/github.workflow.playwright.yml.template
deleted file mode 100644
index 041160cce3..0000000000
--- a/portal-frontend/test/github.workflow.playwright.yml.template
+++ /dev/null
@@ -1,27 +0,0 @@
-name: Playwright Tests
-on:
- push:
- branches: [ main, master ]
- pull_request:
- branches: [ main, master ]
-jobs:
- test:
- timeout-minutes: 60
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-node@v3
- with:
- node-version: 16
- - name: Install dependencies
- run: npm ci
- - name: Install Playwright Browsers
- run: npx playwright install --with-deps
- - name: Run Playwright tests
- run: npx playwright test
- - uses: actions/upload-artifact@v3
- if: always()
- with:
- name: playwright-report
- path: playwright-report/
- retention-days: 30
diff --git a/portal-frontend/test/package-lock.json b/portal-frontend/test/package-lock.json
deleted file mode 100644
index 8a2b3fcae7..0000000000
--- a/portal-frontend/test/package-lock.json
+++ /dev/null
@@ -1,99 +0,0 @@
-{
- "name": "alcs",
- "version": "1.0.0",
- "lockfileVersion": 2,
- "requires": true,
- "packages": {
- "": {
- "name": "alcs",
- "version": "1.0.0",
- "license": "ISC",
- "devDependencies": {
- "@playwright/test": "^1.32.0"
- }
- },
- "node_modules/@playwright/test": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.32.0.tgz",
- "integrity": "sha512-zOdGloaF0jeec7hqoLqM5S3L2rR4WxMJs6lgiAeR70JlH7Ml54ZPoIIf3X7cvnKde3Q9jJ/gaxkFh8fYI9s1rg==",
- "dev": true,
- "dependencies": {
- "@types/node": "*",
- "playwright-core": "1.32.0"
- },
- "bin": {
- "playwright": "cli.js"
- },
- "engines": {
- "node": ">=14"
- },
- "optionalDependencies": {
- "fsevents": "2.3.2"
- }
- },
- "node_modules/@types/node": {
- "version": "18.15.7",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.7.tgz",
- "integrity": "sha512-LFmUbFunqmBn26wJZgZPYZPrDR1RwGOu2v79Mgcka1ndO6V0/cwjivPTc4yoK6n9kmw4/ls1r8cLrvh2iMibFA==",
- "dev": true
- },
- "node_modules/fsevents": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
- "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
- "dev": true,
- "hasInstallScript": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/playwright-core": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.32.0.tgz",
- "integrity": "sha512-Z9Ij17X5Z3bjpp6XKujGBp9Gv4eViESac9aDmwgQFUEJBW0K80T21m/Z+XJQlu4cNsvPygw33b6V1Va6Bda5zQ==",
- "dev": true,
- "bin": {
- "playwright": "cli.js"
- },
- "engines": {
- "node": ">=14"
- }
- }
- },
- "dependencies": {
- "@playwright/test": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.32.0.tgz",
- "integrity": "sha512-zOdGloaF0jeec7hqoLqM5S3L2rR4WxMJs6lgiAeR70JlH7Ml54ZPoIIf3X7cvnKde3Q9jJ/gaxkFh8fYI9s1rg==",
- "dev": true,
- "requires": {
- "@types/node": "*",
- "fsevents": "2.3.2",
- "playwright-core": "1.32.0"
- }
- },
- "@types/node": {
- "version": "18.15.7",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.7.tgz",
- "integrity": "sha512-LFmUbFunqmBn26wJZgZPYZPrDR1RwGOu2v79Mgcka1ndO6V0/cwjivPTc4yoK6n9kmw4/ls1r8cLrvh2iMibFA==",
- "dev": true
- },
- "fsevents": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
- "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
- "dev": true,
- "optional": true
- },
- "playwright-core": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.32.0.tgz",
- "integrity": "sha512-Z9Ij17X5Z3bjpp6XKujGBp9Gv4eViESac9aDmwgQFUEJBW0K80T21m/Z+XJQlu4cNsvPygw33b6V1Va6Bda5zQ==",
- "dev": true
- }
- }
-}
diff --git a/portal-frontend/test/tests/submissions/NFU_submission_creation.spec.ts b/portal-frontend/test/tests/submissions/NFU_submission_creation.spec.ts
deleted file mode 100644
index 79b6c68164..0000000000
--- a/portal-frontend/test/tests/submissions/NFU_submission_creation.spec.ts
+++ /dev/null
@@ -1,154 +0,0 @@
-import { test } from '@playwright/test';
-import { baseUrl, filePathToUseAsUpload, password, userName } from '../../config';
-
-test('test', async ({ page }) => {
- await page.goto(baseUrl);
- await page.getByRole('button', { name: 'Login with BCeID' }).click();
- await page.locator('#user').click();
- await page.locator('#user').fill(userName);
- await page.getByLabel('Password').click();
- await page.getByLabel('Password').fill(password);
- await page.getByRole('button', { name: 'Continue' }).click();
- await page.getByRole('button', { name: 'Continue to inbox' }).click();
- await page.getByRole('button', { name: '+ Create New' }).click();
- await page.getByRole('dialog', { name: 'Create New' }).getByText('Application').click();
- await page.getByRole('button', { name: 'Next' }).click();
- await page.getByText('Non-Farm Uses within the ALR').click();
- await page.getByRole('button', { name: 'create' }).click();
- await page
- .locator('section')
- .filter({ hasText: 'Application Edit Application Download PDF 1. Identify Parcel(s) Under Applicatio' })
- .getByRole('button', { name: 'Edit Application' })
- .click();
- await page.getByRole('button', { name: 'Fee Simple' }).click();
- await page.getByPlaceholder('Type legal description').click();
- await page.getByPlaceholder('Type legal description').fill('some description here');
- await page.getByPlaceholder('Type parcel size').click();
- await page.getByPlaceholder('Type parcel size').fill('11');
- await page.getByPlaceholder('Type PID').click();
- await page.getByPlaceholder('Type PID').fill('111-111-111');
- await page.getByPlaceholder('YYYY-MMM-DD').click();
- await page.getByPlaceholder('YYYY-MMM-DD').fill('2023-Mar-12');
- await page.getByRole('button', { name: 'Yes' }).click();
- await page.getByPlaceholder('Type owner name').click();
- await page
- .getByRole('option', { name: 'No owner matching search Add new owner' })
- .getByRole('button', { name: 'Add new owner' })
- .click();
- await page.getByRole('button', { name: 'Individual' }).click();
- await page.getByPlaceholder('Enter First Name').click();
- await page.getByPlaceholder('Enter First Name').fill('Test');
- await page.getByPlaceholder('Enter Last Name').click();
- await page.getByPlaceholder('Enter Last Name').fill('Individual');
- await page.getByPlaceholder('(555) 555-5555').click();
- await page.getByPlaceholder('(555) 555-5555').fill('(111) 111-11111');
- await page.getByPlaceholder('Enter Email').click();
- await page.getByPlaceholder('Enter Email').fill('11@11');
- await page.getByRole('button', { name: 'Add' }).click();
- await page.setInputFiles('input.file-input', filePathToUseAsUpload);
- await page
- .getByLabel(
- 'I confirm that the owner information provided above matches the current Certificate of Title. Mismatched information can cause significant delays to processing time.'
- )
- .check();
- await page.getByRole('button', { name: 'Add another parcel to the application' }).click();
-
- await page
- .getByRole('region', { name: 'Parcel #2 Details & Owner Information' })
- .getByRole('button', { name: 'Crown' })
- .click();
- await page.getByRole('button', { name: 'Crown' }).click();
- await page.getByRole('textbox', { name: 'Type legal description' }).click();
- await page.getByRole('textbox', { name: 'Type legal description' }).fill('another description');
- await page.getByRole('textbox', { name: 'Type parcel size' }).click();
- await page.getByRole('textbox', { name: 'Type parcel size' }).fill('22');
- await page.getByRole('button', { name: 'No', exact: true }).click();
- await page.getByLabel('Provincial Crown').check();
- await page.getByRole('button', { name: 'Add new government contact' }).click();
- await page.getByPlaceholder('Type ministry or department name').click();
- await page.getByPlaceholder('Type ministry or department name').fill('test ministry');
- await page.getByPlaceholder('Enter First Name').click();
- await page.getByPlaceholder('Enter First Name').fill('Ministry');
- await page.getByPlaceholder('Enter Last Name').click();
- await page.getByPlaceholder('Enter Last Name').fill('test');
- await page.getByPlaceholder('(555) 555-5555').click();
- await page.getByPlaceholder('(555) 555-5555').fill('(333) 333-33333');
- await page.getByPlaceholder('Enter Email').click();
- await page.getByPlaceholder('Enter Email').fill('33@33');
- await page.getByRole('button', { name: 'Add' }).click();
- await page
- .getByRole('checkbox', {
- name: 'I confirm that the owner information provided above matches the current Certificate of Title. Mismatched information can cause significant delays to processing time.',
- })
- .last()
- .check();
- await page.getByRole('button', { name: 'Next Step' }).click();
- await page.locator('#mat-button-toggle-16-button').click();
- await page.getByRole('button', { name: 'Fee Simple' }).click();
- await page.getByPlaceholder('Type legal description').click();
- await page.getByPlaceholder('Type legal description').fill('other parcels description');
- await page.getByPlaceholder('Type parcel size').click();
- await page.getByPlaceholder('Type parcel size').fill('45');
- await page.getByPlaceholder('Type PID').click();
- await page.getByPlaceholder('Type PID').fill('444-444-444');
- await page.getByRole('region', { name: 'Parcel A Details' }).getByRole('button', { name: 'No' }).click();
- await page.getByPlaceholder('Type owner name').click();
- await page.getByRole('option', { name: 'Test Individual Add' }).getByText('Test Individual').click();
- await page.getByRole('button', { name: 'Next Step' }).click();
- await page.waitForTimeout(1000);
- await page.getByRole('button', { name: 'Make Primary Contact' }).first().click();
- await page.setInputFiles('input.file-input', filePathToUseAsUpload);
- await page.getByRole('button', { name: 'Next Step' }).click();
- await page.getByPlaceholder('Type government').click();
- await page.getByPlaceholder('Type government').fill('Peace');
- await page.getByText('Peace River Regional District').click();
- await page.getByRole('button', { name: 'Next Step' }).click();
- await page.getByLabel('Describe all agriculture that currently takes place on the parcel(s).').click();
- await page.getByLabel('Describe all agriculture that currently takes place on the parcel(s).').fill('5');
- await page.getByLabel('Describe all agricultural improvements made to the parcel(s).').click();
- await page.getByLabel('Describe all agricultural improvements made to the parcel(s).').fill('5');
- await page.getByLabel('Describe all other uses that currently take place on the parcel(s).').click();
- await page.getByLabel('Describe all other uses that currently take place on the parcel(s).').fill('5');
- await page.locator('#northLandUseType svg').click();
- await page.getByText('Agricultural / Farm').click();
- await page.locator('#northLandUseTypeDescription').click();
- await page.locator('#northLandUseTypeDescription').fill('north farm');
- await page.locator('#eastLandUseType svg').click();
- await page.getByText('Civic / Institutional').click();
- await page.locator('#eastLandUseTypeDescription').click();
- await page.locator('#eastLandUseTypeDescription').fill('civic east');
- await page.locator('#southLandUseType svg').click();
- await page.getByText('Commercial / Retail').click();
- await page.locator('#southLandUseTypeDescription').click();
- await page.locator('#southLandUseTypeDescription').fill('commercial');
- await page.getByRole('combobox', { name: 'Main Land Use Type' }).locator('svg').click();
- await page.getByText('Industrial').click();
- await page.locator('#westLandUseTypeDescription').click();
- await page.locator('#westLandUseTypeDescription').fill('industrial west');
- await page.getByRole('button', { name: 'Next Step' }).click();
- await page.getByPlaceholder('Type size in hectares').click();
- await page.getByPlaceholder('Type size in hectares').fill('6');
- await page.getByLabel('What is the purpose of the proposal?').click();
- await page.getByLabel('What is the purpose of the proposal?').fill('no purpose');
- await page.getByLabel('Could this proposal be accommodated on lands outside of the ALR?').click();
- await page.getByLabel('Could this proposal be accommodated on lands outside of the ALR?').fill('nope');
- await page.getByLabel('Does the proposal support agriculture in the short or long term?').click();
- await page.getByLabel('Does the proposal support agriculture in the short or long term?').fill('nope');
- await page.getByRole('button', { name: 'Yes' }).click();
- await page.getByLabel('Describe the type and amount of fill proposed to be placed.').click();
- await page.getByLabel('Describe the type and amount of fill proposed to be placed.').fill('6');
- await page.getByLabel('Briefly describe the origin and quality of fill.').click();
- await page.getByLabel('Briefly describe the origin and quality of fill.').fill('very good');
- await page.getByPlaceholder('Type fill depth').click();
- await page.getByPlaceholder('Type fill depth').fill('6');
- await page.getByPlaceholder('Type placement area').click();
- await page.getByPlaceholder('Type placement area').fill('6');
- await page.getByPlaceholder('Type volume').click();
- await page.getByPlaceholder('Type volume').fill('6');
- await page.getByPlaceholder('Type length as a decimal number').click();
- await page.getByPlaceholder('Type length as a decimal number').fill('6');
- await page.getByText('UnitSelect one').click();
- await page.getByText('Months', { exact: true }).click();
- await page.getByRole('button', { name: 'Next Step' }).click();
- await page.getByRole('button', { name: 'Next Step' }).click();
-});
diff --git a/portal-frontend/test/tests/submissions/TUR_submission_creation.spec.ts b/portal-frontend/test/tests/submissions/TUR_submission_creation.spec.ts
deleted file mode 100644
index 013ef67ba4..0000000000
--- a/portal-frontend/test/tests/submissions/TUR_submission_creation.spec.ts
+++ /dev/null
@@ -1,152 +0,0 @@
-import { test } from '@playwright/test';
-import { delay } from 'rxjs';
-import { baseUrl, filePathToUseAsUpload, password, userName } from '../../config';
-
-test('test', async ({ page }) => {
- await page.goto(baseUrl);
- await page.getByRole('button', { name: 'Login with BCeID' }).click();
- await page.locator('#user').click();
- await page.locator('#user').fill(userName);
- await page.getByLabel('Password').click();
- await page.getByLabel('Password').fill(password);
- await page.getByRole('button', { name: 'Continue' }).click();
- await page.getByRole('button', { name: 'Continue to inbox' }).click();
- await page.getByRole('button', { name: '+ Create New' }).click();
- await page.getByRole('dialog', { name: 'Create New' }).getByText('Application').click();
- await page.getByRole('button', { name: 'Next' }).click();
- await page.getByText('Transportation, Utility, or Recreational Trail Uses within the ALR').click();
- await page.getByRole('button', { name: 'create' }).click();
- // await page.locator('.btns-wrapper > button:nth-child(1)').click();
-
- await page.getByRole('button', { name: 'Fee Simple' }).click();
- await page.getByPlaceholder('Type legal description').click();
- await page.getByPlaceholder('Type legal description').fill('1');
- await page
- .locator(
- 'div:nth-child(3) > .mat-mdc-form-field > .mat-mdc-text-field-wrapper > .mat-mdc-form-field-flex > .mat-mdc-form-field-infix'
- )
- .click();
- await page.getByPlaceholder('Type parcel size').fill('1');
- await page.getByPlaceholder('Type PID').click();
- await page.getByPlaceholder('Type PID').fill('111-111-111');
- await page.getByRole('button', { name: 'Open calendar' }).click();
- await page.locator('td:nth-child(3) > .mat-calendar-body-cell').first().click();
- await page.getByRole('button', { name: 'March 23, 2023' }).click();
- await page.getByRole('button', { name: 'Yes' }).click();
- await page.setInputFiles('input.file-input', filePathToUseAsUpload);
- await page
- .locator(
- '.container > .mat-mdc-form-field > .mat-mdc-text-field-wrapper > .mat-mdc-form-field-flex > .mat-mdc-form-field-infix'
- )
- .click();
- await page
- .getByRole('option', { name: 'No owner matching search Add new owner' })
- .getByRole('button', { name: 'Add new owner' })
- .click();
- await page
- .locator(
- '.ng-untouched > .form-row > div:nth-child(2) > .mat-mdc-form-field > .mat-mdc-text-field-wrapper > .mat-mdc-form-field-flex > .mat-mdc-form-field-infix'
- )
- .click();
- await page.getByPlaceholder('Enter First Name').fill('1');
- await page.getByPlaceholder('Enter Last Name').click();
- await page.getByPlaceholder('Enter Last Name').fill('1');
- await page
- .locator(
- '.mat-mdc-dialog-content > form > .form-row > div:nth-child(4) > .mat-mdc-form-field > .mat-mdc-text-field-wrapper > .mat-mdc-form-field-flex > .mat-mdc-form-field-infix'
- )
- .click();
- await page.getByPlaceholder('(555) 555-5555').fill('(111) 111-11111');
- await page.getByPlaceholder('Enter Email').click();
- await page.getByPlaceholder('Enter Email').fill('11@11');
- await page.getByRole('button', { name: 'Add' }).click();
- await page
- .getByLabel(
- 'I confirm that the owner information provided above matches the current Certificate of Title. Mismatched information can cause significant delays to processing time.'
- )
- .check();
- await page.getByRole('button', { name: 'Next Step' }).click();
- await page.locator('#mat-button-toggle-11-button').click();
- await page.getByText('Primary Contact').click();
- await delay(1000);
- await page.locator('.contacts > button').first().click();
- await page.getByText('4').first().click();
- await page.getByPlaceholder('Type government').click();
- await page.getByPlaceholder('Type government').fill('peace');
- await page.getByText('Peace River Regional District').click();
- await page.getByText('5').first().click();
- await page.getByLabel('Describe all agriculture that currently takes place on the parcel(s).').click();
- await page.getByLabel('Describe all agriculture that currently takes place on the parcel(s).').fill('1');
- await page
- .locator(
- 'div:nth-child(2) > .mat-mdc-form-field > .mat-mdc-text-field-wrapper > .mat-mdc-form-field-flex > .mat-mdc-form-field-infix'
- )
- .first()
- .click();
- await page.getByLabel('Describe all agricultural improvements made to the parcel(s).').fill('2');
- await page.getByLabel('Describe all other uses that currently take place on the parcel(s).').click();
- await page.getByLabel('Describe all other uses that currently take place on the parcel(s).').fill('3');
- await page
- .locator(
- '.land-use-type > .mat-mdc-form-field > .mat-mdc-text-field-wrapper > .mat-mdc-form-field-flex > .mat-mdc-form-field-infix'
- )
- .first()
- .click();
- await page.getByRole('option', { name: 'Other' }).click();
- await page.locator('#mat-select-value-5').getByText('Main Land Use Type').click();
- await page.getByText('Industrial').first().click();
- await page.locator('#mat-select-value-7').click();
- await page.getByText('Civic / Institutional').first().click();
- await page.locator('#mat-select-value-9').click();
- await page.getByRole('option', { name: 'Agricultural / Farm' }).first().click();
- await page.locator('#northLandUseTypeDescription').click();
- await page.locator('#northLandUseTypeDescription').fill('4');
- await page.locator('#eastLandUseTypeDescription').click();
- await page.locator('#eastLandUseTypeDescription').fill('5');
- await page
- .locator(
- 'div:nth-child(3) > .land-use-type-wrapper > .full-width-input > .mat-mdc-form-field > .mat-mdc-text-field-wrapper > .mat-mdc-form-field-flex > .mat-mdc-form-field-infix'
- )
- .click();
- await page.locator('#southLandUseTypeDescription').click();
- await page.locator('#southLandUseTypeDescription').fill('5');
- await page
- .locator(
- 'div:nth-child(4) > .land-use-type-wrapper > .full-width-input > .mat-mdc-form-field > .mat-mdc-text-field-wrapper > .mat-mdc-form-field-flex > .mat-mdc-form-field-infix'
- )
- .click();
- await page.locator('#westLandUseTypeDescription').fill('5');
- await page.getByRole('button', { name: 'Next Step' }).click();
- await page.getByLabel('What is the purpose of the proposal?').click();
- await page.getByLabel('What is the purpose of the proposal?').fill('6');
- await page
- .getByLabel(
- 'Specify any agricultural activities such as livestock operations, greenhouses or horticultural activities in proximity to the proposal.'
- )
- .click();
- await page
- .getByLabel(
- 'Specify any agricultural activities such as livestock operations, greenhouses or horticultural activities in proximity to the proposal.'
- )
- .fill('6');
- await page
- .getByLabel('What steps will you take to reduce potential negative impacts on surrounding agricultural lands?')
- .click();
- await page
- .getByLabel('What steps will you take to reduce potential negative impacts on surrounding agricultural lands?')
- .fill('6');
- await page.getByRole('textbox', { name: 'Type comment' }).click();
- await page.getByRole('textbox', { name: 'Type comment' }).fill('6');
- await page.getByPlaceholder('Type total area').click();
- await page.getByPlaceholder('Type total area').fill('6');
- await page.getByLabel('I confirm that all affected property owners with land in the ALR have been notified.').check();
- await page.setInputFiles('#proof-of-serving > input', filePathToUseAsUpload);
-
- await page.setInputFiles('#proposal-map > input', filePathToUseAsUpload);
- await page.getByRole('button', { name: 'Next Step' }).click();
- await page
- .locator('div')
- .filter({ hasText: /^Review & Submit$/ })
- .click();
- await page.getByRole('button', { name: 'Save and Exit' }).click();
-});
diff --git a/services/apps/alcs/src/alcs/admin/unarchive-card/unarchive-card.service.spec.ts b/services/apps/alcs/src/alcs/admin/unarchive-card/unarchive-card.service.spec.ts
index c274ff5526..f376177820 100644
--- a/services/apps/alcs/src/alcs/admin/unarchive-card/unarchive-card.service.spec.ts
+++ b/services/apps/alcs/src/alcs/admin/unarchive-card/unarchive-card.service.spec.ts
@@ -1,15 +1,15 @@
-import { classes } from 'automapper-classes';
-import { AutomapperModule } from 'automapper-nestjs';
import { createMock, DeepMocked } from '@golevelup/nestjs-testing';
import { Test, TestingModule } from '@nestjs/testing';
-import { ApplicationService } from '../../application/application.service';
-import { CovenantService } from '../../covenant/covenant.service';
+import { classes } from 'automapper-classes';
+import { AutomapperModule } from 'automapper-nestjs';
import { ApplicationModificationService } from '../../application-decision/application-modification/application-modification.service';
import { ApplicationReconsiderationService } from '../../application-decision/application-reconsideration/application-reconsideration.service';
+import { ApplicationService } from '../../application/application.service';
+import { CovenantService } from '../../covenant/covenant.service';
import { NoticeOfIntentModificationService } from '../../notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.service';
import { NoticeOfIntentService } from '../../notice-of-intent/notice-of-intent.service';
import { NotificationService } from '../../notification/notification.service';
-import { PlanningReviewService } from '../../planning-review/planning-review.service';
+import { PlanningReferralService } from '../../planning-review/planning-referral/planning-referral.service';
import { UnarchiveCardService } from './unarchive-card.service';
describe('UnarchiveCardService', () => {
@@ -17,7 +17,7 @@ describe('UnarchiveCardService', () => {
let mockApplicationService: DeepMocked
;
let mockReconsiderationService: DeepMocked;
- let mockPlanningReviewService: DeepMocked;
+ let mockPlanningReferralService: DeepMocked;
let mockModificationService: DeepMocked;
let mockCovenantService: DeepMocked;
let mockNOIService: DeepMocked;
@@ -27,7 +27,7 @@ describe('UnarchiveCardService', () => {
beforeEach(async () => {
mockApplicationService = createMock();
mockReconsiderationService = createMock();
- mockPlanningReviewService = createMock();
+ mockPlanningReferralService = createMock();
mockModificationService = createMock();
mockCovenantService = createMock();
mockNOIService = createMock();
@@ -51,8 +51,8 @@ describe('UnarchiveCardService', () => {
useValue: mockReconsiderationService,
},
{
- provide: PlanningReviewService,
- useValue: mockPlanningReviewService,
+ provide: PlanningReferralService,
+ useValue: mockPlanningReferralService,
},
{
provide: ApplicationModificationService,
@@ -87,18 +87,20 @@ describe('UnarchiveCardService', () => {
it('should load from each service for fetch', async () => {
mockApplicationService.getDeletedCard.mockResolvedValue(null);
mockReconsiderationService.getDeletedCards.mockResolvedValue([]);
- mockPlanningReviewService.getDeletedCards.mockResolvedValue([]);
+ mockPlanningReferralService.getDeletedCards.mockResolvedValue([]);
mockModificationService.getDeletedCards.mockResolvedValue([]);
mockCovenantService.getDeletedCards.mockResolvedValue([]);
mockNOIService.getDeletedCards.mockResolvedValue([]);
mockNOIModificationService.getDeletedCards.mockResolvedValue([]);
mockNotificationService.getDeletedCards.mockResolvedValue([]);
- const res = await service.fetchByFileId('uuid');
+ await service.fetchByFileId('uuid');
expect(mockApplicationService.getDeletedCard).toHaveBeenCalledTimes(1);
expect(mockReconsiderationService.getDeletedCards).toHaveBeenCalledTimes(1);
- expect(mockPlanningReviewService.getDeletedCards).toHaveBeenCalledTimes(1);
+ expect(mockPlanningReferralService.getDeletedCards).toHaveBeenCalledTimes(
+ 1,
+ );
expect(mockModificationService.getDeletedCards).toHaveBeenCalledTimes(1);
expect(mockCovenantService.getDeletedCards).toHaveBeenCalledTimes(1);
expect(mockNOIService.getDeletedCards).toHaveBeenCalledTimes(1);
diff --git a/services/apps/alcs/src/alcs/admin/unarchive-card/unarchive-card.service.ts b/services/apps/alcs/src/alcs/admin/unarchive-card/unarchive-card.service.ts
index 734c465f43..84f3a15589 100644
--- a/services/apps/alcs/src/alcs/admin/unarchive-card/unarchive-card.service.ts
+++ b/services/apps/alcs/src/alcs/admin/unarchive-card/unarchive-card.service.ts
@@ -6,19 +6,19 @@ import { CovenantService } from '../../covenant/covenant.service';
import { NoticeOfIntentModificationService } from '../../notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.service';
import { NoticeOfIntentService } from '../../notice-of-intent/notice-of-intent.service';
import { NotificationService } from '../../notification/notification.service';
-import { PlanningReviewService } from '../../planning-review/planning-review.service';
+import { PlanningReferralService } from '../../planning-review/planning-referral/planning-referral.service';
@Injectable()
export class UnarchiveCardService {
constructor(
private applicationService: ApplicationService,
private reconsiderationService: ApplicationReconsiderationService,
- private planningReviewService: PlanningReviewService,
private modificationService: ApplicationModificationService,
private covenantService: CovenantService,
private noticeOfIntentService: NoticeOfIntentService,
private noticeOfIntentModificationService: NoticeOfIntentModificationService,
private notificationService: NotificationService,
+ private planningReferralService: PlanningReferralService,
) {}
async fetchByFileId(fileId: string) {
@@ -39,7 +39,7 @@ export class UnarchiveCardService {
}
await this.fetchAndMapRecons(fileId, result);
- await this.fetchAndMapPlanningReviews(fileId, result);
+ await this.fetchAndMapPlanningReferrals(fileId, result);
await this.fetchAndMapModifications(fileId, result);
await this.fetchAndMapCovenants(fileId, result);
await this.fetchAndMapNOIs(fileId, result);
@@ -89,27 +89,6 @@ export class UnarchiveCardService {
}
}
- private async fetchAndMapPlanningReviews(
- fileId: string,
- result: {
- cardUuid: string;
- type: string;
- status: string;
- createdAt: number;
- }[],
- ) {
- const planningReviews =
- await this.planningReviewService.getDeletedCards(fileId);
- for (const planningReview of planningReviews) {
- result.push({
- cardUuid: planningReview.cardUuid,
- createdAt: planningReview.auditCreatedAt.getTime(),
- type: 'Planning Review',
- status: planningReview.card!.status.label,
- });
- }
- }
-
private async fetchAndMapRecons(
fileId: string,
result: {
@@ -184,4 +163,25 @@ export class UnarchiveCardService {
});
}
}
+
+ private async fetchAndMapPlanningReferrals(
+ fileId: string,
+ result: {
+ cardUuid: string;
+ type: string;
+ status: string;
+ createdAt: number;
+ }[],
+ ) {
+ const notifications =
+ await this.planningReferralService.getDeletedCards(fileId);
+ for (const referral of notifications) {
+ result.push({
+ cardUuid: referral.cardUuid,
+ createdAt: referral.auditCreatedAt.getTime(),
+ type: 'PLAN',
+ status: referral.card!.status.label,
+ });
+ }
+ }
}
diff --git a/services/apps/alcs/src/alcs/board/board.controller.spec.ts b/services/apps/alcs/src/alcs/board/board.controller.spec.ts
index d776352754..c302ca0b67 100644
--- a/services/apps/alcs/src/alcs/board/board.controller.spec.ts
+++ b/services/apps/alcs/src/alcs/board/board.controller.spec.ts
@@ -1,20 +1,21 @@
-import { classes } from 'automapper-classes';
-import { AutomapperModule } from 'automapper-nestjs';
import { createMock, DeepMocked } from '@golevelup/nestjs-testing';
import { Test, TestingModule } from '@nestjs/testing';
+import { classes } from 'automapper-classes';
+import { AutomapperModule } from 'automapper-nestjs';
import { ClsService } from 'nestjs-cls';
import { mockKeyCloakProviders } from '../../../test/mocks/mockTypes';
import { BoardAutomapperProfile } from '../../common/automapper/board.automapper.profile';
import { ApplicationModificationService } from '../application-decision/application-modification/application-modification.service';
import { ApplicationReconsiderationService } from '../application-decision/application-reconsideration/application-reconsideration.service';
import { ApplicationService } from '../application/application.service';
-import { CardType, CARD_TYPE } from '../card/card-type/card-type.entity';
+import { CARD_TYPE, CardType } from '../card/card-type/card-type.entity';
import { Card } from '../card/card.entity';
import { CardService } from '../card/card.service';
import { CovenantService } from '../covenant/covenant.service';
import { NoticeOfIntentModificationService } from '../notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.service';
import { NoticeOfIntentService } from '../notice-of-intent/notice-of-intent.service';
import { NotificationService } from '../notification/notification.service';
+import { PlanningReferralService } from '../planning-review/planning-referral/planning-referral.service';
import { PlanningReviewService } from '../planning-review/planning-review.service';
import { BoardController } from './board.controller';
import { BOARD_CODES } from './board.dto';
@@ -29,7 +30,7 @@ describe('BoardController', () => {
let appReconsiderationService: DeepMocked;
let modificationService: DeepMocked;
let cardService: DeepMocked;
- let planningReviewService: DeepMocked;
+ let planningReferralService: DeepMocked;
let covenantService: DeepMocked;
let noticeOfIntentService: DeepMocked;
let noiModificationService: DeepMocked;
@@ -41,7 +42,7 @@ describe('BoardController', () => {
appService = createMock();
appReconsiderationService = createMock();
modificationService = createMock();
- planningReviewService = createMock();
+ planningReferralService = createMock();
cardService = createMock();
covenantService = createMock();
noticeOfIntentService = createMock();
@@ -60,8 +61,8 @@ describe('BoardController', () => {
appService.mapToDtos.mockResolvedValue([]);
appReconsiderationService.getByBoard.mockResolvedValue([]);
appReconsiderationService.mapToDtos.mockResolvedValue([]);
- planningReviewService.getByBoard.mockResolvedValue([]);
- planningReviewService.mapToDtos.mockResolvedValue([]);
+ planningReferralService.getByBoard.mockResolvedValue([]);
+ planningReferralService.mapToDtos.mockResolvedValue([]);
modificationService.getByBoard.mockResolvedValue([]);
modificationService.mapToDtos.mockResolvedValue([]);
covenantService.getByBoard.mockResolvedValue([]);
@@ -92,8 +93,8 @@ describe('BoardController', () => {
},
{ provide: CardService, useValue: cardService },
{
- provide: PlanningReviewService,
- useValue: planningReviewService,
+ provide: PlanningReferralService,
+ useValue: planningReferralService,
},
{ provide: CovenantService, useValue: covenantService },
{
@@ -148,8 +149,8 @@ describe('BoardController', () => {
expect(appReconsiderationService.mapToDtos).toHaveBeenCalledTimes(1);
expect(modificationService.getByBoard).toHaveBeenCalledTimes(0);
expect(modificationService.mapToDtos).toHaveBeenCalledTimes(1);
- expect(planningReviewService.getByBoard).toHaveBeenCalledTimes(0);
- expect(planningReviewService.mapToDtos).toHaveBeenCalledTimes(1);
+ expect(planningReferralService.getByBoard).toHaveBeenCalledTimes(0);
+ expect(planningReferralService.mapToDtos).toHaveBeenCalledTimes(1);
});
it('should call through to planning review service if board supports planning reviews', async () => {
@@ -162,8 +163,8 @@ describe('BoardController', () => {
await controller.getBoardWithCards(boardCode);
- expect(planningReviewService.getByBoard).toHaveBeenCalledTimes(1);
- expect(planningReviewService.mapToDtos).toHaveBeenCalledTimes(1);
+ expect(planningReferralService.getByBoard).toHaveBeenCalledTimes(1);
+ expect(planningReferralService.mapToDtos).toHaveBeenCalledTimes(1);
});
it('should call through to modification service for boards that support it board', async () => {
diff --git a/services/apps/alcs/src/alcs/board/board.controller.ts b/services/apps/alcs/src/alcs/board/board.controller.ts
index b06ced459a..fe97ea2d39 100644
--- a/services/apps/alcs/src/alcs/board/board.controller.ts
+++ b/services/apps/alcs/src/alcs/board/board.controller.ts
@@ -20,6 +20,7 @@ import { CovenantService } from '../covenant/covenant.service';
import { NoticeOfIntentModificationService } from '../notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.service';
import { NoticeOfIntentService } from '../notice-of-intent/notice-of-intent.service';
import { NotificationService } from '../notification/notification.service';
+import { PlanningReferralService } from '../planning-review/planning-referral/planning-referral.service';
import { PlanningReviewService } from '../planning-review/planning-review.service';
import { BoardDto, MinimalBoardDto } from './board.dto';
import { Board } from './board.entity';
@@ -34,7 +35,7 @@ export class BoardController {
private applicationService: ApplicationService,
private cardService: CardService,
private reconsiderationService: ApplicationReconsiderationService,
- private planningReviewService: PlanningReviewService,
+ private planningReferralService: PlanningReferralService,
private appModificationService: ApplicationModificationService,
private noiModificationService: NoticeOfIntentModificationService,
private covenantService: CovenantService,
@@ -89,8 +90,8 @@ export class BoardController {
? await this.noticeOfIntentService.getByBoard(board.uuid)
: [];
- const planningReviews = allowedCodes.includes(CARD_TYPE.PLAN)
- ? await this.planningReviewService.getByBoard(board.uuid)
+ const planningReferrals = allowedCodes.includes(CARD_TYPE.PLAN)
+ ? await this.planningReferralService.getByBoard(board.uuid)
: [];
const noiModifications = allowedCodes.includes(CARD_TYPE.NOI_MODI)
@@ -105,8 +106,8 @@ export class BoardController {
board: await this.autoMapper.mapAsync(board, Board, BoardDto),
applications: await this.applicationService.mapToDtos(applications),
reconsiderations: await this.reconsiderationService.mapToDtos(recons),
- planningReviews:
- await this.planningReviewService.mapToDtos(planningReviews),
+ planningReferrals:
+ await this.planningReferralService.mapToDtos(planningReferrals),
modifications: await this.appModificationService.mapToDtos(modifications),
covenants: await this.covenantService.mapToDtos(covenants),
noticeOfIntents:
diff --git a/services/apps/alcs/src/alcs/board/board.dto.ts b/services/apps/alcs/src/alcs/board/board.dto.ts
index 422a13ce8e..4a281bc64e 100644
--- a/services/apps/alcs/src/alcs/board/board.dto.ts
+++ b/services/apps/alcs/src/alcs/board/board.dto.ts
@@ -5,6 +5,7 @@ export enum BOARD_CODES {
CEO = 'ceo',
SOIL = 'soil',
EXECUTIVE_COMMITTEE = 'exec',
+ REGIONAL_PLANNING = 'rppp',
}
export class MinimalBoardDto {
diff --git a/services/apps/alcs/src/alcs/home/home.controller.spec.ts b/services/apps/alcs/src/alcs/home/home.controller.spec.ts
index d11864332a..478b071bbf 100644
--- a/services/apps/alcs/src/alcs/home/home.controller.spec.ts
+++ b/services/apps/alcs/src/alcs/home/home.controller.spec.ts
@@ -1,7 +1,7 @@
-import { classes } from 'automapper-classes';
-import { AutomapperModule } from 'automapper-nestjs';
import { createMock, DeepMocked } from '@golevelup/nestjs-testing';
import { Test, TestingModule } from '@nestjs/testing';
+import { classes } from 'automapper-classes';
+import { AutomapperModule } from 'automapper-nestjs';
import { ClsService } from 'nestjs-cls';
import { In, Not } from 'typeorm';
import {
@@ -33,8 +33,6 @@ import { NoticeOfIntent } from '../notice-of-intent/notice-of-intent.entity';
import { NoticeOfIntentService } from '../notice-of-intent/notice-of-intent.service';
import { Notification } from '../notification/notification.entity';
import { NotificationService } from '../notification/notification.service';
-import { PlanningReview } from '../planning-review/planning-review.entity';
-import { PlanningReviewService } from '../planning-review/planning-review.service';
import { HomeController } from './home.controller';
describe('HomeController', () => {
@@ -43,7 +41,6 @@ describe('HomeController', () => {
let mockApplicationSubtaskService: DeepMocked;
let mockApplicationReconsiderationService: DeepMocked;
let mockApplicationModificationService: DeepMocked;
- let mockPlanningReviewService: DeepMocked;
let mockCovenantService: DeepMocked;
let mockApplicationTimeTrackingService: DeepMocked;
let mockNoticeOfIntentService: DeepMocked;
@@ -54,7 +51,6 @@ describe('HomeController', () => {
mockApplicationService = createMock();
mockApplicationSubtaskService = createMock();
mockApplicationReconsiderationService = createMock();
- mockPlanningReviewService = createMock();
mockApplicationTimeTrackingService = createMock();
mockApplicationModificationService = createMock();
mockCovenantService = createMock();
@@ -98,10 +94,6 @@ describe('HomeController', () => {
provide: ApplicationTimeTrackingService,
useValue: mockApplicationTimeTrackingService,
},
- {
- provide: PlanningReviewService,
- useValue: mockPlanningReviewService,
- },
{
provide: CovenantService,
useValue: mockCovenantService,
@@ -136,8 +128,6 @@ describe('HomeController', () => {
mockApplicationReconsiderationService.mapToDtos.mockResolvedValue([]);
mockApplicationModificationService.getBy.mockResolvedValue([]);
mockApplicationModificationService.mapToDtos.mockResolvedValue([]);
- mockPlanningReviewService.getBy.mockResolvedValue([]);
- mockPlanningReviewService.mapToDtos.mockResolvedValue([]);
mockCovenantService.getBy.mockResolvedValue([]);
mockCovenantService.mapToDtos.mockResolvedValue([]);
mockNoticeOfIntentService.getBy.mockResolvedValue([]);
@@ -158,9 +148,6 @@ describe('HomeController', () => {
mockApplicationReconsiderationService.getWithIncompleteSubtaskByType.mockResolvedValue(
[],
);
- mockPlanningReviewService.getWithIncompleteSubtaskByType.mockResolvedValue(
- [],
- );
mockApplicationModificationService.getWithIncompleteSubtaskByType.mockResolvedValue(
[],
);
@@ -213,11 +200,6 @@ describe('HomeController', () => {
mockApplicationReconsiderationService.getBy.mock.calls[0][0],
).toEqual(filterCondition);
- expect(mockPlanningReviewService.getBy).toHaveBeenCalledTimes(1);
- expect(mockPlanningReviewService.getBy.mock.calls[0][0]).toEqual(
- filterCondition,
- );
-
expect(mockNoticeOfIntentService.getBy).toHaveBeenCalledTimes(1);
expect(mockNoticeOfIntentService.getBy.mock.calls[0][0]).toEqual(
filterCondition,
@@ -295,30 +277,31 @@ describe('HomeController', () => {
expect(res[0].paused).toBeFalsy();
});
- it('should call Reconsideration Service and map it', async () => {
- const mockPlanningReview = {
- type: 'fake-type',
- fileNumber: 'fileNumber',
- card: initCardMockEntity('222'),
- } as PlanningReview;
- mockPlanningReviewService.getWithIncompleteSubtaskByType.mockResolvedValue(
- [mockPlanningReview],
- );
-
- const res = await controller.getIncompleteSubtasksByType(
- CARD_SUBTASK_TYPE.GIS,
- );
-
- expect(res.length).toEqual(1);
- expect(
- mockPlanningReviewService.getWithIncompleteSubtaskByType,
- ).toHaveBeenCalledTimes(1);
-
- expect(res[0].title).toContain(mockPlanningReview.fileNumber);
- expect(res[0].title).toContain(mockPlanningReview.type);
- expect(res[0].activeDays).toBeUndefined();
- expect(res[0].paused).toBeFalsy();
- });
+ // TODO: Fix when finishing planning reviews
+ // it('should call Planning Referral Service and map it', async () => {
+ // const mockPlanningReview = {
+ // type: 'fake-type',
+ // fileNumber: 'fileNumber',
+ // card: initCardMockEntity('222'),
+ // } as PlanningReview;
+ // mockPlanningReviewService.getWithIncompleteSubtaskByType.mockResolvedValue(
+ // [mockPlanningReview],
+ // );
+ //
+ // const res = await controller.getIncompleteSubtasksByType(
+ // CARD_SUBTASK_TYPE.GIS,
+ // );
+ //
+ // expect(res.length).toEqual(1);
+ // expect(
+ // mockPlanningReviewService.getWithIncompleteSubtaskByType,
+ // ).toHaveBeenCalledTimes(1);
+ //
+ // expect(res[0].title).toContain(mockPlanningReview.fileNumber);
+ // expect(res[0].title).toContain(mockPlanningReview.type);
+ // expect(res[0].activeDays).toBeUndefined();
+ // expect(res[0].paused).toBeFalsy();
+ // });
it('should call Modification Service and map it', async () => {
const mockModification = initApplicationModificationMockEntity();
@@ -332,7 +315,7 @@ describe('HomeController', () => {
expect(res.length).toEqual(1);
expect(
- mockPlanningReviewService.getWithIncompleteSubtaskByType,
+ mockApplicationModificationService.getWithIncompleteSubtaskByType,
).toHaveBeenCalledTimes(1);
expect(res[0].title).toContain(mockModification.application.fileNumber);
@@ -357,7 +340,7 @@ describe('HomeController', () => {
expect(res.length).toEqual(1);
expect(
- mockPlanningReviewService.getWithIncompleteSubtaskByType,
+ mockCovenantService.getWithIncompleteSubtaskByType,
).toHaveBeenCalledTimes(1);
expect(res[0].title).toContain(mockCovenant.fileNumber);
diff --git a/services/apps/alcs/src/alcs/home/home.controller.ts b/services/apps/alcs/src/alcs/home/home.controller.ts
index 3fe9faac21..1de8cee4de 100644
--- a/services/apps/alcs/src/alcs/home/home.controller.ts
+++ b/services/apps/alcs/src/alcs/home/home.controller.ts
@@ -1,7 +1,7 @@
-import { Mapper } from 'automapper-core';
-import { InjectMapper } from 'automapper-nestjs';
import { Controller, Get, Param, Req, UseGuards } from '@nestjs/common';
import { ApiOAuth2 } from '@nestjs/swagger';
+import { Mapper } from 'automapper-core';
+import { InjectMapper } from 'automapper-nestjs';
import * as config from 'config';
import { In, Not } from 'typeorm';
import { ANY_AUTH_ROLE } from '../../common/authorization/roles';
@@ -36,11 +36,11 @@ import { NoticeOfIntentModificationService } from '../notice-of-intent-decision/
import { NoticeOfIntentDto } from '../notice-of-intent/notice-of-intent.dto';
import { NoticeOfIntent } from '../notice-of-intent/notice-of-intent.entity';
import { NoticeOfIntentService } from '../notice-of-intent/notice-of-intent.service';
+import { NotificationDto } from '../notification/notification.dto';
import { Notification } from '../notification/notification.entity';
import { NotificationService } from '../notification/notification.service';
import { PlanningReviewDto } from '../planning-review/planning-review.dto';
import { PlanningReview } from '../planning-review/planning-review.entity';
-import { PlanningReviewService } from '../planning-review/planning-review.service';
const HIDDEN_CARD_STATUSES = [
CARD_STATUS.CANCELLED,
@@ -56,7 +56,6 @@ export class HomeController {
private applicationService: ApplicationService,
private timeService: ApplicationTimeTrackingService,
private reconsiderationService: ApplicationReconsiderationService,
- private planningReviewService: PlanningReviewService,
private modificationService: ApplicationModificationService,
private covenantService: CovenantService,
private noticeOfIntentService: NoticeOfIntentService,
@@ -71,9 +70,10 @@ export class HomeController {
noticeOfIntentModifications: NoticeOfIntentModificationDto[];
applications: ApplicationDto[];
reconsiderations: ApplicationReconsiderationDto[];
- planningReviews: PlanningReviewDto[];
+ planningReferrals: PlanningReviewDto[];
modifications: ApplicationModificationDto[];
covenants: CovenantDto[];
+ notifications: NotificationDto[];
}> {
const userId = req.user.entity.uuid;
const assignedFindOptions = {
@@ -91,8 +91,8 @@ export class HomeController {
const reconsiderations =
await this.reconsiderationService.getBy(assignedFindOptions);
- const planningReviews =
- await this.planningReviewService.getBy(assignedFindOptions);
+ // const planningReviews =
+ // await this.planningReviewService.getBy(assignedFindOptions);
const modifications =
await this.modificationService.getBy(assignedFindOptions);
@@ -108,7 +108,7 @@ export class HomeController {
const notifications =
await this.notificationService.getBy(assignedFindOptions);
- const result = {
+ return {
noticeOfIntents:
await this.noticeOfIntentService.mapToDtos(noticeOfIntents),
noticeOfIntentModifications:
@@ -118,23 +118,21 @@ export class HomeController {
applications: await this.applicationService.mapToDtos(applications),
reconsiderations:
await this.reconsiderationService.mapToDtos(reconsiderations),
- planningReviews:
- await this.planningReviewService.mapToDtos(planningReviews),
+ planningReferrals: [],
modifications: await this.modificationService.mapToDtos(modifications),
covenants: await this.covenantService.mapToDtos(covenants),
notifications: await this.notificationService.mapToDtos(notifications),
};
-
- return result;
} else {
return {
noticeOfIntents: [],
noticeOfIntentModifications: [],
applications: [],
reconsiderations: [],
- planningReviews: [],
+ planningReferrals: [],
modifications: [],
covenants: [],
+ notifications: [],
};
}
}
@@ -156,13 +154,13 @@ export class HomeController {
);
const reconSubtasks = this.mapReconToDto(reconsiderationWithSubtasks);
- const planningReviewsWithSubtasks =
- await this.planningReviewService.getWithIncompleteSubtaskByType(
- subtaskType,
- );
- const planningReviewSubtasks = this.mapPlanningReviewsToDtos(
- planningReviewsWithSubtasks,
- );
+ // const planningReviewsWithSubtasks =
+ // await this.planningReviewService.getWithIncompleteSubtaskByType(
+ // subtaskType,
+ // );
+ // const planningReviewSubtasks = this.mapPlanningReviewsToDtos(
+ // planningReviewsWithSubtasks,
+ // );
const modificationsWithSubtasks =
await this.modificationService.getWithIncompleteSubtaskByType(
@@ -205,7 +203,6 @@ export class HomeController {
...applicationSubtasks,
...reconSubtasks,
...modificationSubtasks,
- ...planningReviewSubtasks,
...covenantReviewSubtasks,
...noiModificationsSubtasks,
...notificationSubtasks,
@@ -270,21 +267,22 @@ export class HomeController {
private mapPlanningReviewsToDtos(planingReviews: PlanningReview[]) {
const result: HomepageSubtaskDTO[] = [];
- for (const planningReview of planingReviews) {
- for (const subtask of planningReview.card.subtasks) {
- result.push({
- type: subtask.type,
- createdAt: subtask.createdAt.getTime(),
- assignee: this.mapper.map(subtask.assignee, User, AssigneeDto),
- uuid: subtask.uuid,
- card: this.mapper.map(planningReview.card, Card, CardDto),
- completedAt: subtask.completedAt?.getTime(),
- paused: false,
- title: `${planningReview.fileNumber} (${planningReview.type})`,
- parentType: PARENT_TYPE.PLANNING_REVIEW,
- });
- }
- }
+ // TODO
+ // for (const planningReview of planingReviews) {
+ // for (const subtask of planningReview.card.subtasks) {
+ // result.push({
+ // type: subtask.type,
+ // createdAt: subtask.createdAt.getTime(),
+ // assignee: this.mapper.map(subtask.assignee, User, AssigneeDto),
+ // uuid: subtask.uuid,
+ // card: this.mapper.map(planningReview.card, Card, CardDto),
+ // completedAt: subtask.completedAt?.getTime(),
+ // paused: false,
+ // title: `${planningReview.fileNumber} (${planningReview.type})`,
+ // parentType: PARENT_TYPE.PLANNING_REVIEW,
+ // });
+ // }
+ // }
return result;
}
diff --git a/services/apps/alcs/src/alcs/notification/notification-document/notification-document.entity.ts b/services/apps/alcs/src/alcs/notification/notification-document/notification-document.entity.ts
index 087c56a4f2..98edea46a3 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,6 +56,12 @@ export class NotificationDocument extends BaseEntity {
@Column({ nullable: true, type: 'uuid' })
documentUuid?: string | null;
+ @Column({ type: 'text', nullable: true })
+ oatsApplicationId?: string | null;
+
+ @Column({ type: 'text', nullable: true })
+ oatsDocumentId?: string | null;
+
@AutoMap(() => [String])
@Column({ default: [], array: true, type: 'text' })
visibilityFlags: VISIBILITY_FLAG[];
diff --git a/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.controller.spec.ts b/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.controller.spec.ts
new file mode 100644
index 0000000000..bb8235e2b2
--- /dev/null
+++ b/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.controller.spec.ts
@@ -0,0 +1,102 @@
+import { createMock, DeepMocked } from '@golevelup/nestjs-testing';
+import { Test, TestingModule } from '@nestjs/testing';
+import { classes } from 'automapper-classes';
+import { AutomapperModule } from 'automapper-nestjs';
+import { ClsService } from 'nestjs-cls';
+import { mockKeyCloakProviders } from '../../../../test/mocks/mockTypes';
+import { PlanningReviewProfile } from '../../../common/automapper/planning-review.automapper.profile';
+import { Board } from '../../board/board.entity';
+import { BoardService } from '../../board/board.service';
+import { PlanningReviewType } from '../planning-review-type.entity';
+import { PlanningReferralController } from './planning-referral.controller';
+import { PlanningReferral } from './planning-referral.entity';
+import { PlanningReferralService } from './planning-referral.service';
+
+describe('PlanningReviewController', () => {
+ let controller: PlanningReferralController;
+ let mockService: DeepMocked;
+ let mockBoardService: DeepMocked;
+
+ beforeEach(async () => {
+ mockService = createMock();
+ mockBoardService = createMock();
+
+ const module: TestingModule = await Test.createTestingModule({
+ imports: [
+ AutomapperModule.forRoot({
+ strategyInitializer: classes(),
+ }),
+ ],
+ controllers: [PlanningReferralController],
+ providers: [
+ PlanningReviewProfile,
+ {
+ provide: PlanningReferralService,
+ useValue: mockService,
+ },
+ {
+ provide: BoardService,
+ useValue: mockBoardService,
+ },
+ {
+ provide: ClsService,
+ useValue: {},
+ },
+ ...mockKeyCloakProviders,
+ ],
+ }).compile();
+
+ controller = module.get(
+ PlanningReferralController,
+ );
+ });
+
+ it('should be defined', () => {
+ expect(controller).toBeDefined();
+ });
+
+ it('should call through for fetchByCardUuid', async () => {
+ mockService.getByCardUuid.mockResolvedValue(new PlanningReferral());
+
+ const res = await controller.fetchByCardUuid('uuid');
+
+ expect(res).toBeDefined();
+ expect(mockService.getByCardUuid).toHaveBeenCalledTimes(1);
+ });
+
+ it('should load the board then call through for create', async () => {
+ mockService.create.mockResolvedValue(new PlanningReferral());
+ mockBoardService.getOneOrFail.mockResolvedValue(new Board());
+
+ const res = await controller.create({
+ planningReviewUuid: '',
+ referralDescription: '',
+ submissionDate: 0,
+ });
+
+ expect(res).toBeDefined();
+ expect(mockBoardService.getOneOrFail).toHaveBeenCalledTimes(1);
+ expect(mockService.create).toHaveBeenCalledTimes(1);
+ });
+
+ it('should call through for update', async () => {
+ mockService.update.mockResolvedValue();
+
+ const res = await controller.update('', {
+ referralDescription: '',
+ submissionDate: 0,
+ });
+
+ expect(res).toBeDefined();
+ expect(mockService.update).toHaveBeenCalledTimes(1);
+ });
+
+ it('should call through for delete', async () => {
+ mockService.delete.mockResolvedValue();
+
+ const res = await controller.delete('');
+
+ expect(res).toBeDefined();
+ expect(mockService.delete).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.controller.ts b/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.controller.ts
new file mode 100644
index 0000000000..b7f51e0d3d
--- /dev/null
+++ b/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.controller.ts
@@ -0,0 +1,77 @@
+import {
+ Body,
+ Controller,
+ Delete,
+ Get,
+ Param,
+ Patch,
+ 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 { ROLES_ALLOWED_BOARDS } from '../../../common/authorization/roles';
+import { RolesGuard } from '../../../common/authorization/roles-guard.service';
+import { UserRoles } from '../../../common/authorization/roles.decorator';
+import { BOARD_CODES } from '../../board/board.dto';
+import { BoardService } from '../../board/board.service';
+import {
+ CreatePlanningReferralDto,
+ PlanningReferralDto,
+ UpdatePlanningReferralDto,
+} from '../planning-review.dto';
+import { PlanningReferral } from './planning-referral.entity';
+import { PlanningReferralService } from './planning-referral.service';
+
+@Controller('planning-referral')
+@ApiOAuth2(config.get('KEYCLOAK.SCOPES'))
+@UseGuards(RolesGuard)
+export class PlanningReferralController {
+ constructor(
+ private planningReferralService: PlanningReferralService,
+ @InjectMapper()
+ private mapper: Mapper,
+ private boardService: BoardService,
+ ) {}
+
+ @Get('/card/:uuid')
+ @UserRoles(...ROLES_ALLOWED_BOARDS)
+ async fetchByCardUuid(@Param('uuid') uuid: string) {
+ const review = await this.planningReferralService.getByCardUuid(uuid);
+ return this.mapper.map(review, PlanningReferral, PlanningReferralDto);
+ }
+
+ @Post()
+ @UserRoles(...ROLES_ALLOWED_BOARDS)
+ async create(@Body() createDto: CreatePlanningReferralDto) {
+ const board = await this.boardService.getOneOrFail({
+ code: BOARD_CODES.REGIONAL_PLANNING,
+ });
+
+ const review = await this.planningReferralService.create(createDto, board);
+ return this.mapper.map(review, PlanningReferral, PlanningReferralDto);
+ }
+
+ @Patch(':uuid')
+ @UserRoles(...ROLES_ALLOWED_BOARDS)
+ async update(
+ @Param('uuid') uuid: string,
+ @Body() updateDto: UpdatePlanningReferralDto,
+ ) {
+ await this.planningReferralService.update(uuid, updateDto);
+ return {
+ success: true,
+ };
+ }
+
+ @Delete(':uuid')
+ @UserRoles(...ROLES_ALLOWED_BOARDS)
+ async delete(@Param('uuid') uuid: string) {
+ await this.planningReferralService.delete(uuid);
+ return {
+ success: true,
+ };
+ }
+}
diff --git a/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.entity.ts b/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.entity.ts
new file mode 100644
index 0000000000..f2cc8777bd
--- /dev/null
+++ b/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.entity.ts
@@ -0,0 +1,49 @@
+import { AutoMap } from 'automapper-classes';
+import { Type } from 'class-transformer';
+import { Column, Entity, JoinColumn, ManyToOne, OneToOne } from 'typeorm';
+import { Base } from '../../../common/entities/base.entity';
+import { Card } from '../../card/card.entity';
+import { PlanningReview } from '../planning-review.entity';
+
+@Entity({
+ comment:
+ 'Planning Referrals represent each pass of a Planning Review with their own cards',
+})
+export class PlanningReferral extends Base {
+ constructor(data?: Partial) {
+ super();
+ if (data) {
+ Object.assign(this, data);
+ }
+ }
+
+ @Column({ type: 'timestamptz' })
+ submissionDate: Date;
+
+ @Column({ type: 'timestamptz', nullable: true })
+ dueDate?: Date | null;
+
+ @Column({ type: 'timestamptz', nullable: true })
+ responseDate?: Date | null;
+
+ @AutoMap(() => String)
+ @Column({ nullable: true, type: 'text' })
+ referralDescription?: string | null;
+
+ @AutoMap(() => String)
+ @Column({ nullable: true, type: 'text' })
+ responseDescription?: string;
+
+ @ManyToOne(() => PlanningReview)
+ @JoinColumn()
+ @Type(() => PlanningReview)
+ planningReview: PlanningReview;
+
+ @Column({ type: 'uuid' })
+ cardUuid: string;
+
+ @OneToOne(() => Card, { cascade: true })
+ @JoinColumn()
+ @Type(() => Card)
+ card: Card;
+}
diff --git a/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.service.spec.ts b/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.service.spec.ts
new file mode 100644
index 0000000000..c35f9cb3c2
--- /dev/null
+++ b/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.service.spec.ts
@@ -0,0 +1,126 @@
+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 { Board } from '../../board/board.entity';
+import { Card } from '../../card/card.entity';
+import { CardService } from '../../card/card.service';
+import { PlanningReview } from '../planning-review.entity';
+import { PlanningReferral } from './planning-referral.entity';
+import { PlanningReferralService } from './planning-referral.service';
+
+describe('PlanningReferralService', () => {
+ let service: PlanningReferralService;
+ let mockRepository: DeepMocked>;
+ let mockReviewRepository: DeepMocked>;
+ let mockCardService: DeepMocked;
+
+ beforeEach(async () => {
+ mockCardService = createMock();
+ mockReviewRepository = createMock();
+ mockRepository = createMock();
+
+ const module: TestingModule = await Test.createTestingModule({
+ imports: [
+ AutomapperModule.forRoot({
+ strategyInitializer: classes(),
+ }),
+ ],
+ providers: [
+ {
+ provide: getRepositoryToken(PlanningReferral),
+ useValue: mockRepository,
+ },
+ {
+ provide: getRepositoryToken(PlanningReview),
+ useValue: mockReviewRepository,
+ },
+ {
+ provide: CardService,
+ useValue: mockCardService,
+ },
+ PlanningReferralService,
+ ],
+ }).compile();
+
+ service = module.get(PlanningReferralService);
+ });
+
+ it('should be defined', () => {
+ expect(service).toBeDefined();
+ });
+
+ it('should call through to the repo for get by card', async () => {
+ mockRepository.findOneOrFail.mockResolvedValue(new PlanningReferral());
+ const cardUuid = 'fake-card-uuid';
+ await service.getByCardUuid(cardUuid);
+
+ expect(mockRepository.findOneOrFail).toHaveBeenCalledTimes(1);
+ });
+
+ it('should call through to the repo for get cards', async () => {
+ mockRepository.find.mockResolvedValue([]);
+ await service.getByBoard('');
+
+ expect(mockRepository.find).toHaveBeenCalledTimes(1);
+ });
+
+ it('should load deleted cards', async () => {
+ mockRepository.find.mockResolvedValue([]);
+
+ await service.getDeletedCards('file-number');
+
+ expect(mockRepository.find).toHaveBeenCalledTimes(1);
+ expect(mockRepository.find.mock.calls[0][0]!.withDeleted).toEqual(true);
+ });
+
+ it('should load the review then call save for create', async () => {
+ mockReviewRepository.findOneOrFail.mockResolvedValue(new PlanningReview());
+ mockCardService.create.mockResolvedValue(new Card());
+ mockRepository.save.mockResolvedValue(new PlanningReferral());
+ mockRepository.findOneOrFail.mockResolvedValue(new PlanningReferral());
+
+ await service.create(
+ {
+ referralDescription: '',
+ submissionDate: 0,
+ planningReviewUuid: 'uuid',
+ },
+ new Board(),
+ );
+
+ expect(mockReviewRepository.findOneOrFail).toHaveBeenCalledTimes(1);
+ expect(mockCardService.create).toHaveBeenCalledTimes(1);
+ expect(mockRepository.save).toHaveBeenCalledTimes(1);
+ });
+
+ it('should load the review then update its values for update', async () => {
+ const mockReferral = new PlanningReferral();
+ mockRepository.save.mockResolvedValue(mockReferral);
+ mockRepository.findOneOrFail.mockResolvedValue(mockReferral);
+
+ const newDescription = 'newDescription';
+
+ await service.update('', {
+ referralDescription: newDescription,
+ submissionDate: 0,
+ });
+
+ expect(mockRepository.findOneOrFail).toHaveBeenCalledTimes(1);
+ expect(mockRepository.save).toHaveBeenCalledTimes(1);
+ expect(mockReferral.referralDescription).toEqual(newDescription);
+ });
+
+ it('should call through for delete', async () => {
+ const mockReferral = new PlanningReferral();
+ mockRepository.softRemove.mockResolvedValue(mockReferral);
+ mockRepository.findOneOrFail.mockResolvedValue(mockReferral);
+
+ await service.delete('mock-uuid');
+
+ expect(mockRepository.findOneOrFail).toHaveBeenCalledTimes(1);
+ expect(mockRepository.softRemove).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.service.ts b/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.service.ts
new file mode 100644
index 0000000000..da903ebb1c
--- /dev/null
+++ b/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.service.ts
@@ -0,0 +1,153 @@
+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 { 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 {
+ CreatePlanningReferralDto,
+ PlanningReferralDto,
+ UpdatePlanningReferralDto,
+} from '../planning-review.dto';
+import { PlanningReview } from '../planning-review.entity';
+import { PlanningReferral } from './planning-referral.entity';
+
+@Injectable()
+export class PlanningReferralService {
+ constructor(
+ @InjectRepository(PlanningReferral)
+ private referralRepository: Repository,
+ @InjectRepository(PlanningReview)
+ private reviewRepository: Repository,
+ @InjectMapper()
+ private mapper: Mapper,
+ private cardService: CardService,
+ ) {}
+
+ private DEFAULT_RELATIONS: FindOptionsRelations = {
+ card: {
+ type: true,
+ status: true,
+ board: true,
+ },
+ planningReview: {
+ localGovernment: true,
+ region: true,
+ type: true,
+ },
+ };
+
+ async getByBoard(boardUuid: string) {
+ return this.referralRepository.find({
+ where: {
+ card: {
+ boardUuid,
+ },
+ },
+ relations: this.DEFAULT_RELATIONS,
+ });
+ }
+
+ async mapToDtos(planningReferrals: PlanningReferral[]) {
+ return this.mapper.mapArray(
+ planningReferrals,
+ PlanningReferral,
+ PlanningReferralDto,
+ );
+ }
+
+ get(uuid: string) {
+ return this.referralRepository.findOneOrFail({
+ where: {
+ uuid,
+ },
+ relations: this.DEFAULT_RELATIONS,
+ });
+ }
+
+ async getByCardUuid(uuid: string) {
+ return this.referralRepository.findOneOrFail({
+ where: {
+ cardUuid: uuid,
+ },
+ relations: this.DEFAULT_RELATIONS,
+ });
+ }
+
+ getDeletedCards(fileNumber: string) {
+ return this.referralRepository.find({
+ where: {
+ planningReview: {
+ fileNumber: fileNumber,
+ },
+ card: {
+ auditDeletedDateAt: Not(IsNull()),
+ },
+ },
+ withDeleted: true,
+ relations: this.DEFAULT_RELATIONS,
+ });
+ }
+
+ async create(createDto: CreatePlanningReferralDto, board: Board) {
+ const review = await this.reviewRepository.findOneOrFail({
+ where: {
+ uuid: createDto.planningReviewUuid,
+ },
+ });
+
+ const referral = new PlanningReferral({
+ planningReview: review,
+ dueDate: formatIncomingDate(createDto.dueDate),
+ submissionDate: formatIncomingDate(createDto.submissionDate)!,
+ referralDescription: createDto.referralDescription,
+ card: await this.cardService.create(CARD_TYPE.PLAN, board, false),
+ });
+
+ await this.referralRepository.save(referral);
+
+ return this.get(referral.uuid);
+ }
+
+ async update(uuid: string, updateDto: UpdatePlanningReferralDto) {
+ const existingReferral = await this.referralRepository.findOneOrFail({
+ where: {
+ uuid,
+ },
+ });
+
+ existingReferral.referralDescription = filterUndefined(
+ updateDto.referralDescription,
+ existingReferral.referralDescription,
+ );
+ existingReferral.responseDescription = filterUndefined(
+ updateDto.responseDescription,
+ existingReferral.responseDescription,
+ );
+
+ existingReferral.responseDate = formatIncomingDate(updateDto.responseDate);
+ existingReferral.dueDate = formatIncomingDate(updateDto.dueDate);
+
+ if (updateDto.submissionDate) {
+ existingReferral.submissionDate = (
+ formatIncomingDate(updateDto.submissionDate)
+ );
+ }
+
+ await this.referralRepository.save(existingReferral);
+ }
+
+ async delete(uuid: string) {
+ const existingReferral = await this.referralRepository.findOneOrFail({
+ where: {
+ uuid,
+ },
+ });
+
+ await this.referralRepository.softRemove(existingReferral);
+ }
+}
diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.controller.spec.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.controller.spec.ts
new file mode 100644
index 0000000000..8de38ef3fc
--- /dev/null
+++ b/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.controller.spec.ts
@@ -0,0 +1,189 @@
+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 { PlanningReviewProfile } from '../../../common/automapper/planning-review.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 { PlanningReviewDocumentController } from './planning-review-document.controller';
+import { PlanningReviewDocument } from './planning-review-document.entity';
+import { PlanningReviewDocumentService } from './planning-review-document.service';
+
+describe('PlanningReviewDocumentController', () => {
+ let controller: PlanningReviewDocumentController;
+ let mockPlanningReviewDocumentService: DeepMocked;
+
+ const mockDocument = new PlanningReviewDocument({
+ document: new Document({
+ mimeType: 'mimeType',
+ uploadedBy: new User(),
+ uploadedAt: new Date(),
+ }),
+ });
+
+ beforeEach(async () => {
+ mockPlanningReviewDocumentService = createMock();
+
+ const module: TestingModule = await Test.createTestingModule({
+ imports: [
+ AutomapperModule.forRoot({
+ strategyInitializer: classes(),
+ }),
+ ],
+ controllers: [PlanningReviewDocumentController],
+ providers: [
+ {
+ provide: CodeService,
+ useValue: {},
+ },
+ PlanningReviewProfile,
+ {
+ provide: PlanningReviewDocumentService,
+ useValue: mockPlanningReviewDocumentService,
+ },
+ {
+ provide: ClsService,
+ useValue: {},
+ },
+ ...mockKeyCloakProviders,
+ ],
+ }).compile();
+ controller = module.get(
+ PlanningReviewDocumentController,
+ );
+ });
+
+ it('should be defined', () => {
+ expect(controller).toBeDefined();
+ });
+
+ it('should return the attached document', async () => {
+ const mockFile = {};
+ const mockUser = {};
+
+ mockPlanningReviewDocumentService.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(
+ mockPlanningReviewDocumentService.attachDocument,
+ ).toHaveBeenCalledTimes(1);
+ const callData =
+ mockPlanningReviewDocumentService.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 = {};
+
+ mockPlanningReviewDocumentService.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 () => {
+ mockPlanningReviewDocumentService.list.mockResolvedValue([mockDocument]);
+
+ const res = await controller.listDocuments(
+ 'fake-number',
+ DOCUMENT_TYPE.DECISION_DOCUMENT,
+ );
+
+ expect(res[0].mimeType).toEqual(mockDocument.document.mimeType);
+ });
+
+ it('should call through to delete documents', async () => {
+ mockPlanningReviewDocumentService.delete.mockResolvedValue(mockDocument);
+ mockPlanningReviewDocumentService.get.mockResolvedValue(mockDocument);
+
+ await controller.delete('fake-uuid');
+
+ expect(mockPlanningReviewDocumentService.get).toHaveBeenCalledTimes(1);
+ expect(mockPlanningReviewDocumentService.delete).toHaveBeenCalledTimes(1);
+ });
+
+ it('should call through for open', async () => {
+ const fakeUrl = 'fake-url';
+ mockPlanningReviewDocumentService.getInlineUrl.mockResolvedValue(fakeUrl);
+ mockPlanningReviewDocumentService.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';
+ mockPlanningReviewDocumentService.getDownloadUrl.mockResolvedValue(fakeUrl);
+ mockPlanningReviewDocumentService.get.mockResolvedValue(mockDocument);
+
+ const res = await controller.download('fake-uuid');
+
+ expect(res.url).toEqual(fakeUrl);
+ });
+
+ it('should call through for list types', async () => {
+ mockPlanningReviewDocumentService.fetchTypes.mockResolvedValue([]);
+
+ const res = await controller.listTypes();
+
+ expect(mockPlanningReviewDocumentService.fetchTypes).toHaveBeenCalledTimes(
+ 1,
+ );
+ });
+
+ it('should call through for setting sort', async () => {
+ mockPlanningReviewDocumentService.setSorting.mockResolvedValue();
+
+ await controller.sortDocuments([]);
+
+ expect(mockPlanningReviewDocumentService.setSorting).toHaveBeenCalledTimes(
+ 1,
+ );
+ });
+});
diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.controller.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.controller.ts
new file mode 100644
index 0000000000..7cc91ebe29
--- /dev/null
+++ b/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.controller.ts
@@ -0,0 +1,206 @@
+import {
+ BadRequestException,
+ Body,
+ 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 { PlanningReviewDocumentDto } from './planning-review-document.dto';
+import {
+ PlanningReviewDocument,
+ PR_VISIBILITY_FLAG,
+} from './planning-review-document.entity';
+import { PlanningReviewDocumentService } from './planning-review-document.service';
+
+@ApiOAuth2(config.get('KEYCLOAK.SCOPES'))
+@UseGuards(RolesGuard)
+@Controller('planning-review-document')
+export class PlanningReviewDocumentController {
+ constructor(
+ private planningReviewDocumentService: PlanningReviewDocumentService,
+ @InjectMapper() private mapper: Mapper,
+ ) {}
+
+ @Get('/planning-review/:fileNumber')
+ @UserRoles(...ANY_AUTH_ROLE)
+ async listAll(
+ @Param('fileNumber') fileNumber: string,
+ ): Promise {
+ const documents = await this.planningReviewDocumentService.list(fileNumber);
+ return this.mapper.mapArray(
+ documents,
+ PlanningReviewDocument,
+ PlanningReviewDocumentDto,
+ );
+ }
+
+ @Post('/planning-review/:fileNumber')
+ @UserRoles(...ANY_AUTH_ROLE)
+ async attachDocument(
+ @Param('fileNumber') fileNumber: string,
+ @Req() req,
+ ): Promise {
+ if (!req.isMultipart()) {
+ throw new BadRequestException('Request is not multipart');
+ }
+
+ const savedDocument = await this.saveUploadedFile(req, fileNumber);
+
+ return this.mapper.map(
+ savedDocument,
+ PlanningReviewDocument,
+ PlanningReviewDocumentDto,
+ );
+ }
+
+ @Post('/:uuid')
+ @UserRoles(...ANY_AUTH_ROLE)
+ async updateDocument(
+ @Param('uuid') documentUuid: string,
+ @Req() req,
+ ): Promise {
+ 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 visibilityFlags = req.body.visibilityFlags.value.split(', ');
+
+ const savedDocument = await this.planningReviewDocumentService.update({
+ uuid: documentUuid,
+ fileName,
+ file,
+ documentType: documentType as DOCUMENT_TYPE,
+ source: documentSource,
+ visibilityFlags,
+ user: req.user.entity,
+ });
+
+ return this.mapper.map(
+ savedDocument,
+ PlanningReviewDocument,
+ PlanningReviewDocumentDto,
+ );
+ }
+
+ @Get('/planning-review/:fileNumber/reviewDocuments')
+ @UserRoles(...ANY_AUTH_ROLE)
+ async listReviewDocuments(
+ @Param('fileNumber') fileNumber: string,
+ ): Promise {
+ const documents = await this.planningReviewDocumentService.list(fileNumber);
+ const reviewDocuments = documents.filter(
+ (doc) => doc.document.source === DOCUMENT_SOURCE.LFNG,
+ );
+
+ return this.mapper.mapArray(
+ reviewDocuments,
+ PlanningReviewDocument,
+ PlanningReviewDocumentDto,
+ );
+ }
+
+ @Get('/planning-review/:fileNumber/:visibilityFlags')
+ @UserRoles(...ANY_AUTH_ROLE)
+ async listDocuments(
+ @Param('fileNumber') fileNumber: string,
+ @Param('visibilityFlags') visibilityFlags: string,
+ ): Promise {
+ const mappedFlags = visibilityFlags.split('') as PR_VISIBILITY_FLAG[];
+ const documents = await this.planningReviewDocumentService.list(
+ fileNumber,
+ mappedFlags,
+ );
+ return this.mapper.mapArray(
+ documents,
+ PlanningReviewDocument,
+ PlanningReviewDocumentDto,
+ );
+ }
+
+ @Get('/types')
+ @UserRoles(...ANY_AUTH_ROLE)
+ async listTypes() {
+ const types = await this.planningReviewDocumentService.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.planningReviewDocumentService.get(fileUuid);
+ const url = await this.planningReviewDocumentService.getInlineUrl(document);
+ return {
+ url,
+ };
+ }
+
+ @Get('/:uuid/download')
+ @UserRoles(...ANY_AUTH_ROLE)
+ async download(@Param('uuid') fileUuid: string) {
+ const document = await this.planningReviewDocumentService.get(fileUuid);
+ const url =
+ await this.planningReviewDocumentService.getDownloadUrl(document);
+ return {
+ url,
+ };
+ }
+
+ @Delete('/:uuid')
+ @UserRoles(...ANY_AUTH_ROLE)
+ async delete(@Param('uuid') fileUuid: string) {
+ const document = await this.planningReviewDocumentService.get(fileUuid);
+ await this.planningReviewDocumentService.delete(document);
+ return {};
+ }
+
+ @Post('/sort')
+ @UserRoles(...ANY_AUTH_ROLE)
+ async sortDocuments(
+ @Body() data: { uuid: string; order: number }[],
+ ): Promise {
+ await this.planningReviewDocumentService.setSorting(data);
+ }
+
+ 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;
+ const visibilityFlags = req.body.visibilityFlags.value.split(', ');
+
+ return await this.planningReviewDocumentService.attachDocument({
+ fileNumber,
+ fileName,
+ file,
+ user: req.user.entity,
+ documentType: documentType as DOCUMENT_TYPE,
+ source: documentSource,
+ visibilityFlags,
+ system: DOCUMENT_SYSTEM.ALCS,
+ });
+ }
+}
diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.dto.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.dto.ts
new file mode 100644
index 0000000000..14b50a1387
--- /dev/null
+++ b/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.dto.ts
@@ -0,0 +1,29 @@
+import { AutoMap } from 'automapper-classes';
+import { DocumentTypeDto } from '../../../document/document.dto';
+
+export class PlanningReviewDocumentDto {
+ @AutoMap(() => String)
+ description?: string;
+
+ @AutoMap()
+ uuid: string;
+
+ @AutoMap(() => DocumentTypeDto)
+ type?: DocumentTypeDto;
+
+ @AutoMap(() => [String])
+ visibilityFlags: string[];
+
+ @AutoMap(() => [Number])
+ evidentiaryRecordSorting?: number;
+
+ //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/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
new file mode 100644
index 0000000000..3ae3f2c7b2
--- /dev/null
+++ b/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.entity.ts
@@ -0,0 +1,64 @@
+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 { PlanningReview } from '../planning-review.entity';
+
+export enum PR_VISIBILITY_FLAG {
+ COMMISSIONER = 'C',
+}
+
+@Entity({
+ comment: 'Stores planning review documents',
+})
+export class PlanningReviewDocument extends BaseEntity {
+ constructor(data?: Partial) {
+ super();
+ if (data) {
+ Object.assign(this, data);
+ }
+ }
+
+ @AutoMap()
+ @PrimaryGeneratedColumn('uuid')
+ uuid: string;
+
+ @ManyToOne(() => DocumentCode)
+ type?: DocumentCode;
+
+ @Column({ nullable: true })
+ typeCode?: string | null;
+
+ @Column({ type: 'text', nullable: true })
+ description?: string | null;
+
+ @ManyToOne(() => PlanningReview, { nullable: false })
+ planningReview: PlanningReview;
+
+ @Column()
+ @Index()
+ planningReviewUuid: string;
+
+ @Column({ nullable: true, type: 'uuid' })
+ documentUuid?: string | null;
+
+ @AutoMap(() => [String])
+ @Column({ default: [], array: true, type: 'text' })
+ visibilityFlags: PR_VISIBILITY_FLAG[];
+
+ @Column({ nullable: true, type: 'int' })
+ evidentiaryRecordSorting?: number | null;
+
+ @OneToOne(() => Document)
+ @JoinColumn()
+ document: Document;
+}
diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.service.spec.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.service.spec.ts
new file mode 100644
index 0000000000..74805b78a3
--- /dev/null
+++ b/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.service.spec.ts
@@ -0,0 +1,293 @@
+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 { PlanningReview } from '../planning-review.entity';
+import { PlanningReviewService } from '../planning-review.service';
+import { PlanningReviewDocument } from './planning-review-document.entity';
+import { PlanningReviewDocumentService } from './planning-review-document.service';
+
+describe('PlanningReviewDocumentService', () => {
+ let service: PlanningReviewDocumentService;
+ let mockDocumentService: DeepMocked;
+ let mockPlanningReviewService: DeepMocked;
+ let mockRepository: DeepMocked>;
+ let mockTypeRepository: DeepMocked>;
+
+ let mockPlanningReview;
+ const fileNumber = '12345';
+
+ beforeEach(async () => {
+ mockDocumentService = createMock();
+ mockPlanningReviewService = createMock();
+ mockRepository = createMock();
+ mockTypeRepository = createMock();
+
+ mockPlanningReview = new PlanningReview({
+ fileNumber,
+ });
+ mockPlanningReviewService.getDetailedReview.mockResolvedValue(
+ mockPlanningReview,
+ );
+ mockDocumentService.create.mockResolvedValue({} as Document);
+
+ const module: TestingModule = await Test.createTestingModule({
+ providers: [
+ PlanningReviewDocumentService,
+ {
+ provide: DocumentService,
+ useValue: mockDocumentService,
+ },
+ {
+ provide: PlanningReviewService,
+ useValue: mockPlanningReviewService,
+ },
+ {
+ provide: getRepositoryToken(DocumentCode),
+ useValue: mockTypeRepository,
+ },
+ {
+ provide: getRepositoryToken(PlanningReviewDocument),
+ useValue: mockRepository,
+ },
+ ],
+ }).compile();
+
+ service = module.get(
+ PlanningReviewDocumentService,
+ );
+ });
+
+ 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 PlanningReviewDocument,
+ );
+
+ 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,
+ visibilityFlags: [],
+ });
+
+ expect(mockPlanningReviewService.getDetailedReview).toHaveBeenCalledTimes(
+ 1,
+ );
+ expect(mockDocumentService.create).toHaveBeenCalledTimes(1);
+ expect(mockDocumentService.create.mock.calls[0][0]).toBe(
+ 'planning-review/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].planningReview).toBe(
+ mockPlanningReview,
+ );
+
+ expect(res).toBe(mockSavedDocument);
+ });
+
+ it('should delete document and planning review document when deleting', async () => {
+ const mockDocument = {};
+ const mockAppDocument = {
+ uuid: '1',
+ document: mockDocument,
+ } as PlanningReviewDocument;
+
+ mockDocumentService.softRemove.mockResolvedValue();
+ mockRepository.remove.mockResolvedValue({} as any);
+
+ await service.delete(mockAppDocument);
+
+ 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(mockAppDocument);
+ });
+
+ it('should call through for get', async () => {
+ const mockDocument = {};
+ const mockAppDocument = {
+ uuid: '1',
+ document: mockDocument,
+ } as PlanningReviewDocument;
+
+ mockDocumentService.softRemove.mockResolvedValue();
+ mockRepository.findOne.mockResolvedValue(mockAppDocument);
+
+ const res = await service.get('fake-uuid');
+ expect(res).toBe(mockAppDocument);
+ });
+
+ it("should throw an exception when getting a document that doesn't exist", async () => {
+ const mockDocument = {};
+ const mockAppDocument = {
+ uuid: '1',
+ document: mockDocument,
+ } as PlanningReviewDocument;
+
+ mockDocumentService.softRemove.mockResolvedValue();
+ mockRepository.findOne.mockResolvedValue(null);
+
+ await expect(service.get(mockAppDocument.uuid)).rejects.toMatchObject(
+ new ServiceNotFoundException(
+ `Failed to find document ${mockAppDocument.uuid}`,
+ ),
+ );
+ });
+
+ it('should call through for list', async () => {
+ const mockDocument = {};
+ const mockAppDocument = {
+ uuid: '1',
+ document: mockDocument,
+ } as PlanningReviewDocument;
+ mockRepository.find.mockResolvedValue([mockAppDocument]);
+
+ const res = await service.list(fileNumber);
+
+ expect(mockRepository.find).toHaveBeenCalledTimes(1);
+ expect(res[0]).toBe(mockAppDocument);
+ });
+
+ it('should call through for download', async () => {
+ const mockDocument = {};
+ const mockAppDocument = {
+ uuid: '1',
+ document: mockDocument,
+ } as PlanningReviewDocument;
+
+ const fakeUrl = 'mock-url';
+ mockDocumentService.getDownloadUrl.mockResolvedValue(fakeUrl);
+
+ const res = await service.getInlineUrl(mockAppDocument);
+
+ expect(mockDocumentService.getDownloadUrl).toHaveBeenCalledTimes(1);
+ expect(res).toEqual(fakeUrl);
+ });
+
+ 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 PlanningReviewDocument());
+ mockPlanningReviewService.getDetailedReview.mockResolvedValueOnce(
+ mockPlanningReview,
+ );
+ mockRepository.findOne.mockResolvedValue(new PlanningReviewDocument());
+
+ const res = await service.attachExternalDocument(
+ '',
+ {
+ type: DOCUMENT_TYPE.CERTIFICATE_OF_TITLE,
+ description: '',
+ documentUuid: 'fake-uuid',
+ },
+ [],
+ );
+
+ expect(mockPlanningReviewService.getDetailedReview).toHaveBeenCalledTimes(
+ 1,
+ );
+ expect(mockRepository.save).toHaveBeenCalledTimes(1);
+ expect(mockRepository.save.mock.calls[0][0].planningReview).toBe(
+ mockPlanningReview,
+ );
+ 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 PlanningReviewDocument({
+ document: new Document(),
+ }),
+ );
+ mockPlanningReviewService.getFileNumber.mockResolvedValue(
+ mockPlanningReview,
+ );
+ mockRepository.save.mockResolvedValue(new PlanningReviewDocument());
+ 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,
+ visibilityFlags: [],
+ });
+
+ expect(mockRepository.findOne).toHaveBeenCalledTimes(1);
+ expect(mockPlanningReviewService.getFileNumber).toHaveBeenCalledTimes(1);
+ expect(mockDocumentService.create).toHaveBeenCalledTimes(1);
+ expect(mockRepository.save).toHaveBeenCalledTimes(1);
+ });
+
+ it('should load and save the documents with the new sort order', async () => {
+ const mockDoc1 = new PlanningReviewDocument({
+ uuid: 'uuid-1',
+ evidentiaryRecordSorting: 5,
+ });
+ const mockDoc2 = new PlanningReviewDocument({
+ uuid: 'uuid-2',
+ evidentiaryRecordSorting: 6,
+ });
+ mockRepository.find.mockResolvedValue([mockDoc1, mockDoc2]);
+ mockRepository.save.mockResolvedValue({} as any);
+
+ await service.setSorting([
+ {
+ uuid: mockDoc1.uuid,
+ order: 0,
+ },
+ {
+ uuid: mockDoc2.uuid,
+ order: 1,
+ },
+ ]);
+
+ expect(mockRepository.find).toHaveBeenCalledTimes(1);
+ expect(mockRepository.save).toHaveBeenCalledTimes(1);
+ expect(mockDoc1.evidentiaryRecordSorting).toEqual(0);
+ expect(mockDoc2.evidentiaryRecordSorting).toEqual(1);
+ });
+});
diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.service.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.service.ts
new file mode 100644
index 0000000000..d029a129de
--- /dev/null
+++ b/services/apps/alcs/src/alcs/planning-review/planning-review-document/planning-review-document.service.ts
@@ -0,0 +1,219 @@
+import { MultipartFile } from '@fastify/multipart';
+import { Injectable, NotFoundException } from '@nestjs/common';
+import { InjectRepository } from '@nestjs/typeorm';
+import {
+ ArrayOverlap,
+ FindOptionsRelations,
+ FindOptionsWhere,
+ In,
+ 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 { PlanningReviewService } from '../planning-review.service';
+import {
+ PlanningReviewDocument,
+ PR_VISIBILITY_FLAG,
+} from './planning-review-document.entity';
+
+@Injectable()
+export class PlanningReviewDocumentService {
+ private DEFAULT_RELATIONS: FindOptionsRelations = {
+ document: true,
+ type: true,
+ };
+
+ constructor(
+ private documentService: DocumentService,
+ private planningReviewService: PlanningReviewService,
+ @InjectRepository(PlanningReviewDocument)
+ private planningReviewDocumentRepo: Repository,
+ @InjectRepository(DocumentCode)
+ private documentCodeRepository: Repository,
+ ) {}
+
+ async attachDocument({
+ fileNumber,
+ fileName,
+ file,
+ documentType,
+ user,
+ system,
+ source = DOCUMENT_SOURCE.ALC,
+ visibilityFlags = [],
+ }: {
+ fileNumber: string;
+ fileName: string;
+ file: MultipartFile;
+ user: User;
+ documentType: DOCUMENT_TYPE;
+ source?: DOCUMENT_SOURCE;
+ system: DOCUMENT_SYSTEM;
+ visibilityFlags: PR_VISIBILITY_FLAG[];
+ }) {
+ const planningReview =
+ await this.planningReviewService.getDetailedReview(fileNumber);
+ const document = await this.documentService.create(
+ `planning-review/${fileNumber}`,
+ fileName,
+ file,
+ user,
+ source,
+ system,
+ );
+ const appDocument = new PlanningReviewDocument({
+ typeCode: documentType,
+ planningReview,
+ document,
+ visibilityFlags,
+ });
+
+ return this.planningReviewDocumentRepo.save(appDocument);
+ }
+
+ async get(uuid: string) {
+ const document = await this.planningReviewDocumentRepo.findOne({
+ where: {
+ uuid: uuid,
+ },
+ relations: this.DEFAULT_RELATIONS,
+ });
+ if (!document) {
+ throw new NotFoundException(`Failed to find document ${uuid}`);
+ }
+ return document;
+ }
+
+ async delete(document: PlanningReviewDocument) {
+ await this.planningReviewDocumentRepo.remove(document);
+ await this.documentService.softRemove(document.document);
+ return document;
+ }
+
+ async list(fileNumber: string, visibilityFlags?: PR_VISIBILITY_FLAG[]) {
+ const where: FindOptionsWhere = {
+ planningReview: {
+ fileNumber,
+ },
+ };
+ if (visibilityFlags) {
+ where.visibilityFlags = ArrayOverlap(visibilityFlags);
+ }
+ return this.planningReviewDocumentRepo.find({
+ where,
+ order: {
+ document: {
+ uploadedAt: 'DESC',
+ },
+ },
+ relations: this.DEFAULT_RELATIONS,
+ });
+ }
+
+ async getInlineUrl(document: PlanningReviewDocument) {
+ return this.documentService.getDownloadUrl(document.document, true);
+ }
+
+ async getDownloadUrl(document: PlanningReviewDocument) {
+ return this.documentService.getDownloadUrl(document.document);
+ }
+
+ async attachExternalDocument(
+ fileNumber: string,
+ data: {
+ type?: DOCUMENT_TYPE;
+ documentUuid: string;
+ description?: string;
+ },
+ visibilityFlags: PR_VISIBILITY_FLAG[],
+ ) {
+ const planningReview =
+ await this.planningReviewService.getDetailedReview(fileNumber);
+ const document = new PlanningReviewDocument({
+ planningReview,
+ typeCode: data.type,
+ documentUuid: data.documentUuid,
+ description: data.description,
+ visibilityFlags,
+ });
+
+ const savedDocument = await this.planningReviewDocumentRepo.save(document);
+ return this.get(savedDocument.uuid);
+ }
+
+ async fetchTypes() {
+ return await this.documentCodeRepository.find();
+ }
+
+ async update({
+ uuid,
+ documentType,
+ file,
+ fileName,
+ source,
+ visibilityFlags,
+ user,
+ }: {
+ uuid: string;
+ file?: any;
+ fileName: string;
+ documentType: DOCUMENT_TYPE;
+ visibilityFlags: PR_VISIBILITY_FLAG[];
+ source: DOCUMENT_SOURCE;
+ user: User;
+ }) {
+ const appDocument = await this.get(uuid);
+
+ if (file) {
+ const fileNumber = await this.planningReviewService.getFileNumber(
+ appDocument.planningReviewUuid,
+ );
+ await this.documentService.softRemove(appDocument.document);
+ appDocument.document = await this.documentService.create(
+ `planning-review/${fileNumber}`,
+ fileName,
+ file,
+ user,
+ source,
+ appDocument.document.system as DOCUMENT_SYSTEM,
+ );
+ } else {
+ await this.documentService.update(appDocument.document, {
+ fileName,
+ source,
+ });
+ }
+ appDocument.type = undefined;
+ appDocument.typeCode = documentType;
+ appDocument.visibilityFlags = visibilityFlags;
+ return await this.planningReviewDocumentRepo.save(appDocument);
+ }
+
+ async setSorting(data: { uuid: string; order: number }[]) {
+ const uuids = data.map((data) => data.uuid);
+ const documents = await this.planningReviewDocumentRepo.find({
+ where: {
+ uuid: In(uuids),
+ },
+ });
+
+ for (const document of data) {
+ const existingDocument = documents.find(
+ (doc) => doc.uuid === document.uuid,
+ );
+ if (existingDocument) {
+ existingDocument.evidentiaryRecordSorting = document.order;
+ }
+ }
+
+ await this.planningReviewDocumentRepo.save(documents);
+ }
+}
diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-type.entity.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-type.entity.ts
new file mode 100644
index 0000000000..471a88d482
--- /dev/null
+++ b/services/apps/alcs/src/alcs/planning-review/planning-review-type.entity.ts
@@ -0,0 +1,29 @@
+import { AutoMap } from 'automapper-classes';
+import { Column, Entity } from 'typeorm';
+import { BaseCodeEntity } from '../../common/entities/base.code.entity';
+
+@Entity()
+export class PlanningReviewType extends BaseCodeEntity {
+ constructor(data?: Partial) {
+ super();
+ if (data) {
+ Object.assign(this, data);
+ }
+ }
+
+ @AutoMap()
+ @Column()
+ shortLabel: string;
+
+ @AutoMap()
+ @Column()
+ backgroundColor: string;
+
+ @AutoMap()
+ @Column()
+ textColor: string;
+
+ @AutoMap()
+ @Column({ type: 'text', default: '' })
+ htmlDescription: string;
+}
diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review.controller.spec.ts b/services/apps/alcs/src/alcs/planning-review/planning-review.controller.spec.ts
index 64126026ec..1004bf8ff6 100644
--- a/services/apps/alcs/src/alcs/planning-review/planning-review.controller.spec.ts
+++ b/services/apps/alcs/src/alcs/planning-review/planning-review.controller.spec.ts
@@ -1,9 +1,15 @@
import { createMock, DeepMocked } from '@golevelup/nestjs-testing';
import { Test, TestingModule } from '@nestjs/testing';
+import { classes } from 'automapper-classes';
+import { AutomapperModule } from 'automapper-nestjs';
import { ClsService } from 'nestjs-cls';
+import { mockKeyCloakProviders } from '../../../test/mocks/mockTypes';
+import { PlanningReviewProfile } from '../../common/automapper/planning-review.automapper.profile';
+import { FileNumberService } from '../../file-number/file-number.service';
import { Board } from '../board/board.entity';
import { BoardService } from '../board/board.service';
-import { mockKeyCloakProviders } from '../../../test/mocks/mockTypes';
+import { PlanningReferral } from './planning-referral/planning-referral.entity';
+import { PlanningReferralService } from './planning-referral/planning-referral.service';
import { PlanningReviewController } from './planning-review.controller';
import { PlanningReview } from './planning-review.entity';
import { PlanningReviewService } from './planning-review.service';
@@ -11,19 +17,31 @@ import { PlanningReviewService } from './planning-review.service';
describe('PlanningReviewController', () => {
let controller: PlanningReviewController;
let mockService: DeepMocked;
+ let mockPlanningReferralService: DeepMocked;
let mockBoardService: DeepMocked;
beforeEach(async () => {
- mockService = createMock();
- mockBoardService = createMock();
+ mockService = createMock();
+ mockBoardService = createMock();
+ mockPlanningReferralService = createMock();
const module: TestingModule = await Test.createTestingModule({
+ imports: [
+ AutomapperModule.forRoot({
+ strategyInitializer: classes(),
+ }),
+ ],
controllers: [PlanningReviewController],
providers: [
+ PlanningReviewProfile,
{
provide: PlanningReviewService,
useValue: mockService,
},
+ {
+ provide: PlanningReferralService,
+ useValue: mockPlanningReferralService,
+ },
{
provide: BoardService,
useValue: mockBoardService,
@@ -45,29 +63,38 @@ describe('PlanningReviewController', () => {
it('should call board service then main service for create', async () => {
mockBoardService.getOneOrFail.mockResolvedValue({} as Board);
- mockService.create.mockResolvedValue({} as PlanningReview);
- mockService.mapToDtos.mockResolvedValue([]);
+ mockService.create.mockResolvedValue(new PlanningReferral());
+ mockPlanningReferralService.get.mockResolvedValue(new PlanningReferral());
+ mockPlanningReferralService.mapToDtos.mockResolvedValue([]);
await controller.create({
- type: 'type',
+ description: 'description',
+ documentName: 'documentName',
+ submissionDate: 0,
+ typeCode: 'typeCode',
localGovernmentUuid: 'local-gov-uuid',
- fileNumber: 'file-number',
regionCode: 'region-code',
- boardCode: 'board-code',
});
expect(mockBoardService.getOneOrFail).toHaveBeenCalledTimes(1);
expect(mockService.create).toHaveBeenCalledTimes(1);
- expect(mockService.mapToDtos).toHaveBeenCalledTimes(1);
+ expect(mockPlanningReferralService.get).toHaveBeenCalledTimes(1);
+ expect(mockPlanningReferralService.mapToDtos).toHaveBeenCalledTimes(1);
+ });
+
+ it('should call service for fetch types', async () => {
+ mockService.listTypes.mockResolvedValue([]);
+
+ await controller.fetchTypes();
+
+ expect(mockService.listTypes).toHaveBeenCalledTimes(1);
});
- it('should call through to service for get card', async () => {
- mockService.getByCardUuid.mockResolvedValue({} as PlanningReview);
- mockService.mapToDtos.mockResolvedValue([]);
+ it('should call service for fetch by file number', async () => {
+ mockService.getDetailedReview.mockResolvedValue(new PlanningReview());
- await controller.getByCard('uuid');
+ await controller.fetchByFileNumber('file-number');
- expect(mockService.getByCardUuid).toHaveBeenCalledTimes(1);
- expect(mockService.mapToDtos).toHaveBeenCalledTimes(1);
+ expect(mockService.getDetailedReview).toHaveBeenCalledTimes(1);
});
});
diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review.controller.ts b/services/apps/alcs/src/alcs/planning-review/planning-review.controller.ts
index a1e7879159..374073a846 100644
--- a/services/apps/alcs/src/alcs/planning-review/planning-review.controller.ts
+++ b/services/apps/alcs/src/alcs/planning-review/planning-review.controller.ts
@@ -1,11 +1,22 @@
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 { BoardService } from '../board/board.service';
import { ROLES_ALLOWED_BOARDS } from '../../common/authorization/roles';
import { RolesGuard } from '../../common/authorization/roles-guard.service';
import { UserRoles } from '../../common/authorization/roles.decorator';
-import { CreatePlanningReviewDto } from './planning-review.dto';
+import { BOARD_CODES } from '../board/board.dto';
+import { BoardService } from '../board/board.service';
+import { PlanningReferralService } from './planning-referral/planning-referral.service';
+import { PlanningReviewType } from './planning-review-type.entity';
+import {
+ CreatePlanningReviewDto,
+ PlanningReviewDetailedDto,
+ PlanningReviewTypeDto,
+ UpdatePlanningReviewDto,
+} from './planning-review.dto';
+import { PlanningReview } from './planning-review.entity';
import { PlanningReviewService } from './planning-review.service';
@Controller('planning-review')
@@ -14,35 +25,64 @@ import { PlanningReviewService } from './planning-review.service';
export class PlanningReviewController {
constructor(
private planningReviewService: PlanningReviewService,
+ private planningReferralService: PlanningReferralService,
private boardService: BoardService,
+ @InjectMapper()
+ private mapper: Mapper,
) {}
+ @Get('/types')
+ @UserRoles(...ROLES_ALLOWED_BOARDS)
+ async fetchTypes() {
+ const types = await this.planningReviewService.listTypes();
+
+ return this.mapper.mapArray(
+ types,
+ PlanningReviewType,
+ PlanningReviewTypeDto,
+ );
+ }
+
@Post()
@UserRoles(...ROLES_ALLOWED_BOARDS)
async create(@Body() createDto: CreatePlanningReviewDto) {
const board = await this.boardService.getOneOrFail({
- code: createDto.boardCode,
+ code: BOARD_CODES.REGIONAL_PLANNING,
});
- if (!board) {
- throw new Error('Failed to load executive board');
- }
-
- const createdReview = await this.planningReviewService.create(
+ const createdReferral = await this.planningReviewService.create(
createDto,
board,
);
- const mapped = await this.planningReviewService.mapToDtos([createdReview]);
+ const referral = await this.planningReferralService.get(
+ createdReferral.uuid,
+ );
+
+ const mapped = await this.planningReferralService.mapToDtos([referral]);
return mapped[0];
}
- @Get('/card/:uuid')
+ @Get('/:fileNumber')
@UserRoles(...ROLES_ALLOWED_BOARDS)
- async getByCard(@Param('uuid') cardUuid: string) {
- const planningReview =
- await this.planningReviewService.getByCardUuid(cardUuid);
- const mapped = await this.planningReviewService.mapToDtos([planningReview]);
- return mapped[0];
+ async fetchByFileNumber(@Param('fileNumber') fileNumber: string) {
+ const review =
+ await this.planningReviewService.getDetailedReview(fileNumber);
+
+ return this.mapper.map(review, PlanningReview, PlanningReviewDetailedDto);
+ }
+
+ @Post('/:fileNumber')
+ @UserRoles(...ROLES_ALLOWED_BOARDS)
+ async updateByFileNumber(
+ @Param('fileNumber') fileNumber: string,
+ @Body() updateDto: UpdatePlanningReviewDto,
+ ) {
+ const review = await this.planningReviewService.update(
+ fileNumber,
+ updateDto,
+ );
+
+ return this.mapper.map(review, PlanningReview, PlanningReviewDetailedDto);
}
}
diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review.dto.ts b/services/apps/alcs/src/alcs/planning-review/planning-review.dto.ts
index 1bd1e97c8f..39442427c9 100644
--- a/services/apps/alcs/src/alcs/planning-review/planning-review.dto.ts
+++ b/services/apps/alcs/src/alcs/planning-review/planning-review.dto.ts
@@ -1,18 +1,44 @@
import { AutoMap } from 'automapper-classes';
-import { IsNotEmpty, IsString, MaxLength } from 'class-validator';
-import { LocalGovernmentDto } from '../local-government/local-government.dto';
+import {
+ IsBoolean,
+ IsNotEmpty,
+ IsNumber,
+ IsOptional,
+ IsString,
+ IsUUID,
+} from 'class-validator';
+import { BaseCodeDto } from '../../common/dtos/base.dto';
import { CardDto } from '../card/card.dto';
import { ApplicationRegionDto } from '../code/application-code/application-region/application-region.dto';
+import { LocalGovernmentDto } from '../local-government/local-government.dto';
+
+export class PlanningReviewTypeDto extends BaseCodeDto {
+ @AutoMap()
+ shortLabel: string;
+
+ @AutoMap()
+ backgroundColor: string;
+
+ @AutoMap()
+ textColor: string;
+}
export class CreatePlanningReviewDto {
@IsString()
@IsNotEmpty()
- fileNumber: string;
+ description: string;
@IsString()
@IsNotEmpty()
- @MaxLength(40)
- type: string;
+ documentName: string;
+
+ @IsNumber()
+ @IsNotEmpty()
+ submissionDate: number;
+
+ @IsNumber()
+ @IsOptional()
+ dueDate?: number;
@IsString()
@IsNotEmpty()
@@ -20,26 +46,120 @@ export class CreatePlanningReviewDto {
@IsString()
@IsNotEmpty()
- regionCode: string;
+ typeCode: string;
@IsString()
@IsNotEmpty()
- boardCode: string;
+ regionCode: string;
}
export class PlanningReviewDto {
+ @AutoMap()
+ uuid: string;
+
@AutoMap()
fileNumber: string;
+ @AutoMap(() => String)
+ legacyId: string | null;
+
@AutoMap()
- card: CardDto;
+ open: boolean;
@AutoMap()
- localGovernment: LocalGovernmentDto;
+ documentName: string;
+
+ @AutoMap()
+ localGovernmentUuid: string;
@AutoMap()
+ typeCode: string;
+
+ @AutoMap()
+ regionCode: string;
+
+ @AutoMap(() => LocalGovernmentDto)
+ localGovernment: LocalGovernmentDto;
+
+ @AutoMap(() => ApplicationRegionDto)
region: ApplicationRegionDto;
+ @AutoMap(() => PlanningReviewTypeDto)
+ type: PlanningReviewTypeDto;
+}
+
+export class CreatePlanningReferralDto {
+ @IsUUID()
+ @IsNotEmpty()
+ planningReviewUuid: string;
+
+ @IsString()
+ @IsNotEmpty()
+ referralDescription: string;
+
+ @IsNumber()
+ @IsNotEmpty()
+ submissionDate: number;
+
+ @IsNumber()
+ @IsOptional()
+ dueDate?: number;
+}
+
+export class UpdatePlanningReferralDto {
+ @IsString()
+ @IsOptional()
+ referralDescription?: string;
+
+ @IsNumber()
+ @IsOptional()
+ submissionDate?: number;
+
+ @IsNumber()
+ @IsOptional()
+ dueDate?: number;
+
+ @IsNumber()
+ @IsOptional()
+ responseDate?: number;
+
+ @IsString()
+ @IsOptional()
+ responseDescription?: string;
+}
+
+export class PlanningReferralDto {
@AutoMap()
- type: string;
+ uuid: string;
+
+ dueDate: number;
+ submissionDate: number;
+ responseDate?: number;
+
+ @AutoMap(() => String)
+ referralDescription?: string;
+
+ @AutoMap(() => PlanningReviewDto)
+ planningReview: PlanningReviewDto;
+
+ @AutoMap(() => String)
+ responseDescription?: string;
+
+ @AutoMap(() => CardDto)
+ card: CardDto;
+}
+
+export class PlanningReviewDetailedDto extends PlanningReviewDto {
+ @AutoMap(() => [PlanningReferralDto])
+ referrals: PlanningReferralDto[];
+}
+
+export class UpdatePlanningReviewDto {
+ @IsBoolean()
+ @IsOptional()
+ open?: boolean;
+
+ @IsString()
+ @IsOptional()
+ typeCode?: string;
}
diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review.entity.ts b/services/apps/alcs/src/alcs/planning-review/planning-review.entity.ts
index 908e4f9d3e..d56139b567 100644
--- a/services/apps/alcs/src/alcs/planning-review/planning-review.entity.ts
+++ b/services/apps/alcs/src/alcs/planning-review/planning-review.entity.ts
@@ -1,18 +1,15 @@
-import { Type } from 'class-transformer';
-import {
- Column,
- Entity,
- Index,
- JoinColumn,
- ManyToOne,
- OneToOne,
-} from 'typeorm';
+import { AutoMap } from 'automapper-classes';
+import { Column, Entity, Index, ManyToOne, OneToMany } from 'typeorm';
import { Base } from '../../common/entities/base.entity';
-import { Card } from '../card/card.entity';
+import { User } from '../../user/user.entity';
import { ApplicationRegion } from '../code/application-code/application-region/application-region.entity';
import { LocalGovernment } from '../local-government/local-government.entity';
+import { PlanningReferral } from './planning-referral/planning-referral.entity';
+import { PlanningReviewType } from './planning-review-type.entity';
-@Entity()
+@Entity({
+ comment: 'A review of a local government or municipalities plan',
+})
export class PlanningReview extends Base {
constructor(data?: Partial) {
super();
@@ -21,20 +18,20 @@ export class PlanningReview extends Base {
}
}
- @Index()
@Column({ unique: true })
fileNumber: string;
- @Column()
- type: string;
-
- @Column({ type: 'uuid' })
- cardUuid: string;
+ @AutoMap(() => String)
+ @Column({
+ type: 'text',
+ comment:
+ 'Application Id that is applicable only to paper version applications from 70s - 80s',
+ nullable: true,
+ })
+ legacyId?: string | null;
- @OneToOne(() => Card, { cascade: true })
- @JoinColumn()
- @Type(() => Card)
- card: Card;
+ @Column({ nullable: false })
+ documentName: string;
@ManyToOne(() => LocalGovernment)
localGovernment: LocalGovernment;
@@ -50,4 +47,24 @@ export class PlanningReview extends Base {
@Column()
regionCode: string;
+
+ @AutoMap(() => PlanningReviewType)
+ @ManyToOne(() => PlanningReviewType, { nullable: false })
+ type: PlanningReviewType;
+
+ @AutoMap(() => [PlanningReferral])
+ @OneToMany(() => PlanningReferral, (referral) => referral.planningReview)
+ referrals: PlanningReferral[];
+
+ @Column()
+ typeCode: string;
+
+ @Column({ default: true })
+ open: boolean;
+
+ @ManyToOne(() => User)
+ closedBy: User;
+
+ @Column({ type: 'timestamptz', nullable: true })
+ closedDate: Date | null;
}
diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review.module.ts b/services/apps/alcs/src/alcs/planning-review/planning-review.module.ts
index d54f7dbc68..9b81109c95 100644
--- a/services/apps/alcs/src/alcs/planning-review/planning-review.module.ts
+++ b/services/apps/alcs/src/alcs/planning-review/planning-review.module.ts
@@ -1,22 +1,49 @@
import { forwardRef, Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
+import { PlanningReviewProfile } from '../../common/automapper/planning-review.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 { PlanningReviewProfile } from '../../common/automapper/planning-meeting.automapper.profile';
+import { PlanningReferralController } from './planning-referral/planning-referral.controller';
+import { PlanningReferral } from './planning-referral/planning-referral.entity';
+import { PlanningReferralService } from './planning-referral/planning-referral.service';
+import { 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 { PlanningReviewType } from './planning-review-type.entity';
import { PlanningReviewController } from './planning-review.controller';
import { PlanningReview } from './planning-review.entity';
import { PlanningReviewService } from './planning-review.service';
@Module({
imports: [
- TypeOrmModule.forFeature([PlanningReview]),
+ TypeOrmModule.forFeature([
+ PlanningReview,
+ PlanningReferral,
+ PlanningReviewType,
+ PlanningReviewDocument,
+ DocumentCode,
+ ]),
forwardRef(() => BoardModule),
CardModule,
CodeModule,
+ FileNumberModule,
+ DocumentModule,
],
- controllers: [PlanningReviewController],
- providers: [PlanningReviewService, PlanningReviewProfile],
- exports: [PlanningReviewService],
+ controllers: [
+ PlanningReviewController,
+ PlanningReferralController,
+ PlanningReviewDocumentController,
+ ],
+ providers: [
+ PlanningReviewService,
+ PlanningReviewProfile,
+ PlanningReferralService,
+ PlanningReviewDocumentService,
+ ],
+ exports: [PlanningReviewService, PlanningReferralService],
})
export class PlanningReviewModule {}
diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review.service.spec.ts b/services/apps/alcs/src/alcs/planning-review/planning-review.service.spec.ts
index 6e943f743e..447a0327f4 100644
--- a/services/apps/alcs/src/alcs/planning-review/planning-review.service.spec.ts
+++ b/services/apps/alcs/src/alcs/planning-review/planning-review.service.spec.ts
@@ -4,20 +4,29 @@ import { createMock, DeepMocked } from '@golevelup/nestjs-testing';
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
+import { FileNumberService } from '../../file-number/file-number.service';
import { Board } from '../board/board.entity';
import { Card } from '../card/card.entity';
import { CardService } from '../card/card.service';
+import { PlanningReferral } from './planning-referral/planning-referral.entity';
+import { PlanningReviewType } from './planning-review-type.entity';
import { PlanningReview } from './planning-review.entity';
import { PlanningReviewService } from './planning-review.service';
describe('PlanningReviewService', () => {
let service: PlanningReviewService;
let mockRepository: DeepMocked>;
+ let mockTypeRepository: DeepMocked>;
+ let mockReferralRepository: DeepMocked>;
let mockCardService: DeepMocked;
+ let mockFileNumberService: DeepMocked;
beforeEach(async () => {
- mockCardService = createMock();
- mockRepository = createMock>();
+ mockCardService = createMock();
+ mockRepository = createMock();
+ mockTypeRepository = createMock();
+ mockReferralRepository = createMock();
+ mockFileNumberService = createMock();
const module: TestingModule = await Test.createTestingModule({
imports: [
@@ -30,6 +39,18 @@ describe('PlanningReviewService', () => {
provide: getRepositoryToken(PlanningReview),
useValue: mockRepository,
},
+ {
+ provide: getRepositoryToken(PlanningReviewType),
+ useValue: mockTypeRepository,
+ },
+ {
+ provide: getRepositoryToken(PlanningReferral),
+ useValue: mockReferralRepository,
+ },
+ {
+ provide: FileNumberService,
+ useValue: mockFileNumberService,
+ },
{
provide: CardService,
useValue: mockCardService,
@@ -49,84 +70,33 @@ describe('PlanningReviewService', () => {
const mockCard = {} as Card;
const fakeBoard = {} as Board;
- mockRepository.findOne.mockResolvedValueOnce(null);
- mockRepository.findOne.mockResolvedValueOnce({} as PlanningReview);
- mockRepository.save.mockResolvedValue({} as PlanningReview);
- mockCardService.create.mockResolvedValue(mockCard);
-
- const res = await service.create(
- {
- type: 'fake-type',
- fileNumber: '1512311',
- localGovernmentUuid: 'fake-uuid',
- regionCode: 'region-code',
- boardCode: 'board-code',
- },
- fakeBoard,
+ mockFileNumberService.generateNextFileNumber.mockResolvedValue(1);
+ mockTypeRepository.findOneOrFail.mockResolvedValue(
+ new PlanningReviewType(),
);
-
- expect(mockRepository.findOne).toHaveBeenCalledTimes(2);
- expect(mockCardService.create).toHaveBeenCalledTimes(1);
- expect(mockRepository.save).toHaveBeenCalledTimes(1);
- expect(mockRepository.save.mock.calls[0][0].card).toBe(mockCard);
- });
-
- it('should throw an exception when creating a meeting with an existing file ID', async () => {
- const mockCard = {} as Card;
- const fakeBoard = {} as Board;
- const existingFileNumber = '1512311';
-
- mockRepository.findOne.mockResolvedValueOnce({} as PlanningReview);
mockRepository.save.mockResolvedValue({} as PlanningReview);
mockCardService.create.mockResolvedValue(mockCard);
+ mockReferralRepository.save.mockResolvedValue(new PlanningReferral());
- const promise = service.create(
+ await service.create(
{
- type: 'fake-type',
- fileNumber: existingFileNumber,
+ description: '',
+ documentName: '',
+ submissionDate: 0,
+ typeCode: '',
localGovernmentUuid: 'fake-uuid',
regionCode: 'region-code',
- boardCode: 'board-code',
},
fakeBoard,
);
- await expect(promise).rejects.toMatchObject(
- new Error(
- `Planning meeting already exists with File ID ${existingFileNumber}`,
- ),
- );
-
- expect(mockRepository.findOne).toHaveBeenCalledTimes(1);
- expect(mockCardService.create).not.toHaveBeenCalled();
- expect(mockRepository.save).not.toHaveBeenCalled();
- });
-
- it('should call through to the repo for get by card', async () => {
- mockRepository.findOne.mockResolvedValue({} as PlanningReview);
- const cardUuid = 'fake-card-uuid';
- await service.getByCardUuid(cardUuid);
-
- expect(mockRepository.findOne).toHaveBeenCalledTimes(1);
- });
-
- it('should throw an exception when getting by card fails', async () => {
- mockRepository.findOne.mockResolvedValue(null);
- const cardUuid = 'fake-card-uuid';
- const promise = service.getByCardUuid(cardUuid);
-
- await expect(promise).rejects.toMatchObject(
- new Error(`Failed to find planning meeting with card uuid ${cardUuid}`),
+ expect(mockFileNumberService.generateNextFileNumber).toHaveBeenCalledTimes(
+ 1,
);
-
- expect(mockRepository.findOne).toHaveBeenCalledTimes(1);
- });
-
- it('should call through to the repo for get cards', async () => {
- mockRepository.find.mockResolvedValue([]);
- await service.getByBoard('');
-
- expect(mockRepository.find).toHaveBeenCalledTimes(1);
+ expect(mockTypeRepository.findOneOrFail).toHaveBeenCalledTimes(1);
+ expect(mockCardService.create).toHaveBeenCalledTimes(1);
+ expect(mockRepository.save).toHaveBeenCalledTimes(1);
+ expect(mockReferralRepository.save).toHaveBeenCalledTimes(1);
});
it('should call through to the repo for getby', async () => {
@@ -134,18 +104,18 @@ describe('PlanningReviewService', () => {
uuid: '5',
};
mockRepository.find.mockResolvedValue([]);
+
await service.getBy(mockFilter);
expect(mockRepository.find).toHaveBeenCalledTimes(1);
expect(mockRepository.find.mock.calls[0][0]!.where).toEqual(mockFilter);
});
- it('should load deleted cards', async () => {
- mockRepository.find.mockResolvedValue([]);
+ it('should call through to the repo for getDetailedReview', async () => {
+ mockRepository.findOneOrFail.mockResolvedValue(new PlanningReview());
- await service.getDeletedCards('file-number');
+ await service.getDetailedReview('file-number');
- expect(mockRepository.find).toHaveBeenCalledTimes(1);
- expect(mockRepository.find.mock.calls[0][0]!.withDeleted).toEqual(true);
+ expect(mockRepository.findOneOrFail).toHaveBeenCalledTimes(1);
});
});
diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review.service.ts b/services/apps/alcs/src/alcs/planning-review/planning-review.service.ts
index 23ec5bc9d9..561b8c1dad 100644
--- a/services/apps/alcs/src/alcs/planning-review/planning-review.service.ts
+++ b/services/apps/alcs/src/alcs/planning-review/planning-review.service.ts
@@ -1,24 +1,21 @@
-import {
- ServiceNotFoundException,
- ServiceValidationException,
-} from '@app/common/exceptions/base.exception';
-import { Mapper } from 'automapper-core';
-import { InjectMapper } from 'automapper-nestjs';
+import { ServiceNotFoundException } from '@app/common/exceptions/base.exception';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
-import {
- FindOptionsRelations,
- FindOptionsWhere,
- IsNull,
- Not,
- Repository,
-} from 'typeorm';
+import { Mapper } from 'automapper-core';
+import { InjectMapper } from 'automapper-nestjs';
+import { FindOptionsRelations, FindOptionsWhere, Repository } from 'typeorm';
+import { FileNumberService } from '../../file-number/file-number.service';
+import { formatIncomingDate } from '../../utils/incoming-date.formatter';
+import { 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 { PlanningReferral } from './planning-referral/planning-referral.entity';
+import { PlanningReviewType } from './planning-review-type.entity';
import {
CreatePlanningReviewDto,
PlanningReviewDto,
+ UpdatePlanningReviewDto,
} from './planning-review.dto';
import { PlanningReview } from './planning-review.entity';
@@ -27,49 +24,46 @@ export class PlanningReviewService {
constructor(
private cardService: CardService,
@InjectRepository(PlanningReview)
- private repository: Repository,
+ private reviewRepository: Repository,
+ @InjectRepository(PlanningReviewType)
+ private typeRepository: Repository,
+ @InjectRepository(PlanningReferral)
+ private referralRepository: Repository,
@InjectMapper() private mapper: Mapper,
+ private fileNumberService: FileNumberService,
) {}
- private CARD_RELATION = {
- board: true,
- type: true,
- status: true,
- assignee: true,
- };
-
private DEFAULT_RELATIONS: FindOptionsRelations = {
- card: this.CARD_RELATION,
localGovernment: true,
region: true,
+ type: true,
};
async create(data: CreatePlanningReviewDto, board: Board) {
- const existingReview = await this.repository.findOne({
+ const fileNumber = await this.fileNumberService.generateNextFileNumber();
+ const type = await this.typeRepository.findOneOrFail({
where: {
- fileNumber: data.fileNumber,
+ code: data.typeCode,
},
});
- if (existingReview) {
- throw new ServiceValidationException(
- `Planning meeting already exists with File ID ${data.fileNumber}`,
- );
- }
-
const planningReview = new PlanningReview({
- type: data.type,
+ type,
localGovernmentUuid: data.localGovernmentUuid,
- fileNumber: data.fileNumber,
+ fileNumber: fileNumber,
regionCode: data.regionCode,
+ documentName: data.documentName,
});
- planningReview.card = await this.cardService.create(
- CARD_TYPE.PLAN,
- board,
- false,
- );
- const savedReview = await this.repository.save(planningReview);
- return this.getOrFail(savedReview.uuid);
+ const savedReview = await this.reviewRepository.save(planningReview);
+
+ const referral = new PlanningReferral({
+ planningReview: savedReview,
+ dueDate: formatIncomingDate(data.dueDate),
+ submissionDate: formatIncomingDate(data.submissionDate)!,
+ referralDescription: data.description,
+ card: await this.cardService.create(CARD_TYPE.PLAN, board, false),
+ });
+ return await this.referralRepository.save(referral);
}
async getOrFail(uuid: string) {
@@ -91,43 +85,32 @@ export class PlanningReviewService {
);
}
- async getByCardUuid(cardUuid: string) {
- const planningReview = await this.repository.findOne({
- where: { cardUuid },
- relations: this.DEFAULT_RELATIONS,
- });
-
- if (!planningReview) {
- throw new ServiceNotFoundException(
- `Failed to find planning meeting with card uuid ${cardUuid}`,
- );
- }
-
- return planningReview;
- }
-
getBy(findOptions: FindOptionsWhere) {
- return this.repository.find({
+ return this.reviewRepository.find({
where: findOptions,
relations: this.DEFAULT_RELATIONS,
});
}
- getDeletedCards(fileNumber: string) {
- return this.repository.find({
+ getDetailedReview(fileNumber: string) {
+ return this.reviewRepository.findOneOrFail({
where: {
fileNumber,
- card: {
- auditDeletedDateAt: Not(IsNull()),
+ },
+ relations: {
+ ...this.DEFAULT_RELATIONS,
+ referrals: true,
+ },
+ order: {
+ referrals: {
+ auditCreatedAt: 'DESC',
},
},
- withDeleted: true,
- relations: this.DEFAULT_RELATIONS,
});
}
private get(uuid: string) {
- return this.repository.findOne({
+ return this.reviewRepository.findOne({
where: {
uuid,
},
@@ -135,43 +118,37 @@ export class PlanningReviewService {
});
}
- async getByBoard(boardUuid: string) {
- const res = await this.repository.find({
- relations: {
- ...this.DEFAULT_RELATIONS,
- card: { ...this.CARD_RELATION, board: false },
+ async listTypes() {
+ return this.typeRepository.find({
+ order: {
+ label: 'ASC',
},
+ });
+ }
+
+ async update(fileNumber: string, updateDto: UpdatePlanningReviewDto) {
+ const existingApp = await this.reviewRepository.findOneOrFail({
where: {
- card: {
- boardUuid,
- auditDeletedDateAt: IsNull(),
- },
+ fileNumber,
},
});
- //Typeorm bug its returning deleted cards
- return res.filter((review) => !!review.card);
+
+ existingApp.open = filterUndefined(updateDto.open, existingApp.open);
+ existingApp.typeCode = filterUndefined(
+ updateDto.typeCode,
+ existingApp.typeCode,
+ );
+
+ await this.reviewRepository.save(existingApp);
+ return this.getDetailedReview(fileNumber);
}
- async getWithIncompleteSubtaskByType(subtaskType: string) {
- return this.repository.find({
+ async getFileNumber(planningReviewUuid: string) {
+ return this.reviewRepository.findOneOrFail({
where: {
- card: {
- subtasks: {
- completedAt: IsNull(),
- type: {
- code: subtaskType,
- },
- },
- },
- },
- relations: {
- card: {
- status: true,
- board: true,
- type: true,
- subtasks: { type: true, assignee: true },
- },
+ uuid: planningReviewUuid,
},
+ select: ['fileNumber'],
});
}
}
diff --git a/services/apps/alcs/src/alcs/search/non-applications/non-applications-view.entity.ts b/services/apps/alcs/src/alcs/search/non-applications/non-applications-view.entity.ts
deleted file mode 100644
index bd502a97d5..0000000000
--- a/services/apps/alcs/src/alcs/search/non-applications/non-applications-view.entity.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import {
- JoinColumn,
- ManyToOne,
- PrimaryColumn,
- ViewColumn,
- ViewEntity,
-} from 'typeorm';
-import { LocalGovernment } from '../../local-government/local-government.entity';
-
-@ViewEntity({
- expression: `
- SELECT
- non_applications."uuid"
- ,non_applications."file_number"
- ,non_applications."applicant"
- ,non_applications."type"
- ,non_applications."class"
- ,non_applications."local_government_uuid" as "local_government_uuid"
- ,non_applications."card_uuid"
- ,non_applications."board_code"
- ,non_applications."region_code"
- FROM
- (
- SELECT
- cov.uuid AS "uuid",
- cov.file_number AS "file_number",
- "applicant",
- NULL AS "type",
- 'COV' AS "class",
- cov.local_government_uuid AS "local_government_uuid",
- card.uuid AS "card_uuid",
- board.code AS "board_code",
- cov.region_code AS "region_code"
- FROM
- alcs.covenant cov
- LEFT JOIN alcs.card card ON
- cov.card_uuid = card.uuid AND card.audit_deleted_date_at IS NULL
- LEFT JOIN alcs.board board ON
- board.uuid = card.board_uuid AND board.audit_deleted_date_at IS NULL
- WHERE cov.audit_deleted_date_at IS NULL
- UNION
- SELECT
- planning_review.uuid AS "uuid",
- planning_review.file_number AS "file_number",
- NULL AS "applicant",
- "type",
- 'PLAN' AS "class",
- planning_review.local_government_uuid AS "local_government_uuid",
- card.uuid AS "card_uuid",
- board.code AS "board_code",
- planning_review.region_code AS "region_code"
- FROM
- alcs.planning_review planning_review
- LEFT JOIN alcs.card card ON
- planning_review.card_uuid = card.uuid AND card.audit_deleted_date_at IS NULL
- LEFT JOIN alcs.board board ON
- board.uuid = card.board_uuid AND board.audit_deleted_date_at IS NULL
- WHERE planning_review.audit_deleted_date_at IS NULL
- ) AS non_applications
-`,
-})
-export class NonApplicationSearchView {
- @ViewColumn()
- @PrimaryColumn()
- uuid: string;
-
- @ViewColumn()
- fileNumber: string;
-
- @ViewColumn()
- applicant: string | null;
-
- @ViewColumn()
- type: string | null;
-
- @ViewColumn()
- localGovernmentUuid: string | null;
-
- @ViewColumn()
- class: 'COV' | 'PLAN';
-
- @ViewColumn()
- cardUuid: string | null;
-
- @ViewColumn()
- boardCode: string | null;
-
- @ViewColumn()
- regionCode: string;
-
- @ManyToOne(() => LocalGovernment, {
- nullable: true,
- })
- @JoinColumn({ name: 'local_government_uuid' })
- localGovernment: LocalGovernment | null;
-}
diff --git a/services/apps/alcs/src/alcs/search/non-applications/non-applications.service.spec.ts b/services/apps/alcs/src/alcs/search/non-applications/non-applications.service.spec.ts
deleted file mode 100644
index ba300db684..0000000000
--- a/services/apps/alcs/src/alcs/search/non-applications/non-applications.service.spec.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-import { createMock, DeepMocked } from '@golevelup/nestjs-testing';
-import { Test, TestingModule } from '@nestjs/testing';
-import { getRepositoryToken } from '@nestjs/typeorm';
-import { Repository } from 'typeorm';
-import { SearchRequestDto } from '../search.dto';
-import { NonApplicationSearchView } from './non-applications-view.entity';
-import { NonApplicationsAdvancedSearchService } from './non-applications.service';
-
-describe('NonApplicationsService', () => {
- let service: NonApplicationsAdvancedSearchService;
- let mockNonApplicationsRepository: DeepMocked<
- Repository
- >;
-
- let mockQuery: any = {};
-
- const mockSearchRequestDto: SearchRequestDto = {
- fileNumber: '123',
- governmentName: 'B',
- regionCode: 'C',
- name: 'D',
- page: 1,
- pageSize: 10,
- sortField: 'applicant',
- sortDirection: 'ASC',
- fileTypes: [],
- };
-
- beforeEach(async () => {
- mockNonApplicationsRepository = createMock();
-
- mockQuery = {
- getManyAndCount: jest.fn().mockResolvedValue([[], 0]),
- orderBy: jest.fn().mockReturnThis(),
- offset: jest.fn().mockReturnThis(),
- limit: jest.fn().mockReturnThis(),
- leftJoinAndMapOne: jest.fn().mockReturnThis(),
- groupBy: jest.fn().mockReturnThis(),
- where: jest.fn().mockReturnThis(),
- andWhere: jest.fn().mockReturnThis(),
- };
-
- const module: TestingModule = await Test.createTestingModule({
- providers: [
- NonApplicationsAdvancedSearchService,
- {
- provide: getRepositoryToken(NonApplicationSearchView),
- useValue: mockNonApplicationsRepository,
- },
- ],
- }).compile();
-
- service = module.get(
- NonApplicationsAdvancedSearchService,
- );
- });
-
- it('should be defined', () => {
- expect(service).toBeDefined();
- });
-
- it('should successfully build a query using all search parameters defined', async () => {
- mockNonApplicationsRepository.createQueryBuilder.mockReturnValue(
- mockQuery as any,
- );
-
- const result = await service.searchNonApplications(mockSearchRequestDto);
-
- expect(result).toEqual({ data: [], total: 0 });
- expect(mockNonApplicationsRepository.createQueryBuilder).toBeCalledTimes(1);
- expect(mockQuery.andWhere).toBeCalledTimes(4);
- expect(mockQuery.where).toBeCalledTimes(1);
- });
-
- it('should call compileSearchQuery method correctly', async () => {
- const compileNonApplicationSearchQuerySpy = jest
- .spyOn(service as any, 'compileSearchQuery')
- .mockResolvedValue(mockQuery);
-
- const result = await service.searchNonApplications(mockSearchRequestDto);
-
- expect(result).toEqual({ data: [], total: 0 });
- expect(compileNonApplicationSearchQuerySpy).toBeCalledWith(
- mockSearchRequestDto,
- );
- expect(mockQuery.orderBy).toHaveBeenCalledTimes(1);
- expect(mockQuery.offset).toHaveBeenCalledTimes(1);
- expect(mockQuery.limit).toHaveBeenCalledTimes(1);
- });
-});
diff --git a/services/apps/alcs/src/alcs/search/non-applications/non-applications.service.ts b/services/apps/alcs/src/alcs/search/non-applications/non-applications.service.ts
deleted file mode 100644
index 0586789645..0000000000
--- a/services/apps/alcs/src/alcs/search/non-applications/non-applications.service.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-import { Injectable } from '@nestjs/common';
-import { InjectRepository } from '@nestjs/typeorm';
-import { Repository } from 'typeorm';
-import { formatStringToPostgresSearchStringArrayWithWildCard } from '../../../utils/search-helper';
-import { LocalGovernment } from '../../local-government/local-government.entity';
-import { AdvancedSearchResultDto, SearchRequestDto } from '../search.dto';
-import { NonApplicationSearchView } from './non-applications-view.entity';
-
-@Injectable()
-export class NonApplicationsAdvancedSearchService {
- constructor(
- @InjectRepository(NonApplicationSearchView)
- private nonApplicationSearchRepository: Repository,
- ) {}
-
- async searchNonApplications(
- searchDto: SearchRequestDto,
- ): Promise> {
- let query = await this.compileSearchQuery(searchDto);
-
- const sortQuery = this.compileSortQuery(searchDto);
-
- query = query
- .orderBy(
- sortQuery,
- searchDto.sortDirection,
- searchDto.sortDirection === 'ASC' ? 'NULLS FIRST' : 'NULLS LAST',
- )
- .offset((searchDto.page - 1) * searchDto.pageSize)
- .limit(searchDto.pageSize);
-
- const result = await query.getManyAndCount();
-
- return {
- data: result[0],
- total: result[1],
- };
- }
-
- private compileSortQuery(searchDto: SearchRequestDto) {
- switch (searchDto.sortField) {
- case 'applicant':
- return '"nonApp"."applicant"';
-
- case 'government':
- return '"localGovernment"."name"';
-
- case 'type':
- return '"nonApp"."class"';
-
- default:
- case 'fileId':
- return '"nonApp"."file_number"';
- }
- }
-
- private async compileSearchQuery(searchDto: SearchRequestDto) {
- let query = this.nonApplicationSearchRepository
- .createQueryBuilder('nonApp')
- .leftJoinAndMapOne(
- 'nonApp.localGovernment',
- LocalGovernment,
- 'localGovernment',
- '"nonApp"."local_government_uuid" = "localGovernment".uuid',
- )
- .where('1 = 1');
-
- if (searchDto.fileNumber) {
- query = query.andWhere('nonApp.file_number = :fileNumber', {
- fileNumber: searchDto.fileNumber ?? null,
- });
- }
-
- if (searchDto.regionCode) {
- query = query.andWhere('nonApp.region_code = :regionCode', {
- regionCode: searchDto.regionCode,
- });
- }
-
- if (searchDto.governmentName) {
- query = query.andWhere('localGovernment.name = :localGovernmentName', {
- localGovernmentName: searchDto.governmentName,
- });
- }
-
- if (searchDto.name) {
- const formattedSearchString =
- formatStringToPostgresSearchStringArrayWithWildCard(searchDto.name!);
-
- query = query.andWhere('LOWER(nonApp.applicant) LIKE ANY (:names)', {
- names: formattedSearchString,
- });
- }
-
- if (searchDto.fileTypes.length > 0) {
- query = query.andWhere('nonApp.class IN (:...typeCodes)', {
- typeCodes: searchDto.fileTypes,
- });
- }
-
- return query;
- }
-}
diff --git a/services/apps/alcs/src/alcs/search/search.controller.spec.ts b/services/apps/alcs/src/alcs/search/search.controller.spec.ts
index ebf3a417ff..41f513ec68 100644
--- a/services/apps/alcs/src/alcs/search/search.controller.spec.ts
+++ b/services/apps/alcs/src/alcs/search/search.controller.spec.ts
@@ -1,8 +1,8 @@
+import { createMock, DeepMocked } from '@golevelup/nestjs-testing';
+import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { classes } from 'automapper-classes';
import { AutomapperModule } from 'automapper-nestjs';
-import { createMock, DeepMocked } from '@golevelup/nestjs-testing';
-import { Test, TestingModule } from '@nestjs/testing';
import { ClsService } from 'nestjs-cls';
import { DataSource, QueryRunner, Repository } from 'typeorm';
import { mockKeyCloakProviders } from '../../../test/mocks/mockTypes';
@@ -13,9 +13,7 @@ import { ApplicationType } from '../code/application-code/application-type/appli
import { Covenant } from '../covenant/covenant.entity';
import { NoticeOfIntent } from '../notice-of-intent/notice-of-intent.entity';
import { Notification } from '../notification/notification.entity';
-import { PlanningReview } from '../planning-review/planning-review.entity';
import { ApplicationAdvancedSearchService } from './application/application-advanced-search.service';
-import { NonApplicationsAdvancedSearchService } from './non-applications/non-applications.service';
import { NoticeOfIntentAdvancedSearchService } from './notice-of-intent/notice-of-intent-advanced-search.service';
import { NotificationAdvancedSearchService } from './notification/notification-advanced-search.service';
import { SearchController } from './search.controller';
@@ -27,7 +25,6 @@ describe('SearchController', () => {
let mockSearchService: DeepMocked;
let mockNoticeOfIntentAdvancedSearchService: DeepMocked;
let mockApplicationAdvancedSearchService: DeepMocked;
- let mockNonApplicationsAdvancedSearchService: DeepMocked;
let mockNotificationAdvancedSearchService: DeepMocked;
let mockDataSource: DeepMocked;
let mockQueryRunner: DeepMocked;
@@ -37,7 +34,6 @@ describe('SearchController', () => {
mockSearchService = createMock();
mockNoticeOfIntentAdvancedSearchService = createMock();
mockApplicationAdvancedSearchService = createMock();
- mockNonApplicationsAdvancedSearchService = createMock();
mockNotificationAdvancedSearchService = createMock();
mockDataSource = createMock();
mockAppTypeRepo = createMock();
@@ -61,10 +57,6 @@ describe('SearchController', () => {
provide: ApplicationAdvancedSearchService,
useValue: mockApplicationAdvancedSearchService,
},
- {
- provide: NonApplicationsAdvancedSearchService,
- useValue: mockNonApplicationsAdvancedSearchService,
- },
{
provide: NotificationAdvancedSearchService,
useValue: mockNotificationAdvancedSearchService,
@@ -95,15 +87,6 @@ describe('SearchController', () => {
mockSearchService.getApplication.mockResolvedValue(new Application());
mockSearchService.getNoi.mockResolvedValue(new NoticeOfIntent());
mockSearchService.getNotification.mockResolvedValue(new Notification());
- mockSearchService.getPlanningReview.mockResolvedValue(
- new PlanningReview({
- card: {
- board: {
- code: 'fake_board',
- } as Board,
- } as Card,
- }),
- );
mockSearchService.getCovenant.mockResolvedValue(
new Covenant({
card: {
@@ -126,13 +109,6 @@ describe('SearchController', () => {
total: 0,
});
- mockNonApplicationsAdvancedSearchService.searchNonApplications.mockResolvedValue(
- {
- data: [],
- total: 0,
- },
- );
-
mockNotificationAdvancedSearchService.search.mockResolvedValue({
data: [],
total: 0,
@@ -151,14 +127,12 @@ describe('SearchController', () => {
expect(mockSearchService.getApplication).toBeCalledWith(searchString);
expect(mockSearchService.getNoi).toBeCalledTimes(1);
expect(mockSearchService.getNoi).toBeCalledWith(searchString);
- expect(mockSearchService.getPlanningReview).toBeCalledTimes(1);
- expect(mockSearchService.getPlanningReview).toBeCalledWith(searchString);
expect(mockSearchService.getCovenant).toBeCalledTimes(1);
expect(mockSearchService.getCovenant).toBeCalledWith(searchString);
expect(mockSearchService.getNotification).toHaveBeenCalledTimes(1);
expect(mockSearchService.getNotification).toBeCalledWith(searchString);
expect(result).toBeDefined();
- expect(result.length).toBe(5);
+ expect(result.length).toBe(4);
});
it('should call advanced search to retrieve Applications, NOIs, PlanningReviews, Covenants, Notifications', async () => {
@@ -192,15 +166,6 @@ describe('SearchController', () => {
).toBeCalledWith(mockSearchRequestDto);
expect(result.noticeOfIntents).toBeDefined();
expect(result.totalNoticeOfIntents).toBe(0);
-
- expect(
- mockNonApplicationsAdvancedSearchService.searchNonApplications,
- ).toBeCalledTimes(1);
- expect(
- mockNonApplicationsAdvancedSearchService.searchNonApplications,
- ).toBeCalledWith(mockSearchRequestDto);
- expect(result.nonApplications).toBeDefined();
- expect(result.totalNonApplications).toBe(0);
});
it('should call applications advanced search to retrieve Applications', async () => {
@@ -251,28 +216,6 @@ describe('SearchController', () => {
expect(result.total).toBe(0);
});
- it('should call non-applications advanced search to retrieve Non-Applications', async () => {
- const mockSearchRequestDto: SearchRequestDto = {
- pageSize: 1,
- page: 1,
- sortField: '1',
- sortDirection: 'ASC',
- fileTypes: [],
- };
-
- const result =
- await controller.advancedSearchNonApplications(mockSearchRequestDto);
-
- expect(
- mockNonApplicationsAdvancedSearchService.searchNonApplications,
- ).toBeCalledTimes(1);
- expect(
- mockNonApplicationsAdvancedSearchService.searchNonApplications,
- ).toBeCalledWith(mockSearchRequestDto);
- expect(result.data).toBeDefined();
- expect(result.total).toBe(0);
- });
-
it('should call advanced search to retrieve Applications only when application file type selected', async () => {
const mockSearchRequestDto = {
pageSize: 1,
@@ -320,57 +263,4 @@ describe('SearchController', () => {
expect(result.noticeOfIntents).toBeDefined();
expect(result.totalNoticeOfIntents).toBe(0);
});
-
- it('should call advanced search to retrieve Non Applications only when non application file type selected', async () => {
- const mockSearchRequestDto: SearchRequestDto = {
- pageSize: 1,
- page: 1,
- sortField: '1',
- sortDirection: 'ASC',
- fileTypes: ['COV'],
- };
-
- const result = await controller.advancedSearch(mockSearchRequestDto);
-
- expect(result.totalNoticeOfIntents).toBe(0);
-
- expect(
- mockNonApplicationsAdvancedSearchService.searchNonApplications,
- ).toBeCalledTimes(1);
- expect(
- mockNonApplicationsAdvancedSearchService.searchNonApplications,
- ).toBeCalledWith(mockSearchRequestDto);
- expect(result.nonApplications).toBeDefined();
- expect(result.totalNonApplications).toBe(0);
- });
-
- it('should NOT call Non-applications advanced search to retrieve Non-applications if no non-application search fields specified', async () => {
- const baseMockSearchRequestDto: SearchRequestDto = {
- pageSize: 1,
- page: 1,
- sortField: '1',
- sortDirection: 'ASC',
- fileTypes: [],
- };
-
- const result = await controller.advancedSearch({
- ...baseMockSearchRequestDto,
- legacyId: 'test',
- });
-
- expect(
- mockApplicationAdvancedSearchService.searchApplications,
- ).toBeCalledTimes(1);
- expect(
- mockApplicationAdvancedSearchService.searchApplications,
- ).toBeCalledWith({ ...baseMockSearchRequestDto, legacyId: 'test' }, {});
- expect(result.applications).toBeDefined();
- expect(result.totalApplications).toBe(0);
-
- expect(
- mockNonApplicationsAdvancedSearchService.searchNonApplications,
- ).toBeCalledTimes(0);
- expect(result.nonApplications).toBeDefined();
- expect(result.totalNonApplications).toBe(0);
- });
});
diff --git a/services/apps/alcs/src/alcs/search/search.controller.ts b/services/apps/alcs/src/alcs/search/search.controller.ts
index 3cf07d8e6d..1d2616f5ca 100644
--- a/services/apps/alcs/src/alcs/search/search.controller.ts
+++ b/services/apps/alcs/src/alcs/search/search.controller.ts
@@ -11,7 +11,6 @@ import { UserRoles } from '../../common/authorization/roles.decorator';
import { APPLICATION_SUBMISSION_TYPES } from '../../portal/pdf-generation/generate-submission-document.service';
import { isStringSetAndNotEmpty } from '../../utils/string-helper';
import { Application } from '../application/application.entity';
-import { ApplicationService } from '../application/application.service';
import { CARD_TYPE } from '../card/card-type/card-type.entity';
import { ApplicationTypeDto } from '../code/application-code/application-type/application-type.dto';
import { ApplicationType } from '../code/application-code/application-type/application-type.entity';
@@ -21,8 +20,6 @@ import { Notification } from '../notification/notification.entity';
import { PlanningReview } from '../planning-review/planning-review.entity';
import { ApplicationAdvancedSearchService } from './application/application-advanced-search.service';
import { ApplicationSubmissionSearchView } from './application/application-search-view.entity';
-import { NonApplicationSearchView } from './non-applications/non-applications-view.entity';
-import { NonApplicationsAdvancedSearchService } from './non-applications/non-applications.service';
import { NoticeOfIntentAdvancedSearchService } from './notice-of-intent/notice-of-intent-advanced-search.service';
import { NoticeOfIntentSubmissionSearchView } from './notice-of-intent/notice-of-intent-search-view.entity';
import { NotificationAdvancedSearchService } from './notification/notification-advanced-search.service';
@@ -31,7 +28,6 @@ import {
AdvancedSearchResponseDto,
AdvancedSearchResultDto,
ApplicationSearchResultDto,
- NonApplicationSearchResultDto,
NoticeOfIntentSearchResultDto,
NotificationSearchResultDto,
SearchRequestDto,
@@ -48,7 +44,6 @@ export class SearchController {
@InjectMapper() private mapper: Mapper,
private noticeOfIntentSearchService: NoticeOfIntentAdvancedSearchService,
private applicationSearchService: ApplicationAdvancedSearchService,
- private nonApplicationsSearchService: NonApplicationsAdvancedSearchService,
private notificationSearchService: NotificationAdvancedSearchService,
@InjectRepository(ApplicationType)
private appTypeRepo: Repository,
@@ -61,8 +56,6 @@ export class SearchController {
async search(@Param('searchTerm') searchTerm) {
const application = await this.searchService.getApplication(searchTerm);
const noi = await this.searchService.getNoi(searchTerm);
- const planningReview =
- await this.searchService.getPlanningReview(searchTerm);
const covenant = await this.searchService.getCovenant(searchTerm);
const notification = await this.searchService.getNotification(searchTerm);
@@ -72,7 +65,7 @@ export class SearchController {
result,
application,
noi,
- planningReview,
+ null, //TODO
covenant,
notification,
);
@@ -142,17 +135,6 @@ export class SearchController {
searchDto,
);
}
-
- let nonApplications: AdvancedSearchResultDto<
- NonApplicationSearchView[]
- > | null = null;
- if (searchNonApplications) {
- nonApplications =
- await this.nonApplicationsSearchService.searchNonApplications(
- searchDto,
- );
- }
-
let notifications: AdvancedSearchResultDto<
NotificationSubmissionSearchView[]
> | null = null;
@@ -163,7 +145,7 @@ export class SearchController {
return await this.mapAdvancedSearchResults(
applicationSearchResult,
noticeOfIntentSearchService,
- nonApplications,
+ null,
notifications,
);
} finally {
@@ -222,27 +204,6 @@ export class SearchController {
};
}
- @Post('/advanced/non-applications')
- @UserRoles(...ROLES_ALLOWED_APPLICATIONS)
- async advancedSearchNonApplications(
- @Body() searchDto: SearchRequestDto,
- ): Promise> {
- const nonApplications =
- await this.nonApplicationsSearchService.searchNonApplications(searchDto);
-
- const mappedSearchResult = await this.mapAdvancedSearchResults(
- null,
- null,
- nonApplications,
- null,
- );
-
- return {
- total: mappedSearchResult.totalNonApplications,
- data: mappedSearchResult.nonApplications,
- };
- }
-
@Post('/advanced/notifications')
@UserRoles(...ROLES_ALLOWED_APPLICATIONS)
async advancedSearchNotifications(
@@ -327,7 +288,7 @@ export class SearchController {
noticeOfIntents: AdvancedSearchResultDto<
NoticeOfIntentSubmissionSearchView[]
> | null,
- nonApplications: AdvancedSearchResultDto | null,
+ nonApplications: null,
notifications: AdvancedSearchResultDto<
NotificationSubmissionSearchView[]
> | null,
@@ -358,15 +319,6 @@ export class SearchController {
);
}
- const mappedNonApplications: NonApplicationSearchResultDto[] = [];
- if (nonApplications?.data && nonApplications?.data.length > 0) {
- mappedNonApplications.push(
- ...nonApplications.data.map((nonApplication) =>
- this.mapNonApplicationToAdvancedSearchResult(nonApplication),
- ),
- );
- }
-
const mappedNotifications: NotificationSearchResultDto[] = [];
if (notifications && notifications.data && notifications.data.length > 0) {
mappedNotifications.push(
@@ -380,8 +332,6 @@ export class SearchController {
response.totalApplications = applications?.total ?? 0;
response.noticeOfIntents = mappedNoticeOfIntents;
response.totalNoticeOfIntents = noticeOfIntents?.total ?? 0;
- response.nonApplications = mappedNonApplications;
- response.totalNonApplications = nonApplications?.total ?? 0;
response.notifications = mappedNotifications;
response.totalNotifications = notifications?.total ?? 0;
@@ -420,12 +370,12 @@ export class SearchController {
private mapPlanningReviewToSearchResult(
planning: PlanningReview,
): SearchResultDto {
+ //TODO
return {
- type: CARD_TYPE.PLAN,
- referenceId: planning.cardUuid,
- localGovernmentName: planning.localGovernment?.name,
- fileNumber: planning.fileNumber,
- boardCode: planning.card.board.code,
+ fileNumber: '',
+ localGovernmentName: undefined,
+ referenceId: '',
+ type: '',
};
}
@@ -491,20 +441,6 @@ export class SearchController {
};
}
- private mapNonApplicationToAdvancedSearchResult(
- nonApplication: NonApplicationSearchView,
- ): NonApplicationSearchResultDto {
- return {
- referenceId: nonApplication.cardUuid,
- fileNumber: nonApplication.fileNumber,
- applicant: nonApplication.applicant,
- boardCode: nonApplication.boardCode,
- type: nonApplication.type,
- localGovernmentName: nonApplication.localGovernment?.name ?? null,
- class: nonApplication.class,
- };
- }
-
private mapNotificationToAdvancedSearchResult(
notification: NotificationSubmissionSearchView,
): NoticeOfIntentSearchResultDto {
diff --git a/services/apps/alcs/src/alcs/search/search.dto.ts b/services/apps/alcs/src/alcs/search/search.dto.ts
index e169a54787..78b918f28f 100644
--- a/services/apps/alcs/src/alcs/search/search.dto.ts
+++ b/services/apps/alcs/src/alcs/search/search.dto.ts
@@ -69,7 +69,6 @@ export class NotificationSearchResultDto {
export class AdvancedSearchResponseDto {
applications: ApplicationSearchResultDto[];
noticeOfIntents: NoticeOfIntentSearchResultDto[];
- nonApplications: NonApplicationSearchResultDto[];
notifications: NotificationSearchResultDto[];
totalApplications: number;
totalNoticeOfIntents: number;
diff --git a/services/apps/alcs/src/alcs/search/search.module.ts b/services/apps/alcs/src/alcs/search/search.module.ts
index 1ba7c64f06..6fbb75f13e 100644
--- a/services/apps/alcs/src/alcs/search/search.module.ts
+++ b/services/apps/alcs/src/alcs/search/search.module.ts
@@ -2,7 +2,6 @@ import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationProfile } from '../../common/automapper/application.automapper.profile';
import { Application } from '../application/application.entity';
-import { ApplicationModule } from '../application/application.module';
import { ApplicationType } from '../code/application-code/application-type/application-type.entity';
import { Covenant } from '../covenant/covenant.entity';
import { LocalGovernment } from '../local-government/local-government.entity';
@@ -11,8 +10,6 @@ import { Notification } from '../notification/notification.entity';
import { PlanningReview } from '../planning-review/planning-review.entity';
import { ApplicationAdvancedSearchService } from './application/application-advanced-search.service';
import { ApplicationSubmissionSearchView } from './application/application-search-view.entity';
-import { NonApplicationSearchView } from './non-applications/non-applications-view.entity';
-import { NonApplicationsAdvancedSearchService } from './non-applications/non-applications.service';
import { NoticeOfIntentAdvancedSearchService } from './notice-of-intent/notice-of-intent-advanced-search.service';
import { NoticeOfIntentSubmissionSearchView } from './notice-of-intent/notice-of-intent-search-view.entity';
import { NotificationAdvancedSearchService } from './notification/notification-advanced-search.service';
@@ -32,7 +29,6 @@ import { SearchService } from './search.service';
LocalGovernment,
ApplicationSubmissionSearchView,
NoticeOfIntentSubmissionSearchView,
- NonApplicationSearchView,
NotificationSubmissionSearchView,
]),
],
@@ -41,7 +37,6 @@ import { SearchService } from './search.service';
ApplicationProfile,
ApplicationAdvancedSearchService,
NoticeOfIntentAdvancedSearchService,
- NonApplicationsAdvancedSearchService,
NotificationAdvancedSearchService,
],
controllers: [SearchController],
diff --git a/services/apps/alcs/src/alcs/search/search.service.spec.ts b/services/apps/alcs/src/alcs/search/search.service.spec.ts
index e901b4bc0c..a01f160f12 100644
--- a/services/apps/alcs/src/alcs/search/search.service.spec.ts
+++ b/services/apps/alcs/src/alcs/search/search.service.spec.ts
@@ -7,7 +7,6 @@ import { Covenant } from '../covenant/covenant.entity';
import { LocalGovernment } from '../local-government/local-government.entity';
import { NoticeOfIntent } from '../notice-of-intent/notice-of-intent.entity';
import { Notification } from '../notification/notification.entity';
-import { PlanningReview } from '../planning-review/planning-review.entity';
import { ApplicationSubmissionSearchView } from './application/application-search-view.entity';
import { SearchService } from './search.service';
@@ -15,7 +14,6 @@ describe('SearchService', () => {
let service: SearchService;
let mockApplicationRepository: DeepMocked>;
let mockNoiRepository: DeepMocked>;
- let mockPlanningReviewRepository: DeepMocked>;
let mockCovenantRepository: DeepMocked>;
let mockApplicationSubmissionSearchView: DeepMocked<
Repository
@@ -28,7 +26,6 @@ describe('SearchService', () => {
beforeEach(async () => {
mockApplicationRepository = createMock();
mockNoiRepository = createMock();
- mockPlanningReviewRepository = createMock();
mockCovenantRepository = createMock();
mockApplicationSubmissionSearchView = createMock();
mockLocalGovernment = createMock();
@@ -45,10 +42,6 @@ describe('SearchService', () => {
provide: getRepositoryToken(NoticeOfIntent),
useValue: mockNoiRepository,
},
- {
- provide: getRepositoryToken(PlanningReview),
- useValue: mockPlanningReviewRepository,
- },
{
provide: getRepositoryToken(Covenant),
useValue: mockCovenantRepository,
@@ -112,29 +105,6 @@ describe('SearchService', () => {
expect(result).toBeDefined();
});
- it('should call repository to get planning review', async () => {
- mockPlanningReviewRepository.findOne.mockResolvedValue(
- new PlanningReview(),
- );
-
- const result = await service.getPlanningReview('fake');
-
- expect(mockPlanningReviewRepository.findOne).toBeCalledTimes(1);
- expect(mockPlanningReviewRepository.findOne).toBeCalledWith({
- where: {
- fileNumber: fakeFileNumber,
- card: { archived: false },
- },
- relations: {
- card: {
- board: true,
- },
- localGovernment: true,
- },
- });
- expect(result).toBeDefined();
- });
-
it('should call repository to get covenant', async () => {
mockCovenantRepository.findOne.mockResolvedValue(new Covenant());
diff --git a/services/apps/alcs/src/alcs/search/search.service.ts b/services/apps/alcs/src/alcs/search/search.service.ts
index c69dc2906b..1351386f20 100644
--- a/services/apps/alcs/src/alcs/search/search.service.ts
+++ b/services/apps/alcs/src/alcs/search/search.service.ts
@@ -5,6 +5,7 @@ import { Application } from '../application/application.entity';
import { Covenant } from '../covenant/covenant.entity';
import { NoticeOfIntent } from '../notice-of-intent/notice-of-intent.entity';
import { Notification } from '../notification/notification.entity';
+import { PlanningReferral } from '../planning-review/planning-referral/planning-referral.entity';
import { PlanningReview } from '../planning-review/planning-review.entity';
const CARD_RELATIONSHIP = {
@@ -21,8 +22,6 @@ export class SearchService {
private applicationRepository: Repository,
@InjectRepository(NoticeOfIntent)
private noiRepository: Repository,
- @InjectRepository(PlanningReview)
- private planningReviewRepository: Repository,
@InjectRepository(Covenant)
private covenantRepository: Repository,
@InjectRepository(Notification)
@@ -54,16 +53,6 @@ export class SearchService {
});
}
- async getPlanningReview(fileNumber: string) {
- return await this.planningReviewRepository.findOne({
- where: {
- fileNumber,
- card: { archived: false },
- },
- relations: CARD_RELATIONSHIP,
- });
- }
-
async getCovenant(fileNumber: string) {
return await this.covenantRepository.findOne({
where: {
diff --git a/services/apps/alcs/src/alcs/staff-journal/staff-journal.controller.ts b/services/apps/alcs/src/alcs/staff-journal/staff-journal.controller.ts
index 6053726a9b..6d3c7fcb52 100644
--- a/services/apps/alcs/src/alcs/staff-journal/staff-journal.controller.ts
+++ b/services/apps/alcs/src/alcs/staff-journal/staff-journal.controller.ts
@@ -24,6 +24,7 @@ import {
UpdateStaffJournalDto,
CreateNoticeOfIntentStaffJournalDto,
CreateNotificationStaffJournalDto,
+ CreatePlanningReviewStaffJournalDto,
} from './staff-journal.dto';
import { StaffJournal } from './staff-journal.entity';
import { StaffJournalService } from './staff-journal.service';
@@ -92,6 +93,21 @@ export class StaffJournalController {
return this.autoMapper.map(newRecord, StaffJournal, StaffJournalDto);
}
+ @Post('/planning-review')
+ @UserRoles(...ROLES_ALLOWED_BOARDS)
+ async createForPlanningReview(
+ @Body() record: CreatePlanningReviewStaffJournalDto,
+ @Req() req,
+ ): Promise {
+ const newRecord = await this.staffJournalService.createForPlanningReview(
+ record.planningReviewUuid,
+ record.body,
+ req.user.entity,
+ );
+
+ return this.autoMapper.map(newRecord, StaffJournal, StaffJournalDto);
+ }
+
@Patch()
@UserRoles(...ROLES_ALLOWED_BOARDS)
async update(
diff --git a/services/apps/alcs/src/alcs/staff-journal/staff-journal.dto.ts b/services/apps/alcs/src/alcs/staff-journal/staff-journal.dto.ts
index 60c42b0704..4e15dcd8af 100644
--- a/services/apps/alcs/src/alcs/staff-journal/staff-journal.dto.ts
+++ b/services/apps/alcs/src/alcs/staff-journal/staff-journal.dto.ts
@@ -20,42 +20,38 @@ export class StaffJournalDto {
isEditable = false;
}
-export class CreateApplicationStaffJournalDto {
- @IsString()
- @IsNotEmpty()
- applicationUuid: string;
-
+class BaseCreateStaffJournalDto {
@IsString()
@IsNotEmpty()
body: string;
}
-export class CreateNoticeOfIntentStaffJournalDto {
+export class CreateApplicationStaffJournalDto extends BaseCreateStaffJournalDto {
@IsString()
@IsNotEmpty()
- noticeOfIntentUuid: string;
+ applicationUuid: string;
+}
+export class CreateNoticeOfIntentStaffJournalDto extends BaseCreateStaffJournalDto {
@IsString()
@IsNotEmpty()
- body: string;
+ noticeOfIntentUuid: string;
}
-export class CreateNotificationStaffJournalDto {
+export class CreateNotificationStaffJournalDto extends BaseCreateStaffJournalDto {
@IsString()
@IsNotEmpty()
notificationUuid: string;
+}
+export class CreatePlanningReviewStaffJournalDto extends BaseCreateStaffJournalDto {
@IsString()
@IsNotEmpty()
- body: string;
+ planningReviewUuid: string;
}
-export class UpdateStaffJournalDto {
+export class UpdateStaffJournalDto extends BaseCreateStaffJournalDto {
@IsString()
@IsNotEmpty()
uuid: string;
-
- @IsString()
- @IsNotEmpty()
- body: string;
}
diff --git a/services/apps/alcs/src/alcs/staff-journal/staff-journal.entity.ts b/services/apps/alcs/src/alcs/staff-journal/staff-journal.entity.ts
index 0dbf764297..d9726c024a 100644
--- a/services/apps/alcs/src/alcs/staff-journal/staff-journal.entity.ts
+++ b/services/apps/alcs/src/alcs/staff-journal/staff-journal.entity.ts
@@ -5,6 +5,7 @@ import { User } from '../../user/user.entity';
import { Application } from '../application/application.entity';
import { NoticeOfIntent } from '../notice-of-intent/notice-of-intent.entity';
import { Notification } from '../notification/notification.entity';
+import { PlanningReview } from '../planning-review/planning-review.entity';
@Entity()
export class StaffJournal extends Base {
@@ -48,4 +49,11 @@ export class StaffJournal extends Base {
@Column({ nullable: true })
@Index()
notificationUuid: string;
+
+ @ManyToOne(() => PlanningReview)
+ planningReview: PlanningReview | null;
+
+ @Column({ nullable: true })
+ @Index()
+ planningReviewUuid: string;
}
diff --git a/services/apps/alcs/src/alcs/staff-journal/staff-journal.service.ts b/services/apps/alcs/src/alcs/staff-journal/staff-journal.service.ts
index 729d6ca212..a3ab5c8c59 100644
--- a/services/apps/alcs/src/alcs/staff-journal/staff-journal.service.ts
+++ b/services/apps/alcs/src/alcs/staff-journal/staff-journal.service.ts
@@ -32,6 +32,9 @@ export class StaffJournalService {
{
notificationUuid: parentUuid,
},
+ {
+ planningReviewUuid: parentUuid,
+ },
],
relations: this.DEFAULT_STAFF_JOURNAL_RELATIONS,
order: {
@@ -91,6 +94,20 @@ export class StaffJournalService {
return await this.staffJournalRepository.save(record);
}
+ async createForPlanningReview(
+ planningReviewUuid: string,
+ noteBody: string,
+ author: User,
+ ) {
+ const record = new StaffJournal({
+ body: noteBody,
+ planningReviewUuid,
+ author,
+ });
+
+ return await this.staffJournalRepository.save(record);
+ }
+
async delete(uuid: string): Promise {
const note = await this.staffJournalRepository.findOne({
where: { uuid },
diff --git a/services/apps/alcs/src/common/automapper/planning-meeting.automapper.profile.ts b/services/apps/alcs/src/common/automapper/planning-meeting.automapper.profile.ts
deleted file mode 100644
index c5f68bd4dd..0000000000
--- a/services/apps/alcs/src/common/automapper/planning-meeting.automapper.profile.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { createMap, Mapper } from 'automapper-core';
-import { AutomapperProfile, InjectMapper } from 'automapper-nestjs';
-import { Injectable } from '@nestjs/common';
-import { PlanningReviewDto } from '../../alcs/planning-review/planning-review.dto';
-import { PlanningReview } from '../../alcs/planning-review/planning-review.entity';
-
-@Injectable()
-export class PlanningReviewProfile extends AutomapperProfile {
- constructor(@InjectMapper() mapper: Mapper) {
- super(mapper);
- }
-
- override get profile() {
- return (mapper) => {
- createMap(mapper, PlanningReview, PlanningReviewDto);
- };
- }
-}
diff --git a/services/apps/alcs/src/common/automapper/planning-review.automapper.profile.ts b/services/apps/alcs/src/common/automapper/planning-review.automapper.profile.ts
new file mode 100644
index 0000000000..b64c5ef743
--- /dev/null
+++ b/services/apps/alcs/src/common/automapper/planning-review.automapper.profile.ts
@@ -0,0 +1,87 @@
+import { Injectable } from '@nestjs/common';
+import { createMap, forMember, mapFrom, Mapper } from 'automapper-core';
+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 { PlanningReviewType } from '../../alcs/planning-review/planning-review-type.entity';
+import {
+ PlanningReferralDto,
+ PlanningReviewDetailedDto,
+ PlanningReviewDto,
+ PlanningReviewTypeDto,
+} from '../../alcs/planning-review/planning-review.dto';
+import { PlanningReview } from '../../alcs/planning-review/planning-review.entity';
+import { DocumentCode } from '../../document/document-code.entity';
+import { DocumentTypeDto } from '../../document/document.dto';
+
+@Injectable()
+export class PlanningReviewProfile extends AutomapperProfile {
+ constructor(@InjectMapper() mapper: Mapper) {
+ super(mapper);
+ }
+
+ override get profile() {
+ return (mapper) => {
+ createMap(mapper, PlanningReviewType, PlanningReviewTypeDto);
+ createMap(mapper, PlanningReview, PlanningReviewDto);
+ createMap(
+ mapper,
+ PlanningReferral,
+ PlanningReferralDto,
+ forMember(
+ (dto) => dto.dueDate,
+ mapFrom((entity) => entity.dueDate?.getTime()),
+ ),
+ forMember(
+ (dto) => dto.submissionDate,
+ mapFrom((entity) => entity.submissionDate?.getTime()),
+ ),
+ forMember(
+ (dto) => dto.responseDate,
+ mapFrom((entity) => entity.responseDate?.getTime()),
+ ),
+ );
+ createMap(mapper, PlanningReview, PlanningReviewDetailedDto);
+
+ createMap(
+ mapper,
+ PlanningReviewDocument,
+ PlanningReviewDocumentDto,
+ 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/main.module.ts b/services/apps/alcs/src/main.module.ts
index 2983653d97..fb10cabf27 100644
--- a/services/apps/alcs/src/main.module.ts
+++ b/services/apps/alcs/src/main.module.ts
@@ -46,6 +46,9 @@ import { UserModule } from './user/user.module';
RedisModule,
LoggerModule.forRoot({
pinoHttp: {
+ redact: {
+ paths: ['req.headers'],
+ },
level: config.get('LOG_LEVEL'),
autoLogging: false, //Disable auto-logging every request/response for now
transport:
diff --git a/services/apps/alcs/src/main.ts b/services/apps/alcs/src/main.ts
index 372dcf6e0d..9ac498d284 100644
--- a/services/apps/alcs/src/main.ts
+++ b/services/apps/alcs/src/main.ts
@@ -12,7 +12,7 @@ import * as config from 'config';
import { Logger } from 'nestjs-pino';
import { install } from 'source-map-support';
import { generateModuleGraph } from './commands/graph';
-import { importApplications, importNOIs } from './commands/import';
+import { importApplications } from './commands/import';
import { MainModule } from './main.module';
const registerSwagger = (app: NestFastifyApplication) => {
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 e197636f0d..420e84cfda 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
@@ -136,7 +136,9 @@ 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 09394a6491..821fde6f82 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,7 +75,8 @@ export class ApplicationDocumentController {
if (canAccessDocument) {
const url = await this.applicationDocumentService.getInlineUrl(document);
- return { url };
+ const { fileName } = document.document;
+ return { url, fileName };
}
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 c3efe743c9..73c73b9c39 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
@@ -120,8 +120,9 @@ describe('NoticeOfIntentDocumentController', () => {
const res = await controller.open('fake-uuid', mockRequest);
- expect(res.url).toEqual(fakeUrl);
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 4c287b96a2..5ccfe49ff8 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,7 +80,8 @@ export class NoticeOfIntentDocumentController {
if (canAccessDocument) {
const url =
await this.noticeOfIntentDocumentService.getInlineUrl(document);
- return { url };
+ const { fileName } = document.document;
+ return { url, fileName };
}
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 2afbe33066..88891e0ae8 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
@@ -5,7 +5,6 @@ import { Test, TestingModule } from '@nestjs/testing';
import { ClsService } from 'nestjs-cls';
import { mockKeyCloakProviders } from '../../../test/mocks/mockTypes';
import { VISIBILITY_FLAG } from '../../alcs/application/application-document/application-document.entity';
-import { NoticeOfIntentDocument } from '../../alcs/notice-of-intent/notice-of-intent-document/notice-of-intent-document.entity';
import { NotificationDocument } from '../../alcs/notification/notification-document/notification-document.entity';
import { NotificationDocumentService } from '../../alcs/notification/notification-document/notification-document.service';
import { NotificationService } from '../../alcs/notification/notification.service';
@@ -137,7 +136,11 @@ describe('NotificationDocumentController', () => {
},
});
+ expect(mockNotificationDocumentService.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/notification-document/notification-document.controller.ts b/services/apps/alcs/src/portal/notification-document/notification-document.controller.ts
index 54c990bb0a..555cde8cd6 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,7 +78,8 @@ export class NotificationDocumentController {
if (canAccessDocument) {
const url = await this.notificationDocumentService.getInlineUrl(document);
- return { url };
+ const { fileName } = document.document;
+ return { url, fileName };
}
throw new NotFoundException('Failed to find document');
diff --git a/services/apps/alcs/src/providers/keycloak/keycloak-config.service.ts b/services/apps/alcs/src/providers/keycloak/keycloak-config.service.ts
index 99e315c2eb..ed45853805 100644
--- a/services/apps/alcs/src/providers/keycloak/keycloak-config.service.ts
+++ b/services/apps/alcs/src/providers/keycloak/keycloak-config.service.ts
@@ -20,6 +20,7 @@ export class KeycloakConfigService implements KeycloakConnectOptionsFactory {
'confidential-port': 0,
tokenValidation: TokenValidation.OFFLINE,
verifyTokenAudience: true,
+ logLevels: [], //Disable Expired Token Messages
};
}
}
diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1709614448912-add_oats_doc_app_id_to_notification_docs.ts b/services/apps/alcs/src/providers/typeorm/migrations/1709614448912-add_oats_doc_app_id_to_notification_docs.ts
new file mode 100644
index 0000000000..b40d09d386
--- /dev/null
+++ b/services/apps/alcs/src/providers/typeorm/migrations/1709614448912-add_oats_doc_app_id_to_notification_docs.ts
@@ -0,0 +1,35 @@
+import { MigrationInterface, QueryRunner } from 'typeorm';
+
+export class AddOatsDocAppIdToNotificationDocs1709614448912
+ implements MigrationInterface
+{
+ public async up(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."notification_document" ADD "oats_document_id" text`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."notification_document" ADD "oats_application_id" text`,
+ );
+ await queryRunner.query(
+ `COMMENT ON COLUMN "alcs"."notification_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.notification_document.'`,
+ );
+ 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.'`,
+ );
+ }
+
+ public async down(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(
+ `COMMENT ON COLUMN "alcs"."notification_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.notification_document.'`,
+ );
+ 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(
+ `ALTER TABLE "alcs"."notification_document" DROP COLUMN "oats_document_id"`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."notification_document" DROP COLUMN "oats_application_id"`,
+ );
+ }
+}
diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1709615421151-add_notification_unique_exclusion.ts b/services/apps/alcs/src/providers/typeorm/migrations/1709615421151-add_notification_unique_exclusion.ts
new file mode 100644
index 0000000000..f3e692ff08
--- /dev/null
+++ b/services/apps/alcs/src/providers/typeorm/migrations/1709615421151-add_notification_unique_exclusion.ts
@@ -0,0 +1,17 @@
+import { MigrationInterface, QueryRunner } from 'typeorm';
+
+export class AddNotificationUniqueExclusion1709615421151
+ implements MigrationInterface
+{
+ public async up(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."notification_document" ADD CONSTRAINT unique_doc_app_id UNIQUE (oats_document_id, oats_application_id)`,
+ );
+ }
+
+ public async down(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."notification_document" DROP CONSTRAINT unique_doc_app_id UNIQUE (oats_document_id, oats_application_id)`,
+ );
+ }
+}
diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1709662671997-planning_reviews_v2.ts b/services/apps/alcs/src/providers/typeorm/migrations/1709662671997-planning_reviews_v2.ts
new file mode 100644
index 0000000000..464a5f8c2f
--- /dev/null
+++ b/services/apps/alcs/src/providers/typeorm/migrations/1709662671997-planning_reviews_v2.ts
@@ -0,0 +1,108 @@
+import { MigrationInterface, QueryRunner } from 'typeorm';
+
+export class PlanningReviewsV21709662671997 implements MigrationInterface {
+ name = 'PlanningReviewsV21709662671997';
+
+ public async up(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(`DROP VIEW "alcs"."non_application_search_view"`);
+ await queryRunner.query(`TRUNCATE TABLE "alcs"."planning_review"`);
+ await queryRunner.query(
+ `UPDATE "alcs"."card" SET "audit_deleted_date_at" = NOW(), "archived" = true WHERE "type_code" = 'PLAN'`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review" DROP CONSTRAINT "FK_735dcdd4fa909a60d0fa1828f24"`,
+ );
+ await queryRunner.query(
+ `DROP INDEX "alcs"."IDX_a62913da5fae4a128c8e8f264f"`,
+ );
+ await queryRunner.query(
+ `CREATE TABLE "alcs"."planning_review_type" ("audit_deleted_date_at" TIMESTAMP WITH TIME ZONE, "audit_created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "audit_updated_at" TIMESTAMP WITH TIME ZONE DEFAULT now(), "audit_created_by" character varying NOT NULL, "audit_updated_by" character varying, "label" character varying NOT NULL, "code" text NOT NULL, "description" text NOT NULL, "short_label" character varying NOT NULL, "background_color" character varying NOT NULL, "text_color" character varying NOT NULL, "html_description" text NOT NULL DEFAULT '', CONSTRAINT "UQ_ab764743ecbd39b1fc823d2445d" UNIQUE ("description"), CONSTRAINT "PK_d06659689a2bb22ccdc6a1a033b" PRIMARY KEY ("code"))`,
+ );
+ await queryRunner.query(
+ `CREATE TABLE "alcs"."planning_referral" ("audit_deleted_date_at" TIMESTAMP WITH TIME ZONE, "audit_created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "audit_updated_at" TIMESTAMP WITH TIME ZONE DEFAULT now(), "audit_created_by" character varying NOT NULL, "audit_updated_by" character varying, "uuid" uuid NOT NULL DEFAULT gen_random_uuid(), "submission_date" TIMESTAMP WITH TIME ZONE NOT NULL, "due_date" TIMESTAMP WITH TIME ZONE, "response_date" TIMESTAMP WITH TIME ZONE, "referral_description" text, "response_description" text, "card_uuid" uuid NOT NULL, "planning_review_uuid" uuid, CONSTRAINT "REL_57f6fea41fefa2ca864a33b795" UNIQUE ("card_uuid"), CONSTRAINT "PK_1cd8a7e0399adfcc4cbd1ca7cb9" PRIMARY KEY ("uuid"))`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review" DROP COLUMN "type"`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review" DROP CONSTRAINT "REL_03a05aa8fefbc2fc1cdf138d80"`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review" DROP COLUMN "card_uuid"`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review" ADD "document_name" character varying NOT NULL`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review" ADD "type_code" text NOT NULL`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review" ADD "open" boolean NOT NULL DEFAULT true`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review" ADD "closed_date" TIMESTAMP WITH TIME ZONE`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review" ADD "closed_by_uuid" uuid`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review" ADD CONSTRAINT "FK_d06659689a2bb22ccdc6a1a033b" FOREIGN KEY ("type_code") REFERENCES "alcs"."planning_review_type"("code") ON DELETE NO ACTION ON UPDATE NO ACTION`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review" ADD CONSTRAINT "FK_84caebfef3502f3fb80e168ba44" FOREIGN KEY ("closed_by_uuid") REFERENCES "alcs"."user"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_referral" ADD CONSTRAINT "FK_095877a396b8c604d81d674f6f8" FOREIGN KEY ("planning_review_uuid") REFERENCES "alcs"."planning_review"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_referral" ADD CONSTRAINT "FK_57f6fea41fefa2ca864a33b7950" FOREIGN KEY ("card_uuid") REFERENCES "alcs"."card"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`,
+ );
+ }
+
+ public async down(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_referral" DROP CONSTRAINT "FK_57f6fea41fefa2ca864a33b7950"`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_referral" DROP CONSTRAINT "FK_095877a396b8c604d81d674f6f8"`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review" DROP CONSTRAINT "FK_84caebfef3502f3fb80e168ba44"`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review" DROP CONSTRAINT "FK_d06659689a2bb22ccdc6a1a033b"`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review" DROP COLUMN "closed_by_uuid"`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review" DROP COLUMN "closed_date"`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review" DROP COLUMN "open"`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review" DROP COLUMN "type_code"`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review" DROP COLUMN "document_name"`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review" ADD "card_uuid" uuid NOT NULL`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review" ADD CONSTRAINT "REL_03a05aa8fefbc2fc1cdf138d80" UNIQUE ("card_uuid")`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review" ADD "type" character varying NOT NULL`,
+ );
+ await queryRunner.query(`DROP TABLE "alcs"."planning_referral"`);
+ await queryRunner.query(`DROP TABLE "alcs"."planning_review_type"`);
+ await queryRunner.query(
+ `CREATE INDEX "IDX_a62913da5fae4a128c8e8f264f" ON "alcs"."planning_review" ("file_number") `,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review" ADD CONSTRAINT "FK_735dcdd4fa909a60d0fa1828f24" FOREIGN KEY ("card_uuid") REFERENCES "alcs"."card"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`,
+ );
+ }
+}
diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1709663586391-seed_planning_reviews_v2.ts b/services/apps/alcs/src/providers/typeorm/migrations/1709663586391-seed_planning_reviews_v2.ts
new file mode 100644
index 0000000000..271bdd32f0
--- /dev/null
+++ b/services/apps/alcs/src/providers/typeorm/migrations/1709663586391-seed_planning_reviews_v2.ts
@@ -0,0 +1,61 @@
+import { MigrationInterface, QueryRunner } from 'typeorm';
+
+export class SeedPlanningReviewsV21709663586391 implements MigrationInterface {
+ public async up(queryRunner: QueryRunner): Promise {
+ //Statuses
+ await queryRunner.query(`
+ INSERT INTO "alcs"."planning_review_type"
+ ("audit_deleted_date_at", "audit_created_at", "audit_updated_at", "audit_created_by", "audit_updated_by", "label", "code", "description", "short_label", "background_color", "text_color", "html_description") VALUES
+ (NULL, NOW(), NULL, 'migration-seed', NULL, 'Agricultural Area Plan', 'AAPP', 'Agricultural Area Plan', 'AAP', '#F5B8BA', '#313132', DEFAULT),
+ (NULL, NOW(), NULL, 'migration-seed', NULL, 'Misc Studies and Projects', 'MISC', 'Misc Studies and Projects', 'MISC', '#C8FCFC', '#313132', DEFAULT),
+ (NULL, NOW(), NULL, 'migration-seed', NULL, 'L/FNG Boundary Adjustment', 'BAPP', 'L/FNG Boundary Adjustment', 'BA', '#FFDBE3', '#313132', DEFAULT),
+ (NULL, NOW(), NULL, 'migration-seed', NULL, 'ALR Boundary', 'ALRB', 'ALR Boundary', 'ALRB', '#BDDCBD', '#313132', DEFAULT),
+ (NULL, NOW(), NULL, 'migration-seed', NULL, 'Regional Growth Strategy', 'RGSP', 'Regional Growth Strategy', 'RGS', '#FFE1B3', '#313132', DEFAULT),
+ (NULL, NOW(), NULL, 'migration-seed', NULL, 'Crown Land Use Plan', 'CLUP', 'Crown Land Use Plan', 'CLUP', '#B5C7E1', '#313132', DEFAULT),
+ (NULL, NOW(), NULL, 'migration-seed', NULL, 'Official Community Plan', 'OCPP', 'Official Community Plan', 'OCP', '#FFF9C5', '#313132', DEFAULT),
+ (NULL, NOW(), NULL, 'migration-seed', NULL, 'Transportation Plan', 'TPPP', 'Transportation Plan', 'TP', '#EDC0F5', '#313132', DEFAULT),
+ (NULL, NOW(), NULL, 'migration-seed', NULL, 'Utility/Energy Planning', 'UEPP', 'Utility/Energy Planning', 'UEP', '#E1F8C7', '#313132', DEFAULT),
+ (NULL, NOW(), NULL, 'migration-seed', NULL, 'Zoning Bylaw', 'ZBPP', 'Zoning Bylaw', 'ZB', '#B5D5E0', '#313132', DEFAULT),
+ (NULL, NOW(), NULL, 'migration-seed', NULL, 'Parks Planning', 'PARK', 'Parks Planning', 'PARK', '#C8E0FD', '#313132', DEFAULT);
+ `);
+
+ //New Board
+ await queryRunner.query(`
+ INSERT INTO "alcs"."board"
+ ("uuid", "audit_deleted_date_at", "audit_created_at", "audit_updated_at", "audit_created_by", "audit_updated_by", "code", "title", "show_on_schedule") VALUES
+ ('e7b18852-4f8f-419e-83e3-60e706b4a494', NULL, NOW(), NULL, 'migration_seed', NULL, 'rppp', 'Regional Planning', 'f');
+ `);
+
+ //Allow Planning Cards on new board
+ await queryRunner.query(`
+ INSERT INTO "alcs"."board_allowed_card_types_card_type" ("board_uuid", "card_type_code") VALUES
+ ('e7b18852-4f8f-419e-83e3-60e706b4a494', 'PLAN');
+ `);
+
+ //Remove from Vetting
+ await queryRunner.query(`
+ DELETE FROM "alcs"."board_allowed_card_types_card_type" WHERE ("board_uuid" = 'bb70eb85-6250-49b9-9a5c-e3c2e0b9f3a2' AND "card_type_code" = 'PLAN');
+ `);
+
+ //Change creation from Executive Committee to Regional Planning Board
+ await queryRunner.query(`
+ DELETE FROM "alcs"."board_create_card_types_card_type" WHERE ("board_uuid" = 'd8c18278-cb41-474e-a180-534a101243ab' AND "card_type_code" = 'PLAN');
+ `);
+
+ await queryRunner.query(`
+ INSERT INTO "alcs"."board_create_card_types_card_type" ("board_uuid", "card_type_code") VALUES
+ ('e7b18852-4f8f-419e-83e3-60e706b4a494', 'PLAN');
+ `);
+
+ //Add column to board
+ await queryRunner.query(`
+ INSERT INTO "alcs"."board_status"
+ ("uuid", "audit_deleted_date_at", "audit_created_at", "audit_updated_at", "audit_created_by", "audit_updated_by", "order", "board_uuid", "status_code") VALUES
+ ('6560aaec-9b9d-4ad6-9b8b-ccf2ce384b69', NULL, NOW(), NULL, 'migration_seed', NULL, 0, 'e7b18852-4f8f-419e-83e3-60e706b4a494', 'SUBM');
+ `);
+ }
+
+ public async down(): Promise {
+ //Nope
+ }
+}
diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1709752843125-add_legacy_id_to_planning_review.ts b/services/apps/alcs/src/providers/typeorm/migrations/1709752843125-add_legacy_id_to_planning_review.ts
new file mode 100644
index 0000000000..bba87938c6
--- /dev/null
+++ b/services/apps/alcs/src/providers/typeorm/migrations/1709752843125-add_legacy_id_to_planning_review.ts
@@ -0,0 +1,19 @@
+import { MigrationInterface, QueryRunner } from 'typeorm';
+
+export class AddLegacyIdToPlanningReview1709752843125
+ implements MigrationInterface
+{
+ name = 'AddLegacyIdToPlanningReview1709752843125';
+
+ public async up(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_referral" ADD "legacy_id" text`,
+ );
+ }
+
+ public async down(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_referral" DROP COLUMN "legacy_id"`,
+ );
+ }
+}
diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1709754346579-add_table_comments_to_pr.ts b/services/apps/alcs/src/providers/typeorm/migrations/1709754346579-add_table_comments_to_pr.ts
new file mode 100644
index 0000000000..98bc58f880
--- /dev/null
+++ b/services/apps/alcs/src/providers/typeorm/migrations/1709754346579-add_table_comments_to_pr.ts
@@ -0,0 +1,18 @@
+import { MigrationInterface, QueryRunner } from 'typeorm';
+
+export class AddTableCommentsToPr1709754346579 implements MigrationInterface {
+ name = 'AddTableCommentsToPr1709754346579';
+
+ public async up(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(
+ `COMMENT ON TABLE "alcs"."planning_review" IS 'A review of a local government or municipalities plan'`,
+ );
+ await queryRunner.query(
+ `COMMENT ON TABLE "alcs"."planning_referral" IS 'Planning Referrals represent each pass of a Planning Review with their own cards'`,
+ );
+ }
+
+ public async down(): Promise {
+ //No
+ }
+}
diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1709771987741-add_pr_staff_journal.ts b/services/apps/alcs/src/providers/typeorm/migrations/1709771987741-add_pr_staff_journal.ts
new file mode 100644
index 0000000000..fc26c56780
--- /dev/null
+++ b/services/apps/alcs/src/providers/typeorm/migrations/1709771987741-add_pr_staff_journal.ts
@@ -0,0 +1,29 @@
+import { MigrationInterface, QueryRunner } from 'typeorm';
+
+export class AddPrStaffJournal1709771987741 implements MigrationInterface {
+ name = 'AddPrStaffJournal1709771987741';
+
+ public async up(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."staff_journal" ADD "planning_review_uuid" uuid`,
+ );
+ await queryRunner.query(
+ `CREATE INDEX "IDX_dd6d16cefeda057f9f7d1f909b" ON "alcs"."staff_journal" ("planning_review_uuid") `,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."staff_journal" ADD CONSTRAINT "FK_dd6d16cefeda057f9f7d1f909bc" FOREIGN KEY ("planning_review_uuid") REFERENCES "alcs"."planning_review"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`,
+ );
+ }
+
+ public async down(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."staff_journal" DROP CONSTRAINT "FK_dd6d16cefeda057f9f7d1f909bc"`,
+ );
+ await queryRunner.query(
+ `DROP INDEX "alcs"."IDX_dd6d16cefeda057f9f7d1f909b"`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."staff_journal" DROP COLUMN "planning_review_uuid"`,
+ );
+ }
+}
diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1709856439937-add_pr_documents.ts b/services/apps/alcs/src/providers/typeorm/migrations/1709856439937-add_pr_documents.ts
new file mode 100644
index 0000000000..c2b21071a5
--- /dev/null
+++ b/services/apps/alcs/src/providers/typeorm/migrations/1709856439937-add_pr_documents.ts
@@ -0,0 +1,42 @@
+import { MigrationInterface, QueryRunner } from 'typeorm';
+
+export class AddPrDocuments1709856439937 implements MigrationInterface {
+ name = 'AddPrDocuments1709856439937';
+
+ public async up(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(
+ `CREATE TABLE "alcs"."planning_review_document" ("uuid" uuid NOT NULL DEFAULT gen_random_uuid(), "type_code" text, "description" text, "planning_review_uuid" uuid NOT NULL, "document_uuid" uuid, "visibility_flags" text array NOT NULL DEFAULT '{}', "evidentiary_record_sorting" integer, CONSTRAINT "REL_80d9441726c3d26ccd426cd469" UNIQUE ("document_uuid"), CONSTRAINT "PK_b8b1ceeaebfc4a6b5a746f0a85b" PRIMARY KEY ("uuid"))`,
+ );
+ await queryRunner.query(
+ `CREATE INDEX "IDX_e95903f18d734736a1ba855569" ON "alcs"."planning_review_document" ("planning_review_uuid") `,
+ );
+ await queryRunner.query(
+ `COMMENT ON TABLE "alcs"."planning_review_document" IS 'Stores planning review documents'`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review_document" ADD CONSTRAINT "FK_6ed3e4681afbbcd3444d7600a84" FOREIGN KEY ("type_code") REFERENCES "alcs"."document_code"("code") ON DELETE NO ACTION ON UPDATE NO ACTION`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review_document" ADD CONSTRAINT "FK_e95903f18d734736a1ba8555698" 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_document" ADD CONSTRAINT "FK_80d9441726c3d26ccd426cd4699" FOREIGN KEY ("document_uuid") REFERENCES "alcs"."document"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`,
+ );
+ }
+
+ public async down(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review_document" DROP CONSTRAINT "FK_80d9441726c3d26ccd426cd4699"`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review_document" DROP CONSTRAINT "FK_e95903f18d734736a1ba8555698"`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review_document" DROP CONSTRAINT "FK_6ed3e4681afbbcd3444d7600a84"`,
+ );
+ await queryRunner.query(
+ `DROP INDEX "alcs"."IDX_e95903f18d734736a1ba855569"`,
+ );
+ await queryRunner.query(`DROP TABLE "alcs"."planning_review_document"`);
+ }
+}
diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1709857038186-move_legacy_id.ts b/services/apps/alcs/src/providers/typeorm/migrations/1709857038186-move_legacy_id.ts
new file mode 100644
index 0000000000..afebde99c4
--- /dev/null
+++ b/services/apps/alcs/src/providers/typeorm/migrations/1709857038186-move_legacy_id.ts
@@ -0,0 +1,29 @@
+import { MigrationInterface, QueryRunner } from 'typeorm';
+
+export class MoveLegacyId1709857038186 implements MigrationInterface {
+ name = 'MoveLegacyId1709857038186';
+
+ public async up(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_referral" DROP COLUMN "legacy_id"`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review" ADD "legacy_id" text`,
+ );
+ await queryRunner.query(
+ `COMMENT ON COLUMN "alcs"."planning_review"."legacy_id" IS 'Application Id that is applicable only to paper version applications from 70s - 80s'`,
+ );
+ }
+
+ public async down(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(
+ `COMMENT ON COLUMN "alcs"."planning_review"."legacy_id" IS 'Application Id that is applicable only to paper version applications from 70s - 80s'`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_review" DROP COLUMN "legacy_id"`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "alcs"."planning_referral" ADD "legacy_id" text`,
+ );
+ }
+}