From ab9e7dcf410fea538eb5f592aef190a866c46aa8 Mon Sep 17 00:00:00 2001 From: devinleighsmith <41091511+devinleighsmith@users.noreply.github.com> Date: Mon, 16 Dec 2024 21:49:11 -0800 Subject: [PATCH] psp-9457 prevent disposed properties from being added to leases. (#4532) --- source/backend/api/Services/LeaseService.cs | 14 +- .../unit/api/Services/LeaseServiceTest.cs | 166 +++++++++++++++++- .../features/leases/add/AddLeaseYupSchema.ts | 14 +- .../SelectedPropertyRow.tsx | 4 + .../src/features/mapSideBar/shared/models.ts | 2 + 5 files changed, 188 insertions(+), 12 deletions(-) diff --git a/source/backend/api/Services/LeaseService.cs b/source/backend/api/Services/LeaseService.cs index 1fba211a92..14d550a1b7 100644 --- a/source/backend/api/Services/LeaseService.cs +++ b/source/backend/api/Services/LeaseService.cs @@ -232,12 +232,6 @@ public PimsLease Update(PimsLease lease, IEnumerable 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) { @@ -553,8 +547,11 @@ private PimsLease AssociatePropertyLeases(PimsLease lease, IEnumerable 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)) { @@ -573,6 +570,11 @@ private PimsLease AssociatePropertyLeases(PimsLease lease, IEnumerable x.PopulateNewFileProperty(It.IsAny()), 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() + { + new PimsDispositionFileProperty() + { + DispositionFile = new PimsDispositionFile() + { + DispositionFileId = 1, + DispositionFileStatusTypeCode = DispositionFileStatusTypes.COMPLETE.ToString(), + }, + }, + }, + }; + + var service = this.CreateLeaseService(Permissions.LeaseAdd); + + var leaseRepository = this._helper.GetService>(); + leaseRepository.Setup(x => x.Add(It.IsAny())).Returns(lease); + + var propertyRepository = this._helper.GetService>(); + propertyRepository.Setup(x => x.GetByPid(It.IsAny(), true)).Returns(newProperty); + propertyRepository.Setup(x => x.GetAllAssociationsById(It.IsAny())).Returns(disposedProperty); + + var userRepository = this._helper.GetService>(); + userRepository.Setup(x => x.GetByKeycloakUserId(It.IsAny())).Returns(user); + + var propertyService = this._helper.GetService>(); + + // Act + Action act = () => service.Add(lease, new List()); + + // Assert + var ex = act.Should().Throw(); + 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()), Times.Never); + propertyService.Verify(x => x.PopulateNewFileProperty(It.IsAny()), Times.Never); + } + #endregion #region Properties @@ -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>(); + var propertyLeaseRepository = this._helper.GetService>(); + var propertyRepository = this._helper.GetService>(); + var userRepository = this._helper.GetService>(); + + PimsProperty property = new PimsProperty() + { + PropertyId = 100, + Pid = 1, + }; + + PimsProperty disposedProperty = new PimsProperty() + { + PropertyId = 100, + Pid = 1000, + PimsDispositionFileProperties = new List() + { + new PimsDispositionFileProperty() + { + DispositionFile = new PimsDispositionFile() + { + DispositionFileId = 1, + DispositionFileStatusTypeCode = DispositionFileStatusTypes.COMPLETE.ToString(), + }, + }, + }, + }; + + propertyRepository.Setup(x => x.GetByPid(It.IsAny(), true)).Returns(property); + propertyRepository.Setup(x => x.GetAllAssociationsById(It.IsAny())).Returns(disposedProperty); + leaseRepository.Setup(x => x.GetNoTracking(It.IsAny())).Returns(lease); + leaseRepository.Setup(x => x.Get(It.IsAny())).Returns(EntityHelper.CreateLease(1)); + userRepository.Setup(x => x.GetByKeycloakUserId(It.IsAny())).Returns(EntityHelper.CreateUser("Test")); + + // Act + Action act = () => service.Update(lease, new List() { UserOverrideCode.AddLocationToProperty }); + + // Assert + var ex = act.Should().Throw(); + 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); @@ -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() + { + 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())).Returns(new List() { propertyLease }); propertyRepository.Setup(x => x.GetByPid(It.IsAny(), true)).Returns(property); + propertyRepository.Setup(x => x.GetAllAssociationsById(It.IsAny())).Returns(disposedProperty); + leaseRepository.Setup(x => x.GetNoTracking(It.IsAny())).Returns(lease); + leaseRepository.Setup(x => x.Get(It.IsAny())).Returns(EntityHelper.CreateLease(1)); + userRepository.Setup(x => x.GetByKeycloakUserId(It.IsAny())).Returns(EntityHelper.CreateUser("Test")); + + // Act + Action act = () => service.Update(lease, new List() { UserOverrideCode.AddLocationToProperty }); + + // Assert + var ex = act.Should().NotThrow(); + 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>(); + var propertyLeaseRepository = this._helper.GetService>(); + var propertyRepository = this._helper.GetService>(); + var userRepository = this._helper.GetService>(); + + PimsProperty retiredProperty = new PimsProperty() + { + PropertyId = 100, + Pid = 1, + IsRetired = true, + }; + + propertyRepository.Setup(x => x.GetByPid(It.IsAny(), true)).Returns(retiredProperty); leaseRepository.Setup(x => x.GetNoTracking(It.IsAny())).Returns(lease); leaseRepository.Setup(x => x.Get(It.IsAny())).Returns(EntityHelper.CreateLease(1)); userRepository.Setup(x => x.GetByKeycloakUserId(It.IsAny())).Returns(EntityHelper.CreateUser("Test")); @@ -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())).Returns(propertyLeases); propertyRepository.Setup(x => x.GetByPid(It.IsAny(), true)).Returns(lease.PimsPropertyLeases.FirstOrDefault().Property); diff --git a/source/frontend/src/features/leases/add/AddLeaseYupSchema.ts b/source/frontend/src/features/leases/add/AddLeaseYupSchema.ts index cad9b25bbc..7ba438ac1f 100644 --- a/source/frontend/src/features/leases/add/AddLeaseYupSchema.ts +++ b/source/frontend/src/features/leases/add/AddLeaseYupSchema.ts @@ -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( diff --git a/source/frontend/src/features/leases/shared/propertyPicker/selectedPropertyList/SelectedPropertyRow.tsx b/source/frontend/src/features/leases/shared/propertyPicker/selectedPropertyList/SelectedPropertyRow.tsx index d175720d2c..115e90d2f7 100644 --- a/source/frontend/src/features/leases/shared/propertyPicker/selectedPropertyList/SelectedPropertyRow.tsx +++ b/source/frontend/src/features/leases/shared/propertyPicker/selectedPropertyList/SelectedPropertyRow.tsx @@ -66,6 +66,10 @@ export const SelectedPropertyRow: React.FunctionComponent diff --git a/source/frontend/src/features/mapSideBar/shared/models.ts b/source/frontend/src/features/mapSideBar/shared/models.ts index 4e36a0d369..fafebf9e2a 100644 --- a/source/frontend/src/features/mapSideBar/shared/models.ts +++ b/source/frontend/src/features/mapSideBar/shared/models.ts @@ -92,6 +92,7 @@ export class PropertyForm { public landArea?: number; public areaUnit?: AreaUnitTypes; public isRetired?: boolean; + public isDisposed?: boolean; public constructor(baseModel?: Partial) { Object.assign(this, baseModel); @@ -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 ??