Skip to content

Commit

Permalink
PSP-6865 property management models (#3488)
Browse files Browse the repository at this point in the history
* Add new properties to backend models

* Get property leases by property-id

* API models and basic edit/view

* Fix EF query

* Update backend mappings

* Model update
  • Loading branch information
asanchezr authored Sep 22, 2023
1 parent 1c61827 commit ee095d5
Show file tree
Hide file tree
Showing 15 changed files with 277 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class PropertyController : ControllerBase
#region Variables
private readonly IPropertyRepository _propertyRepository;
private readonly IPropertyService _propertyService;
private readonly IPropertyLeaseRepository _propertyLeaseRepository;
private readonly IMapper _mapper;
#endregion

Expand All @@ -35,12 +36,14 @@ public class PropertyController : ControllerBase
/// </summary>
/// <param name="propertyRepository"></param>
/// <param name="propertyService"></param>
/// <param name="propertyLeaseRepository"></param>
/// <param name="mapper"></param>
///
public PropertyController(IPropertyRepository propertyRepository, IPropertyService propertyService, IMapper mapper)
public PropertyController(IPropertyRepository propertyRepository, IPropertyService propertyService, IPropertyLeaseRepository propertyLeaseRepository, IMapper mapper)
{
_propertyRepository = propertyRepository;
_propertyService = propertyService;
_propertyLeaseRepository = propertyLeaseRepository;
_mapper = mapper;
}
#endregion
Expand All @@ -62,6 +65,23 @@ public IActionResult GetPropertyAssociationsWithId(long id)

return new JsonResult(_mapper.Map<PropertyAssociationModel>(property));
}

/// <summary>
/// Get all associated leases for the specified unique property 'id'.
/// </summary>
/// <returns></returns>
[HttpGet("{id}/leases")]
[HasPermission(Permissions.PropertyView)]
[HasPermission(Permissions.LeaseView)]
[Produces("application/json")]
[ProducesResponseType(typeof(IEnumerable<Api.Models.Concepts.PropertyLeaseModel>), 200)]
[SwaggerOperation(Tags = new[] { "property" })]
public IActionResult GetPropertyLeases(long propertyId)
{
var propertyLeases = _propertyLeaseRepository.GetAllByPropertyId(propertyId);

return new JsonResult(_mapper.Map<List<Api.Models.Concepts.PropertyLeaseModel>>(propertyLeases));
}
#endregion

#region Concept Endpoints
Expand Down
3 changes: 2 additions & 1 deletion source/backend/api/Models/Concepts/Lease/PropertyLeaseMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ public void Register(TypeAdapterConfig config)
config.NewConfig<Entity.PimsPropertyLease, Model.PropertyLeaseModel>()
.Map(dest => dest.Property, src => src.Property)
.Map(dest => dest.PropertyId, src => src.PropertyId)
.Map(dest => dest.Lease.Id, src => src.LeaseId)
.Map(dest => dest.Lease, src => src.Lease)
.Map(dest => dest.LeaseId, src => src.LeaseId)
.Map(dest => dest.AreaUnitType, src => src.AreaUnitTypeCodeNavigation)
.Map(dest => dest.LeaseArea, src => src.LeaseArea)
.Map(dest => dest.PropertyName, src => src.Name)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Mapster;
using Entity = Pims.Dal.Entities;

