Skip to content

Commit

Permalink
Psp 3042 (#1090)
Browse files Browse the repository at this point in the history
* psp-3042 security deposit notes.

* lint fixes.

* test corrections.

Co-authored-by: Smith <[email protected]>
  • Loading branch information
devinleighsmith and Smith authored Mar 15, 2022
1 parent 4ef79ae commit 646031a
Show file tree
Hide file tree
Showing 26 changed files with 410 additions and 103 deletions.
17 changes: 17 additions & 0 deletions backend/api/Areas/Leases/Controllers/SecurityDepositController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Pims.Api.Areas.Lease.Models.Lease;
using Pims.Api.Areas.Leases.Models.Lease;
using Pims.Api.Helpers.Exceptions;
using Pims.Api.Models;
using Pims.Api.Models.Concepts;
Expand Down Expand Up @@ -117,6 +118,22 @@ public IActionResult DeleteDeposit(long leaseId, [FromBody] ParentConcurrencyGua

return new JsonResult(_mapper.Map<LeaseModel>(updatedLease));
}

/// <summary>
/// update the deposit note on the given lease
/// </summary>
/// <returns></returns>
[HttpPut("{leaseId:long}/deposits/note")]
[HasPermission(Permissions.LeaseEdit)]
[Produces("application/json")]
[ProducesResponseType(typeof(IEnumerable<LeaseModel>), 200)]
[SwaggerOperation(Tags = new[] { "lease" })]
public IActionResult DeleteDeposit(long leaseId, [FromBody] ParentConcurrencyGuardModel<DepositNoteModel> depositNoteModel)
{
var updatedLease = _pimsService.SecurityDepositService.UpdateLeaseDepositNote(depositNoteModel.ParentId, depositNoteModel.ParentRowVersion, depositNoteModel.Payload.Note);

return new JsonResult(_mapper.Map<LeaseModel>(updatedLease));
}
#endregion
}
}
7 changes: 7 additions & 0 deletions backend/api/Areas/Leases/Models/Lease/DepositNoteModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Pims.Api.Areas.Leases.Models.Lease
{
public class DepositNoteModel
{
public string Note { get; set; }
}
}
8 changes: 4 additions & 4 deletions backend/api/Models/ParentConcurrencyGuardModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ namespace Pims.Api.Models
public class ParentConcurrencyGuardModel<T>
{
/// <summary>
/// get/set - The page number.
/// get/set - The model wrapped by this concurrency guard.
/// </summary>
public T Payload { get; set; }

/// <summary>
/// get/set - The page number.
/// get/set - The id of the parent entity.
/// </summary>
public long ParentId { get; set; }

/// <summary>
/// get/set - The page number.
/// get/set - The row version of the parent entity.
/// </summary>
public long ParentRowVersion { get; set; }
}
}
}
4 changes: 2 additions & 2 deletions backend/api/Pims.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<UserSecretsId>0ef6255f-9ea0-49ec-8c65-c172304b4926</UserSecretsId>
<Version>1.1.1-21.45</Version>
<AssemblyVersion>1.1.1.21</AssemblyVersion>
<Version>1.1.2-21.45</Version>
<AssemblyVersion>1.1.2.21</AssemblyVersion>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProjectGuid>16BC0468-78F6-4C91-87DA-7403C919E646</ProjectGuid>
</PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion backend/dal/Repositories/Interfaces/ILeaseRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public interface ILeaseRepository : IRepository<PimsLease>
int Count();
IEnumerable<PimsLease> Get(LeaseFilter filter, bool loadPayments = false);
long GetRowVersion(long id);
PimsLease Get(long id);
PimsLease Get(long id, bool skipNavigations = false);
Paged<PimsLease> GetPage(LeaseFilter filter);
PimsLease Add(PimsLease lease, bool userOverride = false);
PimsLease Update(PimsLease lease, bool commitTransaction = true);
Expand Down
17 changes: 10 additions & 7 deletions backend/dal/Repositories/LeaseRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,14 @@ public long GetRowVersion(long id)
this.User.ThrowIfNotAuthorized(Permissions.LeaseView);
return this.Context.PimsLeases.Where(l => l.LeaseId == id)?.Select(l => l.ConcurrencyControlNumber)?.FirstOrDefault() ?? throw new KeyNotFoundException();
}

