diff --git a/source/backend/api/Services/DispositionFileService.cs b/source/backend/api/Services/DispositionFileService.cs index e4b13373ab..a7ef0e820f 100644 --- a/source/backend/api/Services/DispositionFileService.cs +++ b/source/backend/api/Services/DispositionFileService.cs @@ -116,35 +116,18 @@ public PimsDispositionFile Update(long id, PimsDispositionFile dispositionFile, throw new BusinessRuleViolationException("The file you are editing is not active or draft, so you cannot save changes. Refresh your browser to see file state."); } - ValidateStaff(dispositionFile); ValidateVersion(id, dispositionFile.ConcurrencyControlNumber); - dispositionFile.ThrowContractorRemovedFromTeam(_user, _userRepository); - - var doNotAddToStatuses = new List() { EnumDispositionFileStatusTypeCode.COMPLETE.ToString(), EnumDispositionFileStatusTypeCode.ARCHIVED.ToString(), EnumDispositionFileStatusTypeCode.CANCELLED.ToString(), }; + // validate disposition file state before proceeding with any database updates var currentDispositionFile = _dispositionFileRepository.GetById(id); + ValidateFileBeforeUpdate(dispositionFile, currentDispositionFile, userOverrides); - if (!userOverrides.Contains(UserOverrideCode.DispositionFileFinalStatus) && !doNotAddToStatuses.Contains(currentDispositionFile.DispositionFileStatusTypeCode) - && doNotAddToStatuses.Contains(dispositionFile.DispositionFileStatusTypeCode)) - { - throw new UserOverrideException(UserOverrideCode.DispositionFileFinalStatus, "You are changing this file to a non-editable state. (Only system administrators can edit the file when set to Archived, Cancelled or Completed state). Do you wish to continue?"); - } - else if (currentDispositionFile.DispositionFileStatusTypeCode != EnumDispositionFileStatusTypeCode.COMPLETE.ToString() - && dispositionFile.DispositionFileStatusTypeCode == EnumDispositionFileStatusTypeCode.COMPLETE.ToString()) - { - if (currentDispositionFile?.PimsDispositionSales?.FirstOrDefault()?.SaleFinalAmt == null) - { - throw new BusinessRuleViolationException("You have not added a Sales Price. Please add a Sales Price before completion."); - } - else if (currentDispositionFile.PimsDispositionFileProperties.Count > 0) - { - DisposeOfProperties(dispositionFile, userOverrides); - } - } + var isFileClosing = currentDispositionFile.DispositionFileStatusTypeCode != EnumDispositionFileStatusTypeCode.COMPLETE.ToString() && + dispositionFile.DispositionFileStatusTypeCode == EnumDispositionFileStatusTypeCode.COMPLETE.ToString(); - if (!userOverrides.Contains(UserOverrideCode.UpdateRegion)) + if (isFileClosing && currentDispositionFile.PimsDispositionFileProperties?.Count > 0) { - ValidateMinistryRegion(id, dispositionFile.RegionCode); + DisposeOfProperties(dispositionFile); } _dispositionFileRepository.Update(id, dispositionFile); @@ -559,30 +542,69 @@ public PimsDispositionFile UpdateProperties(PimsDispositionFile dispositionFile, return _dispositionFileRepository.GetById(dispositionFile.Internal_Id); } - /// - /// Attempt to dispose of any properties if all business rules are met. - /// - /// - private void DisposeOfProperties(PimsDispositionFile dispositionFile, IEnumerable userOverrides) + private void ValidateFileBeforeUpdate(PimsDispositionFile incomingDispositionFile, PimsDispositionFile currentDispositionFile, IEnumerable userOverrides) { - var currentProperties = _dispositionFilePropertyRepository.GetPropertiesByDispositionFileId(dispositionFile.Internal_Id); - if (currentProperties.Any(p => p.Property.IsOwned) && !userOverrides.Contains(UserOverrideCode.DisposeOfProperties)) + // Implement file validation logic before proceeding to update. This includes file closing validation. + // The order of validation checks is important as it has been requested by business users. + var isFileClosing = currentDispositionFile.DispositionFileStatusTypeCode != EnumDispositionFileStatusTypeCode.COMPLETE.ToString() && + incomingDispositionFile.DispositionFileStatusTypeCode == EnumDispositionFileStatusTypeCode.COMPLETE.ToString(); + + var currentProperties = _dispositionFilePropertyRepository.GetPropertiesByDispositionFileId(incomingDispositionFile.Internal_Id); + + // The following checks result in hard STOP errors + if (isFileClosing) { - throw new UserOverrideException(UserOverrideCode.DisposeOfProperties, "You are completing this Disposition File with owned PIMS inventory properties. All properties will be removed from the PIMS inventory (any Other Interests will remain). Do you wish to proceed?"); + if (currentProperties.Any(p => p.Property.IsPropertyOfInterest)) + { + throw new BusinessRuleViolationException("You have one or more properties attached to this Disposition file that is NOT in the \"Core Inventory\" (i.e. owned by BCTFA and/or HMK). To complete this file you must either, remove these non \"Non-Core Inventory\" properties, OR make sure the property is added to the PIMS inventory first."); + } + + if (currentDispositionFile.PimsDispositionSales?.FirstOrDefault()?.SaleFinalAmt == null) + { + throw new BusinessRuleViolationException("You have not added a Sales Price. Please add a Sales Price before completion."); + } } - else if (currentProperties.Any(p => p.Property.IsPropertyOfInterest)) + + ValidateStaff(incomingDispositionFile); + incomingDispositionFile.ThrowContractorRemovedFromTeam(_user, _userRepository); + + // From here on - these checks result in warnings that require user confirmation + if (!userOverrides.Contains(UserOverrideCode.UpdateRegion)) { - throw new BusinessRuleViolationException("You have one or more properties attached to this Disposition file that is NOT in the \"Core Inventory\" (i.e. owned by BCTFA and/or HMK). To complete this file you must either, remove these non \"Non-Core Inventory\" properties, OR make sure the property is added to the PIMS inventory first."); + // confirm user action - file region was changed + ValidateMinistryRegion(incomingDispositionFile.Internal_Id, incomingDispositionFile.RegionCode); } - // Get the current properties in the research file + var nonEditableStatuses = new List() { EnumDispositionFileStatusTypeCode.COMPLETE.ToString(), EnumDispositionFileStatusTypeCode.ARCHIVED.ToString(), EnumDispositionFileStatusTypeCode.CANCELLED.ToString(), }; + var isFileChangingToNonEditableState = !nonEditableStatuses.Contains(currentDispositionFile.DispositionFileStatusTypeCode) && nonEditableStatuses.Contains(incomingDispositionFile.DispositionFileStatusTypeCode); + + // confirm user action - file is changing to non-editable state + if (!userOverrides.Contains(UserOverrideCode.DispositionFileFinalStatus) && isFileChangingToNonEditableState) + { + throw new UserOverrideException(UserOverrideCode.DispositionFileFinalStatus, "You are changing this file to a non-editable state. (Only system administrators can edit the file when set to Archived, Cancelled or Completed state). Do you wish to continue?"); + } + + if (isFileClosing && currentProperties.Any(p => p.Property.IsOwned) && !userOverrides.Contains(UserOverrideCode.DisposeOfProperties)) + { + throw new UserOverrideException(UserOverrideCode.DisposeOfProperties, "You are completing this Disposition File with owned PIMS inventory properties. All properties will be removed from the PIMS inventory (any Other Interests will remain). Do you wish to proceed?"); + } + } + + /// + /// Attempt to dispose of any properties if all business rules are met. + /// + /// The disposition file entity. + private void DisposeOfProperties(PimsDispositionFile dispositionFile) + { + // Get the current properties in the disposition file + var currentProperties = _dispositionFilePropertyRepository.GetPropertiesByDispositionFileId(dispositionFile.Internal_Id); var ownedProperties = currentProperties.Where(p => p.Property.IsOwned); // PSP-7275 Business rule: Transfer properties of interest to disposed when disposition file is completed foreach (var dispositionProperty in ownedProperties) { var property = dispositionProperty.Property; - _propertyRepository.TransferFileProperty(property, new Dal.Models.PropertyOwnershipState() { isDisposed = true, isPropertyOfInterest = false, isOtherInterest = false, isOwned = false}); + _propertyRepository.TransferFileProperty(property, new Dal.Models.PropertyOwnershipState() { isDisposed = true, isPropertyOfInterest = false, isOtherInterest = false, isOwned = false }); } } diff --git a/source/backend/tests/unit/api/Services/DispositionFileServiceTest.cs b/source/backend/tests/unit/api/Services/DispositionFileServiceTest.cs index 5d19a12290..874444e501 100644 --- a/source/backend/tests/unit/api/Services/DispositionFileServiceTest.cs +++ b/source/backend/tests/unit/api/Services/DispositionFileServiceTest.cs @@ -414,6 +414,7 @@ public void Update_Final_Validation_UserOverride(EnumDispositionFileStatusTypeCo // Arrange var service = this.CreateDispositionServiceWithPermissions(Permissions.DispositionEdit); var repository = this._helper.GetService>(); + var dispositionFilePropertyRepository = this._helper.GetService>(); var dispFile = EntityHelper.CreateDispositionFile(1); @@ -429,6 +430,8 @@ public void Update_Final_Validation_UserOverride(EnumDispositionFileStatusTypeCo repository.Setup(x => x.Update(It.IsAny(), It.IsAny())).Returns(dispFile); repository.Setup(x => x.GetById(It.IsAny())).Returns(dispFile); + dispositionFilePropertyRepository.Setup(x => x.GetPropertiesByDispositionFileId(It.IsAny())).Returns(new List() { }); + // Act Action act = () => service.Update(2, updateDispFile, new List() { UserOverrideCode.DispositionFileFinalStatus }); @@ -443,6 +446,7 @@ public void Update_Should_Fail_Complete_NoSale() // Arrange var service = this.CreateDispositionServiceWithPermissions(Permissions.DispositionEdit); var repository = this._helper.GetService>(); + var dispositionFilePropertyRepository = this._helper.GetService>(); var statusMock = this._helper.GetService>(); statusMock.Setup(x => x.CanEditDetails(It.IsAny())).Returns(true); @@ -456,6 +460,7 @@ public void Update_Should_Fail_Complete_NoSale() repository.Setup(x => x.GetRegion(It.IsAny())).Returns(1); repository.Setup(x => x.Update(It.IsAny(), It.IsAny())).Returns(dispFile); repository.Setup(x => x.GetById(It.IsAny())).Returns(dispFile); + dispositionFilePropertyRepository.Setup(x => x.GetPropertiesByDispositionFileId(It.IsAny())).Returns(new List() { }); // Act Action act = () => service.Update(2, updateDispFile, new List() { UserOverrideCode.DispositionFileFinalStatus }); diff --git a/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/update/UpdateDispositionForm.tsx b/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/update/UpdateDispositionForm.tsx index 25b8876bef..37ae201d95 100644 --- a/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/update/UpdateDispositionForm.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/update/UpdateDispositionForm.tsx @@ -93,7 +93,7 @@ const UpdateDispositionForm: React.FC = ({ -
+