From 5982208973eeb4e4144a6e769d19e7e4be33c9c7 Mon Sep 17 00:00:00 2001 From: Graham Stewart Date: Tue, 23 Apr 2024 14:34:56 -0700 Subject: [PATCH 1/7] Fixed wrong coalesce from 0 to 1 --- .../typeorm/Migrations/1713814960226-UpdateIDSequences.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/express-api/src/typeorm/Migrations/1713814960226-UpdateIDSequences.ts b/express-api/src/typeorm/Migrations/1713814960226-UpdateIDSequences.ts index 5a9f2061ba..27a5e2bbaa 100644 --- a/express-api/src/typeorm/Migrations/1713814960226-UpdateIDSequences.ts +++ b/express-api/src/typeorm/Migrations/1713814960226-UpdateIDSequences.ts @@ -20,7 +20,7 @@ export class UpdateIDSequences1713814960226 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { for (const table of tables) { await queryRunner.query( - `SELECT setval('${table}_id_seq', (SELECT COALESCE(MAX(id), 0) FROM ${table}));`, + `SELECT setval('${table}_id_seq', (SELECT COALESCE(MAX(id), 1) FROM ${table}));`, ); } } @@ -31,9 +31,9 @@ export class UpdateIDSequences1713814960226 implements MigrationInterface { try { // Query the maximum ID from the table const result = await queryRunner.query( - `SELECT COALESCE(MAX(id), 0) AS max_id FROM ${table};`, + `SELECT COALESCE(MAX(id), 1) AS max_id FROM ${table};`, ); - const maxId = parseInt(result[0].max_id) || 0; + const maxId = parseInt(result[0].max_id) || 1; // Reset the sequence to max ID + 1 await queryRunner.query(`ALTER SEQUENCE "${sequence}" RESTART WITH ${maxId + 1};`); From c3874795915523477fbab1ca67ebc6eb157e3419 Mon Sep 17 00:00:00 2001 From: Graham Stewart Date: Tue, 23 Apr 2024 16:07:41 -0700 Subject: [PATCH 2/7] PID will now be properly zero padded in table and detail view. Same for edit dialog. Net usable area validation fixed. Fixed searching on classification type. Fixed saving null tenancy date. --- .../src/components/property/PropertyDetail.tsx | 3 ++- .../src/components/property/PropertyDialog.tsx | 15 +++++++++------ .../src/components/property/PropertyForms.tsx | 10 +++++----- .../src/components/property/PropertyTable.tsx | 8 +++++--- react-app/src/utilities/formatters.tsx | 4 ++++ 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/react-app/src/components/property/PropertyDetail.tsx b/react-app/src/components/property/PropertyDetail.tsx index ae0bac3a74..ace61f0639 100644 --- a/react-app/src/components/property/PropertyDetail.tsx +++ b/react-app/src/components/property/PropertyDetail.tsx @@ -21,6 +21,7 @@ import { } from './PropertyDialog'; import { PropertyType } from './PropertyForms'; import MetresSquared from '@/components/text/MetresSquared'; +import { zeroPadPID } from '@/utilities/formatters'; interface IPropertyDetail { onClose: () => void; @@ -150,7 +151,7 @@ const PropertyDetail = (props: IPropertyDetail) => { } else { const info: any = { Classification: data.Classification, - PID: data.PID, + PID: data.PID ? zeroPadPID(data.PID) : undefined, PIN: data.PIN, PostalCode: data.Postal, AdministrativeArea: data.AdministrativeArea?.Name, diff --git a/react-app/src/components/property/PropertyDialog.tsx b/react-app/src/components/property/PropertyDialog.tsx index b5f8fd057b..ec468135e3 100644 --- a/react-app/src/components/property/PropertyDialog.tsx +++ b/react-app/src/components/property/PropertyDialog.tsx @@ -15,7 +15,7 @@ import { PropertyType, NetBookValue, } from './PropertyForms'; -import { parseFloatOrNull, parseIntOrNull } from '@/utilities/formatters'; +import { parseFloatOrNull, parseIntOrNull, zeroPadPID } from '@/utilities/formatters'; interface IParcelInformationEditDialog { initialValues: Parcel; @@ -59,8 +59,8 @@ export const ParcelInformationEditDialog = (props: IParcelInformationEditDialog) infoFormMethods.reset({ NotOwned: initialValues?.NotOwned, Address1: initialValues?.Address1, + PID: initialValues?.PID ? zeroPadPID(initialValues.PID) : '', PIN: String(initialValues?.PIN ?? ''), - PID: String(initialValues?.PID ?? ''), Postal: initialValues?.Postal, AdministrativeAreaId: initialValues?.AdministrativeAreaId, LandArea: String(initialValues?.LandArea ?? ''), @@ -155,7 +155,7 @@ export const BuildingInformationEditDialog = (props: IBuildingInformationEditDia TotalArea: '', RentableArea: '', BuildingTenancy: '', - BuildingTenancyUpdatedOn: dayjs(), + BuildingTenancyUpdatedOn: null, Location: null, }, }); @@ -164,7 +164,7 @@ export const BuildingInformationEditDialog = (props: IBuildingInformationEditDia infoFormMethods.reset({ Address1: initialValues?.Address1, PIN: String(initialValues?.PIN ?? ''), - PID: String(initialValues?.PID ?? ''), + PID: initialValues?.PID ? zeroPadPID(initialValues.PID) : '', Postal: initialValues?.Postal, AdministrativeAreaId: initialValues?.AdministrativeAreaId, IsSensitive: initialValues?.IsSensitive, @@ -176,7 +176,9 @@ export const BuildingInformationEditDialog = (props: IBuildingInformationEditDia TotalArea: String(initialValues?.TotalArea ?? ''), RentableArea: String(initialValues?.RentableArea ?? ''), BuildingTenancy: initialValues?.BuildingTenancy, - BuildingTenancyUpdatedOn: dayjs(initialValues?.BuildingTenancyUpdatedOn), + BuildingTenancyUpdatedOn: initialValues?.BuildingTenancyUpdatedOn + ? dayjs(initialValues?.BuildingTenancyUpdatedOn) + : null, Location: initialValues?.Location, }); }, [initialValues]); @@ -193,7 +195,8 @@ export const BuildingInformationEditDialog = (props: IBuildingInformationEditDia formValues.PIN = parseIntOrNull(formValues.PIN); formValues.TotalArea = parseFloatOrNull(formValues.TotalArea); formValues.RentableArea = parseFloatOrNull(formValues.RentableArea); - formValues.BuildingTenancyUpdatedOn = formValues.BuildingTenancyUpdatedOn.toDate(); + formValues.BuildingTenancyUpdatedOn = + formValues.BuildingTenancyUpdatedOn?.toDate() ?? null; api.buildings.updateBuildingById(initialValues.Id, formValues).then(() => postSubmit()); } }} diff --git a/react-app/src/components/property/PropertyForms.tsx b/react-app/src/components/property/PropertyForms.tsx index 072202fb2b..0f99f21b69 100644 --- a/react-app/src/components/property/PropertyForms.tsx +++ b/react-app/src/components/property/PropertyForms.tsx @@ -388,6 +388,11 @@ export const BuildingInformationForm = (props: IBuildingInformationForm) => { fullWidth required numeric + rules={{ + validate: (val, formVals) => + val <= formVals.RentableArea || + `Cannot be larger than Net usable area: ${val} <= ${formVals?.RentableArea}`, + }} InputProps={{ endAdornment: ( @@ -400,11 +405,6 @@ export const BuildingInformationForm = (props: IBuildingInformationForm) => { - val <= formVals.TotalArea || - `Cannot be larger than Total area: ${val} <= ${formVals?.TotalArea}`, - }} required label={'Net usable area'} fullWidth diff --git a/react-app/src/components/property/PropertyTable.tsx b/react-app/src/components/property/PropertyTable.tsx index 185f671680..40db2bbdb9 100644 --- a/react-app/src/components/property/PropertyTable.tsx +++ b/react-app/src/components/property/PropertyTable.tsx @@ -130,21 +130,23 @@ const PropertyTable = (props: IPropertyTable) => { ); }, renderCell: (params) => { - const classificationName = classifications?.find((cl) => cl.Id === params.value)?.Name; + //const classificationName = classifications?.find((cl) => cl.Id === params.value)?.Name; return ( ); }, + valueGetter: (_value, row) => + classifications?.find((cl) => cl.Id === row.ClassificationId)?.Name ?? '', }, { field: 'PID', headerName: 'PID', flex: 1, - valueFormatter: (value: number | null) => value ?? 'N/A', + valueGetter: (value: number | null) => (value ? String(value).padStart(9, '0') : 'N/A'), }, { field: 'AgencyId', diff --git a/react-app/src/utilities/formatters.tsx b/react-app/src/utilities/formatters.tsx index 756057db5f..ff4053f846 100644 --- a/react-app/src/utilities/formatters.tsx +++ b/react-app/src/utilities/formatters.tsx @@ -81,3 +81,7 @@ export const parseIntOrNull = (int: string) => { export const parseFloatOrNull = (flt: string) => { return flt.length > 0 ? parseFloat(flt) : null; }; + +export const zeroPadPID = (pid: number | string): string => { + return String(pid).padStart(9, '0'); +}; From 4e3b8f5269daed7c9f3fdc7f94b450ebf1d2efd4 Mon Sep 17 00:00:00 2001 From: Graham Stewart Date: Wed, 24 Apr 2024 10:31:55 -0700 Subject: [PATCH 3/7] Reverted change for area validation --- react-app/src/components/property/PropertyForms.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/react-app/src/components/property/PropertyForms.tsx b/react-app/src/components/property/PropertyForms.tsx index 0f99f21b69..7784613c3d 100644 --- a/react-app/src/components/property/PropertyForms.tsx +++ b/react-app/src/components/property/PropertyForms.tsx @@ -388,11 +388,6 @@ export const BuildingInformationForm = (props: IBuildingInformationForm) => { fullWidth required numeric - rules={{ - validate: (val, formVals) => - val <= formVals.RentableArea || - `Cannot be larger than Net usable area: ${val} <= ${formVals?.RentableArea}`, - }} InputProps={{ endAdornment: ( @@ -409,6 +404,11 @@ export const BuildingInformationForm = (props: IBuildingInformationForm) => { label={'Net usable area'} fullWidth numeric + rules={{ + validate: (val, formVals) => + val <= formVals.TotalArea || + `Cannot be larger than Net usable area: ${val} <= ${formVals?.TotalArea}`, + }} InputProps={{ endAdornment: ( From fecec6c2a1c606ec5a92190a59d59dc01550e213 Mon Sep 17 00:00:00 2001 From: Graham Stewart Date: Wed, 24 Apr 2024 11:45:55 -0700 Subject: [PATCH 4/7] Removed check in add parcel service for PID length --- express-api/src/services/parcels/parcelServices.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/express-api/src/services/parcels/parcelServices.ts b/express-api/src/services/parcels/parcelServices.ts index 2af508549a..f801eafb39 100644 --- a/express-api/src/services/parcels/parcelServices.ts +++ b/express-api/src/services/parcels/parcelServices.ts @@ -15,11 +15,6 @@ const parcelRepo = AppDataSource.getRepository(Parcel); const addParcel = async (parcel: DeepPartial) => { const inPID = Number(parcel.PID); - const matchPID = inPID.toString().search(/^\d{9}$/); - if (parcel.PID != null && matchPID === -1) { - throw new ErrorWithCode('PID must be a number and in the format #########'); - } - const existingParcel = parcel.PID != null ? await getParcelByPid(inPID) : undefined; if (existingParcel) { From 826f7db8eeb4dd045d3def31aa396a366553b60b Mon Sep 17 00:00:00 2001 From: Graham Stewart Date: Wed, 24 Apr 2024 11:54:27 -0700 Subject: [PATCH 5/7] Improved add parcel service pid check. Also added to the validation error message on the frontend. --- express-api/src/services/parcels/parcelServices.ts | 9 +++++++-- react-app/src/components/property/PropertyForms.tsx | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/express-api/src/services/parcels/parcelServices.ts b/express-api/src/services/parcels/parcelServices.ts index f801eafb39..655b2d4710 100644 --- a/express-api/src/services/parcels/parcelServices.ts +++ b/express-api/src/services/parcels/parcelServices.ts @@ -13,9 +13,14 @@ const parcelRepo = AppDataSource.getRepository(Parcel); * @throws ErrorWithCode If the parcel already exists or is unable to be added. */ const addParcel = async (parcel: DeepPartial) => { - const inPID = Number(parcel.PID); + const numberPID = Number(parcel.PID); - const existingParcel = parcel.PID != null ? await getParcelByPid(inPID) : undefined; + const stringPID = numberPID.toString(); + if (parcel.PID != null && (stringPID.length > 9 || isNaN(numberPID))) { + throw new ErrorWithCode('PID must be a number and in the format #########'); + } + + const existingParcel = parcel.PID != null ? await getParcelByPid(numberPID) : undefined; if (existingParcel) { throw new ErrorWithCode('Parcel already exists.', 409); diff --git a/react-app/src/components/property/PropertyForms.tsx b/react-app/src/components/property/PropertyForms.tsx index 7784613c3d..0f052871da 100644 --- a/react-app/src/components/property/PropertyForms.tsx +++ b/react-app/src/components/property/PropertyForms.tsx @@ -193,7 +193,7 @@ export const GeneralInformationForm = (props: IGeneralInformationForm) => { validate: (val, formVals) => (val.length <= 9 && (val.length > 0 || formVals['PIN'].length > 0 || propertyType === 'Building')) || - 'Must have set either PID or PIN', + 'Must have set either PID or PIN not exceeding 9 digits.', }} /> @@ -213,7 +213,7 @@ export const GeneralInformationForm = (props: IGeneralInformationForm) => { validate: (val, formVals) => (val.length <= 9 && (val.length > 0 || formVals['PID'].length > 0 || propertyType === 'Building')) || - 'Must have set either PID or PIN', + 'Must have set either PID or PIN not exceeding 9 digits.', }} /> From 80d0d22b34543755f127657529ba862946897efc Mon Sep 17 00:00:00 2001 From: dbarkowsky Date: Wed, 24 Apr 2024 12:36:48 -0700 Subject: [PATCH 6/7] Correct error message on net usable area --- react-app/src/components/property/PropertyForms.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-app/src/components/property/PropertyForms.tsx b/react-app/src/components/property/PropertyForms.tsx index 0f052871da..685a774a43 100644 --- a/react-app/src/components/property/PropertyForms.tsx +++ b/react-app/src/components/property/PropertyForms.tsx @@ -407,7 +407,7 @@ export const BuildingInformationForm = (props: IBuildingInformationForm) => { rules={{ validate: (val, formVals) => val <= formVals.TotalArea || - `Cannot be larger than Net usable area: ${val} <= ${formVals?.TotalArea}`, + `Cannot be larger than Total Area: ${val} <= ${formVals?.TotalArea}`, }} InputProps={{ endAdornment: ( From d877a8f85f3c0146a0791a2a89bb94b1a2fe7fca Mon Sep 17 00:00:00 2001 From: Graham Stewart Date: Wed, 24 Apr 2024 16:07:10 -0700 Subject: [PATCH 7/7] Accounting for text field values possibly being changed from string to number --- react-app/src/components/property/PropertyForms.tsx | 12 ++++++++---- react-app/src/utilities/formatters.tsx | 10 ++++++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/react-app/src/components/property/PropertyForms.tsx b/react-app/src/components/property/PropertyForms.tsx index 0f052871da..e416d8a236 100644 --- a/react-app/src/components/property/PropertyForms.tsx +++ b/react-app/src/components/property/PropertyForms.tsx @@ -191,8 +191,10 @@ export const GeneralInformationForm = (props: IGeneralInformationForm) => { }} rules={{ validate: (val, formVals) => - (val.length <= 9 && - (val.length > 0 || formVals['PIN'].length > 0 || propertyType === 'Building')) || + (String(val).length <= 9 && + (String(val).length > 0 || + String(formVals['PIN']).length > 0 || + propertyType === 'Building')) || 'Must have set either PID or PIN not exceeding 9 digits.', }} /> @@ -211,8 +213,10 @@ export const GeneralInformationForm = (props: IGeneralInformationForm) => { }} rules={{ validate: (val, formVals) => - (val.length <= 9 && - (val.length > 0 || formVals['PID'].length > 0 || propertyType === 'Building')) || + (String(val).length <= 9 && + (String(val).length > 0 || + String(formVals['PID']).length > 0 || + propertyType === 'Building')) || 'Must have set either PID or PIN not exceeding 9 digits.', }} /> diff --git a/react-app/src/utilities/formatters.tsx b/react-app/src/utilities/formatters.tsx index ff4053f846..29d12e264c 100644 --- a/react-app/src/utilities/formatters.tsx +++ b/react-app/src/utilities/formatters.tsx @@ -74,11 +74,17 @@ export const formatMoney = (value?: number | ''): string => { return formatter.format(value || 0); }; -export const parseIntOrNull = (int: string) => { +export const parseIntOrNull = (int: string | number) => { + if (typeof int === 'number') { + return int; + } return int.length > 0 ? parseInt(int) : null; }; -export const parseFloatOrNull = (flt: string) => { +export const parseFloatOrNull = (flt: string | number) => { + if (typeof flt === 'number') { + return flt; + } return flt.length > 0 ? parseFloat(flt) : null; };