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-7854 property consolidation backend service. #3834

Merged
merged 3 commits into from
Mar 5, 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 @@ -87,8 +87,11 @@ public IActionResult RunPropertyOperations([FromBody] IEnumerable<PropertyOperat
switch (propertyOperationTypes)
{
case PropertyOperationTypes.SUBDIVIDE:
var newProperties = _propertyOperationService.SubdivideProperty(propertyOperations);
return new JsonResult(_mapper.Map<IEnumerable<PimsPropertyOperation>>(newProperties));
var subdividedProperties = _propertyOperationService.SubdivideProperty(propertyOperations);
return new JsonResult(_mapper.Map<IEnumerable<PropertyOperationModel>>(subdividedProperties));
case PropertyOperationTypes.CONSOLIDATE:
var consolidatedProperties = _propertyOperationService.ConsolidateProperty(propertyOperations);
return new JsonResult(_mapper.Map<IEnumerable<PropertyOperationModel>>(consolidatedProperties));
default:
return BadRequest("Unsupported property operation type code.");
}
Expand Down
2 changes: 2 additions & 0 deletions source/backend/api/Services/IPropertyOperationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ public interface IPropertyOperationService
IList<PimsPropertyOperation> GetOperationsForProperty(long propertyId);

IEnumerable<PimsPropertyOperation> SubdivideProperty(IEnumerable<PimsPropertyOperation> operations);

IEnumerable<PimsPropertyOperation> ConsolidateProperty(IEnumerable<PimsPropertyOperation> operations);
}
}
104 changes: 91 additions & 13 deletions source/backend/api/Services/PropertyOperationService.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using MapsterMapper;
using LinqKit;
using Microsoft.Extensions.Logging;
using Pims.Core.Exceptions;
using Pims.Dal.Entities;
using Pims.Dal.Helpers.Extensions;
using Pims.Dal.Repositories;
using Pims.Dal.Security;
using Pims.Dal.Helpers.Extensions;

