Skip to content

Commit

Permalink
PIMS-1871: Fuzzy search is returning properties already in other proj…
Browse files Browse the repository at this point in the history
…ects (#2590)
  • Loading branch information
LawrenceLau2020 authored Aug 2, 2024
1 parent 38d9617 commit f37b7a9
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 7 deletions.
52 changes: 45 additions & 7 deletions express-api/src/services/properties/propertiesServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ import userServices from '../users/usersServices';
import { Brackets, FindManyOptions, FindOptionsWhere, ILike, In, QueryRunner } from 'typeorm';
import { SSOUser } from '@bcgov/citz-imb-sso-express';
import { PropertyType } from '@/constants/propertyType';
import { ProjectStatus } from '@/constants/projectStatus';
import { ProjectProperty } from '@/typeorm/Entities/ProjectProperty';
import { ProjectStatus as ProjectStatusEntity } from '@/typeorm/Entities/ProjectStatus';
import { parentPort } from 'worker_threads';

/**
Expand All @@ -42,13 +45,41 @@ import { parentPort } from 'worker_threads';
* @returns An object containing the found parcels and buildings that match the search criteria.
*/
const propertiesFuzzySearch = async (keyword: string, limit?: number, agencyIds?: number[]) => {
const allStatusIds = (await AppDataSource.getRepository(ProjectStatusEntity).find()).map(
(i) => i.Id,
);
const allowedStatusIds = [
ProjectStatus.CANCELLED,
ProjectStatus.DENIED,
ProjectStatus.TRANSFERRED_WITHIN_GRE,
];
const disallowedStatusIds = allStatusIds.filter((s) => !allowedStatusIds.includes(s));

// Find all properties that are attached to projects in states other than Cancelled, Transferred within GRE, or Denied
// Get project properties that are in projects currently in the disallowed statuses
const excludedIds = await AppDataSource.getRepository(ProjectProperty).find({
relations: {
Project: true,
},
where: {
Project: {
StatusId: In(disallowedStatusIds),
},
},
});

const excludedParcelIds = excludedIds.map((row) => row.ParcelId).filter((id) => id != null);

const excludedBuildingIds = excludedIds.map((row) => row.BuildingId).filter((id) => id != null);

const parcelsQuery = await AppDataSource.getRepository(Parcel)
.createQueryBuilder('parcel')
.leftJoinAndSelect('parcel.Agency', 'agency')
.leftJoinAndSelect('parcel.AdministrativeArea', 'adminArea')
.leftJoinAndSelect('parcel.Evaluations', 'evaluations')
.leftJoinAndSelect('parcel.Fiscals', 'fiscals')
.leftJoinAndSelect('parcel.Classification', 'classification')
// Match the search criteria
.where(
new Brackets((qb) => {
qb.where(`LPAD(parcel.pid::text, 9, '0') ILIKE '%${keyword.replaceAll('-', '')}%'`)
Expand All @@ -58,7 +89,10 @@ const propertiesFuzzySearch = async (keyword: string, limit?: number, agencyIds?
.orWhere(`parcel.address1 ILIKE '%${keyword}%'`);
}),
)
.andWhere(`classification.Name in ('Surplus Encumbered', 'Surplus Active')`);
// Only include surplus properties
.andWhere(`classification.Name in ('Surplus Encumbered', 'Surplus Active')`)
// Exclude if already is a project property in a project that's in a disallowed status
.andWhere(`parcel.id NOT IN(:...excludedParcelIds)`, { excludedParcelIds });

// Add the optional agencyIds filter if provided
if (agencyIds && agencyIds.length > 0) {
Expand All @@ -76,16 +110,20 @@ const propertiesFuzzySearch = async (keyword: string, limit?: number, agencyIds?
.leftJoinAndSelect('building.Evaluations', 'evaluations')
.leftJoinAndSelect('building.Fiscals', 'fiscals')
.leftJoinAndSelect('building.Classification', 'classification')
// Match the search criteria
.where(
new Brackets((qb) => {
qb.where(`building.pid::text like :keyword`, { keyword: `%${keyword}%` })
.orWhere(`building.pin::text like :keyword`, { keyword: `%${keyword}%` })
.orWhere(`agency.name like :keyword`, { keyword: `%${keyword}%` })
.orWhere(`adminArea.name like :keyword`, { keyword: `%${keyword}%` })
.orWhere(`building.address1 like :keyword`, { keyword: `%${keyword}%` });
qb.where(`LPAD(building.pid::text, 9, '0') ILIKE '%${keyword.replaceAll('-', '')}%'`)
.orWhere(`building.pin::text ILIKE '%${keyword}%'`)
.orWhere(`agency.name ILIKE '%${keyword}%'`)
.orWhere(`adminArea.name ILIKE '%${keyword}%'`)
.orWhere(`building.address1 ILIKE '%${keyword}%'`);
}),
)
.andWhere(`classification.Name in ('Surplus Encumbered', 'Surplus Active')`);
// Only include surplus properties
.andWhere(`classification.Name in ('Surplus Encumbered', 'Surplus Active')`)
// Exclude if already is a project property in a project that's in a disallowed status
.andWhere(`building.id NOT IN(:...excludedBuildingIds)`, { excludedBuildingIds });

if (agencyIds && agencyIds.length > 0) {
buildingsQuery.andWhere(`building.agency_id IN (:...agencyIds)`, { agencyIds });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { ImportResult } from '@/typeorm/Entities/ImportResult';
import { Parcel } from '@/typeorm/Entities/Parcel';
import { ParcelEvaluation } from '@/typeorm/Entities/ParcelEvaluation';
import { ParcelFiscal } from '@/typeorm/Entities/ParcelFiscal';
import { ProjectProperty } from '@/typeorm/Entities/ProjectProperty';
import { ProjectStatus } from '@/typeorm/Entities/ProjectStatus';
import { PropertyClassification } from '@/typeorm/Entities/PropertyClassification';
import { User } from '@/typeorm/Entities/User';
import { MapProperties } from '@/typeorm/Entities/views/MapPropertiesView';
Expand All @@ -38,6 +40,8 @@ import {
produceBuildingEvaluation,
produceBuildingFiscal,
produceSSO,
produceProjectStatus,
produceProjectProperty,
} from 'tests/testUtils/factories';
import { DeepPartial, EntityTarget, ObjectLiteral } from 'typeorm';
import xlsx, { WorkSheet } from 'xlsx';
Expand Down Expand Up @@ -82,6 +86,46 @@ const _propertyUnionCreateQueryBuilder: any = {
getManyAndCount: () => [[producePropertyUnion()], 1],
};

const _projectStatusCreateQueryBuilder: any = {
select: () => _projectStatusCreateQueryBuilder,
leftJoinAndSelect: () => _projectStatusCreateQueryBuilder,
where: () => _projectStatusCreateQueryBuilder,
orWhere: () => _projectStatusCreateQueryBuilder,
andWhere: () => _projectStatusCreateQueryBuilder,
take: () => _projectStatusCreateQueryBuilder,
skip: () => _projectStatusCreateQueryBuilder,
orderBy: () => _projectStatusCreateQueryBuilder,
getMany: () => [produceProjectStatus()],
};

const _projectPropertyCreateQueryBuilder: any = {
select: () => _projectPropertyCreateQueryBuilder,
leftJoinAndSelect: () => _projectPropertyCreateQueryBuilder,
where: () => _projectPropertyCreateQueryBuilder,
orWhere: () => _projectPropertyCreateQueryBuilder,
andWhere: () => _projectPropertyCreateQueryBuilder,
take: () => _projectPropertyCreateQueryBuilder,
skip: () => _projectPropertyCreateQueryBuilder,
orderBy: () => _projectPropertyCreateQueryBuilder,
getMany: () => [produceProjectProperty()],
};

jest
.spyOn(AppDataSource.getRepository(ProjectProperty), 'createQueryBuilder')
.mockImplementation(() => _projectPropertyCreateQueryBuilder);

jest
.spyOn(AppDataSource.getRepository(ProjectProperty), 'find')
.mockImplementation(async () => [produceProjectProperty()]);

jest
.spyOn(AppDataSource.getRepository(ProjectStatus), 'createQueryBuilder')
.mockImplementation(() => _projectStatusCreateQueryBuilder);

jest
.spyOn(AppDataSource.getRepository(ProjectStatus), 'find')
.mockImplementation(async () => [produceProjectStatus()]);

jest
.spyOn(AppDataSource.getRepository(Parcel), 'createQueryBuilder')
.mockImplementation(() => _parcelsCreateQueryBuilder);
Expand Down

0 comments on commit f37b7a9

Please sign in to comment.