Skip to content

Commit

Permalink
add support for sales edit blocking.
Browse files Browse the repository at this point in the history
  • Loading branch information
devinleighsmith committed Jan 26, 2024
1 parent bb0a8e5 commit 0320c52
Show file tree
Hide file tree
Showing 5 changed files with 267 additions and 1 deletion.
12 changes: 12 additions & 0 deletions source/backend/api/Services/DispositionFileService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,12 @@ public PimsDispositionSale UpdateDispositionFileSale(PimsDispositionSale disposi
_logger.LogInformation("Updating disposition file Sale with DispositionFileId: {id}", dispositionSale.DispositionSaleId);
_user.ThrowIfNotAuthorized(Permissions.DispositionEdit);

DispositionStatusTypes? currentDispositionStatus = GetCurrentDispositionStatus(dispositionSale.DispositionFileId);
if (!_dispositionStatusSolver.CanEditOrDeleteValuesOffersSales(currentDispositionStatus) && !_user.HasPermission(Permissions.SystemAdmin))
{
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.");
}

var updatedSale = _dispositionFileRepository.UpdateDispositionFileSale(dispositionSale);
_dispositionFileRepository.CommitTransaction();

Expand All @@ -299,6 +305,12 @@ public PimsDispositionSale AddDispositionFileSale(PimsDispositionSale dispositio
_logger.LogInformation("Adding disposition file Sale to Disposition File with Id: {id}", dispositionSale.DispositionFileId);
_user.ThrowIfNotAuthorized(Permissions.DispositionEdit);

DispositionStatusTypes? currentDispositionStatus = GetCurrentDispositionStatus(dispositionSale.DispositionFileId);
if (!_dispositionStatusSolver.CanEditOrDeleteValuesOffersSales(currentDispositionStatus) && !_user.HasPermission(Permissions.SystemAdmin))
{
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.");
}

var dispositionFileParent = _dispositionFileRepository.GetById(dispositionSale.DispositionFileId);
if (dispositionFileParent.PimsDispositionSales.Count > 0)
{
Expand Down
1 change: 1 addition & 0 deletions source/backend/api/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ private static void AddPimsApiServices(IServiceCollection services)
services.AddScoped<IExpropriationPaymentService, ExpropriationPaymentService>();
services.AddScoped<IAcquisitionStatusSolver, AcquisitionStatusSolver>();
services.AddScoped<IDispositionFileService, DispositionFileService>();
services.AddScoped<IDispositionStatusSolver, DispositionStatusSolver>();
}

/// <summary>
Expand Down
182 changes: 182 additions & 0 deletions source/backend/tests/unit/api/Services/DispositionFileServiceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@ public void Update_Should_Fail_Final()
repository.Setup(x => x.Update(It.IsAny<long>(), It.IsAny<PimsDispositionFile>())).Returns(dispFile);
repository.Setup(x => x.GetById(It.IsAny<long>())).Returns(dispFile);

var statusMock = this._helper.GetService<Mock<IDispositionStatusSolver>>();
statusMock.Setup(x => x.CanEditDetails(It.IsAny<DispositionStatusTypes>())).Returns(false);

// Act
Action act = () => service.Update(1, dispFile, new List<UserOverrideCode>() { UserOverrideCode.UpdateRegion, UserOverrideCode.DispositionFileFinalStatus });

Expand Down Expand Up @@ -520,6 +523,9 @@ public void Update_Success_FinalButAdmin()
repository.Setup(x => x.Update(It.IsAny<long>(), It.IsAny<PimsDispositionFile>())).Returns(dispFile);
repository.Setup(x => x.GetById(It.IsAny<long>())).Returns(dispFile);

var statusMock = this._helper.GetService<Mock<IDispositionStatusSolver>>();
statusMock.Setup(x => x.CanEditDetails(It.IsAny<DispositionStatusTypes>())).Returns(false);

// Act
var result = service.Update(1, dispFile, new List<UserOverrideCode>() { UserOverrideCode.UpdateRegion, UserOverrideCode.DispositionFileFinalStatus });

Expand Down Expand Up @@ -1163,6 +1169,9 @@ public void AddDispositionFileOffer_Success_Final_SystemAdmin()
DispositionOfferId = 11
});

var statusMock = this._helper.GetService<Mock<IDispositionStatusSolver>>();
statusMock.Setup(x => x.CanEditOrDeleteValuesOffersSales(It.IsAny<DispositionStatusTypes>())).Returns(false);

