diff --git a/.github/workflows/permit-service.unit.yaml b/.github/workflows/permit-service.unit.yaml
index 729b12ab61..b351d09fe2 100644
--- a/.github/workflows/permit-service.unit.yaml
+++ b/.github/workflows/permit-service.unit.yaml
@@ -28,8 +28,8 @@ jobs:
env:
DOCKER_BUILDKIT: 1
run: |
- docker compose -f docker-compose.yaml run -e AZURE_API_KEY=testkey haystack coverage run -m pytest
- docker compose -f docker-compose.yaml run -e AZURE_API_KEY=testkey haystack coverage xml
+ docker compose -f docker-compose.yaml run -e AZURE_API_KEY=testkey -e DOCUMENTINTELLIGENCE_API_KEY=testkey haystack coverage run -m pytest
+ docker compose -f docker-compose.yaml run -e AZURE_API_KEY=testkey -e DOCUMENTINTELLIGENCE_API_KEY=testkey haystack coverage xml
sed -i "s/
}
+ message="Assign a Project Lead"
+ description={
Assigning a Project Lead will set the Project Description status
+ to 'Assigned' in Core and 'Submitted' in MineSpace. Please ensure the project
+ is set at the correct status before continuing.
- This project does not have a Project Lead
+ Assign a Project Lead
- Please assign a Project Lead to this project.
+ Assigning a Project Lead will set the Project Description status to 'Assigned' in Core and 'Submitted' in MineSpace. Please ensure the project is set at the correct status before continuing.
diff --git a/services/common/src/components/projects/projectUtils.spec.ts b/services/common/src/components/projects/projectUtils.spec.ts
index f37cf954d5..9e2c750bf3 100644
--- a/services/common/src/components/projects/projectUtils.spec.ts
+++ b/services/common/src/components/projects/projectUtils.spec.ts
@@ -59,9 +59,20 @@ TEST_PARAMETERS.forEach(
expect(result).toBe(true);
});
});
+
+ if (label !== "areDocumentFieldsDisabled") {
+ const noSubmissionMinespaceDisabledStatuses = [...msDisabledStatuses].filter((status) => status !== "ASG");
+ noSubmissionMinespaceDisabledStatuses.forEach((status) => {
+ it(`MS status: ${status} Should return true (disabled) when submission has not occured`, () => {
+ const result = testFunction(SystemFlagEnum.ms, status);
+ expect(result).toBe(true);
+ });
+ })
+ }
+
msDisabledStatuses.forEach((status) => {
- it(`MS status: ${status} Should return true (disabled)`, () => {
- const result = testFunction(SystemFlagEnum.ms, status);
+ it(`MS status: ${status} Should return true (disabled) when submission has occured`, () => {
+ const result = testFunction(SystemFlagEnum.ms, status, true);
expect(result).toBe(true);
});
});
@@ -72,6 +83,7 @@ TEST_PARAMETERS.forEach(
expect(result).toBe(false);
});
});
+
msEnabledStatuses.forEach((status) => {
it(`MS status: ${status} Should return false (enabled)`, () => {
const result = testFunction(SystemFlagEnum.ms, status);
diff --git a/services/common/src/components/projects/projectUtils.ts b/services/common/src/components/projects/projectUtils.ts
index 4f0d47ac3c..d40af6ab12 100644
--- a/services/common/src/components/projects/projectUtils.ts
+++ b/services/common/src/components/projects/projectUtils.ts
@@ -1,27 +1,23 @@
import { PROJECT_STATUS_CODES, SystemFlagEnum } from "@mds/common/constants/enums";
import { memoize } from "lodash";
-export const areFieldsDisabled = memoize((systemFlag: SystemFlagEnum, projectSummaryStatusCode: string) => {
+export const areFieldsDisabled = memoize((systemFlag: SystemFlagEnum, projectSummaryStatusCode: string, confirmationOfSubmission?: boolean) => {
// Return false (enabled) if status = "" => "Not Started"
const isStatusInEnum = (Object).values(PROJECT_STATUS_CODES).includes(projectSummaryStatusCode);
-
if (!isStatusInEnum) return false;
const projectSummaryStatus = projectSummaryStatusCode as PROJECT_STATUS_CODES;
-
const disabledStatuses = [PROJECT_STATUS_CODES.WDN, PROJECT_STATUS_CODES.COM];
-
const enabledStatuses = systemFlag === SystemFlagEnum.core
? [PROJECT_STATUS_CODES.DFT, PROJECT_STATUS_CODES.ASG, PROJECT_STATUS_CODES.UNR, PROJECT_STATUS_CODES.CHR, PROJECT_STATUS_CODES.OHD, PROJECT_STATUS_CODES.SUB]
- : [PROJECT_STATUS_CODES.DFT, PROJECT_STATUS_CODES.CHR];
+ : [PROJECT_STATUS_CODES.DFT, PROJECT_STATUS_CODES.CHR, ...(!confirmationOfSubmission ? [PROJECT_STATUS_CODES.ASG] : [])];
if (disabledStatuses.includes(projectSummaryStatus)) return true;
return !enabledStatuses.includes(projectSummaryStatus);
-
},
(systemFlag: SystemFlagEnum, projectSummaryStatusCode: string) => `${systemFlag}_${projectSummaryStatusCode}`);
-export const areAuthFieldsDisabled = memoize((systemFlag: SystemFlagEnum, projectSummaryStatusCode: string) => {
- const fieldsDisabled = areFieldsDisabled(systemFlag, projectSummaryStatusCode);
+export const areAuthFieldsDisabled = memoize((systemFlag: SystemFlagEnum, projectSummaryStatusCode: string, confirmationOfSubmission?: boolean) => {
+ const fieldsDisabled = areFieldsDisabled(systemFlag, projectSummaryStatusCode, confirmationOfSubmission);
if (fieldsDisabled) return true;
const extraDisabledStatuses = [PROJECT_STATUS_CODES.CHR, PROJECT_STATUS_CODES.UNR];
@@ -29,8 +25,8 @@ export const areAuthFieldsDisabled = memoize((systemFlag: SystemFlagEnum, projec
return authDisabled;
}, (systemFlag: SystemFlagEnum, projectSummaryStatusCode: string) => `${systemFlag}_${projectSummaryStatusCode}`);
-export const areAuthEnvFieldsDisabled = memoize((systemFlag, projectSummaryStatusCode) => {
- const authFieldsDisabled = areAuthFieldsDisabled(systemFlag, projectSummaryStatusCode);
+export const areAuthEnvFieldsDisabled = memoize((systemFlag: SystemFlagEnum, projectSummaryStatusCode: string, confirmationOfSubmission?: boolean) => {
+ const authFieldsDisabled = areAuthFieldsDisabled(systemFlag, projectSummaryStatusCode, confirmationOfSubmission);
if (authFieldsDisabled) return true;
const extraDisabledStatuses = systemFlag === SystemFlagEnum.core
@@ -56,7 +52,6 @@ export const areDocumentFieldsDisabled = memoize((systemFlag: SystemFlagEnum, pr
if (disabledStatuses.includes(projectSummaryStatus)) return true;
return !enabledStatuses.includes(projectSummaryStatus);
-
},
(systemFlag: SystemFlagEnum, projectSummaryStatusCode: string) => `${systemFlag}_${projectSummaryStatusCode}`);
diff --git a/services/common/src/constants/API.ts b/services/common/src/constants/API.ts
index ac24fbbb56..687f524945 100644
--- a/services/common/src/constants/API.ts
+++ b/services/common/src/constants/API.ts
@@ -387,3 +387,6 @@ export const REGIONS_LIST = "/regions";
// App Help
export const APP_HELP = (helpKey: string, params?: { system?: string; help_guid?: string }) =>
`/help/${helpKey}?${queryString.stringify(params)}`;
+
+// User
+export const USER_PROFILE = () => "/users/profile";
diff --git a/services/common/src/constants/networkReducerTypes.ts b/services/common/src/constants/networkReducerTypes.ts
index 6fa906e702..f17f4bfa43 100644
--- a/services/common/src/constants/networkReducerTypes.ts
+++ b/services/common/src/constants/networkReducerTypes.ts
@@ -143,9 +143,6 @@ export enum NetworkReducerTypes {
CREATE_PROJECT_LINKS = "CREATE_PROJECT_LINKS",
DELETE_PROJECT_LINK = "DELETE_PROJECT_LINK",
- // Core Users
- GET_CORE_USERS = "GET_CORE_USERS",
-
// Incidents
CREATE_MINE_INCIDENT = "CREATE_MINE_INCIDENT",
GET_INCIDENTS = "GET_INCIDENTS",
diff --git a/services/common/src/constants/strings.tsx b/services/common/src/constants/strings.tsx
index a950affaa9..7417157392 100755
--- a/services/common/src/constants/strings.tsx
+++ b/services/common/src/constants/strings.tsx
@@ -491,5 +491,6 @@ export const REPORT_FREQUENCY_HASH = {
"Semi-Annually": 6,
"Annually": 12,
"Bi-Annually": 24,
+ "Every 5 Years": 60,
"Not Specified": 0,
};
diff --git a/services/common/src/interfaces/permits/mineReportPermitRequirements.interface.ts b/services/common/src/interfaces/permits/mineReportPermitRequirements.interface.ts
index 8f0656ad18..e405a5509b 100644
--- a/services/common/src/interfaces/permits/mineReportPermitRequirements.interface.ts
+++ b/services/common/src/interfaces/permits/mineReportPermitRequirements.interface.ts
@@ -1,7 +1,9 @@
export interface IMineReportPermitRequirement {
+ report_name: string;
mine_report_permit_requirement_id: number;
cim_or_cpo: string;
ministry_recipient: string[];
permit_condition_id: number;
due_date_period_months: number;
+ initial_due_date: Date;
}
diff --git a/services/common/src/interfaces/projects/projectSummary.interface.ts b/services/common/src/interfaces/projects/projectSummary.interface.ts
index fb3bd5693a..1dbbeef19b 100644
--- a/services/common/src/interfaces/projects/projectSummary.interface.ts
+++ b/services/common/src/interfaces/projects/projectSummary.interface.ts
@@ -64,4 +64,5 @@ export interface IProjectSummaryForm extends Omit (dispatch) => {
- dispatch(request(NetworkReducerTypes.GET_CORE_USERS));
- return CustomAxios()
- .get(ENVIRONMENT.apiUrl + API.CORE_USER, createRequestHeader())
- .then((response) => {
- dispatch(success(NetworkReducerTypes.GET_CORE_USERS));
- dispatch(userActions.storeCoreUserList(response.data));
- })
- .catch(() => dispatch(error(NetworkReducerTypes.GET_CORE_USERS)));
-};
diff --git a/services/common/src/redux/actions/userActions.js b/services/common/src/redux/actions/userActions.js
deleted file mode 100644
index 3611e28d53..0000000000
--- a/services/common/src/redux/actions/userActions.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import * as actionTypes from "@mds/common/constants/actionTypes";
-
-// This file is anticipated to have multiple exports
-// eslint-disable-next-line import/prefer-default-export
-export const storeCoreUserList = (payload) => ({
- type: actionTypes.STORE_CORE_USERS,
- payload,
-});
diff --git a/services/common/src/redux/reducers.js b/services/common/src/redux/reducers.js
index c13cca3a57..249829fc5f 100644
--- a/services/common/src/redux/reducers.js
+++ b/services/common/src/redux/reducers.js
@@ -21,7 +21,6 @@ import searchReducerObject from "./reducers/searchReducer";
import securitiesReducerObject from "./reducers/securitiesReducer";
import staticContentReducerObject from "./reducers/staticContentReducer";
import tailingsReducerObject from "./reducers/tailingsReducer";
-import userReducerObject from "./reducers/userReducer";
import varianceReducerObject from "./reducers/varianceReducer";
import workInformationReducerObject from "./reducers/workInformationReducer";
import verifiableCredentialReducerObject from "./reducers/verifiableCredentialReducer";
@@ -40,7 +39,6 @@ export const permitReducer = permitReducerObject;
export const reportReducer = reportReducerObject;
export const searchReducer = searchReducerObject;
export const staticContentReducer = staticContentReducerObject;
-export const userReducer = userReducerObject;
export const varianceReducer = varianceReducerObject;
export const securitiesReducer = securitiesReducerObject;
export const orgbookReducer = orgbookReducerObject;
diff --git a/services/common/src/redux/reducers/rootReducerShared.ts b/services/common/src/redux/reducers/rootReducerShared.ts
index da492376e8..96a78bc871 100644
--- a/services/common/src/redux/reducers/rootReducerShared.ts
+++ b/services/common/src/redux/reducers/rootReducerShared.ts
@@ -26,7 +26,6 @@ import {
securitiesReducer,
staticContentReducer,
tailingsReducer,
- userReducer,
varianceReducer,
verifiableCredentialReducer,
workInformationReducer,
@@ -37,13 +36,17 @@ import regionsReducer from "@mds/common/redux/slices/regionsSlice";
import complianceCodeReducer, { complianceCodeReducerType } from "../slices/complianceCodesSlice";
import spatialDataReducer, { spatialDataReducerType } from "../slices/spatialDataSlice";
import permitServiceReducer, { permitServiceReducerType } from "../slices/permitServiceSlice";
-import searchConditionCategoriesReducer, { searchConditionCategoriesType } from "../slices/permitConditionCategorySlice";
+import searchConditionCategoriesReducer, {
+ searchConditionCategoriesType,
+} from "../slices/permitConditionCategorySlice";
import helpReducer, { helpReducerType } from "../slices/helpSlice";
const networkReducers = Object.fromEntries(Object.entries(NetworkReducerTypes).map(([key, value]) =>
[NetworkReducerTypes[key], createReducer(networkReducer, value)]
));
+import userReducer, { userReducerType } from "@mds/common/redux/slices/userSlice";
+
export const sharedReducer = {
...activityReducer,
...authenticationReducer,
@@ -67,7 +70,6 @@ export const sharedReducer = {
...securitiesReducer,
...staticContentReducer,
...tailingsReducer,
- ...userReducer,
...varianceReducer,
...verifiableCredentialReducer,
...workInformationReducer,
@@ -81,5 +83,6 @@ export const sharedReducer = {
[permitServiceReducerType]: permitServiceReducer,
[helpReducerType]: helpReducer,
[searchConditionCategoriesType]: searchConditionCategoriesReducer,
+ [userReducerType]: userReducer,
...networkReducers
};
diff --git a/services/common/src/redux/reducers/userReducer.js b/services/common/src/redux/reducers/userReducer.js
deleted file mode 100644
index e6ba7d8f13..0000000000
--- a/services/common/src/redux/reducers/userReducer.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import * as actionTypes from "@mds/common/constants/actionTypes";
-import { USERS } from "@mds/common/constants/reducerTypes";
-
-const initialState = {
- coreUsers: [],
-};
-
-export const userReducer = (state = initialState, action) => {
- switch (action.type) {
- case actionTypes.STORE_CORE_USERS:
- return {
- ...state,
- coreUsers: action.payload.results,
- };
- default:
- return state;
- }
-};
-
-const userReducerObject = {
- [USERS]: userReducer,
-};
-
-export const getCoreUsers = (state) => state[USERS].coreUsers;
-
-export default userReducerObject;
diff --git a/services/common/src/redux/selectors/userSelectors.js b/services/common/src/redux/selectors/userSelectors.js
deleted file mode 100644
index 0cd08bbcc0..0000000000
--- a/services/common/src/redux/selectors/userSelectors.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import { createSelector } from "reselect";
-import * as userReducer from "../reducers/userReducer";
-import { createLabelHash } from "../utils/helpers";
-
-export const { getCoreUsers } = userReducer;
-
-export const getDropdownCoreUsers = createSelector(
- [getCoreUsers],
- (users) =>
- users.map((user) => {
- // the username is prepended with "IDIR\", remove before displaying on UI
- const formattedLabel = user.idir_user_detail.username
- ? user.idir_user_detail.username.replace("IDIR\\", "")
- : "";
- return { value: user.core_user_guid, label: formattedLabel };
- })
-);
-
-export const getCoreUsersHash = createSelector(
- [getDropdownCoreUsers],
- createLabelHash
-);
diff --git a/services/common/src/redux/slices/userSlice.spec.ts b/services/common/src/redux/slices/userSlice.spec.ts
new file mode 100644
index 0000000000..fb45f6b4e4
--- /dev/null
+++ b/services/common/src/redux/slices/userSlice.spec.ts
@@ -0,0 +1,90 @@
+import { configureStore } from "@reduxjs/toolkit";
+import { userReducer, fetchUser, getUser } from "./userSlice"; // Adjust the import path as necessary
+import { ENVIRONMENT, USER_PROFILE } from "@mds/common/constants";
+import CustomAxios from "@mds/common/redux/customAxios";
+
+const showLoadingMock = jest
+ .fn()
+ .mockReturnValue({ type: "SHOW_LOADING", payload: { show: true } });
+const hideLoadingMock = jest
+ .fn()
+ .mockReturnValue({ type: "HIDE_LOADING", payload: { show: false } });
+
+jest.mock("@mds/common/redux/customAxios");
+jest.mock("react-redux-loading-bar", () => ({
+ showLoading: () => showLoadingMock,
+ hideLoading: () => hideLoadingMock,
+}));
+
+describe("userSlice", () => {
+ let store;
+
+ beforeEach(() => {
+ store = configureStore({
+ reducer: {
+ user: userReducer,
+ },
+ });
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe("fetchUser", () => {
+ const mockResponse = {
+ data: {
+ sub: "mock-sub",
+ display_name: "Mock User",
+ email: "mock@example.com",
+ family_name: "MockFamily",
+ given_name: "MockGiven",
+ last_logged_in: "2023-10-01T12:00:00.000Z",
+ },
+ };
+
+ it("should fetch user data successfully", async () => {
+ (CustomAxios as jest.Mock).mockImplementation(() => ({
+ get: jest.fn().mockResolvedValue(mockResponse),
+ }));
+
+ await store.dispatch(fetchUser());
+ const state = store.getState().user;
+
+ // Verify loading state management
+ expect(showLoadingMock).toHaveBeenCalledTimes(1);
+ expect(hideLoadingMock).toHaveBeenCalledTimes(1);
+
+ // Verify state update
+ expect(getUser({ user: state })).toEqual(mockResponse.data);
+ expect(CustomAxios).toHaveBeenCalledWith({ errorToastMessage: "default" });
+ });
+
+ it("should handle API error", async () => {
+ const error = new Error("API Error");
+ (CustomAxios as jest.Mock).mockImplementation(() => ({
+ get: jest.fn().mockRejectedValue(error),
+ }));
+
+ await store.dispatch(fetchUser());
+ const state = store.getState().user;
+
+ // Check user state remains null on error
+ expect(getUser({ user: state })).toBeNull();
+ });
+
+ it("should construct the correct endpoint URL", async () => {
+ const getMock = jest.fn().mockResolvedValue(mockResponse);
+ (CustomAxios as jest.Mock).mockImplementation(() => ({
+ get: getMock,
+ }));
+
+ await store.dispatch(fetchUser());
+
+ expect(getMock).toHaveBeenCalledWith(
+ `${ENVIRONMENT.apiUrl}${USER_PROFILE()}`,
+ expect.any(Object)
+ );
+ });
+ });
+});
diff --git a/services/common/src/redux/slices/userSlice.ts b/services/common/src/redux/slices/userSlice.ts
new file mode 100644
index 0000000000..f3b506bf66
--- /dev/null
+++ b/services/common/src/redux/slices/userSlice.ts
@@ -0,0 +1,53 @@
+import { createAppSlice, rejectHandler } from "@mds/common/redux/createAppSlice";
+import { createRequestHeader } from "@mds/common/redux/utils/RequestHeaders";
+import { hideLoading, showLoading } from "react-redux-loading-bar";
+import CustomAxios from "@mds/common/redux/customAxios";
+import { ENVIRONMENT, USER_PROFILE } from "@mds/common/constants";
+import { IUser } from "@mds/common/interfaces";
+
+export const userReducerType = "user";
+
+interface UserState {
+ user: IUser;
+}
+
+const initialState: UserState = {
+ user: null,
+};
+
+const userSlice = createAppSlice({
+ name: userReducerType,
+ initialState,
+ reducers: (create) => ({
+ fetchUser: create.asyncThunk(
+ async (_: undefined, thunkApi) => {
+ const headers = createRequestHeader();
+ thunkApi.dispatch(showLoading());
+
+ const response = await CustomAxios({
+ errorToastMessage: "default",
+ }).get(`${ENVIRONMENT.apiUrl}${USER_PROFILE()}`, headers);
+
+ thunkApi.dispatch(hideLoading());
+ return response.data;
+ },
+ {
+ fulfilled: (state: UserState, action) => {
+ state.user = action.payload;
+ },
+ rejected: (state: UserState, action) => {
+ rejectHandler(action);
+ },
+ }
+ ),
+ }),
+ selectors: {
+ getUser: (state) => state.user,
+ },
+});
+
+export const { getUser } = userSlice.selectors;
+export const { fetchUser } = userSlice.actions;
+export const userReducer = userSlice.reducer;
+
+export default userReducer;
diff --git a/services/common/src/tests/mocks/dataMocks.tsx b/services/common/src/tests/mocks/dataMocks.tsx
index 8c86e82d24..e0f0432279 100644
--- a/services/common/src/tests/mocks/dataMocks.tsx
+++ b/services/common/src/tests/mocks/dataMocks.tsx
@@ -21,7 +21,6 @@ import {
VC_CONNECTION_STATES,
VC_CRED_ISSUE_STATES,
} from "@mds/common/constants";
-import { PermitExtraction } from "@mds/common/redux/slices/permitServiceSlice";
export const createMockHeader = () => ({
headers: {
@@ -8967,3 +8966,12 @@ export const HELP_GUIDE_MS = {
},
],
};
+
+export const USER = {
+ sub: '1234',
+ displayName: 'Testerson, Test EMLI:EX',
+ email: 'test@test.ca',
+ family_name: 'Testerson',
+ given_name: 'Test',
+ last_logged_in: '2022-08-08T20:59:01.482461+00:00',
+}
diff --git a/services/core-api/Dockerfile b/services/core-api/Dockerfile
index 09f1ddc377..2396cc900d 100644
--- a/services/core-api/Dockerfile
+++ b/services/core-api/Dockerfile
@@ -9,6 +9,7 @@ RUN apt-get install build-essential -y
# Install the requirements
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
+RUN pip install watchdog
COPY . .
diff --git a/services/core-api/app/api/mines/permits/permit_conditions/models/permit_conditions.py b/services/core-api/app/api/mines/permits/permit_conditions/models/permit_conditions.py
index a4312a69d8..bd5c41366c 100644
--- a/services/core-api/app/api/mines/permits/permit_conditions/models/permit_conditions.py
+++ b/services/core-api/app/api/mines/permits/permit_conditions/models/permit_conditions.py
@@ -1,54 +1,68 @@
-import uuid
-from datetime import datetime
-
from app.api.utils.field_template import FieldTemplate
from app.api.utils.list_lettering_helpers import num_to_letter, num_to_roman
from app.api.utils.models_mixins import AuditMixin, Base, SoftDeleteMixin
from app.extensions import db
-from marshmallow import fields, validate
-from sqlalchemy.dialects.postgresql import UUID
+from marshmallow import fields
+from sqlalchemy.dialects.postgresql import JSONB, UUID
from sqlalchemy.ext.hybrid import hybrid_property
-from sqlalchemy.orm import backref, validates
+from sqlalchemy.orm import backref
from sqlalchemy.schema import FetchedValue
class PermitConditions(SoftDeleteMixin, AuditMixin, Base):
- __tablename__ = 'permit_conditions'
+ __tablename__ = "permit_conditions"
class _ModelSchema(Base._ModelSchema):
permit_condition_id = fields.Integer(dump_only=True)
permit_condition_guid = fields.UUID(dump_only=True)
condition_category_code = FieldTemplate(
- field=fields.String, one_of='PermitConditionCategory')
- condition_type_code = FieldTemplate(field=fields.String, one_of='PermitConditionType')
+ field=fields.String, one_of="PermitConditionCategory"
+ )
+ condition_type_code = FieldTemplate(
+ field=fields.String, one_of="PermitConditionType"
+ )
permit_condition_id = db.Column(db.Integer, primary_key=True)
permit_amendment_id = db.Column(
- db.Integer, db.ForeignKey('permit_amendment.permit_amendment_id'), nullable=False)
- permit_amendment = db.relationship('PermitAmendment', lazy='select', back_populates='conditions')
+ db.Integer,
+ db.ForeignKey("permit_amendment.permit_amendment_id"),
+ nullable=False,
+ )
+ permit_amendment = db.relationship(
+ "PermitAmendment", lazy="select", back_populates="conditions"
+ )
permit_condition_guid = db.Column(UUID(as_uuid=True), server_default=FetchedValue())
condition = db.Column(db.String, nullable=False)
condition_category_code = db.Column(
db.String,
- db.ForeignKey('permit_condition_category.condition_category_code'),
- nullable=False)
-
- condition_category = db.relationship('PermitConditionCategory', lazy='select')
+ db.ForeignKey("permit_condition_category.condition_category_code"),
+ nullable=False,
+ )
+
+ condition_category = db.relationship("PermitConditionCategory", lazy="select")
condition_type_code = db.Column(
- db.String, db.ForeignKey('permit_condition_type.condition_type_code'), nullable=False)
- parent_permit_condition_id = db.Column(db.Integer,
- db.ForeignKey('permit_conditions.permit_condition_id'))
+ db.String,
+ db.ForeignKey("permit_condition_type.condition_type_code"),
+ nullable=False,
+ )
+ parent_permit_condition_id = db.Column(
+ db.Integer, db.ForeignKey("permit_conditions.permit_condition_id")
+ )
display_order = db.Column(db.Integer, nullable=False)
- _step = db.Column('step', db.String, nullable=True)
+
+ meta = db.Column(JSONB(astext_type=db.Text()), nullable=True)
+
+ _step = db.Column("step", db.String, nullable=True)
__versioned__ = {}
all_sub_conditions = db.relationship(
- 'PermitConditions',
- lazy='joined',
- order_by='asc(PermitConditions.display_order)',
- backref=backref('parent', remote_side=[permit_condition_id]))
+ "PermitConditions",
+ lazy="joined",
+ order_by="asc(PermitConditions.display_order)",
+ backref=backref("parent", remote_side=[permit_condition_id]),
+ )
@hybrid_property
def sub_conditions(self):
@@ -68,64 +82,80 @@ def step(self):
if self._step:
# Format the first level with a trailing dot - A. B. C. and the rest with () - (a), (i), (ii)
if depth == 0:
- return f'{self._step}.'
- return f'({self._step})'
- if self._step == '':
- return ''
+ return f"{self._step}."
+ return f"({self._step})"
+ if self._step == "":
+ return ""
step_format = depth % 3
if step_format == 0:
- return str(self.display_order) + '.'
+ return str(self.display_order) + "."
elif step_format == 1:
- return num_to_letter(self.display_order) + '.'
+ return num_to_letter(self.display_order) + "."
elif step_format == 2:
- return num_to_roman(self.display_order) + '.'
+ return num_to_roman(self.display_order) + "."
def __repr__(self):
- return '' % (self.permit_condition_id,
- self.permit_condition_guid, self.display_order)
+ return "" % (
+ self.permit_condition_id,
+ self.permit_condition_guid,
+ self.display_order,
+ )
@classmethod
- def create(cls,
- condition_category_code,
- condition_type_code,
- permit_amendment_id,
- condition,
- display_order,
- sub_conditions,
- parent=None):
+ def create(
+ cls,
+ condition_category_code,
+ condition_type_code,
+ permit_amendment_id,
+ condition,
+ display_order,
+ sub_conditions,
+ parent=None,
+ ):
permit_condition = cls(
condition_category_code=condition_category_code,
condition_type_code=condition_type_code,
permit_amendment_id=permit_amendment_id,
condition=condition,
display_order=display_order,
- parent=parent)
+ parent=parent,
+ )
permit_condition.save(commit=False)
for condition in sub_conditions:
- PermitConditions.create(condition.condition_category_code,
- condition.condition_type_code, permit_amendment_id,
- condition.condition, condition.display_order,
- condition.sub_conditions, permit_condition)
+ PermitConditions.create(
+ condition.condition_category_code,
+ condition.condition_type_code,
+ permit_amendment_id,
+ condition.condition,
+ condition.display_order,
+ condition.sub_conditions,
+ permit_condition,
+ )
return permit_condition
-
@classmethod
def delete_all_by_permit_amendment_id(cls, permit_amendment_id, commit=False):
- parent_conditions = cls.query.filter_by(
- permit_amendment_id=permit_amendment_id,
- parent_permit_condition_id=None,
- deleted_ind=False).order_by(cls.display_order).all()
+ parent_conditions = (
+ cls.query.filter_by(
+ permit_amendment_id=permit_amendment_id,
+ parent_permit_condition_id=None,
+ deleted_ind=False,
+ )
+ .order_by(cls.display_order)
+ .all()
+ )
for condition in parent_conditions:
condition.delete_condition(commit=commit)
if commit:
condition.save()
-
def delete_condition(self, commit=False):
if self.all_sub_conditions is not None:
- subconditions = [c for c in self.all_sub_conditions if c.deleted_ind == False]
+ subconditions = [
+ c for c in self.all_sub_conditions if c.deleted_ind == False
+ ]
if len(subconditions) > 0:
for item in subconditions:
item.deleted_ind = True
@@ -134,25 +164,32 @@ def delete_condition(self, commit=False):
item.save()
self.deleted_ind = True
-
@classmethod
def find_all_by_permit_amendment_id(cls, permit_amendment_id):
- return cls.query.filter_by(
- permit_amendment_id=permit_amendment_id,
- parent_permit_condition_id=None,
- deleted_ind=False).order_by(cls.display_order).all()
+ return (
+ cls.query.filter_by(
+ permit_amendment_id=permit_amendment_id,
+ parent_permit_condition_id=None,
+ deleted_ind=False,
+ )
+ .order_by(cls.display_order)
+ .all()
+ )
@classmethod
def find_by_permit_condition_guid(cls, permit_condition_guid):
return cls.query.filter_by(
- permit_condition_guid=permit_condition_guid, deleted_ind=False).first()
+ permit_condition_guid=permit_condition_guid, deleted_ind=False
+ ).first()
@classmethod
def find_by_permit_condition_id(cls, permit_condition_id):
return cls.query.filter_by(
- permit_condition_id=permit_condition_id, deleted_ind=False).first()
+ permit_condition_id=permit_condition_id, deleted_ind=False
+ ).first()
@classmethod
def find_by_condition_category_code(cls, condition_category_code):
return cls.query.filter_by(
- condition_category_code=condition_category_code, deleted_ind=False).all()
+ condition_category_code=condition_category_code, deleted_ind=False
+ ).all()
diff --git a/services/core-api/app/api/mines/permits/permit_extraction/create_permit_condition_report_requirement.py b/services/core-api/app/api/mines/permits/permit_extraction/create_permit_condition_report_requirement.py
new file mode 100644
index 0000000000..e24f32ed19
--- /dev/null
+++ b/services/core-api/app/api/mines/permits/permit_extraction/create_permit_condition_report_requirement.py
@@ -0,0 +1,141 @@
+from typing import Optional
+
+from app.api.mines.reports.models.mine_report_permit_requirement import (
+ MineReportPermitRequirement,
+)
+from dateutil.parser import parse
+from flask import current_app
+
+from .models.permit_condition_result import PermitConditionResult
+
+
+def create_permit_condition_report_requirement(
+ task, condition: PermitConditionResult, condition_id
+) -> Optional[MineReportPermitRequirement]:
+ """
+ Creates a MineReportPermitRequirement based on permit condition details.
+ Args:
+ task: Task object containing permit amendment information
+ condition (PermitConditionResult): Permit condition details
+ condition_id: ID of the permit condition
+ Returns:
+ MineReportPermitRequirement: Created requirement object, or None if report not required
+ """
+
+ meta = condition.meta or {}
+ questions = meta.get("questions", [])
+
+ require_report = False
+ recurring = False
+ frequency = None
+ mention_chief_inspector = False
+ mention_chief_permitting_officer = False
+ initial_due_date = None
+ report_name = None
+
+ for q in questions:
+ key = q.get("question_key")
+ answer = q.get("answer")
+ if key == "require_report":
+ require_report = answer
+ elif key == "due_date":
+ initial_due_date = answer # Parse date if necessary
+ elif key == "recurring":
+ recurring = answer
+ elif key == "frequency":
+ frequency = answer
+ elif key == "mention_chief_inspector":
+ mention_chief_inspector = answer
+ elif key == "mention_chief_permitting_officer":
+ mention_chief_permitting_officer = answer
+ elif key == "report_name":
+ report_name = answer
+
+ if not require_report:
+ return None
+
+ initial_due_date = _parse_initial_due_date(condition_id, initial_due_date)
+
+ # Determine cim_or_cpo based on mentions
+ cim_or_cpo = _parse_cim_cpo(
+ mention_chief_inspector, mention_chief_permitting_officer
+ )
+
+ # Calculate due_date_period_months based on frequency
+ due_date_period_months = _parse_due_date_period(recurring, frequency)
+
+ # Create the MineReportPermitRequirement
+ mine_report_permit_requirement = MineReportPermitRequirement(
+ report_name=report_name,
+ permit_condition_id=condition_id,
+ permit_amendment_id=task.permit_amendment.permit_amendment_id,
+ cim_or_cpo=cim_or_cpo,
+ due_date_period_months=due_date_period_months or 0,
+ initial_due_date=initial_due_date,
+ ministry_recipient=None, # Not specified in permits themselves.
+ )
+
+ return mine_report_permit_requirement
+
+
+def _parse_due_date_period(recurring, frequency):
+ due_date_period_months = None
+ if recurring and frequency:
+ frequency_mapping = {
+ "monthly": 1,
+ "per month": 1,
+ "every month": 1,
+ "quarterly": 3,
+ "every quarter": 3,
+ "semiannually": 6,
+ "semiannual": 6,
+ "every six months": 6,
+ "twice yearly": 6,
+ "annually": 12,
+ "yearly": 12,
+ "annual": 12,
+ "per year": 12,
+ "every year": 12,
+ "biannually": 24,
+ "biannual": 24,
+ "asneeded": 0,
+ "as needed": 0,
+ "as required": 0,
+ "5 years": 60,
+ "every 5 years": 60,
+ "five years": 60,
+ "every five years": 60,
+ }
+ # Clean frequency string by removing spaces and special characters before lookup
+ frequency = "".join(
+ e for e in frequency.lower() if e.isalnum() or e.isspace()
+ ).strip()
+ frequency = " ".join(frequency.split())
+ due_date_period_months = frequency_mapping.get(frequency)
+ return due_date_period_months
+
+
+def _parse_cim_cpo(mention_chief_inspector, mention_chief_permitting_officer):
+ cim_or_cpo = None
+ if mention_chief_inspector and mention_chief_permitting_officer:
+ cim_or_cpo = "BOTH"
+ elif mention_chief_inspector:
+ cim_or_cpo = "CIM"
+ elif mention_chief_permitting_officer:
+ cim_or_cpo = "CPO"
+ return cim_or_cpo
+
+
+def _parse_initial_due_date(condition_id, initial_due_date):
+ if initial_due_date == "":
+ initial_due_date = None
+
+ if initial_due_date:
+ try:
+ initial_due_date = parse(initial_due_date)
+ except ValueError:
+ current_app.logger.error(
+ f"Could not parse due date for condition {condition_id}: {initial_due_date}"
+ )
+ initial_due_date = None
+ return initial_due_date
diff --git a/services/core-api/app/api/mines/permits/permit_extraction/create_permit_conditions.py b/services/core-api/app/api/mines/permits/permit_extraction/create_permit_conditions.py
index 8cddcf9180..f72b6e70a9 100644
--- a/services/core-api/app/api/mines/permits/permit_extraction/create_permit_conditions.py
+++ b/services/core-api/app/api/mines/permits/permit_extraction/create_permit_conditions.py
@@ -1,6 +1,5 @@
import uuid
-from difflib import SequenceMatcher
-from typing import List, Optional
+from typing import Optional
from app.api.mines.permits.permit_amendment.models.permit_amendment import (
PermitAmendment,
@@ -17,6 +16,9 @@
from app.extensions import db
from flask import current_app
+from .create_permit_condition_report_requirement import (
+ create_permit_condition_report_requirement,
+)
from .models.permit_condition_result import (
CreatePermitConditionsResult,
PermitConditionResult,
@@ -24,15 +26,16 @@
indentation_type_code_mapping = {
0: None,
- 1: 'SEC',
- 2: 'CON',
- 3: 'LIS',
- 4: 'LIS',
- 5: 'LIS',
+ 1: "SEC",
+ 2: "CON",
+ 3: "LIS",
+ 4: "LIS",
+ 5: "LIS",
}
# For conditions that don't match any category, put them in a "Terms and conditions" category
-DEFAULT_CATEGORY_TEXT = 'Terms and Conditions'
+DEFAULT_CATEGORY_TEXT = "Terms and Conditions"
+
def create_permit_conditions_from_task(task: PermitExtractionTask):
"""
@@ -40,67 +43,93 @@ def create_permit_conditions_from_task(task: PermitExtractionTask):
"""
result = task.task_result
last_condition_id_by_hierarchy = {}
+ display_order_by_parent = {}
current_category = None
+ try:
+ result = CreatePermitConditionsResult.model_validate(result)
- result = CreatePermitConditionsResult.model_validate(result)
-
- has_category = any([condition.is_top_level_section for condition in result.conditions])
-
- conditions = result.conditions
- if not has_category:
- top_level_section = PermitConditionResult(
- section='A',
- condition_text=DEFAULT_CATEGORY_TEXT
+ has_category = any(
+ [condition.is_top_level_section for condition in result.conditions]
)
- for c in conditions:
- c.set_section(top_level_section)
- conditions = [top_level_section] + conditions
- num_categories = 0
+ conditions = result.conditions
+ if not has_category:
+ top_level_section = PermitConditionResult(
+ section="A", condition_text=DEFAULT_CATEGORY_TEXT
+ )
+ for c in conditions:
+ c.set_section(top_level_section)
+ conditions = [top_level_section] + conditions
- default_section = None
+ num_categories = 0
- for idx, condition in enumerate(conditions):
- if condition.is_top_level_section:
- section_category = _create_permit_condition_category(
- condition=condition,
- permit_amendment=task.permit_amendment,
- display_order=num_categories,
- step=condition.step
- )
- if condition.condition_text == DEFAULT_CATEGORY_TEXT:
- default_section = section_category
- current_category = section_category
- num_categories += 1
- else:
- parent = _determine_parent(condition, last_condition_id_by_hierarchy)
- type_code = _map_condition_to_type_code(condition)
-
- title_cond = None
-
- if not current_category and not default_section:
- default_section = _create_permit_condition_category(
- condition=PermitConditionResult(
- section='A',
- condition_text=DEFAULT_CATEGORY_TEXT
- ),
+ default_section = None
+
+ for idx, condition in enumerate(conditions):
+ if condition.is_top_level_section:
+ section_category = _create_permit_condition_category(
+ condition=condition,
permit_amendment=task.permit_amendment,
display_order=num_categories,
- step='A'
+ step=condition.step,
+ )
+ if condition.condition_text == DEFAULT_CATEGORY_TEXT:
+ default_section = section_category
+ current_category = section_category
+ num_categories += 1
+ else:
+ parent = _determine_parent(condition, last_condition_id_by_hierarchy)
+ type_code = _map_condition_to_type_code(condition)
+
+ parent_id = parent.permit_condition_id if parent else None
+
+ if parent_id not in display_order_by_parent:
+ display_order_by_parent[parent_id] = 0
+ display_order_by_parent[parent_id] += 1
+ current_display_order = display_order_by_parent[parent_id]
+
+ title_cond = None
+
+ if not current_category and not default_section:
+ default_section = _create_permit_condition_category(
+ condition=PermitConditionResult(
+ section="A", condition_text=DEFAULT_CATEGORY_TEXT
+ ),
+ permit_amendment=task.permit_amendment,
+ display_order=num_categories,
+ step="A",
+ )
+
+ category_code = current_category or default_section
+
+ if condition.condition_title:
+ title_cond = _create_title_condition(
+ task,
+ category_code,
+ condition,
+ parent,
+ current_display_order,
+ type_code,
+ )
+
+ parent_condition_id = _get_parent_condition_id(title_cond, parent)
+ cond = _create_permit_condition(
+ task,
+ category_code,
+ condition,
+ parent_condition_id,
+ current_display_order,
+ type_code,
)
- category_code = current_category or default_section
- if condition.condition_title:
- title_cond = _create_title_condition(task, category_code, condition, parent, idx, type_code)
-
- parent_condition_id = _get_parent_condition_id(title_cond, parent)
- cond = _create_permit_condition(task, category_code, condition, parent_condition_id, idx, type_code)
+ hierarchy_key = ".".join(condition.numbering_structure)
+ last_condition_id_by_hierarchy[hierarchy_key] = cond
- hierarchy_key = ".".join(condition.numbering_structure)
- last_condition_id_by_hierarchy[hierarchy_key] = cond
- db.session.commit()
+ db.session.commit()
+ except:
+ db.session.rollback()
+ raise
-
def _map_condition_to_type_code(condition: PermitConditionResult):
"""
@@ -109,15 +138,22 @@ def _map_condition_to_type_code(condition: PermitConditionResult):
Example: ['A', '1', '', '', ''] would have an indentation of 2 -> type code is 'CON'
Example: ['A', '', '', '', ''] would have an indentation of 1 -> type code is 'SEC'
"""
- indentation = next((i-1 for i, x in enumerate(condition.numbering_structure) if x == ''), 0)
+ indentation = next(
+ (i - 1 for i, x in enumerate(condition.numbering_structure) if x == ""), 0
+ )
type_code = indentation_type_code_mapping[indentation]
-
+
if not type_code:
- current_app.logger.error(f"Could not determine type code for condition {condition}")
+ current_app.logger.error(
+ f"Could not determine type code for condition {condition}"
+ )
- return type_code or 'LIS'
+ return type_code or "LIS"
-def _create_title_condition(task, current_category, condition, parent, idx, type_code) -> PermitConditionResult:
+
+def _create_title_condition(
+ task, current_category, condition, parent, idx, type_code
+) -> PermitConditionResult:
condition = PermitConditions(
permit_amendment_id=task.permit_amendment.permit_amendment_id,
permit_condition_guid=uuid.uuid4(),
@@ -126,6 +162,7 @@ def _create_title_condition(task, current_category, condition, parent, idx, type
condition_type_code=type_code,
parent_permit_condition_id=parent.permit_condition_id if parent else None,
display_order=idx,
+ meta=condition.meta,
_step=condition.step,
)
@@ -133,7 +170,10 @@ def _create_title_condition(task, current_category, condition, parent, idx, type
db.session.flush() # This assigns an ID to title_cond without committing the transaction
return condition
-def _get_parent_condition_id(title_cond: PermitConditionResult, parent: PermitConditionResult) -> Optional[str]:
+
+def _get_parent_condition_id(
+ title_cond: PermitConditionResult, parent: PermitConditionResult
+) -> Optional[str]:
if title_cond:
# If the condition has a title, the parent is the title condition
return title_cond.permit_condition_id
@@ -142,7 +182,10 @@ def _get_parent_condition_id(title_cond: PermitConditionResult, parent: PermitCo
else:
return None
-def _create_permit_condition(task, current_category, condition, parent_condition_id, idx, type_code) -> PermitConditions:
+
+def _create_permit_condition(
+ task, current_category, condition, parent_condition_id, idx, type_code
+) -> PermitConditions:
condition = PermitConditions(
permit_amendment_id=task.permit_amendment.permit_amendment_id,
permit_condition_guid=uuid.uuid4(),
@@ -151,15 +194,29 @@ def _create_permit_condition(task, current_category, condition, parent_condition
condition_type_code=type_code,
parent_permit_condition_id=parent_condition_id,
display_order=idx,
- _step=condition.step if not condition.condition_title else '', # If the condition has a title, the parent is the title condition, which has the numbering associated with it already
+ meta=condition.meta,
+ _step=(
+ condition.step if not condition.condition_title else ""
+ ), # If the condition has a title, the parent is the title condition, which has the numbering associated with it already
)
db.session.add(condition)
+
db.session.flush() # This assigns an ID to cond without committing the transaction
+ report_requirement = create_permit_condition_report_requirement(
+ task, condition, condition.permit_condition_id
+ )
+
+ if report_requirement:
+ db.session.add(report_requirement)
+ db.session.flush()
+
return condition
-def _determine_parent(condition: PermitConditionResult, last_condition_id_by_number_structure) -> Optional[PermitConditionResult]:
+def _determine_parent(
+ condition: PermitConditionResult, last_condition_id_by_number_structure
+) -> Optional[PermitConditionResult]:
"""
Determine the parent ID based on the hierarchy.
@@ -170,14 +227,22 @@ def _determine_parent(condition: PermitConditionResult, last_condition_id_by_num
parent_number_structure = [item for item in number_structure if item][:-1]
if len(parent_number_structure) < len(number_structure):
- parent_number_structure += [''] * (len(number_structure) - len(parent_number_structure))
+ parent_number_structure += [""] * (
+ len(number_structure) - len(parent_number_structure)
+ )
parent_key = ".".join(parent_number_structure)
parent = last_condition_id_by_number_structure.get(parent_key)
return parent
-def _create_permit_condition_category(condition: PermitConditionResult, permit_amendment: PermitAmendment, display_order: int, step: str) -> Optional[str]:
+
+def _create_permit_condition_category(
+ condition: PermitConditionResult,
+ permit_amendment: PermitAmendment,
+ display_order: int,
+ step: str,
+) -> Optional[str]:
"""
Finds the matching PermitConditionCategory code for the given condition based on the title or text it contains.
@@ -192,19 +257,21 @@ def _create_permit_condition_category(condition: PermitConditionResult, permit_a
Args:
condition_categories: List of PermitConditionCategory objects
condition: Condition object
-
+
"""
- text = condition.condition_title if condition.condition_title else condition.condition_text
+ text = (
+ condition.condition_title
+ if condition.condition_title
+ else condition.condition_text
+ )
cat = PermitConditionCategory.create(
condition_category_code=str(uuid.uuid4()),
description=text,
display_order=display_order,
permit_amendment_id=permit_amendment.permit_amendment_id,
- step=step
+ step=step,
)
return cat.condition_category_code
-
-
diff --git a/services/core-api/app/api/mines/permits/permit_extraction/models/permit_condition_result.py b/services/core-api/app/api/mines/permits/permit_extraction/models/permit_condition_result.py
index cb8a80a32e..7a0d165f0a 100644
--- a/services/core-api/app/api/mines/permits/permit_extraction/models/permit_condition_result.py
+++ b/services/core-api/app/api/mines/permits/permit_extraction/models/permit_condition_result.py
@@ -12,6 +12,7 @@ class PermitConditionResult(BaseModel):
subsubclause: Optional[str] = None
condition_title: Optional[str] = None
condition_text: str
+ meta: Optional[dict] = None
@computed_field
def numbering_structure(self) -> List[str]:
diff --git a/services/core-api/app/api/mines/reports/models/mine_report_permit_requirement.py b/services/core-api/app/api/mines/reports/models/mine_report_permit_requirement.py
index cf2b336740..eb319232d1 100644
--- a/services/core-api/app/api/mines/reports/models/mine_report_permit_requirement.py
+++ b/services/core-api/app/api/mines/reports/models/mine_report_permit_requirement.py
@@ -2,12 +2,11 @@
from enum import Enum
from typing import Optional
+from app.api.utils.models_mixins import AuditMixin, Base, SoftDeleteMixin
+from app.extensions import db
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.schema import FetchedValue
-from app.api.utils.models_mixins import Base, AuditMixin, SoftDeleteMixin
-from app.extensions import db
-
class CimOrCpo(str, Enum):
CIM = "CIM"
@@ -34,6 +33,8 @@ class MineReportPermitRequirement(SoftDeleteMixin, Base, AuditMixin):
mine_report_permit_requirement_id: int = db.Column(db.Integer, primary_key=True, server_default=FetchedValue())
due_date_period_months: int = db.Column(db.Integer, nullable=False)
initial_due_date: Optional[date] = db.Column(db.Date, nullable=True)
+ report_name = db.Column(db.String(512), nullable=True)
+
active_ind: bool = db.Column(db.Boolean, nullable=False, server_default=FetchedValue())
cim_or_cpo: Optional[CimOrCpo] = db.Column(db.Enum(CimOrCpo, name='cim_or_cpo_type'), nullable=True)
ministry_recipient: Optional[list[OfficeDestination]] = db.Column(
@@ -67,6 +68,7 @@ def get_all(cls) -> list["MineReportPermitRequirement"]:
@classmethod
def create(cls,
+ report_name: Optional[str],
due_date_period_months: int,
initial_due_date: date,
cim_or_cpo: Optional[CimOrCpo],
@@ -75,6 +77,7 @@ def create(cls,
permit_amendment_id: int) -> "MineReportPermitRequirement":
mine_report_permit_requirement = cls(
+ report_name=report_name,
due_date_period_months=due_date_period_months,
initial_due_date=initial_due_date,
cim_or_cpo=cim_or_cpo,
diff --git a/services/core-api/app/api/mines/reports/resources/mine_report_permit_requirement.py b/services/core-api/app/api/mines/reports/resources/mine_report_permit_requirement.py
index 323db4b4cd..2388e06b2c 100644
--- a/services/core-api/app/api/mines/reports/resources/mine_report_permit_requirement.py
+++ b/services/core-api/app/api/mines/reports/resources/mine_report_permit_requirement.py
@@ -1,74 +1,85 @@
from datetime import datetime
-from flask import current_app
-from werkzeug.exceptions import NotFound, BadRequest
-
from app.api.mines.mine.models.mine import Mine
-from app.api.mines.permits.permit.models.permit import Permit
-from app.api.mines.permits.permit_amendment.models.permit_amendment import PermitAmendment
+from app.api.mines.permits.permit_amendment.models.permit_amendment import (
+ PermitAmendment,
+)
from app.api.mines.permits.permit_conditions.models import PermitConditions
+from app.api.mines.reports.models.mine_report_permit_requirement import (
+ CimOrCpo,
+ MineReportPermitRequirement,
+)
from app.api.mines.response_models import MINE_REPORT_PERMIT_REQUIREMENT
-from app.api.utils.access_decorators import requires_any_of, EDIT_REPORT
-from app.extensions import api
-
-from flask_restx import Resource
-
-from app.api.mines.reports.models.mine_report_permit_requirement import CimOrCpo, MineReportPermitRequirement
+from app.api.utils.access_decorators import EDIT_REPORT, requires_any_of
from app.api.utils.custom_reqparser import CustomReqparser
from app.api.utils.resources_mixins import UserMixin
+from app.extensions import api
+from flask import current_app
+from flask_restx import Resource
+from werkzeug.exceptions import BadRequest, NotFound
class MineReportPermitRequirementResource(Resource, UserMixin):
parser = CustomReqparser()
- parser.add_argument('due_date_period_months', type=int, location='json')
- parser.add_argument('initial_due_date', type=lambda x: datetime.strptime(x, '%Y-%m-%d') if x else None, location='json')
- parser.add_argument('cim_or_cpo', type=str, location='json')
- parser.add_argument('ministry_recipient', type=list, location='json')
- parser.add_argument('permit_condition_id', type=int, location='json')
- parser.add_argument('permit_amendment_id', type=int, location='json')
+ parser.add_argument("due_date_period_months", type=int, location="json")
+ parser.add_argument(
+ "initial_due_date",
+ type=lambda x: datetime.strptime(x, "%Y-%m-%d") if x else None,
+ location="json",
+ )
+ parser.add_argument("cim_or_cpo", type=str, location="json")
+ parser.add_argument("ministry_recipient", type=list, location="json")
+ parser.add_argument("permit_condition_id", type=int, location="json")
+ parser.add_argument("permit_amendment_id", type=int, location="json")
@api.expect(parser)
- @api.doc(description='creates a new mine report permit requirement')
+ @api.doc(description="creates a new mine report permit requirement")
@api.marshal_with(MINE_REPORT_PERMIT_REQUIREMENT, code=201)
@requires_any_of([EDIT_REPORT])
def post(self, mine_guid):
- current_app.logger.debug('CREATING REQUIREMENT')
+ current_app.logger.debug("CREATING REQUIREMENT")
data = self.parser.parse_args()
mine = Mine.find_by_mine_guid(mine_guid)
if not mine:
- raise NotFound('Mine not found')
+ raise NotFound("Mine not found")
- permit_amendment_id = data.get('permit_amendment_id')
- permit_amendment = PermitAmendment.find_by_permit_amendment_id(permit_amendment_id)
+ permit_amendment_id = data.get("permit_amendment_id")
+ permit_amendment = PermitAmendment.find_by_permit_amendment_id(
+ permit_amendment_id
+ )
if permit_amendment is None:
- raise NotFound('Permit not found')
+ raise NotFound("Permit not found")
if permit_amendment:
permit_amendment._context_mine = mine
if permit_amendment.mine_guid != mine.mine_guid:
- raise BadRequest('The permit must be associated with the selected mine.')
+ raise BadRequest(
+ "The permit must be associated with the selected mine."
+ )
- permit_condition_id = data.get('permit_condition_id')
- permit_condition = PermitConditions.find_by_permit_condition_id(permit_condition_id)
+ permit_condition_id = data.get("permit_condition_id")
+ permit_condition = PermitConditions.find_by_permit_condition_id(
+ permit_condition_id
+ )
if permit_condition is None:
- raise NotFound('Permit Condition not found')
+ raise NotFound("Permit Condition not found")
- cim_or_cpo = data.get('cim_or_cpo')
- if cim_or_cpo == 'NONE':
+ cim_or_cpo = data.get("cim_or_cpo")
+ if cim_or_cpo == "NONE":
cim_or_cpo = None
else:
cim_or_cpo = CimOrCpo(cim_or_cpo)
mine_report_permit_requirement = MineReportPermitRequirement.create(
- due_date_period_months=data.get('due_date_period_months'),
- initial_due_date=data.get('initial_due_date'),
+ report_name=data.get("report_name"),
+ due_date_period_months=data.get("due_date_period_months"),
+ initial_due_date=data.get("initial_due_date"),
cim_or_cpo=cim_or_cpo,
- ministry_recipient=data.get('ministry_recipient'),
+ ministry_recipient=data.get("ministry_recipient"),
permit_condition_id=permit_condition_id,
permit_amendment_id=permit_amendment_id,
)
return mine_report_permit_requirement, 201
-
diff --git a/services/core-api/app/api/mines/response_models.py b/services/core-api/app/api/mines/response_models.py
index cbd117ff97..1a54e52bbb 100644
--- a/services/core-api/app/api/mines/response_models.py
+++ b/services/core-api/app/api/mines/response_models.py
@@ -252,6 +252,7 @@ def format(self, value):
MINE_REPORT_PERMIT_REQUIREMENT = api.model(
'MineReportPermitRequirement', {
+ 'report_name': fields.String,
'mine_report_permit_requirement_id': fields.Integer,
'due_date_period_months': fields.Integer,
'initial_due_date': fields.Date,
@@ -895,7 +896,8 @@ def format(self, value):
'parent_permit_condition_id': fields.Integer,
'sub_conditions': fields.List(PermitCondition),
'step': fields.String,
- 'display_order': fields.Integer
+ 'display_order': fields.Integer,
+ 'meta': fields.Raw,
})
PERMIT_CONDITION_TEMPLATE_MODEL = api.model('PermitConditionTemplate', {
diff --git a/services/core-api/app/api/projects/project_summary/models/project_summary.py b/services/core-api/app/api/projects/project_summary/models/project_summary.py
index 5cc169a715..543938d59a 100644
--- a/services/core-api/app/api/projects/project_summary/models/project_summary.py
+++ b/services/core-api/app/api/projects/project_summary/models/project_summary.py
@@ -1045,9 +1045,9 @@ def update(self,
# Update simple properties.
# If we assign a project lead update status to Assigned and vice versa Submitted.
- if project_lead_party_guid and project.project_lead_party_guid is None:
+ if project_lead_party_guid and project.project_lead_party_guid is None and self.status_code == status_code:
self.status_code = "ASG"
- elif project_lead_party_guid is None and project.project_lead_party_guid:
+ elif project_lead_party_guid is None and project.project_lead_party_guid and self.status_code == status_code:
self.status_code = "SUB"
else:
self.status_code = status_code
@@ -1327,7 +1327,7 @@ def send_project_summary_email(self, mine, message) -> None:
'COM': [PERM_RECL_EMAIL, project_lead_email]
}
- send_ms_email = self.status_code != "DFT"
+ send_ms_email = self.status_code != "DFT" and self.status_code != "ASG"
emli_recipients = emli_emails.get(self.status_code)
cc = [MDS_EMAIL]
diff --git a/services/core-api/app/api/projects/project_summary/resources/project_summary.py b/services/core-api/app/api/projects/project_summary/resources/project_summary.py
index 6afa4a97f5..ee9302e108 100644
--- a/services/core-api/app/api/projects/project_summary/resources/project_summary.py
+++ b/services/core-api/app/api/projects/project_summary/resources/project_summary.py
@@ -18,6 +18,7 @@
from app.api.activity.utils import trigger_notification
from app.api.projects.project.project_util import notify_file_updates
from decimal import Decimal
+from app.api.activity.models.activity_notification import ActivityRecipients
PAGE_DEFAULT = 1
PER_PAGE_DEFAULT = 25
@@ -206,7 +207,7 @@ def put(self, project_guid, project_summary_guid):
project = Project.find_by_project_guid(project_guid)
data = self.parser.parse_args()
is_historic = data.get('is_historic')
-
+ activity_recipients = ActivityRecipients.all_users
project_summary_validation = project_summary.validate_project_summary(data, is_historic)
if any(project_summary_validation[i] != [] for i in project_summary_validation):
current_app.logger.error(f'Project Summary schema validation failed with errors: {project_summary_validation}')
@@ -280,6 +281,7 @@ def put(self, project_guid, project_summary_guid):
if project_summary.status_code == 'ASG':
message = f'{project.project_title} for {project.mine_name} has been assigned'
+ activity_recipients = ActivityRecipients.core_users
if project_summary.status_code == 'CHR':
message = f'Changes have been requested by the ministry for {project.project_title} at {project.mine_name}'
@@ -295,12 +297,12 @@ def put(self, project_guid, project_summary_guid):
if project_summary.status_code == 'COM':
message = f'The status of the project description {project.project_title} for {project.mine_name} has been completed'
-
- project_summary.send_project_summary_email(mine, message)
- trigger_notification(message, ActivityType.major_mine_desc_submitted, project.mine, 'ProjectSummary',
- project_summary.project_summary_guid, extra_data)
-
+ if message != '':
+ project_summary.send_project_summary_email(mine, message)
+ trigger_notification(message, ActivityType.major_mine_desc_submitted, project.mine, 'ProjectSummary',
+ project_summary.project_summary_guid, extra_data, None, activity_recipients)
+
# notify on document updates
if has_new_documents:
notify_file_updates(project, mine, project_summary.status_code)
diff --git a/services/core-api/app/api/users/models/user.py b/services/core-api/app/api/users/models/user.py
new file mode 100644
index 0000000000..e5483d1c88
--- /dev/null
+++ b/services/core-api/app/api/users/models/user.py
@@ -0,0 +1,76 @@
+from datetime import datetime
+from pytz import utc
+from app.api.utils.models_mixins import SoftDeleteMixin, Base, AuditMixin
+from app.extensions import db
+
+
+class User(SoftDeleteMixin, AuditMixin, Base):
+ __tablename__ = "user"
+ __versioned__ = {
+ 'exclude': ['last_logged_in']
+ }
+
+ sub = db.Column(db.String(), primary_key=True)
+ email = db.Column(db.String(), nullable=False)
+ given_name = db.Column(db.String(), nullable=False)
+ family_name = db.Column(db.String(), nullable=False)
+ display_name = db.Column(db.String(), nullable=False)
+ idir_username = db.Column(db.String(), nullable=False)
+ identity_provider = db.Column(db.String(), nullable=False)
+ idir_user_guid = db.Column(db.String(), nullable=False)
+ last_logged_in = db.Column(db.DateTime(), nullable=False)
+
+ def __repr__(self):
+ return f'{self.__class__.__name__} {self.sub}'
+
+ @classmethod
+ def find_by_sub(cls, sub):
+ return cls.query.filter_by(sub=sub).filter_by(deleted_ind=False).first()
+
+ @classmethod
+ def create(cls, **kwargs):
+ kwargs['create_user'] = 'system'
+ kwargs['create_timestamp'] = datetime.now(tz=utc)
+ kwargs['update_user'] = 'system'
+ kwargs['update_timestamp'] = datetime.now(tz=utc)
+
+ user = cls(**kwargs)
+ db.session.add(user)
+ db.session.commit()
+ return user
+
+ @classmethod
+ def create_or_update_user(cls, **kwargs):
+ sub = kwargs.get("sub")
+ existing_user = cls.find_by_sub(sub)
+
+ if existing_user:
+ # `last_logged_in` should always be updated
+ existing_user.last_logged_in = datetime.now(tz=utc)
+
+ # Update other fields only if there are changes
+ for key, value in kwargs.items():
+ if key not in ["sub", "last_logged_in"] and hasattr(existing_user, key):
+ current_value = getattr(existing_user, key)
+ if current_value != value:
+ setattr(existing_user, key, value)
+
+ db.session.commit()
+ result = existing_user
+
+ else:
+ # Create a new user if one does not already exist
+ new_user = cls.create(**kwargs)
+ result = new_user
+
+ return result
+
+ def update(self, **kwargs):
+ # Add/Update attributes of the User instance
+ for key, value in kwargs.items():
+ if hasattr(self, key):
+ setattr(self, key, value)
+
+ # Commit changes to the database
+ db.session.commit()
+ return self
diff --git a/services/core-api/app/api/users/namespace.py b/services/core-api/app/api/users/namespace.py
index d36376b055..89adad1b32 100644
--- a/services/core-api/app/api/users/namespace.py
+++ b/services/core-api/app/api/users/namespace.py
@@ -1,4 +1,4 @@
-from app.api.users.resources.user import UserResource
+from app.api.users.resources.user_resource import UserResource
from flask_restx import Namespace
from app.api.users.core.resources.core_user import CoreUserListResource, CoreUserResource
@@ -14,4 +14,4 @@
api.add_resource(MinespaceUserMineResource, '/minespace//mines/')
api.add_resource(CoreUserListResource, '/core')
api.add_resource(CoreUserResource, '/core/')
-api.add_resource(UserResource, '/me')
+api.add_resource(UserResource, '/profile')
diff --git a/services/core-api/app/api/users/resources/user.py b/services/core-api/app/api/users/resources/user.py
deleted file mode 100644
index 877de7162f..0000000000
--- a/services/core-api/app/api/users/resources/user.py
+++ /dev/null
@@ -1,25 +0,0 @@
-import uuid
-
-from flask import request, current_app
-from app.api.utils.include.user_info import User
-from flask_restx import Resource, reqparse
-from werkzeug.exceptions import BadRequest, NotFound, InternalServerError
-
-from app.extensions import api, db
-from app.api.utils.access_decorators import requires_role_mine_admin
-from app.api.utils.resources_mixins import UserMixin
-
-from app.api.users.minespace.models.minespace_user import MinespaceUser
-from app.api.users.minespace.models.minespace_user_mine import MinespaceUserMine
-from app.api.users.response_models import MINESPACE_USER_MODEL
-from app.api.mines.mine.models.mine import Mine
-
-
-class UserResource(Resource, UserMixin):
- def get(self):
- user = User()
-
- user_info = user.get_user_raw_info()
- user_info['preferred_username'] = user.get_user_username()
-
- return user_info
diff --git a/services/core-api/app/api/users/resources/user_resource.py b/services/core-api/app/api/users/resources/user_resource.py
new file mode 100644
index 0000000000..d6df5cd4bf
--- /dev/null
+++ b/services/core-api/app/api/users/resources/user_resource.py
@@ -0,0 +1,44 @@
+from datetime import datetime
+
+from flask import current_app
+from flask_restx import Resource
+from pytz import utc
+
+from app.api.users.models.user import User
+from app.api.users.response_models import USER_MODEL
+from app.api.utils.access_decorators import requires_role_view_all
+from app.api.utils.include.user_info import User as UserUtils
+from app.api.utils.resources_mixins import UserMixin
+from app.extensions import api
+
+
+class UserResource(Resource, UserMixin):
+ @api.doc(description='Update and retrieve the user from the token')
+ @requires_role_view_all
+ @api.marshal_with(USER_MODEL, code=200)
+ def get(self):
+ user_util = UserUtils()
+
+ user_info = user_util.get_user_raw_info()
+
+ try:
+ # Extract token information
+ user_data = {
+ "sub": user_info.get("sub"),
+ "email": user_info.get("email", ""),
+ "given_name": user_info.get("given_name", ""),
+ "family_name": user_info.get("family_name", ""),
+ "display_name": user_info.get("display_name", ""),
+ "idir_username": user_info.get("idir_username", ""),
+ "identity_provider": user_info.get("identity_provider", ""),
+ "idir_user_guid": user_info.get("idir_user_guid", ""),
+ "last_logged_in": datetime.now(tz=utc),
+ }
+
+ user = User.create_or_update_user(**user_data)
+
+ return user
+
+ except Exception as e:
+ current_app.logger.error(f'Error: {str(e)}')
+ return {"message": f"An error occurred: {str(e)}"}, 500
\ No newline at end of file
diff --git a/services/core-api/app/api/users/response_models.py b/services/core-api/app/api/users/response_models.py
index 8b2d9f70fb..c89fc15356 100644
--- a/services/core-api/app/api/users/response_models.py
+++ b/services/core-api/app/api/users/response_models.py
@@ -8,3 +8,14 @@
'email_or_username': fields.String,
'mines': fields.List(fields.String),
})
+
+USER_MODEL = api.model(
+ 'User', {
+ 'sub': fields.String,
+ 'email': fields.String,
+ 'given_name': fields.String,
+ 'family_name': fields.String,
+ 'display_name': fields.String,
+ 'last_logged_in': fields.DateTime,
+ }
+)
diff --git a/services/core-api/app/api/utils/include/user_info.py b/services/core-api/app/api/utils/include/user_info.py
index 219aa61380..1987af5eda 100644
--- a/services/core-api/app/api/utils/include/user_info.py
+++ b/services/core-api/app/api/utils/include/user_info.py
@@ -9,8 +9,14 @@
"username": "mds",
"preferred_username": "mds",
"email": "test-email",
- "given_name": "test-given-name",
- "client_roles": []
+ "client_roles": [],
+ "sub": 'bce4ffa4b74741c79afa82287bfffbc8@idir',
+ "given_name": 'Test',
+ "family_name": 'Testerson',
+ "display_name": 'Testerson, Test: EMLI:EX',
+ "idir_username": "mds",
+ "identity_provider": "idir",
+ "idir_user_guid": "BCE4FFA4B63641C79AFA82287BFFFBC8",
}
diff --git a/services/core-api/celery_dev.sh b/services/core-api/celery_dev.sh
new file mode 100755
index 0000000000..72b18def44
--- /dev/null
+++ b/services/core-api/celery_dev.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+# This script is used to start the Celery worker in development mode with auto-reload on file changes.
+# The production entrypoint can be found in celery.sh
+# -n is the number of tasks to consume
+# -A is the name of the app to run
+# -Q is the name of the queue to consume from
+# -concurrency is the number of child processes processing the queue
+# -B is the Beat
+# --scheduler is the scheduler class to use
+# -s Path to the schedule database.
+# -E Enable sending task-related events that can be captured by monitors
+# --pidfile is the location of the pid file
+
+
+cd /app || exit
+
+watchmedo auto-restart --directory=./ --pattern=*.py --recursive -- celery -A app.tasks.celery_entrypoint worker -n core_tasks@%h -Q core_tasks --loglevel=${CELERY_LOG_LEVEL:-info} --concurrency=1 -B --scheduler redbeat.RedBeatScheduler -E
diff --git a/services/core-api/tests/auth/test_expected_auth.py b/services/core-api/tests/auth/test_expected_auth.py
index 248f722c46..04bdc4e233 100644
--- a/services/core-api/tests/auth/test_expected_auth.py
+++ b/services/core-api/tests/auth/test_expected_auth.py
@@ -1,6 +1,7 @@
import pytest
from app.api.mines.reports.resources.mine_report_permit_requirement import MineReportPermitRequirementResource
+from app.api.users.resources.user_resource import UserResource
from app.api.utils.access_decorators import VIEW_ALL, MINE_EDIT, MINE_ADMIN, MINESPACE_PROPONENT, EDIT_PARTY, \
EDIT_PERMIT, EDIT_STANDARD_PERMIT_CONDITIONS, EDIT_DO, EDIT_VARIANCE, EDIT_REPORT, EDIT_SUBMISSIONS, EDIT_SECURITIES, \
GIS, EDIT_PROJECT_SUMMARIES, EDIT_INCIDENTS, EDIT_TSF, EDIT_INFORMATION_REQUIREMENTS_TABLE, EDIT_REQUIREMENTS, \
@@ -121,6 +122,7 @@
(MinespaceUserResource, 'get', [MINE_ADMIN]), (MinespaceUserResource, 'delete', [MINE_ADMIN]),
(MinespaceUserMineListResource, 'post', [MINE_ADMIN]),
(MinespaceUserMineResource, 'delete', [MINE_ADMIN]),
+ (UserResource, 'get', [VIEW_ALL]),
(NOWActivityTypeResource, 'get', [VIEW_ALL]),
(NOWApplicationImportResource, 'post', [EDIT_PERMIT]),
(NOWApplicationListResource, 'get', [VIEW_ALL, GIS]),
diff --git a/services/core-api/tests/factories.py b/services/core-api/tests/factories.py
index ecd06c99ff..6ad49d5c82 100644
--- a/services/core-api/tests/factories.py
+++ b/services/core-api/tests/factories.py
@@ -14,6 +14,7 @@
from app.api.mines.reports.models.mine_report_permit_requirement import MineReportPermitRequirement, OfficeDestination
from app.api.projects.project_link.models.project_link import ProjectLink
from app.api.projects.project_summary.models.project_summary_ministry_comment import ProjectSummaryMinistryComment
+from app.api.users.models.user import User
from app.extensions import db
from tests.status_code_gen import *
from app.api.mines.documents.models.mine_document import MineDocument
@@ -783,6 +784,20 @@ class Meta:
last_logon = TODAY
idir_user_detail = factory.RelatedFactory('tests.factories.IdirUserDetailFactory', 'core_user')
+class UserFactory(BaseFactory):
+ class Meta:
+ model = User
+
+ sub = factory.Faker('uuid4')
+ email = factory.Faker('email')
+ given_name = factory.Faker('first_name')
+ family_name = factory.Faker('last_name')
+ display_name = factory.LazyAttribute(lambda obj: f"{obj.given_name} {obj.family_name} EMLI:EX")
+ idir_username = factory.Faker('user_name')
+ identity_provider = factory.Faker('random_element', elements=['idir', 'bceid'])
+ idir_user_guid = factory.Faker('uuid4')
+ last_logged_in = factory.LazyFunction(lambda: datetime.now(tz=utc))
+
class IdirUserDetailFactory(BaseFactory):
diff --git a/services/core-api/tests/permits/permit_extraction/test_create_permit_condition_report_requirement.py b/services/core-api/tests/permits/permit_extraction/test_create_permit_condition_report_requirement.py
new file mode 100644
index 0000000000..4bb0b3d245
--- /dev/null
+++ b/services/core-api/tests/permits/permit_extraction/test_create_permit_condition_report_requirement.py
@@ -0,0 +1,120 @@
+from datetime import datetime
+from unittest.mock import MagicMock, patch
+
+import pytest
+from app.api.mines.permits.permit_extraction.create_permit_condition_report_requirement import (
+ create_permit_condition_report_requirement,
+)
+from app.api.mines.permits.permit_extraction.models.permit_condition_result import (
+ PermitConditionResult,
+)
+
+
+@pytest.fixture
+def mock_task():
+ task = MagicMock()
+ task.permit_amendment.permit_amendment_id = "test-amendment-id"
+ return task
+
+
+def test_create_report_requirement_with_no_report_required(mock_task):
+ condition = PermitConditionResult(
+ condition_text="Test condition text",
+ meta={"questions": [{"question_key": "require_report", "answer": False}]},
+ )
+
+ result = create_permit_condition_report_requirement(mock_task, condition, "test-id")
+ assert result is None
+
+
+def test_create_report_requirement_basic(mock_task):
+ condition = PermitConditionResult(
+ condition_text="Test condition text",
+ meta={
+ "questions": [
+ {"question_key": "require_report", "answer": True},
+ {"question_key": "report_name", "answer": "Test Report"},
+ {"question_key": "due_date", "answer": "2023-12-31"},
+ {"question_key": "recurring", "answer": True},
+ {"question_key": "frequency", "answer": "monthly"},
+ {"question_key": "mention_chief_inspector", "answer": True},
+ {"question_key": "mention_chief_permitting_officer", "answer": False},
+ ]
+ },
+ )
+
+ result = create_permit_condition_report_requirement(mock_task, condition, "test-id")
+ assert result is not None
+ assert result.report_name == "Test Report"
+ assert result.permit_condition_id == "test-id"
+ assert result.permit_amendment_id == "test-amendment-id"
+ assert result.cim_or_cpo == "CIM"
+ assert result.due_date_period_months == 1
+ assert result.initial_due_date == datetime(2023, 12, 31)
+
+
+def test_create_report_requirement_both_cim_cpo(mock_task):
+ condition = PermitConditionResult(
+ condition_text="Test condition text",
+ meta={
+ "questions": [
+ {"question_key": "require_report", "answer": True},
+ {"question_key": "mention_chief_inspector", "answer": True},
+ {"question_key": "mention_chief_permitting_officer", "answer": True},
+ ]
+ },
+ )
+
+ result = create_permit_condition_report_requirement(mock_task, condition, "test-id")
+ assert result is not None
+ assert result.cim_or_cpo == "BOTH"
+
+
+def test_create_report_requirement_various_frequencies(mock_task):
+ test_cases = [
+ ("annually", 12),
+ ("quarterly", 3),
+ ("semiannually", 6),
+ ("as needed", 0),
+ ("every 5 years", 60),
+ ]
+
+ for frequency, expected_months in test_cases:
+ condition = PermitConditionResult(
+ condition_text="Test condition text",
+ meta={
+ "questions": [
+ {"question_key": "require_report", "answer": True},
+ {"question_key": "recurring", "answer": True},
+ {"question_key": "frequency", "answer": frequency},
+ ]
+ },
+ )
+
+ result = create_permit_condition_report_requirement(
+ mock_task, condition, "test-id"
+ )
+ assert result is not None
+ assert result.due_date_period_months == expected_months
+
+
+@patch(
+ "app.api.mines.permits.permit_extraction.create_permit_condition_report_requirement.current_app"
+)
+def test_create_report_requirement_invalid_date(
+ mock_current_app, mock_task, test_client
+):
+ condition = PermitConditionResult(
+ condition_text="Test condition text",
+ meta={
+ "questions": [
+ {"question_key": "require_report", "answer": True},
+ {"question_key": "due_date", "answer": "invalid-date"},
+ ]
+ },
+ )
+
+ result = create_permit_condition_report_requirement(mock_task, condition, "test-id")
+ assert result is not None
+ assert result.initial_due_date is None
+ mock_current_app.logger.error.assert_called_once()
diff --git a/services/core-api/tests/permits/permit_extraction/test_create_permit_conditions_from_task.py b/services/core-api/tests/permits/permit_extraction/test_create_permit_conditions_from_task.py
index b6091f6a73..5652a8a6d5 100644
--- a/services/core-api/tests/permits/permit_extraction/test_create_permit_conditions_from_task.py
+++ b/services/core-api/tests/permits/permit_extraction/test_create_permit_conditions_from_task.py
@@ -8,6 +8,9 @@
from app.api.mines.permits.permit_extraction.models.permit_extraction_task import (
PermitExtractionTask,
)
+from app.api.mines.reports.models.mine_report_permit_requirement import (
+ MineReportPermitRequirement,
+)
from tests.factories import create_mine_and_permit
@@ -61,7 +64,7 @@ def permit_conditions(permit_amendment):
"section": "A",
"paragraph": "1",
"subparagraph": "1",
- "clause": 'a',
+ "clause": "a",
"subclause": None,
"subsubclause": None,
"condition_title": None,
@@ -71,8 +74,8 @@ def permit_conditions(permit_amendment):
"section": "A",
"paragraph": "1",
"subparagraph": "1",
- "clause": 'a',
- "subclause": 'b',
+ "clause": "a",
+ "subclause": "b",
"subsubclause": None,
"condition_title": "This condition has a title",
"condition_text": "This is a subclause",
@@ -129,56 +132,101 @@ def permit_conditions(permit_amendment):
return permit_conditions
-def test_create_permit_conditions_from_task(permit_conditions, permit_amendment, db_session):
+
+def test_create_permit_conditions_from_task(
+ permit_conditions, permit_amendment, db_session
+):
### General Section
gen_cat = permit_conditions[0]
# Top level sections are not created as a PermitCondition. They are mapped to a PermitConditionCategory instead
- assert permit_conditions[0].permit_amendment_id == permit_amendment.permit_amendment_id
+ assert (
+ permit_conditions[0].permit_amendment_id == permit_amendment.permit_amendment_id
+ )
assert permit_conditions[0].condition_category_code != "GEC"
assert permit_conditions[0].condition_category.description == "General"
assert permit_conditions[0].condition == "This is a paragraph"
- assert permit_conditions[0].condition_type_code == "SEC" # First level is a section
+ assert permit_conditions[0].condition_type_code == "SEC" # First level is a section
assert permit_conditions[0].parent_permit_condition_id is None
assert permit_conditions[0]._step == "1"
- assert permit_conditions[1].permit_amendment_id == permit_amendment.permit_amendment_id
- assert permit_conditions[1].condition_category_code == gen_cat.condition_category_code
+ assert (
+ permit_conditions[1].permit_amendment_id == permit_amendment.permit_amendment_id
+ )
+ assert (
+ permit_conditions[1].condition_category_code == gen_cat.condition_category_code
+ )
assert permit_conditions[1].condition == "This is a subparagraph"
- assert permit_conditions[1].condition_type_code == "CON" # Second level is a condition
- assert permit_conditions[1].parent_permit_condition_id == gen_cat.permit_condition_id
+ assert (
+ permit_conditions[1].condition_type_code == "CON"
+ ) # Second level is a condition
+ assert (
+ permit_conditions[1].parent_permit_condition_id == gen_cat.permit_condition_id
+ )
assert permit_conditions[1]._step == "1"
- assert permit_conditions[2].permit_amendment_id == permit_amendment.permit_amendment_id
- assert permit_conditions[2].condition_category_code == gen_cat.condition_category_code
+ assert (
+ permit_conditions[2].permit_amendment_id == permit_amendment.permit_amendment_id
+ )
+ assert (
+ permit_conditions[2].condition_category_code == gen_cat.condition_category_code
+ )
assert permit_conditions[2].condition == "This is a clause"
- assert permit_conditions[2].condition_type_code == "LIS" # Third level on is a list item
- assert permit_conditions[2].parent_permit_condition_id == permit_conditions[1].permit_condition_id
+ assert (
+ permit_conditions[2].condition_type_code == "LIS"
+ ) # Third level on is a list item
+ assert (
+ permit_conditions[2].parent_permit_condition_id
+ == permit_conditions[1].permit_condition_id
+ )
assert permit_conditions[2]._step == "a"
# When a condition both has a title and text, they are created as two conditions, with the text as a child of the title
# Note: This was an assumption made to make the display more accurately reflect the PDF. May need a revision.
- assert permit_conditions[3].permit_amendment_id == permit_amendment.permit_amendment_id
- assert permit_conditions[3].condition_category_code == gen_cat.condition_category_code
+ assert (
+ permit_conditions[3].permit_amendment_id == permit_amendment.permit_amendment_id
+ )
+ assert (
+ permit_conditions[3].condition_category_code == gen_cat.condition_category_code
+ )
assert permit_conditions[3].condition == "This condition has a title"
assert permit_conditions[3].condition_type_code == "LIS"
- assert permit_conditions[3].parent_permit_condition_id == permit_conditions[2].permit_condition_id
+ assert (
+ permit_conditions[3].parent_permit_condition_id
+ == permit_conditions[2].permit_condition_id
+ )
assert permit_conditions[3]._step == "b"
- assert permit_conditions[4].permit_amendment_id == permit_amendment.permit_amendment_id
- assert permit_conditions[4].condition_category_code == gen_cat.condition_category_code
+ assert (
+ permit_conditions[4].permit_amendment_id == permit_amendment.permit_amendment_id
+ )
+ assert (
+ permit_conditions[4].condition_category_code == gen_cat.condition_category_code
+ )
assert permit_conditions[4].condition == "This is a subclause"
assert permit_conditions[4].condition_type_code == "LIS"
- assert permit_conditions[4].parent_permit_condition_id == permit_conditions[3].permit_condition_id
- assert permit_conditions[4]._step == "" # This is a child of the title condition - which in the PDFs do not have a step
+ assert (
+ permit_conditions[4].parent_permit_condition_id
+ == permit_conditions[3].permit_condition_id
+ )
+ assert (
+ permit_conditions[4]._step == ""
+ ) # This is a child of the title condition - which in the PDFs do not have a step
-def test_creates_general_conditions_as_unique_for_permit_amendment(permit_conditions, permit_amendment, db_session):
+def test_creates_general_conditions_as_unique_for_permit_amendment(
+ permit_conditions, permit_amendment, db_session
+):
# Protection of Land and Watercourses Section
- assert permit_conditions[5].permit_amendment_id == permit_amendment.permit_amendment_id
+ assert (
+ permit_conditions[5].permit_amendment_id == permit_amendment.permit_amendment_id
+ )
assert permit_conditions[5].condition_category_code != "ELC"
- assert permit_conditions[5].condition_category.description == "Protection of Land and Watercourses"
+ assert (
+ permit_conditions[5].condition_category.description
+ == "Protection of Land and Watercourses"
+ )
assert permit_conditions[5].condition == "Another paragraph"
assert permit_conditions[5].condition_type_code == "SEC"
assert permit_conditions[5].parent_permit_condition_id is None
@@ -187,9 +235,169 @@ def test_creates_general_conditions_as_unique_for_permit_amendment(permit_condit
def test_creates_custom_conditions(permit_conditions, permit_amendment, db_session):
# Can handle custom sections
- assert permit_conditions[6].permit_amendment_id == permit_amendment.permit_amendment_id
+ assert (
+ permit_conditions[6].permit_amendment_id == permit_amendment.permit_amendment_id
+ )
assert permit_conditions[6].condition_category.description == "This is just a test"
assert permit_conditions[6].condition == "A test paragraph"
assert permit_conditions[6].parent_permit_condition_id is None
- assert permit_conditions[6].condition_category.permit_amendment_id == permit_amendment.permit_amendment_id
- assert permit_conditions[6]._step == "1"
\ No newline at end of file
+ assert (
+ permit_conditions[6].condition_category.permit_amendment_id
+ == permit_amendment.permit_amendment_id
+ )
+ assert permit_conditions[6]._step == "1"
+
+
+def test_report_requirement_exists(permit_amendment, db_session):
+ task = PermitExtractionTask(
+ task_result={
+ "conditions": [
+ {
+ "section": "D",
+ "paragraph": "1",
+ "subparagraph": None,
+ "clause": None,
+ "subclause": None,
+ "subsubclause": None,
+ "condition_title": None,
+ "condition_text": "This is a report requirement",
+ "meta": {
+ "questions": [
+ {"question_key": "require_report", "answer": True},
+ {"question_key": "report_name", "answer": "Test Report"},
+ {"question_key": "due_date", "answer": "2023-12-31"},
+ {"question_key": "recurring", "answer": True},
+ {"question_key": "frequency", "answer": "monthly"},
+ {"question_key": "mention_chief_inspector", "answer": True},
+ {
+ "question_key": "mention_chief_permitting_officer",
+ "answer": False,
+ },
+ ]
+ },
+ }
+ ]
+ },
+ permit_amendment=permit_amendment,
+ )
+ create_permit_conditions_from_task(task)
+ report_requirements = MineReportPermitRequirement.query.all()
+ assert len(report_requirements) == 1
+ assert report_requirements[0].report_name == "Test Report"
+
+
+def test_nested_display_order(test_client, db_session, permit_amendment):
+ # Create a task with nested conditions
+ task = PermitExtractionTask(
+ permit_amendment=permit_amendment,
+ task_result={
+ "conditions": [
+ {
+ "section": "A",
+ "condition_text": "General",
+ },
+ {
+ "section": "A",
+ "paragraph": "1",
+ "condition_text": "First sub-condition",
+ },
+ {
+ "section": "A",
+ "paragraph": "2",
+ "condition_text": "Second sub-condition",
+ },
+ {
+ "section": "A",
+ "paragraph": "2",
+ "subparagraph": "a",
+ "condition_text": "Nested condition",
+ },
+ {
+ "section": "A",
+ "paragraph": "2",
+ "subparagraph": "a",
+ "clause": "i",
+ "condition_text": "Clause",
+ },
+ {
+ "section": "A",
+ "paragraph": "2",
+ "subparagraph": "a",
+ "clause": "ii",
+ "condition_text": "Clause2",
+ },
+ {"section": "B", "condition_text": "Another section"},
+ ]
+ },
+ )
+
+ create_permit_conditions_from_task(task)
+
+ # Query conditions and verify display orders
+ conditions = db_session.query(PermitConditions).all()
+
+ # Create a map of conditions by their text for easier testing
+ conditions_map = {c.condition: c for c in conditions}
+
+ assert len(conditions_map.keys()) == 5
+
+ # Verify sub-conditions are top-level (sections are "Categories", so not part of the tree)
+ assert conditions_map["First sub-condition"].parent_permit_condition_id is None
+ assert conditions_map["First sub-condition"].display_order == 1
+
+ assert conditions_map["Second sub-condition"].parent_permit_condition_id is None
+ assert conditions_map["Second sub-condition"].display_order == 2
+
+ assert conditions_map["Nested condition"].display_order == 1
+
+ assert conditions_map["Clause"].display_order == 1
+ assert conditions_map["Clause2"].display_order == 2
+
+ # Verify nested condition is a child of the second sub-condition
+ assert (
+ conditions_map["Nested condition"].parent_permit_condition_id
+ == conditions_map["Second sub-condition"].permit_condition_id
+ )
+ assert (
+ conditions_map["Clause"].parent_permit_condition_id
+ == conditions_map["Nested condition"].permit_condition_id
+ )
+ assert (
+ conditions_map["Clause2"].parent_permit_condition_id
+ == conditions_map["Nested condition"].permit_condition_id
+ )
+
+
+def test_display_order_with_titles(test_client, db_session, permit_amendment):
+ task = PermitExtractionTask(
+ permit_amendment=permit_amendment,
+ task_result={
+ "conditions": [
+ {
+ "section": "A",
+ "condition_text": "Firstt secion",
+ },
+ {
+ "section": "A",
+ "paragraph": "1",
+ "condition_text": "Sub 1",
+ },
+ {
+ "section": "A",
+ "paragraph": "2",
+ "condition_text": "Sub 2",
+ },
+ ]
+ },
+ )
+
+ create_permit_conditions_from_task(task)
+
+ conditions = db_session.query(PermitConditions).all()
+ conditions_map = {c.condition: c for c in conditions}
+
+ assert len(conditions_map.keys()) == 2
+
+ # Verify display orders with titles
+ assert conditions_map["Sub 1"].display_order == 1
+ assert conditions_map["Sub 2"].display_order == 2
diff --git a/services/core-api/tests/projects/project_summaries/resources/test_project_summary_emails.py b/services/core-api/tests/projects/project_summaries/resources/test_project_summary_emails.py
index bb1271c200..187e708d77 100644
--- a/services/core-api/tests/projects/project_summaries/resources/test_project_summary_emails.py
+++ b/services/core-api/tests/projects/project_summaries/resources/test_project_summary_emails.py
@@ -40,21 +40,10 @@ def test_sub_to_asg(mock_send_template_email, test_client, db_session, auth_head
"message": f'{updated_project_summary_title} for {project_summary.project.mine_name} has been assigned',
"core_project_summary_link": f'{Config.CORE_WEB_URL}/pre-applications/{project_summary.project.project_guid}/overview'
}
-
- minespace_context = {
- "mine": {
- "mine_name": project_summary.mine_name,
- "mine_no": project_summary.project.mine_no,
- },
- "message": f'{updated_project_summary_title} for {project_summary.project.mine_name} has been assigned',
- "minespace_project_summary_link": f'{Config.MINESPACE_PROD_URL}/projects/{project_summary.project.project_guid}/overview',
- "ema_auth_link": f'{Config.EMA_AUTH_LINK}',
- }
# ARGS: subject, recipients, body, context, cc (ignore comparison with ANY)
emli_call = call(f'Project Description Notification for {project_summary.mine_name}', ANY, ANY, emli_context, cc=[MDS_EMAIL])
- ms_call = call(f'Project Description Notification for {project_summary.mine_name}', ANY, ANY, minespace_context, cc=[MDS_EMAIL])
assert put_resp.status_code == 200
- calls = [emli_call, ms_call]
+ calls = [emli_call]
mock_send_template_email.assert_has_calls(calls, True)
\ No newline at end of file
diff --git a/services/core-api/tests/projects/project_summaries/resources/test_project_summary_resource.py b/services/core-api/tests/projects/project_summaries/resources/test_project_summary_resource.py
index 66511a3f71..c2aa2af272 100644
--- a/services/core-api/tests/projects/project_summaries/resources/test_project_summary_resource.py
+++ b/services/core-api/tests/projects/project_summaries/resources/test_project_summary_resource.py
@@ -67,7 +67,7 @@ def test_delete_project_summary_bad_status_code(test_client, db_session, auth_he
def test_update_project_summary_assign_project_lead(test_client, db_session, auth_headers):
'''Assigning a project lead will change status code to ASG'''
project = ProjectFactory(project_summary=0)
- project_summary = ProjectSummaryFactory(project=project)
+ project_summary = ProjectSummaryFactory(project=project, set_status_code='DFT')
party = PartyFactory(person=True)
data = {}
diff --git a/services/core-api/tests/users/models/test_user.py b/services/core-api/tests/users/models/test_user.py
new file mode 100644
index 0000000000..28d370315e
--- /dev/null
+++ b/services/core-api/tests/users/models/test_user.py
@@ -0,0 +1,87 @@
+from datetime import datetime
+from pytz import utc
+from app.api.users.models.user import User
+from tests.factories import UserFactory
+
+
+def test_user_find_by_sub(db_session):
+ user = UserFactory()
+
+ # Find the user by its `sub`
+ found_user = User.find_by_sub(user.sub)
+
+ # Assertions to ensure the user can be found correctly
+ assert found_user is not None
+ assert found_user.sub == user.sub
+ assert found_user.email == user.email
+
+
+def test_user_model_find_all(db_session):
+ # Define batch size and create multiple User instances
+ batch_size = 3
+ users = UserFactory.create_batch(size=batch_size)
+
+ # Get all the users from the database
+ all_users = User.query.filter_by(deleted_ind=False).all() # Assuming `SoftDeleteMixin` is in use
+ assert len(all_users) == batch_size # Ensure the batch size matches the created instances
+
+
+def test_user_create_or_update_user_create_new_user(db_session):
+ # Create a user via the `create_or_update_user` method
+ user_data = {
+ "sub": "unique-sub-id",
+ "email": "test@example.com",
+ "given_name": "Test",
+ "family_name": "User",
+ "display_name": "Test User",
+ "idir_username": "testuser",
+ "identity_provider": "idir",
+ "idir_user_guid": "unique-idir-guid",
+ "last_logged_in": datetime.now(tz=utc),
+ }
+
+ new_user = User.create_or_update_user(**user_data)
+
+ # Validate that the user was created correctly
+ assert new_user is not None
+ assert new_user.sub == user_data["sub"]
+ assert new_user.email == user_data["email"]
+
+
+def test_user_create_or_update_user_update_existing_user(db_session):
+ # Create an initial user
+ user = UserFactory()
+
+ # Update the existing user's data
+ updated_data = {
+ "sub": user.sub,
+ "email": "updated@example.com",
+ "given_name": "Updated",
+ "family_name": "Name",
+ "idir_username": "testuser",
+ "display_name": "Updated Name",
+ }
+
+ updated_user = User.create_or_update_user(**updated_data)
+
+ # Validate that the user's information was updated
+ assert updated_user is not None
+ assert updated_user.email == "updated@example.com"
+ assert updated_user.given_name == "Updated"
+ assert updated_user.family_name == "Name"
+ assert updated_user.display_name == "Updated Name"
+
+
+def test_user_soft_delete(db_session):
+ # Create a user
+ user = UserFactory()
+
+ # Delete the user
+ user.delete()
+
+ # Validate that the user is marked as deleted
+ assert user.deleted_ind is True
+
+ # Ensure the user is not returned in a default query
+ active_users = User.query.filter_by(deleted_ind=False).all()
+ assert user not in active_users
\ No newline at end of file
diff --git a/services/core-api/tests/users/resources/test_user_profile_resource.py b/services/core-api/tests/users/resources/test_user_profile_resource.py
new file mode 100644
index 0000000000..3fad58a3ea
--- /dev/null
+++ b/services/core-api/tests/users/resources/test_user_profile_resource.py
@@ -0,0 +1,37 @@
+import json
+from datetime import datetime
+from pytz import utc
+
+from unittest.mock import patch
+from app.api.users.models.user import User
+from app.api.utils.include.user_info import User as UserUtils
+
+
+def test_user_resource_get(test_client, auth_headers):
+ # Setup test user info
+ test_user_info = {
+ "sub": "bce4ffa4b74741c79afa82287bfffbc8@idir",
+ "email": "test-email",
+ "given_name": "Test",
+ "family_name": "Testerson",
+ "display_name": "Testerson, Test: EMLI:EX",
+ "idir_username": "TTESTERSON",
+ "idir_user_guid": "BCE4FFA4B63641C79AFA82287BFFFBC8",
+ "last_logged_in": datetime.now(tz=utc),
+ }
+
+ # Mock UserUtils and User.create_or_update_user
+ with patch.object(UserUtils, 'get_user_raw_info', return_value=test_user_info), \
+ patch.object(User, 'create_or_update_user', return_value=test_user_info):
+ # Make GET request
+ get_resp = test_client.get('/users/profile', headers=auth_headers['full_auth_header'])
+ assert get_resp.status_code == 200
+
+ # Parse response
+ get_data = json.loads(get_resp.data.decode())
+ # Validate response matches test_user_info
+ assert get_data["email"] == test_user_info["email"]
+ assert get_data["sub"] == test_user_info["sub"]
+ assert get_data["given_name"] == test_user_info["given_name"]
+ assert get_data["family_name"] == test_user_info["family_name"]
+ assert get_data["display_name"] == test_user_info["display_name"]
diff --git a/services/core-web/cypress/e2e/majorprojects.cy.ts b/services/core-web/cypress/e2e/majorprojects.cy.ts
index 35dc1cbfd7..00a4c119e2 100644
--- a/services/core-web/cypress/e2e/majorprojects.cy.ts
+++ b/services/core-web/cypress/e2e/majorprojects.cy.ts
@@ -18,6 +18,12 @@ describe("Major Projects", () => {
it("should upload and download a document successfully", () => {
const fileName = "dummy.pdf";
+ cy.get('body').then((element) => {
+ if (element.find("button[data-cy=view-project-description-details-button]").length > 0) {
+ cy.contains("View Project Description Details").click();
+ }
+ })
+
cy.contains("Document Upload").click();
cy.contains("Edit Project Description").click();
diff --git a/services/core-web/src/components/Forms/reports/ReportPermitRequirementForm.tsx b/services/core-web/src/components/Forms/reports/ReportPermitRequirementForm.tsx
index 9c41112ac7..d5699c1ade 100644
--- a/services/core-web/src/components/Forms/reports/ReportPermitRequirementForm.tsx
+++ b/services/core-web/src/components/Forms/reports/ReportPermitRequirementForm.tsx
@@ -13,7 +13,7 @@ import {
REPORT_REGULATORY_AUTHORITY_CODES_HASH,
REPORT_TYPE_CODES,
} from "@mds/common";
-import { required, requiredRadioButton } from "@mds/common/redux/utils/Validate";
+import { required, requiredRadioButton, maxLength } from "@mds/common/redux/utils/Validate";
import FormWrapper from "@mds/common/components/forms/FormWrapper";
import RenderSelect from "@mds/common/components/forms/RenderSelect";
import RenderDate from "@mds/common/components/forms/RenderDate";
@@ -54,19 +54,19 @@ export const ReportPermitRequirementForm: FC = ({
initialValues={
mineReportPermitRequirement
? {
- ...mineReportPermitRequirement,
- stepPath: condition.stepPath,
- permit_amendment_id: latestPermitAmendment.permit_amendment_id,
- }
+ ...mineReportPermitRequirement,
+ stepPath: condition.stepPath,
+ permit_amendment_id: latestPermitAmendment.permit_amendment_id,
+ }
: {
- mine_report_status_code: MINE_REPORT_SUBMISSION_CODES.NON,
- stepPath: condition.stepPath,
- permit_condition_category_code: condition.condition_category_code,
- permit_condition_type_code: REPORT_TYPE_CODES.PRR,
- permit_condition_id: condition.permit_condition_id,
- permit_guid: permitGuid,
- permit_amendment_id: latestPermitAmendment.permit_amendment_id,
- }
+ mine_report_status_code: MINE_REPORT_SUBMISSION_CODES.NON,
+ stepPath: condition.stepPath,
+ permit_condition_category_code: condition.condition_category_code,
+ permit_condition_type_code: REPORT_TYPE_CODES.PRR,
+ permit_condition_id: condition.permit_condition_id,
+ permit_guid: permitGuid,
+ permit_amendment_id: latestPermitAmendment.permit_amendment_id,
+ }
}
>
@@ -80,6 +80,14 @@ export const ReportPermitRequirementForm: FC = ({
disabled
/>
+
+
+
= ({
name="initial_due_date"
label="Initial Due Date"
placeholder="Select date"
- required
- validate={[required]}
formatViewDate
component={RenderDate}
/>
diff --git a/services/core-web/src/components/Home.js b/services/core-web/src/components/Home.js
deleted file mode 100644
index 59f803fb84..0000000000
--- a/services/core-web/src/components/Home.js
+++ /dev/null
@@ -1,138 +0,0 @@
-import React, { Component } from "react";
-import { bindActionCreators } from "redux";
-import { connect } from "react-redux";
-import { Layout, BackTop, Button } from "antd";
-import { ArrowUpOutlined } from "@ant-design/icons";
-import PropTypes from "prop-types";
-import MediaQuery from "react-responsive";
-import LoadingBar from "react-redux-loading-bar";
-import { getStaticContentLoadingIsComplete } from "@mds/common/redux/selectors/staticContentSelectors";
-import {
- loadBulkStaticContent,
- fetchInspectors,
- fetchProjectLeads,
-} from "@mds/common/redux/actionCreators/staticContentActionCreator";
-import { detectIE, detectTestEnvironment, detectDevelopmentEnvironment } from "@mds/common/utils";
-import DashboardRoutes from "@/routes/DashboardRoutes";
-import { AuthenticationGuard } from "@/HOC/AuthenticationGuard";
-import WarningBanner, { WARNING_TYPES } from "@/components/common/WarningBanner";
-import * as Styles from "@/constants/styles";
-import NavBar from "./navigation/NavBar";
-import Loading from "./common/Loading";
-
-/**
- * @class Home contains the navigation and wraps the Dashboard routes. Home should not contain any redux logic/state.
- * Home is wrapped in AuthenticationGuard which checks keycloak authorization.
- */
-
-const propTypes = {
- staticContentLoadingIsComplete: PropTypes.bool.isRequired,
- location: PropTypes.shape({ pathname: PropTypes.string }).isRequired,
- loadBulkStaticContent: PropTypes.func.isRequired,
- fetchInspectors: PropTypes.func.isRequired,
- fetchProjectLeads: PropTypes.func.isRequired,
-};
-
-export class Home extends Component {
- state = {
- isIE: false,
- isTest: false,
- isDev: false,
- isMobile: true,
- activeNavButton: "",
- isMenuOpen: false,
- };
-
- componentDidMount() {
- this.setState({
- isIE: detectIE(),
- isTest: detectTestEnvironment(),
- isDev: detectDevelopmentEnvironment(),
- });
- this.handleActiveButton(this.props.location.pathname);
- this.loadStaticContent();
- }
-
- componentWillReceiveProps(nextProps) {
- if (this.props.location !== nextProps.location) {
- this.handleActiveButton(nextProps.location.pathname);
- // close Menu when link is clicked
- this.setState({ isMenuOpen: false });
- }
- }
-
- handleActiveButton = (path) => {
- this.setState({ activeNavButton: path });
- };
-
- handleIEClose = () => {
- this.setState({ isIE: false });
- };
-
- handleMobileWarningClose = () => {
- this.setState({ isMobile: false });
- };
-
- toggleHamburgerMenu = () => {
- this.setState((prevState) => ({ isMenuOpen: !prevState.isMenuOpen }));
- };
-
- loadStaticContent = () => {
- this.props.loadBulkStaticContent();
- this.props.fetchInspectors();
- this.props.fetchProjectLeads();
- };
-
- render() {
- if (!this.props.staticContentLoadingIsComplete) {
- return ;
- }
- return (
-
-