public PimsLease Get(long id)
public PimsLease Get(long id, bool skipNavigations = false)
{
this.User.ThrowIfNotAuthorized(Permissions.LeaseView);
PimsLease lease = this.Context.PimsLeases.Include(l => l.PimsPropertyLeases)

IQueryable<PimsLease> leaseQuery = this.Context.PimsLeases.Where(l => l.LeaseId == id);
if (!skipNavigations)
{
leaseQuery = leaseQuery.Include(l => l.PimsPropertyLeases)
.ThenInclude(p => p.Property)
.ThenInclude(p => p.Address)
.ThenInclude(p => p.Country)
Expand Down Expand Up @@ -148,11 +151,11 @@ public PimsLease Get(long id)
.ThenInclude(t => t.LeasePaymentMethodTypeCodeNavigation)
.Include(t => t.PimsLeaseTerms)
.ThenInclude(t => t.PimsLeasePayments)
.ThenInclude(t => t.LeasePaymentStatusTypeCodeNavigation)

.Where(l => l.LeaseId == id)
.FirstOrDefault() ?? throw new KeyNotFoundException();
.ThenInclude(t => t.LeasePaymentStatusTypeCodeNavigation);
}
PimsLease lease = leaseQuery.FirstOrDefault() ?? throw new KeyNotFoundException();

lease.LeasePurposeTypeCodeNavigation = this.Context.PimsLeasePurposeTypes.Single(type => type.LeasePurposeTypeCode == lease.LeasePurposeTypeCode);
lease.PimsPropertyImprovements = lease.PimsPropertyImprovements.OrderBy(i => i.PropertyImprovementTypeCode).ToArray();
lease.PimsLeaseTerms = lease.PimsLeaseTerms.OrderBy(t => t.TermStartDate).ThenBy(t => t.LeaseTermId).Select(t =>
{
Expand Down
1 change: 1 addition & 0 deletions backend/dal/Services/Interfaces/ISecurityDepositService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ public interface ISecurityDepositService
{
PimsLease AddLeaseDeposit(long leaseId, long leaseRowVersion, PimsSecurityDeposit deposit);
PimsLease UpdateLeaseDeposit(long leaseId, long leaseRowVersion, PimsSecurityDeposit deposit);
PimsLease UpdateLeaseDepositNote(long leaseId, long leaseRowVersion, string note);
PimsLease DeleteLeaseDeposit(long leaseId, long leaseRowVersion, PimsSecurityDeposit deposit);
}
}
11 changes: 11 additions & 0 deletions backend/dal/Services/SecurityDepositService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@ public PimsLease UpdateLeaseDeposit(long leaseId, long leaseRowVersion, PimsSecu
return _leaseRepository.Get(leaseId);
}

public PimsLease UpdateLeaseDepositNote(long leaseId, long leaseRowVersion, string note)
{
_user.ThrowIfNotAuthorized(Permissions.LeaseEdit);
ValidateServiceCall(leaseId, leaseRowVersion);
var lease = _leaseRepository.Get(leaseId, true);
lease.ReturnNotes = note;
_leaseRepository.Update(lease);
_leaseRepository.CommitTransaction();
return _leaseRepository.Get(leaseId);
}

public PimsLease DeleteLeaseDeposit(long leaseId, long leaseRowVersion, PimsSecurityDeposit deposit)
{
_user.ThrowIfNotAuthorized(Permissions.LeaseDelete);
Expand Down
14 changes: 0 additions & 14 deletions backend/entities/Lease.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
Expand Down Expand Up @@ -33,18 +32,5 @@ public partial class PimsLease : IdentityBaseAppEntity<long>, IBaseAppEntity
/// </summary>
public ICollection<PimsPropertyImprovement> GetImprovements() => PimsPropertyImprovements;
#endregion

#region Constructors

/// <summary>
/// Create a new instance of a Lease class.
/// </summary>
/// <param name="purposeType"></param>
public PimsLease(PimsLeasePurposeType purposeType)
{
this.LeasePurposeTypeCode = purposeType?.LeasePurposeTypeCode ?? throw new ArgumentNullException(nameof(purposeType));
this.LeasePurposeTypeCodeNavigation = purposeType;
}
#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public void GetLeases_All_Success()
var service = helper.GetService<Mock<IPimsRepository>>();
var mapper = helper.GetService<IMapper>();

service.Setup(m => m.Lease.Get(It.IsAny<long>())).Returns(lease);
service.Setup(m => m.Lease.Get(It.IsAny<long>(), false)).Returns(lease);

// Act
var result = controller.GetLease(1);
Expand All @@ -46,7 +46,7 @@ public void GetLeases_All_Success()
var actualResult = Assert.IsType<Model.LeaseModel>(actionResult.Value);
var expectedResult = mapper.Map<Model.LeaseModel>(lease);
Assert.Equal(expectedResult, actualResult, new DeepPropertyCompare());
service.Verify(m => m.Lease.Get(It.IsAny<long>()), Times.Once());
service.Verify(m => m.Lease.Get(It.IsAny<long>(), false), Times.Once());
}
#endregion
#region UpdateProperties
Expand Down
10 changes: 5 additions & 5 deletions backend/tests/unit/dal/Services/LeaseTermServiceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public void AddTerm()
var leaseRepository = helper.GetService<Mock<Repositories.ILeaseRepository>>();
var leaseTermRepository = helper.GetService<Mock<Repositories.ILeaseTermRepository>>();
leaseService.Setup(x => x.IsRowVersionEqual(It.IsAny<long>(), It.IsAny<long>())).Returns(true);
leaseRepository.Setup(x => x.Get(It.IsAny<long>())).Returns(lease);
leaseRepository.Setup(x => x.Get(It.IsAny<long>(), false)).Returns(lease);

// Act
var term = new PimsLeaseTerm() { TermStartDate = DateTime.Now, LeaseId = lease.Id, Lease = lease };
Expand All @@ -47,7 +47,7 @@ public void AddTerm()

// Assert
leaseTermRepository.Verify(x => x.Add(term), Times.Once);
leaseRepository.Verify(x => x.Get(lease.Id), Times.Once);
leaseRepository.Verify(x => x.Get(lease.Id, false), Times.Once);
}

[Fact]
Expand Down Expand Up @@ -204,7 +204,7 @@ public void UpdateTerm()
var leaseService = helper.GetService<Mock<ILeaseService>>();
var leaseRepository = helper.GetService<Mock<Repositories.ILeaseRepository>>();
leaseService.Setup(x => x.IsRowVersionEqual(It.IsAny<long>(), It.IsAny<long>())).Returns(true);
leaseRepository.Setup(x => x.Get(It.IsAny<long>())).Returns(lease);
leaseRepository.Setup(x => x.Get(It.IsAny<long>(), false)).Returns(lease);
var leaseTermRepository = helper.GetService<Mock<Repositories.ILeaseTermRepository>>();
leaseTermRepository.Setup(x => x.GetById(It.IsAny<long>(), It.IsAny<bool>())).Returns(originalTerm);

Expand All @@ -215,7 +215,7 @@ public void UpdateTerm()

// Assert
leaseTermRepository.Verify(x => x.Update(term), Times.Once);
leaseRepository.Verify(x => x.Get(lease.Id), Times.Once);
leaseRepository.Verify(x => x.Get(lease.Id, false), Times.Once);
}