namespace Pims.Api.Services
{
Expand Down Expand Up @@ -47,21 +47,13 @@ public IEnumerable<PimsPropertyOperation> SubdivideProperty(IEnumerable<PimsProp
// validate
long sourcePropertyId = operations.FirstOrDefault().SourcePropertyId;
PimsProperty dbSourceProperty = _propertyService.GetById(sourcePropertyId);

CommonPropertyOperationValidation(operations);
if (dbSourceProperty.IsRetired == true)
{
throw new BusinessRuleViolationException("Retired properties cannot be subdivided.");
}

if (operations.Any(op => op.PropertyOperationNo != operations.FirstOrDefault().PropertyOperationNo))
{
throw new BusinessRuleViolationException("All property operations must have matching operation numbers.");
}

if (operations.Any(op => op.PropertyOperationTypeCode != operations.FirstOrDefault().PropertyOperationTypeCode))
{
throw new BusinessRuleViolationException("All property operations must have matching type codes.");
}

if (operations.Any(op => op.SourcePropertyId != operations.FirstOrDefault().SourcePropertyId))
{
throw new BusinessRuleViolationException("All property operations must have the same PIMS parent property.");
Expand Down Expand Up @@ -99,11 +91,97 @@ public IEnumerable<PimsPropertyOperation> SubdivideProperty(IEnumerable<PimsProp
var newProperty = _propertyService.PopulateNewProperty(operation.DestinationProperty, isOwned: true, isPropertyOfInterest: false);
operation.DestinationProperty = newProperty;
operation.DestinationPropertyId = newProperty.PropertyId;
operation.SourceProperty = null; // do not allow the property operation to modify the source in the add range operation.
}
var completedOperations = _repository.AddRange(operations);
_repository.CommitTransaction();

return completedOperations;
}

public IEnumerable<PimsPropertyOperation> ConsolidateProperty(IEnumerable<PimsPropertyOperation> operations)
{
var destinationProperty = operations.FirstOrDefault()?.DestinationProperty;
_user.ThrowIfNotAuthorized(Permissions.PropertyEdit);

// validate
IEnumerable<PimsProperty> sourceProperties = operations.Select(p => p.SourceProperty);
IEnumerable<PimsProperty> dbSourceProperties = _propertyService.GetMultipleById(sourceProperties.Select(sp => sp.PropertyId).ToList());

CommonPropertyOperationValidation(operations);
if(destinationProperty?.Pid == null)
{
throw new BusinessRuleViolationException("Consolidation child must have a property with a valid PID.");
}

if (dbSourceProperties.Any(sp => sp.IsRetired == true))
{
throw new BusinessRuleViolationException("Retired properties cannot be consolidated.");
}

if (operations.Any(op => op.DestinationProperty.Pid != destinationProperty?.Pid))
{
throw new BusinessRuleViolationException("All property operations must have the same child property with the same PID.");
}

if (operations.Select(o => o.SourceProperty).GroupBy(s => s.PropertyId).Count() < 2)
{
throw new BusinessRuleViolationException("Consolidations must contain at least two parent properties.");
}

// either the property exists in pims, and is present in the source properties list, or the property does not have a match in PIMS at all (neither pid nor property_id).
if (destinationProperty?.PropertyId > 0)
{
if (!dbSourceProperties.Any(sp => sp.PropertyId == destinationProperty?.PropertyId))
{
throw new BusinessRuleViolationException("Consolidated child property may not be in the PIMS inventory unless also in the parent property list.");
}
}
else
{
try
{
_propertyService.GetByPid(destinationProperty?.Pid?.ToString());
throw new BusinessRuleViolationException("Consolidated child may not already be in the PIMS inventory.");
}
catch (KeyNotFoundException)
{
// ignore exception, the pid should not exist.
}
}

// retire the source properties
foreach (var sp in dbSourceProperties)
{
sp.IsRetired = true;
_propertyService.Update(sp, false);
}

destinationProperty.PropertyId = 0; // in the case this property already exists, this will force it to be recreated.
var newProperty = _propertyService.PopulateNewProperty(destinationProperty, isOwned: true, isPropertyOfInterest: false);
operations.ForEach(op => {
op.DestinationProperty = newProperty;
op.DestinationPropertyId = newProperty.PropertyId;
op.SourceProperty = null; // do not allow the property operation to modify the source in the add range operation.
});

var completedOperations = _repository.AddRange(operations);
_repository.CommitTransaction();

return completedOperations;
}

private static void CommonPropertyOperationValidation(IEnumerable<PimsPropertyOperation> operations)
{
if (operations.Any(op => op.PropertyOperationNo != operations.FirstOrDefault().PropertyOperationNo))
{
throw new BusinessRuleViolationException("All property operations must have matching operation numbers.");
}

if (operations.Any(op => op.PropertyOperationTypeCode != operations.FirstOrDefault().PropertyOperationTypeCode))
{
throw new BusinessRuleViolationException("All property operations must have matching type codes.");
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public void Register(TypeAdapterConfig config)
config.NewConfig<PropertyOperationModel, Entity.PimsPropertyOperation>()
.Map(dest => dest.PropertyOperationId, src => src.Id)
.Map(dest => dest.SourcePropertyId, src => src.SourcePropertyId)
.Map(dest => dest.SourceProperty, src => src.SourceProperty)
.Map(dest => dest.DestinationPropertyId, src => src.DestinationPropertyId)
.Map(dest => dest.DestinationProperty, src => src.DestinationProperty)
.Map(dest => dest.PropertyOperationNo, src => src.PropertyOperationNo)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public PropertyOperationRepository(PimsContext dbContext, ClaimsPrincipal user,

#region Methods

/// <summary>
/// Retrieves the property operations for the given operation number.
/// </summary>
/// <param name="operationNumber"></param>
Expand Down Expand Up @@ -103,8 +104,6 @@ public IEnumerable<PimsPropertyOperation> AddRange(IEnumerable<PimsPropertyOpera
using var scope = Logger.QueryScope();
operations.ThrowIfNull(nameof(operations));

Context.PimsPropertyOperations.AddRange(operations);

long operationNo = _sequenceRepository.GetNextSequenceValue(PROPERTYOPERATIONNOSEQUENCE);
DateTime dateTime = DateTime.UtcNow;

Expand All @@ -113,6 +112,8 @@ public IEnumerable<PimsPropertyOperation> AddRange(IEnumerable<PimsPropertyOpera
operation.PropertyOperationNo = operationNo;
operation.OperationDt = dateTime;
}

Context.PimsPropertyOperations.AddRange(operations);
return operations;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public PropertyOperationControllerTest()
}

#region Tests
#region ExportProperties
#region Operations

/// <summary>
/// Mock a successful request with minimally valid models.
Expand All @@ -81,6 +81,28 @@ public void RunPropertyOperations_Success()
service.Verify(m => m.SubdivideProperty(It.IsAny<IEnumerable<PimsPropertyOperation>>()), Times.Once());
}

[Fact]
public void RunPropertyOperationsConsolidate_Success()
{
// Arrange
var helper = new TestHelper();
var controller = helper.CreateController<PropertyOperationController>(Permissions.PropertyEdit);
IEnumerable<PropertyOperationModel> pimsPropertyOperations = new List<PropertyOperationModel>()
{
new PropertyOperationModel() { PropertyOperationTypeCode = new Models.Base.CodeTypeModel<string>() { Id = PropertyOperationTypes.CONSOLIDATE.ToString() }, PropertyOperationNo = 1 },
new PropertyOperationModel() { PropertyOperationTypeCode = new Models.Base.CodeTypeModel<string>() { Id = PropertyOperationTypes.CONSOLIDATE.ToString() }, PropertyOperationNo = 1 },
};


var service = helper.GetService<Mock<IPropertyOperationService>>();

// Act
controller.RunPropertyOperations(pimsPropertyOperations);

// Assert
service.Verify(m => m.ConsolidateProperty(It.IsAny<IEnumerable<PimsPropertyOperation>>()), Times.Once());
}

/// <summary>
/// Mock a successful request with minimally valid models.
/// </summary>
Expand Down
Loading
Loading