// Act
var result = service.AddDispositionFileOffer(1, new()
{
Expand Down Expand Up @@ -1306,6 +1315,9 @@ public void AddDispositionFileOffer_Should_Fail_Final()
DispositionOfferId = 11
});

var statusMock = this._helper.GetService<Mock<IDispositionStatusSolver>>();
statusMock.Setup(x => x.CanEditOrDeleteValuesOffersSales(It.IsAny<DispositionStatusTypes>())).Returns(false);

// Act
Action act = () => service.AddDispositionFileOffer(1, new()
{
Expand Down Expand Up @@ -1400,6 +1412,9 @@ public void UpdateDispositionFileOffer_Success_Final_SystemAdmin()
DispositionOfferId = 11
});

var statusMock = this._helper.GetService<Mock<IDispositionStatusSolver>>();
statusMock.Setup(x => x.CanEditOrDeleteValuesOffersSales(It.IsAny<DispositionStatusTypes>())).Returns(false);

// Act
var result = service.UpdateDispositionFileOffer(1, 11, new()
{
Expand Down Expand Up @@ -1446,6 +1461,9 @@ public void UpdateDispositionFileOffer_Should_Fail_Final_SystemAdmin()
DispositionOfferId = 11
});

var statusMock = this._helper.GetService<Mock<IDispositionStatusSolver>>();
statusMock.Setup(x => x.CanEditOrDeleteValuesOffersSales(It.IsAny<DispositionStatusTypes>())).Returns(false);

// Act
Action act = () => service.UpdateDispositionFileOffer(1, 11, new()
{
Expand Down Expand Up @@ -1850,6 +1868,9 @@ public void AddDispositionFile_Sale_Should_Fail_Sale_Exists()
var service = this.CreateDispositionServiceWithPermissions(Permissions.DispositionEdit);
var repository = this._helper.GetService<Mock<IDispositionFileRepository>>();

var statusMock = this._helper.GetService<Mock<IDispositionStatusSolver>>();
statusMock.Setup(x => x.CanEditOrDeleteValuesOffersSales(It.IsAny<DispositionStatusTypes>())).Returns(true);

repository.Setup(x => x.GetById(1)).Returns(new PimsDispositionFile()
{
DispositionFileId = 1,
Expand All @@ -1860,6 +1881,7 @@ public void AddDispositionFile_Sale_Should_Fail_Sale_Exists()
DispositionFileId = 1,
},
},
DispositionFileStatusTypeCode = EnumDispositionFileStatusTypeCode.ACTIVE.ToString(),
});

// Act
Expand All @@ -1872,6 +1894,40 @@ public void AddDispositionFile_Sale_Should_Fail_Sale_Exists()
act.Should().Throw<DuplicateEntityException>();
}

[Fact]
public void AddDispositionFile_Sale_Should_Fail_Final()
{
// Arrange
var service = this.CreateDispositionServiceWithPermissions(Permissions.DispositionEdit);
var repository = this._helper.GetService<Mock<IDispositionFileRepository>>();

repository.Setup(x => x.GetById(1)).Returns(new PimsDispositionFile()
{
DispositionFileId = 1,
PimsDispositionOffers = new List<PimsDispositionOffer>() { },
DispositionFileStatusTypeCode = EnumDispositionFileStatusTypeCode.COMPLETE.ToString(),
});
repository.Setup(x => x.AddDispositionFileSale(It.IsAny<PimsDispositionSale>())).Returns(new PimsDispositionSale()
{
DispositionFileId = 1,
DispositionSaleId = 100,
});

var statusMock = this._helper.GetService<Mock<IDispositionStatusSolver>>();
statusMock.Setup(x => x.CanEditOrDeleteValuesOffersSales(It.IsAny<DispositionStatusTypes>())).Returns(false);

// Act
Action act = () => service.AddDispositionFileSale(new()
{
DispositionFileId = 1,
DispositionSaleId = 0,
});

// Assert
var exception = act.Should().Throw<BusinessRuleViolationException>();
exception.WithMessage("The file you are editing is not active or draft, so you cannot save changes. Refresh your browser to see file state.");
}

[Fact]
public void AddDispositionFile_Sale_Success()
{
Expand All @@ -1883,13 +1939,51 @@ public void AddDispositionFile_Sale_Success()
{
DispositionFileId = 1,
PimsDispositionOffers = new List<PimsDispositionOffer>() { },
DispositionFileStatusTypeCode = EnumDispositionFileStatusTypeCode.ACTIVE.ToString(),
});
repository.Setup(x => x.AddDispositionFileSale(It.IsAny<PimsDispositionSale>())).Returns(new PimsDispositionSale()
{
DispositionFileId = 1,
DispositionSaleId = 100,
});

