diff --git a/source/backend/dal/Models/PropertyFilterCriteria.cs b/source/backend/dal/Models/PropertyFilterCriteria.cs
index aa1692b895..3adc064f15 100644
--- a/source/backend/dal/Models/PropertyFilterCriteria.cs
+++ b/source/backend/dal/Models/PropertyFilterCriteria.cs
@@ -34,6 +34,11 @@ public class PropertyFilterCriteria
///
public string LeaseStatus { get; set; }
+ ///
+ /// get/set - The lease receivable/payable type to filter by.
+ ///
+ public string LeasePayRcvblType { get; set; }
+
///
/// get/set - The multiple lease types to filter by.
///
diff --git a/source/backend/dal/Repositories/PropertyRepository.cs b/source/backend/dal/Repositories/PropertyRepository.cs
index 8f378ff0f5..53b2849a72 100644
--- a/source/backend/dal/Repositories/PropertyRepository.cs
+++ b/source/backend/dal/Repositories/PropertyRepository.cs
@@ -448,6 +448,12 @@ public HashSet GetMatchingIds(PropertyFilterCriteria filter)
p.PimsPropertyLeases.Any(pl => filter.LeasePurposes.Contains(pl.Lease.LeasePurposeTypeCode)));
}
+ if (!string.IsNullOrEmpty(filter.LeasePayRcvblType))
+ {
+ query = query.Where(p =>
+ p.PimsPropertyLeases.Any(pl => pl.Lease.LeasePayRvblTypeCode == filter.LeasePayRcvblType || filter.LeasePayRcvblType == "all"));
+ }
+
// Anomalies
if (filter.AnomalyIds != null && filter.AnomalyIds.Count > 0)
{
diff --git a/source/backend/tests/unit/dal/Repositories/PropertyRepositoryTest.cs b/source/backend/tests/unit/dal/Repositories/PropertyRepositoryTest.cs
index 3471d2955a..a3bfb687e1 100644
--- a/source/backend/tests/unit/dal/Repositories/PropertyRepositoryTest.cs
+++ b/source/backend/tests/unit/dal/Repositories/PropertyRepositoryTest.cs
@@ -132,6 +132,165 @@ public void GetById_Success()
}
#endregion
+ #region GetMatchingIds
+ [Fact]
+ public void GetMatchingIds_LeaseRcbvl_All_Success()
+ {
+ // Arrange
+ var repository = CreateRepositoryWithPermissions(Permissions.PropertyView);
+ var property = EntityHelper.CreateProperty(100);
+ var lease = EntityHelper.CreateLease(1, addProperty:false);
+ property.PimsPropertyLeases.Add(new PimsPropertyLease() { PropertyId = property.Internal_Id, LeaseId = lease.Internal_Id, Lease = lease });
+ _helper.AddAndSaveChanges(property);
+
+ // Act
+ var result = repository.GetMatchingIds(new PropertyFilterCriteria() { LeasePayRcvblType = "all" });
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Should().HaveCount(1);
+ }
+
+ [Fact]
+ public void GetMatchingIds_LeaseStatus_Success()
+ {
+ // Arrange
+ var repository = CreateRepositoryWithPermissions(Permissions.PropertyView);
+ var property = EntityHelper.CreateProperty(100);
+ var lease = EntityHelper.CreateLease(1, pimsLeaseStatusType: new PimsLeaseStatusType() { Id = "test2" }, addProperty: false);
+ property.PimsPropertyLeases.Add(new PimsPropertyLease() { PropertyId = property.Internal_Id, LeaseId = lease.Internal_Id, Lease = lease });
+ _helper.AddAndSaveChanges(property);
+
+ // Act
+ var result = repository.GetMatchingIds(new PropertyFilterCriteria() { LeaseStatus = "test2" });
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Should().HaveCount(1);
+ }
+
+ [Fact]
+ public void GetMatchingIds_LeaseType_Success()
+ {
+ // Arrange
+ var repository = CreateRepositoryWithPermissions(Permissions.PropertyView);
+ var property = EntityHelper.CreateProperty(100);
+ var lease = EntityHelper.CreateLease(1, pimsLeaseLicenseType: new PimsLeaseLicenseType() { Id = "test" }, addProperty: false);
+ property.PimsPropertyLeases.Add(new PimsPropertyLease() { PropertyId = property.Internal_Id, LeaseId = lease.Internal_Id, Lease = lease });
+ _helper.AddAndSaveChanges(property);
+
+ // Act
+ var result = repository.GetMatchingIds(new PropertyFilterCriteria() { LeaseTypes = new List() { "test" } });
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Should().HaveCount(1);
+ }
+
+ [Fact]
+ public void GetMatchingIds_LeasePurpose_Success()
+ {
+ // Arrange
+ var repository = CreateRepositoryWithPermissions(Permissions.PropertyView);
+ var property = EntityHelper.CreateProperty(100);
+ var lease = EntityHelper.CreateLease(1, pimsLeasePurposeType: new PimsLeasePurposeType() { Id = "test" }, addProperty: false);
+ property.PimsPropertyLeases.Add(new PimsPropertyLease() { PropertyId = property.Internal_Id, LeaseId = lease.Internal_Id, Lease = lease });
+ _helper.AddAndSaveChanges(property);
+
+ // Act
+ var result = repository.GetMatchingIds(new PropertyFilterCriteria() { LeasePurposes = new List() { "test" } });
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Should().HaveCount(1);
+ }
+
+ [Fact]
+ public void GetMatchingIds_Anomaly_Success()
+ {
+ // Arrange
+ var repository = CreateRepositoryWithPermissions(Permissions.PropertyView);
+ var property = EntityHelper.CreateProperty(100);
+ property.PimsPropPropAnomalyTypes.Add(new PimsPropPropAnomalyType() { PropertyAnomalyTypeCode = "test" });
+ _helper.AddAndSaveChanges(property);
+
+ // Act
+ var result = repository.GetMatchingIds(new PropertyFilterCriteria() { AnomalyIds = new List() { "test" } });
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Should().HaveCount(1);
+ }
+
+ [Fact]
+ public void GetMatchingIds_Project_Success()
+ {
+ // Arrange
+ var repository = CreateRepositoryWithPermissions(Permissions.PropertyView);
+ var property = EntityHelper.CreateProperty(100);
+ property.PimsPropertyAcquisitionFiles.Add(new PimsPropertyAcquisitionFile() { AcquisitionFile = new PimsAcquisitionFile() { ProjectId = 1 } });
+ _helper.AddAndSaveChanges(property);
+
+ // Act
+ var result = repository.GetMatchingIds(new PropertyFilterCriteria() { ProjectId = 1 });
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Should().HaveCount(1);
+ }
+
+ [Fact]
+ public void GetMatchingIds_Tenure_Success()
+ {
+ // Arrange
+ var repository = CreateRepositoryWithPermissions(Permissions.PropertyView);
+ var property = EntityHelper.CreateProperty(100);
+ property.PimsPropPropTenureTypes.Add(new PimsPropPropTenureType() { PropertyTenureTypeCode = "test" });
+ _helper.AddAndSaveChanges(property);
+
+ // Act
+ var result = repository.GetMatchingIds(new PropertyFilterCriteria() { TenureStatuses = new List() { "test" } });
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Should().HaveCount(1);
+ }
+
+ [Fact]
+ public void GetMatchingIds_TenureRoad_Success()
+ {
+ // Arrange
+ var repository = CreateRepositoryWithPermissions(Permissions.PropertyView);
+ var property = EntityHelper.CreateProperty(100);
+ property.PimsPropPropRoadTypes.Add(new PimsPropPropRoadType() { PropertyRoadTypeCode = "test" });
+ _helper.AddAndSaveChanges(property);
+
+ // Act
+ var result = repository.GetMatchingIds(new PropertyFilterCriteria() { TenureRoadTypes = new List() { "test" } });
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Should().HaveCount(1);
+ }
+
+ [Fact]
+ public void GetMatchingIds_TenurePph_Success()
+ {
+ // Arrange
+ var repository = CreateRepositoryWithPermissions(Permissions.PropertyView);
+ var property = EntityHelper.CreateProperty(100);
+ property.PphStatusTypeCode = "test";
+ _helper.AddAndSaveChanges(property);
+
+ // Act
+ var result = repository.GetMatchingIds(new PropertyFilterCriteria() { TenurePPH = "test" });
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Should().HaveCount(1);
+ }
+ #endregion
+
#region GetByPid
[Fact]
public void GetByPid_Success()
diff --git a/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/FilterContentContainer.test.tsx b/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/FilterContentContainer.test.tsx
new file mode 100644
index 0000000000..65d7e51e11
--- /dev/null
+++ b/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/FilterContentContainer.test.tsx
@@ -0,0 +1,87 @@
+import { FormikProps } from 'formik';
+import { createMemoryHistory } from 'history';
+import { forwardRef } from 'react';
+
+import { useMapStateMachine } from '@/components/common/mapFSM/MapStateMachineContext';
+import { mockLookups } from '@/mocks/lookups.mock';
+import { mapMachineBaseMock } from '@/mocks/mapFSM.mock';
+import { getMockApiPropertyManagement } from '@/mocks/propertyManagement.mock';
+import { lookupCodesSlice } from '@/store/slices/lookupCodes';
+import { render, RenderOptions, waitFor } from '@/utils/test-utils';
+
+import { FilterContentContainer, IFilterContentContainerProps } from './FilterContentContainer';
+import { IFilterContentFormProps } from './FilterContentForm';
+import { PropertyFilterFormModel } from './models';
+
+const history = createMemoryHistory();
+const storeState = {
+ [lookupCodesSlice.name]: { lookupCodes: mockLookups },
+};
+
+const mockGetApi = {
+ error: undefined,
+ response: [1] as number[] | undefined,
+ execute: jest.fn().mockResolvedValue([1]),
+ loading: false,
+};
+jest.mock('@/components/common/mapFSM/MapStateMachineContext');
+jest.mock('@/hooks/repositories/usePimsPropertyRepository', () => ({
+ usePimsPropertyRepository: () => {
+ return {
+ getMatchingProperties: mockGetApi,
+ };
+ },
+}));
+
+describe('FilterContentContainer component', () => {
+ let viewProps: IFilterContentFormProps;
+
+ const View = forwardRef, IFilterContentFormProps>((props, ref) => {
+ viewProps = props;
+ return <>>;
+ });
+
+ const setup = (
+ renderOptions?: RenderOptions & { props?: Partial },
+ ) => {
+ renderOptions = renderOptions ?? {};
+ const utils = render(, {
+ ...renderOptions,
+ store: storeState,
+ history,
+ });
+
+ return {
+ ...utils,
+ };
+ };
+
+ beforeEach(() => {
+ jest.resetAllMocks();
+ (useMapStateMachine as jest.Mock).mockImplementation(() => mapMachineBaseMock);
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('fetches filter data from the api', async () => {
+ mockGetApi.execute.mockResolvedValue(getMockApiPropertyManagement(1));
+ setup({});
+ viewProps.onChange(new PropertyFilterFormModel());
+ expect(mockGetApi.execute).toBeCalledWith(new PropertyFilterFormModel().toApi());
+ await waitFor(() =>
+ expect(mapMachineBaseMock.setVisiblePimsProperties).toBeCalledWith({
+ additionalDetails: 'test',
+ id: 1,
+ isLeaseActive: false,
+ isLeaseExpired: false,
+ isTaxesPayable: null,
+ isUtilitiesPayable: null,
+ leaseExpiryDate: null,
+ managementPurposes: [],
+ rowVersion: 1,
+ }),
+ );
+ });
+});
diff --git a/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/FilterContentContainer.tsx b/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/FilterContentContainer.tsx
index 345b6588d8..0f5f4f6a2b 100644
--- a/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/FilterContentContainer.tsx
+++ b/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/FilterContentContainer.tsx
@@ -9,7 +9,7 @@ import { Api_PropertyFilterCriteria } from '@/models/api/ProjectFilterCriteria';
import { IFilterContentFormProps } from './FilterContentForm';
import { PropertyFilterFormModel } from './models';
-interface IFilterContentContainerProps {
+export interface IFilterContentContainerProps {
View: React.FunctionComponent;
}
diff --git a/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/FilterContentForm.test.tsx b/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/FilterContentForm.test.tsx
new file mode 100644
index 0000000000..acce1d6491
--- /dev/null
+++ b/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/FilterContentForm.test.tsx
@@ -0,0 +1,74 @@
+import { createMemoryHistory } from 'history';
+
+import { mockLookups } from '@/mocks/lookups.mock';
+import { getMockApiPropertyManagement } from '@/mocks/propertyManagement.mock';
+import { Api_PropertyManagement } from '@/models/api/Property';
+import { lookupCodesSlice } from '@/store/slices/lookupCodes';
+import { act, render, RenderOptions, userEvent } from '@/utils/test-utils';
+
+import { FilterContentForm, IFilterContentFormProps } from './FilterContentForm';
+
+const history = createMemoryHistory();
+const storeState = {
+ [lookupCodesSlice.name]: { lookupCodes: mockLookups },
+};
+
+const mockGetApi = {
+ error: undefined,
+ response: undefined as Api_PropertyManagement | undefined,
+ execute: jest.fn(),
+ loading: false,
+};
+
+jest.mock('@/hooks/repositories/usePropertyManagementRepository', () => ({
+ usePropertyManagementRepository: () => {
+ return {
+ getPropertyManagement: mockGetApi,
+ };
+ },
+}));
+
+describe('FilterContentForm component', () => {
+ const onChange = jest.fn();
+
+ const setup = (renderOptions: RenderOptions & { props: IFilterContentFormProps }) => {
+ renderOptions = renderOptions ?? ({} as any);
+ const utils = render(, {
+ ...renderOptions,
+ store: storeState,
+ history,
+ });
+
+ return {
+ ...utils,
+ };
+ };
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('shows loading spinner when loading', () => {
+ mockGetApi.execute.mockResolvedValue(getMockApiPropertyManagement(1));
+ const { getByTestId } = setup({ props: { onChange, isLoading: true } });
+ expect(getByTestId('filter-backdrop-loading')).toBeVisible();
+ });
+
+ it('displays filters when not loading', async () => {
+ const apiManagement = getMockApiPropertyManagement(1);
+ mockGetApi.response = apiManagement;
+ const { getByDisplayValue } = setup({ props: { onChange, isLoading: false } });
+ expect(getByDisplayValue('Select a highway')).toBeVisible();
+ expect(getByDisplayValue('Select Lease Transaction')).toBeVisible();
+ });
+
+ it('calls onChange when a filter is changed', async () => {
+ const apiManagement = getMockApiPropertyManagement(1);
+ mockGetApi.response = apiManagement;
+ const { getByTestId } = setup({ props: { onChange, isLoading: false } });
+ await act(async () => {
+ userEvent.selectOptions(getByTestId('leasePayRcvblType'), ['all']);
+ expect(onChange).toBeCalled();
+ });
+ });
+});
diff --git a/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/FilterContentForm.tsx b/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/FilterContentForm.tsx
index 3df8450006..312adf501d 100644
--- a/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/FilterContentForm.tsx
+++ b/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/FilterContentForm.tsx
@@ -83,6 +83,13 @@ export const FilterContentForm: React.FC(
+ x => {
+ return { value: x.id.toString(), label: x.name };
+ },
+ );
+ leasePaymentRcvblOptions.push({ value: 'all', label: 'Payable and Receivable' });
+
return (
initialValues={initialFilter} onSubmit={noop}>