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-6865 property management models #3488

Merged
merged 6 commits into from
Sep 22, 2023
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 @@ -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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to confirm that this approach is appropriate given that is requires LeaseView. That means that a user that has management view will get an error unless they also have lease-view when viewing the management summary.

We may need to return just the boolean field against the property calculated on the backend with no lease restrictions instead.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, as said in the PR description this is the basic model stuff. I can change that in my next PR

{
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 */}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@asanchezr the management claims are available, can you add those please?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are available now - not when I sent the PR originally. Did you add them to the frontend enum as well or do I need to do that?

{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