Skip to content

Commit

Permalink
psp-9457 prevent disposed properties from being added to leases. (#4532)
Browse files Browse the repository at this point in the history
  • Loading branch information
devinleighsmith authored Dec 17, 2024
1 parent dea99af commit ab9e7dc
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 12 deletions.
14 changes: 8 additions & 6 deletions source/backend/api/Services/LeaseService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -232,12 +232,6 @@ public PimsLease Update(PimsLease lease, IEnumerable<UserOverrideCode> userOverr
pimsUser.ThrowInvalidAccessToLeaseFile(lease.RegionCode);

var currentFileProperties = _propertyLeaseRepository.GetAllByLeaseId(lease.LeaseId);
var newPropertiesAdded = lease.PimsPropertyLeases.Where(x => !currentFileProperties.Any(y => y.Internal_Id == x.Internal_Id)).ToList();

if (newPropertiesAdded.Any(x => x.Property.IsRetired.HasValue && x.Property.IsRetired.Value))
{
throw new BusinessRuleViolationException("Retired property can not be selected.");
}

if (currentLease.LeaseStatusTypeCode != lease.LeaseStatusTypeCode)
{
Expand Down Expand Up @@ -553,8 +547,11 @@ private PimsLease AssociatePropertyLeases(PimsLease lease, IEnumerable<UserOverr
{
PimsProperty property = propertyLease.Property;
var existingPropertyLeases = _propertyLeaseRepository.GetAllByPropertyId(property.PropertyId);
var propertyWithAssociations = _propertyRepository.GetAllAssociationsById(property.PropertyId);
var isPropertyOnOtherLease = existingPropertyLeases.Any(p => p.LeaseId != lease.Internal_Id);
var isPropertyOnThisLease = existingPropertyLeases.Any(p => p.LeaseId == lease.Internal_Id);
var isDisposedOrRetired = existingPropertyLeases.Any(p => p.Property.IsRetired.HasValue && p.Property.IsRetired.Value)
|| propertyWithAssociations?.PimsDispositionFileProperties?.Any(d => d.DispositionFile.DispositionFileStatusTypeCode == DispositionFileStatusTypes.COMPLETE.ToString()) == true;

if (isPropertyOnOtherLease && !isPropertyOnThisLease && !userOverrides.Contains(UserOverrideCode.AddPropertyToInventory))
{
Expand All @@ -573,6 +570,11 @@ private PimsLease AssociatePropertyLeases(PimsLease lease, IEnumerable<UserOverr
throw new UserOverrideException(UserOverrideCode.AddPropertyToInventory, overrideError);
}

if (isDisposedOrRetired && !isPropertyOnThisLease)
{
throw new BusinessRuleViolationException("Disposed or retired properties may not be added to a Lease. Remove any disposed or retired properties before continuing.");
}

// If the property exist dont update it, just refer to it by id.
if (property.Internal_Id != 0)
{
Expand Down
166 changes: 164 additions & 2 deletions source/backend/tests/unit/api/Services/LeaseServiceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,63 @@ public void Add_WithRetiredProperty_Should_Fail()
propertyService.Verify(x => x.PopulateNewFileProperty(It.IsAny<PimsPropertyLease>()), Times.Never);
}

[Fact]
public void Add_WithDisposedProperty_Should_Fail()
{
// Arrange
var lease = EntityHelper.CreateLease(1);
lease.RegionCode = 1;
var user = EntityHelper.CreateUser("Test");
user.PimsRegionUsers.Add(new PimsRegionUser() { RegionCode = lease.RegionCode.Value });

PimsProperty newProperty = new PimsProperty()
{
PropertyId = 100,
Pid = 1000,
};

PimsProperty disposedProperty = new PimsProperty()
{
PropertyId = 100,
Pid = 1000,
PimsDispositionFileProperties = new List<PimsDispositionFileProperty>()
{
new PimsDispositionFileProperty()
{
DispositionFile = new PimsDispositionFile()
{
DispositionFileId = 1,
DispositionFileStatusTypeCode = DispositionFileStatusTypes.COMPLETE.ToString(),
},
},
},
};

var service = this.CreateLeaseService(Permissions.LeaseAdd);

var leaseRepository = this._helper.GetService<Mock<ILeaseRepository>>();
leaseRepository.Setup(x => x.Add(It.IsAny<PimsLease>())).Returns(lease);

var propertyRepository = this._helper.GetService<Mock<IPropertyRepository>>();
propertyRepository.Setup(x => x.GetByPid(It.IsAny<int>(), true)).Returns(newProperty);
propertyRepository.Setup(x => x.GetAllAssociationsById(It.IsAny<long>())).Returns(disposedProperty);

var userRepository = this._helper.GetService<Mock<IUserRepository>>();
userRepository.Setup(x => x.GetByKeycloakUserId(It.IsAny<Guid>())).Returns(user);

var propertyService = this._helper.GetService<Mock<IPropertyService>>();

// Act
Action act = () => service.Add(lease, new List<UserOverrideCode>());

// Assert
var ex = act.Should().Throw<BusinessRuleViolationException>();
ex.WithMessage("Disposed or retired properties may not be added to a Lease. Remove any disposed or retired properties before continuing.");

leaseRepository.Verify(x => x.Add(It.IsAny<PimsLease>()), Times.Never);
propertyService.Verify(x => x.PopulateNewFileProperty(It.IsAny<PimsPropertyLease>()), Times.Never);
}

#endregion

#region Properties
Expand Down Expand Up @@ -464,7 +521,56 @@ public void UpdateProperties_Success()
}

[Fact]
public void UpdateProperties_WithRetiredProperty_Should_Fail()
public void UpdateProperties_WithDisposedProperty_Should_Fail()
{
// Arrange
var lease = EntityHelper.CreateLease(1);

var service = this.CreateLeaseService(Permissions.LeaseEdit, Permissions.PropertyAdd, Permissions.PropertyView);
var leaseRepository = this._helper.GetService<Mock<ILeaseRepository>>();
var propertyLeaseRepository = this._helper.GetService<Mock<IPropertyLeaseRepository>>();
var propertyRepository = this._helper.GetService<Mock<IPropertyRepository>>();
var userRepository = this._helper.GetService<Mock<IUserRepository>>();

PimsProperty property = new PimsProperty()
{
PropertyId = 100,
Pid = 1,
};

PimsProperty disposedProperty = new PimsProperty()
{
PropertyId = 100,
Pid = 1000,
PimsDispositionFileProperties = new List<PimsDispositionFileProperty>()
{
new PimsDispositionFileProperty()
{
DispositionFile = new PimsDispositionFile()
{
DispositionFileId = 1,
DispositionFileStatusTypeCode = DispositionFileStatusTypes.COMPLETE.ToString(),
},
},
},
};

propertyRepository.Setup(x => x.GetByPid(It.IsAny<int>(), true)).Returns(property);
propertyRepository.Setup(x => x.GetAllAssociationsById(It.IsAny<long>())).Returns(disposedProperty);
leaseRepository.Setup(x => x.GetNoTracking(It.IsAny<long>())).Returns(lease);
leaseRepository.Setup(x => x.Get(It.IsAny<long>())).Returns(EntityHelper.CreateLease(1));
userRepository.Setup(x => x.GetByKeycloakUserId(It.IsAny<Guid>())).Returns(EntityHelper.CreateUser("Test"));

// Act
Action act = () => service.Update(lease, new List<UserOverrideCode>() { UserOverrideCode.AddLocationToProperty });

// Assert
var ex = act.Should().Throw<BusinessRuleViolationException>();
ex.WithMessage("Disposed or retired properties may not be added to a Lease. Remove any disposed or retired properties before continuing.");
}

[Fact]
public void UpdateProperties_WithExistingDisposedProperty_Should_Pass()
{
// Arrange
var lease = EntityHelper.CreateLease(1);
Expand All @@ -479,10 +585,65 @@ public void UpdateProperties_WithRetiredProperty_Should_Fail()
{
PropertyId = 100,
Pid = 1,
IsRetired = true,
};

PimsProperty disposedProperty = new PimsProperty()
{
PropertyId = 100,
Pid = 1000,
PimsDispositionFileProperties = new List<PimsDispositionFileProperty>()
{
new PimsDispositionFileProperty()
{
DispositionFile = new PimsDispositionFile()
{
DispositionFileId = 1,
DispositionFileStatusTypeCode = DispositionFileStatusTypes.COMPLETE.ToString(),
},
},
},
};
PimsPropertyLease propertyLease = new PimsPropertyLease()
{
Property = disposedProperty,
LeaseId = lease.LeaseId
};

propertyLeaseRepository.Setup(x => x.GetAllByPropertyId(It.IsAny<long>())).Returns(new List<PimsPropertyLease>() { propertyLease });
propertyRepository.Setup(x => x.GetByPid(It.IsAny<int>(), true)).Returns(property);
propertyRepository.Setup(x => x.GetAllAssociationsById(It.IsAny<long>())).Returns(disposedProperty);
leaseRepository.Setup(x => x.GetNoTracking(It.IsAny<long>())).Returns(lease);
leaseRepository.Setup(x => x.Get(It.IsAny<long>())).Returns(EntityHelper.CreateLease(1));
userRepository.Setup(x => x.GetByKeycloakUserId(It.IsAny<Guid>())).Returns(EntityHelper.CreateUser("Test"));

// Act
Action act = () => service.Update(lease, new List<UserOverrideCode>() { UserOverrideCode.AddLocationToProperty });

// Assert
var ex = act.Should().NotThrow<BusinessRuleViolationException>();
leaseRepository.Verify(x => x.Update(lease, false), Times.Once);
}

[Fact]
public void UpdateProperties_WithRetiredProperty_Should_Fail()
{
// Arrange
var lease = EntityHelper.CreateLease(1);

var service = this.CreateLeaseService(Permissions.LeaseEdit, Permissions.PropertyAdd, Permissions.PropertyView);
var leaseRepository = this._helper.GetService<Mock<ILeaseRepository>>();
var propertyLeaseRepository = this._helper.GetService<Mock<IPropertyLeaseRepository>>();
var propertyRepository = this._helper.GetService<Mock<IPropertyRepository>>();
var userRepository = this._helper.GetService<Mock<IUserRepository>>();

PimsProperty retiredProperty = new PimsProperty()
{
PropertyId = 100,
Pid = 1,
IsRetired = true,
};

propertyRepository.Setup(x => x.GetByPid(It.IsAny<int>(), true)).Returns(retiredProperty);
leaseRepository.Setup(x => x.GetNoTracking(It.IsAny<long>())).Returns(lease);
leaseRepository.Setup(x => x.Get(It.IsAny<long>())).Returns(EntityHelper.CreateLease(1));
userRepository.Setup(x => x.GetByKeycloakUserId(It.IsAny<Guid>())).Returns(EntityHelper.CreateUser("Test"));
Expand Down Expand Up @@ -542,6 +703,7 @@ public void UpdateProperties_MatchProperties_Success_NoInternalId()
Lease = lease,
Internal_Id = 1,
} };
lease.PimsPropertyLeases = propertyLeases;

propertyLeaseRepository.Setup(x => x.GetAllByLeaseId(It.IsAny<long>())).Returns(propertyLeases);
propertyRepository.Setup(x => x.GetByPid(It.IsAny<int>(), true)).Returns(lease.PimsPropertyLeases.FirstOrDefault().Property);
Expand Down
14 changes: 10 additions & 4 deletions source/frontend/src/features/leases/add/AddLeaseYupSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,16 @@ export const AddLeaseYupSchema = Yup.object().shape({
properties: Yup.array().of(
Yup.object().shape({
name: Yup.string().max(250, 'Property name must be at most ${max} characters'),
isRetired: Yup.boolean().notOneOf(
[true],
'Selected property is retired and can not be added to the file',
),
property: Yup.object().shape({
isRetired: Yup.boolean().notOneOf(
[true],
'Selected property is retired and can not be added to the file',
),
isDisposed: Yup.boolean().notOneOf(
[true],
'Selected property is disposed and can not be added to the file',
),
}),
}),
),
consultations: Yup.array().of(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ export const SelectedPropertyRow: React.FunctionComponent<ISelectedPropertyRowPr
label=""
field={withNameSpace(nameSpace, 'name')}
displayErrorTooltips={true}
errorKeys={[
withNameSpace(nameSpace, 'property.isRetired'),
withNameSpace(nameSpace, 'property.isDisposed'),
]}
/>
</Col>
<Col md={1} className="pl-3">
Expand Down
2 changes: 2 additions & 0 deletions source/frontend/src/features/mapSideBar/shared/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export class PropertyForm {
public landArea?: number;
public areaUnit?: AreaUnitTypes;
public isRetired?: boolean;
public isDisposed?: boolean;

public constructor(baseModel?: Partial<PropertyForm>) {
Object.assign(this, baseModel);
Expand Down Expand Up @@ -152,6 +153,7 @@ export class PropertyForm {
? enumFromValue(model?.pimsFeature?.properties?.PROPERTY_AREA_UNIT_TYPE_CODE, AreaUnitTypes)
: AreaUnitTypes.SquareMeters,
isRetired: model?.pimsFeature?.properties?.IS_RETIRED ?? false,
isDisposed: model?.pimsFeature?.properties?.IS_DISPOSED ?? false,
legalDescription:
model?.pimsFeature?.properties?.LAND_LEGAL_DESCRIPTION ??
model?.parcelFeature?.properties?.LEGAL_DESCRIPTION ??
Expand Down

0 comments on commit ab9e7dc

Please sign in to comment.