[Fact]
Expand Down Expand Up @@ -324,7 +324,7 @@ public void DeleteTerm()
var leaseService = helper.GetService<Mock<ILeaseService>>();
var leaseRepository = helper.GetService<Mock<Repositories.ILeaseRepository>>();
leaseService.Setup(x => x.IsRowVersionEqual(It.IsAny<long>(), It.IsAny<long>())).Returns(true);
leaseRepository.Setup(x => x.Get(It.IsAny<long>())).Returns(lease);
leaseRepository.Setup(x => x.Get(It.IsAny<long>(), false)).Returns(lease);
var leaseTermRepository = helper.GetService<Mock<Repositories.ILeaseTermRepository>>();
leaseTermRepository.Setup(x => x.GetById(It.IsAny<long>(), It.IsAny<bool>())).Returns(originalTerm);

Expand Down
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "frontend",
"version": "1.1.1-21.45",
"version": "1.1.2-21.45",
"private": true,
"dependencies": {
"@bcgov/bc-sans": "1.0.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import { Button } from 'components/common/form';
import { FormikProps } from 'formik';
import { ILease } from 'interfaces';
import { isEqual } from 'lodash';
import * as React from 'react';

import * as Styled from './styles';
import * as Styled from './add/styles';

interface IAddLeaseFormButtonsProps {
interface ISaveCancelButtonsProps {
onCancel: () => void;
onSaveOverride?: () => Promise<ILease | undefined>;
formikProps: FormikProps<any>;
className?: string;
}

const AddLeaseFormButtons: React.FunctionComponent<IAddLeaseFormButtonsProps> = ({
const SaveCancelButtons: React.FunctionComponent<ISaveCancelButtonsProps> = ({
onCancel,
formikProps,
onSaveOverride,
className,
}) => {
return (
<Styled.FormButtons>
<Styled.FormButtons className={className}>
<Button variant="secondary" onClick={onCancel}>
Cancel
</Button>
Expand All @@ -27,8 +32,12 @@ const AddLeaseFormButtons: React.FunctionComponent<IAddLeaseFormButtonsProps> =
}
isSubmitting={formikProps.isSubmitting}
onClick={async () => {
formikProps.setSubmitting(true);
formikProps.submitForm();
if (onSaveOverride) {
onSaveOverride();
} else {
formikProps.setSubmitting(true);
formikProps.submitForm();
}
}}
>
Save
Expand All @@ -37,4 +46,4 @@ const AddLeaseFormButtons: React.FunctionComponent<IAddLeaseFormButtonsProps> =
);
};

export default AddLeaseFormButtons;
export default SaveCancelButtons;
4 changes: 2 additions & 2 deletions frontend/src/features/leases/add/AddLeaseForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as React from 'react';
import { Prompt } from 'react-router-dom';

import { addFormLeaseToApiLease } from '../leaseUtils';
import AddLeaseFormButtons from './AddLeaseFormButtons';
import SaveCancelButtons from '../SaveCancelButtons';
import { LeaseSchema } from './AddLeaseYupSchema';
import AdministrationSubForm from './AdministrationSubForm';
import LeaseDatesSubForm from './LeaseDatesSubForm';
Expand Down Expand Up @@ -47,7 +47,7 @@ const AddLeaseForm: React.FunctionComponent<IAddLeaseFormProps> = ({
<AdministrationSubForm formikProps={formikProps}></AdministrationSubForm>
<ReferenceSubForm />
<PropertyInformationSubForm />
<AddLeaseFormButtons formikProps={formikProps} onCancel={onCancel} />
<SaveCancelButtons formikProps={formikProps} onCancel={onCancel} />
</Styled.LeaseForm>
</>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import { useKeycloak } from '@react-keycloak/web';
import { cleanup } from '@testing-library/react-hooks';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { Claims } from 'constants/claims';
import { Formik } from 'formik';
import { defaultFormLease, IFormLease } from 'interfaces';
import { noop } from 'lodash';
import { Api_SecurityDeposit, Api_SecurityDepositReturn } from 'models/api/SecurityDeposit';
import { render, RenderOptions, RenderResult } from 'utils/test-utils';
import {
fillInput,
render,
RenderOptions,
RenderResult,
userEvent,
waitFor,
} from 'utils/test-utils';

import DepositsContainer from './DepositsContainer';

Expand Down Expand Up @@ -41,6 +52,7 @@ const mockDepositReturns: Api_SecurityDepositReturn[] = [
},
];

const mockAxios = new MockAdapter(axios);
jest.mock('@react-keycloak/web');
(useKeycloak as jest.Mock).mockReturnValue({
keycloak: {
Expand Down Expand Up @@ -69,6 +81,10 @@ describe('DepositsContainer', () => {
beforeEach(() => {
Date.now = jest.fn().mockReturnValue(new Date('2020-10-15T18:33:37.000Z'));
});
afterEach(() => {
mockAxios.reset();
cleanup();
});
afterAll(() => {
jest.restoreAllMocks();
});
Expand All @@ -83,4 +99,45 @@ describe('DepositsContainer', () => {
});
expect(result.asFragment()).toMatchSnapshot();
});

it('saves deposit notes', async () => {
mockAxios.onPost().reply(200, {});
const { getByText, getByTestId, container } = setup({
lease: {
...defaultFormLease,
returnNotes: 'Tenant no longer has a dog, deposit returned, less fee for carpet cleaning',
securityDeposits: mockDeposits,
securityDepositReturns: mockDepositReturns,
},
claims: [Claims.LEASE_EDIT],
});
const editButton = getByTestId('edit-notes');
userEvent.click(editButton);
await fillInput(container, 'returnNotes', 'test note', 'textarea');
const saveButton = getByText('Save');
userEvent.click(saveButton);
await waitFor(() => {
expect(mockAxios.history.put).toHaveLength(1);
});
});

it('cancels an edited deposit note', async () => {
const { getByText, getByTestId, container } = await setup({
lease: {
...defaultFormLease,
returnNotes: 'Tenant no longer has a dog, deposit returned, less fee for carpet cleaning',
securityDeposits: mockDeposits,
securityDepositReturns: mockDepositReturns,
},
claims: [Claims.LEASE_EDIT],
});
const editButton = getByTestId('edit-notes');
userEvent.click(editButton);
const noteField = await fillInput(container, 'returnNotes', 'test note', 'textarea');
const cancelButton = getByText('Cancel');
userEvent.click(cancelButton);
await waitFor(() => {
expect(noteField.input).toHaveValue('');
});
});
});
Loading

0 comments on commit 646031a

Please sign in to comment.