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-8771 & psp-8683 | Lease expiry using renewal information #4189

Merged
merged 4 commits into from
Jul 13, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using Pims.Core.Extensions;
using Pims.Dal.Entities;
using Pims.Dal.Entities.Models;
using Pims.Dal.Helpers.Extensions;
using Pims.Dal.Repositories;
using Pims.Dal.Security;
using Swashbuckle.AspNetCore.Annotations;
Expand Down Expand Up @@ -204,9 +205,13 @@ public IActionResult ExportLeasePayments(int fiscalYearStart)
public IEnumerable<LeaseModel> GetCrossJoinLeases(Lease.Models.Search.LeaseFilterModel filter, bool all = false)
{
var page = _leaseService.GetPage((LeaseFilter)filter, all);
var allLeases = page.Items.SelectMany(l => l.PimsLeasePeriods.DefaultIfEmpty(), (lease, period) => (lease, period))
var expiryDate = page.Items.Select(l => l.GetExpiryDate());

var allLeases = page.Items
.SelectMany(l => l.PimsLeasePeriods.DefaultIfEmpty(), (lease, period) => (lease, period))
.SelectMany(lt => lt.lease.PimsPropertyLeases.DefaultIfEmpty(), (leasePeriod, property) => (leasePeriod.period, leasePeriod.lease, property))
.SelectMany(ltp => ltp.lease.PimsLeaseTenants.DefaultIfEmpty(), (leasePeriodProperty, tenant) => (leasePeriodProperty.period, leasePeriodProperty.lease, leasePeriodProperty.property, tenant));
.SelectMany(ltp => ltp.lease.PimsLeaseTenants.DefaultIfEmpty(), (leasePeriodProperty, tenant) => (leasePeriodProperty.period, leasePeriodProperty.lease, leasePeriodProperty.property, tenant))
.SelectMany(ltpr => expiryDate, (leasePeriodPropertyTenant, expiryDate) => (leasePeriodPropertyTenant.period, leasePeriodPropertyTenant.lease, leasePeriodPropertyTenant.property, leasePeriodPropertyTenant.tenant, expiryDate));
var flatLeases = _mapper.Map<IEnumerable<LeaseModel>>(allLeases);
return flatLeases;
}
Expand Down
8 changes: 4 additions & 4 deletions source/backend/api/Areas/Reports/Mapping/Lease/LeaseMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@ public class LeaseMap : IRegister
{
public void Register(TypeAdapterConfig config)
{
config.NewConfig<(Entity.PimsLeasePeriod period, Entity.PimsLease lease, Entity.PimsPropertyLease property, Entity.PimsLeaseTenant tenant), Model.LeaseModel>()
config.NewConfig<(Entity.PimsLeasePeriod period, Entity.PimsLease lease, Entity.PimsPropertyLease property, Entity.PimsLeaseTenant tenant, DateTime? expiryDate), Model.LeaseModel>()
.AfterMapping((src, dest) =>
{
MapLease(src, dest);
});
}

private static void MapLease((Entity.PimsLeasePeriod period, Entity.PimsLease lease, Entity.PimsPropertyLease property, Entity.PimsLeaseTenant tenant) src, Model.LeaseModel dest)
private static void MapLease((Entity.PimsLeasePeriod period, Entity.PimsLease lease, Entity.PimsPropertyLease property, Entity.PimsLeaseTenant tenant, DateTime? expiryDate) src, Model.LeaseModel dest)
{
dest.LFileNo = src.lease.LFileNo;
dest.MotiRegion = src.lease.RegionCodeNavigation?.RegionName;
dest.StartDate = src.lease.OrigStartDate.FilterSqlMinDate().ToNullableDateOnly();
dest.EndDate = src.lease.OrigExpiryDate?.FilterSqlMinDate().ToNullableDateOnly();
dest.EndDate = src.expiryDate.ToNullableDateOnly();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to filter the sql min date here? previously this would cause exceptions converting from an sql min date to a date time.

dest.CurrentPeriodStartDate = src.lease.GetCurrentPeriodStartDate()?.FilterSqlMinDate().ToNullableDateOnly();
dest.CurrentTermEndDate = src.lease.GetCurrentPeriodEndDate()?.FilterSqlMinDate().ToNullableDateOnly();
dest.ProgramName = src.lease.LeaseProgramTypeCodeNavigation?.GetTypeDescriptionOther(src.lease.OtherLeaseProgramType);
Expand All @@ -36,7 +36,7 @@ private static void MapLease((Entity.PimsLeasePeriod period, Entity.PimsLease le
dest.InspectionNotes = src.lease.InspectionNotes;
dest.InspectionDate = src.lease.InspectionDate?.FilterSqlMinDate();
dest.LeaseNotes = src.lease.LeaseNotes;
dest.IsExpired = (src.lease.GetExpiryDate() < DateTime.Now).BoolToYesNo();
dest.IsExpired = (src.expiryDate < DateTime.Now).BoolToYesNo();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to include renewals in this calculation?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not 100%. I dont think this field is actually used in the frontend. But I will clean it up as tech debt

dest.LeaseAmount = src.period?.PaymentAmount;
dest.PeriodStartDate = src.period?.PeriodStartDate.FilterSqlMinDate().ToNullableDateOnly();
dest.PeriodExpiryDate = src.period?.PeriodExpiryDate?.FilterSqlMinDate().ToNullableDateOnly();
Expand Down
25 changes: 17 additions & 8 deletions source/backend/dal/Repositories/LeaseRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -895,7 +895,7 @@ public IQueryable<PimsLease> GenerateLeaseQuery(LeaseFilter filter, HashSet<shor
.ThenInclude(p => p.Address)
.Include(l => l.PimsPropertyLeases)
.ThenInclude(p => p.AreaUnitTypeCodeNavigation)
.Include(pl => pl.PimsPropertyLeases)
.Include(l => l.PimsPropertyLeases)
.ThenInclude(p => p.Property)
.ThenInclude(n => n.PimsHistoricalFileNumbers)
.ThenInclude(t => t.HistoricalFileNumberTypeCodeNavigation)
Expand All @@ -908,8 +908,9 @@ public IQueryable<PimsLease> GenerateLeaseQuery(LeaseFilter filter, HashSet<shor
.ThenInclude(t => t.Person)
.Include(l => l.PimsLeaseTenants)
.ThenInclude(t => t.Organization)
.Include(p => p.RegionCodeNavigation)
.Include(l => l.RegionCodeNavigation)
.Include(l => l.PimsLeasePeriods)
.Include(l => l.PimsLeaseRenewals)
.AsNoTracking();

if (loadPayments)
Expand Down Expand Up @@ -1100,17 +1101,25 @@ private static ExpressionStarter<PimsLease> GenerateCommonLeaseQuery(LeaseFilter

var expiryStartDate = filter.ExpiryStartDate.ToNullableDateTime();
var expiryEndDate = filter.ExpiryEndDate.ToNullableDateTime();
if (filter.ExpiryStartDate != null && filter.ExpiryEndDate != null)
if (expiryStartDate != null && expiryEndDate != null)
{
predicateBuilder.And(l => l.OrigExpiryDate >= expiryStartDate && l.OrigExpiryDate <= expiryEndDate);
predicateBuilder = predicateBuilder.And(l => l.PimsLeaseRenewals.Where(r => r.IsExercised == true).Select(rf => rf.ExpiryDt).Max().HasValue ?
l.PimsLeaseRenewals.Where(r => r.IsExercised == true).Select(rf => rf.ExpiryDt).Max() >= expiryStartDate &&
l.PimsLeaseRenewals.Where(r => r.IsExercised == true).Select(rf => rf.ExpiryDt).Max() <= expiryEndDate :
l.OrigExpiryDate >= expiryStartDate &&
l.OrigExpiryDate <= expiryEndDate);
}
else if (filter.ExpiryStartDate != null)
else if (expiryStartDate != null)
{
predicateBuilder = predicateBuilder.And(l => l.OrigExpiryDate >= expiryStartDate);
predicateBuilder = predicateBuilder.And(l => l.PimsLeaseRenewals.Where(r => r.IsExercised == true).Select(rf => rf.ExpiryDt).Max().HasValue ?
l.PimsLeaseRenewals.Where(r => r.IsExercised == true).Select(rf => rf.ExpiryDt).Max() >= expiryStartDate :
l.OrigExpiryDate >= expiryStartDate);
}
else if (filter.ExpiryEndDate != null)
else if (expiryEndDate != null)
{
predicateBuilder = predicateBuilder.And(l => l.OrigExpiryDate <= expiryEndDate);
predicateBuilder = predicateBuilder.And(l => l.PimsLeaseRenewals.Where(r => r.IsExercised == true).Select(rf => rf.ExpiryDt).Max().HasValue ?
l.PimsLeaseRenewals.Where(r => r.IsExercised == true).Select(rf => rf.ExpiryDt).Max() <= expiryEndDate :
l.OrigExpiryDate <= expiryEndDate);
}

if (filter.RegionType.HasValue)
Expand Down
2 changes: 2 additions & 0 deletions source/backend/dal/Repositories/PropertyLeaseRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ public IEnumerable<PimsPropertyLease> GetAllByPropertyId(long propertyId)
.ThenInclude(l => l.LeaseStatusTypeCodeNavigation)
.Include(pl => pl.Lease)
.ThenInclude(l => l.PimsLeasePeriods)
.Include(pl => pl.Lease)
.ThenInclude(l => l.PimsLeaseRenewals)
.Where(p => p.PropertyId == propertyId);
}

Expand Down
15 changes: 2 additions & 13 deletions source/backend/entities/Extensions/LeaseExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,8 @@ public static string GetTenantName(this Pims.Dal.Entities.PimsLeaseTenant lease)
/// <returns></returns>
public static DateTime? GetExpiryDate(this Pims.Dal.Entities.PimsLease lease)
{
if (lease.PimsLeasePeriods != null && lease.PimsLeasePeriods.Any(p => p.PeriodExpiryDate == null && !p.IsFlexibleDuration))
{
return null;
}
if (lease.OrigExpiryDate != null)
{
if (lease.PimsLeasePeriods != null && lease.PimsLeasePeriods.Any(p => p.PeriodExpiryDate > lease.OrigExpiryDate && !p.IsFlexibleDuration))
{
return lease.PimsLeasePeriods.OrderByDescending(p => p.PeriodExpiryDate).FirstOrDefault().PeriodExpiryDate;
}
return lease.OrigExpiryDate;
}
return lease.PimsLeasePeriods?.OrderByDescending(p => p.PeriodExpiryDate).FirstOrDefault(p => !p.IsFlexibleDuration)?.PeriodExpiryDate;
var expiryDate = lease.PimsLeaseRenewals.Where(r => r.IsExercised == true).Select(fr => fr.ExpiryDt).Append(lease.OrigExpiryDate).Max();
return expiryDate;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,31 @@ public void GetExpiryDate_OrigExpiry()
}

[Fact]
public void GetExpiryDate_TermExpiry()
public void GetExpiryDate_RenewalExpiry()
{
DateTime now = DateTime.Now;
PimsLease lease = new PimsLease()
{
OrigExpiryDate = null,
PimsLeasePeriods = new List<PimsLeasePeriod>() {
new PimsLeasePeriod() { PeriodExpiryDate = now }, },
PimsLeaseRenewals = new List<PimsLeaseRenewal>() {
new PimsLeaseRenewal() { IsExercised = true, ExpiryDt = now }, },
};
Assert.Equal(now, lease.GetExpiryDate());
}

[Fact]
public void GetExpiryDate_RenewalExpiry_NotExercised()
{
DateTime now = DateTime.Now;
PimsLease lease = new PimsLease()
{
OrigExpiryDate = null,
PimsLeaseRenewals = new List<PimsLeaseRenewal>() {
new PimsLeaseRenewal() { IsExercised = false, ExpiryDt = now }, },
};
Assert.Equal(null, lease.GetExpiryDate());
}

[Fact]
public void GetExpiryDate_OrigExpiryLater()
{
Expand All @@ -50,56 +63,56 @@ public void GetExpiryDate_OrigExpiryLater()
PimsLease lease = new PimsLease()
{
OrigExpiryDate = later,
PimsLeasePeriods = new List<PimsLeasePeriod>() {
new PimsLeasePeriod() { PeriodExpiryDate = now }, },
PimsLeaseRenewals = new List<PimsLeaseRenewal>() {
new PimsLeaseRenewal() { IsExercised=true, ExpiryDt = now }, },
};
Assert.Equal(later, lease.GetExpiryDate());
}

[Fact]
public void GetExpiryDate_TermExpiryLater()
public void GetExpiryDate_RenewalExpiryLater()
{
DateTime now = DateTime.Now;
DateTime later = now.AddDays(1);
PimsLease lease = new PimsLease()
{
OrigExpiryDate = now,
PimsLeasePeriods =
new List<PimsLeasePeriod>() { new PimsLeasePeriod() { PeriodExpiryDate = later } },
PimsLeaseRenewals =
new List<PimsLeaseRenewal>() { new PimsLeaseRenewal() { IsExercised = true, ExpiryDt = later } },
};
Assert.Equal(later, lease.GetExpiryDate());
}

[Fact]
public void GetExpiryDate_MultipleTermExpiryLater()
public void GetExpiryDate_MultipleRenewalExpiryLater()
{
DateTime now = DateTime.Now;
DateTime later = now.AddDays(1);
DateTime before = now.AddDays(-1);
PimsLease lease = new PimsLease()
{
OrigExpiryDate = now,
PimsLeasePeriods =
new List<PimsLeasePeriod>() { new PimsLeasePeriod() { PeriodExpiryDate = later },
new PimsLeasePeriod() { PeriodExpiryDate = before }, },
PimsLeaseRenewals =
new List<PimsLeaseRenewal>() { new PimsLeaseRenewal() { IsExercised=true, ExpiryDt = later },
new PimsLeaseRenewal() { IsExercised=true, ExpiryDt = before }, },
};
Assert.Equal(later, lease.GetExpiryDate());
}

[Fact]
public void GetExpiryDate_TermNoExpiry()
public void GetExpiryDate_RenewalNoExpiry()
{
DateTime now = DateTime.Now;
DateTime later = now.AddDays(1);
DateTime before = now.AddDays(-1);
PimsLease lease = new PimsLease()
{
OrigExpiryDate = now,
PimsLeasePeriods =
new List<PimsLeasePeriod>() { new PimsLeasePeriod() { PeriodExpiryDate = null },
new PimsLeasePeriod() { PeriodExpiryDate = before }, },
PimsLeaseRenewals =
new List<PimsLeaseRenewal>() { new PimsLeaseRenewal() { IsExercised=true, ExpiryDt = null },
new PimsLeaseRenewal() { IsExercised = false, ExpiryDt = before }, },
};
Assert.Equal(null, lease.GetExpiryDate());
Assert.Equal(now, lease.GetExpiryDate());
}
#endregion
}
Expand Down
19 changes: 19 additions & 0 deletions source/frontend/src/features/leases/leaseUtils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import moment from 'moment';

import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease';
import { ApiGen_Concepts_LeaseRenewal } from '@/models/api/generated/ApiGen_Concepts_LeaseRenewal';
import { ApiGen_Concepts_LeaseTenant } from '@/models/api/generated/ApiGen_Concepts_LeaseTenant';
import { isValidId } from '@/utils';
import { formatNames } from '@/utils/personUtils';
Expand All @@ -16,6 +20,21 @@ export const getAllNames = (tenants: ApiGen_Concepts_LeaseTenant[]): string[] =>
return allNames;
};

export const getCalculatedExpiry = (
lease: ApiGen_Concepts_Lease,
renewals: ApiGen_Concepts_LeaseRenewal[],
): string => {
const excercisedRenewalDates = renewals.filter(r => r.isExercised).flatMap(fr => fr.expiryDt);

let calculatedExpiry: string | null = lease?.expiryDate ?? '';
for (let i = 0; i < excercisedRenewalDates.length; i++) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could also do with a sort? this seems fine though.

if (moment(excercisedRenewalDates[i]).isAfter(calculatedExpiry)) {
calculatedExpiry = excercisedRenewalDates[i];
}
}
return calculatedExpiry;
};

export const getSuggestedFee = (isPublicBenefit: boolean, isFinancialGain: boolean): string => {
if (isPublicBenefit == null || isFinancialGain == null) return 'Unknown';
else if (isPublicBenefit && isFinancialGain) return 'Licence Administration Fee (LAF) *';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ResetButton, SearchButton } from '@/components/common/buttons';
import { FastDatePicker, Input } from '@/components/common/form';
import { UserRegionSelectContainer } from '@/components/common/form/UserRegionSelect/UserRegionSelectContainer';
import { SelectInput } from '@/components/common/List/SelectInput';
import { SectionField } from '@/components/common/Section/SectionField';
import { FilterBoxForm } from '@/components/common/styles';
import TooltipIcon from '@/components/common/TooltipIcon';
import { LEASE_PROGRAM_TYPES, LEASE_STATUS_TYPES } from '@/constants/API';
Expand Down Expand Up @@ -107,9 +108,9 @@ export const LeaseFilter: React.FunctionComponent<React.PropsWithChildren<ILease
{formikProps => (
<FilterBoxForm className="p-3">
<Row>
<Col xl="6">
<Col xs="6">
<Row>
<Col xl="auto">
<Col xs="auto">
<strong>Search by:</strong>
</Col>
<Col>
Expand Down Expand Up @@ -206,48 +207,45 @@ export const LeaseFilter: React.FunctionComponent<React.PropsWithChildren<ILease
</Col>
</Row>
</Col>
<Col xl="5">
<Row>
<Col xl="auto">
<strong>Expiry date:</strong>
</Col>
<Col>
<Row>
<Col>
<FastDatePicker
field="expiryStartDate"
formikProps={formikProps}
placeholderText="from date"
/>
</Col>
<Col>
<FastDatePicker
field="expiryEndDate"
formikProps={formikProps}
placeholderText="to date"
/>
</Col>
</Row>
<Row>
<Col>
<UserRegionSelectContainer field="regionType" placeholder="All Regions" />
</Col>
<Col>
<Row>
<Col xs="10" className="pr-0 mr-0">
<Input field="details" placeholder="Keyword" />
</Col>
<Col xs="1" className="pl-0 ml-0">
<TooltipIcon
toolTipId="lease-search-keyword-tooltip"
toolTip="Search 'Lease description' and 'Notes' fields"
/>
</Col>
</Row>
</Col>
</Row>
</Col>
</Row>
<Col xs="5">
<SectionField label="Expiry date" labelWidth="2">
<Row>
<Col>
<FastDatePicker
field="expiryStartDate"
formikProps={formikProps}
placeholderText="from date"
/>
</Col>
<Col>
<FastDatePicker
field="expiryEndDate"
formikProps={formikProps}
placeholderText="to date"
/>
</Col>
</Row>
</SectionField>
<SectionField label="" labelWidth="2">
<Row>
<Col>
<UserRegionSelectContainer field="regionType" placeholder="All Regions" />
</Col>
<Col>
<Row noGutters>
<Col xs="9">
<Input field="details" placeholder="Keyword" />
</Col>
<Col xs="1">
<TooltipIcon
toolTipId="lease-search-keyword-tooltip"
toolTip="Search 'Lease description' and 'Notes' fields"
/>
</Col>
</Row>
</Col>
</Row>
</SectionField>
</Col>
<ColButtons xl="1">
<Row>
Expand Down
Loading
Loading