namespace Pims.Api.Models.Concepts
{
public class PropertyManagementPurposeMap : IRegister
{
public void Register(TypeAdapterConfig config)
{
config.NewConfig<Entity.PimsPropPropPurpose, PropertyManagementPurposeModel>()
.Map(dest => dest.Id, src => src.Internal_Id)
.Map(dest => dest.PropertyId, src => src.PropertyId)
.Map(dest => dest.PropertyPurposeTypeCode, src => src.PropertyPurposeTypeCodeNavigation)
.Inherits<Entity.IBaseAppEntity, BaseAppModel>();

config.NewConfig<PropertyManagementPurposeModel, Entity.PimsPropPropPurpose>()
.Map(dest => dest.Internal_Id, src => src.Id)
.Map(dest => dest.PropertyId, src => src.PropertyId)
.Map(dest => dest.PropertyPurposeTypeCode, src => src.PropertyPurposeTypeCode.Id)
.Inherits<BaseAppModel, Entity.IBaseAppEntity>();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace Pims.Api.Models.Concepts
{
public class PropertyManagementPurposeModel : BaseAppModel
{
#region Properties

/// <summary>
/// get/set - The relationship id.
/// </summary>
public long Id { get; set; }

/// <summary>
/// get/set - Parent property id.
/// </summary>
public long PropertyId { get; set; }

/// <summary>
/// get/set - The property purpose type code.
/// </summary>
public TypeModel<string> PropertyPurposeTypeCode { get; set; }

#endregion
}
}
10 changes: 10 additions & 0 deletions source/backend/api/Models/Concepts/Property/PropertyMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ public void Register(TypeAdapterConfig config)
.Map(dest => dest.SurplusDeclarationComment, src => src.SurplusDeclarationComment)
.Map(dest => dest.SurplusDeclarationDate, src => src.SurplusDeclarationDate)
.Map(dest => dest.SurplusDeclarationType, src => src.SurplusDeclarationTypeCodeNavigation)

.Map(dest => dest.ManagementPurposes, src => src.PimsPropPropPurposes)
.Map(dest => dest.AdditionalDetails, src => src.AdditionalDetails)
.Map(dest => dest.IsUtilitiesPayable, src => src.IsUtilitiesPayable)
.Map(dest => dest.IsTaxesPayable, src => src.IsTaxesPayable)
.Inherits<Entity.IBaseEntity, Api.Models.BaseModel>();

config.NewConfig<PropertyModel, Entity.PimsProperty>()
Expand Down Expand Up @@ -109,6 +114,11 @@ public void Register(TypeAdapterConfig config)
.Map(dest => dest.Zoning, src => src.Zoning)
.Map(dest => dest.ZoningPotential, src => src.ZoningPotential)

.Map(dest => dest.PimsPropPropPurposes, src => src.ManagementPurposes)
.Map(dest => dest.AdditionalDetails, src => src.AdditionalDetails)
.Map(dest => dest.IsUtilitiesPayable, src => src.IsUtilitiesPayable)
.Map(dest => dest.IsTaxesPayable, src => src.IsTaxesPayable)

.Inherits<BaseModel, Entity.IBaseEntity>();
}
}
Expand Down
23 changes: 23 additions & 0 deletions source/backend/api/Models/Concepts/Property/PropertyModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,29 @@ public class PropertyModel : BaseModel

public DateTime SurplusDeclarationDate { get; set; }
#endregion

#region Management
/// <summary>
/// get/set - The property management purposes.
/// </summary>
public IList<PropertyManagementPurposeModel> ManagementPurposes { get; set; }

/// <summary>
/// get/set - Additional details when property management purpose is OTHER.
/// </summary>
public string AdditionalDetails { get; set; }

/// <summary>
/// get/set - Whether utilities are payable for this property..
/// </summary>
public bool? IsUtilitiesPayable { get; set; }

/// <summary>
/// get/set - Whether taxes are payable for this property.
/// </summary>
public bool? IsTaxesPayable { get; set; }
#endregion

#endregion
}
}
11 changes: 7 additions & 4 deletions source/backend/dal/Repositories/PropertyLeaseRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,19 @@ public PropertyLeaseRepository(PimsContext dbContext, ClaimsPrincipal user, ILog
#region Methods

/// <summary>
/// Get the property lease for the specified property id.
/// Get the associated property leases for the specified property id.
/// </summary>
/// <param name="propertyId"></param>
/// <returns></returns>
public IEnumerable<PimsPropertyLease> GetAllByPropertyId(long propertyId)
{
return this.Context.PimsPropertyLeases.AsNoTracking()
.Include(p => p.Property)
return Context.PimsPropertyLeases.AsNoTracking()
.Include(pl => pl.Property)
.ThenInclude(p => p.Address)
.Include(l => l.Lease)
.Include(pl => pl.Lease)
.ThenInclude(l => l.LeaseStatusTypeCodeNavigation)
.Include(pl => pl.Lease)
.ThenInclude(l => l.PimsLeaseTerms)
.Where(p => p.PropertyId == propertyId);
}

Expand Down
15 changes: 15 additions & 0 deletions source/backend/entities/Partials/PropPropPurpose.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.ComponentModel.DataAnnotations.Schema;

namespace Pims.Dal.Entities
{
/// <summary>
/// PimsPropPropPurpose class, provides an entity for the datamodel to manage property management purpose types.
/// </summary>
public partial class PimsPropPropPurpose : StandardIdentityBaseAppEntity<long>, IBaseAppEntity
{
#region Properties
[NotMapped]
public override long Internal_Id { get => this.PropPropPurposeId; set => this.PropPropPurposeId = value; }
#endregion
}
}
32 changes: 32 additions & 0 deletions source/backend/entities/Partials/PropertyPurposeType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.ComponentModel.DataAnnotations.Schema;

namespace Pims.Dal.Entities
{
/// <summary>
/// PimsPropertyPurposeType class, provides an entity for the datamodel to manage a list of property management purposes.
/// </summary>
public partial class PimsPropertyPurposeType : ITypeEntity<string>
{
#region Properties

/// <summary>
/// get/set - Primary key to identify property management purpose type.
/// </summary>
[NotMapped]
public string Id { get => PropertyPurposeTypeCode; set => PropertyPurposeTypeCode = value; }
#endregion

#region Constructors

/// <summary>
/// Create a new instance of a PimsPropertyPurposeType class.
/// </summary>
/// <param name="id"></param>
public PimsPropertyPurposeType(string id)
: this()
{
Id = id;
}
#endregion
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useCallback, useEffect, useState } from 'react';

import { usePimsPropertyRepository } from '@/hooks/repositories/usePimsPropertyRepository';
import { Api_Property } from '@/models/api/Property';
import { Api_PropertyLease } from '@/models/api/PropertyLease';

import { IManagementSummaryViewProps } from './ManagementSummaryView';

interface IManagementSummaryContainerProps {
property: Api_Property;
setEditMode: (isEditing: boolean) => void;
View: React.FC<IManagementSummaryViewProps>;
}

export const ManagementSummaryContainer: React.FunctionComponent<
IManagementSummaryContainerProps
> = ({ property, setEditMode, View }) => {
const [propertyLeases, setPropertyLeases] = useState<Api_PropertyLease[]>([]);

const {
getPropertyLeases: { execute: getPropertyLeases, loading },
} = usePimsPropertyRepository();

const fetchPropertyLeases = useCallback(async () => {
if (!property.id) {
return;
}
const propertyLeasesResponse = await getPropertyLeases(property.id);
if (propertyLeasesResponse) {
setPropertyLeases(propertyLeasesResponse);
}
}, [getPropertyLeases, property]);

useEffect(() => {
fetchPropertyLeases();
}, [fetchPropertyLeases]);

return (
<View
isLoading={loading}
property={property}
propertyLeases={propertyLeases}
setEditMode={setEditMode}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { EditButton } from '@/components/common/EditButton';
import { Section } from '@/components/common/Section/Section';
import { StyledEditWrapper } from '@/components/common/Section/SectionStyles';
import { Claims } from '@/constants/index';
import useKeycloakWrapper from '@/hooks/useKeycloakWrapper';
import { Api_Property } from '@/models/api/Property';
import { Api_PropertyLease } from '@/models/api/PropertyLease';

export interface IManagementSummaryViewProps {
isLoading: boolean;
property: Api_Property;
propertyLeases: Api_PropertyLease[];
setEditMode: (isEditing: boolean) => void;
}

export const ManagementSummaryView: React.FunctionComponent<IManagementSummaryViewProps> = ({
isLoading,
property,
propertyLeases,
setEditMode,
}) => {
const { hasClaim } = useKeycloakWrapper();
return (
<Section header="Summary">
<StyledEditWrapper className="mr-3 my-1">
{/** TODO: Use MANAGEMENT CLAIMS when available */}
{setEditMode !== undefined && hasClaim(Claims.PROPERTY_EDIT) && (
<EditButton
title="Edit property management information"
onClick={() => setEditMode(true)}
/>
)}
</StyledEditWrapper>
</Section>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import LoadingBackdrop from '@/components/common/LoadingBackdrop';
import { StyledSummarySection } from '@/components/common/Section/SectionStyles';
import { Api_Property } from '@/models/api/Property';

import { ManagementSummaryContainer } from './ManagementSummaryContainer';
import { ManagementSummaryView } from './ManagementSummaryView';
import { PropertyContactContainer } from './PropertyContactContainer';
import { PropertyContactView } from './PropertyContactView';

Expand All @@ -26,7 +28,11 @@ export const PropertyManagementTabView: React.FunctionComponent<IPropertyManagem
return (
<StyledSummarySection>
<LoadingBackdrop show={loading} parentScreen={true} />

<ManagementSummaryContainer
property={property}
View={ManagementSummaryView}
setEditMode={setEditMode}
/>
<PropertyContactContainer
propertyId={property.id}
View={PropertyContactView}
Expand Down
3 changes: 3 additions & 0 deletions source/frontend/src/hooks/pims-api/useApiProperties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { IPropertyFilter } from '@/features/properties/filter/IPropertyFilter';
import { IPagedItems, IProperty } from '@/interfaces';
import { Api_PropertyFilterCriteria } from '@/models/api/ProjectFilterCriteria';
import { Api_Property, Api_PropertyAssociations } from '@/models/api/Property';
import { Api_PropertyLease } from '@/models/api/PropertyLease';

import useAxiosApi from './useApi';

Expand All @@ -26,6 +27,8 @@ export const useApiProperties = () => {
api.post<number[]>(`/properties/search/advanced-filter`, params),
getPropertyAssociationsApi: (id: number) =>
api.get<Api_PropertyAssociations>(`/properties/${id}/associations`),
getPropertyLeasesApi: (id: number) =>
api.get<Api_PropertyLease[]>(`/properties/${id}/leases`),
exportPropertiesApi: (filter: IPaginateProperties, outputFormat: 'csv' | 'excel' = 'excel') =>
api.get<Blob>(
`/reports/properties?${filter ? queryString.stringify({ ...filter, all: true }) : ''}`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@ import { useAxiosErrorHandler } from '@/utils';
* hook that retrieves a property from the inventory.
*/
export const usePimsPropertyRepository = () => {
const { getPropertyConceptWithIdApi, putPropertyConceptApi, getMatchingPropertiesApi } =
useApiProperties();
const {
getPropertyConceptWithIdApi,
putPropertyConceptApi,
getMatchingPropertiesApi,
getPropertyLeasesApi,
} = useApiProperties();

const getPropertyWrapper = useApiRequestWrapper({
requestFunction: useCallback(
Expand Down Expand Up @@ -53,8 +57,17 @@ export const usePimsPropertyRepository = () => {
}, []),
});

const getPropertyLeases = useApiRequestWrapper({
requestFunction: useCallback(
async (id: number) => await getPropertyLeasesApi(id),
[getPropertyLeasesApi],
),
requestName: 'getPropertyLeases',
onError: useAxiosErrorHandler('Failed to retrieve property leases.'),
});

return useMemo(
() => ({ getPropertyWrapper, updatePropertyWrapper, getMatchingProperties }),
[getPropertyWrapper, updatePropertyWrapper, getMatchingProperties],
() => ({ getPropertyWrapper, updatePropertyWrapper, getMatchingProperties, getPropertyLeases }),
[getPropertyWrapper, updatePropertyWrapper, getMatchingProperties, getPropertyLeases],
);
};
Loading

0 comments on commit ee095d5

Please sign in to comment.