Skip to content

Commit

Permalink
Merge branch 'main' into PIMS-1967
Browse files Browse the repository at this point in the history
  • Loading branch information
LawrenceLau2020 authored Aug 15, 2024
2 parents 05f1a0d + 604f054 commit 03f78f8
Show file tree
Hide file tree
Showing 10 changed files with 82 additions and 49 deletions.
5 changes: 3 additions & 2 deletions express-api/src/services/buildings/buildingServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,9 @@ export const updateBuildingById = async (building: DeepPartial<Building>, ssoUse
if (!existingBuilding) {
throw new ErrorWithCode('Building does not exists.', 404);
}
if (building.AgencyId && building.AgencyId !== existingBuilding.AgencyId && !isAdmin(ssoUser)) {
throw new ErrorWithCode('Changing agency is not permitted.', 403);
const validUserAgencies = await userServices.getAgencies(ssoUser.preferred_username);
if (!isAdmin(ssoUser) && !validUserAgencies.includes(building.AgencyId)) {
throw new ErrorWithCode('This agency change is not permitted.', 403);
}
if (building.Fiscals && building.Fiscals.length) {
building.Fiscals = await Promise.all(
Expand Down
9 changes: 3 additions & 6 deletions express-api/src/services/parcels/parcelServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,12 +163,9 @@ const updateParcel = async (incomingParcel: DeepPartial<Parcel>, ssoUser: SSOUse
if (findParcel == null || findParcel.Id !== incomingParcel.Id) {
throw new ErrorWithCode('Parcel not found', 404);
}
if (
incomingParcel.AgencyId &&
incomingParcel.AgencyId !== findParcel.AgencyId &&
!isAdmin(ssoUser)
) {
throw new ErrorWithCode('Changing agency is not permitted.', 403);
const validUserAgencies = await userServices.getAgencies(ssoUser.preferred_username);
if (!isAdmin(ssoUser) && !validUserAgencies.includes(incomingParcel.AgencyId)) {
throw new ErrorWithCode('This agency change is not permitted.', 403);
}
if (incomingParcel.Fiscals && incomingParcel.Fiscals.length) {
incomingParcel.Fiscals = await Promise.all(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ import userServices from '@/services/users/usersServices';
import { ProjectProperty } from '@/typeorm/Entities/ProjectProperty';
import { Roles } from '@/constants/roles';

const buildingRepo = AppDataSource.getRepository(Building);
jest.spyOn(userServices, 'getUser').mockImplementation(async () => produceUser());
jest.spyOn(userServices, 'getAgencies').mockImplementation(async () => []);

const buildingRepo = AppDataSource.getRepository(Building);
const _buildingSave = jest
.spyOn(buildingRepo, 'save')
.mockImplementation(async (building: DeepPartial<Building> & Building) => building);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ jest.spyOn(AppDataSource.getRepository(ProjectProperty), 'find').mockImplementat
jest.spyOn(parcelRepo, 'find').mockImplementation(async () => [produceParcel(), produceParcel()]);

jest.spyOn(userServices, 'getUser').mockImplementation(async () => produceUser());
jest.spyOn(userServices, 'getAgencies').mockImplementation(async () => []);

jest
.spyOn(AppDataSource.getRepository(ParcelEvaluation), 'find')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AppDataSource } from '@/appDataSource';
import { ProjectStatus } from '@/constants/projectStatus';
import { ProjectType } from '@/constants/projectType';
import { ProjectWorkflow } from '@/constants/projectWorkflow';
import { Roles } from '@/constants/roles';
import projectServices from '@/services/projects/projectsServices';
import userServices from '@/services/users/usersServices';
import { Agency } from '@/typeorm/Entities/Agency';
Expand Down Expand Up @@ -109,6 +110,7 @@ const _getNextSequence = jest.spyOn(AppDataSource, 'query').mockImplementation(a
]);

jest.spyOn(userServices, 'getUser').mockImplementation(async () => produceUser());
jest.spyOn(userServices, 'getAgencies').mockImplementation(async () => [1]);

const _mockStartTransaction = jest.fn(async () => {});
const _mockRollbackTransaction = jest.fn(async () => {});
Expand Down Expand Up @@ -526,7 +528,7 @@ describe('UNIT - Project Services', () => {
parcels: [1, 3],
buildings: [4, 5],
},
produceSSO(),
produceSSO({ client_roles: [Roles.ADMIN] }),
);
expect(result.StatusId).toBe(2);
expect(result.Name).toBe('New Name');
Expand Down Expand Up @@ -610,7 +612,7 @@ describe('UNIT - Project Services', () => {
parcels: [1, 3],
buildings: [4, 5],
},
produceSSO(),
produceSSO({ client_roles: [Roles.ADMIN] }),
),
).rejects.toThrow(new ErrorWithCode('Error updating project: bad save', 500));
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { AppDataSource } from '@/appDataSource';
import { Roles } from '@/constants/roles';
import propertyServices, {
Expand Down Expand Up @@ -46,7 +47,6 @@ import {
import { DeepPartial, EntityTarget, ObjectLiteral } from 'typeorm';
import xlsx, { WorkSheet } from 'xlsx';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const _parcelsCreateQueryBuilder: any = {
select: () => _parcelsCreateQueryBuilder,
leftJoinAndSelect: () => _parcelsCreateQueryBuilder,
Expand All @@ -59,7 +59,6 @@ const _parcelsCreateQueryBuilder: any = {
getMany: () => [produceParcel()],
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const _buildingsCreateQueryBuilder: any = {
select: () => _buildingsCreateQueryBuilder,
leftJoinAndSelect: () => _buildingsCreateQueryBuilder,
Expand All @@ -72,7 +71,6 @@ const _buildingsCreateQueryBuilder: any = {
getMany: () => [produceBuilding()],
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const _propertyUnionCreateQueryBuilder: any = {
select: () => _propertyUnionCreateQueryBuilder,
leftJoinAndSelect: () => _propertyUnionCreateQueryBuilder,
Expand Down
14 changes: 7 additions & 7 deletions react-app/src/components/property/AddProperty.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,28 +21,28 @@ import {
BuildingConstructionType,
BuildingPredominateUse,
} from '@/hooks/api/useBuildingsApi';
import { AuthContext } from '@/contexts/authContext';
import { parseFloatOrNull, parseIntOrNull } from '@/utilities/formatters';
import useDataSubmitter from '@/hooks/useDataSubmitter';
import { LoadingButton } from '@mui/lab';
import { LookupContext } from '@/contexts/lookupContext';
import { Classification } from '@/hooks/api/useLookupApi';
import useHistoryAwareNavigate from '@/hooks/useHistoryAwareNavigate';
import useUserAgencies from '@/hooks/api/useUserAgencies';

const AddProperty = () => {
//const years = [new Date().getFullYear(), new Date().getFullYear() - 1];
const [propertyType, setPropertyType] = useState<PropertyType>('Parcel');
const [showErrorText, setShowErrorTest] = useState(false);
const [showErrorText, setShowErrorText] = useState(false);
const { goToFromStateOrSetRoute } = useHistoryAwareNavigate();
const api = usePimsApi();
const userContext = useContext(AuthContext);
const { data: lookupData } = useContext(LookupContext);
const { submit: submitParcel, submitting: submittingParcel } = useDataSubmitter(
api.parcels.addParcel,
);
const { submit: submitBuilding, submitting: submittingBuilding } = useDataSubmitter(
api.buildings.addBuilding,
);
const { menuItems: agencyOptions } = useUserAgencies();

const formMethods = useForm({
defaultValues: {
Expand All @@ -68,6 +68,7 @@ const AddProperty = () => {
BuildingTenancyUpdatedOn: dayjs(),
Fiscals: [],
Evaluations: [],
AgencyId: null,
},
});

Expand Down Expand Up @@ -112,6 +113,7 @@ const AddProperty = () => {
/>
</RadioGroup>
<GeneralInformationForm
agencies={agencyOptions}
defaultLocationValue={undefined}
propertyType={propertyType}
adminAreas={
Expand Down Expand Up @@ -149,7 +151,7 @@ const AddProperty = () => {
onClick={async () => {
const isValid = await formMethods.trigger();
if (isValid && formMethods.getValues()['Location'] != null) {
setShowErrorTest(false);
setShowErrorText(false);
if (propertyType === 'Parcel') {
const formValues = formMethods.getValues();
const addParcel: ParcelAdd = {
Expand All @@ -159,7 +161,6 @@ const AddProperty = () => {
PIN: parseIntOrNull(formValues.PIN),
Postal: formValues.Postal.replace(/ /g, '').toUpperCase(),
PropertyTypeId: 0,
AgencyId: userContext.pimsUser.data.AgencyId,
IsVisibleToOtherAgencies: false,
Fiscals: formValues.Fiscals.map((a) => ({
...a,
Expand Down Expand Up @@ -187,7 +188,6 @@ const AddProperty = () => {
TotalArea: parseFloatOrNull(formValues.TotalArea),
BuildingFloorCount: 0,
PropertyTypeId: 1,
AgencyId: userContext.pimsUser.data.AgencyId,
IsVisibleToOtherAgencies: false,
Fiscals: formValues.Fiscals.map((a) => ({
...a,
Expand All @@ -208,7 +208,7 @@ const AddProperty = () => {
}
} else {
console.log('Error!');
setShowErrorTest(true);
setShowErrorText(true);
}
}}
variant="contained"
Expand Down
33 changes: 5 additions & 28 deletions react-app/src/components/property/PropertyDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ import { parseFloatOrNull, parseIntOrNull, pidFormatter } from '@/utilities/form
import useDataSubmitter from '@/hooks/useDataSubmitter';
import { LookupContext } from '@/contexts/lookupContext';
import { Classification } from '@/hooks/api/useLookupApi';
import { AuthContext } from '@/contexts/authContext';
import { Roles } from '@/constants/roles';
import AutocompleteFormField from '../form/AutocompleteFormField';
import useUserAgencies from '@/hooks/api/useUserAgencies';

interface IParcelInformationEditDialog {
initialValues: Parcel;
Expand All @@ -39,8 +37,8 @@ export const ParcelInformationEditDialog = (props: IParcelInformationEditDialog)
const { initialValues, postSubmit } = props;

const api = usePimsApi();
const { menuItems: agencyOptions } = useUserAgencies();
const { data: lookupData } = useContext(LookupContext);
const { keycloak } = useContext(AuthContext);

const { submit, submitting } = useDataSubmitter(api.parcels.updateParcelById);

Expand Down Expand Up @@ -76,8 +74,6 @@ export const ParcelInformationEditDialog = (props: IParcelInformationEditDialog)
});
}, [initialValues]);

const isAdmin = keycloak.hasRoles([Roles.ADMIN]);

return (
<ConfirmDialog
title={'Edit Parcel Information'}
Expand All @@ -99,6 +95,7 @@ export const ParcelInformationEditDialog = (props: IParcelInformationEditDialog)
<FormProvider {...infoFormMethods}>
<Box display={'flex'} flexDirection={'column'} gap={'1rem'}>
<GeneralInformationForm
agencies={agencyOptions ?? []}
propertyType={'Parcel'}
defaultLocationValue={initialValues?.Location}
adminAreas={
Expand All @@ -116,15 +113,6 @@ export const ParcelInformationEditDialog = (props: IParcelInformationEditDialog)
})) ?? []
}
/>
{isAdmin && (
<AutocompleteFormField
name={'AgencyId'}
label={'Agency'}
options={
lookupData?.Agencies.map((agc) => ({ value: agc.Id, label: agc.Name })) ?? []
}
/>
)}
</Box>
</FormProvider>
</ConfirmDialog>
Expand All @@ -140,9 +128,8 @@ interface IBuildingInformationEditDialog {

export const BuildingInformationEditDialog = (props: IBuildingInformationEditDialog) => {
const api = usePimsApi();
const { menuItems: agencyOptions } = useUserAgencies();
const { data: lookupData } = useContext(LookupContext);
const { keycloak } = useContext(AuthContext);

const { submit, submitting } = useDataSubmitter(api.buildings.updateBuildingById);

const { initialValues, open, onCancel, postSubmit } = props;
Expand Down Expand Up @@ -193,8 +180,6 @@ export const BuildingInformationEditDialog = (props: IBuildingInformationEditDia
});
}, [initialValues]);

const isAdmin = keycloak.hasRoles([Roles.ADMIN]);

return (
<ConfirmDialog
title={'Edit Building Information'}
Expand All @@ -218,6 +203,7 @@ export const BuildingInformationEditDialog = (props: IBuildingInformationEditDia
<FormProvider {...infoFormMethods}>
<Box display={'flex'} flexDirection={'column'} gap={'1rem'}>
<GeneralInformationForm
agencies={agencyOptions}
propertyType={'Building'}
defaultLocationValue={initialValues?.Location}
adminAreas={
Expand All @@ -232,15 +218,6 @@ export const BuildingInformationEditDialog = (props: IBuildingInformationEditDia
constructionOptions={lookupData?.ConstructionTypes as BuildingConstructionType[]}
predominateUseOptions={lookupData?.PredominateUses as BuildingPredominateUse[]}
/>
{isAdmin && (
<AutocompleteFormField
name={'AgencyId'}
label={'Agency'}
options={
lookupData?.Agencies.map((agc) => ({ value: agc.Id, label: agc.Name })) ?? []
}
/>
)}
</Box>
</FormProvider>
</ConfirmDialog>
Expand Down
10 changes: 10 additions & 0 deletions react-app/src/components/property/PropertyForms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ interface IGeneralInformationForm {
propertyType: PropertyType;
defaultLocationValue: GeoPoint | null;
adminAreas: ISelectMenuItem[];
agencies: ISelectMenuItem[];
}

export const GeneralInformationForm = (props: IGeneralInformationForm) => {
Expand Down Expand Up @@ -258,6 +259,15 @@ export const GeneralInformationForm = (props: IGeneralInformationForm) => {
}}
/>
</Grid>
<Grid item xs={12}>
<AutocompleteFormField
allowNestedIndent
required
name={'AgencyId'}
label={'Agency'}
options={props.agencies ?? []}
/>
</Grid>
<Grid item xs={12}>
<ParcelMap
height={'500px'}
Expand Down
45 changes: 45 additions & 0 deletions react-app/src/hooks/api/useUserAgencies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ISelectMenuItem } from '@/components/form/SelectFormField';
import { Roles } from '@/constants/roles';
import { useContext, useEffect, useMemo } from 'react';
import useDataLoader from '../useDataLoader';
import { AuthContext } from '@/contexts/authContext';
import usePimsApi from '../usePimsApi';
import useGroupedAgenciesApi from './useGroupedAgenciesApi';

const useUserAgencies = () => {
const userContext = useContext(AuthContext);
const { ungroupedAgencies, agencyOptions } = useGroupedAgenciesApi();
const api = usePimsApi();
const isAdmin = userContext.keycloak.hasRoles([Roles.ADMIN]);
const { data: userAgencies, refreshData: refreshUserAgencies } = useDataLoader(() =>
api.users.getUsersAgencyIds(userContext.keycloak.user.preferred_username),
);

useEffect(() => {
refreshUserAgencies();
}, [userContext.keycloak]);

const userAgencyObjects = useMemo(() => {
if (ungroupedAgencies && userAgencies) {
return ungroupedAgencies.filter((a) => userAgencies.includes(a.Id));
} else {
return [];
}
}, [ungroupedAgencies, userAgencies]);

const menuItems: ISelectMenuItem[] = useMemo(() => {
if (isAdmin) {
return agencyOptions;
} else if (userAgencyObjects) {
return agencyOptions.filter((agc) =>
userAgencyObjects.some((useragc) => useragc.Id === agc.value),
);
} else {
return [];
}
}, [agencyOptions, userAgencyObjects, isAdmin]);

return { menuItems, userAgencies: userAgencyObjects };
};

export default useUserAgencies;

0 comments on commit 03f78f8

Please sign in to comment.