var statusMock = this._helper.GetService<Mock<IDispositionStatusSolver>>();
statusMock.Setup(x => x.CanEditOrDeleteValuesOffersSales(It.IsAny<DispositionStatusTypes>())).Returns(true);

// Act
var result = service.AddDispositionFileSale(new()
{
DispositionFileId = 1,
DispositionSaleId = 0,
});

// Assert
Assert.NotNull(result);
repository.Verify(x => x.AddDispositionFileSale(It.IsAny<PimsDispositionSale>()), Times.Once);
}

[Fact]
public void AddDispositionFile_Sale_Success_Final_SystemAdmin()
{
// Arrange
var service = this.CreateDispositionServiceWithPermissions(Permissions.DispositionEdit, Permissions.SystemAdmin);
var repository = this._helper.GetService<Mock<IDispositionFileRepository>>();

repository.Setup(x => x.GetById(1)).Returns(new PimsDispositionFile()
{
DispositionFileId = 1,
PimsDispositionOffers = new List<PimsDispositionOffer>() { },
DispositionFileStatusTypeCode = EnumDispositionFileStatusTypeCode.COMPLETE.ToString(),
});
repository.Setup(x => x.AddDispositionFileSale(It.IsAny<PimsDispositionSale>())).Returns(new PimsDispositionSale()
{
DispositionFileId = 1,
DispositionSaleId = 100,
});

var statusMock = this._helper.GetService<Mock<IDispositionStatusSolver>>();
statusMock.Setup(x => x.CanEditOrDeleteValuesOffersSales(It.IsAny<DispositionStatusTypes>())).Returns(false);

// Act
var result = service.AddDispositionFileSale(new()
{
Expand Down Expand Up @@ -1919,13 +2013,57 @@ public void UpdateDispositionFile_Sale_Should_Fail_NoPermission()
act.Should().Throw<NotAuthorizedException>();
}

[Fact]
public void UpdateDispositionFile_Sale_Should_Fail_Final()
{
// Arrange
var service = this.CreateDispositionServiceWithPermissions(Permissions.DispositionEdit);
var repository = this._helper.GetService<Mock<IDispositionFileRepository>>();

var statusMock = this._helper.GetService<Mock<IDispositionStatusSolver>>();
statusMock.Setup(x => x.CanEditOrDeleteValuesOffersSales(It.IsAny<DispositionStatusTypes>())).Returns(false);

repository.Setup(x => x.GetById(1)).Returns(new PimsDispositionFile()
{
DispositionFileId = 1,
PimsDispositionSales = new List<PimsDispositionSale>() {
new PimsDispositionSale()
{
DispositionFileId = 1,
DispositionSaleId = 10
}
},
DispositionFileStatusTypeCode = EnumDispositionFileStatusTypeCode.COMPLETE.ToString()
});
repository.Setup(x => x.UpdateDispositionFileSale(It.IsAny<PimsDispositionSale>())).Returns(new PimsDispositionSale()
{
DispositionFileId = 1,
DispositionSaleId = 10,
});

// Act
Action act = () => service.UpdateDispositionFileSale(new()
{
DispositionFileId = 1,
DispositionSaleId = 10,
SaleFinalAmt = 2000,
});

// Assert
var exception = act.Should().Throw<BusinessRuleViolationException>();
exception.WithMessage("The file you are editing is not active or draft, so you cannot save changes. Refresh your browser to see file state.");
}

[Fact]
public void UpdateDispositionFile_Sale_Success()
{
// Arrange
var service = this.CreateDispositionServiceWithPermissions(Permissions.DispositionEdit);
var repository = this._helper.GetService<Mock<IDispositionFileRepository>>();

var statusMock = this._helper.GetService<Mock<IDispositionStatusSolver>>();
statusMock.Setup(x => x.CanEditOrDeleteValuesOffersSales(It.IsAny<DispositionStatusTypes>())).Returns(true);

repository.Setup(x => x.GetById(1)).Returns(new PimsDispositionFile()
{
DispositionFileId = 1,
Expand All @@ -1936,6 +2074,7 @@ public void UpdateDispositionFile_Sale_Success()
DispositionSaleId = 10
}
},
DispositionFileStatusTypeCode = EnumDispositionFileStatusTypeCode.ACTIVE.ToString(),
});
repository.Setup(x => x.UpdateDispositionFileSale(It.IsAny<PimsDispositionSale>())).Returns(new PimsDispositionSale()
{
Expand All @@ -1956,6 +2095,49 @@ public void UpdateDispositionFile_Sale_Success()
repository.Verify(x => x.UpdateDispositionFileSale(It.IsAny<PimsDispositionSale>()), Times.Once);
}

[Fact]
public void UpdateDispositionFile_Sale_Success_Final_SystemAdmin()
{
// Arrange
var service = this.CreateDispositionServiceWithPermissions(Permissions.DispositionEdit, Permissions.SystemAdmin);
var repository = this._helper.GetService<Mock<IDispositionFileRepository>>();

var statusMock = this._helper.GetService<Mock<IDispositionStatusSolver>>();
statusMock.Setup(x => x.CanEditOrDeleteValuesOffersSales(It.IsAny<DispositionStatusTypes>())).Returns(false);

repository.Setup(x => x.GetById(1)).Returns(new PimsDispositionFile()
{
DispositionFileId = 1,
PimsDispositionSales = new List<PimsDispositionSale>() {
new PimsDispositionSale()
{
DispositionFileId = 1,
DispositionSaleId = 10
}
},
DispositionFileStatusTypeCode = EnumDispositionFileStatusTypeCode.COMPLETE.ToString()
});
repository.Setup(x => x.UpdateDispositionFileSale(It.IsAny<PimsDispositionSale>())).Returns(new PimsDispositionSale()
{
DispositionFileId = 1,
DispositionSaleId = 10,
});

// Act
var result = service.UpdateDispositionFileSale(new()
{
DispositionFileId = 1,
DispositionSaleId = 10,
SaleFinalAmt = 2000,
});

// Assert
Assert.NotNull(result);
repository.Verify(x => x.UpdateDispositionFileSale(It.IsAny<PimsDispositionSale>()), Times.Once);
}

#endregion

#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,71 @@ describe('Disposition Offer Detail View component', () => {
expect(editButton).toBeVisible();
});

