Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

psp-7045 add lease advanced filter. #3551

Merged
merged 1 commit into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions source/backend/dal/Models/PropertyFilterCriteria.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ public class PropertyFilterCriteria
/// </summary>
public string LeaseStatus { get; set; }

/// <summary>
/// get/set - The lease receivable/payable type to filter by.
/// </summary>
public string LeasePayRcvblType { get; set; }

/// <summary>
/// get/set - The multiple lease types to filter by.
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions source/backend/dal/Repositories/PropertyRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,12 @@ public HashSet<long> 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)
{
Expand Down
159 changes: 159 additions & 0 deletions source/backend/tests/unit/dal/Repositories/PropertyRepositoryTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>() { "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<string>() { "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<string>() { "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<string>() { "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<string>() { "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()
Expand Down
Original file line number Diff line number Diff line change
@@ -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<FormikProps<any>, IFilterContentFormProps>((props, ref) => {
viewProps = props;
return <></>;
});

const setup = (
renderOptions?: RenderOptions & { props?: Partial<IFilterContentContainerProps> },
) => {
renderOptions = renderOptions ?? {};
const utils = render(<FilterContentContainer {...renderOptions.props} View={View} />, {
...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,
}),
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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<IFilterContentFormProps>;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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(<FilterContentForm {...renderOptions.props} />, {
...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();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ export const FilterContentForm: React.FC<React.PropsWithChildren<IFilterContentF
return { codeType: x.id.toString(), codeTypeDescription: x.name };
});

const leasePaymentRcvblOptions = getByType(API.LEASE_PAYMENT_RECEIVABLE_TYPES).map<SelectOption>(
x => {
return { value: x.id.toString(), label: x.name };
},
);
leasePaymentRcvblOptions.push({ value: 'all', label: 'Payable and Receivable' });

return (
<Formik<PropertyFilterFormModel> initialValues={initialFilter} onSubmit={noop}>
<Form>
Expand Down Expand Up @@ -122,6 +129,18 @@ export const FilterContentForm: React.FC<React.PropsWithChildren<IFilterContentF
</SectionField>
</Section>
<Section header="Lease / License" isCollapsable initiallyExpanded>
<SectionField
label="Lease Transaction"
contentWidth="12"
tooltip="Selecting the Payable and Receivable lease transaction option will display properties that have both a payable and a receivable lease on them."
>
<Select
field="leasePayRcvblType"
placeholder="Select Lease Transaction"
options={leasePaymentRcvblOptions}
data-testid="leasePayRcvblType"
/>
</SectionField>
<SectionField label="Status" contentWidth="12">
<Select
field="leaseStatus"
Expand Down
Loading
Loading