it('hides the edit button for Sale for users without permissions', async () => {
const { queryByTitle, queryByTestId } = await setup({
props: { dispositionFile: mockDispositionFileResponse() },
claims: [Claims.DISPOSITION_VIEW],
});
await waitForEffects();

const editButton = queryByTitle('Edit Sale');
const icon = queryByTestId('tooltip-icon-1-sale-summary-cannot-edit-tooltip');

expect(editButton).toBeNull();
expect(icon).toBeNull();
});

it('shows a warning above sale if the user has edit permissions but file is in non-editable state', async () => {
const { queryByTitle, queryByTestId } = await setup({
props: {
dispositionFile: {
...mockDispositionFileResponse(),
fileStatusTypeCode: { id: DispositionFileStatus.Complete },
},
},
claims: [Claims.DISPOSITION_EDIT],
});
await waitForEffects();

const editButton = queryByTitle('Edit Sale');
const icon = queryByTestId('tooltip-icon-1-sale-summary-cannot-edit-tooltip');

expect(editButton).toBeNull();
expect(icon).toBeVisible();
});

it('shows sale edit button if the user has edit permissions but file is in non-editable state and user is admin', async () => {
const { queryByTitle, queryByTestId } = await setup({
props: {
dispositionFile: {
...mockDispositionFileResponse(),
fileStatusTypeCode: { id: DispositionFileStatus.Complete },
},
},
claims: [Claims.DISPOSITION_EDIT],
roles: [Roles.SYSTEM_ADMINISTRATOR],
});
await waitForEffects();

const editButton = queryByTitle('Edit Sale');
const icon = queryByTestId('tooltip-icon-1-sale-summary-cannot-edit-tooltip');

expect(editButton).toBeVisible();
expect(icon).toBeNull();
});

it('renders the edit button for Sale for users with disposition edit permissions', async () => {
const { getByTitle } = await setup({
props: { dispositionFile: mockDispositionFileResponse() },
claims: [Claims.DISPOSITION_EDIT],
});
await waitForEffects();

const editButton = getByTitle('Edit Sale');

expect(editButton).toBeVisible();
});

it('displays a message when Disposition has no offers', async () => {
const mockDisposition = mockDispositionFileApi;
mockDisposition.dispositionSale = null;
Expand Down
Loading

0 comments on commit 0320c52

Please sign in to comment.