From 3d17d5f47099cb95cb80b7f30c49e6c26d5c9baf Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 21 Jul 2023 12:06:36 -0700 Subject: [PATCH 001/234] fix get is portfolio issue --- .../request_api/services/records/recordservicegetter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/request-management-api/request_api/services/records/recordservicegetter.py b/request-management-api/request_api/services/records/recordservicegetter.py index 54902e961..c16bc5786 100644 --- a/request-management-api/request_api/services/records/recordservicegetter.py +++ b/request-management-api/request_api/services/records/recordservicegetter.py @@ -75,6 +75,8 @@ def __preparerecord(self, record, _computingresponse, computingresponses, divisi if _computingresponse_err is not None: _attachement['failed'] = _computingresponse_err _record['attachments'].append(_attachement) + else: + _record['attributes'] = self.__formatrecordattributes(_record['attributes'], divisions) return _record From 8ff60eb5e7f62710fc3f700df4fee6b85213af2e Mon Sep 17 00:00:00 2001 From: Milos Despotovic Date: Tue, 25 Jul 2023 14:31:33 -0700 Subject: [PATCH 002/234] Add Dockerfile for mac --- .../forms-flow-bpm/mac.Dockerfile | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 apps/forms-flow-ai/forms-flow-bpm/mac.Dockerfile diff --git a/apps/forms-flow-ai/forms-flow-bpm/mac.Dockerfile b/apps/forms-flow-ai/forms-flow-bpm/mac.Dockerfile new file mode 100644 index 000000000..bdacbeeae --- /dev/null +++ b/apps/forms-flow-ai/forms-flow-bpm/mac.Dockerfile @@ -0,0 +1,50 @@ +# Modified by Yichun Zhao and Walter Moar + +# Maven build +FROM artifacts.developer.gov.bc.ca/docker-remote/maven:3.6.1-jdk-11-slim AS MAVEN_TOOL_CHAIN + +RUN apt-get update \ + && apt-get install -y git + +ARG FORMIO_SOURCE_REPO_BRANCH=v4.0.5-alpha +ARG FORMIO_SOURCE_REPO_URL=https://github.com/AOT-Technologies/forms-flow-ai.git + +RUN git clone -b ${FORMIO_SOURCE_REPO_BRANCH} ${FORMIO_SOURCE_REPO_URL} /bpm/ + +#RUN cp /bpm/forms-flow-bpm/pom-docker.xml /tmp/pom.xml +#RUN cp /bpm/forms-flow-bpm/settings-docker.xml /usr/share/maven/ref/ +COPY ./pom-docker.xml /tmp/pom.xml +COPY ./settings-docker.xml /usr/share/maven/ref/ + +WORKDIR /tmp/ + +# This allows Docker to cache most of the maven dependencies +RUN mvn -s /usr/share/maven/ref/settings-docker.xml dependency:resolve-plugins dependency:resolve dependency:go-offline -B +RUN rm -rf /bpm/forms-flow-bpm/src/main/resources/processes +RUN cp -r /bpm/forms-flow-bpm/src/ /tmp/src/ + +ARG CUSTOM_SRC_DIR=src/main/ + +# Override these files they have custom changes in the sbc_divapps directory +COPY ./${CUSTOM_SRC_DIR}/ /tmp/${CUSTOM_SRC_DIR}/ +RUN mvn -s /usr/share/maven/ref/settings-docker.xml package -Dmaven.test.skip + + + +# Final custom slim java image (for apk command see jdk-11.0.3_7-alpine-slim) +FROM --platform=amd64 artifacts.developer.gov.bc.ca/docker-remote/adoptopenjdk/openjdk11:jdk-11.0.3_7-alpine + +ENV JAVA_VERSION jdk-11.0.3+7 +ENV JAVA_HOME=/opt/java/openjdk \ + PATH="/opt/java/openjdk/bin:$PATH" + +EXPOSE 8080 +# OpenShift has /app in the image, but it's missing when doing local development - Create it when missing +RUN test ! -d /app && mkdir /app || : +# Add spring boot application +RUN mkdir -p /app +COPY --from=MAVEN_TOOL_CHAIN /tmp/target/forms-flow-bpm.jar ./app +RUN chmod a+rwx -R /app +WORKDIR /app +VOLUME /tmp +ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app/forms-flow-bpm.jar"] \ No newline at end of file From 396c782d08e3a612cdf9a9b19f95fb3acc48b97a Mon Sep 17 00:00:00 2001 From: Milos Despotovic Date: Tue, 25 Jul 2023 14:42:37 -0700 Subject: [PATCH 003/234] Update README --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index e83caf170..86ce7fe83 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,19 @@ Freedom of Information modernization. ## Installation +### For mac +1. Clone this repo +2. Copy the .env file to the root folder of entire repo +3. Change docker-compose.yml line 152 from windows.Dockerfile to mac.Dockerfile + +#### Add IP address to hosts on local system if accessing remotely +1. Log into vpn +2. Click statistics icon (bottom left of AnyConnect, the graph icon) +3. Note down client address (IPv4) +4. In your terminal run the command ``` sudo nano /etc/hosts ``` +5. Add the ip address from above to the list, with alias value ``` foiflow.local ``` +6. Save and exit + ## Project Status The project is in the very early stages of development. The codebase will be changing frequently. From 272ea5777f0bb64ac8e209a369a651bfbbefb497 Mon Sep 17 00:00:00 2001 From: Milos Despotovic Date: Wed, 26 Jul 2023 09:49:48 -0700 Subject: [PATCH 004/234] Add instructions for turning off Redis cache --- README.md | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 86ce7fe83..908fe8af6 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,13 @@ Freedom of Information modernization. ## Installation ### For mac +#### Run the microservices in docker 1. Clone this repo -2. Copy the .env file to the root folder of entire repo +2. Place the appropriate .env file to the root folder of entire repo 3. Change docker-compose.yml line 152 from windows.Dockerfile to mac.Dockerfile +4. Make sure you are logged into the VPN if working remotely +5. Compose up the docker-compose.yml file in the root folder by either right-clicking and selecting 'Compose up' in VS Code or running the command +```docker compose -f "docker-compose.yml" up -d --build``` #### Add IP address to hosts on local system if accessing remotely 1. Log into vpn @@ -25,6 +29,42 @@ Freedom of Information modernization. 5. Add the ip address from above to the list, with alias value ``` foiflow.local ``` 6. Save and exit +#### API performance issues +The app (particularly the API) may run very slowly. Performance can be improved by turning off Redis caching, commenting out the following code in [KeycloakAdminService](https://github.com/bcgov/foi-flow/blob/main/request-management-api/request_api/services/external/keycloakadminservice.py) (at ```foi-flow/request-management-api/request_api/services/external/keycloakadminservice.py```). Make sure that you don't commit these changes. + +Comment out the following, and change the _accesstoken value to None +```diff + def get_token(self): + _accesstoken=None + try: ++ #cache_client = redis.from_url(self.cache_redis_url,decode_responses=True) +- cache_client = redis.from_url(self.cache_redis_url,decode_responses=True) ++ _accesstoken = None +- _accesstoken = cache_client.get("foi:kcsrcacnttoken") + if _accesstoken is None: + url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(self.keycloakhost,self.keycloakrealm) + params = { + + 'client_id': self.keycloakclientid, + 'grant_type': 'password', + 'username' : self.keycloakadminserviceaccount, + 'password': self.keycloakadminservicepassword, + 'client_secret':self.keycloakclientsecret + } + x = requests.post(url, params, verify=True).content.decode('utf-8') + _accesstoken = str(ast.literal_eval(x)['access_token']) ++ #cache_client.set("foi:kcsrcacnttoken",_accesstoken,ex=int(self.kctokenexpiry)) +- cache_client.set("foi:kcsrcacnttoken",_accesstoken,ex=int(self.kctokenexpiry)) + except BusinessException as exception: + print("Error happened while accessing token on KeycloakAdminService {0}".format(exception.message)) ++ #finally: ++ #cache_client = None +- finally: +- cache_client = None + return _accesstoken +``` +Again, make sure you don't commit these changes. + ## Project Status The project is in the very early stages of development. The codebase will be changing frequently. From 37a5f95463add0fad4515dba230f932fafcf63a4 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Wed, 26 Jul 2023 15:03:48 -0700 Subject: [PATCH 005/234] #4140 adding migration reference columns --- .../versions/2e43869513c2_migrationcolumns.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 request-management-api/migrations/versions/2e43869513c2_migrationcolumns.py diff --git a/request-management-api/migrations/versions/2e43869513c2_migrationcolumns.py b/request-management-api/migrations/versions/2e43869513c2_migrationcolumns.py new file mode 100644 index 000000000..eb0a2689d --- /dev/null +++ b/request-management-api/migrations/versions/2e43869513c2_migrationcolumns.py @@ -0,0 +1,32 @@ +"""Columns for migrations + +Revision ID: 2e43869513c2 +Revises: eea7ea4d60ac +Create Date: 2023-07-26 13:27:04.441402 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '2e43869513c2' +down_revision = 'eea7ea4d60ac' +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column('FOIRequestApplicants', sa.Column('migrationreference', sa.Text, nullable=True)) + op.add_column('FOIRequests', sa.Column('migrationreference', sa.Text, nullable=True)) + op.add_column('FOIMinistryRequests', sa.Column('migrationreference', sa.Text, nullable=True)) + op.add_column('FOIRequestApplicantMappings', sa.Column('migrationreference', sa.Text, nullable=True)) + op.add_column('FOIRequestContactInformation', sa.Column('migrationreference', sa.Text, nullable=True)) + + +def downgrade(): + op.drop_column('FOIRequestApplicants', 'migrationreference') + op.drop_column('FOIRequests', 'migrationreference') + op.drop_column('FOIMinistryRequests', 'migrationreference') + op.drop_column('FOIRequestApplicantMappings', 'migrationreference') + op.drop_column('FOIRequestContactInformation', 'migrationreference') From 233945ad8517f5a0d27aef5fb06a9ab588e21c23 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Wed, 26 Jul 2023 15:20:43 -0700 Subject: [PATCH 006/234] #4140 migration ref columns to extension table --- .../migrations/versions/2e43869513c2_migrationcolumns.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/request-management-api/migrations/versions/2e43869513c2_migrationcolumns.py b/request-management-api/migrations/versions/2e43869513c2_migrationcolumns.py index eb0a2689d..9613a53c1 100644 --- a/request-management-api/migrations/versions/2e43869513c2_migrationcolumns.py +++ b/request-management-api/migrations/versions/2e43869513c2_migrationcolumns.py @@ -21,7 +21,8 @@ def upgrade(): op.add_column('FOIRequests', sa.Column('migrationreference', sa.Text, nullable=True)) op.add_column('FOIMinistryRequests', sa.Column('migrationreference', sa.Text, nullable=True)) op.add_column('FOIRequestApplicantMappings', sa.Column('migrationreference', sa.Text, nullable=True)) - op.add_column('FOIRequestContactInformation', sa.Column('migrationreference', sa.Text, nullable=True)) + op.add_column('FOIRequestContactInformation', sa.Column('migrationreference', sa.Text, nullable=True)) + op.add_column('FOIRequestExtensions', sa.Column('migrationreference', sa.Text, nullable=True)) def downgrade(): @@ -30,3 +31,4 @@ def downgrade(): op.drop_column('FOIMinistryRequests', 'migrationreference') op.drop_column('FOIRequestApplicantMappings', 'migrationreference') op.drop_column('FOIRequestContactInformation', 'migrationreference') + op.drop_column('FOIRequestExtensions', 'migrationreference') From 2220965016120143a3d6540f0f8f2d1da31e0f43 Mon Sep 17 00:00:00 2001 From: nkan-aot <96087745+nkan-aot@users.noreply.github.com> Date: Fri, 28 Jul 2023 11:10:15 -0700 Subject: [PATCH 007/234] Fix eea7ea4d60ac_.py revises info --- request-management-api/migrations/versions/eea7ea4d60ac_.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/request-management-api/migrations/versions/eea7ea4d60ac_.py b/request-management-api/migrations/versions/eea7ea4d60ac_.py index 6822028a7..80344955d 100644 --- a/request-management-api/migrations/versions/eea7ea4d60ac_.py +++ b/request-management-api/migrations/versions/eea7ea4d60ac_.py @@ -1,7 +1,7 @@ """empty message Revision ID: eea7ea4d60ac -Revises: d64df04da9df +Revises: 7d393fc1fc31 Create Date: 2023-07-12 18:09:59.244266 """ From 2ac5e5497c52594e162b33641ddbe389e1509b1a Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Fri, 28 Jul 2023 13:08:22 -0700 Subject: [PATCH 008/234] #4140 ETL base checkin --- datamigrations/basequeries/applicants.sql | 32 +++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 datamigrations/basequeries/applicants.sql diff --git a/datamigrations/basequeries/applicants.sql b/datamigrations/basequeries/applicants.sql new file mode 100644 index 000000000..0769a3217 --- /dev/null +++ b/datamigrations/basequeries/applicants.sql @@ -0,0 +1,32 @@ + +SELECT email,firstname,lastname,middleName,birthDate,businessName, STRING_AGG(CAST(requestid as nvarchar(max)),', ') as requests FROM ( +SELECT + requesters.vcEmailID as email, + + requesters.vcFirstName as firstName, + requesters.vcLastName as lastName, + requesters.vcMiddleName as middleName, + requestorfields.CUSTOMFIELD35 as birthDate, + requesters.vcCompany as businessName, + requests.vcVisibleRequestID as requestid + + FROM + tblRequests requests WITH (NOLOCK) LEFT OUTER JOIN EC_OFFICE office WITH (NOLOCK) ON requests.tiOfficeID = office.OFFICE_ID + + LEFT OUTER JOIN tblRequesters requesters WITH (NOLOCK) ON requests.iRequesterID = requesters.iRequesterID + + LEFT OUTER JOIN dbo.TBLREQUESTERCUSTOMFIELDS requestorfields WITH (NOLOCK) ON requesters.iRequesterID = requestorfields.IREQUESTERID + + WHERE + office.OFFICE_CODE = 'CFD' AND requests.iRequestStatusID NOT IN ( SELECT iRequestStatusID FROM tblRequestStatuses WHERE vcRequestStatus in ('Closed')) + + ) AS T + + --WHERE requestid in ('CFD-2021-12651') + + GROUP BY email,firstname,lastname,middleName,birthDate,businessName + + + + + \ No newline at end of file From eb3e4fb7bb8336c513d0572a4b8c3f2e10ca4b2e Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Fri, 28 Jul 2023 13:08:49 -0700 Subject: [PATCH 009/234] #4140 migrations ETL --- .gitignore | 1 + .../FOIMOD.CFD.ETL.DataMigration.sln | 22 ++ .../Applicants.dtsx | 364 ++++++++++++++++++ .../FOIMOD.CFD.ETL.DataMigration.database | 13 + .../FOIMOD.CFD.ETL.DataMigration.dtproj | 83 ++++ .../FOIMOD.CFD.ETL.DataMigration.dtproj.user | 20 + .../IXIAS_CIRMOTST , 1435.ATIPITEST.conmgr | 12 + .../PostgreSQLFOIFLOW.conmgr | 10 + .../Project.params | 24 ++ 9 files changed, 549 insertions(+) create mode 100644 datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.sln create mode 100644 datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/Applicants.dtsx create mode 100644 datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.database create mode 100644 datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj create mode 100644 datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj.user create mode 100644 datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/IXIAS_CIRMOTST , 1435.ATIPITEST.conmgr create mode 100644 datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/PostgreSQLFOIFLOW.conmgr create mode 100644 datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/Project.params diff --git a/.gitignore b/.gitignore index 90ca4972c..cda80c750 100644 --- a/.gitignore +++ b/.gitignore @@ -91,3 +91,4 @@ axisintegrationapi/MCS.FOI.AXISIntegration/MCS.FOI.AXISIntegrationWebAPI/*.user axisintegrationapi/MCS.FOI.AXISIntegration/MCS.FOI.AXISIntegrationWebAPI/Properties/PublishProfiles/FolderProfile.pubxml axisintegrationapi/MCS.FOI.AXISIntegration/MCS.FOI.AXISIntegrationWebAPI/Properties/PublishProfiles/FolderProfile.pubxml.user apps/ +datamigrations/FOIMOD.CFD.ETL.DataMigration/.vs/FOIMOD.CFD.ETL.DataMigration/v16/.suo diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.sln b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.sln new file mode 100644 index 000000000..10d4cc900 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.32228.343 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{159641D6-6404-4A2A-AE62-294DE0FE8301}") = "FOIMOD.CFD.ETL.DataMigration", "FOIMOD.CFD.ETL.DataMigration\FOIMOD.CFD.ETL.DataMigration.dtproj", "{7A8CD9B3-2536-4317-A3F6-A6008408442B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Development|Default = Development|Default + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7A8CD9B3-2536-4317-A3F6-A6008408442B}.Development|Default.ActiveCfg = Development + {7A8CD9B3-2536-4317-A3F6-A6008408442B}.Development|Default.Build.0 = Development + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C25DF891-25B3-4F39-B2B2-A9B1FC3BA056} + EndGlobalSection +EndGlobal diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/Applicants.dtsx b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/Applicants.dtsx new file mode 100644 index 000000000..7573d7e4a --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/Applicants.dtsx @@ -0,0 +1,364 @@ + + + 8 + + + + + + + + + + 0 + + + SELECT email,firstname,lastname,middleName,birthDate,businessName, STRING_AGG(CAST(requestid as nvarchar(max)),', ') as requests FROM ( +SELECT + requesters.vcEmailID as email, + + requesters.vcFirstName as firstName, + requesters.vcLastName as lastName, + requesters.vcMiddleName as middleName, + requestorfields.CUSTOMFIELD35 as birthDate, + requesters.vcCompany as businessName, + requests.vcVisibleRequestID as requestid + + FROM + tblRequests requests WITH (NOLOCK) LEFT OUTER JOIN EC_OFFICE office WITH (NOLOCK) ON requests.tiOfficeID = office.OFFICE_ID + + LEFT OUTER JOIN tblRequesters requesters WITH (NOLOCK) ON requests.iRequesterID = requesters.iRequesterID + + LEFT OUTER JOIN dbo.TBLREQUESTERCUSTOMFIELDS requestorfields WITH (NOLOCK) ON requesters.iRequesterID = requestorfields.IREQUESTERID + + WHERE + office.OFFICE_CODE = 'CFD' AND requests.iRequestStatusID NOT IN ( SELECT iRequestStatusID FROM tblRequestStatuses WHERE vcRequestStatus in ('Closed','Canceled','CWithheld')) + + ) AS T + + GROUP BY email,firstname,lastname,middleName,birthDate,businessName + $Project::applicantsqlquery + 1252 + false + 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataSourceViewID + + + +]]> + \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.database b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.database new file mode 100644 index 000000000..03617d442 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.database @@ -0,0 +1,13 @@ + + FOIMOD.CFD.ETL.DataMigration + FOIMOD.CFD.ETL.DataMigration + 0001-01-01T00:00:00Z + 0001-01-01T00:00:00Z + 0001-01-01T00:00:00Z + Unprocessed + 0001-01-01T00:00:00Z + + Default + Unchanged + + \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj new file mode 100644 index 000000000..dad914219 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj @@ -0,0 +1,83 @@ + + + Project + 15.0.2000.180 + 9.0.1.0 + $base64$PFNvdXJjZUNvbnRyb2xJbmZvIHhtbG5zOnhzZD0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOmRkbDI9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vYW5hbHlzaXNzZXJ2aWNlcy8yMDAzL2VuZ2luZS8yIiB4bWxuczpkZGwyXzI9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vYW5hbHlzaXNzZXJ2aWNlcy8yMDAzL2VuZ2luZS8yLzIiIHhtbG5zOmRkbDEwMF8xMDA9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vYW5hbHlzaXNzZXJ2aWNlcy8yMDA4L2VuZ2luZS8xMDAvMTAwIiB4bWxuczpkZGwyMDA9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vYW5hbHlzaXNzZXJ2aWNlcy8yMDEwL2VuZ2luZS8yMDAiIHhtbG5zOmRkbDIwMF8yMDA9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vYW5hbHlzaXNzZXJ2aWNlcy8yMDEwL2VuZ2luZS8yMDAvMjAwIiB4bWxuczpkZGwzMDA9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vYW5hbHlzaXNzZXJ2aWNlcy8yMDExL2VuZ2luZS8zMDAiIHhtbG5zOmRkbDMwMF8zMDA9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vYW5hbHlzaXNzZXJ2aWNlcy8yMDExL2VuZ2luZS8zMDAvMzAwIiB4bWxuczpkZGw0MDA9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vYW5hbHlzaXNzZXJ2aWNlcy8yMDEyL2VuZ2luZS80MDAiIHhtbG5zOmRkbDQwMF80MDA9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vYW5hbHlzaXNzZXJ2aWNlcy8yMDEyL2VuZ2luZS80MDAvNDAwIiB4bWxuczpkZGw1MDA9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vYW5hbHlzaXNzZXJ2aWNlcy8yMDEzL2VuZ2luZS81MDAiIHhtbG5zOmRkbDUwMF81MDA9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vYW5hbHlzaXNzZXJ2aWNlcy8yMDEzL2VuZ2luZS81MDAvNTAwIiB4bWxuczpkd2Q9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vRGF0YVdhcmVob3VzZS9EZXNpZ25lci8xLjAiPg0KICA8RW5hYmxlZD5mYWxzZTwvRW5hYmxlZD4NCiAgPFByb2plY3ROYW1lPjwvUHJvamVjdE5hbWU+DQogIDxBdXhQYXRoPjwvQXV4UGF0aD4NCiAgPExvY2FsUGF0aD48L0xvY2FsUGF0aD4NCiAgPFByb3ZpZGVyPjwvUHJvdmlkZXI+DQo8L1NvdXJjZUNvbnRyb2xJbmZvPg== + + FOIMOD.CFD.ETL.DataMigration.database + FOIMOD.CFD.ETL.DataMigration.database + + + + + + + + {46811412-61ea-42c7-abaa-4731edc96c50} + FOIMOD.CFD.ETL.DataMigration + 1 + 0 + 0 + + + 2023-07-26T15:32:55.9483598-07:00 + IDIR\AANTON_A + WALTZ + + + AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAFieOUvPkcUqQDxLl/khbKQAAAAACAAAAAAADZgAAwAAAABAAAADIGgSnFKHJBNFMYZ0xWaKEAAAAAASAAACgAAAAEAAAAJBxAOqmxVuTcLPfect6bMmIAAAAUp6yg4DcINFnWuyrW9eh+IMBcwGVDEAGJMQtXcmLORFMfDf4iq0dVEzYosOj7zOARFk0HUULDgadm/cqRGLOpslnHn3yqCm62LYHc6sxK2ALjegkUEkZrJUNKdCLvxf0Mc6iOQdPnfwPNzBJAmxiSEhzMBQ52DTPcruC2oGWLg8f0D3CYaFyfhQAAABUN3uxSTwArgWEbz1rXPzrvosYJQ== + 1 + + + + + + + + + + + {A8051341-9A9E-4D11-BEB5-AF0259A25F9F} + Package + 1 + 0 + 0 + + + {C963289B-2F1B-4B9F-AFC7-21DA46F83F24} + 8 + + + 1 + + + + + + + + + + + + + Development + + bin + + + + + SQLServer2019 + false + + + + + + + + + + \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj.user b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj.user new file mode 100644 index 000000000..2e5d452cb --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj.user @@ -0,0 +1,20 @@ + + + + + Development + + + false + + + false + + + false + true + + + + + \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/IXIAS_CIRMOTST , 1435.ATIPITEST.conmgr b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/IXIAS_CIRMOTST , 1435.ATIPITEST.conmgr new file mode 100644 index 000000000..50afb1bbe --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/IXIAS_CIRMOTST , 1435.ATIPITEST.conmgr @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/PostgreSQLFOIFLOW.conmgr b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/PostgreSQLFOIFLOW.conmgr new file mode 100644 index 000000000..759192ac1 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/PostgreSQLFOIFLOW.conmgr @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/Project.params b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/Project.params new file mode 100644 index 000000000..a1ba48200 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/Project.params @@ -0,0 +1,24 @@ + + + + + {ed0ced49-9631-4b81-8877-2fbb3b5a9c50} + + + 0 + 0 + 0 + SELECT email,firstname,lastname,middleName,requesterid,birthDate,businessName, STRING_AGG(CAST(requestid as nvarchar(max)),', ') as requests FROM ( SELECT requesters.vcEmailID as email, requesters.iRequesterID as requesterid, requesters.vcFirstName as firstName, requesters.vcLastName as lastName, requesters.vcMiddleName as middleName, requestorfields.CUSTOMFIELD35 as birthDate, requesters.vcCompany as businessName, requests.vcVisibleRequestID as requestid FROM tblRequests requests WITH (NOLOCK) LEFT OUTER JOIN EC_OFFICE office WITH (NOLOCK) ON requests.tiOfficeID = office.OFFICE_ID LEFT OUTER JOIN tblRequesters requesters WITH (NOLOCK) ON requests.iRequesterID = requesters.iRequesterID LEFT OUTER JOIN dbo.TBLREQUESTERCUSTOMFIELDS requestorfields WITH (NOLOCK) ON requesters.iRequesterID = requestorfields.IREQUESTERID WHERE office.OFFICE_CODE = 'CFD' AND requests.vcRequestStatus NOT IN ('Closed') ) AS T GROUP BY T.email,T.requesterid,T.firstname,T.lastname,T.middleName,T.requesterid,T.birthDate,T.businessName ORDER BY T.firstname ASC + 18 + + + \ No newline at end of file From 015c49b7e0c1b2952416f589c866d6a4a6762b89 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Sun, 30 Jul 2023 21:55:33 -0700 Subject: [PATCH 010/234] #4140 FOI Requests ETL --- .../FOIMOD.CFD.ETL.DataMigration.dtproj | 293 +++++++++++++- .../FOIMOD.CFD.ETL.DataMigration.dtproj.user | 8 +- .../FOIRequests.dtsx | 367 ++++++++++++++++++ .../Project.params | 21 + 4 files changed, 680 insertions(+), 9 deletions(-) create mode 100644 datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIRequests.dtsx diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj index dad914219..61761d6ff 100644 --- a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj @@ -26,26 +26,297 @@ WALTZ - AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAFieOUvPkcUqQDxLl/khbKQAAAAACAAAAAAADZgAAwAAAABAAAADIGgSnFKHJBNFMYZ0xWaKEAAAAAASAAACgAAAAEAAAAJBxAOqmxVuTcLPfect6bMmIAAAAUp6yg4DcINFnWuyrW9eh+IMBcwGVDEAGJMQtXcmLORFMfDf4iq0dVEzYosOj7zOARFk0HUULDgadm/cqRGLOpslnHn3yqCm62LYHc6sxK2ALjegkUEkZrJUNKdCLvxf0Mc6iOQdPnfwPNzBJAmxiSEhzMBQ52DTPcruC2oGWLg8f0D3CYaFyfhQAAABUN3uxSTwArgWEbz1rXPzrvosYJQ== + AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAFieOUvPkcUqQDxLl/khbKQAAAAACAAAAAAADZgAAwAAAABAAAAA0uY/HT1+YGqkIDcHoIhLfAAAAAASAAACgAAAAEAAAALs+25gAe0Pus6RvAnrRt9yIAAAApcfE78axW3E+nxvTE6Ln89YvTtBE/p94gc9UvsKyKTIHZgRbq9/b8Z75mbqE+UYRdJJoVEddkELctS6J8phlHERAtbQM035Z+yaPXmcylrWMvOSz2FJQhcAr96Dhr5U87u55a09iF/DET7AXNVvwFHMvxSL6Ba+WQ3TnvuvfDsDFq4J/BoClBhQAAABr0IikLWsAxjt5m5fs+rkwiFSNHQ== 1 - + + - + + + + - + + + + + + + + + + 0 + 0 + 0 + false + 3 + + + + + + + + + + + 0 + 0 + 0 + Data Source=IXIAS\CIRMOTST , 1435;Initial Catalog=ATIPITEST;Provider=SQLNCLI11.1;Integrated Security=SSPI;Auto Translate=False; + 18 + + + + + + + + + + + 0 + 0 + 0 + 1 + 9 + + + + + + + + + + + 0 + 0 + 0 + 5 + 9 + + + + + + + + + + + 0 + 0 + 0 + false + 3 + + + + + + + + + + + 0 + 0 + 0 + ATIPITEST + 18 + + + + + + + + + + + 0 + 0 + 1 + 18 + + + + + + + + + + + 0 + 0 + 0 + false + 3 + + + + + + + + + + + 0 + 0 + 0 + IXIAS\CIRMOTST , 1435 + 18 + + + + + + + + + + + 0 + 0 + 0 + 18 + + + + + + + + + + + 0 + 0 + 0 + false + 3 + + + + + + + + + + + 0 + 0 + 0 + Dsn=PostgreSQLFOIFLOW; + 18 + + + + + + + + + + + 0 + 0 + 0 + 18 + + + + + + + + + + + 0 + 0 + 1 + 18 + + + + + + + + + + + 0 + 0 + 0 + false + 3 + + + + + + + + + + + 0 + 0 + 0 + 18 + + + + + + + + + + + 0 + 0 + 0 + 18 + + + - + {A8051341-9A9E-4D11-BEB5-AF0259A25F9F} Package 1 0 - 0 + 3 + + + {7E49DCB1-E15B-4DF3-A59B-DC1F6ADA1C42} + 8 + + + 1 + + + + + + {6B1FFF75-7F3F-438B-9C8E-FB5B50CBB326} + FOIRequests + 1 + 0 + 9 - {C963289B-2F1B-4B9F-AFC7-21DA46F83F24} + {A53BEF0F-E656-45C4-B2EE-63BD4B829959} 8 @@ -76,7 +347,13 @@ - + + + LastModifiedTime + LastModifiedTime + 2023-07-28T21:54:01.1505194Z + + diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj.user b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj.user index 2e5d452cb..fb5695dc9 100644 --- a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj.user +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj.user @@ -13,7 +13,13 @@ false true - + + + LastModifiedTime + LastModifiedTime + 2023-07-28T21:54:01.1525191Z + + diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIRequests.dtsx b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIRequests.dtsx new file mode 100644 index 000000000..41c0503e1 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIRequests.dtsx @@ -0,0 +1,367 @@ + + + 8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/Project.params b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/Project.params index a1ba48200..29b5e0d2c 100644 --- a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/Project.params +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/Project.params @@ -21,4 +21,25 @@ SSIS:Name="DataType">18 + + + {c1ff4bfe-2ac1-4abf-81a3-c236db112364} + + + 0 + 0 + 0 + SELECT (SELECT terminology.vcTerminology from tblTerminologyLookup terminology WHERE terminology.iLabelID = requestTypes.iLabelID and terminology.tiLocaleID = 1) as requestType, convert(varchar, requests.sdtRequestedDate,120) as receivedDate, requests.vcDescription as requestdescription, convert(varchar,requests.sdtRqtDescFromdate,120) as reqDescriptionFromDate, convert(varchar,requests.sdtRqtDescTodate,120) as reqDescriptionToDate, (SELECT terminology.vcTerminology from tblTerminologyLookup terminology WHERE terminology.iLabelID = deliveryModes.iLabelID and terminology.tiLocaleID = 1) as deliveryMode, (SELECT terminology.vcTerminology from tblTerminologyLookup terminology WHERE terminology.iLabelID = receivedModes.iLabelID and terminology.tiLocaleID = 1) as receivedMode, requesterTypes.vcDescription as category, requests.vcVisibleRequestID as filenumber FROM tblRequests requests WITH (NOLOCK) LEFT OUTER JOIN EC_OFFICE office WITH (NOLOCK) ON requests.tiOfficeID = office.OFFICE_ID LEFT OUTER JOIN tblRequesterTypes requesterTypes WITH (NOLOCK) ON requests.tiRequesterCategoryID = requesterTypes.tiRequesterTypeID LEFT OUTER JOIN tblRequestStatuses requeststatuses WITH (NOLOCK) on requests.irequeststatusid = requeststatuses.irequeststatusid LEFT OUTER JOIN tblReceivedModes receivedModes WITH (NOLOCK) ON requests.tiReceivedType = receivedModes.tiReceivedModeID LEFT OUTER JOIN tblDeliveryModes deliveryModes WITH (NOLOCK) ON requests.tiDeliveryType = deliveryModes.tiDeliveryModeID LEFT OUTER JOIN tblRequestTypes requestTypes WITH (NOLOCK) ON requests.tiRequestTypeID = requestTypes.tiRequestTypeID WHERE vcVisibleRequestID IN ('CFD-2022-20261', 'CFD-2021-14313','CFD-2021-12866' , 'CFD-2021-12905', 'CFD-2021-12651') AND office.OFFICE_CODE = 'CFD' AND requests.vcRequestStatus NOT IN ('Closed') GROUP BY requests.vcVisibleRequestID,requests.vcRequestStatus,requeststatuses.vcRequestStatus,requests.sdtReceivedDate, requests.sdtTargetDate, requests.sdtOriginalTargetDate, requests.vcDescription, requests.sdtRqtDescFromdate, requests.sdtRqtDescTodate, requests.sdtRequestedDate, office.OFFICE_CODE, requesterTypes.vcDescription, receivedModes.iLabelID, deliveryModes.iLabelID, requests.iRequestID, requestTypes.iLabelID, requests.vcVisibleRequestID, requests.tiOfficeID, office.OFFICE_ID + 18 + + \ No newline at end of file From b5d3608f6956e153885a314d979e779d3d66b098 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Mon, 31 Jul 2023 16:24:14 -0700 Subject: [PATCH 011/234] #4140 Datamigrations MCFD FOIMInistryrequests --- .../FOIMOD.CFD.ETL.DataMigration.dtproj | 24 +- .../FOIMinistryRequests.dtsx | 518 ++++++++++++++++++ .../Project.params | 21 + .../basequeries/FOIMinistryrequests.sql | 38 ++ datamigrations/basequeries/FOIRequests.sql | 49 ++ 5 files changed, 647 insertions(+), 3 deletions(-) create mode 100644 datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMinistryRequests.dtsx create mode 100644 datamigrations/basequeries/FOIMinistryrequests.sql create mode 100644 datamigrations/basequeries/FOIRequests.sql diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj index 61761d6ff..83966c887 100644 --- a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj @@ -26,12 +26,13 @@ WALTZ - AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAFieOUvPkcUqQDxLl/khbKQAAAAACAAAAAAADZgAAwAAAABAAAAA0uY/HT1+YGqkIDcHoIhLfAAAAAASAAACgAAAAEAAAALs+25gAe0Pus6RvAnrRt9yIAAAApcfE78axW3E+nxvTE6Ln89YvTtBE/p94gc9UvsKyKTIHZgRbq9/b8Z75mbqE+UYRdJJoVEddkELctS6J8phlHERAtbQM035Z+yaPXmcylrWMvOSz2FJQhcAr96Dhr5U87u55a09iF/DET7AXNVvwFHMvxSL6Ba+WQ3TnvuvfDsDFq4J/BoClBhQAAABr0IikLWsAxjt5m5fs+rkwiFSNHQ== + AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAFieOUvPkcUqQDxLl/khbKQAAAAACAAAAAAADZgAAwAAAABAAAAC6a+9AzSIG7OBIn+brxgPLAAAAAASAAACgAAAAEAAAAM82KkSimExaKNPoq5z0JmyIAAAA4W96VuEICkDQHGk6mUrxrr9BzGbYHh4w3tN3v1aT/S6eYrBuTQAAwxZQWtVHhqWuEHtVpxpQtPsbmaLI1Weo4Vd/H9bqBtOANwq0vZKGI1DBCzCW319QjMiVqiQEC5qWny9PTzTp2HAKTiv+g7x7gyt/UX0WtoM3syhz9VM9NTNoM9IKn4YVYxQAAABw5grK2okwk1JC0mNwqztWhVg+tA== 1 + @@ -313,10 +314,27 @@ FOIRequests 1 0 - 9 + 34 - {A53BEF0F-E656-45C4-B2EE-63BD4B829959} + {535D8DAE-8E33-4885-9C83-8FA17FFC4A15} + 8 + + + 1 + + + + + + {2A065F0D-C74A-4625-A968-B48E416F3285} + Package1 + 1 + 0 + 3 + + + {3F06372C-7E40-4226-8010-B0CFED5D0E62} 8 diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMinistryRequests.dtsx b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMinistryRequests.dtsx new file mode 100644 index 000000000..1356d95b4 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMinistryRequests.dtsx @@ -0,0 +1,518 @@ + + + 8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/Project.params b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/Project.params index 29b5e0d2c..8bb23229d 100644 --- a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/Project.params +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/Project.params @@ -42,4 +42,25 @@ SSIS:Name="DataType">18 + + + {62e916d5-5bd9-44a8-8b86-1809f18e5780} + + + 0 + 0 + 0 + SELECT requests.vcVisibleRequestID as filenumber, requests.vcDescription as requestdescription, convert(varchar,requests.sdtRqtDescFromdate,120) as reqDescriptionFromDate, convert(varchar,requests.sdtRqtDescTodate,120) as reqDescriptionToDate, convert(varchar, requests.sdtReceivedDate,120) as requestProcessStart, convert(varchar,requests.sdtTargetDate,120) as dueDate, convert(varchar, (SELECT TOP 1 cfr.sdtDueDate FROM tblRequestForDocuments cfr WITH (NOLOCK) INNER JOIN tblProgramOffices programoffice WITH (NOLOCK) ON programoffice.tiProgramOfficeID = cfr.tiProgramOfficeID WHERE requests.iRequestID = cfr.iRequestID AND requests.tiOfficeID = programoffice.tiOfficeID AND office.OFFICE_ID = programoffice.tiOfficeID AND cfr.sdtDueDate IS NOT NULL ORDER BY cfr.sdtDueDate DESC),120) as cfrDueDate, convert(varchar,sum(distinct case when requests.IREQUESTID = reviewlog.IREQUESTID and reviewlog.IDOCID = documents.IDOCID then documents.SIPAGECOUNT when requests.IREQUESTID = redaction.IREQUESTID and redaction.IDOCID = ldocuments.IDOCID then ldocuments.SIPAGECOUNT else 0 end),120) as requestPageCount FROM tblRequests requests WITH (NOLOCK) LEFT OUTER JOIN EC_OFFICE office WITH (NOLOCK) ON requests.tiOfficeID = office.OFFICE_ID LEFT OUTER JOIN tblRequestStatuses requeststatuses WITH (NOLOCK) on requests.irequeststatusid = requeststatuses.irequeststatusid LEFT OUTER JOIN dbo.TBLdocumentreviewlog reviewlog WITH (NOLOCK) ON requests.IREQUESTID = reviewlog.IREQUESTID LEFT OUTER JOIN dbo.TBLRedactionlayers redaction WITH (NOLOCK) ON requests.IREQUESTID = redaction.IREQUESTID LEFT OUTER JOIN dbo.TBLDOCUMENTS documents WITH (NOLOCK) ON reviewlog.IDOCID = documents.IDOCID LEFT OUTER JOIN dbo.TBLDOCUMENTS ldocuments WITH (NOLOCK) ON redaction.IDOCID = ldocuments.IDOCID WHERE vcVisibleRequestID IN ('CFD-2022-20261', 'CFD-2021-14313','CFD-2021-12866' , 'CFD-2021-12905', 'CFD-2021-12651') AND office.OFFICE_CODE = 'CFD' AND requests.vcRequestStatus NOT IN ('Closed') GROUP BY requests.vcVisibleRequestID,requests.vcRequestStatus,requeststatuses.vcRequestStatus,requests.sdtReceivedDate, requests.sdtTargetDate, requests.sdtOriginalTargetDate, requests.vcDescription, requests.sdtRqtDescFromdate, requests.sdtRqtDescTodate, requests.sdtRequestedDate, office.OFFICE_CODE, requests.iRequestID, requests.vcVisibleRequestID, requests.tiOfficeID, office.OFFICE_ID + 18 + + \ No newline at end of file diff --git a/datamigrations/basequeries/FOIMinistryrequests.sql b/datamigrations/basequeries/FOIMinistryrequests.sql new file mode 100644 index 000000000..8a8678d96 --- /dev/null +++ b/datamigrations/basequeries/FOIMinistryrequests.sql @@ -0,0 +1,38 @@ + + +SELECT + requests.vcVisibleRequestID as filenumber, + requests.vcDescription as requestdescription, + convert(varchar,requests.sdtRqtDescFromdate,120) as reqDescriptionFromDate, + convert(varchar,requests.sdtRqtDescTodate,120) as reqDescriptionToDate, + convert(varchar, requests.sdtReceivedDate,120) as requestProcessStart, + convert(varchar,requests.sdtTargetDate,120) as dueDate, + convert(varchar, (SELECT TOP 1 cfr.sdtDueDate FROM tblRequestForDocuments cfr WITH (NOLOCK) + INNER JOIN tblProgramOffices programoffice WITH (NOLOCK) ON programoffice.tiProgramOfficeID = cfr.tiProgramOfficeID + WHERE requests.iRequestID = cfr.iRequestID + AND requests.tiOfficeID = programoffice.tiOfficeID + AND office.OFFICE_ID = programoffice.tiOfficeID + AND cfr.sdtDueDate IS NOT NULL + ORDER BY cfr.sdtDueDate DESC),120) as cfrDueDate, + convert(varchar,sum(distinct case when requests.IREQUESTID = reviewlog.IREQUESTID and reviewlog.IDOCID = documents.IDOCID then documents.SIPAGECOUNT + when requests.IREQUESTID = redaction.IREQUESTID and redaction.IDOCID = ldocuments.IDOCID then ldocuments.SIPAGECOUNT + else 0 end),120) as requestPageCount + FROM + tblRequests requests WITH (NOLOCK) LEFT OUTER JOIN EC_OFFICE office WITH (NOLOCK) ON requests.tiOfficeID = office.OFFICE_ID + LEFT OUTER JOIN tblRequestStatuses requeststatuses WITH (NOLOCK) on requests.irequeststatusid = requeststatuses.irequeststatusid + LEFT OUTER JOIN dbo.TBLdocumentreviewlog reviewlog WITH (NOLOCK) ON requests.IREQUESTID = reviewlog.IREQUESTID + + LEFT OUTER JOIN dbo.TBLRedactionlayers redaction WITH (NOLOCK) ON requests.IREQUESTID = redaction.IREQUESTID + LEFT OUTER JOIN dbo.TBLDOCUMENTS documents WITH (NOLOCK) ON reviewlog.IDOCID = documents.IDOCID + LEFT OUTER JOIN dbo.TBLDOCUMENTS ldocuments WITH (NOLOCK) ON redaction.IDOCID = ldocuments.IDOCID + WHERE + vcVisibleRequestID IN ('CFD-2022-20261', 'CFD-2021-14313','CFD-2021-12866' , 'CFD-2021-12905', 'CFD-2021-12651') AND + + office.OFFICE_CODE = 'CFD' + + AND requests.vcRequestStatus NOT IN ('Closed') + GROUP BY requests.vcVisibleRequestID,requests.vcRequestStatus,requeststatuses.vcRequestStatus,requests.sdtReceivedDate, requests.sdtTargetDate, requests.sdtOriginalTargetDate, requests.vcDescription, + requests.sdtRqtDescFromdate, requests.sdtRqtDescTodate, requests.sdtRequestedDate, office.OFFICE_CODE, + + requests.iRequestID, + requests.vcVisibleRequestID, requests.tiOfficeID, office.OFFICE_ID \ No newline at end of file diff --git a/datamigrations/basequeries/FOIRequests.sql b/datamigrations/basequeries/FOIRequests.sql new file mode 100644 index 000000000..1ce5fbede --- /dev/null +++ b/datamigrations/basequeries/FOIRequests.sql @@ -0,0 +1,49 @@ +--DECLARE @vcVisibleRequestID VARCHAR(MAX) +--SET @vcVisibleRequestID = 'CFD-2021-12651','CFD-2021-12905' + +SELECT + (SELECT terminology.vcTerminology from tblTerminologyLookup terminology WHERE terminology.iLabelID = requestTypes.iLabelID and terminology.tiLocaleID = 1) as requestType, + requests.sdtReceivedDate as requestProcessStart, + requests.vcDescription as requestdescription, + requests.sdtRqtDescFromdate as reqDescriptionFromDate, + requests.sdtRqtDescTodate as reqDescriptionToDate, + (SELECT terminology.vcTerminology from tblTerminologyLookup terminology WHERE terminology.iLabelID = deliveryModes.iLabelID and terminology.tiLocaleID = 1) as deliveryMode, + (SELECT terminology.vcTerminology from tblTerminologyLookup terminology WHERE terminology.iLabelID = receivedModes.iLabelID and terminology.tiLocaleID = 1) as receivedMode, + requesterTypes.vcDescription as category, + requests.vcVisibleRequestID as filenumber, + + requests.sdtTargetDate as dueDate, + requests.sdtOriginalTargetDate as originalDueDate, + requests.vcRequestStatus, + requeststatuses.vcRequestStatus as _requeststatus, + (SELECT TOP 1 cfr.sdtDueDate FROM tblRequestForDocuments cfr WITH (NOLOCK) + INNER JOIN tblProgramOffices programoffice WITH (NOLOCK) ON programoffice.tiProgramOfficeID = cfr.tiProgramOfficeID + WHERE requests.iRequestID = cfr.iRequestID + AND requests.tiOfficeID = programoffice.tiOfficeID + AND office.OFFICE_ID = programoffice.tiOfficeID + AND cfr.sdtDueDate IS NOT NULL + ORDER BY cfr.sdtDueDate DESC) as cfrDueDate, + requests.sdtRequestedDate as receivedDate, + requests.sdtRequestedDate as receivedDateUF + + FROM + tblRequests requests WITH (NOLOCK) LEFT OUTER JOIN EC_OFFICE office WITH (NOLOCK) ON requests.tiOfficeID = office.OFFICE_ID + LEFT OUTER JOIN tblRequesterTypes requesterTypes WITH (NOLOCK) ON requests.tiRequesterCategoryID = requesterTypes.tiRequesterTypeID + LEFT OUTER JOIN tblRequestStatuses requeststatuses WITH (NOLOCK) on requests.irequeststatusid = requeststatuses.irequeststatusid + LEFT OUTER JOIN tblReceivedModes receivedModes WITH (NOLOCK) ON requests.tiReceivedType = receivedModes.tiReceivedModeID + LEFT OUTER JOIN tblDeliveryModes deliveryModes WITH (NOLOCK) ON requests.tiDeliveryType = deliveryModes.tiDeliveryModeID + + LEFT OUTER JOIN tblRequestTypes requestTypes WITH (NOLOCK) ON requests.tiRequestTypeID = requestTypes.tiRequestTypeID + LEFT OUTER JOIN dbo.TBLdocumentreviewlog reviewlog WITH (NOLOCK) ON requests.IREQUESTID = reviewlog.IREQUESTID + + WHERE + vcVisibleRequestID IN ('CFD-2022-20261', 'CFD-2021-14313','CFD-2021-12866' , 'CFD-2021-12905', 'CFD-2021-12651') AND + --vcVisibleRequestID IN ('CFD-2019-92736') AND + office.OFFICE_CODE = 'CFD' + + AND requests.vcRequestStatus NOT IN ('Closed') + GROUP BY requests.vcVisibleRequestID,requests.vcRequestStatus,requeststatuses.vcRequestStatus,requests.sdtReceivedDate, requests.sdtTargetDate, requests.sdtOriginalTargetDate, requests.vcDescription, + requests.sdtRqtDescFromdate, requests.sdtRqtDescTodate, requests.sdtRequestedDate, office.OFFICE_CODE, requesterTypes.vcDescription, + receivedModes.iLabelID, deliveryModes.iLabelID, + requests.iRequestID, + requestTypes.iLabelID, requests.vcVisibleRequestID, requests.tiOfficeID, office.OFFICE_ID \ No newline at end of file From 7567ea57c422e61ecd7b45bc381150749366e6dd Mon Sep 17 00:00:00 2001 From: nkan-aot <96087745+nkan-aot@users.noreply.github.com> Date: Tue, 1 Aug 2023 14:22:25 -0700 Subject: [PATCH 012/234] make bulk actions checkmark green --- .../src/components/FOI/customComponents/Records/index.js | 4 ++-- .../src/components/FOI/customComponents/Records/records.scss | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/index.js b/forms-flow-web/src/components/FOI/customComponents/Records/index.js index 2493b8467..808d3aae7 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/index.js @@ -1291,7 +1291,7 @@ export const RecordsLog = ({ Date: Tue, 1 Aug 2023 17:30:22 -0700 Subject: [PATCH 013/234] #4140 ApplicantREquestMapping ETL --- .../ApplicantRequestMappings.dtsx | 647 ++++++++++++++++++ .../CFDApplicantsMigration.dtsx | 604 ++++++++++++++++ .../FOIMOD.CFD.ETL.DataMigration.dtproj | 42 +- .../Project.params | 2 +- 4 files changed, 1291 insertions(+), 4 deletions(-) create mode 100644 datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/ApplicantRequestMappings.dtsx create mode 100644 datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/CFDApplicantsMigration.dtsx diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/ApplicantRequestMappings.dtsx b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/ApplicantRequestMappings.dtsx new file mode 100644 index 000000000..f07cecc1f --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/ApplicantRequestMappings.dtsx @@ -0,0 +1,647 @@ + + + 8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/CFDApplicantsMigration.dtsx b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/CFDApplicantsMigration.dtsx new file mode 100644 index 000000000..6d923521f --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/CFDApplicantsMigration.dtsx @@ -0,0 +1,604 @@ + + + 8 + + + + + + + + + + "public"."FOIRequestApplicants" + 1000 + 1000 + 32768 + 0 + 1252 + 0 + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + $Project::applicantsqlquery + 1252 + false + 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataSourceViewID + + + TableInfoObjectType + Table + + + + + + + DataSourceViewID + + + +]]> + \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj index 83966c887..3d2190ebe 100644 --- a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj @@ -26,13 +26,15 @@ WALTZ - AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAFieOUvPkcUqQDxLl/khbKQAAAAACAAAAAAADZgAAwAAAABAAAAC6a+9AzSIG7OBIn+brxgPLAAAAAASAAACgAAAAEAAAAM82KkSimExaKNPoq5z0JmyIAAAA4W96VuEICkDQHGk6mUrxrr9BzGbYHh4w3tN3v1aT/S6eYrBuTQAAwxZQWtVHhqWuEHtVpxpQtPsbmaLI1Weo4Vd/H9bqBtOANwq0vZKGI1DBCzCW319QjMiVqiQEC5qWny9PTzTp2HAKTiv+g7x7gyt/UX0WtoM3syhz9VM9NTNoM9IKn4YVYxQAAABw5grK2okwk1JC0mNwqztWhVg+tA== + AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAFieOUvPkcUqQDxLl/khbKQAAAAACAAAAAAADZgAAwAAAABAAAAAeEJHk31mTik8CecOG+qs5AAAAAASAAACgAAAAEAAAAPdbO3uxHeyX6kN4B5Ovxj6IAAAAs8dcZpdX8IiWLKgAN/8PMuAEeatRhYmtYerYqDo8oALpSkxO6fVastf4BNhijBrKJgziwNUFlp2d2tgYAs+WyeciTaBGOowLNdYEJPDqMEANf8V+2Zs4bM5CMUgTMPblDn2RmiXy0FwP0PUPz3wqn32xfWdpc4oIM4scyh7jjDnL56f4Q+VUhRQAAACuTG5YZxR0lLGrnaF2nOQ8xqqxBA== 1 + + @@ -331,10 +333,44 @@ Package1 1 0 - 3 + 12 + + + {7AFB3826-7143-4167-8101-43C21A36278A} + 8 + + + 1 + + + + + + {39F3640B-BFA0-4D76-8137-EC8A98C6675A} + CFDApplicantsMigration + 1 + 0 + 12 + + + {C771D95D-EF7A-4E67-A35D-A350817C33E2} + 8 + + + 1 + + + + + + {6E7CF625-11EC-4619-ACB0-07C38F8367D2} + ApplicantRequestMappings + 1 + 0 + 20 - {3F06372C-7E40-4226-8010-B0CFED5D0E62} + {CB9E68C9-3BE7-44FD-B3B8-A1A693CE13C0} 8 diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/Project.params b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/Project.params index 8bb23229d..315db995a 100644 --- a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/Project.params +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/Project.params @@ -16,7 +16,7 @@ 0 SELECT email,firstname,lastname,middleName,requesterid,birthDate,businessName, STRING_AGG(CAST(requestid as nvarchar(max)),', ') as requests FROM ( SELECT requesters.vcEmailID as email, requesters.iRequesterID as requesterid, requesters.vcFirstName as firstName, requesters.vcLastName as lastName, requesters.vcMiddleName as middleName, requestorfields.CUSTOMFIELD35 as birthDate, requesters.vcCompany as businessName, requests.vcVisibleRequestID as requestid FROM tblRequests requests WITH (NOLOCK) LEFT OUTER JOIN EC_OFFICE office WITH (NOLOCK) ON requests.tiOfficeID = office.OFFICE_ID LEFT OUTER JOIN tblRequesters requesters WITH (NOLOCK) ON requests.iRequesterID = requesters.iRequesterID LEFT OUTER JOIN dbo.TBLREQUESTERCUSTOMFIELDS requestorfields WITH (NOLOCK) ON requesters.iRequesterID = requestorfields.IREQUESTERID WHERE office.OFFICE_CODE = 'CFD' AND requests.vcRequestStatus NOT IN ('Closed') ) AS T GROUP BY T.email,T.requesterid,T.firstname,T.lastname,T.middleName,T.requesterid,T.birthDate,T.businessName ORDER BY T.firstname ASC + SSIS:Name="Value">SELECT firstname,lastname,middleName,requesterid,CONVERT(varchar(MAX),'01-01-1900',120) as birthDate,businessName, STRING_AGG(CAST(requestid as nvarchar(max)),', ') as requests ,MainApplicant FROM ( SELECT requesters.vcEmailID as email, requesters.iRequesterID as requesterid, requesters.vcFirstName as firstName, requesters.vcLastName as lastName, requesters.vcMiddleName as middleName, requestorfields.CUSTOMFIELD35 as birthDate, requesters.vcCompany as businessName, requests.vcVisibleRequestID as requestid, 1 as MainApplicant FROM tblRequests requests WITH (NOLOCK) LEFT OUTER JOIN EC_OFFICE office WITH (NOLOCK) ON requests.tiOfficeID = office.OFFICE_ID LEFT OUTER JOIN tblRequesters requesters WITH (NOLOCK) ON requests.iRequesterID = requesters.iRequesterID LEFT OUTER JOIN dbo.TBLREQUESTERCUSTOMFIELDS requestorfields WITH (NOLOCK) ON requesters.iRequesterID = requestorfields.IREQUESTERID WHERE office.OFFICE_CODE = 'CFD' AND requests.vcRequestStatus NOT IN ('Closed') AND requests.vcVisibleRequestID IN ('CFD-2022-20261', 'CFD-2021-14313','CFD-2021-12866' , 'CFD-2021-12905', 'CFD-2021-12651','CFD-2023-30011') UNION ALL SELECT onbehalf.vcEmailID as email, onbehalf.iRequesterID as requesterid, onbehalf.vcFirstName as firstName, onbehalf.vcLastName as lastName, onbehalf.vcMiddleName as middleName, requestorfields.CUSTOMFIELD35 as birthDate, onbehalf.vcCompany as businessName, requests.vcVisibleRequestID as requestid, 0 as MainApplicant FROM tblRequests requests WITH (NOLOCK) LEFT OUTER JOIN EC_OFFICE office WITH (NOLOCK) ON requests.tiOfficeID = office.OFFICE_ID LEFT OUTER JOIN tblRequesters onbehalf WITH (NOLOCK) ON requests.iOnBehalfOf = onbehalf.iRequesterID LEFT OUTER JOIN dbo.TBLREQUESTERCUSTOMFIELDS requestorfields WITH (NOLOCK) ON onbehalf.iRequesterID = requestorfields.IREQUESTERID WHERE office.OFFICE_CODE = 'CFD' AND requests.vcRequestStatus NOT IN ('Closed') AND requests.vcVisibleRequestID IN ('CFD-2022-20261', 'CFD-2021-14313','CFD-2021-12866' , 'CFD-2021-12905', 'CFD-2021-12651','CFD-2023-30011') and onbehalf.vcFirstName is NOT NULL ) AS T GROUP BY T.email,T.requesterid,T.firstname,T.lastname,T.middleName,T.requesterid,T.birthDate,T.businessName,T.MainApplicant ORDER BY T.firstname ASC 18 From c8e7faf6a432c80a91ba7e6e01f7734e7e8eaf59 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Wed, 2 Aug 2023 13:54:02 -0700 Subject: [PATCH 014/234] #4140 AXIS Migration Mapper --- .../migrations/versions/9ba0a7a4fe69_.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 request-management-api/migrations/versions/9ba0a7a4fe69_.py diff --git a/request-management-api/migrations/versions/9ba0a7a4fe69_.py b/request-management-api/migrations/versions/9ba0a7a4fe69_.py new file mode 100644 index 000000000..bf35dd5f9 --- /dev/null +++ b/request-management-api/migrations/versions/9ba0a7a4fe69_.py @@ -0,0 +1,34 @@ +"""migration mapper for AXIS to FOIMOD states + +Revision ID: 9ba0a7a4fe69 +Revises: 2e43869513c2 +Create Date: 2023-08-02 12:53:20.184435 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '9ba0a7a4fe69' +down_revision = '2e43869513c2' +branch_labels = None +depends_on = None + + +def upgrade(): + axismigration = op.create_table('AXISMigrationMapper', + sa.Column('stateonAXIS', sa.String(length=255), nullable=False), + sa.Column('stateonFOI', sa.String(length=255), nullable=True) + ) + + op.bulk_insert( + axismigration, + [ + {'stateonAXIS':'DAddRvwLog','stateonFOI':'Records Review'}, + {'stateonAXIS':'ReqforDocs','stateonFOI':'Call For Records'}, + ]) + + +def downgrade(): + op.drop_table('AXISMigrationMapper') From b3dce7da1de7127518765f782f8d134b09a7a6f1 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Fri, 4 Aug 2023 09:59:32 -0700 Subject: [PATCH 015/234] Bug Fix. Estimated Total Fees validation added. Adjusted utils function getMessage to check if estimatedTotalFees is <= to 0 to return an appropriate modal error message. Adjusted index.js in confirmation modal component to ensure that estimated total fees are > 0 for state to be changed from CFR to Fee Estimate. --- .../FOI/customComponents/ConfirmationModal/index.js | 10 +++++++--- .../FOI/customComponents/ConfirmationModal/util.js | 10 ++++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/index.js b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/index.js index 6d96bbb7e..a4c52da1c 100644 --- a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/index.js @@ -100,13 +100,14 @@ export default function ConfirmationModal({requestId, openModal, handleModal, st } const amountDue = cfrFeeData?.totalamountdue; + const estimatedTotalDue = cfrFeeData?.estimatedtotaldue; const [mailed, setMailed] = useState(false); const handleMailedChange = (event) => { setMailed(event.target.checked); setDisableSaveBtn(!event.target.checked); saveRequestObject.isofflinepayment=event.target.checked; }; - + React.useEffect(() => { setDisableSaveBtn(state.toLowerCase() === StateEnum.closed.name.toLowerCase()); @@ -118,6 +119,7 @@ export default function ConfirmationModal({requestId, openModal, handleModal, st const isBtnDisabled = () => { if ((state.toLowerCase() === StateEnum.feeassessed.name.toLowerCase() && cfrStatus === 'init') + || (state.toLowerCase() === StateEnum.feeassessed.name.toLowerCase() && estimatedTotalDue === 0) || (state.toLowerCase() === StateEnum.onhold.name.toLowerCase() && amountDue === 0) || (state.toLowerCase() === StateEnum.onhold.name.toLowerCase() && cfrStatus !== 'approved') || (state.toLowerCase() === StateEnum.onhold.name.toLowerCase() && cfrStatus === 'approved' && !saveRequestObject.email && !mailed) @@ -178,7 +180,7 @@ export default function ConfirmationModal({requestId, openModal, handleModal, st handleModal(true, fileInfoList, files); } - let message = getMessage(saveRequestObject, state, axisRequestId, currentState, requestId, cfrStatus,allowStateChange,isAnyAmountPaid); + let message = getMessage(saveRequestObject, state, axisRequestId, currentState, requestId, cfrStatus,allowStateChange,isAnyAmountPaid, estimatedTotalDue); const attchmentFileNameList = attachmentsArray?.map(_file => _file.filename); const getDaysRemaining = () => { @@ -226,6 +228,9 @@ export default function ConfirmationModal({requestId, openModal, handleModal, st ); } + else if ((state.toLowerCase() !== StateEnum.feeassessed.name.toLowerCase() || cfrStatus !== 'init') && estimatedTotalDue <= 0) { + return null; + } else if ((state.toLowerCase() !== StateEnum.feeassessed.name.toLowerCase() || cfrStatus !== 'init') && (state.toLowerCase() !== StateEnum.onhold.name.toLowerCase() && cfrStatus !== 'approved')) { return ( @@ -251,7 +256,6 @@ export default function ConfirmationModal({requestId, openModal, handleModal, st : null } ); - } } diff --git a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js index 47a3eeb84..608e64fc8 100644 --- a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js +++ b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js @@ -40,7 +40,7 @@ import { getFullnameList } from "../../../../helper/FOI/helper"; return _selectedMinistry; } - export const getMessage = (_saveRequestObject, _state, _requestNumber, _currentState, _requestId, _cfrStatus,allowStateChange,isAnyAmountPaid) => { + export const getMessage = (_saveRequestObject, _state, _requestNumber, _currentState, _requestId, _cfrStatus,allowStateChange,isAnyAmountPaid, estimatedTotalFeesDue) => { if ((_currentState?.toLowerCase() === StateEnum.closed.name.toLowerCase() && _state.toLowerCase() !== StateEnum.closed.name.toLowerCase())) { _saveRequestObject.reopen = true; return {title: "Re-Open Request", body: <>Are you sure you want to re-open Request # {_requestNumber ? _requestNumber : `U-00${_requestId}`}?
The request will be re-opened to the previous state: {_state} }; @@ -77,7 +77,13 @@ import { getFullnameList } from "../../../../helper/FOI/helper"; title: "Fee Estimate", body: "To update the state you must first complete the estimated hours in the CFR Form so that Total Fees are due." }; - } else { + } else if (estimatedTotalFeesDue <= 0) { + return { + title: "Fee Estimate", + body: "To update state to Fee Estimate, Estimated Total must be greater than $0." + } + } + else { return {title: "Fee Estimate", body: `Are you sure you want to change Request #${_requestNumber} to ${StateEnum.feeassessed.name}? The CFR Form will be locked for editing and sent to IAO for review.`}; } case StateEnum.deduplication.name.toLowerCase(): From f72337b4e49af9a341343f7c68164f0e558578f0 Mon Sep 17 00:00:00 2001 From: Nimya John <78105113+nimya-aot@users.noreply.github.com> Date: Fri, 4 Aug 2023 13:14:25 -0700 Subject: [PATCH 016/234] Update katalon-ci.yaml Version updated on the file --- .github/workflows/katalon-ci.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/katalon-ci.yaml b/.github/workflows/katalon-ci.yaml index 15879c74e..c038f7a64 100644 --- a/.github/workflows/katalon-ci.yaml +++ b/.github/workflows/katalon-ci.yaml @@ -12,7 +12,7 @@ jobs: runs-on: windows-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: ref: dev-automationscripts - name: set screen resolution @@ -35,7 +35,7 @@ jobs: dir shell: cmd - name: Katalon Studio Github Action - uses: katalon-studio/katalon-studio-github-action@v2 + uses: katalon-studio/katalon-studio-github-action@v3 with: version: '8.2.0' projectPath: '${{ github.workspace }}/testing/foi-qa-automation/foi-qa-automation.prj' @@ -50,7 +50,7 @@ jobs: dir shell: cmd - name: Setup Node.js - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: '14' - name: Create collection report @@ -58,7 +58,7 @@ jobs: npm i -g xunit-viewer xunit-viewer -r artifacts\JUnit_Report.xml -o artifacts\foi-test.html - name: Archive Katalon report - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: katalon-report path: artifacts @@ -67,9 +67,9 @@ jobs: # runs-on: macos-latest # steps: # - name: Checkout - # uses: actions/checkout@v2 + # uses: actions/checkout@v3 # - name: Katalon Studio Github Action - # uses: katalon-studio/katalon-studio-github-action@v2.1 + # uses: katalon-studio/katalon-studio-github-action@v3.2.0 # with: # version: '7.5.5' # projectPath: '${{ github.workspace }}' From ac3c229fe042a1f7005833f15610c5cb5cf3326e Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Tue, 8 Aug 2023 15:23:45 -0700 Subject: [PATCH 017/234] #4249 Migration script for MSD,GCP,OOP code update --- .../migrations/versions/e25110cba030_.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 request-management-api/migrations/versions/e25110cba030_.py diff --git a/request-management-api/migrations/versions/e25110cba030_.py b/request-management-api/migrations/versions/e25110cba030_.py new file mode 100644 index 000000000..1529de944 --- /dev/null +++ b/request-management-api/migrations/versions/e25110cba030_.py @@ -0,0 +1,40 @@ +"""Updating ministry codes for MSD,GCP,OOP,CTZ + +Revision ID: e25110cba030 +Revises: 9ba0a7a4fe69 +Create Date: 2023-08-08 14:19:03.966363 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'e25110cba030' +down_revision = '9ba0a7a4fe69' +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute('Update public."ProgramAreas" set bcgovcode = \'MSD\' where iaocode = \'MSD\';commit;') + op.execute('Update public."ProgramAreas" set bcgovcode = \'GCP\' where iaocode = \'GCP\';commit;') + op.execute('Update public."ProgramAreas" set bcgovcode = \'OOP\' where iaocode = \'OOP\';commit;') + + + op.execute('UPDATE public."OperatingTeams" SET name=\'MSD Ministry Team\', description=\'MSD Ministry Team\' WHERE name =\'SDPR Ministry Team\';commit;') + op.execute('UPDATE public."OperatingTeams" SET name=\'GCP Ministry Team\', description=\'GCP Ministry Team\' WHERE name =\'GCPE Ministry Team\';commit;') + op.execute('UPDATE public."OperatingTeams" SET name=\'OOP Ministry Team\', description=\'OOP Ministry Team\' WHERE name =\'PREM Ministry Team\';commit;') + + + +def downgrade(): + op.execute('Update public."ProgramAreas" set bcgovcode = \'SDPR\' where iaocode = \'MSD\';commit;') + op.execute('Update public."ProgramAreas" set bcgovcode = \'GCPE\' where iaocode = \'GCP\';commit;') + op.execute('Update public."ProgramAreas" set bcgovcode = \'PREM\' where iaocode = \'OOP\';commit;') + + + op.execute('UPDATE public."OperatingTeams" SET name=\'SDPR Ministry Team\', description=\'SDPR Ministry Team\' WHERE name =\'SDPR Ministry Team\';commit;') + op.execute('UPDATE public."OperatingTeams" SET name=\'GCPE Ministry Team\', description=\'GCPE Ministry Team\' WHERE name =\'GCPE Ministry Team\';commit;') + op.execute('UPDATE public."OperatingTeams" SET name=\'PREM Ministry Team\', description=\'PREM Ministry Team\' WHERE name =\'PREM Ministry Team\';commit;') + From e201a41afdbbe7202b858c43c8dab9c026be8d14 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Tue, 8 Aug 2023 15:29:28 -0700 Subject: [PATCH 018/234] #4240 Update for downgrade --- request-management-api/migrations/versions/e25110cba030_.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/request-management-api/migrations/versions/e25110cba030_.py b/request-management-api/migrations/versions/e25110cba030_.py index 1529de944..79f46f8b1 100644 --- a/request-management-api/migrations/versions/e25110cba030_.py +++ b/request-management-api/migrations/versions/e25110cba030_.py @@ -34,7 +34,7 @@ def downgrade(): op.execute('Update public."ProgramAreas" set bcgovcode = \'PREM\' where iaocode = \'OOP\';commit;') - op.execute('UPDATE public."OperatingTeams" SET name=\'SDPR Ministry Team\', description=\'SDPR Ministry Team\' WHERE name =\'SDPR Ministry Team\';commit;') - op.execute('UPDATE public."OperatingTeams" SET name=\'GCPE Ministry Team\', description=\'GCPE Ministry Team\' WHERE name =\'GCPE Ministry Team\';commit;') - op.execute('UPDATE public."OperatingTeams" SET name=\'PREM Ministry Team\', description=\'PREM Ministry Team\' WHERE name =\'PREM Ministry Team\';commit;') + op.execute('UPDATE public."OperatingTeams" SET name=\'SDPR Ministry Team\', description=\'SDPR Ministry Team\' WHERE name =\'MSD Ministry Team\';commit;') + op.execute('UPDATE public."OperatingTeams" SET name=\'GCPE Ministry Team\', description=\'GCPE Ministry Team\' WHERE name =\'GCP Ministry Team\';commit;') + op.execute('UPDATE public."OperatingTeams" SET name=\'PREM Ministry Team\', description=\'PREM Ministry Team\' WHERE name =\'OOP Ministry Team\';commit;') From cd1ea0cee20ad0e600c06411d3239203508d7e3d Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Tue, 8 Aug 2023 16:11:43 -0700 Subject: [PATCH 019/234] #4249 updates to Enums, constants ministry codes --- .../src/constants/FOI/foiministrygroupConstants.js | 6 +++--- request-management-api/request_api/utils/enums.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/forms-flow-web/src/constants/FOI/foiministrygroupConstants.js b/forms-flow-web/src/constants/FOI/foiministrygroupConstants.js index 3dc76ac82..72d78c16d 100644 --- a/forms-flow-web/src/constants/FOI/foiministrygroupConstants.js +++ b/forms-flow-web/src/constants/FOI/foiministrygroupConstants.js @@ -16,7 +16,7 @@ const MINISTRYGROUPS = { EMLI : "EMLI Ministry Team", FIN : "FIN Ministry Team", FOR : "FOR Ministry Team", - GCPE : "GCPE Ministry Team", + GCP : "GCP Ministry Team", HTH : "HTH Ministry Team", IIO : "IIO Ministry Team", IRR : "IRR Ministry Team", @@ -31,10 +31,10 @@ const MINISTRYGROUPS = { MMHA : "MMHA Ministry Team", MUNI : "MUNI Ministry Team", ENV : "ENV Ministry Team", - SDPR : "SDPR Ministry Team", + MSD : "MSD Ministry Team", OBC : "OBC Ministry Team", OCC : "OCC Ministry Team", - PREM : "PREM Ministry Team", + OOP : "OOP Ministry Team", PSA : "PSA Ministry Team", PSSG : "PSSG Ministry Team", TACS : "TACS Ministry Team", diff --git a/request-management-api/request_api/utils/enums.py b/request-management-api/request_api/utils/enums.py index b61187175..3e0c8d8a2 100644 --- a/request-management-api/request_api/utils/enums.py +++ b/request-management-api/request_api/utils/enums.py @@ -47,7 +47,7 @@ class MinistryTeamWithKeycloackGroup(Enum): ENV = "ENV Ministry Team" FIN = "FIN Ministry Team" FOR = "FOR Ministry Team" - GCPE = "GCPE Ministry Team" + GCP = "GCP Ministry Team" HTH = "HTH Ministry Team" IIO = "IIO Ministry Team" IRR = "IRR Ministry Team" @@ -62,10 +62,10 @@ class MinistryTeamWithKeycloackGroup(Enum): MUNI = "MUNI Ministry Team" OBC = "OBC Ministry Team" OCC = "OCC Ministry Team" - PREM = "PREM Ministry Team" + OOP = "OOP Ministry Team" PSA = "PSA Ministry Team" PSSG = "PSSG Ministry Team" - SDPR = "SDPR Ministry Team" + MSD = "MSD Ministry Team" TACS = "TACS Ministry Team" TIC = "TIC Ministry Team" TRAN = "TRAN Ministry Team" From 640094a4930debd90ee13f8ed828a5ed639a244c Mon Sep 17 00:00:00 2001 From: Nimya John <78105113+nimya-aot@users.noreply.github.com> Date: Wed, 9 Aug 2023 11:31:45 -0700 Subject: [PATCH 020/234] Update katalon-ci.yaml --- .github/workflows/katalon-ci.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/katalon-ci.yaml b/.github/workflows/katalon-ci.yaml index c038f7a64..e90d1638b 100644 --- a/.github/workflows/katalon-ci.yaml +++ b/.github/workflows/katalon-ci.yaml @@ -12,7 +12,7 @@ jobs: runs-on: windows-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v3.0 with: ref: dev-automationscripts - name: set screen resolution @@ -35,7 +35,7 @@ jobs: dir shell: cmd - name: Katalon Studio Github Action - uses: katalon-studio/katalon-studio-github-action@v3 + uses: katalon-studio/katalon-studio-github-action@v3.0 with: version: '8.2.0' projectPath: '${{ github.workspace }}/testing/foi-qa-automation/foi-qa-automation.prj' @@ -50,7 +50,7 @@ jobs: dir shell: cmd - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v3.0 with: node-version: '14' - name: Create collection report @@ -58,7 +58,7 @@ jobs: npm i -g xunit-viewer xunit-viewer -r artifacts\JUnit_Report.xml -o artifacts\foi-test.html - name: Archive Katalon report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v3.0 with: name: katalon-report path: artifacts From 37fcec8996836e00ed725dbbb0a9f8f53a1589cd Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Thu, 10 Aug 2023 22:34:05 -0700 Subject: [PATCH 021/234] #4141 Base Doc migration , project check in --- .gitignore | 14 ++++ .../FOIMOD.CFD.ConsoleApp.DocMigration.sln | 67 ++++++++++++++++++ .../FOIMOD.CFD.ConsoleApp.DocMigration.csproj | 10 +++ .../Program.cs | 2 + .../FOIMOD.CFD.DocMigration.BAL/Class1.cs | 7 ++ .../FOIMOD.CFD.DocMigration.BAL.csproj | 9 +++ .../FOIMOD.CFD.DocMigration.DAL/Class1.cs | 7 ++ .../FOIMOD.CFD.DocMigration.AXIS.DAL.csproj | 9 +++ .../Class1.cs | 7 ++ ...FOIMOD.CFD.DocMigration.FOIFLOW.DAL.csproj | 9 +++ .../FOIFLOWDestination/UploadFile.cs | 37 ++++++++++ .../FOIFLOWDestination/UploadType.cs | 10 +++ .../FOIMOD.CFD.DocMigration.Models.csproj | 13 ++++ .../Class1.cs | 7 ++ .../FOIMOD.CFD.DocMigration.S3Uploader.csproj | 9 +++ ...OD.CFD.DocMigration.Utils.UnitTests.csproj | 24 +++++++ .../S3UploadTest.cs | 48 +++++++++++++ .../Usings.cs | 4 ++ .../samples/DOCX1.pdf | Bin 0 -> 49449 bytes .../DocMigrationS3Client.cs | 43 +++++++++++ .../FOIMOD.CFD.DocMigration.Utils.csproj | 17 +++++ 21 files changed, 353 insertions(+) create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.sln create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.csproj create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/Program.cs create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/Class1.cs create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/FOIMOD.CFD.DocMigration.BAL.csproj create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/Class1.cs create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/FOIMOD.CFD.DocMigration.AXIS.DAL.csproj create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/Class1.cs create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.csproj create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/FOIFLOWDestination/UploadFile.cs create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/FOIFLOWDestination/UploadType.cs create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/FOIMOD.CFD.DocMigration.Models.csproj create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.S3Uploader/Class1.cs create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.S3Uploader/FOIMOD.CFD.DocMigration.S3Uploader.csproj create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/FOIMOD.CFD.DocMigration.Utils.UnitTests.csproj create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/S3UploadTest.cs create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/Usings.cs create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/samples/DOCX1.pdf create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/DocMigrationS3Client.cs create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/FOIMOD.CFD.DocMigration.Utils.csproj diff --git a/.gitignore b/.gitignore index cda80c750..c19df07ab 100644 --- a/.gitignore +++ b/.gitignore @@ -92,3 +92,17 @@ axisintegrationapi/MCS.FOI.AXISIntegration/MCS.FOI.AXISIntegrationWebAPI/Propert axisintegrationapi/MCS.FOI.AXISIntegration/MCS.FOI.AXISIntegrationWebAPI/Properties/PublishProfiles/FolderProfile.pubxml.user apps/ datamigrations/FOIMOD.CFD.ETL.DataMigration/.vs/FOIMOD.CFD.ETL.DataMigration/v16/.suo + +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/*/bin/* +datamigrations/FOIMOD.CFD.DocMigration.AXIS.DAL/*/bin/* +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/.vs/* +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/obj/* +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/bin/* +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/obj/* + +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/obj/* +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/obj/* +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.S3Uploader/obj/* +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/obj/* +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/obj/* +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/obj/* \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.sln b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.sln new file mode 100644 index 000000000..f7fd4ef94 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.sln @@ -0,0 +1,67 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33213.308 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FOIMOD.CFD.ConsoleApp.DocMigration", "FOIMOD.CFD.ConsoleApp.DocMigration\FOIMOD.CFD.ConsoleApp.DocMigration.csproj", "{F3EE4F36-2422-4F11-ADEA-8455BA9B7A27}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FOIMOD.CFD.DocMigration.AXIS.DAL", "FOIMOD.CFD.DocMigration.DAL\FOIMOD.CFD.DocMigration.AXIS.DAL.csproj", "{F63AE4FE-892B-40EB-8D88-3BC3433A0FF5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FOIMOD.CFD.DocMigration.S3Uploader", "FOIMOD.CFD.DocMigration.S3Uploader\FOIMOD.CFD.DocMigration.S3Uploader.csproj", "{59E9C03A-DE05-48ED-8B16-252CC23DB5E6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FOIMOD.CFD.DocMigration.FOIFLOW.DAL", "FOIMOD.CFD.DocMigration.FOIFLOW.DAL\FOIMOD.CFD.DocMigration.FOIFLOW.DAL.csproj", "{F2A928AC-15B5-4124-95D1-E7706000EFB4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FOIMOD.CFD.DocMigration.BAL", "FOIMOD.CFD.DocMigration.BAL\FOIMOD.CFD.DocMigration.BAL.csproj", "{FF7CB66C-60A6-4130-919A-3DBECB869CAE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FOIMOD.CFD.DocMigration.Utils", "FOIMOD.CFD.DocMigration.Utils\FOIMOD.CFD.DocMigration.Utils.csproj", "{68B638B6-9118-48CE-AC5A-A1D00265B653}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FOIMOD.CFD.DocMigration.Models", "FOIMOD.CFD.DocMigration.Models\FOIMOD.CFD.DocMigration.Models.csproj", "{AC053E49-6F19-4F5D-AF91-DA6CFD4C4D74}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FOIMOD.CFD.DocMigration.Utils.UnitTests", "FOIMOD.CFD.DocMigration.Utils.UnitTests\FOIMOD.CFD.DocMigration.Utils.UnitTests.csproj", "{9D27E50C-7209-4BDB-9379-627A7CD9B2FA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F3EE4F36-2422-4F11-ADEA-8455BA9B7A27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F3EE4F36-2422-4F11-ADEA-8455BA9B7A27}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F3EE4F36-2422-4F11-ADEA-8455BA9B7A27}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F3EE4F36-2422-4F11-ADEA-8455BA9B7A27}.Release|Any CPU.Build.0 = Release|Any CPU + {F63AE4FE-892B-40EB-8D88-3BC3433A0FF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F63AE4FE-892B-40EB-8D88-3BC3433A0FF5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F63AE4FE-892B-40EB-8D88-3BC3433A0FF5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F63AE4FE-892B-40EB-8D88-3BC3433A0FF5}.Release|Any CPU.Build.0 = Release|Any CPU + {59E9C03A-DE05-48ED-8B16-252CC23DB5E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {59E9C03A-DE05-48ED-8B16-252CC23DB5E6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {59E9C03A-DE05-48ED-8B16-252CC23DB5E6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {59E9C03A-DE05-48ED-8B16-252CC23DB5E6}.Release|Any CPU.Build.0 = Release|Any CPU + {F2A928AC-15B5-4124-95D1-E7706000EFB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2A928AC-15B5-4124-95D1-E7706000EFB4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2A928AC-15B5-4124-95D1-E7706000EFB4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2A928AC-15B5-4124-95D1-E7706000EFB4}.Release|Any CPU.Build.0 = Release|Any CPU + {FF7CB66C-60A6-4130-919A-3DBECB869CAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF7CB66C-60A6-4130-919A-3DBECB869CAE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF7CB66C-60A6-4130-919A-3DBECB869CAE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF7CB66C-60A6-4130-919A-3DBECB869CAE}.Release|Any CPU.Build.0 = Release|Any CPU + {68B638B6-9118-48CE-AC5A-A1D00265B653}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68B638B6-9118-48CE-AC5A-A1D00265B653}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68B638B6-9118-48CE-AC5A-A1D00265B653}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68B638B6-9118-48CE-AC5A-A1D00265B653}.Release|Any CPU.Build.0 = Release|Any CPU + {AC053E49-6F19-4F5D-AF91-DA6CFD4C4D74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AC053E49-6F19-4F5D-AF91-DA6CFD4C4D74}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AC053E49-6F19-4F5D-AF91-DA6CFD4C4D74}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AC053E49-6F19-4F5D-AF91-DA6CFD4C4D74}.Release|Any CPU.Build.0 = Release|Any CPU + {9D27E50C-7209-4BDB-9379-627A7CD9B2FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D27E50C-7209-4BDB-9379-627A7CD9B2FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D27E50C-7209-4BDB-9379-627A7CD9B2FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D27E50C-7209-4BDB-9379-627A7CD9B2FA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {ED380BD1-F2BB-40F6-A8D3-12308661A924} + EndGlobalSection +EndGlobal diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.csproj b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.csproj new file mode 100644 index 000000000..f02677bf6 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.csproj @@ -0,0 +1,10 @@ + + + + Exe + net7.0 + enable + enable + + + diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/Program.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/Program.cs new file mode 100644 index 000000000..3751555cb --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/Program.cs @@ -0,0 +1,2 @@ +// See https://aka.ms/new-console-template for more information +Console.WriteLine("Hello, World!"); diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/Class1.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/Class1.cs new file mode 100644 index 000000000..6612081f1 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/Class1.cs @@ -0,0 +1,7 @@ +namespace FOIMOD.CFD.DocMigration.BAL +{ + public class Class1 + { + + } +} \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/FOIMOD.CFD.DocMigration.BAL.csproj b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/FOIMOD.CFD.DocMigration.BAL.csproj new file mode 100644 index 000000000..cfadb03dd --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/FOIMOD.CFD.DocMigration.BAL.csproj @@ -0,0 +1,9 @@ + + + + net7.0 + enable + enable + + + diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/Class1.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/Class1.cs new file mode 100644 index 000000000..08eb29cb3 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/Class1.cs @@ -0,0 +1,7 @@ +namespace FOIMOD.CFD.DocMigration.DAL +{ + public class Class1 + { + + } +} \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/FOIMOD.CFD.DocMigration.AXIS.DAL.csproj b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/FOIMOD.CFD.DocMigration.AXIS.DAL.csproj new file mode 100644 index 000000000..cfadb03dd --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/FOIMOD.CFD.DocMigration.AXIS.DAL.csproj @@ -0,0 +1,9 @@ + + + + net7.0 + enable + enable + + + diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/Class1.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/Class1.cs new file mode 100644 index 000000000..1b85cb4e1 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/Class1.cs @@ -0,0 +1,7 @@ +namespace FOIMOD.CFD.DocMigration.FOIFLOW.DAL +{ + public class Class1 + { + + } +} \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.csproj b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.csproj new file mode 100644 index 000000000..cfadb03dd --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.csproj @@ -0,0 +1,9 @@ + + + + net7.0 + enable + enable + + + diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/FOIFLOWDestination/UploadFile.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/FOIFLOWDestination/UploadFile.cs new file mode 100644 index 000000000..526d9b7b7 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/FOIFLOWDestination/UploadFile.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FOIMOD.CFD.DocMigration.Models.FOIFLOWDestination +{ + public class UploadFile + { + + public string SourceFileName { get; set; } + + public string DestinationFileName { get; set; } + + public string AXISRequestID { get; set; } + + public string SubFolderPath { get; set; } + + public UploadType UploadType { get; set; } + + public string S3BucketName { get; set; } + + public string ContentType { get; set; } + + public Stream FileStream { get; set; } + } + + public enum UploadType + { + Attachments = 1, + Records = 2 + + } + + +} diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/FOIFLOWDestination/UploadType.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/FOIFLOWDestination/UploadType.cs new file mode 100644 index 000000000..61dd923f8 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/FOIFLOWDestination/UploadType.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FOIMOD.CFD.DocMigration.Models.FOIFLOWDestination +{ + +} diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/FOIMOD.CFD.DocMigration.Models.csproj b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/FOIMOD.CFD.DocMigration.Models.csproj new file mode 100644 index 000000000..87ecebb67 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/FOIMOD.CFD.DocMigration.Models.csproj @@ -0,0 +1,13 @@ + + + + net7.0 + enable + enable + + + + + + + diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.S3Uploader/Class1.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.S3Uploader/Class1.cs new file mode 100644 index 000000000..72bb4515c --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.S3Uploader/Class1.cs @@ -0,0 +1,7 @@ +namespace FOIMOD.CFD.DocMigration.S3Uploader +{ + public class Class1 + { + + } +} \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.S3Uploader/FOIMOD.CFD.DocMigration.S3Uploader.csproj b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.S3Uploader/FOIMOD.CFD.DocMigration.S3Uploader.csproj new file mode 100644 index 000000000..cfadb03dd --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.S3Uploader/FOIMOD.CFD.DocMigration.S3Uploader.csproj @@ -0,0 +1,9 @@ + + + + net7.0 + enable + enable + + + diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/FOIMOD.CFD.DocMigration.Utils.UnitTests.csproj b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/FOIMOD.CFD.DocMigration.Utils.UnitTests.csproj new file mode 100644 index 000000000..dc4220580 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/FOIMOD.CFD.DocMigration.Utils.UnitTests.csproj @@ -0,0 +1,24 @@ + + + + net7.0 + enable + enable + + false + + + + + + + + + + + + + + + + diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/S3UploadTest.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/S3UploadTest.cs new file mode 100644 index 000000000..2742fea11 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/S3UploadTest.cs @@ -0,0 +1,48 @@ + + +using Amazon.Runtime; +using Amazon.S3; +using Amazon.S3.Model; +using Microsoft.VisualStudio.TestPlatform.Utilities; +using System.Buffers.Text; + +namespace FOIMOD.CFD.DocMigration.Utils.UnitTests +{ + [TestClass] + public class S3UploadTest + { + [TestMethod] + public void S3Upload_UploadFileAsync() + { + string baseA = AppDomain.CurrentDomain.SetupInformation.ApplicationBase; + string testfile = System.IO.Directory.GetParent(baseA).Parent.FullName; + + AWSCredentials s3credentials = new BasicAWSCredentials("", ""); + AmazonS3Config config = new() + { + ServiceURL = "https://citz-foi-prod.objectstore.gov.bc.ca/" + }; + AmazonS3Client amazonS3Client = new AmazonS3Client(s3credentials, config); + + DocMigrationS3Client docMigrationS3Client = new DocMigrationS3Client(amazonS3Client); + + + using var client = new HttpClient(); + using (FileStream fs = File.Open(Path.Combine(getSourceFolder(),"DOCX1.pdf"), FileMode.Open)) + { + fs.Position = 0; + var destinationfilename = string.Format("{0}.pdf", Guid.NewGuid().ToString()); + + UploadFile uploadFile = new UploadFile() { ContentType="application/pdf", AXISRequestID = "TEST-10001-12342", DestinationFileName = destinationfilename, S3BucketName = "test123-protected", SubFolderPath = "test123-protected/Abintest/unittestmigration", UploadType = UploadType.Attachments, SourceFileName = "DOC1.pdf", FileStream = fs }; + var result = docMigrationS3Client.UploadFileAsync(uploadFile).Result; + } + } + + private string getSourceFolder() + { + return "C:\\AOT\\FOI\\Source\\foi-flow\\datamigrations\\FOIMOD.CFD.ConsoleApp.DocMigration\\FOIMOD.CFD.DocMigration.Utils.UnitTests\\samples\\"; + } + + + } +} \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/Usings.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/Usings.cs new file mode 100644 index 000000000..400371e9c --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/Usings.cs @@ -0,0 +1,4 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; + +global using FOIMOD.CFD.DocMigration.Utils; +global using FOIMOD.CFD.DocMigration.Models.FOIFLOWDestination; \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/samples/DOCX1.pdf b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/samples/DOCX1.pdf new file mode 100644 index 0000000000000000000000000000000000000000..31d2528d75452f7f3817b5788ad9fba423abdf09 GIT binary patch literal 49449 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 UploadFileAsync(UploadFile file) + { + var presignedPutURL = this.GetPresignedURL(_s3Client, string.Format("{0}/{1}",file.SubFolderPath,file.DestinationFileName), HttpVerb.PUT); + using var client = new HttpClient(); + file.FileStream.Position = 0; + using (StreamContent strm = new(file.FileStream)) + { + strm.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream"); + return await client.PutAsync(presignedPutURL, strm); + } + + } + } +} \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/FOIMOD.CFD.DocMigration.Utils.csproj b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/FOIMOD.CFD.DocMigration.Utils.csproj new file mode 100644 index 000000000..dde289d18 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/FOIMOD.CFD.DocMigration.Utils.csproj @@ -0,0 +1,17 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + From 04dd595e1be84333aec913f4ee3beb643b2d0704 Mon Sep 17 00:00:00 2001 From: nimya-aot Date: Fri, 11 Aug 2023 10:52:02 -0700 Subject: [PATCH 022/234] yaml updation --- .github/workflows/katalon-ci.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/katalon-ci.yaml b/.github/workflows/katalon-ci.yaml index c038f7a64..e90d1638b 100644 --- a/.github/workflows/katalon-ci.yaml +++ b/.github/workflows/katalon-ci.yaml @@ -12,7 +12,7 @@ jobs: runs-on: windows-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v3.0 with: ref: dev-automationscripts - name: set screen resolution @@ -35,7 +35,7 @@ jobs: dir shell: cmd - name: Katalon Studio Github Action - uses: katalon-studio/katalon-studio-github-action@v3 + uses: katalon-studio/katalon-studio-github-action@v3.0 with: version: '8.2.0' projectPath: '${{ github.workspace }}/testing/foi-qa-automation/foi-qa-automation.prj' @@ -50,7 +50,7 @@ jobs: dir shell: cmd - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v3.0 with: node-version: '14' - name: Create collection report @@ -58,7 +58,7 @@ jobs: npm i -g xunit-viewer xunit-viewer -r artifacts\JUnit_Report.xml -o artifacts\foi-test.html - name: Archive Katalon report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v3.0 with: name: katalon-report path: artifacts From 5665f5cafe1d17917d6ce50bfa7875583bbd04d7 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Fri, 11 Aug 2023 14:40:07 -0700 Subject: [PATCH 023/234] Creating new API endpoint for Program Area Divisions and Program Areas in FOI-FLOW. WIP --- .../resources/foiflowmasterdata.py | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/request-management-api/request_api/resources/foiflowmasterdata.py b/request-management-api/request_api/resources/foiflowmasterdata.py index 297632c9a..17fd80103 100644 --- a/request-management-api/request_api/resources/foiflowmasterdata.py +++ b/request-management-api/request_api/resources/foiflowmasterdata.py @@ -71,7 +71,7 @@ def get(): except BusinessException: return "Error happened while accessing applicant categories" , 500 - +## USE THIS API CALL TO GET ALL PROGRAM AREAS @cors_preflight('GET,OPTIONS') @API.route('/foiflow/programareas') class FOIFlowProgramAreas(Resource): @@ -185,6 +185,24 @@ def get(bcgovcode): return jsondata , 200 except BusinessException: return "Error happened while accessing divisions" , 500 + +#MAKE API CALL HERE TO GATHER ALL PROGRAM AREA DIVISIONS +@cors_preflight('GET,OPTIONS') +@API.route('/foiflow/divisions') +class FOIFlowDivisions(Resource): + """Retrieves all active divisions. + """ + def get(): + pass + +#MAKE API CALL HERE TO GATHER ASSOCIATED PROGRAM AREA DIVISIONS FOR RECORDS/DOCUMENTS BASED ON MINISTRY ID / BC GOV CODE / PROGRAM ID +@cors_preflight('GET,OPTIONS') +@API.route('/foiflow/divisions/') +class FOIFlowDivisions(Resource): + """Retrieves all active divisions. + """ + def get(): + pass @cors_preflight('GET,OPTIONS') @API.route('/foiflow/closereasons') From ccba80fc10ceba6619daafeaea07976e7b058b5b Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Fri, 11 Aug 2023 15:10:11 -0700 Subject: [PATCH 024/234] Completed get route to gather all program area divisions. WIP get route to gather all program area divisions associated with a specific foi request record --- .../resources/foiflowmasterdata.py | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/request-management-api/request_api/resources/foiflowmasterdata.py b/request-management-api/request_api/resources/foiflowmasterdata.py index 17fd80103..3a064ff41 100644 --- a/request-management-api/request_api/resources/foiflowmasterdata.py +++ b/request-management-api/request_api/resources/foiflowmasterdata.py @@ -33,6 +33,7 @@ from request_api.services.extensionreasonservice import extensionreasonservice from request_api.services.cacheservice import cacheservice from request_api.services.subjectcodeservice import subjectcodeservice +from request_api.services.programareadivisionservice import programareadivisionservice import json import request_api import requests @@ -192,15 +193,30 @@ def get(bcgovcode): class FOIFlowDivisions(Resource): """Retrieves all active divisions. """ + @staticmethod + @TRACER.trace() + @cross_origin(origins=allowedorigins()) + @auth.require + @auth.ismemberofgroups(getrequiredmemberships()) def get(): - pass + try: + division_data = programareadivisionservice.getallprogramareadivisions() + json_response= json.dumps(division_data) + return json_response, 200 + except BusinessException: + return "Error occured while accessing divisions", 500 #MAKE API CALL HERE TO GATHER ASSOCIATED PROGRAM AREA DIVISIONS FOR RECORDS/DOCUMENTS BASED ON MINISTRY ID / BC GOV CODE / PROGRAM ID @cors_preflight('GET,OPTIONS') -@API.route('/foiflow/divisions/') +@API.route('/foiflow/divisions/') class FOIFlowDivisions(Resource): - """Retrieves all active divisions. + """Retrieves divisions associated with records based on ministry request id. """ + @staticmethod + @TRACER.trace() + @cross_origin(origins=allowedorigins()) + @auth.require + @auth.ismemberofgroups(getrequiredmemberships()) def get(): pass From 9da36ab3f1706860749497b3363f6254a6f8917c Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Fri, 11 Aug 2023 16:51:25 -0700 Subject: [PATCH 025/234] #4141 Updates with PDFStitching , unit tests --- .../Document/PDFDocToMerge.cs | 10 +++ ...OD.CFD.DocMigration.Utils.UnitTests.csproj | 1 + .../S3UploadTest.cs | 68 ++++++++++++++---- .../samples/cat1.pdf | Bin 0 -> 117899 bytes .../DocMigrationPDFStitcher.cs | 50 +++++++++++++ .../FOIMOD.CFD.DocMigration.Utils.csproj | 1 + 6 files changed, 116 insertions(+), 14 deletions(-) create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/Document/PDFDocToMerge.cs create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/samples/cat1.pdf create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/DocMigrationPDFStitcher.cs diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/Document/PDFDocToMerge.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/Document/PDFDocToMerge.cs new file mode 100644 index 000000000..4ddff91d9 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/Document/PDFDocToMerge.cs @@ -0,0 +1,10 @@ +namespace FOIMOD.CFD.DocMigration.Models.Document +{ + public class PDFDocToMerge + { + public int PageSequenceNumber { get; set; } + + public string PageFilePath { get; set; } + + } +} diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/FOIMOD.CFD.DocMigration.Utils.UnitTests.csproj b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/FOIMOD.CFD.DocMigration.Utils.UnitTests.csproj index dc4220580..46e27df74 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/FOIMOD.CFD.DocMigration.Utils.UnitTests.csproj +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/FOIMOD.CFD.DocMigration.Utils.UnitTests.csproj @@ -14,6 +14,7 @@ + diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/S3UploadTest.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/S3UploadTest.cs index 2742fea11..361d0ffca 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/S3UploadTest.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/S3UploadTest.cs @@ -3,7 +3,9 @@ using Amazon.Runtime; using Amazon.S3; using Amazon.S3.Model; +using FOIMOD.CFD.DocMigration.Models.Document; using Microsoft.VisualStudio.TestPlatform.Utilities; +using PdfSharpCore.Pdf; using System.Buffers.Text; namespace FOIMOD.CFD.DocMigration.Utils.UnitTests @@ -11,31 +13,69 @@ namespace FOIMOD.CFD.DocMigration.Utils.UnitTests [TestClass] public class S3UploadTest { + public AWSCredentials s3credentials = new BasicAWSCredentials("", ""); + + public AmazonS3Config config = new() + { + ServiceURL = "https://citz-foi-prod.objectstore.gov.bc.ca/" + }; [TestMethod] - public void S3Upload_UploadFileAsync() + public void S3Upload_UploadFileAsync() { - string baseA = AppDomain.CurrentDomain.SetupInformation.ApplicationBase; - string testfile = System.IO.Directory.GetParent(baseA).Parent.FullName; - - AWSCredentials s3credentials = new BasicAWSCredentials("", ""); - AmazonS3Config config = new() - { - ServiceURL = "https://citz-foi-prod.objectstore.gov.bc.ca/" - }; + + + + AmazonS3Client amazonS3Client = new AmazonS3Client(s3credentials, config); - + DocMigrationS3Client docMigrationS3Client = new DocMigrationS3Client(amazonS3Client); using var client = new HttpClient(); - using (FileStream fs = File.Open(Path.Combine(getSourceFolder(),"DOCX1.pdf"), FileMode.Open)) + using (FileStream fs = File.Open(Path.Combine(getSourceFolder(), "DOCX1.pdf"), FileMode.Open)) + { + fs.Position = 0; + var destinationfilename = string.Format("{0}.pdf", Guid.NewGuid().ToString()); + + UploadFile uploadFile = new UploadFile() { ContentType = "application/pdf", AXISRequestID = "TEST-10001-12342", DestinationFileName = destinationfilename, S3BucketName = "test123-protected", SubFolderPath = "test123-protected/Abintest/unittestmigration", UploadType = UploadType.Attachments, SourceFileName = "DOC1.pdf", FileStream = fs }; + var result = docMigrationS3Client.UploadFileAsync(uploadFile).Result; + Assert.IsNotNull(result); + Assert.IsTrue(result.IsSuccessStatusCode); + } + } + + [TestMethod] + public void PDFMerge_UploadTest() + { + PDFDocToMerge[] pDFDocToMerges = new PDFDocToMerge[2]; + pDFDocToMerges[0] = new PDFDocToMerge() { PageFilePath = Path.Combine(getSourceFolder(), "DOCX1.pdf"), PageSequenceNumber = 2 }; + pDFDocToMerges[1] = new PDFDocToMerge() { PageFilePath = Path.Combine(getSourceFolder(), "cat1.pdf"), PageSequenceNumber = 1 }; + + + using (DocMigrationPDFStitcher docMigrationPDFStitcher = new DocMigrationPDFStitcher()) { + using Stream fs = docMigrationPDFStitcher.MergePDFs(pDFDocToMerges); + + AmazonS3Client amazonS3Client = new AmazonS3Client(s3credentials, config); + + DocMigrationS3Client docMigrationS3Client = new DocMigrationS3Client(amazonS3Client); + + + using var client = new HttpClient(); + fs.Position = 0; - var destinationfilename = string.Format("{0}.pdf", Guid.NewGuid().ToString()); - - UploadFile uploadFile = new UploadFile() { ContentType="application/pdf", AXISRequestID = "TEST-10001-12342", DestinationFileName = destinationfilename, S3BucketName = "test123-protected", SubFolderPath = "test123-protected/Abintest/unittestmigration", UploadType = UploadType.Attachments, SourceFileName = "DOC1.pdf", FileStream = fs }; + var destinationfilename = string.Format("{0}.pdf", Guid.NewGuid().ToString()); + + UploadFile uploadFile = new UploadFile() { ContentType = "application/pdf", AXISRequestID = "TEST-10001-12342", DestinationFileName = destinationfilename, S3BucketName = "test123-protected", SubFolderPath = "test123-protected/Abintest/unittestmigration", UploadType = UploadType.Attachments, SourceFileName = "DOC1.pdf", FileStream = fs }; var result = docMigrationS3Client.UploadFileAsync(uploadFile).Result; + Assert.IsNotNull(result); + Assert.IsTrue(result.IsSuccessStatusCode); + } + + + + } private string getSourceFolder() diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/samples/cat1.pdf b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/samples/cat1.pdf new file mode 100644 index 0000000000000000000000000000000000000000..1b329ca52369aa2a8bd7e489f1a56e213e9fbc89 GIT binary patch literal 117899 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 Sk1RN9fAgE08^Ha$-~S8!d-rPq literal 0 HcmV?d00001 diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/DocMigrationPDFStitcher.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/DocMigrationPDFStitcher.cs new file mode 100644 index 000000000..feaacb8f0 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/DocMigrationPDFStitcher.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Amazon; +using FOIMOD.CFD.DocMigration.Models.Document; +using PdfSharpCore; +using PdfSharpCore.Pdf; +using PdfSharpCore.Pdf.IO; + +namespace FOIMOD.CFD.DocMigration.Utils +{ + public class DocMigrationPDFStitcher :IDisposable + { + + private Stream mergeddocstream = null; + public DocMigrationPDFStitcher() + { + + } + + public void Dispose() + { + + mergeddocstream.Close(); + mergeddocstream.Dispose(); + } + + public Stream MergePDFs(PDFDocToMerge[] pdfpages) + { + var _pdfpages = pdfpages.OrderBy(p => p.PageSequenceNumber).ToArray(); + using (PdfDocument pdfdocument = new PdfDocument()) + { + foreach (PDFDocToMerge pDFDocToMerge in _pdfpages) + { + using PdfDocument inputPDFDocument = PdfReader.Open(pDFDocToMerge.PageFilePath, PdfDocumentOpenMode.Import); + pdfdocument.Version = inputPDFDocument.Version; + foreach (PdfPage page in inputPDFDocument.Pages) + { + pdfdocument.AddPage(page); + } + } + mergeddocstream = new MemoryStream(); + pdfdocument.Save(mergeddocstream); + } + return mergeddocstream; + } + } +} diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/FOIMOD.CFD.DocMigration.Utils.csproj b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/FOIMOD.CFD.DocMigration.Utils.csproj index dde289d18..85c75d9ab 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/FOIMOD.CFD.DocMigration.Utils.csproj +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/FOIMOD.CFD.DocMigration.Utils.csproj @@ -8,6 +8,7 @@ + From 597c9b96851c20ced73d2beab34809f671f9799e Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Mon, 14 Aug 2023 14:00:32 -0700 Subject: [PATCH 026/234] Added 2 API endpoints that return all divisions and all divisions associated with each record (based on request ID). WIP implementation & testing --- .../resources/foiflowmasterdata.py | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/request-management-api/request_api/resources/foiflowmasterdata.py b/request-management-api/request_api/resources/foiflowmasterdata.py index 3a064ff41..e7a51c0e1 100644 --- a/request-management-api/request_api/resources/foiflowmasterdata.py +++ b/request-management-api/request_api/resources/foiflowmasterdata.py @@ -34,6 +34,7 @@ from request_api.services.cacheservice import cacheservice from request_api.services.subjectcodeservice import subjectcodeservice from request_api.services.programareadivisionservice import programareadivisionservice +from request_api.services.recordservice import recordservice import json import request_api import requests @@ -72,7 +73,7 @@ def get(): except BusinessException: return "Error happened while accessing applicant categories" , 500 -## USE THIS API CALL TO GET ALL PROGRAM AREAS +## USE THIS API CALL TO GET ALL PROGRAM AREAS - TEST WIP @cors_preflight('GET,OPTIONS') @API.route('/foiflow/programareas') class FOIFlowProgramAreas(Resource): @@ -187,7 +188,7 @@ def get(bcgovcode): except BusinessException: return "Error happened while accessing divisions" , 500 -#MAKE API CALL HERE TO GATHER ALL PROGRAM AREA DIVISIONS +#MAKE API CALL HERE TO GATHER ALL PROGRAM AREA DIVISIONS - TEST WIP @cors_preflight('GET,OPTIONS') @API.route('/foiflow/divisions') class FOIFlowDivisions(Resource): @@ -204,21 +205,35 @@ def get(): json_response= json.dumps(division_data) return json_response, 200 except BusinessException: - return "Error occured while accessing divisions", 500 + return "Error happened while accessing divisions", 500 -#MAKE API CALL HERE TO GATHER ASSOCIATED PROGRAM AREA DIVISIONS FOR RECORDS/DOCUMENTS BASED ON MINISTRY ID / BC GOV CODE / PROGRAM ID +#MAKE API CALL HERE TO GATHER ASSOCIATED PROGRAM AREA DIVISIONS FOR RECORDS BASED ON REQUEST ID - TEST WIP @cors_preflight('GET,OPTIONS') -@API.route('/foiflow/divisions/') -class FOIFlowDivisions(Resource): - """Retrieves divisions associated with records based on ministry request id. +@API.route('/foiflow/divisions/') +class FOIFlowDivisionsForFOIRequestRecords(Resource): + """Retrieves all active divisions associated with foi request records based on foi request id. """ @staticmethod @TRACER.trace() @cross_origin(origins=allowedorigins()) @auth.require @auth.ismemberofgroups(getrequiredmemberships()) - def get(): - pass + def get(foirequestid): + try: + divisions = {div['divisionid']: div for div in programareadivisionservice.getallprogramareadivisions()} + # {1: Divobj, 2: divobj... } + records = {record['recordid']: record for record in recordservice.fetch(foirequestid)} + + for recordid in records: + record = records[recordid] + record_divisions = set(map(lambda d: d['divisionid'], record['attributes']['divisions'])) + record['divisions'] = list(map(lambda d: divisions[d], record_divisions)) + + response = [records[recordid] for recordid in records] + json_response = json.dumps(response) + return json_response, 200 + except BusinessException: + return "Error happened while accessing records", 500 @cors_preflight('GET,OPTIONS') @API.route('/foiflow/closereasons') From 85711dbd52080d89646b3e2665364d37fd6fd08d Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Mon, 14 Aug 2023 17:32:43 -0700 Subject: [PATCH 027/234] Testing completed, two new api endpoints for divisions and proramareas tested with Postman. Implementation to doc reviewer WIP --- .../request_api/resources/foiflowmasterdata.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/request-management-api/request_api/resources/foiflowmasterdata.py b/request-management-api/request_api/resources/foiflowmasterdata.py index e7a51c0e1..4be5e6ac0 100644 --- a/request-management-api/request_api/resources/foiflowmasterdata.py +++ b/request-management-api/request_api/resources/foiflowmasterdata.py @@ -201,7 +201,7 @@ class FOIFlowDivisions(Resource): @auth.ismemberofgroups(getrequiredmemberships()) def get(): try: - division_data = programareadivisionservice.getallprogramareadivisions() + division_data = programareadivisionservice().getallprogramareadivisions() json_response= json.dumps(division_data) return json_response, 200 except BusinessException: @@ -220,8 +220,7 @@ class FOIFlowDivisionsForFOIRequestRecords(Resource): @auth.ismemberofgroups(getrequiredmemberships()) def get(foirequestid): try: - divisions = {div['divisionid']: div for div in programareadivisionservice.getallprogramareadivisions()} - # {1: Divobj, 2: divobj... } + divisions = {div['divisionid']: div for div in programareadivisionservice().getallprogramareadivisions()} records = {record['recordid']: record for record in recordservice.fetch(foirequestid)} for recordid in records: From 2213e595918d9571eb1c4d6547ecf7ca5eb24aed Mon Sep 17 00:00:00 2001 From: "sumathi.thirumani" Date: Tue, 15 Aug 2023 09:52:15 -0700 Subject: [PATCH 028/234] Changes to get api of formats. --- .../src/apiManager/services/FOI/foiRecordServices.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forms-flow-web/src/apiManager/services/FOI/foiRecordServices.js b/forms-flow-web/src/apiManager/services/FOI/foiRecordServices.js index 1e190ee33..18a15cfb7 100644 --- a/forms-flow-web/src/apiManager/services/FOI/foiRecordServices.js +++ b/forms-flow-web/src/apiManager/services/FOI/foiRecordServices.js @@ -1,6 +1,6 @@ import { httpGETRequest, - httpGETRequest1, + httpOpenGETRequest, httpPOSTRequest, } from "../../httpRequestHandler"; import API from "../../endpoints"; @@ -216,7 +216,7 @@ const postRecord = (dispatch, apiUrl, data, errorMessage, rest, type="download") export const getRecordFormats = (...rest) => { const done = fnDone(rest); return (dispatch) => { - httpGETRequest1(FOI_RECORD_FORMATS, null) + httpOpenGETRequest(FOI_RECORD_FORMATS) .then((res) => { if (res.data) { dispatch(setRecordFormats([... new Set([...res.data.conversion, ...res.data.dedupe, ...res.data.nonredactable])])) From 0d669cace21b89b915f70a5bcb43e307b57f41a2 Mon Sep 17 00:00:00 2001 From: "sumathi.thirumani" Date: Wed, 16 Aug 2023 09:16:43 -0700 Subject: [PATCH 029/234] changes to show modal --- .../components/FOI/FOIRequest/FOIRequest.js | 1 + .../MinistryReview/MinistryReview.js | 1 + .../FOI/customComponents/Records/index.js | 32 +++++++++++++++++-- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js b/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js index 826673de7..4ec6bbea1 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js @@ -1255,6 +1255,7 @@ const FOIRequest = React.memo(({ userDetail }) => { bcgovcode={JSON.parse(bcgovcode)} setRecordsUploading={setRecordsUploading} divisions={requestDetails.divisions} + tabStatus={tabLinksStatuses} /> } diff --git a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js index e295ce685..2f5ec5e7d 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js @@ -855,6 +855,7 @@ const MinistryReview = React.memo(({ userDetail }) => { isMinistryCoordinator={true} bcgovcode={JSON.parse(bcgovcode)} setRecordsUploading={setRecordsUploading} + tabStatus={tabLinksStatuses} /> ) : ( diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/index.js b/forms-flow-web/src/components/FOI/customComponents/Records/index.js index 808d3aae7..a74482f78 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/index.js @@ -155,7 +155,8 @@ export const RecordsLog = ({ iaoassignedToList, ministryAssignedToList, isMinistryCoordinator, - setRecordsUploading + setRecordsUploading, + tabStatus }) => { let recordsObj = useSelector( @@ -189,6 +190,32 @@ export const RecordsLog = ({ }, []) const conversionFormats = useSelector((state) => state.foiRequests.conversionFormats) + + + useEffect(() => { + console.log(tabStatus); + console.log(conversionFormats?.length); + if (tabStatus?.Records.isactive && conversionFormats?.length < 1) { + console.log("match"); + toast.error( + "Temporarily unable to save your request. Please try again in a few minutes.", + { + position: "top-right", + autoClose: 3000, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + } + ); + + } + }, [tabStatus, conversionFormats]) + + + + const divisionFilters = [...new Map(recordsObj?.records?.reduce((acc, file) => [...acc, ...new Map(file?.attributes?.divisions?.map(division => [division?.divisionid, division]))], [])).values()] if (divisionFilters?.length > 0) divisionFilters?.push( {divisionid: -1, divisionname: "All"}, @@ -1069,7 +1096,7 @@ export const RecordsLog = ({ className="download-menu-item" key={item.id} value={index} - disabled={item.disabled} + disabled={item.disabled || conversionFormats?.length < 1} sx={{ display: 'flex' }} > { @@ -1109,6 +1136,7 @@ export const RecordsLog = ({ variant="contained" onClick={addAttachments} color="primary" + disabled = {conversionFormats?.length < 1} > + Upload Records : From 6cac39b969f2697127ae8b75b0470b182704c90d Mon Sep 17 00:00:00 2001 From: nimya-aot Date: Wed, 16 Aug 2023 09:48:38 -0700 Subject: [PATCH 030/234] yaml file changed for node version --- .github/workflows/katalon-ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/katalon-ci.yaml b/.github/workflows/katalon-ci.yaml index e90d1638b..b461747b0 100644 --- a/.github/workflows/katalon-ci.yaml +++ b/.github/workflows/katalon-ci.yaml @@ -50,7 +50,7 @@ jobs: dir shell: cmd - name: Setup Node.js - uses: actions/setup-node@v3.0 + uses: actions/setup-node@v2 with: node-version: '14' - name: Create collection report @@ -58,7 +58,7 @@ jobs: npm i -g xunit-viewer xunit-viewer -r artifacts\JUnit_Report.xml -o artifacts\foi-test.html - name: Archive Katalon report - uses: actions/upload-artifact@v3.0 + uses: actions/upload-artifact@v2 with: name: katalon-report path: artifacts From a8241fe7ddff11d10a47d66dc458ec5057b1c7e7 Mon Sep 17 00:00:00 2001 From: Aparna Date: Wed, 16 Aug 2023 11:33:53 -0700 Subject: [PATCH 031/234] #4258 & #4256 Identity Verified Field - Sync with AXIS#4258 Original LDD Needs to Not Change#4256 --- .../MCS.FOI.AXISIntegration.DAL/RequestsDA.cs | 5 ++- .../AXISRequest.cs | 3 ++ .../FOIRequest/AdditionalApplicantDetails.js | 8 +++-- .../ExtensionDetails/ActionContext.js | 2 +- .../ExtensionDetails/ActionContext.js | 2 +- .../src/components/FOI/FOIRequest/utils.js | 6 ++-- .../constants/FOI/axisSyncDisplayFields.js | 3 +- .../migrations/versions/5782617db307_.py | 33 +++++++++++++++++++ .../request_api/models/FOIMinistryRequests.py | 8 +++-- .../request_api/schemas/foirequestwrapper.py | 5 ++- .../foirequest/requestservicebuilder.py | 2 ++ .../foirequest/requestservicegetter.py | 13 +++++--- .../requestserviceministrybuilder.py | 2 ++ 13 files changed, 76 insertions(+), 16 deletions(-) create mode 100644 request-management-api/migrations/versions/5782617db307_.py diff --git a/axisintegrationapi/MCS.FOI.AXISIntegration/MCS.FOI.AXISIntegration.DAL/RequestsDA.cs b/axisintegrationapi/MCS.FOI.AXISIntegration/MCS.FOI.AXISIntegration.DAL/RequestsDA.cs index a969735f8..12ce38682 100644 --- a/axisintegrationapi/MCS.FOI.AXISIntegration/MCS.FOI.AXISIntegration.DAL/RequestsDA.cs +++ b/axisintegrationapi/MCS.FOI.AXISIntegration/MCS.FOI.AXISIntegration.DAL/RequestsDA.cs @@ -53,6 +53,7 @@ public AXISRequest GetAXISRequest(string request) axisRequest.StartDate = RequestsHelper.ConvertDateToString(row, "requestProcessStart", "yyyy-MM-dd"); axisRequest.DueDate = RequestsHelper.ConvertDateToString(row, "dueDate", "yyyy-MM-dd"); axisRequest.CFRDueDate = RequestsHelper.ConvertDateToString(row, "cfrDueDate", "yyyy-MM-dd"); + axisRequest.OriginalDueDate = RequestsHelper.ConvertDateToString(row, "originalDueDate", "yyyy-MM-dd"); axisRequest.DeliveryMode = RequestsHelper.GetDeliveryMode(Convert.ToString(row["deliveryMode"])); axisRequest.ReceivedMode = RequestsHelper.GetReceivedMode(Convert.ToString(row["receivedMode"])); @@ -83,6 +84,7 @@ public AXISRequest GetAXISRequest(string request) axisRequest.Ispiiredacted = true; axisRequest.RequestPageCount = Convert.ToInt32(row["requestPageCount"]); axisRequest.SubjectCode = Convert.ToString(row["subjectCode"]); + axisRequest.IdentityVerified = Convert.ToString(row["identityVerified"]); List ministryList = new() { new Ministry(RequestsHelper.GetMinistryCode(Convert.ToString(row["selectedMinistry"]))) @@ -151,6 +153,7 @@ private DataTable GetAxisRequestData(string request) when requests.IREQUESTID = redaction.IREQUESTID and redaction.IDOCID = ldocuments.IDOCID then ldocuments.SIPAGECOUNT else 0 end) as requestPageCount, REPLACE(requestfields.CUSTOMFIELD33, CHAR(160), ' ') as subjectCode, + requestfields.CUSTOMFIELD75 as identityVerified, (SELECT TOP 1 cfr.sdtDueDate FROM tblRequestForDocuments cfr WITH (NOLOCK) INNER JOIN tblProgramOffices programoffice WITH (NOLOCK) ON programoffice.tiProgramOfficeID = cfr.tiProgramOfficeID WHERE requests.iRequestID = cfr.iRequestID @@ -182,7 +185,7 @@ LEFT OUTER JOIN dbo.TBLREQUESTCUSTOMFIELDS requestfields WITH (NOLOCK) ON reques requesters.vcAddress1, requesters.vcAddress2, requesters.vcCity, requesters.vcZipCode, requesters.vcHome, requesters.vcMobile, requesters.vcWork1, requesters.vcWork2, requesters.vcFirstName, requesters.vcLastName, requesters.vcMiddleName, requests.iRequestID, requesters.vcCompany, requesters.vcEmailID, onbehalf.vcFirstName, onbehalf.vcLastName, onbehalf.vcMiddleName, - requestTypes.iLabelID, requests.vcVisibleRequestID, requests.tiOfficeID, office.OFFICE_ID,requestorfields.CUSTOMFIELD35, REPLACE(requestfields.CUSTOMFIELD33, CHAR(160), ' ')"; + requestTypes.iLabelID, requests.vcVisibleRequestID, requests.tiOfficeID, office.OFFICE_ID,requestorfields.CUSTOMFIELD35, REPLACE(requestfields.CUSTOMFIELD33, CHAR(160), ' '),requestfields.CUSTOMFIELD75"; DataTable dataTable = new(); using (sqlConnection = new SqlConnection(ConnectionString)) { diff --git a/axisintegrationapi/MCS.FOI.AXISIntegration/MCS.FOI.AXISIntegration.DataModels/AXISRequest.cs b/axisintegrationapi/MCS.FOI.AXISIntegration/MCS.FOI.AXISIntegration.DataModels/AXISRequest.cs index 1521c09b4..3c8feb896 100644 --- a/axisintegrationapi/MCS.FOI.AXISIntegration/MCS.FOI.AXISIntegration.DataModels/AXISRequest.cs +++ b/axisintegrationapi/MCS.FOI.AXISIntegration/MCS.FOI.AXISIntegration.DataModels/AXISRequest.cs @@ -135,6 +135,9 @@ public class AXISRequest [DataMember(Name = "Extensions")] public List Extensions { get; set; } + [DataMember(Name = "identityVerified")] + public string IdentityVerified { get; set; } + } [DataContract] diff --git a/forms-flow-web/src/components/FOI/FOIRequest/AdditionalApplicantDetails.js b/forms-flow-web/src/components/FOI/FOIRequest/AdditionalApplicantDetails.js index baf6a4418..7efc0b2b2 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/AdditionalApplicantDetails.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/AdditionalApplicantDetails.js @@ -16,6 +16,7 @@ const AdditionalApplicantDetails = React.memo(({requestDetails, createSaveReques * No mandatory fields here */ + console.log(requestDetails); const useStyles = makeStyles({ heading: { color: '#FFF', @@ -53,7 +54,7 @@ const AdditionalApplicantDetails = React.memo(({requestDetails, createSaveReques ); const [identityVerifiedText, setIdentityVerified] = React.useState( validateField( - requestDetails?.additionalPersonalInfo, + requestDetails, FOI_COMPONENT_CONSTANTS.IDENTITY_VERIFIED ) ); @@ -81,7 +82,7 @@ const AdditionalApplicantDetails = React.memo(({requestDetails, createSaveReques FOI_COMPONENT_CONSTANTS.PERSONAL_HEALTH_NUMBER )); setIdentityVerified(validateField( - requestDetails?.additionalPersonalInfo, + requestDetails, FOI_COMPONENT_CONSTANTS.IDENTITY_VERIFIED )); setCorrectionsNumber(validateField(requestDetails, FOI_COMPONENT_CONSTANTS.CORRECTIONS_NUMBER)); @@ -105,6 +106,7 @@ const AdditionalApplicantDetails = React.memo(({requestDetails, createSaveReques } const handleIdentityVerified = (e) => { + console.log("IdentityVerified:",e.target.value) setIdentityVerified(e.target.value); createSaveRequestObject(FOI_COMPONENT_CONSTANTS.IDENTITY_VERIFIED, e.target.value); } @@ -169,7 +171,7 @@ const AdditionalApplicantDetails = React.memo(({requestDetails, createSaveReques value={identityVerifiedText} onChange={handleIdentityVerified} fullWidth - disabled={disableInput} + disabled />

diff --git a/forms-flow-web/src/components/FOI/FOIRequest/ExtensionDetails/ActionContext.js b/forms-flow-web/src/components/FOI/FOIRequest/ExtensionDetails/ActionContext.js index d73271ab9..8c5ece77c 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/ExtensionDetails/ActionContext.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/ExtensionDetails/ActionContext.js @@ -28,7 +28,7 @@ export const ActionProvider = ({ children, requestDetails, requestState }) => { const currentDueDate = formatDate(requestDetails.dueDate); const startDate = formatDate(requestDetails.requestProcessStart); - const originalDueDate = formatDate(requestDetails.originalDueDate); + const originalDueDate = formatDate(requestDetails.legislativeDueDate); const extensions = useSelector( (state) => state.foiRequests.foiRequestExtesions ); diff --git a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/ExtensionDetails/ActionContext.js b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/ExtensionDetails/ActionContext.js index 5503cfaf1..429cb5f90 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/ExtensionDetails/ActionContext.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/ExtensionDetails/ActionContext.js @@ -8,7 +8,7 @@ export const ActionProvider = ({ children, requestDetails }) => { const currentDueDate = formatDate(requestDetails.dueDate) const startDate = formatDate(requestDetails.requestProcessStart); - const originalDueDate = formatDate(requestDetails.originalDueDate); + const originalDueDate = formatDate(requestDetails.legislativeDueDate); const extensions = useSelector( (state) => state.foiRequests.foiRequestExtesions ); diff --git a/forms-flow-web/src/components/FOI/FOIRequest/utils.js b/forms-flow-web/src/components/FOI/FOIRequest/utils.js index 621bcbe84..a6c33f112 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/utils.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/utils.js @@ -162,7 +162,7 @@ export const updateAdditionalInfo = (name, value, requestObject) => { adoptiveFatherLastName: "", adoptiveFatherFirstName: "", personalHealthNumber: "", - identityVerified: "", + //identityVerified: "", }; } requestObject.additionalPersonalInfo[name] = value; @@ -262,8 +262,10 @@ export const createRequestDetailsObjectFunc = ( case FOI_COMPONENT_CONSTANTS.LINKED_REQUESTS: requestObject.linkedRequests = typeof value == 'string' ? JSON.parse(value) :value; break; - case FOI_COMPONENT_CONSTANTS.PERSONAL_HEALTH_NUMBER: case FOI_COMPONENT_CONSTANTS.IDENTITY_VERIFIED: + requestObject.identityVerified = value; + break; + case FOI_COMPONENT_CONSTANTS.PERSONAL_HEALTH_NUMBER: case FOI_COMPONENT_CONSTANTS.DOB: case FOI_COMPONENT_CONSTANTS.CHILD_NICKNAME: case FOI_COMPONENT_CONSTANTS.CHILD_FIRST_NAME: diff --git a/forms-flow-web/src/constants/FOI/axisSyncDisplayFields.js b/forms-flow-web/src/constants/FOI/axisSyncDisplayFields.js index d4f2e5970..f435ee8a9 100644 --- a/forms-flow-web/src/constants/FOI/axisSyncDisplayFields.js +++ b/forms-flow-web/src/constants/FOI/axisSyncDisplayFields.js @@ -41,7 +41,8 @@ const AXIS_SYNC_DISPLAY_FIELDS = { cfrDueDate: "CFR Due Date", requestPageCount: "Total number of pages", subjectCode: "Subject Code", - linkedRequests: "Linked Requests" + linkedRequests: "Linked Requests", + identityVerified:"Identity Verified" }; export default AXIS_SYNC_DISPLAY_FIELDS; diff --git a/request-management-api/migrations/versions/5782617db307_.py b/request-management-api/migrations/versions/5782617db307_.py new file mode 100644 index 000000000..64d4e0411 --- /dev/null +++ b/request-management-api/migrations/versions/5782617db307_.py @@ -0,0 +1,33 @@ +"""empty message + +Revision ID: 5782617db307 +Revises: e25110cba030 +Create Date: 2023-08-13 20:49:54.912638 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '5782617db307' +down_revision = 'e25110cba030' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('FOIMinistryRequests', sa.Column('originalldd', sa.DateTime(), nullable=True)) + op.add_column('FOIMinistryRequests', sa.Column('identityverified', postgresql.JSON(astext_type=sa.Text()), nullable=True)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('FOIMinistryRequests', 'originalldd') + op.drop_column('FOIMinistryRequests', 'identityverified') + + + # ### end Alembic commands ### diff --git a/request-management-api/request_api/models/FOIMinistryRequests.py b/request-management-api/request_api/models/FOIMinistryRequests.py index ab9c12077..b93d646c6 100644 --- a/request-management-api/request_api/models/FOIMinistryRequests.py +++ b/request-management-api/request_api/models/FOIMinistryRequests.py @@ -50,6 +50,7 @@ class FOIMinistryRequest(db.Model): startdate = db.Column(db.DateTime, nullable=False,default=datetime.now) duedate = db.Column(db.DateTime, nullable=False) cfrduedate = db.Column(db.DateTime, nullable=True) + originalldd = db.Column(db.DateTime, nullable=True) assignedgroup = db.Column(db.String(250), unique=False, nullable=True) assignedto = db.Column(db.String(120), ForeignKey('FOIAssignees.username'), unique=False, nullable=True) @@ -65,6 +66,7 @@ class FOIMinistryRequest(db.Model): axisrequestid = db.Column(db.String(120), nullable=True) requestpagecount = db.Column(db.String(20), nullable=True) linkedrequests = db.Column(JSON, unique=False, nullable=True) + identityverified = db.Column(JSON, unique=False, nullable=True) #ForeignKey References @@ -183,7 +185,8 @@ def getrequests(cls, group = None): _request["linkedrequests"] = ministryrequest['linkedrequests'] _request["currentState"] = ministryrequest["requeststatus.name"] _request["dueDate"] = ministryrequest["duedate"] - _request["cfrDueDate"] = ministryrequest["cfrduedate"] + _request["cfrDueDate"] = ministryrequest["cfrduedate"] + _request["originalDueDate"] = ministryrequest["originalldd"] _request["receivedDate"] = _receiveddate.strftime('%Y %b, %d') _request["receivedDateUF"] =str(_receiveddate) _request["assignedGroup"]=ministryrequest["assignedgroup"] @@ -195,6 +198,7 @@ def getrequests(cls, group = None): _request["id"] = parentrequest.foirequestid _request["ministryrequestid"] = ministryrequest['foiministryrequestid'] _request["applicantcategory"]=parentrequest.applicantcategory.name + _request["identityverified"] = ministryrequest['identityverified'] _requests.append(_request) return _requests @@ -1302,5 +1306,5 @@ class Meta: 'foirequest.receivedmodeid','requeststatus.requeststatusid','requeststatus.name','programarea.bcgovcode', 'programarea.name','foirequest_id','foirequestversion_id','created_at','updated_at','createdby','assignedministryperson', 'assignedministrygroup','cfrduedate','closedate','closereasonid','closereason.name', - 'assignee.firstname','assignee.lastname','ministryassignee.firstname','ministryassignee.lastname', 'axisrequestid', 'axissyncdate', 'requestpagecount', 'linkedrequests') + 'assignee.firstname','assignee.lastname','ministryassignee.firstname','ministryassignee.lastname', 'axisrequestid', 'axissyncdate', 'requestpagecount', 'linkedrequests','identityverified','originalldd') diff --git a/request-management-api/request_api/schemas/foirequestwrapper.py b/request-management-api/request_api/schemas/foirequestwrapper.py index bf2990df6..0f8a68ce0 100644 --- a/request-management-api/request_api/schemas/foirequestwrapper.py +++ b/request-management-api/request_api/schemas/foirequestwrapper.py @@ -40,7 +40,7 @@ class Meta: # pylint: disable=too-few-public-methods adoptiveFatherLastName = fields.Str(data_key="adoptiveFatherLastName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) personalHealthNumber = fields.Str(data_key="personalHealthNumber",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) - identityVerified = fields.Str(data_key="identityVerified",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) + #identityVerified = fields.Str(data_key="identityVerified",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) birthDate = fields.Str(data_key="birthDate",allow_none=True) alsoKnownAs = fields.Str(data_key="alsoKnownAs",allow_none=True) @@ -78,6 +78,7 @@ class Meta: # pylint: disable=too-few-public-methods dueDate = fields.Str(data_key="dueDate", required=True,validate=[validate.Length(min=1, error=BLANK_EXCEPTION_MESSAGE)]) paymentExpiryDate = fields.Str(data_key="paymentExpiryDate", required=False,allow_none=True) cfrDueDate = fields.Date(data_key="cfrDueDate", required=False,allow_none=True) + originalDueDate = fields.Date(data_key="originalDueDate", required=False,allow_none=True) deliveryMode = fields.Str(data_key="deliveryMode", required=True,validate=[validate.Length(min=1, error=BLANK_EXCEPTION_MESSAGE)]) receivedMode = fields.Str(data_key="receivedMode", required=True,validate=[validate.Length(min=1, error=BLANK_EXCEPTION_MESSAGE)]) receivedDate = fields.Str(data_key="receivedDateUF", required=True,validate=[validate.Length(min=1, error=BLANK_EXCEPTION_MESSAGE)]) @@ -117,6 +118,8 @@ class Meta: # pylint: disable=too-few-public-methods subjectCode = fields.Str(data_key="subjectCode",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) isofflinepayment = fields.Bool(data_key="isofflinepayment") linkedRequests = fields.List(fields.Dict(data_key="linkedRequests", required=False)) + identityVerified = fields.Str(data_key="identityVerified",allow_none=True) + class EditableFOIMinistryRequestWrapperSchema(Schema): class Meta: # pylint: disable=too-few-public-methods diff --git a/request-management-api/request_api/services/foirequest/requestservicebuilder.py b/request-management-api/request_api/services/foirequest/requestservicebuilder.py index b41914f72..4f6db7aa5 100644 --- a/request-management-api/request_api/services/foirequest/requestservicebuilder.py +++ b/request-management-api/request_api/services/foirequest/requestservicebuilder.py @@ -32,6 +32,8 @@ def createministry(self, requestschema, ministry, activeversion, userid, filenum foiministryrequest.description = requestschema.get("description") foiministryrequest.duedate = requestschema.get("dueDate") foiministryrequest.linkedrequests = requestschema.get("linkedRequests") + foiministryrequest.identityverified = requestschema.get("identityVerified") + foiministryrequest.originalldd = requestschema.get("originalDueDate") if requestschema.get("cfrDueDate") is not None and requestschema.get("cfrDueDate") != "": foiministryrequest.cfrduedate = requestschema.get("cfrDueDate") startdate = "" diff --git a/request-management-api/request_api/services/foirequest/requestservicegetter.py b/request-management-api/request_api/services/foirequest/requestservicegetter.py index 5d48d28c8..67b561a14 100644 --- a/request-management-api/request_api/services/foirequest/requestservicegetter.py +++ b/request-management-api/request_api/services/foirequest/requestservicegetter.py @@ -54,8 +54,9 @@ def getrequest(self,foirequestid,foiministryrequestid): additionalpersonalinfo.update(additionalpersonalinfodetails) baserequestinfo['additionalPersonalInfo'] = additionalpersonalinfo - originalduedate = FOIMinistryRequest.getrequestoriginalduedate(foiministryrequestid) - baserequestinfo['originalDueDate'] = originalduedate.strftime(self.__genericdateformat()) + legislativeDueDate = FOIMinistryRequest.getrequestoriginalduedate(foiministryrequestid) + baserequestinfo['legislativeDueDate'] = legislativeDueDate.strftime(self.__genericdateformat()) + baserequestinfo['originalDueDate'] = parse(requestministry['originalldd']).strftime(self.__genericdateformat()) if requestministry['originalldd'] is not None else '' baserequestinfo['iaorestricteddetails'] = iaorestrictrequestdetails return baserequestinfo @@ -127,6 +128,7 @@ def getrequestdetails(self,foirequestid, foiministryrequestid): return requestdetails def __preparebaseinfo(self,request,foiministryrequestid,requestministry,requestministrydivisions): + print("requestministry-:",requestministry['originalldd']) _receiveddate = parse(request['receiveddate']) axissyncdatenoneorempty = self.__noneorempty(requestministry["axissyncdate"]) linkedministryrequests= [] @@ -153,7 +155,8 @@ def __preparebaseinfo(self,request,foiministryrequestid,requestministry,requestm 'currentState':requestministry['requeststatus.name'], 'requeststatusid':requestministry['requeststatus.requeststatusid'], 'requestProcessStart': parse(requestministry['startdate']).strftime(self.__genericdateformat()) if requestministry['startdate'] is not None else '', - 'dueDate':parse(requestministry['duedate']).strftime(self.__genericdateformat()), + 'dueDate':parse(requestministry['duedate']).strftime(self.__genericdateformat()), + 'originalDueDate': parse(requestministry['originalldd']).strftime(self.__genericdateformat()) if requestministry['originalldd'] is not None else '', 'programareaid':requestministry['programarea.programareaid'], 'bcgovcode':requestministry['programarea.bcgovcode'], 'category':request['applicantcategory.name'], @@ -171,7 +174,9 @@ def __preparebaseinfo(self,request,foiministryrequestid,requestministry,requestm 'closedate': parse(requestministry['closedate']).strftime(self.__genericdateformat()) if requestministry['closedate'] is not None else None, 'subjectCode': subjectcodeservice().getministrysubjectcodename(foiministryrequestid), 'isofflinepayment': FOIMinistryRequest.getofflinepaymentflag(foiministryrequestid), - 'linkedRequests' : linkedministryrequests + 'linkedRequests' : linkedministryrequests, + 'identityVerified':requestministry['identityverified'], + } if requestministry['cfrduedate'] is not None: baserequestinfo.update({'cfrDueDate':parse(requestministry['cfrduedate']).strftime(self.__genericdateformat())}) diff --git a/request-management-api/request_api/services/foirequest/requestserviceministrybuilder.py b/request-management-api/request_api/services/foirequest/requestserviceministrybuilder.py index 26aea4526..a0a236f2b 100644 --- a/request-management-api/request_api/services/foirequest/requestserviceministrybuilder.py +++ b/request-management-api/request_api/services/foirequest/requestserviceministrybuilder.py @@ -49,6 +49,8 @@ def createfoiministryrequestfromobject(self, ministryschema, requestschema, user foiministryrequest.axissyncdate = ministryschema["axissyncdate"] foiministryrequest.axisrequestid = ministryschema["axisrequestid"] foiministryrequest.linkedrequests = ministryschema['linkedrequests'] + foiministryrequest.identityverified = ministryschema['identityverified'] + foiministryrequest.originalldd = ministryschema['originalldd'] foiministryrequest.requestpagecount = ministryschema["requestpagecount"] foiministryrequest.cfrduedate = requestdict['cfrduedate'] foiministryrequest.startdate = requestdict['startdate'] From 9e1cd1c5c2f48c0b193097bfd8e0f5a9f9655a6f Mon Sep 17 00:00:00 2001 From: "sumathi.thirumani" Date: Wed, 16 Aug 2023 12:08:33 -0700 Subject: [PATCH 032/234] Changes to fix broken CFR notification. --- .../request_api/models/OperatingTeams.py | 2 +- .../services/notifications/notificationuser.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/request-management-api/request_api/models/OperatingTeams.py b/request-management-api/request_api/models/OperatingTeams.py index f16356477..dbf1778c2 100644 --- a/request-management-api/request_api/models/OperatingTeams.py +++ b/request-management-api/request_api/models/OperatingTeams.py @@ -30,7 +30,7 @@ def getalloperatingteams(cls): def getteam(cls, team): try: sql = """select type, name from "OperatingTeams" ot - where replace(lower(name),' ','') = replace(:team,' ','')""" + where replace(lower(name),' ','') = replace(lower(:team),' ','')""" rs = db.session.execute(text(sql), {'team': team}) for row in rs: return {'type': row["type"], 'name': row['name']} diff --git a/request-management-api/request_api/services/notifications/notificationuser.py b/request-management-api/request_api/services/notifications/notificationuser.py index 29aae9e78..2b5a104cf 100644 --- a/request-management-api/request_api/services/notifications/notificationuser.py +++ b/request-management-api/request_api/services/notifications/notificationuser.py @@ -125,7 +125,9 @@ def __getgroupmembers(self,groupid): notificationusers = [] notificationtypeid = notificationconfig().getnotificationusertypeid("Group Members") usergroupfromkeycloak= KeycloakAdminService().getmembersbygroupname(groupid) - for user in usergroupfromkeycloak[0].get("members"): - notificationusers.append({"userid":user["username"], "usertype":notificationtypeid}) - return notificationusers + if usergroupfromkeycloak is not None and len(usergroupfromkeycloak) > 0: + for user in usergroupfromkeycloak[0].get("members"): + notificationusers.append({"userid":user["username"], "usertype":notificationtypeid}) + return notificationusers + return [] \ No newline at end of file From a71832a2da1726248edccec667f53ea2f92f61e5 Mon Sep 17 00:00:00 2001 From: Aparna Date: Wed, 16 Aug 2023 12:10:37 -0700 Subject: [PATCH 033/234] Removed debug code & minor update to state transition check with peer review --- .../components/FOI/FOIRequest/AdditionalApplicantDetails.js | 2 -- .../src/components/FOI/customComponents/StateDropDown.js | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/forms-flow-web/src/components/FOI/FOIRequest/AdditionalApplicantDetails.js b/forms-flow-web/src/components/FOI/FOIRequest/AdditionalApplicantDetails.js index 7efc0b2b2..3a1e1b4e5 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/AdditionalApplicantDetails.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/AdditionalApplicantDetails.js @@ -16,7 +16,6 @@ const AdditionalApplicantDetails = React.memo(({requestDetails, createSaveReques * No mandatory fields here */ - console.log(requestDetails); const useStyles = makeStyles({ heading: { color: '#FFF', @@ -106,7 +105,6 @@ const AdditionalApplicantDetails = React.memo(({requestDetails, createSaveReques } const handleIdentityVerified = (e) => { - console.log("IdentityVerified:",e.target.value) setIdentityVerified(e.target.value); createSaveRequestObject(FOI_COMPONENT_CONSTANTS.IDENTITY_VERIFIED, e.target.value); } diff --git a/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js b/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js index 4cf1c6068..28329fb8f 100644 --- a/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js +++ b/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js @@ -100,8 +100,8 @@ const StateDropDown = ({ return _stateList.intakeinprogress; case StateEnum.peerreview.name.toLowerCase(): if(!isMinistryCoordinator){ - const currentStatusVersion = stateTransition[0]?.version; - const previousState = stateTransition?.find((state)=>state?.version == (currentStatusVersion-1) )?.status; + //const currentStatusVersion = stateTransition[0]?.version; + const previousState = stateTransition?.length > 0 && stateTransition[1]?.status; if(previousState === StateEnum.intakeinprogress.name){ return _stateList.intakeinprogress; } @@ -167,7 +167,7 @@ const StateDropDown = ({ return isValidationError || requestState === StateEnum.unopened.name; }; const statusList = getStatusList(); - + console.log("statusList",statusList) const menuItems = statusList.length > 0 && statusList.map((item, index) => { From 5e02d73246c8e91258b219a0859614bd11265281 Mon Sep 17 00:00:00 2001 From: Aparna Date: Wed, 16 Aug 2023 12:32:08 -0700 Subject: [PATCH 034/234] Update on duedate -extension --- .../FOI/FOIRequest/ExtensionDetails/ActionContext.js | 2 +- .../MinistryReview/ExtensionDetails/ActionContext.js | 2 +- .../request_api/services/foirequest/requestservicegetter.py | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/forms-flow-web/src/components/FOI/FOIRequest/ExtensionDetails/ActionContext.js b/forms-flow-web/src/components/FOI/FOIRequest/ExtensionDetails/ActionContext.js index 8c5ece77c..d73271ab9 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/ExtensionDetails/ActionContext.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/ExtensionDetails/ActionContext.js @@ -28,7 +28,7 @@ export const ActionProvider = ({ children, requestDetails, requestState }) => { const currentDueDate = formatDate(requestDetails.dueDate); const startDate = formatDate(requestDetails.requestProcessStart); - const originalDueDate = formatDate(requestDetails.legislativeDueDate); + const originalDueDate = formatDate(requestDetails.originalDueDate); const extensions = useSelector( (state) => state.foiRequests.foiRequestExtesions ); diff --git a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/ExtensionDetails/ActionContext.js b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/ExtensionDetails/ActionContext.js index 429cb5f90..5503cfaf1 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/ExtensionDetails/ActionContext.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/ExtensionDetails/ActionContext.js @@ -8,7 +8,7 @@ export const ActionProvider = ({ children, requestDetails }) => { const currentDueDate = formatDate(requestDetails.dueDate) const startDate = formatDate(requestDetails.requestProcessStart); - const originalDueDate = formatDate(requestDetails.legislativeDueDate); + const originalDueDate = formatDate(requestDetails.originalDueDate); const extensions = useSelector( (state) => state.foiRequests.foiRequestExtesions ); diff --git a/request-management-api/request_api/services/foirequest/requestservicegetter.py b/request-management-api/request_api/services/foirequest/requestservicegetter.py index 67b561a14..62f62852a 100644 --- a/request-management-api/request_api/services/foirequest/requestservicegetter.py +++ b/request-management-api/request_api/services/foirequest/requestservicegetter.py @@ -54,9 +54,8 @@ def getrequest(self,foirequestid,foiministryrequestid): additionalpersonalinfo.update(additionalpersonalinfodetails) baserequestinfo['additionalPersonalInfo'] = additionalpersonalinfo - legislativeDueDate = FOIMinistryRequest.getrequestoriginalduedate(foiministryrequestid) - baserequestinfo['legislativeDueDate'] = legislativeDueDate.strftime(self.__genericdateformat()) - baserequestinfo['originalDueDate'] = parse(requestministry['originalldd']).strftime(self.__genericdateformat()) if requestministry['originalldd'] is not None else '' + originalLdd= FOIMinistryRequest.getrequestoriginalduedate(foiministryrequestid).strftime(self.__genericdateformat()) + baserequestinfo['originalDueDate'] = parse(requestministry['originalldd']).strftime(self.__genericdateformat()) if requestministry['originalldd'] is not None else originalLdd baserequestinfo['iaorestricteddetails'] = iaorestrictrequestdetails return baserequestinfo From 34a64451cf47f0836ee044790aa4e87326db3424 Mon Sep 17 00:00:00 2001 From: Aparna Date: Wed, 16 Aug 2023 13:47:50 -0700 Subject: [PATCH 035/234] Revert "Merge branch 'dev-marshal' into dev-AS-4256" This reverts commit 606952493498b2e62d902fe7e22e3cb7a5395f2f, reversing changes made to 5e02d73246c8e91258b219a0859614bd11265281. --- .../src/apiManager/endpoints/index.js | 1 - .../services/FOI/foiRecordServices.js | 30 ------------ .../components/FOI/FOIRequest/FOIRequest.js | 11 +---- .../FOI/FOIRequest/RedactionSummary.js | 48 ------------------- forms-flow-web/src/constants/constants.js | 2 +- 5 files changed, 2 insertions(+), 90 deletions(-) delete mode 100644 forms-flow-web/src/components/FOI/FOIRequest/RedactionSummary.js diff --git a/forms-flow-web/src/apiManager/endpoints/index.js b/forms-flow-web/src/apiManager/endpoints/index.js index 9a0c383b0..f16966d9b 100644 --- a/forms-flow-web/src/apiManager/endpoints/index.js +++ b/forms-flow-web/src/apiManager/endpoints/index.js @@ -100,7 +100,6 @@ const API = { FOI_POST_RECORDS:`${FOI_BASE_API_URL}/api/foirecord//ministryrequest/`, FOI_UPDATE_RECORDS:`${FOI_BASE_API_URL}/api/foirecord//ministryrequest//update`, DOC_REVIEWER_DELETE_RECORDS:`${DOC_REVIEWER_BASE_API_URL}/api/document/delete`, - DOC_REVIEWER_REDACTED_SECTIONS:`${DOC_REVIEWER_BASE_API_URL}/api/redactedsections/ministryrequest/`, FOI_TRIGGER_DOWNLOAD_RECORDS_FOR_HARMS:`${FOI_BASE_API_URL}/api/foirecord//ministryrequest//triggerdownload/harms`, diff --git a/forms-flow-web/src/apiManager/services/FOI/foiRecordServices.js b/forms-flow-web/src/apiManager/services/FOI/foiRecordServices.js index 8a6a14441..1e190ee33 100644 --- a/forms-flow-web/src/apiManager/services/FOI/foiRecordServices.js +++ b/forms-flow-web/src/apiManager/services/FOI/foiRecordServices.js @@ -134,36 +134,6 @@ export const fetchFOIRecords = (requestId, ministryId, ...rest) => { }; }; -export const fetchRedactedSections = (ministryId, ...rest) => { - if (!ministryId) { - return () => {}; - } - const done = fnDone(rest); - let apiUrl = replaceUrl( - API.DOC_REVIEWER_REDACTED_SECTIONS, - "", ministryId); - return (dispatch) => { - dispatch(setRecordsLoader('inprogress')) - httpGETRequest(apiUrl, {}, UserService.getToken()) - .then((res) => { - if (res.data) { - dispatch(setRecordsLoader('completed')) - done(null, res.data); - } else { - console.log("Error in fetching redacted sections", res); - dispatch(serviceActionError(res)); - dispatch(setRecordsLoader('error')) - } - }) - .catch((error) => { - console.log("Error in fetching redacted section", error); - dispatch(serviceActionError(error)); - dispatch(setRecordsLoader('error')) - done(error); - }); - }; -} - export const saveFOIRecords = (requestId, ministryId, data, ...rest) => { let apiUrl = replaceUrl(replaceUrl( API.FOI_GET_RECORDS, diff --git a/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js b/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js index 0b6c092e2..826673de7 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js @@ -41,7 +41,7 @@ import { fetchApplicantCorrespondenceTemplates } from "../../../apiManager/services/FOI/foiCorrespondenceServices"; import { fetchFOIRequestNotesList } from "../../../apiManager/services/FOI/foiRequestNoteServices"; -import { fetchFOIRecords, fetchPDFStitchStatusForHarms, fetchRedactedSections } from "../../../apiManager/services/FOI/foiRecordServices"; +import { fetchFOIRecords, fetchPDFStitchStatusForHarms } from "../../../apiManager/services/FOI/foiRecordServices"; import { makeStyles } from '@material-ui/core/styles'; import FOI_COMPONENT_CONSTANTS from '../../../constants/FOI/foiComponentConstants'; import { push } from "connected-react-router"; @@ -73,7 +73,6 @@ import { } from "./utils"; import { ConditionalComponent, formatDate, isRequestRestricted } from '../../../helper/FOI/helper'; import DivisionalTracking from './DivisionalTracking'; -import RedactionSummary from './RedactionSummary'; import AxisDetails from './AxisDetails/AxisDetails'; import AxisMessageBanner from "./AxisDetails/AxisMessageBanner"; import HomeIcon from '@mui/icons-material/Home'; @@ -230,7 +229,6 @@ const FOIRequest = React.memo(({ userDetail }) => { document.title = requestDetails.axisRequestId || requestDetails.idNumber || headerText; const dispatch = useDispatch(); const [isIAORestricted, setIsIAORestricted] = useState(false); - const [redactedSections, setRedactedSections] = useState(""); useEffect(() => { if (window.location.href.indexOf("comments") > -1) { @@ -255,9 +253,6 @@ const FOIRequest = React.memo(({ userDetail }) => { fetchCFRForm(ministryId,dispatch); dispatch(fetchApplicantCorrespondence(requestId, ministryId)); dispatch(fetchApplicantCorrespondenceTemplates()); - dispatch(fetchRedactedSections(ministryId, (_err, res) => { - setRedactedSections(res.sections); - })); } dispatch(fetchFOICategoryList()); @@ -1047,10 +1042,6 @@ const FOIRequest = React.memo(({ userDetail }) => { disableInput={disableInput} /> - {redactedSections.length > 0 && } - { - - const useStyles = makeStyles({ - heading: { - color: '#FFF', - fontSize: '16px !important', - fontWeight: 'bold !important' - }, - accordionSummary: { - flexDirection: 'row-reverse' - }, - sectionsHeading: { - fontFamily: '"BCSans-Bold", sans-serif !important', - fontSize: '16px !important', - } - }); - const classes = useStyles(); - - - return ( -
- - } aria-controls="redactionSummary" id="redactionSummary-header"> - SUMMARY OF REDACTIONS - - -
- - Redaction Sections Applied - - {sections} -
-
-
-
- ); -}); -export default RedactionSummary; \ No newline at end of file diff --git a/forms-flow-web/src/constants/constants.js b/forms-flow-web/src/constants/constants.js index c8762d5aa..ab289438e 100644 --- a/forms-flow-web/src/constants/constants.js +++ b/forms-flow-web/src/constants/constants.js @@ -62,6 +62,6 @@ export const FOI_RECORD_FORMATS = `${(window._env_ && window._env_.REACT_APP_FOI export const RECORD_PROCESSING_HRS = (window._env_ && window._env_.REACT_APP_RECORD_PROCESSING_HRS) || process.env.REACT_APP_RECORD_PROCESSING_HRS || 4; -export const DISABLE_REDACT_WEBLINK = (window._env_ && window._env_.REACT_APP_DISABLE_REDACT_WEBLINK) || process.env.REACT_APP_DISABLE_REDACT_WEBLINK || "false"; +export const DISABLE_REDACT_WEBLINK = (window._env_ && window._env_.REACT_APP_DISABLE_REDACT_WEBLINK) || process.env.REACT_APP_DISABLE_REDACT_WEBLINK || false; export const DISABLE_GATHERINGRECORDS_TAB = (window._env_ && window._env_.REACT_APP_DISABLE_GATHERINGRECORDS_TAB) || process.env.REACT_APP_DISABLE_GATHERINGRECORDS_TAB || 'false'; From 5b06609a47cb5255262a6c1086de54e80e02d707 Mon Sep 17 00:00:00 2001 From: Aparna Date: Wed, 16 Aug 2023 13:51:26 -0700 Subject: [PATCH 036/234] Condition added --- .../request_api/services/foirequest/requestservicegetter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/request-management-api/request_api/services/foirequest/requestservicegetter.py b/request-management-api/request_api/services/foirequest/requestservicegetter.py index 62f62852a..2f865dea6 100644 --- a/request-management-api/request_api/services/foirequest/requestservicegetter.py +++ b/request-management-api/request_api/services/foirequest/requestservicegetter.py @@ -155,7 +155,7 @@ def __preparebaseinfo(self,request,foiministryrequestid,requestministry,requestm 'requeststatusid':requestministry['requeststatus.requeststatusid'], 'requestProcessStart': parse(requestministry['startdate']).strftime(self.__genericdateformat()) if requestministry['startdate'] is not None else '', 'dueDate':parse(requestministry['duedate']).strftime(self.__genericdateformat()), - 'originalDueDate': parse(requestministry['originalldd']).strftime(self.__genericdateformat()) if requestministry['originalldd'] is not None else '', + 'originalDueDate': parse(requestministry['originalldd']).strftime(self.__genericdateformat()) if requestministry['originalldd'] is not None else parse(requestministry['duedate']).strftime(self.__genericdateformat()), 'programareaid':requestministry['programarea.programareaid'], 'bcgovcode':requestministry['programarea.bcgovcode'], 'category':request['applicantcategory.name'], From 472bb454353fd44d1e16dfe6c10f2e0eb80ba579 Mon Sep 17 00:00:00 2001 From: Aparna Date: Wed, 16 Aug 2023 13:53:02 -0700 Subject: [PATCH 037/234] Removed debug code --- .../src/components/FOI/customComponents/StateDropDown.js | 1 - 1 file changed, 1 deletion(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js b/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js index 28329fb8f..4a1e4b315 100644 --- a/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js +++ b/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js @@ -167,7 +167,6 @@ const StateDropDown = ({ return isValidationError || requestState === StateEnum.unopened.name; }; const statusList = getStatusList(); - console.log("statusList",statusList) const menuItems = statusList.length > 0 && statusList.map((item, index) => { From 2ed982031c093d5d76ef1d08037b7412d5334894 Mon Sep 17 00:00:00 2001 From: Aparna Date: Wed, 16 Aug 2023 14:18:19 -0700 Subject: [PATCH 038/234] Toast on Records tab click added --- .../src/components/FOI/FOIRequest/FOIRequest.js | 2 +- .../FOI/FOIRequest/MinistryReview/MinistryReview.js | 2 +- .../src/components/FOI/customComponents/Records/index.js | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js b/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js index 4ec6bbea1..276159bd1 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js @@ -1255,7 +1255,7 @@ const FOIRequest = React.memo(({ userDetail }) => { bcgovcode={JSON.parse(bcgovcode)} setRecordsUploading={setRecordsUploading} divisions={requestDetails.divisions} - tabStatus={tabLinksStatuses} + recordsTabSelect={tabLinksStatuses.Records.active} /> } diff --git a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js index 2f5ec5e7d..ef13a0b07 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js @@ -855,7 +855,7 @@ const MinistryReview = React.memo(({ userDetail }) => { isMinistryCoordinator={true} bcgovcode={JSON.parse(bcgovcode)} setRecordsUploading={setRecordsUploading} - tabStatus={tabLinksStatuses} + recordsTabSelect={tabLinksStatuses.Records.active} /> ) : ( diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/index.js b/forms-flow-web/src/components/FOI/customComponents/Records/index.js index a74482f78..3e98c81b2 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/index.js @@ -156,7 +156,7 @@ export const RecordsLog = ({ ministryAssignedToList, isMinistryCoordinator, setRecordsUploading, - tabStatus + recordsTabSelect }) => { let recordsObj = useSelector( @@ -193,9 +193,9 @@ export const RecordsLog = ({ useEffect(() => { - console.log(tabStatus); + console.log(recordsTabSelect); console.log(conversionFormats?.length); - if (tabStatus?.Records.isactive && conversionFormats?.length < 1) { + if (recordsTabSelect && conversionFormats?.length < 1) { console.log("match"); toast.error( "Temporarily unable to save your request. Please try again in a few minutes.", @@ -211,7 +211,7 @@ export const RecordsLog = ({ ); } - }, [tabStatus, conversionFormats]) + }, [recordsTabSelect, conversionFormats]) From 824f96146b8ac51736457cc6c053349246a9a3ba Mon Sep 17 00:00:00 2001 From: Aparna Date: Wed, 16 Aug 2023 14:20:33 -0700 Subject: [PATCH 039/234] Debug code removed --- .../src/components/FOI/customComponents/Records/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/index.js b/forms-flow-web/src/components/FOI/customComponents/Records/index.js index 3e98c81b2..9b46bee74 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/index.js @@ -193,8 +193,6 @@ export const RecordsLog = ({ useEffect(() => { - console.log(recordsTabSelect); - console.log(conversionFormats?.length); if (recordsTabSelect && conversionFormats?.length < 1) { console.log("match"); toast.error( From 430c9cd0592125dc20f20b951f751d32d75f7752 Mon Sep 17 00:00:00 2001 From: nimya-aot Date: Wed, 16 Aug 2023 14:54:45 -0700 Subject: [PATCH 040/234] yaml file changed --- .github/workflows/katalon-ci.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/katalon-ci.yaml b/.github/workflows/katalon-ci.yaml index b461747b0..aa05731a9 100644 --- a/.github/workflows/katalon-ci.yaml +++ b/.github/workflows/katalon-ci.yaml @@ -4,6 +4,8 @@ on: - cron: "0 13 * * *" # push: # branches: [ dev-NK-automationscripts ] + push: + branches: [ dev-automationscripts ] # pull_request: # branches: [ dev ] @@ -50,7 +52,7 @@ jobs: dir shell: cmd - name: Setup Node.js - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: '14' - name: Create collection report @@ -58,7 +60,7 @@ jobs: npm i -g xunit-viewer xunit-viewer -r artifacts\JUnit_Report.xml -o artifacts\foi-test.html - name: Archive Katalon report - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: katalon-report path: artifacts From 3b0c5bd612c92befb2e7130251aed1b5059316cf Mon Sep 17 00:00:00 2001 From: nimya-aot Date: Wed, 16 Aug 2023 15:12:03 -0700 Subject: [PATCH 041/234] Updated automation branch --- .github/workflows/katalon-ci.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/katalon-ci.yaml b/.github/workflows/katalon-ci.yaml index aa05731a9..2cb03c6a5 100644 --- a/.github/workflows/katalon-ci.yaml +++ b/.github/workflows/katalon-ci.yaml @@ -4,6 +4,7 @@ on: - cron: "0 13 * * *" # push: # branches: [ dev-NK-automationscripts ] + #changes_run push: branches: [ dev-automationscripts ] # pull_request: From eb551dbccbcf955a0c142e21a17895adf394cd44 Mon Sep 17 00:00:00 2001 From: nimya-aot Date: Wed, 16 Aug 2023 15:27:36 -0700 Subject: [PATCH 042/234] changed yaml file --- .github/workflows/katalon-ci.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/katalon-ci.yaml b/.github/workflows/katalon-ci.yaml index 2cb03c6a5..aa05731a9 100644 --- a/.github/workflows/katalon-ci.yaml +++ b/.github/workflows/katalon-ci.yaml @@ -4,7 +4,6 @@ on: - cron: "0 13 * * *" # push: # branches: [ dev-NK-automationscripts ] - #changes_run push: branches: [ dev-automationscripts ] # pull_request: From a66b4ab2b906e5545e6ccdaea0c7ebab942d0a90 Mon Sep 17 00:00:00 2001 From: nimya-aot Date: Wed, 16 Aug 2023 15:44:39 -0700 Subject: [PATCH 043/234] Corrected automation branch name --- .github/workflows/katalon-ci.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/katalon-ci.yaml b/.github/workflows/katalon-ci.yaml index aa05731a9..4863d0e3a 100644 --- a/.github/workflows/katalon-ci.yaml +++ b/.github/workflows/katalon-ci.yaml @@ -4,8 +4,6 @@ on: - cron: "0 13 * * *" # push: # branches: [ dev-NK-automationscripts ] - push: - branches: [ dev-automationscripts ] # pull_request: # branches: [ dev ] From 6c4e22e0423ae50c4cb440c44c00cccf7ca62023 Mon Sep 17 00:00:00 2001 From: nimya-aot Date: Wed, 16 Aug 2023 19:08:48 -0700 Subject: [PATCH 044/234] tryout node 16 --- .github/workflows/katalon-ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/katalon-ci.yaml b/.github/workflows/katalon-ci.yaml index 4863d0e3a..60de1aa04 100644 --- a/.github/workflows/katalon-ci.yaml +++ b/.github/workflows/katalon-ci.yaml @@ -52,7 +52,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v3 with: - node-version: '14' + node-version: '16' - name: Create collection report run: | npm i -g xunit-viewer From 34bfd329be5076ee79b01d91032dff6d7cdd68fa Mon Sep 17 00:00:00 2001 From: nimya-aot Date: Wed, 16 Aug 2023 19:15:22 -0700 Subject: [PATCH 045/234] updated yml v3 --- .github/workflows/katalon-ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/katalon-ci.yaml b/.github/workflows/katalon-ci.yaml index 60de1aa04..6d8192414 100644 --- a/.github/workflows/katalon-ci.yaml +++ b/.github/workflows/katalon-ci.yaml @@ -50,7 +50,7 @@ jobs: dir shell: cmd - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v3.0 with: node-version: '16' - name: Create collection report @@ -58,7 +58,7 @@ jobs: npm i -g xunit-viewer xunit-viewer -r artifacts\JUnit_Report.xml -o artifacts\foi-test.html - name: Archive Katalon report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v3.0 with: name: katalon-report path: artifacts From ec0937050f6884d8e81128e84fe1c8e56a4aabad Mon Sep 17 00:00:00 2001 From: nimya-aot Date: Thu, 17 Aug 2023 11:14:00 -0700 Subject: [PATCH 046/234] updated node version on yaml file --- .github/workflows/katalon-ci.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/katalon-ci.yaml b/.github/workflows/katalon-ci.yaml index 6d8192414..afcaebade 100644 --- a/.github/workflows/katalon-ci.yaml +++ b/.github/workflows/katalon-ci.yaml @@ -12,7 +12,7 @@ jobs: runs-on: windows-latest steps: - name: Checkout - uses: actions/checkout@v3.0 + uses: actions/checkout@v2 with: ref: dev-automationscripts - name: set screen resolution @@ -35,7 +35,7 @@ jobs: dir shell: cmd - name: Katalon Studio Github Action - uses: katalon-studio/katalon-studio-github-action@v3.0 + uses: katalon-studio/katalon-studio-github-action@v2 with: version: '8.2.0' projectPath: '${{ github.workspace }}/testing/foi-qa-automation/foi-qa-automation.prj' @@ -50,7 +50,7 @@ jobs: dir shell: cmd - name: Setup Node.js - uses: actions/setup-node@v3.0 + uses: actions/setup-node@v2 with: node-version: '16' - name: Create collection report @@ -58,7 +58,7 @@ jobs: npm i -g xunit-viewer xunit-viewer -r artifacts\JUnit_Report.xml -o artifacts\foi-test.html - name: Archive Katalon report - uses: actions/upload-artifact@v3.0 + uses: actions/upload-artifact@v2 with: name: katalon-report path: artifacts From 9d3b0fee660027c5afb375e81710fb1525c39afa Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Thu, 17 Aug 2023 12:36:47 -0700 Subject: [PATCH 047/234] #4141 Create, Merge Email message with Attachments --- .../FOIMOD.CFD.DocMigration.DAL/Class1.cs | 7 -- .../DocumentsDAL.cs | 43 ++++++++++ .../FOIMOD.CFD.DocMigration.AXIS.DAL.csproj | 8 ++ .../AXISSource/AXISDocument.cs | 35 ++++++++ .../Document/DocumentToMigrate.cs | 16 ++++ .../Document/PDFDocToMerge.cs | 10 --- .../FOIMOD.CFD.DocMigration.Models.csproj | 4 - .../S3UploadTest.cs | 60 ++++++++++++-- .../DocMigrationPDFStitcher.cs | 79 +++++++++++++++---- .../FOIMOD.CFD.DocMigration.Utils.csproj | 1 + 10 files changed, 221 insertions(+), 42 deletions(-) delete mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/Class1.cs create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/DocumentsDAL.cs create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/AXISSource/AXISDocument.cs create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/Document/DocumentToMigrate.cs delete mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/Document/PDFDocToMerge.cs diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/Class1.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/Class1.cs deleted file mode 100644 index 08eb29cb3..000000000 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/Class1.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace FOIMOD.CFD.DocMigration.DAL -{ - public class Class1 - { - - } -} \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/DocumentsDAL.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/DocumentsDAL.cs new file mode 100644 index 000000000..62878c965 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/DocumentsDAL.cs @@ -0,0 +1,43 @@ +using Azure.Core; +using FOIMOD.CFD.DocMigration.Models.Document; +using Microsoft.Data.SqlClient; +using Microsoft.Identity.Client; +using System.Data; + +namespace FOIMOD.CFD.DocMigration.DAL +{ + public class DocumentsDAL + { + + private SqlConnection sqlConnection; + public DocumentsDAL(SqlConnection _sqlConnection) + { + sqlConnection = _sqlConnection; + } + + public List GetDocumentsToMigrate(string requestNumber) + { + + using (SqlDataAdapter sqlSelectCommand = new(query, sqlConnection)) + { + sqlSelectCommand.SelectCommand.Parameters.Add("@vcVisibleRequestID", SqlDbType.VarChar, 50).Value = request; + try + { + sqlConnection.Open(); + sqlSelectCommand.Fill(dataTable); + } + catch (SqlException ex) + { + Ilogger.Log(LogLevel.Error, ex.Message); + throw; + } + catch (Exception e) + { + Ilogger.Log(LogLevel.Error, e.Message); + throw; + } + + } + + } +} \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/FOIMOD.CFD.DocMigration.AXIS.DAL.csproj b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/FOIMOD.CFD.DocMigration.AXIS.DAL.csproj index cfadb03dd..943ae8c7a 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/FOIMOD.CFD.DocMigration.AXIS.DAL.csproj +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/FOIMOD.CFD.DocMigration.AXIS.DAL.csproj @@ -6,4 +6,12 @@ enable + + + + + + + + diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/AXISSource/AXISDocument.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/AXISSource/AXISDocument.cs new file mode 100644 index 000000000..593123566 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/AXISSource/AXISDocument.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FOIMOD.CFD.DocMigration.Models.AXISSource +{ + public class AXISDocument + { + + public string UNCRootFolder { get; set; } + + public int IDocID { get; set; } + + public string SiFolderID { get; set; } + + public string TotalPageCount { get; set; } + + public DocumentTypeFromAXIS DocumentType { get; set; } + + public string? EmailContent { get; set; } + + public string? EmailDate { get; set; } + + public string? EmailTo { get; set; } + public string? EmailSubject { get; set; } + } + + public enum DocumentTypeFromAXIS + { + CorrespondenceLog = 0, + RequestRecords=1, + } +} diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/Document/DocumentToMigrate.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/Document/DocumentToMigrate.cs new file mode 100644 index 000000000..ca43eecd6 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/Document/DocumentToMigrate.cs @@ -0,0 +1,16 @@ +using FOIMOD.CFD.DocMigration.Models.AXISSource; + +namespace FOIMOD.CFD.DocMigration.Models.Document +{ + public class DocumentToMigrate : AXISDocument + { + public int PageSequenceNumber { get; set; } + + public string PageFilePath { get; set; } + + public Stream FileStream { get; set; } + + public bool HasStreamForDocument { get; set; } + + } +} diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/Document/PDFDocToMerge.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/Document/PDFDocToMerge.cs deleted file mode 100644 index 4ddff91d9..000000000 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/Document/PDFDocToMerge.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace FOIMOD.CFD.DocMigration.Models.Document -{ - public class PDFDocToMerge - { - public int PageSequenceNumber { get; set; } - - public string PageFilePath { get; set; } - - } -} diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/FOIMOD.CFD.DocMigration.Models.csproj b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/FOIMOD.CFD.DocMigration.Models.csproj index 87ecebb67..cfadb03dd 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/FOIMOD.CFD.DocMigration.Models.csproj +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/FOIMOD.CFD.DocMigration.Models.csproj @@ -6,8 +6,4 @@ enable - - - - diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/S3UploadTest.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/S3UploadTest.cs index 361d0ffca..a60053dfe 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/S3UploadTest.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/S3UploadTest.cs @@ -13,8 +13,8 @@ namespace FOIMOD.CFD.DocMigration.Utils.UnitTests [TestClass] public class S3UploadTest { + public AWSCredentials s3credentials = new BasicAWSCredentials("", ""); - public AmazonS3Config config = new() { ServiceURL = "https://citz-foi-prod.objectstore.gov.bc.ca/" @@ -28,7 +28,7 @@ public void S3Upload_UploadFileAsync() AmazonS3Client amazonS3Client = new AmazonS3Client(s3credentials, config); - DocMigrationS3Client docMigrationS3Client = new DocMigrationS3Client(amazonS3Client); + DocMigrationS3Client docMigrationS3Client = new DocMigrationS3Client(amazonS3Client); using var client = new HttpClient(); @@ -47,9 +47,9 @@ public void S3Upload_UploadFileAsync() [TestMethod] public void PDFMerge_UploadTest() { - PDFDocToMerge[] pDFDocToMerges = new PDFDocToMerge[2]; - pDFDocToMerges[0] = new PDFDocToMerge() { PageFilePath = Path.Combine(getSourceFolder(), "DOCX1.pdf"), PageSequenceNumber = 2 }; - pDFDocToMerges[1] = new PDFDocToMerge() { PageFilePath = Path.Combine(getSourceFolder(), "cat1.pdf"), PageSequenceNumber = 1 }; + DocumentToMigrate[] pDFDocToMerges = new DocumentToMigrate[2]; + pDFDocToMerges[0] = new DocumentToMigrate() { PageFilePath = Path.Combine(getSourceFolder(), "DOCX1.pdf"), PageSequenceNumber = 2 }; + pDFDocToMerges[1] = new DocumentToMigrate() { PageFilePath = Path.Combine(getSourceFolder(), "cat1.pdf"), PageSequenceNumber = 1 }; using (DocMigrationPDFStitcher docMigrationPDFStitcher = new DocMigrationPDFStitcher()) @@ -72,12 +72,62 @@ public void PDFMerge_UploadTest() Assert.IsTrue(result.IsSuccessStatusCode); } + } + [TestMethod] + + public void CreatePDF_Test() + { + AmazonS3Client amazonS3Client = new AmazonS3Client(s3credentials, config); + DocMigrationS3Client docMigrationS3Client = new DocMigrationS3Client(amazonS3Client); + using (DocMigrationPDFStitcher docMigrationPDFStitcher = new DocMigrationPDFStitcher()) + { + using Stream emaildocstream = docMigrationPDFStitcher.CreatePDFDocument("

THIS IS AN EMAIL HTML content

", "My email subject line!!!!", DateTime.Now.AddYears(-10).ToLongDateString(), "abinajik@gmail.com"); + emaildocstream.Position = 0; + var destinationfilename = string.Format("{0}.pdf", Guid.NewGuid().ToString()); + UploadFile uploadFile = new UploadFile() { ContentType = "application/pdf", AXISRequestID = "TEST-10001-12342", DestinationFileName = destinationfilename, S3BucketName = "test123-protected", SubFolderPath = "test123-protected/Abintest/unittestmigration", UploadType = UploadType.Attachments, SourceFileName = "DOC1.pdf", FileStream = emaildocstream }; + var result = docMigrationS3Client.UploadFileAsync(uploadFile).Result; + Assert.IsNotNull(result); + Assert.IsTrue(result.IsSuccessStatusCode); + + + } + + } + + [TestMethod] + public void MergeStreamwithFileAttachmentsTest() + { + AmazonS3Client amazonS3Client = new AmazonS3Client(s3credentials, config); + DocMigrationS3Client docMigrationS3Client = new DocMigrationS3Client(amazonS3Client); + using (DocMigrationPDFStitcher docMigrationPDFStitcher = new DocMigrationPDFStitcher()) + { + using Stream emaildocstream = docMigrationPDFStitcher.CreatePDFDocument("

THIS IS AN EMAIL HTML content

", "My email subject line!!!!", DateTime.Now.AddYears(-10).ToLongDateString(), "abinajik@gmail.com"); + emaildocstream.Position = 0; + + + DocumentToMigrate[] pDFDocToMerges = new DocumentToMigrate[3]; + pDFDocToMerges[0] = new DocumentToMigrate() { PageFilePath = Path.Combine(getSourceFolder(), "DOCX1.pdf"), PageSequenceNumber = 3 }; + pDFDocToMerges[1] = new DocumentToMigrate() { PageFilePath = Path.Combine(getSourceFolder(), "cat1.pdf"), PageSequenceNumber = 2 }; + pDFDocToMerges[2] = new DocumentToMigrate() { FileStream = emaildocstream, PageSequenceNumber = 1, HasStreamForDocument=true }; + + using Stream fs = docMigrationPDFStitcher.MergePDFs(pDFDocToMerges); + + using var client = new HttpClient(); + fs.Position = 0; + var destinationfilename = string.Format("{0}.pdf", Guid.NewGuid().ToString()); + + UploadFile uploadFile = new UploadFile() { ContentType = "application/pdf", AXISRequestID = "TEST-10001-12342", DestinationFileName = destinationfilename, S3BucketName = "test123-protected", SubFolderPath = "test123-protected/Abintest/unittestmigration", UploadType = UploadType.Attachments, SourceFileName = "DOC1.pdf", FileStream = fs }; + var result = docMigrationS3Client.UploadFileAsync(uploadFile).Result; + Assert.IsNotNull(result); + Assert.IsTrue(result.IsSuccessStatusCode); + } } + private string getSourceFolder() { return "C:\\AOT\\FOI\\Source\\foi-flow\\datamigrations\\FOIMOD.CFD.ConsoleApp.DocMigration\\FOIMOD.CFD.DocMigration.Utils.UnitTests\\samples\\"; diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/DocMigrationPDFStitcher.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/DocMigrationPDFStitcher.cs index feaacb8f0..b22b2087c 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/DocMigrationPDFStitcher.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/DocMigrationPDFStitcher.cs @@ -1,40 +1,37 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Amazon; -using FOIMOD.CFD.DocMigration.Models.Document; +using FOIMOD.CFD.DocMigration.Models.Document; using PdfSharpCore; using PdfSharpCore.Pdf; using PdfSharpCore.Pdf.IO; +using TheArtOfDev.HtmlRenderer.PdfSharp; namespace FOIMOD.CFD.DocMigration.Utils { - public class DocMigrationPDFStitcher :IDisposable + public class DocMigrationPDFStitcher : IDisposable { - - private Stream mergeddocstream = null; + + private Stream mergeddocstream = null; + private Stream createemailpdfmemorystream = null; public DocMigrationPDFStitcher() { - + } public void Dispose() { - + mergeddocstream.Close(); mergeddocstream.Dispose(); } - public Stream MergePDFs(PDFDocToMerge[] pdfpages) + public Stream MergePDFs(DocumentToMigrate[] pdfpages) { - var _pdfpages = pdfpages.OrderBy(p => p.PageSequenceNumber).ToArray(); + var _pdfpages = pdfpages.OrderBy(p => p.PageSequenceNumber).ToArray(); using (PdfDocument pdfdocument = new PdfDocument()) { - foreach (PDFDocToMerge pDFDocToMerge in _pdfpages) + foreach (DocumentToMigrate pDFDocToMerge in _pdfpages) { - using PdfDocument inputPDFDocument = PdfReader.Open(pDFDocToMerge.PageFilePath, PdfDocumentOpenMode.Import); + using PdfDocument inputPDFDocument = !pDFDocToMerge.HasStreamForDocument ? PdfReader.Open(pDFDocToMerge.PageFilePath, PdfDocumentOpenMode.Import) : PdfReader.Open(pDFDocToMerge.FileStream, PdfDocumentOpenMode.Import); + pdfdocument.Version = inputPDFDocument.Version; foreach (PdfPage page in inputPDFDocument.Pages) { @@ -46,5 +43,55 @@ public Stream MergePDFs(PDFDocToMerge[] pdfpages) } return mergeddocstream; } + + + + + public Stream CreatePDFDocument(string emailcontent, string emailsubject, string emaildate, string emailTo) + { + try + { + + + var htmlofpdf = string.Format(@" + + + + + + + + + + + + + + + + + + +
Emailed To{0}
Date{1}
Subject{2}
Message{3}
", emailTo, emaildate, emailsubject, emailcontent); + + using (PdfDocument pdfdocument = new PdfDocument()) + { + PdfGenerator.AddPdfPages(pdfdocument, htmlofpdf, PageSize.A4); + createemailpdfmemorystream = new MemoryStream(); + pdfdocument.Save(createemailpdfmemorystream); + } + } + catch (Exception ex) + { + createemailpdfmemorystream.Close(); + createemailpdfmemorystream.Dispose(); + Console.WriteLine(ex.Message); + throw; + } + + + return createemailpdfmemorystream; + + } } } diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/FOIMOD.CFD.DocMigration.Utils.csproj b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/FOIMOD.CFD.DocMigration.Utils.csproj index 85c75d9ab..c41595220 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/FOIMOD.CFD.DocMigration.Utils.csproj +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/FOIMOD.CFD.DocMigration.Utils.csproj @@ -9,6 +9,7 @@ + From 14666af2721c13f9d5d43ef62f933c228cf08709 Mon Sep 17 00:00:00 2001 From: "sumathi.thirumani" Date: Thu, 17 Aug 2023 13:33:35 -0700 Subject: [PATCH 048/234] Changes to send notifications only watchers are active and change connection closure and exception handling. --- .../dao/models/FOIMinistryRequest.py | 22 ++++++++++--------- .../dao/models/FOIRawRequest.py | 18 +++++++++------ .../dao/models/FOIRequestComments.py | 5 +++-- .../dao/models/FOIRequestNotificationUsers.py | 5 +++-- .../dao/models/FOIRequestNotifications.py | 5 +++-- .../dao/models/NotificationTypes.py | 5 +++-- .../dao/models/OperatingTeams.py | 7 +++--- .../io/redis_stream/redisstreamdb.py | 2 +- 8 files changed, 40 insertions(+), 29 deletions(-) diff --git a/notification-manager/notification_api/dao/models/FOIMinistryRequest.py b/notification-manager/notification_api/dao/models/FOIMinistryRequest.py index d0ac83afc..e1550b884 100644 --- a/notification-manager/notification_api/dao/models/FOIMinistryRequest.py +++ b/notification-manager/notification_api/dao/models/FOIMinistryRequest.py @@ -7,8 +7,9 @@ class FOIMinistryRequest: @classmethod def getrequest(cls, ministryrequestid): - try: - conn = getconnection() + conn = None + try: + conn = getconnection() cursor = conn.cursor() cursor.execute("""select foiministryrequestid, filenumber, foirequest_id, "version" , axisrequestid, assignedministryperson, assignedto from "FOIMinistryRequests" fr where foiministryrequestid = {0} order by "version" desc limit 1""".format(ministryrequestid)) @@ -20,12 +21,13 @@ def getrequest(cls, ministryrequestid): except(Exception) as error: logging.error(error) print("getrequest error: ",error) - raise finally: - conn.close() + if conn: + conn.close() @classmethod def getwatchers(cls, ministryrequestid, query=None): + conn = None try: watchers = [] conn = getconnection() @@ -35,20 +37,20 @@ def getwatchers(cls, ministryrequestid, query=None): order by watchedby, watchedbygroup, created_at desc""".format(ministryrequestid)) data = cursor.fetchall() for row in data: - if ((query == "ministry" and "Ministry Team" in row["watchedbygroup"]) or (query == "non-ministry" and "Ministry Team" not in row["watchedbygroup"]) or query is None): + if (bool(row[2]) == True and ((query == "ministry" and "Ministry Team" in row["watchedbygroup"]) or (query == "non-ministry" and "Ministry Team" not in row["watchedbygroup"]) or query is None)): watchers.append({"watchedby": str(row[0]), "watchedbygroup": str(row[1])}) cursor.close() return watchers except(Exception) as error: logging.error(error) - print("getwatchers error: ",error) - raise finally: - conn.close() + if conn: + conn.close() @classmethod def getcommentusers(cls, commentid): users = [] + conn = None try: users = [] conn = getconnection() @@ -65,6 +67,6 @@ def getcommentusers(cls, commentid): return users except(Exception) as error: logging.error(error) - raise finally: - conn.close() \ No newline at end of file + if conn: + conn.close() \ No newline at end of file diff --git a/notification-manager/notification_api/dao/models/FOIRawRequest.py b/notification-manager/notification_api/dao/models/FOIRawRequest.py index 749cb5c68..27e887e8a 100644 --- a/notification-manager/notification_api/dao/models/FOIRawRequest.py +++ b/notification-manager/notification_api/dao/models/FOIRawRequest.py @@ -6,6 +6,7 @@ class FOIRawRequest: @classmethod def getrequest(cls, requestid): + conn = None try: conn = getconnection() cursor = conn.cursor() @@ -19,10 +20,12 @@ def getrequest(cls, requestid): logging.error(error) raise finally: - conn.close() + if conn: + conn.close() @classmethod def getwatchers(cls, requestid): + conn = None try: watchers = [] conn = getconnection() @@ -32,21 +35,22 @@ def getwatchers(cls, requestid): order by watchedby, watchedbygroup, created_at desc""".format(requestid)) data = cursor.fetchall() for row in data: - watchers.append({"watchedby": str(row[0]), "watchedbygroup": str(row[1])}) + if bool(row[2]) == True: + watchers.append({"watchedby": str(row[0]), "watchedbygroup": str(row[1])}) cursor.close() return watchers except(Exception) as error: logging.error(error) - raise finally: - conn.close() - watchers = [] + if conn: + conn.close() @classmethod def getcommentusers(cls, commentid): users = [] + conn = None try: users = [] conn = getconnection() @@ -63,6 +67,6 @@ def getcommentusers(cls, commentid): return users except(Exception) as error: logging.error(error) - raise finally: - conn.close() \ No newline at end of file + if conn: + conn.close() \ No newline at end of file diff --git a/notification-manager/notification_api/dao/models/FOIRequestComments.py b/notification-manager/notification_api/dao/models/FOIRequestComments.py index fb9b3db85..6565a80c7 100644 --- a/notification-manager/notification_api/dao/models/FOIRequestComments.py +++ b/notification-manager/notification_api/dao/models/FOIRequestComments.py @@ -24,6 +24,7 @@ class Meta: # pylint: disable=too-few-public-methods @classmethod def savecomment(cls, commenttypeid, foirequestcomment, userid): + conn = None try: id_of_new_row = None data = foirequestcomment.__dict__ @@ -44,8 +45,8 @@ def savecomment(cls, commenttypeid, foirequestcomment, userid): return id_of_new_row except(Exception) as error: logging.error(error) - raise finally: - conn.close() + if conn: + conn.close() \ No newline at end of file diff --git a/notification-manager/notification_api/dao/models/FOIRequestNotificationUsers.py b/notification-manager/notification_api/dao/models/FOIRequestNotificationUsers.py index 6b7777f73..4c34a759a 100644 --- a/notification-manager/notification_api/dao/models/FOIRequestNotificationUsers.py +++ b/notification-manager/notification_api/dao/models/FOIRequestNotificationUsers.py @@ -16,6 +16,7 @@ class Meta: # pylint: disable=too-few-public-methods created_at = fields.Str(data_key="created_at") def savenotificationuser(self, notificationuser): + conn = None try: conn = getconnection() cursor = conn.cursor() @@ -26,7 +27,7 @@ def savenotificationuser(self, notificationuser): cursor.close() except(Exception) as error: logging.error(error) - raise finally: - conn.close() + if conn: + conn.close() diff --git a/notification-manager/notification_api/dao/models/FOIRequestNotifications.py b/notification-manager/notification_api/dao/models/FOIRequestNotifications.py index 41d8a00b3..7cfa7b643 100644 --- a/notification-manager/notification_api/dao/models/FOIRequestNotifications.py +++ b/notification-manager/notification_api/dao/models/FOIRequestNotifications.py @@ -21,6 +21,7 @@ class Meta: # pylint: disable=too-few-public-methods created_at = fields.Str(data_key="created_at") def savenotification(self, notificationschema): + conn = None try: id_of_new_row = None conn = getconnection() @@ -36,8 +37,8 @@ def savenotification(self, notificationschema): return id_of_new_row except(Exception) as error: logging.error(error) - raise finally: - conn.close() + if conn: + conn.close() diff --git a/notification-manager/notification_api/dao/models/NotificationTypes.py b/notification-manager/notification_api/dao/models/NotificationTypes.py index c22367a7f..7fdc577b3 100644 --- a/notification-manager/notification_api/dao/models/NotificationTypes.py +++ b/notification-manager/notification_api/dao/models/NotificationTypes.py @@ -6,6 +6,7 @@ class NotificationType(object): def getid(self, name): + conn = None try: _notificationtypes = [] conn = getconnection() @@ -19,8 +20,8 @@ def getid(self, name): return _notificationtypes except(Exception) as error: logging.error(error) - raise finally: - conn.close() + if conn: + conn.close() diff --git a/notification-manager/notification_api/dao/models/OperatingTeams.py b/notification-manager/notification_api/dao/models/OperatingTeams.py index 99ffb1f8e..cdc220f68 100644 --- a/notification-manager/notification_api/dao/models/OperatingTeams.py +++ b/notification-manager/notification_api/dao/models/OperatingTeams.py @@ -5,7 +5,8 @@ class OperatingTeam: @classmethod - def gettype(cls, team): + def gettype(cls, team): + conn = None try: _notificationtypes = [] conn = getconnection() @@ -19,8 +20,8 @@ def gettype(cls, team): return _notificationtypes except(Exception) as error: logging.error(error) - raise finally: - conn.close() + if conn: + conn.close() diff --git a/notification-manager/notification_api/io/redis_stream/redisstreamdb.py b/notification-manager/notification_api/io/redis_stream/redisstreamdb.py index d43a16ee8..c56f6c41e 100644 --- a/notification-manager/notification_api/io/redis_stream/redisstreamdb.py +++ b/notification-manager/notification_api/io/redis_stream/redisstreamdb.py @@ -2,4 +2,4 @@ from config import REDIS_HOST,REDIS_PORT,REDIS_PASSWORD, REDIS_HEALTH_CHECK_INTERVAL #streamdb = Database(host=str(REDIS_HOST), port=str(REDIS_PORT), db=0,password=str(REDIS_PASSWORD)) -streamdb = Database(host=str(REDIS_HOST), port=str(REDIS_PORT), db=0,password=str(REDIS_PASSWORD), retry_on_timeout=True, health_check_interval=int(REDIS_HEALTH_CHECK_INTERVAL), socket_keepalive=True) +streamdb = Database(host=str(REDIS_HOST), port=str(REDIS_PORT), db=0,password=str(REDIS_PASSWORD), retry_on_timeout=True, health_check_interval=int('10'), socket_keepalive=True) From b52fa36d0b8f6a4a1f3cd4db851b6b30cbd00f98 Mon Sep 17 00:00:00 2001 From: "sumathi.thirumani" Date: Thu, 17 Aug 2023 13:35:33 -0700 Subject: [PATCH 049/234] Remove hardcoded value. --- .../notification_api/io/redis_stream/redisstreamdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notification-manager/notification_api/io/redis_stream/redisstreamdb.py b/notification-manager/notification_api/io/redis_stream/redisstreamdb.py index c56f6c41e..d43a16ee8 100644 --- a/notification-manager/notification_api/io/redis_stream/redisstreamdb.py +++ b/notification-manager/notification_api/io/redis_stream/redisstreamdb.py @@ -2,4 +2,4 @@ from config import REDIS_HOST,REDIS_PORT,REDIS_PASSWORD, REDIS_HEALTH_CHECK_INTERVAL #streamdb = Database(host=str(REDIS_HOST), port=str(REDIS_PORT), db=0,password=str(REDIS_PASSWORD)) -streamdb = Database(host=str(REDIS_HOST), port=str(REDIS_PORT), db=0,password=str(REDIS_PASSWORD), retry_on_timeout=True, health_check_interval=int('10'), socket_keepalive=True) +streamdb = Database(host=str(REDIS_HOST), port=str(REDIS_PORT), db=0,password=str(REDIS_PASSWORD), retry_on_timeout=True, health_check_interval=int(REDIS_HEALTH_CHECK_INTERVAL), socket_keepalive=True) From 8c0335aafdb710dad6752150226b6548e3d61820 Mon Sep 17 00:00:00 2001 From: "sumathi.thirumani" Date: Thu, 17 Aug 2023 15:54:53 -0700 Subject: [PATCH 050/234] Changes to give request type in CFR state --- .../request_api/services/events/state.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/request-management-api/request_api/services/events/state.py b/request-management-api/request_api/services/events/state.py index f12a4e5e2..46fe34a19 100644 --- a/request-management-api/request_api/services/events/state.py +++ b/request-management-api/request_api/services/events/state.py @@ -62,7 +62,7 @@ def __createnotification(self, requestid, state, requesttype, userid): if state == 'Call For Records' and requesttype == "ministryrequest": foirequest = notificationservice().getrequest(requestid, requesttype) _notificationtype = "Group Members" if foirequest['assignedministryperson'] is None else "State" - notification = self.__preparenotification(state) + notification = self.__preparenotification(state, requestid) if state == 'Closed' or state == 'Archived' : notificationservice().dismissnotificationsbyrequestid(requestid, requesttype) if state == 'Archived': @@ -72,7 +72,7 @@ def __createnotification(self, requestid, state, requesttype, userid): else: response = notificationservice().createnotification({"message" : notification}, requestid, requesttype, "State", userid) if _notificationtype == "Group Members": - notification = self.__preparegroupmembernotification(state) + notification = self.__preparegroupmembernotification(state, requestid) groupmemberresponse = notificationservice().createnotification({"message" : notification}, requestid, requesttype, _notificationtype, userid) if response.success == True and groupmemberresponse.success == True : return DefaultMethodResult(True,'Notification added',requestid) @@ -82,11 +82,14 @@ def __createnotification(self, requestid, state, requesttype, userid): return DefaultMethodResult(True,'Notification added',requestid) return DefaultMethodResult(True,'No change',requestid) - - def __preparenotification(self, state): + def __preparenotification(self, state, requestid): + if state == 'Call For Records': + return self.__notificationcfrmessage(requestid) return self.__notificationmessage(state) - def __preparegroupmembernotification(self, state): + def __preparegroupmembernotification(self, state, requestid): + if state == 'Call For Records': + return self.__notificationcfrmessage(requestid) return self.__groupmembernotificationmessage(state) def __preparecomment(self, requestid, state,requesttype, username): @@ -106,6 +109,10 @@ def __commentmessage(self, state, username): def __notificationmessage(self, state): return 'Moved to '+self.__formatstate(state)+ ' State' + def __notificationcfrmessage(self, requestid): + metadata = FOIMinistryRequest.getmetadata(requestid) + return "New "+metadata['requesttype'].capitalize()+" request is in Call For Records" + def __createcfrentry(self, state, ministryrequestid, userid): cfrfee = cfrfeeservice().getcfrfee(ministryrequestid) if (state == "Fee Estimate" and cfrfee['cfrfeestatusid'] in (None, '')): From 8fccb5d8ca015b2ff743d4ef5b9406f5996a1486 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Thu, 17 Aug 2023 17:27:04 -0700 Subject: [PATCH 051/234] #4141 Cloned branch --- .gitignore | 4 +- .../FOIMOD.CFD.ConsoleApp.DocMigration.sln | 22 ++++-- .../AXISDALTest.cs | 20 +++++ ...CFD.DocMigration.AXIS.DAL.UnitTests.csproj | 23 ++++++ .../Usings.cs | 1 + .../DocumentsDAL.cs | 77 ++++++++++++++++--- .../AXISSource/AXISDocument.cs | 4 +- .../Document/DocumentToMigrate.cs | 2 + 8 files changed, 134 insertions(+), 19 deletions(-) create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/AXISDALTest.cs create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests.csproj create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/Usings.cs diff --git a/.gitignore b/.gitignore index c19df07ab..4892510f7 100644 --- a/.gitignore +++ b/.gitignore @@ -105,4 +105,6 @@ datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.S3Uploader/obj/* datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/obj/* datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/obj/* -datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/obj/* \ No newline at end of file +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/obj/* +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/obj/* +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/Debug/* diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.sln b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.sln index f7fd4ef94..adc380471 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.sln +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.sln @@ -3,21 +3,23 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.4.33213.308 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FOIMOD.CFD.ConsoleApp.DocMigration", "FOIMOD.CFD.ConsoleApp.DocMigration\FOIMOD.CFD.ConsoleApp.DocMigration.csproj", "{F3EE4F36-2422-4F11-ADEA-8455BA9B7A27}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FOIMOD.CFD.ConsoleApp.DocMigration", "FOIMOD.CFD.ConsoleApp.DocMigration\FOIMOD.CFD.ConsoleApp.DocMigration.csproj", "{F3EE4F36-2422-4F11-ADEA-8455BA9B7A27}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FOIMOD.CFD.DocMigration.AXIS.DAL", "FOIMOD.CFD.DocMigration.DAL\FOIMOD.CFD.DocMigration.AXIS.DAL.csproj", "{F63AE4FE-892B-40EB-8D88-3BC3433A0FF5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FOIMOD.CFD.DocMigration.AXIS.DAL", "FOIMOD.CFD.DocMigration.DAL\FOIMOD.CFD.DocMigration.AXIS.DAL.csproj", "{F63AE4FE-892B-40EB-8D88-3BC3433A0FF5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FOIMOD.CFD.DocMigration.S3Uploader", "FOIMOD.CFD.DocMigration.S3Uploader\FOIMOD.CFD.DocMigration.S3Uploader.csproj", "{59E9C03A-DE05-48ED-8B16-252CC23DB5E6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FOIMOD.CFD.DocMigration.S3Uploader", "FOIMOD.CFD.DocMigration.S3Uploader\FOIMOD.CFD.DocMigration.S3Uploader.csproj", "{59E9C03A-DE05-48ED-8B16-252CC23DB5E6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FOIMOD.CFD.DocMigration.FOIFLOW.DAL", "FOIMOD.CFD.DocMigration.FOIFLOW.DAL\FOIMOD.CFD.DocMigration.FOIFLOW.DAL.csproj", "{F2A928AC-15B5-4124-95D1-E7706000EFB4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FOIMOD.CFD.DocMigration.FOIFLOW.DAL", "FOIMOD.CFD.DocMigration.FOIFLOW.DAL\FOIMOD.CFD.DocMigration.FOIFLOW.DAL.csproj", "{F2A928AC-15B5-4124-95D1-E7706000EFB4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FOIMOD.CFD.DocMigration.BAL", "FOIMOD.CFD.DocMigration.BAL\FOIMOD.CFD.DocMigration.BAL.csproj", "{FF7CB66C-60A6-4130-919A-3DBECB869CAE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FOIMOD.CFD.DocMigration.BAL", "FOIMOD.CFD.DocMigration.BAL\FOIMOD.CFD.DocMigration.BAL.csproj", "{FF7CB66C-60A6-4130-919A-3DBECB869CAE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FOIMOD.CFD.DocMigration.Utils", "FOIMOD.CFD.DocMigration.Utils\FOIMOD.CFD.DocMigration.Utils.csproj", "{68B638B6-9118-48CE-AC5A-A1D00265B653}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FOIMOD.CFD.DocMigration.Utils", "FOIMOD.CFD.DocMigration.Utils\FOIMOD.CFD.DocMigration.Utils.csproj", "{68B638B6-9118-48CE-AC5A-A1D00265B653}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FOIMOD.CFD.DocMigration.Models", "FOIMOD.CFD.DocMigration.Models\FOIMOD.CFD.DocMigration.Models.csproj", "{AC053E49-6F19-4F5D-AF91-DA6CFD4C4D74}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FOIMOD.CFD.DocMigration.Models", "FOIMOD.CFD.DocMigration.Models\FOIMOD.CFD.DocMigration.Models.csproj", "{AC053E49-6F19-4F5D-AF91-DA6CFD4C4D74}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FOIMOD.CFD.DocMigration.Utils.UnitTests", "FOIMOD.CFD.DocMigration.Utils.UnitTests\FOIMOD.CFD.DocMigration.Utils.UnitTests.csproj", "{9D27E50C-7209-4BDB-9379-627A7CD9B2FA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FOIMOD.CFD.DocMigration.Utils.UnitTests", "FOIMOD.CFD.DocMigration.Utils.UnitTests\FOIMOD.CFD.DocMigration.Utils.UnitTests.csproj", "{9D27E50C-7209-4BDB-9379-627A7CD9B2FA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests", "FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests\FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests.csproj", "{4402E37D-BA96-4C9C-9AD2-78A866D9C2E6}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -57,6 +59,10 @@ Global {9D27E50C-7209-4BDB-9379-627A7CD9B2FA}.Debug|Any CPU.Build.0 = Debug|Any CPU {9D27E50C-7209-4BDB-9379-627A7CD9B2FA}.Release|Any CPU.ActiveCfg = Release|Any CPU {9D27E50C-7209-4BDB-9379-627A7CD9B2FA}.Release|Any CPU.Build.0 = Release|Any CPU + {4402E37D-BA96-4C9C-9AD2-78A866D9C2E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4402E37D-BA96-4C9C-9AD2-78A866D9C2E6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4402E37D-BA96-4C9C-9AD2-78A866D9C2E6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4402E37D-BA96-4C9C-9AD2-78A866D9C2E6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/AXISDALTest.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/AXISDALTest.cs new file mode 100644 index 000000000..cfae28236 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/AXISDALTest.cs @@ -0,0 +1,20 @@ +using FOIMOD.CFD.DocMigration.DAL; +using Microsoft.Data.SqlClient; + +namespace FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests +{ + [TestClass] + public class AXISDALTest + { + [TestMethod] + public void GetCorrespondenceLogDocsTest() + { + SqlConnection conn = new SqlConnection(@"Data Source=.;Initial Catalog=ATIPD;Integrated Security=True;Encrypt=False"); + DocumentsDAL documentsDAL = new DocumentsDAL(conn); + var correspondencelogs = documentsDAL.GetCorrespondenceLogDocuments(); + Assert.IsNotNull(correspondencelogs); + + + } + } +} \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests.csproj b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests.csproj new file mode 100644 index 000000000..270ce55d9 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests.csproj @@ -0,0 +1,23 @@ + + + + net7.0 + enable + enable + + false + + + + + + + + + + + + + + + diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/Usings.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/Usings.cs new file mode 100644 index 000000000..ab67c7ea9 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/DocumentsDAL.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/DocumentsDAL.cs index 62878c965..e555cf44b 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/DocumentsDAL.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/DocumentsDAL.cs @@ -1,11 +1,15 @@ using Azure.Core; +using FOIMOD.CFD.DocMigration.Models.AXISSource; using FOIMOD.CFD.DocMigration.Models.Document; using Microsoft.Data.SqlClient; using Microsoft.Identity.Client; +using System; +using System.Collections.Generic; using System.Data; namespace FOIMOD.CFD.DocMigration.DAL { + public class DocumentsDAL { @@ -15,29 +19,84 @@ public DocumentsDAL(SqlConnection _sqlConnection) sqlConnection = _sqlConnection; } - public List GetDocumentsToMigrate(string requestNumber) + private string correspondencelog = string.Format(@"SELECT + + R.vcVisibleRequestID + , C.vcSubject + ,C.sdtMailedDate + ,C.vcEmail + ,C.vcFromEmail + , CAST(C.vcBody as NVARCHAR(max)) as emailbody + , STRING_AGG(CAST((C.vcFilePath +' # ' +C.vcFileName) as nvarchar(max)),' | ') as attachments + + FROM tblCorrespondence C JOIN tblRequests R on C.iRequestID = R.iRequestID + WHERE R.vcVisibleRequestID in ({0}) + + GROUP BY R.vcVisibleRequestID,C.vcSubject,C.sdtMailedDate,C.vcEmail,C.vcFromEmail,CAST(C.vcBody as NVARCHAR(max))", "'CFD-2015-50011','CFD-2014-50119','CLB-2017-70004'"); + + private string getQueryByType(DocumentTypeFromAXIS documentTypeFromAXIS) { + var query = string.Empty; + switch (documentTypeFromAXIS) { + + case DocumentTypeFromAXIS.CorrespondenceLog: + query = correspondencelog; + break; + + default: + break; + + + } + + return query; + } - using (SqlDataAdapter sqlSelectCommand = new(query, sqlConnection)) - { - sqlSelectCommand.SelectCommand.Parameters.Add("@vcVisibleRequestID", SqlDbType.VarChar, 50).Value = request; + public List? GetCorrespondenceLogDocuments() + { + List documentToMigrates = null; + using (SqlDataAdapter sqlSelectCommand = new(getQueryByType(DocumentTypeFromAXIS.CorrespondenceLog), sqlConnection)) + { try { sqlConnection.Open(); + using DataTable dataTable = new(); sqlSelectCommand.Fill(dataTable); + if (!dataTable.HasErrors && dataTable.Rows.Count > 0) + { + documentToMigrates = new(); + foreach (DataRow row in dataTable.Rows) + { + documentToMigrates.Add(new DocumentToMigrate() + { + EmailContent = Convert.ToString(row["emailbody"]), + EmailSubject= Convert.ToString(row["vcSubject"]), + EmailTo = Convert.ToString(row["vcEmail"]), + EmailFrom = Convert.ToString(row["vcFromEmail"]), + EmailDate = Convert.ToString(row["sdtMailedDate"]), + EmailAttachmentDelimitedString = Convert.ToString(row["attachments"]), + AXISRequestNumber = Convert.ToString(row["vcVisibleRequestID"]), + }); + + } + } } catch (SqlException ex) { - Ilogger.Log(LogLevel.Error, ex.Message); - throw; + + throw ex; } catch (Exception e) - { - Ilogger.Log(LogLevel.Error, e.Message); + { throw; } + finally + { + sqlConnection.Close(); + } } - + return documentToMigrates; + } } } \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/AXISSource/AXISDocument.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/AXISSource/AXISDocument.cs index 593123566..4fd236ede 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/AXISSource/AXISDocument.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/AXISSource/AXISDocument.cs @@ -8,7 +8,7 @@ namespace FOIMOD.CFD.DocMigration.Models.AXISSource { public class AXISDocument { - + public string? AXISRequestNumber { get; set; } public string UNCRootFolder { get; set; } public int IDocID { get; set; } @@ -24,6 +24,8 @@ public class AXISDocument public string? EmailDate { get; set; } public string? EmailTo { get; set; } + + public string? EmailFrom { get; set; } public string? EmailSubject { get; set; } } diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/Document/DocumentToMigrate.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/Document/DocumentToMigrate.cs index ca43eecd6..45f6ce68c 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/Document/DocumentToMigrate.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/Document/DocumentToMigrate.cs @@ -12,5 +12,7 @@ public class DocumentToMigrate : AXISDocument public bool HasStreamForDocument { get; set; } + public string? EmailAttachmentDelimitedString { get; set; } + } } From a59511fb18cbbb95fe8b137e4ea464ec4fe2cf4a Mon Sep 17 00:00:00 2001 From: "sumathi.thirumani" Date: Fri, 18 Aug 2023 10:53:42 -0700 Subject: [PATCH 052/234] Changes to show request type only for group notifications. --- request-management-api/request_api/services/events/state.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/request-management-api/request_api/services/events/state.py b/request-management-api/request_api/services/events/state.py index 46fe34a19..80424b5d7 100644 --- a/request-management-api/request_api/services/events/state.py +++ b/request-management-api/request_api/services/events/state.py @@ -62,7 +62,7 @@ def __createnotification(self, requestid, state, requesttype, userid): if state == 'Call For Records' and requesttype == "ministryrequest": foirequest = notificationservice().getrequest(requestid, requesttype) _notificationtype = "Group Members" if foirequest['assignedministryperson'] is None else "State" - notification = self.__preparenotification(state, requestid) + notification = self.__preparenotification(state) if state == 'Closed' or state == 'Archived' : notificationservice().dismissnotificationsbyrequestid(requestid, requesttype) if state == 'Archived': @@ -82,9 +82,7 @@ def __createnotification(self, requestid, state, requesttype, userid): return DefaultMethodResult(True,'Notification added',requestid) return DefaultMethodResult(True,'No change',requestid) - def __preparenotification(self, state, requestid): - if state == 'Call For Records': - return self.__notificationcfrmessage(requestid) + def __preparenotification(self, state): return self.__notificationmessage(state) def __preparegroupmembernotification(self, state, requestid): From 85b3a094c87788403e849b0d7746f0dd4fe15a65 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Fri, 18 Aug 2023 15:32:34 -0700 Subject: [PATCH 053/234] #4141 DAL update for Records Fetch --- .../AXISDALTest.cs | 14 +++- .../DocumentsDAL.cs | 83 ++++++++++++++++++- .../Document/DocumentToMigrate.cs | 2 +- 3 files changed, 95 insertions(+), 4 deletions(-) diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/AXISDALTest.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/AXISDALTest.cs index cfae28236..816da5245 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/AXISDALTest.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/AXISDALTest.cs @@ -6,14 +6,26 @@ namespace FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests [TestClass] public class AXISDALTest { + SqlConnection conn = new SqlConnection(@"Data Source=.;Initial Catalog=ATIPD;Integrated Security=True;Encrypt=False"); + [TestMethod] public void GetCorrespondenceLogDocsTest() { - SqlConnection conn = new SqlConnection(@"Data Source=.;Initial Catalog=ATIPD;Integrated Security=True;Encrypt=False"); + DocumentsDAL documentsDAL = new DocumentsDAL(conn); var correspondencelogs = documentsDAL.GetCorrespondenceLogDocuments(); Assert.IsNotNull(correspondencelogs); + } + + + [TestMethod] + public void GetRecordsTest() + { + + DocumentsDAL documentsDAL = new DocumentsDAL(conn); + var correspondencelogs = documentsDAL.GetRecordsByRequest("NGD-2015-50137"); + Assert.IsNotNull(correspondencelogs); } } diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/DocumentsDAL.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/DocumentsDAL.cs index e555cf44b..f111d36e6 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/DocumentsDAL.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/DocumentsDAL.cs @@ -34,7 +34,29 @@ WHERE R.vcVisibleRequestID in ({0}) GROUP BY R.vcVisibleRequestID,C.vcSubject,C.sdtMailedDate,C.vcEmail,C.vcFromEmail,CAST(C.vcBody as NVARCHAR(max))", "'CFD-2015-50011','CFD-2014-50119','CLB-2017-70004'"); - private string getQueryByType(DocumentTypeFromAXIS documentTypeFromAXIS) + + private string recordsbyrequestid = @" + DECLARE @SectionList VARCHAR(MAX); + DECLARE @irequestid INT + SET @irequestid=(SELECT TOP 1 iRequestID FROM tblRequests WHERE vcVisibleRequestID = '{0}') + SET @SectionList = NULL; + SELECT + @SectionList = COALESCE(@SectionList+':', '')+vcSectionList + FROM [dbo].[tblDocumentReviewLog] WHERE iRequestID =@irequestid + + SELECT D.iDocID,D.tiSections,vcFileName as FilePath,D.siFolderID,D.siPageCount ,p.siPageNum FROM tblPages P inner join tblDocuments D on P.iDocID=D.iDocID + + WHERE D.iDocID in( + --- Review Log Documents + SELECT iDocID FROM [dbo].[tblDocumentReviewLog] WHERE iRequestID = @irequestid + union + SELECT iDocID FROM tblDocuments d with(nolock) where iDocID IN (SELECT Data FROM [dbo].[AFX_Splitter](@SectionList, ':')) + union + --- Request Folder Documents + select iDocID from tblRedactionLayers where irequestid=@irequestid AND iDeliveryID is NULL + )"; + + private string getQueryByType(DocumentTypeFromAXIS documentTypeFromAXIS, string requestnumber="") { var query = string.Empty; switch (documentTypeFromAXIS) { @@ -42,7 +64,9 @@ private string getQueryByType(DocumentTypeFromAXIS documentTypeFromAXIS) case DocumentTypeFromAXIS.CorrespondenceLog: query = correspondencelog; break; - + case DocumentTypeFromAXIS.RequestRecords: + query = string.Format(recordsbyrequestid, requestnumber); + break; default: break; @@ -98,5 +122,60 @@ private string getQueryByType(DocumentTypeFromAXIS documentTypeFromAXIS) } return documentToMigrates; } + + + public List? GetRecordsByRequest(string requestnumber) + { + List documentToMigrates = null; + using (SqlDataAdapter sqlSelectCommand = new(getQueryByType(DocumentTypeFromAXIS.RequestRecords,requestnumber), sqlConnection)) + { + try + { + sqlConnection.Open(); + using DataTable dataTable = new(); + sqlSelectCommand.Fill(dataTable); + if (!dataTable.HasErrors && dataTable.Rows.Count > 0) + { + documentToMigrates = new(); + foreach (DataRow row in dataTable.Rows) + { + documentToMigrates.Add(new DocumentToMigrate() + { + IDocID= Convert.ToInt32(row["iDocID"]), + PageFilePath = Convert.ToString(row["FilePath"]), + SiFolderID = Convert.ToString(row["siFolderID"]), + TotalPageCount = Convert.ToString(row["siPageCount"]), + PageSequenceNumber = Convert.ToInt32(row["siPageNum"]), + AXISRequestNumber = requestnumber.ToUpper(), + DocumentType = DocumentTypeFromAXIS.RequestRecords + }); + + } + } + } + catch (SqlException ex) + { + + throw ex; + } + catch (Exception e) + { + throw; + } + finally + { + sqlConnection.Close(); + } + + } + return documentToMigrates; + } + + + + + } + + } \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/Document/DocumentToMigrate.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/Document/DocumentToMigrate.cs index 45f6ce68c..66c77e564 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/Document/DocumentToMigrate.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/Document/DocumentToMigrate.cs @@ -6,7 +6,7 @@ public class DocumentToMigrate : AXISDocument { public int PageSequenceNumber { get; set; } - public string PageFilePath { get; set; } + public string? PageFilePath { get; set; } public Stream FileStream { get; set; } From 4ec7da1179990767e2a7864e1b1918df7fc70fc7 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Fri, 18 Aug 2023 15:35:57 -0700 Subject: [PATCH 054/234] Code clean. Testing for divsion and program area endpoints WIP --- .../resources/foiflowmasterdata.py | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/request-management-api/request_api/resources/foiflowmasterdata.py b/request-management-api/request_api/resources/foiflowmasterdata.py index 4be5e6ac0..5a1a8bf09 100644 --- a/request-management-api/request_api/resources/foiflowmasterdata.py +++ b/request-management-api/request_api/resources/foiflowmasterdata.py @@ -73,7 +73,6 @@ def get(): except BusinessException: return "Error happened while accessing applicant categories" , 500 -## USE THIS API CALL TO GET ALL PROGRAM AREAS - TEST WIP @cors_preflight('GET,OPTIONS') @API.route('/foiflow/programareas') class FOIFlowProgramAreas(Resource): @@ -188,7 +187,6 @@ def get(bcgovcode): except BusinessException: return "Error happened while accessing divisions" , 500 -#MAKE API CALL HERE TO GATHER ALL PROGRAM AREA DIVISIONS - TEST WIP @cors_preflight('GET,OPTIONS') @API.route('/foiflow/divisions') class FOIFlowDivisions(Resource): @@ -207,33 +205,6 @@ def get(): except BusinessException: return "Error happened while accessing divisions", 500 -#MAKE API CALL HERE TO GATHER ASSOCIATED PROGRAM AREA DIVISIONS FOR RECORDS BASED ON REQUEST ID - TEST WIP -@cors_preflight('GET,OPTIONS') -@API.route('/foiflow/divisions/') -class FOIFlowDivisionsForFOIRequestRecords(Resource): - """Retrieves all active divisions associated with foi request records based on foi request id. - """ - @staticmethod - @TRACER.trace() - @cross_origin(origins=allowedorigins()) - @auth.require - @auth.ismemberofgroups(getrequiredmemberships()) - def get(foirequestid): - try: - divisions = {div['divisionid']: div for div in programareadivisionservice().getallprogramareadivisions()} - records = {record['recordid']: record for record in recordservice.fetch(foirequestid)} - - for recordid in records: - record = records[recordid] - record_divisions = set(map(lambda d: d['divisionid'], record['attributes']['divisions'])) - record['divisions'] = list(map(lambda d: divisions[d], record_divisions)) - - response = [records[recordid] for recordid in records] - json_response = json.dumps(response) - return json_response, 200 - except BusinessException: - return "Error happened while accessing records", 500 - @cors_preflight('GET,OPTIONS') @API.route('/foiflow/closereasons') class FOIFlowCloseReasons(Resource): From 10aa99713ca59a7ee73683c6bb5e236cd0e881df Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Fri, 18 Aug 2023 15:44:50 -0700 Subject: [PATCH 055/234] #4141 Update to DAL with REQUEST list param --- .../AXISDALTest.cs | 2 +- .../FOIMOD.CFD.DocMigration.DAL/DocumentsDAL.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/AXISDALTest.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/AXISDALTest.cs index 816da5245..eff844895 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/AXISDALTest.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/AXISDALTest.cs @@ -13,7 +13,7 @@ public void GetCorrespondenceLogDocsTest() { DocumentsDAL documentsDAL = new DocumentsDAL(conn); - var correspondencelogs = documentsDAL.GetCorrespondenceLogDocuments(); + var correspondencelogs = documentsDAL.GetCorrespondenceLogDocuments("'CFD-2015-50011','CFD-2014-50119','CLB-2017-70004'"); Assert.IsNotNull(correspondencelogs); } diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/DocumentsDAL.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/DocumentsDAL.cs index f111d36e6..007b62180 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/DocumentsDAL.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/DocumentsDAL.cs @@ -19,7 +19,7 @@ public DocumentsDAL(SqlConnection _sqlConnection) sqlConnection = _sqlConnection; } - private string correspondencelog = string.Format(@"SELECT + private string correspondencelog = @"SELECT R.vcVisibleRequestID , C.vcSubject @@ -32,7 +32,7 @@ public DocumentsDAL(SqlConnection _sqlConnection) FROM tblCorrespondence C JOIN tblRequests R on C.iRequestID = R.iRequestID WHERE R.vcVisibleRequestID in ({0}) - GROUP BY R.vcVisibleRequestID,C.vcSubject,C.sdtMailedDate,C.vcEmail,C.vcFromEmail,CAST(C.vcBody as NVARCHAR(max))", "'CFD-2015-50011','CFD-2014-50119','CLB-2017-70004'"); + GROUP BY R.vcVisibleRequestID,C.vcSubject,C.sdtMailedDate,C.vcEmail,C.vcFromEmail,CAST(C.vcBody as NVARCHAR(max))"; private string recordsbyrequestid = @" @@ -62,7 +62,7 @@ private string getQueryByType(DocumentTypeFromAXIS documentTypeFromAXIS, string switch (documentTypeFromAXIS) { case DocumentTypeFromAXIS.CorrespondenceLog: - query = correspondencelog; + query =string.Format(correspondencelog,requestnumber); break; case DocumentTypeFromAXIS.RequestRecords: query = string.Format(recordsbyrequestid, requestnumber); @@ -76,10 +76,10 @@ private string getQueryByType(DocumentTypeFromAXIS documentTypeFromAXIS, string return query; } - public List? GetCorrespondenceLogDocuments() + public List? GetCorrespondenceLogDocuments(string cs_requestnumbers) { List documentToMigrates = null; - using (SqlDataAdapter sqlSelectCommand = new(getQueryByType(DocumentTypeFromAXIS.CorrespondenceLog), sqlConnection)) + using (SqlDataAdapter sqlSelectCommand = new(getQueryByType(DocumentTypeFromAXIS.CorrespondenceLog, cs_requestnumbers), sqlConnection)) { try { From 36ef3b0e8440446c65372dea76cef2d2c0dbac66 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Fri, 18 Aug 2023 16:54:45 -0700 Subject: [PATCH 056/234] #4141 Enable config for DAL, S3 utils functions --- .../AXISDALTest.cs | 24 +++++++++-- ...CFD.DocMigration.AXIS.DAL.UnitTests.csproj | 9 ++++ .../appsettings.json | 8 ++++ .../FOIMOD.CFD.DocMigration.BAL.csproj | 4 ++ .../SystemSettings.cs | 23 +++++++++++ ...OD.CFD.DocMigration.Utils.UnitTests.csproj | 13 ++++++ .../S3UploadTest.cs | 41 +++++++++++-------- .../appsettings.json | 10 +++++ 8 files changed, 113 insertions(+), 19 deletions(-) create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/appsettings.json create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/SystemSettings.cs create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/appsettings.json diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/AXISDALTest.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/AXISDALTest.cs index eff844895..d6d289528 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/AXISDALTest.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/AXISDALTest.cs @@ -1,19 +1,37 @@ using FOIMOD.CFD.DocMigration.DAL; +using FOIMOD.CFD.DocMigration.Models; using Microsoft.Data.SqlClient; - +using Microsoft.Extensions.Configuration; namespace FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests { [TestClass] public class AXISDALTest { - SqlConnection conn = new SqlConnection(@"Data Source=.;Initial Catalog=ATIPD;Integrated Security=True;Encrypt=False"); + SqlConnection conn = null; + string requeststomigrate = String.Empty; + [TestInitialize] + public void AXISDALTestInit() + { + var configurationbuilder = new ConfigurationBuilder() + .AddJsonFile($"appsettings.json", true, true) + .AddEnvironmentVariables().Build(); + + SystemSettings.AXISConnectionString = configurationbuilder.GetSection("AXISConfiguration:SQLConnectionString").Value; + SystemSettings.RequestToMigrate = configurationbuilder.GetSection("AXISConfiguration:RequestToMigrate").Value; + + conn = new SqlConnection(SystemSettings.AXISConnectionString); + requeststomigrate = SystemSettings.RequestToMigrate; + + } + + [TestMethod] public void GetCorrespondenceLogDocsTest() { DocumentsDAL documentsDAL = new DocumentsDAL(conn); - var correspondencelogs = documentsDAL.GetCorrespondenceLogDocuments("'CFD-2015-50011','CFD-2014-50119','CLB-2017-70004'"); + var correspondencelogs = documentsDAL.GetCorrespondenceLogDocuments(requeststomigrate); Assert.IsNotNull(correspondencelogs); } diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests.csproj b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests.csproj index 270ce55d9..a8136f018 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests.csproj +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests.csproj @@ -14,10 +14,19 @@ + + + + + + Always + + + diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/appsettings.json b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/appsettings.json new file mode 100644 index 000000000..1287cc6f8 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/appsettings.json @@ -0,0 +1,8 @@ +{ + "AXISConfiguration": { + "SQLConnectionString": "Data Source=.;Initial Catalog=ATIPD;Integrated Security=True;Encrypt=False", + "RequestToMigrate": "'CFD-2015-50011','CFD-2014-50119','CLB-2017-70004'" + } + + +} diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/FOIMOD.CFD.DocMigration.BAL.csproj b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/FOIMOD.CFD.DocMigration.BAL.csproj index cfadb03dd..248ddb931 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/FOIMOD.CFD.DocMigration.BAL.csproj +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/FOIMOD.CFD.DocMigration.BAL.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/SystemSettings.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/SystemSettings.cs new file mode 100644 index 000000000..6bd3b5a2f --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/SystemSettings.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FOIMOD.CFD.DocMigration.Models +{ + public static class SystemSettings + { + public static string S3_AccessKey { get; set; } + + public static string S3_SecretKey { get; set; } + + + public static string S3_EndPoint { get; set; } + + public static string AXISConnectionString{ get; set; } + + public static string RequestToMigrate { get; set; } + + } +} diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/FOIMOD.CFD.DocMigration.Utils.UnitTests.csproj b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/FOIMOD.CFD.DocMigration.Utils.UnitTests.csproj index 46e27df74..0bfcd4a06 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/FOIMOD.CFD.DocMigration.Utils.UnitTests.csproj +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/FOIMOD.CFD.DocMigration.Utils.UnitTests.csproj @@ -10,11 +10,15 @@ + + + + @@ -22,4 +26,13 @@ + + + Always + + + Always + + + diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/S3UploadTest.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/S3UploadTest.cs index a60053dfe..a5d2de132 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/S3UploadTest.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/S3UploadTest.cs @@ -2,34 +2,43 @@ using Amazon.Runtime; using Amazon.S3; -using Amazon.S3.Model; +using FOIMOD.CFD.DocMigration.Models; using FOIMOD.CFD.DocMigration.Models.Document; -using Microsoft.VisualStudio.TestPlatform.Utilities; -using PdfSharpCore.Pdf; -using System.Buffers.Text; +using Microsoft.Extensions.Configuration; namespace FOIMOD.CFD.DocMigration.Utils.UnitTests { [TestClass] public class S3UploadTest { - - public AWSCredentials s3credentials = new BasicAWSCredentials("", ""); - public AmazonS3Config config = new() - { - ServiceURL = "https://citz-foi-prod.objectstore.gov.bc.ca/" - }; - [TestMethod] - public void S3Upload_UploadFileAsync() + public AWSCredentials s3credentials = null; + public AmazonS3Config config = null; + [TestInitialize] + public void S3UploadTestInit() { + var configurationbuilder = new ConfigurationBuilder() + .AddJsonFile($"appsettings.json", true, true) + .AddEnvironmentVariables().Build(); + SystemSettings.S3_AccessKey = configurationbuilder.GetSection("S3Configuration:AWS_accesskey").Value; + SystemSettings.S3_SecretKey = configurationbuilder.GetSection("S3Configuration:AWS_secret").Value; + SystemSettings.S3_EndPoint = configurationbuilder.GetSection("S3Configuration:AWS_S3_Url").Value; + s3credentials = new BasicAWSCredentials(SystemSettings.S3_AccessKey, SystemSettings.S3_SecretKey); + config = new() + { + ServiceURL = SystemSettings.S3_EndPoint - AmazonS3Client amazonS3Client = new AmazonS3Client(s3credentials, config); + }; + } - DocMigrationS3Client docMigrationS3Client = new DocMigrationS3Client(amazonS3Client); + [TestMethod] + public void S3Upload_UploadFileAsync() + { + AmazonS3Client amazonS3Client = new AmazonS3Client(s3credentials, config); + DocMigrationS3Client docMigrationS3Client = new DocMigrationS3Client(amazonS3Client); using var client = new HttpClient(); using (FileStream fs = File.Open(Path.Combine(getSourceFolder(), "DOCX1.pdf"), FileMode.Open)) @@ -104,12 +113,12 @@ public void MergeStreamwithFileAttachmentsTest() { using Stream emaildocstream = docMigrationPDFStitcher.CreatePDFDocument("

THIS IS AN EMAIL HTML content

", "My email subject line!!!!", DateTime.Now.AddYears(-10).ToLongDateString(), "abinajik@gmail.com"); emaildocstream.Position = 0; - + DocumentToMigrate[] pDFDocToMerges = new DocumentToMigrate[3]; pDFDocToMerges[0] = new DocumentToMigrate() { PageFilePath = Path.Combine(getSourceFolder(), "DOCX1.pdf"), PageSequenceNumber = 3 }; pDFDocToMerges[1] = new DocumentToMigrate() { PageFilePath = Path.Combine(getSourceFolder(), "cat1.pdf"), PageSequenceNumber = 2 }; - pDFDocToMerges[2] = new DocumentToMigrate() { FileStream = emaildocstream, PageSequenceNumber = 1, HasStreamForDocument=true }; + pDFDocToMerges[2] = new DocumentToMigrate() { FileStream = emaildocstream, PageSequenceNumber = 1, HasStreamForDocument = true }; using Stream fs = docMigrationPDFStitcher.MergePDFs(pDFDocToMerges); diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/appsettings.json b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/appsettings.json new file mode 100644 index 000000000..9a8817608 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/appsettings.json @@ -0,0 +1,10 @@ +{ + "S3Configuration": { + "AWS_accesskey": "", + "AWS_secret": "", + "AWS_S3_Url": "" + + } + + +} From cd53e0021af6434d80f06ea536b68f18cba93248 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Fri, 18 Aug 2023 17:10:37 -0700 Subject: [PATCH 057/234] #4141 appsettting for main app. --- .../FOIMOD.CFD.ConsoleApp.DocMigration.csproj | 15 ++++++++++++++- .../FOIMOD.CFD.ConsoleApp.DocMigration/Program.cs | 14 +++++++++++++- .../appsettings.json | 14 ++++++++++++++ ...FOIMOD.CFD.DocMigration.Utils.UnitTests.csproj | 5 +---- 4 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/appsettings.json diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.csproj b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.csproj index f02677bf6..5af90959f 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.csproj +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.csproj @@ -6,5 +6,18 @@ enable enable - + + + + + + + + + + + + Always + + diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/Program.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/Program.cs index 3751555cb..6b842ba46 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/Program.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/Program.cs @@ -1,2 +1,14 @@ // See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); +using FOIMOD.CFD.DocMigration.Models; +using Microsoft.Extensions.Configuration; + +Console.WriteLine("Starting, CFD Document Migration!"); +var configurationbuilder = new ConfigurationBuilder() + .AddJsonFile($"appsettings.json", true, true) + .AddEnvironmentVariables().Build(); + +SystemSettings.S3_AccessKey = configurationbuilder.GetSection("S3Configuration:AWS_accesskey").Value; +SystemSettings.S3_SecretKey = configurationbuilder.GetSection("S3Configuration:AWS_secret").Value; +SystemSettings.S3_EndPoint = configurationbuilder.GetSection("S3Configuration:AWS_S3_Url").Value; +SystemSettings.AXISConnectionString = configurationbuilder.GetSection("AXISConfiguration:SQLConnectionString").Value; +SystemSettings.RequestToMigrate = configurationbuilder.GetSection("AXISConfiguration:RequestToMigrate").Value; \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/appsettings.json b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/appsettings.json new file mode 100644 index 000000000..f877387fa --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/appsettings.json @@ -0,0 +1,14 @@ +{ + "S3Configuration": { + "AWS_accesskey": "", + "AWS_secret": "", + "AWS_S3_Url": "" + + }, + "AXISConfiguration": { + "SQLConnectionString": "Data Source=.;Initial Catalog=ATIPD;Integrated Security=True;Encrypt=False", + "RequestToMigrate": "'CFD-2015-50011','CFD-2014-50119','CLB-2017-70004'" + } + + +} diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/FOIMOD.CFD.DocMigration.Utils.UnitTests.csproj b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/FOIMOD.CFD.DocMigration.Utils.UnitTests.csproj index 0bfcd4a06..26683b729 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/FOIMOD.CFD.DocMigration.Utils.UnitTests.csproj +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/FOIMOD.CFD.DocMigration.Utils.UnitTests.csproj @@ -1,4 +1,4 @@ - + net7.0 @@ -27,9 +27,6 @@ - - Always - Always From 98f3dc253ea4dfe32c11737dc090f3cd4fe86c37 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Mon, 21 Aug 2023 12:42:49 -0700 Subject: [PATCH 058/234] Completed testing. Added a unit test for new restapi endpoint --- .../tests/restapi/test_foimasterdata_api.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/request-management-api/tests/restapi/test_foimasterdata_api.py b/request-management-api/tests/restapi/test_foimasterdata_api.py index 93d49c533..7f5a1ef05 100644 --- a/request-management-api/tests/restapi/test_foimasterdata_api.py +++ b/request-management-api/tests/restapi/test_foimasterdata_api.py @@ -55,4 +55,8 @@ def test_post_fois3storagerequests(app, client): def test_get_extensionreasons(app, client): response = client.get('/api/foiflow/extensionreasons', headers=factory_user_auth_header(app, client), content_type='application/json') - assert response.status_code == 200 \ No newline at end of file + assert response.status_code == 200 + +def test_get_all_divisions(app, client): + response = client.get('/api/foiflow/divisions', headers=factory_user_auth_header(app, client), content_type='application/json') + assert response.status_code == 200 \ No newline at end of file From 17ced642ed0b4b6891910791b95f85b4fa902224 Mon Sep 17 00:00:00 2001 From: Aparna Date: Mon, 21 Aug 2023 13:26:44 -0700 Subject: [PATCH 059/234] Delete/Remove Redundant Groups from KeyCloak#4201 Migration script added for soft deleting the mentioned groups from DB so that it won't show up in assignee dropdowns. --- .../migrations/versions/d43db71d53e5_.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 request-management-api/migrations/versions/d43db71d53e5_.py diff --git a/request-management-api/migrations/versions/d43db71d53e5_.py b/request-management-api/migrations/versions/d43db71d53e5_.py new file mode 100644 index 000000000..5eaa53664 --- /dev/null +++ b/request-management-api/migrations/versions/d43db71d53e5_.py @@ -0,0 +1,34 @@ +"""empty message + +Revision ID: d43db71d53e5 +Revises: 5782617db307 +Create Date: 2023-08-18 12:40:33.968711 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'd43db71d53e5' +down_revision = '5782617db307' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.execute('UPDATE public."OperatingTeams" set isactive = false where name in (\'Central Team\', \'Flex Team\', \'Justice Health Team\', \'Resource Team\', \'Social Education\');') + + op.execute('UPDATE public."FOIRequestTeams" set isactive = false where teamid in (select teamid from public."OperatingTeams" where name in (\'Central Team\', \'Flex Team\', \'Justice Health Team\', \'Resource Team\', \'Social Education\'));') + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.execute('UPDATE public."OperatingTeams" set isactive = true where name in (\'Central Team\', \'Flex Team\', \'Justice Health Team\', \'Resource Team\', \'Social Education\');') + + op.execute('UPDATE public."FOIRequestTeams" set isactive = true where teamid in (select teamid from public."OperatingTeams" where name in (\'Central Team\', \'Flex Team\', \'Justice Health Team\', \'Resource Team\', \'Social Education\'));') + + # ### end Alembic commands ### From 2bc063202078655529302ddd7fd35b47e5d970d8 Mon Sep 17 00:00:00 2001 From: Aparna Date: Tue, 22 Aug 2023 09:47:13 -0700 Subject: [PATCH 060/234] Removed print statement --- .../request_api/services/foirequest/requestservicegetter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/request-management-api/request_api/services/foirequest/requestservicegetter.py b/request-management-api/request_api/services/foirequest/requestservicegetter.py index 2f865dea6..59c516ed7 100644 --- a/request-management-api/request_api/services/foirequest/requestservicegetter.py +++ b/request-management-api/request_api/services/foirequest/requestservicegetter.py @@ -127,7 +127,6 @@ def getrequestdetails(self,foirequestid, foiministryrequestid): return requestdetails def __preparebaseinfo(self,request,foiministryrequestid,requestministry,requestministrydivisions): - print("requestministry-:",requestministry['originalldd']) _receiveddate = parse(request['receiveddate']) axissyncdatenoneorempty = self.__noneorempty(requestministry["axissyncdate"]) linkedministryrequests= [] From 1b87865973f34e470a48e7f42e60553f55d1545e Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Tue, 22 Aug 2023 11:26:29 -0700 Subject: [PATCH 061/234] #4141 - BAL Updates --- .../FOIMOD.CFD.ConsoleApp.DocMigration.csproj | 3 +- .../Program.cs | 10 +++- .../appsettings.json | 6 ++- .../FOIMOD.CFD.DocMigration.BAL/Class1.cs | 7 --- .../CorrespondenceLogMigration.cs | 49 +++++++++++++++++++ .../FOIMOD.CFD.DocMigration.BAL.csproj | 6 +++ .../SystemSettings.cs | 6 +++ 7 files changed, 77 insertions(+), 10 deletions(-) delete mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/Class1.cs create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/CorrespondenceLogMigration.cs diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.csproj b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.csproj index 5af90959f..f25b62880 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.csproj +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.csproj @@ -1,4 +1,4 @@ - + Exe @@ -13,6 +13,7 @@ + diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/Program.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/Program.cs index 6b842ba46..cc442e02f 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/Program.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/Program.cs @@ -1,5 +1,7 @@ // See https://aka.ms/new-console-template for more information +using FOIMOD.CFD.DocMigration.BAL; using FOIMOD.CFD.DocMigration.Models; +using Microsoft.Data.SqlClient; using Microsoft.Extensions.Configuration; Console.WriteLine("Starting, CFD Document Migration!"); @@ -11,4 +13,10 @@ SystemSettings.S3_SecretKey = configurationbuilder.GetSection("S3Configuration:AWS_secret").Value; SystemSettings.S3_EndPoint = configurationbuilder.GetSection("S3Configuration:AWS_S3_Url").Value; SystemSettings.AXISConnectionString = configurationbuilder.GetSection("AXISConfiguration:SQLConnectionString").Value; -SystemSettings.RequestToMigrate = configurationbuilder.GetSection("AXISConfiguration:RequestToMigrate").Value; \ No newline at end of file +SystemSettings.RequestToMigrate = configurationbuilder.GetSection("AXISConfiguration:RequestToMigrate").Value; + +SqlConnection axissqlConnection = new SqlConnection(SystemSettings.AXISConnectionString); + +CorrespondenceLogMigration correspondenceLogMigration = new CorrespondenceLogMigration(axissqlConnection, null, null); +correspondenceLogMigration.RequestsToMigrate = SystemSettings.RequestToMigrate; +await correspondenceLogMigration.RunMigration(); \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/appsettings.json b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/appsettings.json index f877387fa..a44fc7aad 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/appsettings.json +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/appsettings.json @@ -2,7 +2,11 @@ "S3Configuration": { "AWS_accesskey": "", "AWS_secret": "", - "AWS_S3_Url": "" + "AWS_S3_Url": "", + "FileServerRoot": "\\\\solis\\ATIPDocs\\", + "CorrespondenceLogBaseFolder": "AFXWCORL", + "RecordsbaseFolder": "AFXWDOCS" + }, "AXISConfiguration": { diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/Class1.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/Class1.cs deleted file mode 100644 index 6612081f1..000000000 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/Class1.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace FOIMOD.CFD.DocMigration.BAL -{ - public class Class1 - { - - } -} \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/CorrespondenceLogMigration.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/CorrespondenceLogMigration.cs new file mode 100644 index 000000000..79eac75a2 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/CorrespondenceLogMigration.cs @@ -0,0 +1,49 @@ +using Amazon.S3; +using FOIMOD.CFD.DocMigration.DAL; +using Microsoft.Data.SqlClient; +using System.Data; +using System.Data.Odbc; +using FOIMOD.CFD.DocMigration.Models.Document; +using FOIMOD.CFD.DocMigration.Models.FOIFLOWDestination; +using FOIMOD.CFD.DocMigration.Utils; + +namespace FOIMOD.CFD.DocMigration.BAL +{ + public class CorrespondenceLogMigration + { + private IDbConnection sourceaxisSQLConnection; + private IDbConnection destinationfoiflowQLConnection; + private IAmazonS3 amazonS3; + + public string RequestsToMigrate { get; set; } + public CorrespondenceLogMigration(IDbConnection _sourceAXISConnection, IDbConnection _destinationFOIFLOWDB, IAmazonS3 _amazonS3 ) + { + sourceaxisSQLConnection = _sourceAXISConnection; + destinationfoiflowQLConnection= _destinationFOIFLOWDB; + amazonS3 = _amazonS3; + } + + public async Task RunMigration() + { + DocMigrationS3Client docMigrationS3Client = new DocMigrationS3Client(amazonS3); + + DocumentsDAL documentsDAL = new DocumentsDAL((SqlConnection)sourceaxisSQLConnection); + List? correspondencelogs = documentsDAL.GetCorrespondenceLogDocuments(this.RequestsToMigrate); + foreach (DocumentToMigrate attachment in correspondencelogs) + { + if (string.IsNullOrEmpty(attachment.EmailTo) && string.IsNullOrEmpty(attachment.EmailContent)) + { + //NOT AN EMAIL UPLOAD - DIRECT FILE UPLOAD + + //await docMigrationS3Client.UploadFileAsync(new UploadFile() { AXISRequestID = attachment.AXISRequestNumber , FileStream}) + } + else + { + //Create EMAIL MSG PDF and sticth with the attachment documents + } + } + + } + + } +} \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/FOIMOD.CFD.DocMigration.BAL.csproj b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/FOIMOD.CFD.DocMigration.BAL.csproj index 248ddb931..ebce8e7eb 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/FOIMOD.CFD.DocMigration.BAL.csproj +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/FOIMOD.CFD.DocMigration.BAL.csproj @@ -6,8 +6,14 @@ enable + + + + + + diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/SystemSettings.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/SystemSettings.cs index 6bd3b5a2f..186f6f1ef 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/SystemSettings.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/SystemSettings.cs @@ -19,5 +19,11 @@ public static class SystemSettings public static string RequestToMigrate { get; set; } + public static string FileServerRoot { get; set; } + + public static string CorrespondenceLogBaseFolder { get; set; } + + public static string RecordsbaseFolder { get; set; } + } } From f900e6878e0e79f64e84525ab8576a4e48ca89ca Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Tue, 22 Aug 2023 12:25:30 -0700 Subject: [PATCH 062/234] Branch created and ticket work started. Added logic in disable division endpoint to return 400 status code if result from disableprogramareadivision service fails (is success is False). --- request-management-api/request_api/resources/foiadmin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/request-management-api/request_api/resources/foiadmin.py b/request-management-api/request_api/resources/foiadmin.py index 864157ec4..54e4df8f8 100644 --- a/request-management-api/request_api/resources/foiadmin.py +++ b/request-management-api/request_api/resources/foiadmin.py @@ -115,8 +115,8 @@ class DisableFOIProgramAreaDivision(Resource): def put(divisionid): try: result = programareadivisionservice().disableprogramareadivision(divisionid, AuthHelper.getuserid()) - # if result.success == True: - # asyncio.ensure_future(); + if result.success != True: + return {'status': result.success, 'message': result.message,}, 400 return {'status': result.success, 'message':result.message, 'id':result.identifier}, 200 except KeyError as err: return {'status': False, 'message':err.messages}, 400 From 165844215471620226f9226cb395af60e5433759 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Tue, 22 Aug 2023 14:41:27 -0700 Subject: [PATCH 063/234] #4141 update for file string parsing --- .../Program.cs | 10 ++++++ .../appsettings.json | 8 +++-- .../CorrespondenceLogMigration.cs | 29 ++++++++++++---- .../AXISFIle.cs | 17 ++++++++++ .../SystemSettings.cs | 8 ++++- .../FillePathUtils.cs | 34 +++++++++++++++++++ 6 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/AXISFIle.cs create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/FillePathUtils.cs diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/Program.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/Program.cs index cc442e02f..2ea42b3b2 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/Program.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/Program.cs @@ -9,12 +9,22 @@ .AddJsonFile($"appsettings.json", true, true) .AddEnvironmentVariables().Build(); + +SystemSettings.FileServerRoot = configurationbuilder.GetSection("S3Configuration:FileServerRoot").Value; +SystemSettings.CorrespondenceLogBaseFolder = configurationbuilder.GetSection("S3Configuration:CorrespondenceLogBaseFolder").Value; +SystemSettings.RecordsbaseFolder = configurationbuilder.GetSection("S3Configuration:RecordsbaseFolder").Value; SystemSettings.S3_AccessKey = configurationbuilder.GetSection("S3Configuration:AWS_accesskey").Value; SystemSettings.S3_SecretKey = configurationbuilder.GetSection("S3Configuration:AWS_secret").Value; SystemSettings.S3_EndPoint = configurationbuilder.GetSection("S3Configuration:AWS_S3_Url").Value; +SystemSettings.S3_Attachements_BasePath = configurationbuilder.GetSection("S3Configuration:S3_Attachements_BasePath").Value; +SystemSettings.S3_Attachements_Bucket = configurationbuilder.GetSection("S3Configuration:S3_Attachements_Bucket").Value; +SystemSettings.AttachmentTag = configurationbuilder.GetSection("S3Configuration:AttachmentTag").Value; + SystemSettings.AXISConnectionString = configurationbuilder.GetSection("AXISConfiguration:SQLConnectionString").Value; SystemSettings.RequestToMigrate = configurationbuilder.GetSection("AXISConfiguration:RequestToMigrate").Value; + + SqlConnection axissqlConnection = new SqlConnection(SystemSettings.AXISConnectionString); CorrespondenceLogMigration correspondenceLogMigration = new CorrespondenceLogMigration(axissqlConnection, null, null); diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/appsettings.json b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/appsettings.json index a44fc7aad..1742c9f3a 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/appsettings.json +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/appsettings.json @@ -2,11 +2,13 @@ "S3Configuration": { "AWS_accesskey": "", "AWS_secret": "", - "AWS_S3_Url": "", + "AWS_S3_Url": "https://citz-foi-prod.objectstore.gov.bc.ca", "FileServerRoot": "\\\\solis\\ATIPDocs\\", "CorrespondenceLogBaseFolder": "AFXWCORL", - "RecordsbaseFolder": "AFXWDOCS" - + "RecordsbaseFolder": "AFXWDOCS", + "S3_Attachements_BasePath": "/dev-forms-foirequests-e/Misc", + "S3_Attachements_Bucket": "dev-forms-foirequests-e", + "AttachmentTag": "applicant" }, "AXISConfiguration": { diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/CorrespondenceLogMigration.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/CorrespondenceLogMigration.cs index 79eac75a2..52507306f 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/CorrespondenceLogMigration.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/CorrespondenceLogMigration.cs @@ -1,11 +1,11 @@ using Amazon.S3; using FOIMOD.CFD.DocMigration.DAL; -using Microsoft.Data.SqlClient; -using System.Data; -using System.Data.Odbc; +using FOIMOD.CFD.DocMigration.Models; using FOIMOD.CFD.DocMigration.Models.Document; using FOIMOD.CFD.DocMigration.Models.FOIFLOWDestination; using FOIMOD.CFD.DocMigration.Utils; +using Microsoft.Data.SqlClient; +using System.Data; namespace FOIMOD.CFD.DocMigration.BAL { @@ -16,13 +16,14 @@ public class CorrespondenceLogMigration private IAmazonS3 amazonS3; public string RequestsToMigrate { get; set; } - public CorrespondenceLogMigration(IDbConnection _sourceAXISConnection, IDbConnection _destinationFOIFLOWDB, IAmazonS3 _amazonS3 ) + public CorrespondenceLogMigration(IDbConnection _sourceAXISConnection, IDbConnection _destinationFOIFLOWDB, IAmazonS3 _amazonS3) { sourceaxisSQLConnection = _sourceAXISConnection; - destinationfoiflowQLConnection= _destinationFOIFLOWDB; + destinationfoiflowQLConnection = _destinationFOIFLOWDB; amazonS3 = _amazonS3; } + [Obsolete] public async Task RunMigration() { DocMigrationS3Client docMigrationS3Client = new DocMigrationS3Client(amazonS3); @@ -33,9 +34,23 @@ public async Task RunMigration() { if (string.IsNullOrEmpty(attachment.EmailTo) && string.IsNullOrEmpty(attachment.EmailContent)) { - //NOT AN EMAIL UPLOAD - DIRECT FILE UPLOAD + var files = FilePathUtils.GetFileDetailsFromdelimitedstring(attachment.EmailAttachmentDelimitedString); + foreach (var file in files) + { + //NOT AN EMAIL UPLOAD - DIRECT FILE UPLOAD - https://citz-foi-prod.objectstore.gov.bc.ca/dev-forms-foirequests-e/Misc/CFD-2023-22081302/applicant/00b8ad71-5d11-4624-9c12-839193cf4a7e.docx + var UNCFileLocation = Path.Combine(SystemSettings.FileServerRoot, SystemSettings.CorrespondenceLogBaseFolder, file.FilePathOnServer); + + + using (FileStream fs = File.Open(UNCFileLocation, FileMode.Open)) + { + var s3filesubpath = string.Format("{0}/{1}/{2}/{3}", SystemSettings.S3_Attachements_BasePath, attachment.AXISRequestNumber, SystemSettings.AttachmentTag, string.Format("{0}.{1}", Guid.NewGuid().ToString(), file.FileExtension)); + var fulls3filepath = string.Format("{0}/{1}", SystemSettings.S3_EndPoint, s3filesubpath); + await docMigrationS3Client.UploadFileAsync(new UploadFile() { AXISRequestID = attachment.AXISRequestNumber.ToUpper(), SubFolderPath = s3filesubpath, DestinationFileName = file.FileName }); + + } + + } - //await docMigrationS3Client.UploadFileAsync(new UploadFile() { AXISRequestID = attachment.AXISRequestNumber , FileStream}) } else { diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/AXISFIle.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/AXISFIle.cs new file mode 100644 index 000000000..6de9154e8 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/AXISFIle.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FOIMOD.CFD.DocMigration.Models +{ + public class AXISFIle + { + public string FilePathOnServer { get; set; } + + public string FileName { get; set;} + + public string FileExtension { get; set;} + } +} diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/SystemSettings.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/SystemSettings.cs index 186f6f1ef..9e5c3b30d 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/SystemSettings.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/SystemSettings.cs @@ -12,7 +12,6 @@ public static class SystemSettings public static string S3_SecretKey { get; set; } - public static string S3_EndPoint { get; set; } public static string AXISConnectionString{ get; set; } @@ -25,5 +24,12 @@ public static class SystemSettings public static string RecordsbaseFolder { get; set; } + public static string S3_Attachements_BasePath { get; set; } + + public static string AttachmentTag { get; set; } + + public static string S3_Attachements_Bucket { get; set; } + + } } diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/FillePathUtils.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/FillePathUtils.cs new file mode 100644 index 000000000..c9958b345 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/FillePathUtils.cs @@ -0,0 +1,34 @@ +using FOIMOD.CFD.DocMigration.Models; +namespace FOIMOD.CFD.DocMigration.Utils +{ + public static class FilePathUtils + { + + public static List? GetFileDetailsFromdelimitedstring(string delimitedstring) + { + List? result = null; + if (!string.IsNullOrEmpty(delimitedstring)) + { + string[] files = delimitedstring.Split('|'); + + if (files != null && files.Length > 0) + { + result = new List(); + foreach (string fileinfodelimited in files) + { + string[] singlefileinfo = fileinfodelimited.Split('#'); + if (singlefileinfo != null && singlefileinfo.Length > 0) + { + result.Add(new AXISFIle() { FileName = singlefileinfo[1], FilePathOnServer = singlefileinfo[0], FileExtension = singlefileinfo[1].Split('.')[1] }); + } + + } + } + + } + + return result; + + } + } +} From ad2f70b959f3acebf4b108abbed82daf7a34a1c5 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Wed, 23 Aug 2023 13:31:49 -0700 Subject: [PATCH 064/234] #4141 --- .../Program.cs | 16 +++++++++++++++- .../CorrespondenceLogMigration.cs | 16 ++++++++-------- .../FOIFLOWDestination/UploadFile.cs | 2 +- .../S3UploadTest.cs | 8 ++++---- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/Program.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/Program.cs index 2ea42b3b2..9078484a8 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/Program.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/Program.cs @@ -1,8 +1,11 @@ // See https://aka.ms/new-console-template for more information +using Amazon.Runtime; +using Amazon.S3; using FOIMOD.CFD.DocMigration.BAL; using FOIMOD.CFD.DocMigration.Models; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Configuration; +using System.Net; Console.WriteLine("Starting, CFD Document Migration!"); var configurationbuilder = new ConfigurationBuilder() @@ -27,6 +30,17 @@ SqlConnection axissqlConnection = new SqlConnection(SystemSettings.AXISConnectionString); -CorrespondenceLogMigration correspondenceLogMigration = new CorrespondenceLogMigration(axissqlConnection, null, null); + +AWSCredentials s3credentials = new BasicAWSCredentials(SystemSettings.S3_AccessKey, SystemSettings.S3_SecretKey); + +AmazonS3Config config = new() +{ + ServiceURL = SystemSettings.S3_EndPoint + +}; + +AmazonS3Client amazonS3Client = new AmazonS3Client(s3credentials, config); + +CorrespondenceLogMigration correspondenceLogMigration = new CorrespondenceLogMigration(axissqlConnection, null, amazonS3Client); correspondenceLogMigration.RequestsToMigrate = SystemSettings.RequestToMigrate; await correspondenceLogMigration.RunMigration(); \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/CorrespondenceLogMigration.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/CorrespondenceLogMigration.cs index 52507306f..e1d918a87 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/CorrespondenceLogMigration.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/CorrespondenceLogMigration.cs @@ -22,8 +22,7 @@ public CorrespondenceLogMigration(IDbConnection _sourceAXISConnection, IDbConnec destinationfoiflowQLConnection = _destinationFOIFLOWDB; amazonS3 = _amazonS3; } - - [Obsolete] + public async Task RunMigration() { DocMigrationS3Client docMigrationS3Client = new DocMigrationS3Client(amazonS3); @@ -37,15 +36,16 @@ public async Task RunMigration() var files = FilePathUtils.GetFileDetailsFromdelimitedstring(attachment.EmailAttachmentDelimitedString); foreach (var file in files) { - //NOT AN EMAIL UPLOAD - DIRECT FILE UPLOAD - https://citz-foi-prod.objectstore.gov.bc.ca/dev-forms-foirequests-e/Misc/CFD-2023-22081302/applicant/00b8ad71-5d11-4624-9c12-839193cf4a7e.docx + //NOT AN EMAIL UPLOAD - DIRECT FILE UPLOAD - For e.g. https://citz-foi-prod.objectstore.gov.bc.ca/dev-forms-foirequests-e/Misc/CFD-2023-22081302/applicant/00b8ad71-5d11-4624-9c12-839193cf4a7e.docx var UNCFileLocation = Path.Combine(SystemSettings.FileServerRoot, SystemSettings.CorrespondenceLogBaseFolder, file.FilePathOnServer); + //var UNCFileLocation = file.FileExtension == "pdf" ? @"\\DESKTOP-U67UC02\ioashare\db7e84e1-4202-4837-b4dd-49af233ae006.pdf" : @"\\DESKTOP-U67UC02\ioashare\DOCX1.docx"; + - - using (FileStream fs = File.Open(UNCFileLocation, FileMode.Open)) + using (FileStream fs = File.Open(UNCFileLocation, FileMode.Open)) { - var s3filesubpath = string.Format("{0}/{1}/{2}/{3}", SystemSettings.S3_Attachements_BasePath, attachment.AXISRequestNumber, SystemSettings.AttachmentTag, string.Format("{0}.{1}", Guid.NewGuid().ToString(), file.FileExtension)); - var fulls3filepath = string.Format("{0}/{1}", SystemSettings.S3_EndPoint, s3filesubpath); - await docMigrationS3Client.UploadFileAsync(new UploadFile() { AXISRequestID = attachment.AXISRequestNumber.ToUpper(), SubFolderPath = s3filesubpath, DestinationFileName = file.FileName }); + var s3filesubpath = string.Format("{0}/{1}/{2}", SystemSettings.S3_Attachements_BasePath, attachment.AXISRequestNumber, SystemSettings.AttachmentTag); + + await docMigrationS3Client.UploadFileAsync(new UploadFile() { AXISRequestID = attachment.AXISRequestNumber.ToUpper(), SubFolderPath = s3filesubpath, DestinationFileName = string.Format("{0}.{1}", Guid.NewGuid().ToString(), file.FileExtension), FileStream =fs}); } diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/FOIFLOWDestination/UploadFile.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/FOIFLOWDestination/UploadFile.cs index 526d9b7b7..61a68c177 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/FOIFLOWDestination/UploadFile.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/FOIFLOWDestination/UploadFile.cs @@ -21,7 +21,7 @@ public class UploadFile public string S3BucketName { get; set; } - public string ContentType { get; set; } + public Stream FileStream { get; set; } } diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/S3UploadTest.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/S3UploadTest.cs index a5d2de132..03fb7d23f 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/S3UploadTest.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/S3UploadTest.cs @@ -46,7 +46,7 @@ public void S3Upload_UploadFileAsync() fs.Position = 0; var destinationfilename = string.Format("{0}.pdf", Guid.NewGuid().ToString()); - UploadFile uploadFile = new UploadFile() { ContentType = "application/pdf", AXISRequestID = "TEST-10001-12342", DestinationFileName = destinationfilename, S3BucketName = "test123-protected", SubFolderPath = "test123-protected/Abintest/unittestmigration", UploadType = UploadType.Attachments, SourceFileName = "DOC1.pdf", FileStream = fs }; + UploadFile uploadFile = new UploadFile() { AXISRequestID = "TEST-10001-12342", DestinationFileName = destinationfilename, S3BucketName = "test123-protected", SubFolderPath = "test123-protected/Abintest/unittestmigration", UploadType = UploadType.Attachments, SourceFileName = "DOC1.pdf", FileStream = fs }; var result = docMigrationS3Client.UploadFileAsync(uploadFile).Result; Assert.IsNotNull(result); Assert.IsTrue(result.IsSuccessStatusCode); @@ -75,7 +75,7 @@ public void PDFMerge_UploadTest() fs.Position = 0; var destinationfilename = string.Format("{0}.pdf", Guid.NewGuid().ToString()); - UploadFile uploadFile = new UploadFile() { ContentType = "application/pdf", AXISRequestID = "TEST-10001-12342", DestinationFileName = destinationfilename, S3BucketName = "test123-protected", SubFolderPath = "test123-protected/Abintest/unittestmigration", UploadType = UploadType.Attachments, SourceFileName = "DOC1.pdf", FileStream = fs }; + UploadFile uploadFile = new UploadFile() { AXISRequestID = "TEST-10001-12342", DestinationFileName = destinationfilename, S3BucketName = "test123-protected", SubFolderPath = "test123-protected/Abintest/unittestmigration", UploadType = UploadType.Attachments, SourceFileName = "DOC1.pdf", FileStream = fs }; var result = docMigrationS3Client.UploadFileAsync(uploadFile).Result; Assert.IsNotNull(result); Assert.IsTrue(result.IsSuccessStatusCode); @@ -93,7 +93,7 @@ public void CreatePDF_Test() using Stream emaildocstream = docMigrationPDFStitcher.CreatePDFDocument("

THIS IS AN EMAIL HTML content

", "My email subject line!!!!", DateTime.Now.AddYears(-10).ToLongDateString(), "abinajik@gmail.com"); emaildocstream.Position = 0; var destinationfilename = string.Format("{0}.pdf", Guid.NewGuid().ToString()); - UploadFile uploadFile = new UploadFile() { ContentType = "application/pdf", AXISRequestID = "TEST-10001-12342", DestinationFileName = destinationfilename, S3BucketName = "test123-protected", SubFolderPath = "test123-protected/Abintest/unittestmigration", UploadType = UploadType.Attachments, SourceFileName = "DOC1.pdf", FileStream = emaildocstream }; + UploadFile uploadFile = new UploadFile() { AXISRequestID = "TEST-10001-12342", DestinationFileName = destinationfilename, S3BucketName = "test123-protected", SubFolderPath = "test123-protected/Abintest/unittestmigration", UploadType = UploadType.Attachments, SourceFileName = "DOC1.pdf", FileStream = emaildocstream }; var result = docMigrationS3Client.UploadFileAsync(uploadFile).Result; Assert.IsNotNull(result); Assert.IsTrue(result.IsSuccessStatusCode); @@ -127,7 +127,7 @@ public void MergeStreamwithFileAttachmentsTest() fs.Position = 0; var destinationfilename = string.Format("{0}.pdf", Guid.NewGuid().ToString()); - UploadFile uploadFile = new UploadFile() { ContentType = "application/pdf", AXISRequestID = "TEST-10001-12342", DestinationFileName = destinationfilename, S3BucketName = "test123-protected", SubFolderPath = "test123-protected/Abintest/unittestmigration", UploadType = UploadType.Attachments, SourceFileName = "DOC1.pdf", FileStream = fs }; + UploadFile uploadFile = new UploadFile() { AXISRequestID = "TEST-10001-12342", DestinationFileName = destinationfilename, S3BucketName = "test123-protected", SubFolderPath = "test123-protected/Abintest/unittestmigration", UploadType = UploadType.Attachments, SourceFileName = "DOC1.pdf", FileStream = fs }; var result = docMigrationS3Client.UploadFileAsync(uploadFile).Result; Assert.IsNotNull(result); Assert.IsTrue(result.IsSuccessStatusCode); From 37a487715fa4f539dfb2e008878263a8f52d2b7b Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Wed, 23 Aug 2023 17:37:34 -0700 Subject: [PATCH 065/234] #4141 - FOIFLOW DAL --- .gitignore | 1 + .../FOIMOD.CFD.ConsoleApp.DocMigration.sln | 6 +++ .../FOIFLOWDBUnitTests.cs | 37 ++++++++++++++++ ....CFD.DocMigration.FOIFLOW.DAL.Tests.csproj | 35 ++++++++++++++++ .../Usings.cs | 1 + .../appsettings.json | 7 ++++ .../AttachmentsDAL.cs | 42 +++++++++++++++++++ .../Class1.cs | 7 ---- ...FOIMOD.CFD.DocMigration.FOIFLOW.DAL.csproj | 4 ++ .../SystemSettings.cs | 2 + 10 files changed, 135 insertions(+), 7 deletions(-) create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests/FOIFLOWDBUnitTests.cs create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests.csproj create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests/Usings.cs create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests/appsettings.json create mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/AttachmentsDAL.cs delete mode 100644 datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/Class1.cs diff --git a/.gitignore b/.gitignore index 4892510f7..4b0560cea 100644 --- a/.gitignore +++ b/.gitignore @@ -108,3 +108,4 @@ datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils. datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/obj/* datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/obj/* datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/Debug/* +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests/obj/* diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.sln b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.sln index adc380471..6e06785c4 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.sln +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.sln @@ -21,6 +21,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FOIMOD.CFD.DocMigration.Uti EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests", "FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests\FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests.csproj", "{4402E37D-BA96-4C9C-9AD2-78A866D9C2E6}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests", "FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests\FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests.csproj", "{E964523D-91C1-4E1C-860F-52E57E826E16}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -63,6 +65,10 @@ Global {4402E37D-BA96-4C9C-9AD2-78A866D9C2E6}.Debug|Any CPU.Build.0 = Debug|Any CPU {4402E37D-BA96-4C9C-9AD2-78A866D9C2E6}.Release|Any CPU.ActiveCfg = Release|Any CPU {4402E37D-BA96-4C9C-9AD2-78A866D9C2E6}.Release|Any CPU.Build.0 = Release|Any CPU + {E964523D-91C1-4E1C-860F-52E57E826E16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E964523D-91C1-4E1C-860F-52E57E826E16}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E964523D-91C1-4E1C-860F-52E57E826E16}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E964523D-91C1-4E1C-860F-52E57E826E16}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests/FOIFLOWDBUnitTests.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests/FOIFLOWDBUnitTests.cs new file mode 100644 index 000000000..2db64ea08 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests/FOIFLOWDBUnitTests.cs @@ -0,0 +1,37 @@ +using FOIMOD.CFD.DocMigration.Models; +using Microsoft.Extensions.Configuration; +using System.Data.Odbc; +using System.Net; +using FOIMOD.CFD.DocMigration.FOIFLOW.DAL; +namespace FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests +{ + [TestClass] + public class FOIFLOWDBUnitTests + { + + [TestInitialize] + public void FOIFLOWDBUnitTestInit() + { + var configurationbuilder = new ConfigurationBuilder() + .AddJsonFile($"appsettings.json", true, true) + .AddEnvironmentVariables().Build(); + + SystemSettings.FOIFLOWConnectionString = configurationbuilder.GetSection("FOIFLOWConfiguration:FOIFLOWConnectionString").Value; + + + + } + + + [TestMethod] + public void AttachmentInsertTest() + { + OdbcConnection connection = new OdbcConnection(SystemSettings.FOIFLOWConnectionString); + AttachmentsDAL attachmentsDAL = new AttachmentsDAL(connection); + var result = attachmentsDAL.InsertIntoMinistryRequestDocuments("https://unittest", "unittestfile.docx", "CFD-2023-22081302"); + Assert.IsNotNull(result); + + + } + } +} \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests.csproj b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests.csproj new file mode 100644 index 000000000..766edf4eb --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests.csproj @@ -0,0 +1,35 @@ + + + + net7.0 + enable + enable + + false + + x86 + + + + + + + + + + + + + + + + + + + + + Always + + + + diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests/Usings.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests/Usings.cs new file mode 100644 index 000000000..ab67c7ea9 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests/appsettings.json b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests/appsettings.json new file mode 100644 index 000000000..5749679a0 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests/appsettings.json @@ -0,0 +1,7 @@ +{ + "FOIFLOWConfiguration": { + "FOIFLOWConnectionString": "DSN=FOIFLOWDBdsn" + } + + +} diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/AttachmentsDAL.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/AttachmentsDAL.cs new file mode 100644 index 000000000..0a5428f05 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/AttachmentsDAL.cs @@ -0,0 +1,42 @@ +using System.Data; +using System.Data.Common; +using System.Data.Odbc; + +namespace FOIMOD.CFD.DocMigration.FOIFLOW.DAL +{ + public class AttachmentsDAL + { + private IDbConnection dbConnection; + public AttachmentsDAL(IDbConnection _dbConnection) + { + dbConnection = _dbConnection; + } + + public bool InsertIntoMinistryRequestDocuments(string s3filepath, string actualfilename, string axisrequestnumber) + { + bool result = false; + try + { + dbConnection.Open(); + var cmdString = @"DO $$ DECLARE requestnumber VARCHAR; BEGIN requestnumber := '{0}' ; INSERT INTO public.""FOIMinistryRequestDocuments"" ( documentpath, created_at, createdby, foiministryrequest_id, foiministryrequestversion_id, filename, isactive, category, version) VALUES ('{1}', NOW(), 'cfdmigration', (SELECT foiministryrequestid FROM public.""FOIMinistryRequests"" WHERE axisrequestid=requestnumber ORDER BY created_at DESC LIMIT 1), (SELECT version FROM public.""FOIMinistryRequests"" WHERE axisrequestid=requestnumber ORDER BY created_at DESC LIMIT 1), '{2}', True, 'applicant', 1); END $$"; + using (OdbcCommand comm = new OdbcCommand()) + { + comm.Connection = (OdbcConnection)dbConnection; + comm.CommandText = string.Format(cmdString,axisrequestnumber,s3filepath,actualfilename); + comm.CommandType = CommandType.Text; + comm.ExecuteNonQuery(); + dbConnection.Close(); + result = true; + } + } + catch (Exception ex) { + dbConnection.Close(); + result = false; + } + return result; + } + + + + } +} \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/Class1.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/Class1.cs deleted file mode 100644 index 1b85cb4e1..000000000 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/Class1.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace FOIMOD.CFD.DocMigration.FOIFLOW.DAL -{ - public class Class1 - { - - } -} \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.csproj b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.csproj index cfadb03dd..b01aaa0fc 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.csproj +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.csproj @@ -6,4 +6,8 @@ enable + + + +
diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/SystemSettings.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/SystemSettings.cs index 9e5c3b30d..5bca03986 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/SystemSettings.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/SystemSettings.cs @@ -16,6 +16,8 @@ public static class SystemSettings public static string AXISConnectionString{ get; set; } + public static string FOIFLOWConnectionString { get; set; } + public static string RequestToMigrate { get; set; } public static string FileServerRoot { get; set; } From 27aa5bdec7bcbab43d8712b327d3bb63f374f165 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Wed, 23 Aug 2023 18:02:29 -0700 Subject: [PATCH 066/234] #4141 Correspondence Log complete --- .gitignore | 1 + .../FOIMOD.CFD.ConsoleApp.DocMigration.csproj | 5 ++++ .../Program.cs | 15 ++++++++++-- .../appsettings.json | 9 ++++--- .../CorrespondenceLogMigration.cs | 24 +++++++++++++------ .../FOIMOD.CFD.DocMigration.BAL.csproj | 1 + .../AttachmentsDAL.cs | 1 + 7 files changed, 44 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 4b0560cea..5d90a726b 100644 --- a/.gitignore +++ b/.gitignore @@ -109,3 +109,4 @@ datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigra datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/obj/* datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/Debug/* datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests/obj/* +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/appsettings.dev.json diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.csproj b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.csproj index f25b62880..fa9ad3da9 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.csproj +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.csproj @@ -5,18 +5,23 @@ net7.0 enable enable + x86 + + + Always + Always diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/Program.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/Program.cs index 9078484a8..7bd47164a 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/Program.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/Program.cs @@ -5,13 +5,22 @@ using FOIMOD.CFD.DocMigration.Models; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Configuration; +using System.Data.Odbc; using System.Net; Console.WriteLine("Starting, CFD Document Migration!"); + +#if DEBUG +var configurationbuilder = new ConfigurationBuilder() + .AddJsonFile($"appsettings.dev.json", true, true) + .AddEnvironmentVariables().Build(); +#else var configurationbuilder = new ConfigurationBuilder() .AddJsonFile($"appsettings.json", true, true) .AddEnvironmentVariables().Build(); +#endif + SystemSettings.FileServerRoot = configurationbuilder.GetSection("S3Configuration:FileServerRoot").Value; SystemSettings.CorrespondenceLogBaseFolder = configurationbuilder.GetSection("S3Configuration:CorrespondenceLogBaseFolder").Value; @@ -26,10 +35,12 @@ SystemSettings.AXISConnectionString = configurationbuilder.GetSection("AXISConfiguration:SQLConnectionString").Value; SystemSettings.RequestToMigrate = configurationbuilder.GetSection("AXISConfiguration:RequestToMigrate").Value; +SystemSettings.FOIFLOWConnectionString = configurationbuilder.GetSection("FOIFLOWConfiguration:FOIFLOWConnectionString").Value; -SqlConnection axissqlConnection = new SqlConnection(SystemSettings.AXISConnectionString); +SqlConnection axissqlConnection = new SqlConnection(SystemSettings.AXISConnectionString); +OdbcConnection odbcConnection = new OdbcConnection(SystemSettings.FOIFLOWConnectionString); AWSCredentials s3credentials = new BasicAWSCredentials(SystemSettings.S3_AccessKey, SystemSettings.S3_SecretKey); @@ -41,6 +52,6 @@ AmazonS3Client amazonS3Client = new AmazonS3Client(s3credentials, config); -CorrespondenceLogMigration correspondenceLogMigration = new CorrespondenceLogMigration(axissqlConnection, null, amazonS3Client); +CorrespondenceLogMigration correspondenceLogMigration = new CorrespondenceLogMigration(axissqlConnection, odbcConnection, amazonS3Client); correspondenceLogMigration.RequestsToMigrate = SystemSettings.RequestToMigrate; await correspondenceLogMigration.RunMigration(); \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/appsettings.json b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/appsettings.json index 1742c9f3a..2e9102a54 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/appsettings.json +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/appsettings.json @@ -1,12 +1,12 @@ { "S3Configuration": { - "AWS_accesskey": "", - "AWS_secret": "", + "AWS_accesskey": "", + "AWS_secret": "", "AWS_S3_Url": "https://citz-foi-prod.objectstore.gov.bc.ca", "FileServerRoot": "\\\\solis\\ATIPDocs\\", "CorrespondenceLogBaseFolder": "AFXWCORL", "RecordsbaseFolder": "AFXWDOCS", - "S3_Attachements_BasePath": "/dev-forms-foirequests-e/Misc", + "S3_Attachements_BasePath": "dev-forms-foirequests-e/Misc", "S3_Attachements_Bucket": "dev-forms-foirequests-e", "AttachmentTag": "applicant" @@ -14,6 +14,9 @@ "AXISConfiguration": { "SQLConnectionString": "Data Source=.;Initial Catalog=ATIPD;Integrated Security=True;Encrypt=False", "RequestToMigrate": "'CFD-2015-50011','CFD-2014-50119','CLB-2017-70004'" + }, + "FOIFLOWConfiguration": { + "FOIFLOWConnectionString": "DSN=FOIFLOWDBdsn" } diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/CorrespondenceLogMigration.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/CorrespondenceLogMigration.cs index e1d918a87..0c5781612 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/CorrespondenceLogMigration.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/CorrespondenceLogMigration.cs @@ -1,11 +1,13 @@ using Amazon.S3; using FOIMOD.CFD.DocMigration.DAL; +using FOIMOD.CFD.DocMigration.FOIFLOW.DAL; using FOIMOD.CFD.DocMigration.Models; using FOIMOD.CFD.DocMigration.Models.Document; using FOIMOD.CFD.DocMigration.Models.FOIFLOWDestination; using FOIMOD.CFD.DocMigration.Utils; using Microsoft.Data.SqlClient; using System.Data; +using System.Data.Odbc; namespace FOIMOD.CFD.DocMigration.BAL { @@ -22,12 +24,13 @@ public CorrespondenceLogMigration(IDbConnection _sourceAXISConnection, IDbConnec destinationfoiflowQLConnection = _destinationFOIFLOWDB; amazonS3 = _amazonS3; } - + public async Task RunMigration() { DocMigrationS3Client docMigrationS3Client = new DocMigrationS3Client(amazonS3); DocumentsDAL documentsDAL = new DocumentsDAL((SqlConnection)sourceaxisSQLConnection); + AttachmentsDAL attachmentsDAL = new AttachmentsDAL((OdbcConnection)destinationfoiflowQLConnection); List? correspondencelogs = documentsDAL.GetCorrespondenceLogDocuments(this.RequestsToMigrate); foreach (DocumentToMigrate attachment in correspondencelogs) { @@ -37,15 +40,22 @@ public async Task RunMigration() foreach (var file in files) { //NOT AN EMAIL UPLOAD - DIRECT FILE UPLOAD - For e.g. https://citz-foi-prod.objectstore.gov.bc.ca/dev-forms-foirequests-e/Misc/CFD-2023-22081302/applicant/00b8ad71-5d11-4624-9c12-839193cf4a7e.docx - var UNCFileLocation = Path.Combine(SystemSettings.FileServerRoot, SystemSettings.CorrespondenceLogBaseFolder, file.FilePathOnServer); - //var UNCFileLocation = file.FileExtension == "pdf" ? @"\\DESKTOP-U67UC02\ioashare\db7e84e1-4202-4837-b4dd-49af233ae006.pdf" : @"\\DESKTOP-U67UC02\ioashare\DOCX1.docx"; - + //var UNCFileLocation = Path.Combine(SystemSettings.FileServerRoot, SystemSettings.CorrespondenceLogBaseFolder, file.FilePathOnServer); + var UNCFileLocation = file.FileExtension == "pdf" ? @"\\DESKTOP-U67UC02\ioashare\db7e84e1-4202-4837-b4dd-49af233ae006.pdf" : @"\\DESKTOP-U67UC02\ioashare\DOCX1.docx"; + - using (FileStream fs = File.Open(UNCFileLocation, FileMode.Open)) + using (FileStream fs = File.Open(UNCFileLocation, FileMode.Open)) { var s3filesubpath = string.Format("{0}/{1}/{2}", SystemSettings.S3_Attachements_BasePath, attachment.AXISRequestNumber, SystemSettings.AttachmentTag); - - await docMigrationS3Client.UploadFileAsync(new UploadFile() { AXISRequestID = attachment.AXISRequestNumber.ToUpper(), SubFolderPath = s3filesubpath, DestinationFileName = string.Format("{0}.{1}", Guid.NewGuid().ToString(), file.FileExtension), FileStream =fs}); + var destinationfilename = string.Format("{0}.{1}", Guid.NewGuid().ToString(), file.FileExtension); + var uploadresponse = await docMigrationS3Client.UploadFileAsync(new UploadFile() { AXISRequestID = attachment.AXISRequestNumber.ToUpper(), SubFolderPath = s3filesubpath, DestinationFileName = destinationfilename, FileStream = fs }); + var fullfileurl = string.Format("{0}/{1}/{2}", SystemSettings.S3_EndPoint, s3filesubpath,destinationfilename); + if (uploadresponse.IsSuccessStatusCode) + { + //INSERT INTO TABLE - FOIMinistryRequestDocuments + attachmentsDAL.InsertIntoMinistryRequestDocuments(fullfileurl, file.FileName, attachment.AXISRequestNumber); + + } } diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/FOIMOD.CFD.DocMigration.BAL.csproj b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/FOIMOD.CFD.DocMigration.BAL.csproj index ebce8e7eb..05fc1d8ec 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/FOIMOD.CFD.DocMigration.BAL.csproj +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/FOIMOD.CFD.DocMigration.BAL.csproj @@ -12,6 +12,7 @@ + diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/AttachmentsDAL.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/AttachmentsDAL.cs index 0a5428f05..46e71506c 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/AttachmentsDAL.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/AttachmentsDAL.cs @@ -32,6 +32,7 @@ public bool InsertIntoMinistryRequestDocuments(string s3filepath, string actualf catch (Exception ex) { dbConnection.Close(); result = false; + throw; } return result; } From ab1542621a5fe13b6afbf0e7b01e8a34bff4cc71 Mon Sep 17 00:00:00 2001 From: Nimya John <78105113+nimya-aot@users.noreply.github.com> Date: Wed, 23 Aug 2023 22:40:06 -0700 Subject: [PATCH 067/234] Update katalon-ci.yaml Downgrading Katalon version to see --- .github/workflows/katalon-ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/katalon-ci.yaml b/.github/workflows/katalon-ci.yaml index afcaebade..35b3be825 100644 --- a/.github/workflows/katalon-ci.yaml +++ b/.github/workflows/katalon-ci.yaml @@ -37,7 +37,7 @@ jobs: - name: Katalon Studio Github Action uses: katalon-studio/katalon-studio-github-action@v2 with: - version: '8.2.0' + version: '7.2.10' projectPath: '${{ github.workspace }}/testing/foi-qa-automation/foi-qa-automation.prj' args: '-noSplash -retry=0 -testSuiteCollectionPath="Test Suites/foi-test" -apiKey=${{ secrets.KATALON_API_KEY }} --config -webui.autoUpdateDrivers=true -licenseRelease=true -browserType="Chrome" -proxy.auth.option=NO_PROXY -proxy.system.option=NO_PROXY -proxy.system.applyToDesiredCapabilities=true -executionProfile="dev" ' - name: copy reports to one folder From b2f2b9be8052f96fa86a7986d6feb936fc2a2c2d Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Thu, 24 Aug 2023 12:39:10 -0700 Subject: [PATCH 068/234] Added backend validation to halt the user from deleting a programareadivision if division is applied/tagged to any records. If a divsion is tagged to any records, a 400 response is sent and disabling/deletion of division is stopped, if a division is not tagged to any records the division is disabled with a 200 response. WIP Frontend work --- .../request_api/models/FOIRequestRecords.py | 14 ++++++++++++++ .../request_api/resources/foiadmin.py | 2 +- .../services/programareadivisionservice.py | 10 ++++++++++ .../request_api/services/recordservice.py | 5 ++++- 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/request-management-api/request_api/models/FOIRequestRecords.py b/request-management-api/request_api/models/FOIRequestRecords.py index 2e8d99e78..34a7385d6 100644 --- a/request-management-api/request_api/models/FOIRequestRecords.py +++ b/request-management-api/request_api/models/FOIRequestRecords.py @@ -126,6 +126,20 @@ def getrecordsbyid(cls, recordids): finally: db.session.close() return records + + @classmethod + def get_all_records(cls): + records_schema = FOIRequestRecordSchema(many=True) + # sql = """select + # (to_json(attributes)->>'divisions\')::json as divisions + # from public. "FOIRequestRecords" + # """ + # search_value = {"divisions": [{"divisionid": divisionid}]} + # query = db.session.query(FOIRequestRecord).filter(FOIRequestRecord.isactive==True).filter(FOIRequestRecord.attributes.contains(search_value)).all() + # query = db.session.query(FOIRequestRecord).filter(FOIRequestRecord.isactive==True).filter(FOIRequestRecord.attributes['divisions'].astext.ilike(divisionid)).all() + # DocumentAttributes.attributes['isattachment'].astext.cast(db.Boolean).label('isattachment') + query = db.session.query(FOIRequestRecord).filter_by(isactive=True).all() + return records_schema.dump(query) @classmethod def replace(cls,replacingrecordid,records): diff --git a/request-management-api/request_api/resources/foiadmin.py b/request-management-api/request_api/resources/foiadmin.py index 54e4df8f8..eb32e28d9 100644 --- a/request-management-api/request_api/resources/foiadmin.py +++ b/request-management-api/request_api/resources/foiadmin.py @@ -116,7 +116,7 @@ def put(divisionid): try: result = programareadivisionservice().disableprogramareadivision(divisionid, AuthHelper.getuserid()) if result.success != True: - return {'status': result.success, 'message': result.message,}, 400 + return {'status': result.success, 'message': result.message, 'id':result.identifier}, 400 return {'status': result.success, 'message':result.message, 'id':result.identifier}, 200 except KeyError as err: return {'status': False, 'message':err.messages}, 400 diff --git a/request-management-api/request_api/services/programareadivisionservice.py b/request-management-api/request_api/services/programareadivisionservice.py index 57dc2fd10..40052675c 100644 --- a/request-management-api/request_api/services/programareadivisionservice.py +++ b/request-management-api/request_api/services/programareadivisionservice.py @@ -1,5 +1,8 @@ from request_api.models.ProgramAreaDivisions import ProgramAreaDivision from request_api.services.programareaservice import programareaservice +from request_api.services.recordservice import recordservice +import json +from request_api.models.default_method_result import DefaultMethodResult class programareadivisionservice: @@ -23,6 +26,13 @@ def updateprogramareadivision(self, divisionid, data,userid): def disableprogramareadivision(self, divisionid,userid): """ Disable a program area division """ + # Validation to see if division id exists in any records. If so deletion cannot be completed. + records = recordservice().get_all_records() + for record in records: + attributes = json.loads(record['attributes']) + if any(int(divisionid) in division.values() for division in attributes["divisions"]): + return DefaultMethodResult(False,'Division is currently tagged to various records and cannot be disabled at this time', divisionid) + return ProgramAreaDivision.disableprogramareadivision(divisionid,userid) def __prepareprogramareas(self, data): diff --git a/request-management-api/request_api/services/recordservice.py b/request-management-api/request_api/services/recordservice.py index 96db2ccf6..7a7111cf9 100644 --- a/request-management-api/request_api/services/recordservice.py +++ b/request-management-api/request_api/services/recordservice.py @@ -36,7 +36,10 @@ def create(self, requestid, ministryrequestid, recordschema, userid): return self.__bulkcreate(requestid, ministryrequestid, recordschema.get("records"), userid) def fetch(self, requestid, ministryrequestid): - return recordservicegetter().fetch(requestid, ministryrequestid) + return recordservicegetter().fetch(requestid, ministryrequestid) + + def get_all_records(self): + return FOIRequestRecord.get_all_records() def update(self, requestid, ministryrequestid, requestdata, userid): newrecords = [] From e9b8dab6a1eb8a1ea583cdc13b9bf4bff248e6e2 Mon Sep 17 00:00:00 2001 From: Nimya John <78105113+nimya-aot@users.noreply.github.com> Date: Thu, 24 Aug 2023 13:33:18 -0700 Subject: [PATCH 069/234] Update katalon-ci.yaml --- .github/workflows/katalon-ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/katalon-ci.yaml b/.github/workflows/katalon-ci.yaml index 35b3be825..1f01beceb 100644 --- a/.github/workflows/katalon-ci.yaml +++ b/.github/workflows/katalon-ci.yaml @@ -37,7 +37,7 @@ jobs: - name: Katalon Studio Github Action uses: katalon-studio/katalon-studio-github-action@v2 with: - version: '7.2.10' + version: '8.6.6' projectPath: '${{ github.workspace }}/testing/foi-qa-automation/foi-qa-automation.prj' args: '-noSplash -retry=0 -testSuiteCollectionPath="Test Suites/foi-test" -apiKey=${{ secrets.KATALON_API_KEY }} --config -webui.autoUpdateDrivers=true -licenseRelease=true -browserType="Chrome" -proxy.auth.option=NO_PROXY -proxy.system.option=NO_PROXY -proxy.system.applyToDesiredCapabilities=true -executionProfile="dev" ' - name: copy reports to one folder From a1ddee709cdcf274c8f06b281ed82c0f1f379ba7 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Thu, 24 Aug 2023 15:02:24 -0700 Subject: [PATCH 070/234] Adjusted front end validation to provide user with details when disabling a division fails (requests them to ensure that divisions are not tagged to any records --- .../src/components/FOI/Admin/Divisions/Divisions.jsx | 4 ++-- .../request_api/services/programareadivisionservice.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx b/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx index f17670026..af1397eb4 100644 --- a/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx +++ b/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx @@ -144,10 +144,10 @@ const Divisions = ({userDetail}) => { }); } else { toast.error( - "Temporarily unable to disable division. Please try again in a few minutes.", + "Unable to disable division. Please try again in a few minutes and ensure that the division is not tagged to any FOI Request records.", { position: "top-right", - autoClose: 3000, + autoClose: 4500, hideProgressBar: true, closeOnClick: true, pauseOnHover: true, diff --git a/request-management-api/request_api/services/programareadivisionservice.py b/request-management-api/request_api/services/programareadivisionservice.py index 40052675c..c459e0e3b 100644 --- a/request-management-api/request_api/services/programareadivisionservice.py +++ b/request-management-api/request_api/services/programareadivisionservice.py @@ -31,7 +31,7 @@ def disableprogramareadivision(self, divisionid,userid): for record in records: attributes = json.loads(record['attributes']) if any(int(divisionid) in division.values() for division in attributes["divisions"]): - return DefaultMethodResult(False,'Division is currently tagged to various records and cannot be disabled at this time', divisionid) + return DefaultMethodResult(False,'Division is currently tagged to various records and cannot be disabled', divisionid) return ProgramAreaDivision.disableprogramareadivision(divisionid,userid) From 47d37250a175cf3da2055b601e02f349760c49de Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Thu, 24 Aug 2023 15:02:41 -0700 Subject: [PATCH 071/234] #4141 Updates with attachment sticthing --- .../CorrespondenceLogMigration.cs | 57 ++++++++++++++++++- .../DocumentsDAL.cs | 2 +- .../S3UploadTest.cs | 22 +++---- .../DocMigrationPDFStitcher.cs | 2 +- .../FillePathUtils.cs | 14 ++++- 5 files changed, 80 insertions(+), 17 deletions(-) diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/CorrespondenceLogMigration.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/CorrespondenceLogMigration.cs index 0c5781612..ad529cc55 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/CorrespondenceLogMigration.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/CorrespondenceLogMigration.cs @@ -31,6 +31,7 @@ public async Task RunMigration() DocumentsDAL documentsDAL = new DocumentsDAL((SqlConnection)sourceaxisSQLConnection); AttachmentsDAL attachmentsDAL = new AttachmentsDAL((OdbcConnection)destinationfoiflowQLConnection); + DocMigrationPDFStitcher docMigrationPDFStitcher = new DocMigrationPDFStitcher(); List? correspondencelogs = documentsDAL.GetCorrespondenceLogDocuments(this.RequestsToMigrate); foreach (DocumentToMigrate attachment in correspondencelogs) { @@ -49,7 +50,7 @@ public async Task RunMigration() var s3filesubpath = string.Format("{0}/{1}/{2}", SystemSettings.S3_Attachements_BasePath, attachment.AXISRequestNumber, SystemSettings.AttachmentTag); var destinationfilename = string.Format("{0}.{1}", Guid.NewGuid().ToString(), file.FileExtension); var uploadresponse = await docMigrationS3Client.UploadFileAsync(new UploadFile() { AXISRequestID = attachment.AXISRequestNumber.ToUpper(), SubFolderPath = s3filesubpath, DestinationFileName = destinationfilename, FileStream = fs }); - var fullfileurl = string.Format("{0}/{1}/{2}", SystemSettings.S3_EndPoint, s3filesubpath,destinationfilename); + var fullfileurl = string.Format("{0}/{1}/{2}", SystemSettings.S3_EndPoint, s3filesubpath, destinationfilename); if (uploadresponse.IsSuccessStatusCode) { //INSERT INTO TABLE - FOIMinistryRequestDocuments @@ -65,6 +66,60 @@ public async Task RunMigration() else { //Create EMAIL MSG PDF and sticth with the attachment documents + List documentToMigrateEmail = new List(); + using var emaildocstream = docMigrationPDFStitcher.CreatePDFDocument(attachment.EmailContent, attachment.EmailSubject, attachment.EmailDate, attachment.EmailTo); + emaildocstream.Position = 0; + documentToMigrateEmail.Add(new DocumentToMigrate() { FileStream = emaildocstream, DocumentType = Models.AXISSource.DocumentTypeFromAXIS.CorrespondenceLog, AXISRequestNumber = attachment.AXISRequestNumber.ToUpper(), PageSequenceNumber = 1, HasStreamForDocument = true }); + + var attachmentfiles = FilePathUtils.GetFileDetailsFromdelimitedstring(attachment.EmailAttachmentDelimitedString); + if (attachmentfiles != null) + { + foreach (var file in attachmentfiles) + { + //var UNCFileLocation = Path.Combine(SystemSettings.FileServerRoot, SystemSettings.CorrespondenceLogBaseFolder, file.FilePathOnServer); + var UNCFileLocation = file.FileExtension == "pdf" ? @"\\DESKTOP-U67UC02\ioashare\db7e84e1-4202-4837-b4dd-49af233ae006.pdf" : @"\\DESKTOP-U67UC02\ioashare\DOCX1.docx"; + if (file.FileExtension == "pdf") + { + + documentToMigrateEmail.Add(new DocumentToMigrate() + { PageFilePath = UNCFileLocation, PageSequenceNumber = 2 }); + + } + else + { + using (FileStream fs = File.Open(UNCFileLocation, FileMode.Open)) + { + var s3filesubpath = string.Format("{0}/{1}/{2}", SystemSettings.S3_Attachements_BasePath, attachment.AXISRequestNumber, SystemSettings.AttachmentTag); + var destinationfilename = string.Format("{0}.{1}", Guid.NewGuid().ToString(), file.FileExtension); + var uploadresponse = await docMigrationS3Client.UploadFileAsync(new UploadFile() { AXISRequestID = attachment.AXISRequestNumber.ToUpper(), SubFolderPath = s3filesubpath, DestinationFileName = destinationfilename, FileStream = fs }); + var fullfileurl = string.Format("{0}/{1}/{2}", SystemSettings.S3_EndPoint, s3filesubpath, destinationfilename); + if (uploadresponse.IsSuccessStatusCode) + { + //INSERT INTO TABLE - FOIMinistryRequestDocuments + attachmentsDAL.InsertIntoMinistryRequestDocuments(fullfileurl, file.FileName, attachment.AXISRequestNumber); + + } + + } + } + } + } + + + using (Stream emailmessagepdfstream = docMigrationPDFStitcher.MergePDFs(documentToMigrateEmail)) + { + var s3filesubpath = string.Format("{0}/{1}/{2}", SystemSettings.S3_Attachements_BasePath, attachment.AXISRequestNumber, SystemSettings.AttachmentTag); + var destinationfilename = string.Format("{0}.pdf", Guid.NewGuid().ToString()); + var uploadresponse = await docMigrationS3Client.UploadFileAsync(new UploadFile() { AXISRequestID = attachment.AXISRequestNumber.ToUpper(), SubFolderPath = s3filesubpath, DestinationFileName = destinationfilename, FileStream = emailmessagepdfstream }); + var fullfileurl = string.Format("{0}/{1}/{2}", SystemSettings.S3_EndPoint, s3filesubpath, destinationfilename); + if (uploadresponse.IsSuccessStatusCode) + { + //INSERT INTO TABLE - FOIMinistryRequestDocuments + attachmentsDAL.InsertIntoMinistryRequestDocuments(fullfileurl, string.Format("{0}.pdf",attachment.EmailSubject), attachment.AXISRequestNumber); + + } + } + } } diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/DocumentsDAL.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/DocumentsDAL.cs index 007b62180..71ab5e1e5 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/DocumentsDAL.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/DocumentsDAL.cs @@ -27,7 +27,7 @@ public DocumentsDAL(SqlConnection _sqlConnection) ,C.vcEmail ,C.vcFromEmail , CAST(C.vcBody as NVARCHAR(max)) as emailbody - , STRING_AGG(CAST((C.vcFilePath +' # ' +C.vcFileName) as nvarchar(max)),' | ') as attachments + , STRING_AGG(CAST((C.vcFilePath +' * ' +C.vcFileName) as nvarchar(max)),' | ') as attachments FROM tblCorrespondence C JOIN tblRequests R on C.iRequestID = R.iRequestID WHERE R.vcVisibleRequestID in ({0}) diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/S3UploadTest.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/S3UploadTest.cs index 03fb7d23f..6fa1682cb 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/S3UploadTest.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/S3UploadTest.cs @@ -46,7 +46,7 @@ public void S3Upload_UploadFileAsync() fs.Position = 0; var destinationfilename = string.Format("{0}.pdf", Guid.NewGuid().ToString()); - UploadFile uploadFile = new UploadFile() { AXISRequestID = "TEST-10001-12342", DestinationFileName = destinationfilename, S3BucketName = "test123-protected", SubFolderPath = "test123-protected/Abintest/unittestmigration", UploadType = UploadType.Attachments, SourceFileName = "DOC1.pdf", FileStream = fs }; + UploadFile uploadFile = new UploadFile() { AXISRequestID = "TEST-10001-12342", DestinationFileName = destinationfilename, S3BucketName = "test123-protected", SubFolderPath = "test123-protected/Abintest/unittestmigration", UploadType = UploadType.Attachments, SourceFileName = "DOC1.pdf", FileStream = fs }; var result = docMigrationS3Client.UploadFileAsync(uploadFile).Result; Assert.IsNotNull(result); Assert.IsTrue(result.IsSuccessStatusCode); @@ -56,9 +56,9 @@ public void S3Upload_UploadFileAsync() [TestMethod] public void PDFMerge_UploadTest() { - DocumentToMigrate[] pDFDocToMerges = new DocumentToMigrate[2]; - pDFDocToMerges[0] = new DocumentToMigrate() { PageFilePath = Path.Combine(getSourceFolder(), "DOCX1.pdf"), PageSequenceNumber = 2 }; - pDFDocToMerges[1] = new DocumentToMigrate() { PageFilePath = Path.Combine(getSourceFolder(), "cat1.pdf"), PageSequenceNumber = 1 }; + List pDFDocToMerges = new List(); + pDFDocToMerges.Add(new DocumentToMigrate() { PageFilePath = Path.Combine(getSourceFolder(), "DOCX1.pdf"), PageSequenceNumber = 3 }); + pDFDocToMerges.Add(new DocumentToMigrate() { PageFilePath = Path.Combine(getSourceFolder(), "cat1.pdf"), PageSequenceNumber = 2 }); using (DocMigrationPDFStitcher docMigrationPDFStitcher = new DocMigrationPDFStitcher()) @@ -75,7 +75,7 @@ public void PDFMerge_UploadTest() fs.Position = 0; var destinationfilename = string.Format("{0}.pdf", Guid.NewGuid().ToString()); - UploadFile uploadFile = new UploadFile() { AXISRequestID = "TEST-10001-12342", DestinationFileName = destinationfilename, S3BucketName = "test123-protected", SubFolderPath = "test123-protected/Abintest/unittestmigration", UploadType = UploadType.Attachments, SourceFileName = "DOC1.pdf", FileStream = fs }; + UploadFile uploadFile = new UploadFile() { AXISRequestID = "TEST-10001-12342", DestinationFileName = destinationfilename, S3BucketName = "test123-protected", SubFolderPath = "test123-protected/Abintest/unittestmigration", UploadType = UploadType.Attachments, SourceFileName = "DOC1.pdf", FileStream = fs }; var result = docMigrationS3Client.UploadFileAsync(uploadFile).Result; Assert.IsNotNull(result); Assert.IsTrue(result.IsSuccessStatusCode); @@ -93,7 +93,7 @@ public void CreatePDF_Test() using Stream emaildocstream = docMigrationPDFStitcher.CreatePDFDocument("

THIS IS AN EMAIL HTML content

", "My email subject line!!!!", DateTime.Now.AddYears(-10).ToLongDateString(), "abinajik@gmail.com"); emaildocstream.Position = 0; var destinationfilename = string.Format("{0}.pdf", Guid.NewGuid().ToString()); - UploadFile uploadFile = new UploadFile() { AXISRequestID = "TEST-10001-12342", DestinationFileName = destinationfilename, S3BucketName = "test123-protected", SubFolderPath = "test123-protected/Abintest/unittestmigration", UploadType = UploadType.Attachments, SourceFileName = "DOC1.pdf", FileStream = emaildocstream }; + UploadFile uploadFile = new UploadFile() { AXISRequestID = "TEST-10001-12342", DestinationFileName = destinationfilename, S3BucketName = "test123-protected", SubFolderPath = "test123-protected/Abintest/unittestmigration", UploadType = UploadType.Attachments, SourceFileName = "DOC1.pdf", FileStream = emaildocstream }; var result = docMigrationS3Client.UploadFileAsync(uploadFile).Result; Assert.IsNotNull(result); Assert.IsTrue(result.IsSuccessStatusCode); @@ -115,10 +115,10 @@ public void MergeStreamwithFileAttachmentsTest() emaildocstream.Position = 0; - DocumentToMigrate[] pDFDocToMerges = new DocumentToMigrate[3]; - pDFDocToMerges[0] = new DocumentToMigrate() { PageFilePath = Path.Combine(getSourceFolder(), "DOCX1.pdf"), PageSequenceNumber = 3 }; - pDFDocToMerges[1] = new DocumentToMigrate() { PageFilePath = Path.Combine(getSourceFolder(), "cat1.pdf"), PageSequenceNumber = 2 }; - pDFDocToMerges[2] = new DocumentToMigrate() { FileStream = emaildocstream, PageSequenceNumber = 1, HasStreamForDocument = true }; + List pDFDocToMerges = new List(); + pDFDocToMerges.Add(new DocumentToMigrate() { PageFilePath = Path.Combine(getSourceFolder(), "DOCX1.pdf"), PageSequenceNumber = 3 }); + pDFDocToMerges.Add(new DocumentToMigrate() { PageFilePath = Path.Combine(getSourceFolder(), "cat1.pdf"), PageSequenceNumber = 2 }); + pDFDocToMerges.Add(new DocumentToMigrate() { FileStream = emaildocstream, PageSequenceNumber = 1, HasStreamForDocument = true }); using Stream fs = docMigrationPDFStitcher.MergePDFs(pDFDocToMerges); @@ -127,7 +127,7 @@ public void MergeStreamwithFileAttachmentsTest() fs.Position = 0; var destinationfilename = string.Format("{0}.pdf", Guid.NewGuid().ToString()); - UploadFile uploadFile = new UploadFile() { AXISRequestID = "TEST-10001-12342", DestinationFileName = destinationfilename, S3BucketName = "test123-protected", SubFolderPath = "test123-protected/Abintest/unittestmigration", UploadType = UploadType.Attachments, SourceFileName = "DOC1.pdf", FileStream = fs }; + UploadFile uploadFile = new UploadFile() { AXISRequestID = "TEST-10001-12342", DestinationFileName = destinationfilename, S3BucketName = "test123-protected", SubFolderPath = "test123-protected/Abintest/unittestmigration", UploadType = UploadType.Attachments, SourceFileName = "DOC1.pdf", FileStream = fs }; var result = docMigrationS3Client.UploadFileAsync(uploadFile).Result; Assert.IsNotNull(result); Assert.IsTrue(result.IsSuccessStatusCode); diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/DocMigrationPDFStitcher.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/DocMigrationPDFStitcher.cs index b22b2087c..0142a885f 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/DocMigrationPDFStitcher.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/DocMigrationPDFStitcher.cs @@ -23,7 +23,7 @@ public void Dispose() mergeddocstream.Dispose(); } - public Stream MergePDFs(DocumentToMigrate[] pdfpages) + public Stream MergePDFs(List pdfpages) { var _pdfpages = pdfpages.OrderBy(p => p.PageSequenceNumber).ToArray(); using (PdfDocument pdfdocument = new PdfDocument()) diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/FillePathUtils.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/FillePathUtils.cs index c9958b345..9ed84c456 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/FillePathUtils.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/FillePathUtils.cs @@ -10,13 +10,13 @@ public static class FilePathUtils if (!string.IsNullOrEmpty(delimitedstring)) { string[] files = delimitedstring.Split('|'); - + result = new List(); if (files != null && files.Length > 0) { - result = new List(); + foreach (string fileinfodelimited in files) { - string[] singlefileinfo = fileinfodelimited.Split('#'); + string[] singlefileinfo = fileinfodelimited.Split('*'); if (singlefileinfo != null && singlefileinfo.Length > 0) { result.Add(new AXISFIle() { FileName = singlefileinfo[1], FilePathOnServer = singlefileinfo[0], FileExtension = singlefileinfo[1].Split('.')[1] }); @@ -24,6 +24,14 @@ public static class FilePathUtils } } + else if(delimitedstring.IndexOf('*') >0 ) + { + string[] singlefileinfo = delimitedstring.Split('*'); + if (singlefileinfo != null && singlefileinfo.Length > 0) + { + result.Add(new AXISFIle() { FileName = singlefileinfo[1], FilePathOnServer = singlefileinfo[0], FileExtension = singlefileinfo[1].Split('.')[1] }); + } + } } From 83ccce71a30d94e76ad31dd24a81c522b6e621b8 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Fri, 25 Aug 2023 09:59:37 -0700 Subject: [PATCH 072/234] #4141 Updates on migration with Attachmetns --- .../CorrespondenceLogMigration.cs | 5 ++-- .../DocMigrationPDFStitcher.cs | 25 ++++++++++++++++--- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/CorrespondenceLogMigration.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/CorrespondenceLogMigration.cs index ad529cc55..4bfc869ec 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/CorrespondenceLogMigration.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/CorrespondenceLogMigration.cs @@ -67,11 +67,12 @@ public async Task RunMigration() { //Create EMAIL MSG PDF and sticth with the attachment documents List documentToMigrateEmail = new List(); - using var emaildocstream = docMigrationPDFStitcher.CreatePDFDocument(attachment.EmailContent, attachment.EmailSubject, attachment.EmailDate, attachment.EmailTo); + var attachmentfiles = FilePathUtils.GetFileDetailsFromdelimitedstring(attachment.EmailAttachmentDelimitedString); + using var emaildocstream = docMigrationPDFStitcher.CreatePDFDocument(attachment.EmailContent, attachment.EmailSubject, attachment.EmailDate, attachment.EmailTo, attachmentfiles); emaildocstream.Position = 0; documentToMigrateEmail.Add(new DocumentToMigrate() { FileStream = emaildocstream, DocumentType = Models.AXISSource.DocumentTypeFromAXIS.CorrespondenceLog, AXISRequestNumber = attachment.AXISRequestNumber.ToUpper(), PageSequenceNumber = 1, HasStreamForDocument = true }); - var attachmentfiles = FilePathUtils.GetFileDetailsFromdelimitedstring(attachment.EmailAttachmentDelimitedString); + if (attachmentfiles != null) { foreach (var file in attachmentfiles) diff --git a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/DocMigrationPDFStitcher.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/DocMigrationPDFStitcher.cs index 0142a885f..8ab03f55f 100644 --- a/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/DocMigrationPDFStitcher.cs +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/DocMigrationPDFStitcher.cs @@ -1,4 +1,5 @@ -using FOIMOD.CFD.DocMigration.Models.Document; +using FOIMOD.CFD.DocMigration.Models; +using FOIMOD.CFD.DocMigration.Models.Document; using PdfSharpCore; using PdfSharpCore.Pdf; using PdfSharpCore.Pdf.IO; @@ -47,11 +48,21 @@ public Stream MergePDFs(List pdfpages) - public Stream CreatePDFDocument(string emailcontent, string emailsubject, string emaildate, string emailTo) + public Stream CreatePDFDocument(string emailcontent, string emailsubject, string emaildate, string emailTo,List attachementfiles) { try { + string attachmentlist = string.Empty; + if (attachementfiles!=null) + { + attachmentlist += "
    "; + foreach (var attachment in attachementfiles) + { + attachmentlist += string.Format("
  • {0}
  • ", attachment.FileName); + } + attachmentlist += "
"; + } var htmlofpdf = string.Format(@" @@ -67,12 +78,18 @@ public Stream CreatePDFDocument(string emailcontent, string emailsubject, string + + + + - + -
Subject {2}
Attachments + {3} +
Message{3}{4}
", emailTo, emaildate, emailsubject, emailcontent); + ", emailTo, emaildate, emailsubject, attachmentlist,emailcontent); using (PdfDocument pdfdocument = new PdfDocument()) { From b396e691412fe4c33ee809e8f2796b55cb1e649e Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Fri, 25 Aug 2023 13:35:21 -0700 Subject: [PATCH 073/234] Ticket revised. Added a more complex and specific sql query to gather all records based on division id. If this query returns a list with a length more than 0 -> we cannot delete the division as the division is currently tagged to a record --- .../request_api/models/FOIRequestRecords.py | 27 ++++++++++--------- .../services/programareadivisionservice.py | 10 +++---- .../request_api/services/recordservice.py | 4 +-- 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/request-management-api/request_api/models/FOIRequestRecords.py b/request-management-api/request_api/models/FOIRequestRecords.py index 34a7385d6..444a3dc12 100644 --- a/request-management-api/request_api/models/FOIRequestRecords.py +++ b/request-management-api/request_api/models/FOIRequestRecords.py @@ -128,19 +128,20 @@ def getrecordsbyid(cls, recordids): return records @classmethod - def get_all_records(cls): - records_schema = FOIRequestRecordSchema(many=True) - # sql = """select - # (to_json(attributes)->>'divisions\')::json as divisions - # from public. "FOIRequestRecords" - # """ - # search_value = {"divisions": [{"divisionid": divisionid}]} - # query = db.session.query(FOIRequestRecord).filter(FOIRequestRecord.isactive==True).filter(FOIRequestRecord.attributes.contains(search_value)).all() - # query = db.session.query(FOIRequestRecord).filter(FOIRequestRecord.isactive==True).filter(FOIRequestRecord.attributes['divisions'].astext.ilike(divisionid)).all() - # DocumentAttributes.attributes['isattachment'].astext.cast(db.Boolean).label('isattachment') - query = db.session.query(FOIRequestRecord).filter_by(isactive=True).all() - return records_schema.dump(query) - + def get_all_records_by_divisionid(cls, divisionid): + records = [] + try: + sql = """SELECT * FROM public."FOIRequestRecords" WHERE cast(attributes::json -> 'divisions' as text) like '%{"divisionid": """+ divisionid +"""}%' and isactive = 'true'""" + result = db.session.execute(text(sql)) + for row in result: + records.append({"recordid": row["recordid"], "foirequestid": row["foirequestid"], "ministryrequestid": row["ministryrequestid"], "filename": row["filename"], "s3uripath": row["s3uripath"], "attributes": row["attributes"], "isactive": row["isactive"], "createdby": row["createdby"], "created_at": row["created_at"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return records + @classmethod def replace(cls,replacingrecordid,records): replacingrecord = db.session.query(FOIRequestRecord).filter_by(recordid=replacingrecordid).order_by(FOIRequestRecord.version.desc()).first() diff --git a/request-management-api/request_api/services/programareadivisionservice.py b/request-management-api/request_api/services/programareadivisionservice.py index c459e0e3b..913f6d7d0 100644 --- a/request-management-api/request_api/services/programareadivisionservice.py +++ b/request-management-api/request_api/services/programareadivisionservice.py @@ -1,7 +1,6 @@ from request_api.models.ProgramAreaDivisions import ProgramAreaDivision from request_api.services.programareaservice import programareaservice from request_api.services.recordservice import recordservice -import json from request_api.models.default_method_result import DefaultMethodResult @@ -27,12 +26,9 @@ def disableprogramareadivision(self, divisionid,userid): """ Disable a program area division """ # Validation to see if division id exists in any records. If so deletion cannot be completed. - records = recordservice().get_all_records() - for record in records: - attributes = json.loads(record['attributes']) - if any(int(divisionid) in division.values() for division in attributes["divisions"]): - return DefaultMethodResult(False,'Division is currently tagged to various records and cannot be disabled', divisionid) - + records = recordservice().get_all_records_by_divisionid(divisionid) + if len(records) > 0: + return DefaultMethodResult(False,'Division is currently tagged to various records and cannot be disabled', divisionid) return ProgramAreaDivision.disableprogramareadivision(divisionid,userid) def __prepareprogramareas(self, data): diff --git a/request-management-api/request_api/services/recordservice.py b/request-management-api/request_api/services/recordservice.py index 7a7111cf9..eda12999a 100644 --- a/request-management-api/request_api/services/recordservice.py +++ b/request-management-api/request_api/services/recordservice.py @@ -38,8 +38,8 @@ def create(self, requestid, ministryrequestid, recordschema, userid): def fetch(self, requestid, ministryrequestid): return recordservicegetter().fetch(requestid, ministryrequestid) - def get_all_records(self): - return FOIRequestRecord.get_all_records() + def get_all_records_by_divisionid(self, divisionid): + return FOIRequestRecord.get_all_records_by_divisionid(divisionid) def update(self, requestid, ministryrequestid, requestdata, userid): newrecords = [] From 40a1109a7a7f8c2ac2e9b0a34fd20188b5a6598e Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Fri, 25 Aug 2023 15:32:12 -0700 Subject: [PATCH 074/234] #4140 ETL packages update --- .../ApplicantRequestMappings.dtsx | 33 +- .../FOIApplicants.dtsx | 660 ++++++++ .../FOIMOD.CFD.ETL.DataMigration.dtproj | 92 +- .../FOIMinistryRequestSubjectCodes.dtsx | 232 +++ .../FOIMinistryRequests.dtsx | 126 +- .../FOIRequestContactInformation.dtsx | 1499 +++++++++++++++++ .../FOIRequestExtensions.dtsx | 1110 ++++++++++++ .../FOIRequests.dtsx | 11 +- .../Project.params | 90 +- 9 files changed, 3767 insertions(+), 86 deletions(-) create mode 100644 datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIApplicants.dtsx create mode 100644 datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMinistryRequestSubjectCodes.dtsx create mode 100644 datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIRequestContactInformation.dtsx create mode 100644 datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIRequestExtensions.dtsx diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/ApplicantRequestMappings.dtsx b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/ApplicantRequestMappings.dtsx index f07cecc1f..9f0a03d27 100644 --- a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/ApplicantRequestMappings.dtsx +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/ApplicantRequestMappings.dtsx @@ -11,8 +11,8 @@ DTS:LocaleID="1033" DTS:ObjectName="ApplicantRequestMappings" DTS:PackageType="5" - DTS:VersionBuild="63" - DTS:VersionGUID="{96AA768E-449C-42D6-A3B6-B379236D045F}"> + DTS:VersionBuild="64" + DTS:VersionGUID="{E74162C7-CB89-4371-98DA-C1E08F361BDA}"> 8 @@ -57,11 +57,12 @@ DTS:TaskContact="Execute SQL Task; Microsoft Corporation; SQL Server 2019; © 2019 Microsoft Corporation; All Rights Reserved;http://www.microsoft.com/sql/support/default.asp;1" DTS:ThreadHint="0"> + REPLACE( @[$Project::foirequestsquery] , "@requestids", @[$Project::requestidstomigrate] ) + DTS:ThreadHint="1"> + DTS:ThreadHint="0"> + DTS:ThreadHint="1"> + DTS:ThreadHint="2"> - + + - diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIApplicants.dtsx b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIApplicants.dtsx new file mode 100644 index 000000000..eb4ed7987 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIApplicants.dtsx @@ -0,0 +1,660 @@ + + + 8 + + + SELECT firstname,lastname,middleName,requesterid,CONVERT(varchar(MAX),'01-01-1900',120) as birthDate,businessName, GETDATE() as createdAt, 'cfdmigration' as createdBy,STRING_AGG(CAST(requestid as nvarchar(max)),', ') as requests ,MainApplicant FROM ( SELECT requesters.vcEmailID as email, requesters.iRequesterID as requesterid, requesters.vcFirstName as firstName, requesters.vcLastName as lastName, requesters.vcMiddleName as middleName, requestorfields.CUSTOMFIELD35 as birthDate, requesters.vcCompany as businessName, requests.vcVisibleRequestID as requestid, 1 as MainApplicant FROM tblRequests requests WITH (NOLOCK) LEFT OUTER JOIN EC_OFFICE office WITH (NOLOCK) ON requests.tiOfficeID = office.OFFICE_ID LEFT OUTER JOIN tblRequesters requesters WITH (NOLOCK) ON requests.iRequesterID = requesters.iRequesterID LEFT OUTER JOIN dbo.TBLREQUESTERCUSTOMFIELDS requestorfields WITH (NOLOCK) ON requesters.iRequesterID = requestorfields.IREQUESTERID WHERE office.OFFICE_CODE = 'CFD' AND requests.vcRequestStatus NOT IN ('Closed') AND requests.vcVisibleRequestID IN ('CFD-2021-12651','CFD-2023-30193') UNION ALL SELECT onbehalf.vcEmailID as email, onbehalf.iRequesterID as requesterid, onbehalf.vcFirstName as firstName, onbehalf.vcLastName as lastName, onbehalf.vcMiddleName as middleName, requestorfields.CUSTOMFIELD35 as birthDate, onbehalf.vcCompany as businessName, requests.vcVisibleRequestID as requestid, 0 as MainApplicant FROM tblRequests requests WITH (NOLOCK) LEFT OUTER JOIN EC_OFFICE office WITH (NOLOCK) ON requests.tiOfficeID = office.OFFICE_ID LEFT OUTER JOIN tblRequesters onbehalf WITH (NOLOCK) ON requests.iOnBehalfOf = onbehalf.iRequesterID LEFT OUTER JOIN dbo.TBLREQUESTERCUSTOMFIELDS requestorfields WITH (NOLOCK) ON onbehalf.iRequesterID = requestorfields.IREQUESTERID WHERE office.OFFICE_CODE = 'CFD' AND requests.vcRequestStatus NOT IN ('Closed') AND requests.vcVisibleRequestID IN ('CFD-2021-12651','CFD-2023-30193') and onbehalf.vcFirstName is NOT NULL ) AS T GROUP BY T.email,T.requesterid,T.firstname,T.lastname,T.middleName,T.requesterid,T.birthDate,T.businessName,T.MainApplicant ORDER BY T.firstname ASC + + + + + + + + + + + "public"."FOIRequestApplicants" + 1000 + 1000 + 32768 + 0 + 1252 + 0 + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + User::applicantqueryparam + 1252 + false + 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataSourceViewID + + + TableInfoObjectType + Table + + + + + + + DataSourceViewID + + + +]]> + \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj index 3d2190ebe..b1b17101b 100644 --- a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj @@ -26,15 +26,17 @@ WALTZ - AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAFieOUvPkcUqQDxLl/khbKQAAAAACAAAAAAADZgAAwAAAABAAAAAeEJHk31mTik8CecOG+qs5AAAAAASAAACgAAAAEAAAAPdbO3uxHeyX6kN4B5Ovxj6IAAAAs8dcZpdX8IiWLKgAN/8PMuAEeatRhYmtYerYqDo8oALpSkxO6fVastf4BNhijBrKJgziwNUFlp2d2tgYAs+WyeciTaBGOowLNdYEJPDqMEANf8V+2Zs4bM5CMUgTMPblDn2RmiXy0FwP0PUPz3wqn32xfWdpc4oIM4scyh7jjDnL56f4Q+VUhRQAAACuTG5YZxR0lLGrnaF2nOQ8xqqxBA== + AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAAxNy8IYXQUikuyRHfwqVBQAAAAACAAAAAAADZgAAwAAAABAAAAA0WsvjZzR76ItQUOsvjFPxAAAAAASAAACgAAAAEAAAAB6WqM0e3bF/ytcNb+00Vj+IAAAAbsMnsLvWvIvySgXvXtMoEl3R8G67DLCHIBL3kR/GH2MUqZpdgHf+GA623nwneseHO7/k1WEwNtQKoj3RJSAzVR2kKCBf/WuRo7OE7YHbUgHk4tAJ5+pg9H5AVaFycQMdmyhXf3aH5TbvKirS4dBijjsR1DVko71vw8v5SeKsbxBGdvk7myw8hBQAAABx9sgy9GQ3TLvYi2NH3xvLCs5wfg== 1 - - + + + + @@ -293,16 +295,16 @@ - + - {A8051341-9A9E-4D11-BEB5-AF0259A25F9F} - Package + {6B1FFF75-7F3F-438B-9C8E-FB5B50CBB326} + FOIRequests 1 0 - 3 + 36 - {7E49DCB1-E15B-4DF3-A59B-DC1F6ADA1C42} + {2215EF75-9178-4019-A65C-548BCB848EB3} 8 @@ -310,16 +312,16 @@ - + - {6B1FFF75-7F3F-438B-9C8E-FB5B50CBB326} - FOIRequests + {2A065F0D-C74A-4625-A968-B48E416F3285} + Package1 1 0 - 34 + 49 - {535D8DAE-8E33-4885-9C83-8FA17FFC4A15} + {959785B0-CF2F-46BD-B18F-F60A275D3B69} 8 @@ -327,16 +329,16 @@ - + - {2A065F0D-C74A-4625-A968-B48E416F3285} - Package1 + {6E7CF625-11EC-4619-ACB0-07C38F8367D2} + ApplicantRequestMappings 1 0 - 12 + 64 - {7AFB3826-7143-4167-8101-43C21A36278A} + {E74162C7-CB89-4371-98DA-C1E08F361BDA} 8 @@ -344,16 +346,16 @@ - + - {39F3640B-BFA0-4D76-8137-EC8A98C6675A} - CFDApplicantsMigration + {F6BAB303-5655-462B-B60C-AC54FEE248D6} + FOIRequestContactInformation 1 0 - 12 + 71 - {C771D95D-EF7A-4E67-A35D-A350817C33E2} + {D8F0E47C-87C0-4352-B380-D36429864036} 8 @@ -361,16 +363,50 @@ - + - {6E7CF625-11EC-4619-ACB0-07C38F8367D2} - ApplicantRequestMappings + {E6F29F2B-5CB1-454D-94EE-7BC34A459E92} + FOIApplicants + 1 + 0 + 8 + + + {47713682-1301-458C-9DB7-6F85D70A1E2F} + 8 + + + 1 + + + + + + {D96776B2-BF23-4689-A2D2-9CCA44C5DBFC} + FOIRequestExtensions + 1 + 0 + 29 + + + {8B50757C-EDCD-463C-B927-303DF6880258} + 8 + + + 1 + + + + + + {15AF90B9-2E9B-4CE0-89C9-DB31763C6BDF} + FOIMinistryRequestSubjectCodes 1 0 - 20 + 15 - {CB9E68C9-3BE7-44FD-B3B8-A1A693CE13C0} + {FECD18DF-8B81-43E0-812E-6647F05F22D6} 8 diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMinistryRequestSubjectCodes.dtsx b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMinistryRequestSubjectCodes.dtsx new file mode 100644 index 000000000..e92f7f5a1 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMinistryRequestSubjectCodes.dtsx @@ -0,0 +1,232 @@ + + + 8 + + + + + + + + + + + + + + + + + + + + + REPLACE( @[$Project::foiministryrequestsubjectcodes] , "@requestids", @[$Project::requestidstomigrate] ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMinistryRequests.dtsx b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMinistryRequests.dtsx index 1356d95b4..6c371fb3b 100644 --- a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMinistryRequests.dtsx +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMinistryRequests.dtsx @@ -11,8 +11,8 @@ DTS:LocaleID="1033" DTS:ObjectName="Package1" DTS:PackageType="5" - DTS:VersionBuild="12" - DTS:VersionGUID="{7AFB3826-7143-4167-8101-43C21A36278A}"> + DTS:VersionBuild="49" + DTS:VersionGUID="{959785B0-CF2F-46BD-B18F-F60A275D3B69}"> 8 @@ -84,6 +84,16 @@ DTS:DataType="8" xml:space="preserve"> + + + + + + + REPLACE(@[$Project::foiministryrequestquery], "@requestids", @[$Project::requestidstomigrate] ) + SQLTask:SqlStatementSource="INSERT INTO public."FOIMinistryRequests"( version, isactive, filenumber, description, recordsearchfromdate, recordsearchtodate, startdate, duedate, assignedto, created_at, updated_at, createdby, updatedby, programareaid, requeststatusid, foirequest_id, foirequestversion_id, cfrduedate, axisrequestid, requestpagecount, linkedrequests, migrationreference,assignedgroup,assignedministrygroup) VALUES ( 1, False, ?, ?, to_timestamp(?,'YYYY-MM-DD hh24:mi:ss'), to_timestamp(?,'YYYY-MM-DD hh24:mi:ss'), to_timestamp(?,'YYYY-MM-DD hh24:mi:ss'), to_timestamp(?,'YYYY-MM-DD hh24:mi:ss'), NULL, NOW(), NOW(), 'cfdmigration', 'cfdmigration', (SELECT programareaid FROM public."ProgramAreas" PA WHERE PA.iaocode='CFD' and PA.isactive=True), (SELECT requeststatusid FROM public."FOIRequestStatuses" RS WHERE RS.name='Open' and RS.isactive=True), (SELECT foirequestid FROM public."FOIRequests" R WHERE R.migrationreference=? ORDER BY R.created_at DESC LIMIT 1), (SELECT version FROM public."FOIRequests" R WHERE R.migrationreference=? ORDER BY R.created_at DESC LIMIT 1), to_timestamp(?,'YYYY-MM-DD hh24:mi:ss'), ?, ?, ?::json, ?,'MCFD Personals Team','MCF Ministry Team');" xmlns:SQLTask="www.microsoft.com/sqlserver/dts/tasks/sqltask"> + @@ -263,7 +291,7 @@ + SQLTask:SqlStatementSource="INSERT INTO public."FOIMinistryRequests"( foiministryrequestid, version, isactive, filenumber, description, recordsearchfromdate, recordsearchtodate, startdate, duedate, assignedto, created_at, updated_at, createdby, updatedby, programareaid, requeststatusid, foirequest_id, foirequestversion_id, cfrduedate, axisrequestid, requestpagecount, linkedrequests, migrationreference,assignedgroup,assignedministrygroup) VALUES ( (SELECT foiministryrequestid FROM public."FOIMinistryRequests" WHERE axisrequestid = ? LIMIT 1) , 2, True, ?, ?, to_timestamp(?,'YYYY-MM-DD hh24:mi:ss'), to_timestamp(?,'YYYY-MM-DD hh24:mi:ss'), to_timestamp(?,'YYYY-MM-DD hh24:mi:ss'), to_timestamp(?,'YYYY-MM-DD hh24:mi:ss'), NULL, NOW(), NOW(), 'cfdmigration', 'cfdmigration', (SELECT programareaid FROM public."ProgramAreas" PA WHERE PA.iaocode='CFD' and PA.isactive=True), (SELECT requeststatusid FROM public."FOIRequestStatuses" RS WHERE RS.name= (SELECT "stateonFOI" FROM public."AXISMigrationMapper" WHERE "stateonAXIS"=? LIMIT 1) and RS.isactive=True), (SELECT foirequestid FROM public."FOIRequests" R WHERE R.migrationreference=? ORDER BY R.created_at DESC LIMIT 1), (SELECT version FROM public."FOIRequests" R WHERE R.migrationreference=? ORDER BY R.created_at DESC LIMIT 1), to_timestamp(?,'YYYY-MM-DD hh24:mi:ss'), ?, ?, ?::json, ?,'MCFD Personals Team','MCF Ministry Team');" xmlns:SQLTask="www.microsoft.com/sqlserver/dts/tasks/sqltask"> + + + @@ -353,52 +399,64 @@ + + diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIRequestContactInformation.dtsx b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIRequestContactInformation.dtsx new file mode 100644 index 000000000..2d2bd8da5 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIRequestContactInformation.dtsx @@ -0,0 +1,1499 @@ + + + 8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + REPLACE( @[$Project::foirequestcontactinformation] , "@requestids", @[$Project::requestidstomigrate] ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIRequestExtensions.dtsx b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIRequestExtensions.dtsx new file mode 100644 index 000000000..85fecdb56 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIRequestExtensions.dtsx @@ -0,0 +1,1110 @@ + + + 8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + REPLACE( @[$Project::foirequestextensionsquery] , "@requestids", @[$Project::requestidstomigrate] ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + +]]> + +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +[assembly: global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope="member", Target="ST_251772c76dbb4c2db05ebd5d32199133.Properties.Settings.get_Default():ST_251772c76dbb4c2db05ebd5d32199133.Properties.Sett" + + "ings")] + +namespace ST_251772c76dbb4c2db05ebd5d32199133.Properties { + + + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + private static Settings defaultInstance = new Settings(); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +}]]> + + /// ScriptMain is the entry point class of the script. Do not change the name, attributes, + /// or parent of this class. + /// + [Microsoft.SqlServer.Dts.Tasks.ScriptTask.SSISScriptTaskEntryPointAttribute] + public partial class ScriptMain : Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase + { + #region Help: Using Integration Services variables and parameters in a script + /* To use a variable in this script, first ensure that the variable has been added to + * either the list contained in the ReadOnlyVariables property or the list contained in + * the ReadWriteVariables property of this script task, according to whether or not your + * code needs to write to the variable. To add the variable, save this script, close this instance of + * Visual Studio, and update the ReadOnlyVariables and + * ReadWriteVariables properties in the Script Transformation Editor window. + * To use a parameter in this script, follow the same steps. Parameters are always read-only. + * + * Example of reading from a variable: + * DateTime startTime = (DateTime) Dts.Variables["System::StartTime"].Value; + * + * Example of writing to a variable: + * Dts.Variables["User::myStringVariable"].Value = "new value"; + * + * Example of reading from a package parameter: + * int batchId = (int) Dts.Variables["$Package::batchId"].Value; + * + * Example of reading from a project parameter: + * int batchId = (int) Dts.Variables["$Project::batchId"].Value; + * + * Example of reading from a sensitive project parameter: + * int batchId = (int) Dts.Variables["$Project::batchId"].GetSensitiveValue(); + * */ + + #endregion + + #region Help: Firing Integration Services events from a script + /* This script task can fire events for logging purposes. + * + * Example of firing an error event: + * Dts.Events.FireError(18, "Process Values", "Bad value", "", 0); + * + * Example of firing an information event: + * Dts.Events.FireInformation(3, "Process Values", "Processing has started", "", 0, ref fireAgain) + * + * Example of firing a warning event: + * Dts.Events.FireWarning(14, "Process Values", "No values received for input", "", 0); + * */ + #endregion + + #region Help: Using Integration Services connection managers in a script + /* Some types of connection managers can be used in this script task. See the topic + * "Working with Connection Managers Programatically" for details. + * + * Example of using an ADO.Net connection manager: + * object rawConnection = Dts.Connections["Sales DB"].AcquireConnection(Dts.Transaction); + * SqlConnection myADONETConnection = (SqlConnection)rawConnection; + * //Use the connection in some code here, then release the connection + * Dts.Connections["Sales DB"].ReleaseConnection(rawConnection); + * + * Example of using a File connection manager + * object rawConnection = Dts.Connections["Prices.zip"].AcquireConnection(Dts.Transaction); + * string filePath = (string)rawConnection; + * //Use the connection in some code here, then release the connection + * Dts.Connections["Prices.zip"].ReleaseConnection(rawConnection); + * */ + #endregion + + + /// + /// This method is called when this script task executes in the control flow. + /// Before returning from this method, set the value of Dts.TaskResult to indicate success or failure. + /// To open Help, press F1. + /// + public void Main() + { + // TODO: Add your code here + // Access the SSIS variable + string reason = Dts.Variables["User::reason"].Value.ToString(); + // Apply conditional checks and assign new values + + switch (reason) + { + case "OIPC - Applicant Consent": + Dts.Variables["User::reason"].Value = "6"; + break; + + case "OIPC - Consultation": + Dts.Variables["User::reason"].Value = "7"; + break; + + case "OIPC - Fair and Reasonable to do so": + Dts.Variables["User::reason"].Value = "11"; + break; + + case "OIPC - Further Detail from Applicant Needed": + Dts.Variables["User::reason"].Value = "8"; + break; + + case "OIPC - Large Volume and/or Volume of Search": + Dts.Variables["User::reason"].Value = "9"; + break; + + case "OIPC - Large Volume and/or Volume of Search and Consultation": + Dts.Variables["User::reason"].Value = "10"; + break; + + case "PB - Applicant Consent": + Dts.Variables["User::reason"].Value = "1"; + break; + + case "PB - Consultation": + Dts.Variables["User::reason"].Value = "2"; + break; + + case "PB - Further Detail from Applicant Needed": + Dts.Variables["User::reason"].Value = "3"; + break; + + case "PB - Large Volume and/or Volume of Search": + Dts.Variables["User::reason"].Value = "4"; + break; + + case "PB - Large Volume and/or Volume of Search and Consultation": + Dts.Variables["User::reason"].Value = "5"; + break; + + default: + // If none of the conditions match, you can set a default value + Dts.Variables["User::reason"].Value = "0"; + break; + } + + // Access the SSIS variable + string status = Dts.Variables["User::status"].Value.ToString(); + + // Apply conditional checks and assign new values + switch (status) + { + case "N": + Dts.Variables["User::status"].Value = "1"; + break; + + case "A": + Dts.Variables["User::status"].Value = "2"; + break; + + case "D": + Dts.Variables["User::status"].Value = "3"; + break; + + default: + // If none of the conditions match, you can set a default value + Dts.Variables["User::status"].Value = "0"; + break; + } + + Dts.TaskResult = (int)ScriptResults.Success; + } + + #region ScriptResults declaration + /// + /// This enum provides a convenient shorthand within the scope of this class for setting the + /// result of the script. + /// + /// This code was generated automatically. + /// + enum ScriptResults + { + Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success, + Failure = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Failure + }; + #endregion + + } +}]]> + + + + msBuild + ST_251772c76dbb4c2db05ebd5d32199133 + ST_251772c76dbb4c2db05ebd5d32199133 + {BB0EE068-327D-42A4-81E2-BFAD0A3D7849} + + + + + + + + + + +]]> + + + + + {30D016F9-3734-4E33-A861-5E7D899E18F3};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Debug + AnyCPU + 8.0.30703 + 2.0 + {78c3f5a7-d913-4fc6-a916-5960b0a7b495} + Library + Properties + ST_251772c76dbb4c2db05ebd5d32199133 + ST_251772c76dbb4c2db05ebd5d32199133 + v4.7 + 512 + true + + + + true + full + false + .\bin\Debug\ + false + DEBUG;TRACE + prompt + 4 + + + + false + true + .\bin\Release\ + false + TRACE + prompt + 4 + + + + + + + + + + + + + + + Code + + + ResXFileCodeGenerator + Resources.Designer.cs + + + True + Resources.resx + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + + + Code + + + + + + + + + + + + + + SSIS_ST150 + + + + +]]> + + + + + + +]]> + +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +[assembly: global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope="member", Target="ST_251772c76dbb4c2db05ebd5d32199133.Properties.Resources.get_ResourceManager():System.Resources.Resou" + + "rceManager")] +[assembly: global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope="member", Target="ST_251772c76dbb4c2db05ebd5d32199133.Properties.Resources.get_Culture():System.Globalization.CultureIn" + + "fo")] +[assembly: global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope="member", Target="ST_251772c76dbb4c2db05ebd5d32199133.Properties.Resources.set_Culture(System.Globalization.CultureInfo" + + "):Void")] + +namespace ST_251772c76dbb4c2db05ebd5d32199133.Properties { + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if ((resourceMan == null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ST_251772c76dbb4c2db05ebd5d32199133.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +}]]> + 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 +QlNKQgEAAQAAAAAADAAAAHY0LjAuMzAzMTkAAAAABQBsAAAABAQAACN+AABwBAAAuAUAACNTdHJp +bmdzAAAAACgKAAAkBAAAI1VTAEwOAAAQAAAAI0dVSUQAAABcDgAAhAEAACNCbG9iAAAAAAAAAAIA +AAFXHaIBCQMAAAD6ATMAFgAAAQAAAB4AAAAGAAAABgAAAAoAAAACAAAAHwAAAAIAAAARAAAAAgAA +AAIAAAADAAAABAAAAAEAAAAEAAAAAQAAAAEAAAAAALsDAQAAAAAABgBEAosEBgDTAosEBgCQAXgE +DwD/BAAABgC9ATkEBgAnAjkEBgAIAjkEBgC6AjkEBgBkAjkEBgB9AjkEBgDUATkEBgDvAQsDCgCY +AmoDCgAPAWoDBgDzA+wDBgBzAXgEBgB8BewDBgBbBKsEBgBLBCQEDgBDAaUDDgCkAaUDDgArAQ8E +BgBYAYsECgCTA2oDEgBBBboAEgCNALoABgAnA+wDBgDaAOwDBgCWAOwDBgCiBTkEAAAAACUAAAAA +AAEAAQABABAABAQBADkAAQABAAAAEACyBA4FRQABAAMAAAEQAEsFDgVZAAMABwAAAQAALgAAAEUA +BAAKAAMBAABuBQAAPQAEAAsAEQD4A4EAEQD/AIUAEQB9AIkABgZsAI0AVoBeBZAAVoDfAJAAUCAA +AAAAhgAKBAYAAQCAJAAAAACGGGsEBgABAIgkAAAAAIMYawQGAAEAkCQAAAAAkwhXBJQAAQC8JAAA +AACTCOcAmQABAMMkAAAAAJMI8wCeAAEAyyQAAAAAlgiDBaQAAgDSJAAAAACGGGsEBgACANokAAAA +AJEYcQSpAAIA6CQAAAAAkwAuA60AAgAAAAEABQMAAAEAegUJAGsEAQARAGsEBgAZAGsECgApAGsE +EAAxAGsEEAA5AGsEEABBAGsEEABJAGsEEABRAGsEEABZAGsEEABhAGsEEABpAGsEBgCBAGsEBgCp +AGsEFQC5AGsEBgBxAGYFIQDBAD0FJgDJAOMDKwDRAPECMQCJACUDNQDZAKsFOQDRAPsCPwDBAI8F +AQBxAGsEBgCJAGsEBgDhAKgARADhAJ4FSwCRAGsEUACxAGsEBgDZAFQFXADZAEADYQAIABQAdwAI +ABgAfAApAHMA6QAuAAsAwQAuABMAygAuABsA6QAuACMA8gAuACsAGwEuADMAGwEuADsAGwEuAEMA +8gAuAEsAIQEuAFMAGwEuAFsAOAFDAGMAfABJAHMA6QBhAHsAfABjAGsAfACjAHsAfAAbAFcAAwAB +AAQAAwAAAFsEsgAAAAcBtwAAAIcFvAACAAQAAwACAAUABQABAAYABQACAAcABwAEgAAAAQAAAKYh +GzwAAAAAAAABAAAABAAAAAAAAAAAAAAAZQB0AAAAAAAPAAAAAAAAAAAAAABuAEsDAAAAAAQAAAAA +AAAAAAAAAGUA7AMAAAAADwAAAAAAAAAAAAAAbgBNAAAAAAAAAAAAAQAAALwEAAAGAAIAAAAAU1Rf +MjUxNzcyYzc2ZGJiNGMyZGIwNWViZDVkMzIxOTkxMzMAPE1vZHVsZT4APFByaXZhdGVJbXBsZW1l +bnRhdGlvbkRldGFpbHM+AE1pY3Jvc29mdC5TcWxTZXJ2ZXIuTWFuYWdlZERUUwB2YWx1ZV9fAG1z +Y29ybGliAGRlZmF1bHRJbnN0YW5jZQBWYXJpYWJsZQBSdW50aW1lVHlwZUhhbmRsZQBHZXRUeXBl +RnJvbUhhbmRsZQBNaWNyb3NvZnQuU3FsU2VydmVyLkR0cy5SdW50aW1lAFR5cGUARmFpbHVyZQBn +ZXRfQ3VsdHVyZQBzZXRfQ3VsdHVyZQByZXNvdXJjZUN1bHR1cmUAVlNUQVJUU2NyaXB0T2JqZWN0 +TW9kZWxCYXNlAEFwcGxpY2F0aW9uU2V0dGluZ3NCYXNlAEVkaXRvckJyb3dzYWJsZVN0YXRlAENv +bXBpbGVyR2VuZXJhdGVkQXR0cmlidXRlAERlYnVnZ2VyTm9uVXNlckNvZGVBdHRyaWJ1dGUARGVi +dWdnYWJsZUF0dHJpYnV0ZQBFZGl0b3JCcm93c2FibGVBdHRyaWJ1dGUAQXNzZW1ibHlUaXRsZUF0 +dHJpYnV0ZQBBc3NlbWJseVRyYWRlbWFya0F0dHJpYnV0ZQBUYXJnZXRGcmFtZXdvcmtBdHRyaWJ1 +dGUAQXNzZW1ibHlDb25maWd1cmF0aW9uQXR0cmlidXRlAEFzc2VtYmx5RGVzY3JpcHRpb25BdHRy +aWJ1dGUAQ29tcGlsYXRpb25SZWxheGF0aW9uc0F0dHJpYnV0ZQBBc3NlbWJseVByb2R1Y3RBdHRy +aWJ1dGUAQXNzZW1ibHlDb3B5cmlnaHRBdHRyaWJ1dGUAU1NJU1NjcmlwdFRhc2tFbnRyeVBvaW50 +QXR0cmlidXRlAEFzc2VtYmx5Q29tcGFueUF0dHJpYnV0ZQBSdW50aW1lQ29tcGF0aWJpbGl0eUF0 +dHJpYnV0ZQBnZXRfVmFsdWUAc2V0X1ZhbHVlAHZhbHVlAFN5c3RlbS5SdW50aW1lLlZlcnNpb25p +bmcAVG9TdHJpbmcAQ29tcHV0ZVN0cmluZ0hhc2gAZ2V0X0xlbmd0aABNaWNyb3NvZnQuU3FsU2Vy +dmVyLlNjcmlwdFRhc2sATWljcm9zb2Z0LlNxbFNlcnZlci5EdHMuVGFza3MuU2NyaXB0VGFzawBT +Y3JpcHRPYmplY3RNb2RlbABTeXN0ZW0uQ29tcG9uZW50TW9kZWwAU1RfMjUxNzcyYzc2ZGJiNGMy +ZGIwNWViZDVkMzIxOTkxMzMuZGxsAGdldF9JdGVtAFN5c3RlbQBFbnVtAHJlc291cmNlTWFuAFNj +cmlwdE1haW4AU3lzdGVtLkNvbmZpZ3VyYXRpb24AU3lzdGVtLkdsb2JhbGl6YXRpb24AU3lzdGVt +LlJlZmxlY3Rpb24AQ3VsdHVyZUluZm8AZ2V0X1Jlc291cmNlTWFuYWdlcgAuY3RvcgAuY2N0b3IA +U3lzdGVtLkRpYWdub3N0aWNzAFN5c3RlbS5SdW50aW1lLkNvbXBpbGVyU2VydmljZXMAU3lzdGVt +LlJlc291cmNlcwBTVF8yNTE3NzJjNzZkYmI0YzJkYjA1ZWJkNWQzMjE5OTEzMy5Qcm9wZXJ0aWVz +LlJlc291cmNlcy5yZXNvdXJjZXMARGVidWdnaW5nTW9kZXMAU1RfMjUxNzcyYzc2ZGJiNGMyZGIw +NWViZDVkMzIxOTkxMzMuUHJvcGVydGllcwBnZXRfVmFyaWFibGVzAFNldHRpbmdzAGdldF9DaGFy +cwBTdWNjZXNzAGdldF9EdHMAU2NyaXB0UmVzdWx0cwBPYmplY3QAZ2V0X0RlZmF1bHQAc2V0X1Rh +c2tSZXN1bHQAZ2V0X0Fzc2VtYmx5AG9wX0VxdWFsaXR5AAAAGVUAcwBlAHIAOgA6AHIAZQBhAHMA +bwBuAAAxTwBJAFAAQwAgAC0AIABBAHAAcABsAGkAYwBhAG4AdAAgAEMAbwBuAHMAZQBuAHQAASdP +AEkAUABDACAALQAgAEMAbwBuAHMAdQBsAHQAYQB0AGkAbwBuAAFHTwBJAFAAQwAgAC0AIABGAGEA +aQByACAAYQBuAGQAIABSAGUAYQBzAG8AbgBhAGIAbABlACAAdABvACAAZABvACAAcwBvAAFXTwBJ +AFAAQwAgAC0AIABGAHUAcgB0AGgAZQByACAARABlAHQAYQBpAGwAIABmAHIAbwBtACAAQQBwAHAA +bABpAGMAYQBuAHQAIABOAGUAZQBkAGUAZAABV08ASQBQAEMAIAAtACAATABhAHIAZwBlACAAVgBv +AGwAdQBtAGUAIABhAG4AZAAvAG8AcgAgAFYAbwBsAHUAbQBlACAAbwBmACAAUwBlAGEAcgBjAGgA +AXlPAEkAUABDACAALQAgAEwAYQByAGcAZQAgAFYAbwBsAHUAbQBlACAAYQBuAGQALwBvAHIAIABW +AG8AbAB1AG0AZQAgAG8AZgAgAFMAZQBhAHIAYwBoACAAYQBuAGQAIABDAG8AbgBzAHUAbAB0AGEA +dABpAG8AbgABLVAAQgAgAC0AIABBAHAAcABsAGkAYwBhAG4AdAAgAEMAbwBuAHMAZQBuAHQAASNQ +AEIAIAAtACAAQwBvAG4AcwB1AGwAdABhAHQAaQBvAG4AAVNQAEIAIAAtACAARgB1AHIAdABoAGUA +cgAgAEQAZQB0AGEAaQBsACAAZgByAG8AbQAgAEEAcABwAGwAaQBjAGEAbgB0ACAATgBlAGUAZABl +AGQAAVNQAEIAIAAtACAATABhAHIAZwBlACAAVgBvAGwAdQBtAGUAIABhAG4AZAAvAG8AcgAgAFYA +bwBsAHUAbQBlACAAbwBmACAAUwBlAGEAcgBjAGgAAXVQAEIAIAAtACAATABhAHIAZwBlACAAVgBv +AGwAdQBtAGUAIABhAG4AZAAvAG8AcgAgAFYAbwBsAHUAbQBlACAAbwBmACAAUwBlAGEAcgBjAGgA +IABhAG4AZAAgAEMAbwBuAHMAdQBsAHQAYQB0AGkAbwBuAAEDNgAAAzcAAAUxADEAAAM4AAADOQAA +BTEAMAAAAzEAAAMyAAADMwAAAzQAAAM1AAADMAAAGVUAcwBlAHIAOgA6AHMAdABhAHQAdQBzAAAD +TgAAA0EAAANEAABxUwBUAF8AMgA1ADEANwA3ADIAYwA3ADYAZABiAGIANABjADIAZABiADAANQBl +AGIAZAA1AGQAMwAyADEAOQA5ADEAMwAzAC4AUAByAG8AcABlAHIAdABpAGUAcwAuAFIAZQBzAG8A +dQByAGMAZQBzAAAAOwZzzwYld0KcWSYggKlkZgAEIAEBCAMgAAEFIAEBEREEIAEBDgUgAQERUQUH +Aw4OCQQgABJhBCAAEmUFIAESaRwDIAAcAyAADgUAAgIODgQgAQEcBgABEnERdQQgABJ5BiACAQ4S +eQQHAgkIBCABAwgDIAAICLd6XFYZNOCJCImEXc2AgMyRBAAAAAAEAQAAAAMGEkkDBhJNAwYSEAIG +CAMGERgEAAASSQQAABJNBQABARJNBAAAEhADAAABBAABCQ4ECAASSQQIABJNBAgAEhAIAQAIAAAA +AAAeAQABAFQCFldyYXBOb25FeGNlcHRpb25UaHJvd3MBCAEAAgAAAAAAKAEAI1NUXzI1MTc3MmM3 +NmRiYjRjMmRiMDVlYmQ1ZDMyMTk5MTMzAAAFAQAAAAAWAQARQ29weXJpZ2h0IEAgIDIwMjMAAEkB +ABouTkVURnJhbWV3b3JrLFZlcnNpb249djQuNwEAVA4URnJhbWV3b3JrRGlzcGxheU5hbWUSLk5F +VCBGcmFtZXdvcmsgNC43AAC0AAAAzsrvvgEAAACRAAAAbFN5c3RlbS5SZXNvdXJjZXMuUmVzb3Vy +Y2VSZWFkZXIsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVi +bGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OSNTeXN0ZW0uUmVzb3VyY2VzLlJ1bnRpbWVSZXNv +dXJjZVNldAIAAAAAAAAAAAAAAFBBRFBBRFC0AAAA4DUAAAAAAAAAAAAA+jUAAAAgAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAOw1AAAAAAAAAAAAAAAAX0NvckRsbE1haW4AbXNjb3JlZS5kbGwAAAAAAP8l +ACAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAEAEAAAABgAAIAAAAAAAAAAAAAAAAAAAAEAAQAAADAAAIAAAAAAAAAAAAAAAAAAAAEAAAAAAEgA +AABYQAAAGgQAAAAAAAAAAAAAGgQ0AAAAVgBTAF8AVgBFAFIAUwBJAE8ATgBfAEkATgBGAE8AAAAA +AL0E7/4AAAEAAAABABs8piEAAAEAGzymIT8AAAAAAAAABAAAAAIAAAAAAAAAAAAAAAAAAABEAAAA +AQBWAGEAcgBGAGkAbABlAEkAbgBmAG8AAAAAACQABAAAAFQAcgBhAG4AcwBsAGEAdABpAG8AbgAA +AAAAAACwBHoDAAABAFMAdAByAGkAbgBnAEYAaQBsAGUASQBuAGYAbwAAAFYDAAABADAAMAAwADAA +MAA0AGIAMAAAABoAAQABAEMAbwBtAG0AZQBuAHQAcwAAAAAAAAAiAAEAAQBDAG8AbQBwAGEAbgB5 +AE4AYQBtAGUAAAAAAAAAAABwACQAAQBGAGkAbABlAEQAZQBzAGMAcgBpAHAAdABpAG8AbgAAAAAA +UwBUAF8AMgA1ADEANwA3ADIAYwA3ADYAZABiAGIANABjADIAZABiADAANQBlAGIAZAA1AGQAMwAy +ADEAOQA5ADEAMwAzAAAAPgAPAAEARgBpAGwAZQBWAGUAcgBzAGkAbwBuAAAAAAAxAC4AMAAuADgA +NgAxADQALgAxADUAMwA4ADcAAAAAAHAAKAABAEkAbgB0AGUAcgBuAGEAbABOAGEAbQBlAAAAUwBU +AF8AMgA1ADEANwA3ADIAYwA3ADYAZABiAGIANABjADIAZABiADAANQBlAGIAZAA1AGQAMwAyADEA +OQA5ADEAMwAzAC4AZABsAGwAAABIABIAAQBMAGUAZwBhAGwAQwBvAHAAeQByAGkAZwBoAHQAAABD +AG8AcAB5AHIAaQBnAGgAdAAgAEAAIAAgADIAMAAyADMAAAAqAAEAAQBMAGUAZwBhAGwAVAByAGEA +ZABlAG0AYQByAGsAcwAAAAAAAAAAAHgAKAABAE8AcgBpAGcAaQBuAGEAbABGAGkAbABlAG4AYQBt +AGUAAABTAFQAXwAyADUAMQA3ADcAMgBjADcANgBkAGIAYgA0AGMAMgBkAGIAMAA1AGUAYgBkADUA +ZAAzADIAMQA5ADkAMQAzADMALgBkAGwAbAAAAGgAJAABAFAAcgBvAGQAdQBjAHQATgBhAG0AZQAA +AAAAUwBUAF8AMgA1ADEANwA3ADIAYwA3ADYAZABiAGIANABjADIAZABiADAANQBlAGIAZAA1AGQA +MwAyADEAOQA5ADEAMwAzAAAAQgAPAAEAUAByAG8AZAB1AGMAdABWAGUAcgBzAGkAbwBuAAAAMQAu +ADAALgA4ADYAMQA0AC4AMQA1ADMAOAA3AAAAAABGAA8AAQBBAHMAcwBlAG0AYgBsAHkAIABWAGUA +cgBzAGkAbwBuAAAAMQAuADAALgA4ADYAMQA0AC4AMQA1ADMAOAA3AAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAADAAAAAw2AAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + \ No newline at end of file diff --git a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIRequests.dtsx b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIRequests.dtsx index 41c0503e1..cbd3779dd 100644 --- a/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIRequests.dtsx +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIRequests.dtsx @@ -11,8 +11,8 @@ DTS:LocaleID="1033" DTS:ObjectName="FOIRequests" DTS:PackageType="5" - DTS:VersionBuild="34" - DTS:VersionGUID="{535D8DAE-8E33-4885-9C83-8FA17FFC4A15}"> + DTS:VersionBuild="37" + DTS:VersionGUID="{9927CBA3-B5E5-4FA2-809A-E9EEBE87B69E}"> 8 @@ -137,12 +137,13 @@ DTS:TaskContact="Execute SQL Task; Microsoft Corporation; SQL Server 2019; © 2019 Microsoft Corporation; All Rights Reserved;http://www.microsoft.com/sql/support/default.asp;1" DTS:ThreadHint="0"> + REPLACE( @[$Project::foirequestsquery] ,"@requestids", @[$Project::requestidstomigrate] ) + SQLTask:SqlStatementSource="INSERT INTO public."FOIRequests"( version, requesttype, receiveddate, isactive, initialdescription, initialrecordsearchfromdate, initialrecordsearchtodate, created_at, updated_at, createdby, updatedby, deliverymodeid, receivedmodeid, foirawrequestid, applicantcategoryid, wfinstanceid, migrationreference) VALUES ( 1, LOWER(?), to_timestamp(?,'YYYY-MM-DD hh24:mi:ss'), True, ?, to_timestamp(?,'YYYY-MM-DD hh24:mi:ss'), to_timestamp(?,'YYYY-MM-DD hh24:mi:ss'), NOW(), NOW(), 'cfdmigration', 'cfdmigration', 1, (SELECT receivedmodeid FROM public."ReceivedModes" RM WHERE RM.name=?), -100, (SELECT applicantcategoryid FROM public."ApplicantCategories" AC WHERE AC.name=?), NULL, ?);" xmlns:SQLTask="www.microsoft.com/sqlserver/dts/tasks/sqltask"> 0 SELECT firstname,lastname,middleName,requesterid,CONVERT(varchar(MAX),'01-01-1900',120) as birthDate,businessName, STRING_AGG(CAST(requestid as nvarchar(max)),', ') as requests ,MainApplicant FROM ( SELECT requesters.vcEmailID as email, requesters.iRequesterID as requesterid, requesters.vcFirstName as firstName, requesters.vcLastName as lastName, requesters.vcMiddleName as middleName, requestorfields.CUSTOMFIELD35 as birthDate, requesters.vcCompany as businessName, requests.vcVisibleRequestID as requestid, 1 as MainApplicant FROM tblRequests requests WITH (NOLOCK) LEFT OUTER JOIN EC_OFFICE office WITH (NOLOCK) ON requests.tiOfficeID = office.OFFICE_ID LEFT OUTER JOIN tblRequesters requesters WITH (NOLOCK) ON requests.iRequesterID = requesters.iRequesterID LEFT OUTER JOIN dbo.TBLREQUESTERCUSTOMFIELDS requestorfields WITH (NOLOCK) ON requesters.iRequesterID = requestorfields.IREQUESTERID WHERE office.OFFICE_CODE = 'CFD' AND requests.vcRequestStatus NOT IN ('Closed') AND requests.vcVisibleRequestID IN ('CFD-2022-20261', 'CFD-2021-14313','CFD-2021-12866' , 'CFD-2021-12905', 'CFD-2021-12651','CFD-2023-30011') UNION ALL SELECT onbehalf.vcEmailID as email, onbehalf.iRequesterID as requesterid, onbehalf.vcFirstName as firstName, onbehalf.vcLastName as lastName, onbehalf.vcMiddleName as middleName, requestorfields.CUSTOMFIELD35 as birthDate, onbehalf.vcCompany as businessName, requests.vcVisibleRequestID as requestid, 0 as MainApplicant FROM tblRequests requests WITH (NOLOCK) LEFT OUTER JOIN EC_OFFICE office WITH (NOLOCK) ON requests.tiOfficeID = office.OFFICE_ID LEFT OUTER JOIN tblRequesters onbehalf WITH (NOLOCK) ON requests.iOnBehalfOf = onbehalf.iRequesterID LEFT OUTER JOIN dbo.TBLREQUESTERCUSTOMFIELDS requestorfields WITH (NOLOCK) ON onbehalf.iRequesterID = requestorfields.IREQUESTERID WHERE office.OFFICE_CODE = 'CFD' AND requests.vcRequestStatus NOT IN ('Closed') AND requests.vcVisibleRequestID IN ('CFD-2022-20261', 'CFD-2021-14313','CFD-2021-12866' , 'CFD-2021-12905', 'CFD-2021-12651','CFD-2023-30011') and onbehalf.vcFirstName is NOT NULL ) AS T GROUP BY T.email,T.requesterid,T.firstname,T.lastname,T.middleName,T.requesterid,T.birthDate,T.businessName,T.MainApplicant ORDER BY T.firstname ASC + SSIS:Name="Value">SELECT firstname,lastname,middleName,requesterid,CONVERT(varchar(MAX),'01-01-1900',120) as birthDate,businessName, GETDATE() as createdAt, 'cfdmigration' as createdBy,STRING_AGG(CAST(requestid as nvarchar(max)),', ') as requests ,MainApplicant FROM ( SELECT requesters.vcEmailID as email, requesters.iRequesterID as requesterid, requesters.vcFirstName as firstName, requesters.vcLastName as lastName, requesters.vcMiddleName as middleName, requestorfields.CUSTOMFIELD35 as birthDate, requesters.vcCompany as businessName, requests.vcVisibleRequestID as requestid, 1 as MainApplicant FROM tblRequests requests WITH (NOLOCK) LEFT OUTER JOIN EC_OFFICE office WITH (NOLOCK) ON requests.tiOfficeID = office.OFFICE_ID LEFT OUTER JOIN tblRequesters requesters WITH (NOLOCK) ON requests.iRequesterID = requesters.iRequesterID LEFT OUTER JOIN dbo.TBLREQUESTERCUSTOMFIELDS requestorfields WITH (NOLOCK) ON requesters.iRequesterID = requestorfields.IREQUESTERID WHERE office.OFFICE_CODE = 'CFD' AND requests.vcRequestStatus NOT IN ('Closed') AND requests.vcVisibleRequestID IN (@requestids) UNION ALL SELECT onbehalf.vcEmailID as email, onbehalf.iRequesterID as requesterid, onbehalf.vcFirstName as firstName, onbehalf.vcLastName as lastName, onbehalf.vcMiddleName as middleName, requestorfields.CUSTOMFIELD35 as birthDate, onbehalf.vcCompany as businessName, requests.vcVisibleRequestID as requestid, 0 as MainApplicant FROM tblRequests requests WITH (NOLOCK) LEFT OUTER JOIN EC_OFFICE office WITH (NOLOCK) ON requests.tiOfficeID = office.OFFICE_ID LEFT OUTER JOIN tblRequesters onbehalf WITH (NOLOCK) ON requests.iOnBehalfOf = onbehalf.iRequesterID LEFT OUTER JOIN dbo.TBLREQUESTERCUSTOMFIELDS requestorfields WITH (NOLOCK) ON onbehalf.iRequesterID = requestorfields.IREQUESTERID WHERE office.OFFICE_CODE = 'CFD' AND requests.vcRequestStatus NOT IN ('Closed') AND requests.vcVisibleRequestID IN (@requestids) and onbehalf.vcFirstName is NOT NULL ) AS T GROUP BY T.email,T.requesterid,T.firstname,T.lastname,T.middleName,T.requesterid,T.birthDate,T.businessName,T.MainApplicant ORDER BY T.firstname ASC 18 @@ -37,7 +37,7 @@ 0 SELECT (SELECT terminology.vcTerminology from tblTerminologyLookup terminology WHERE terminology.iLabelID = requestTypes.iLabelID and terminology.tiLocaleID = 1) as requestType, convert(varchar, requests.sdtRequestedDate,120) as receivedDate, requests.vcDescription as requestdescription, convert(varchar,requests.sdtRqtDescFromdate,120) as reqDescriptionFromDate, convert(varchar,requests.sdtRqtDescTodate,120) as reqDescriptionToDate, (SELECT terminology.vcTerminology from tblTerminologyLookup terminology WHERE terminology.iLabelID = deliveryModes.iLabelID and terminology.tiLocaleID = 1) as deliveryMode, (SELECT terminology.vcTerminology from tblTerminologyLookup terminology WHERE terminology.iLabelID = receivedModes.iLabelID and terminology.tiLocaleID = 1) as receivedMode, requesterTypes.vcDescription as category, requests.vcVisibleRequestID as filenumber FROM tblRequests requests WITH (NOLOCK) LEFT OUTER JOIN EC_OFFICE office WITH (NOLOCK) ON requests.tiOfficeID = office.OFFICE_ID LEFT OUTER JOIN tblRequesterTypes requesterTypes WITH (NOLOCK) ON requests.tiRequesterCategoryID = requesterTypes.tiRequesterTypeID LEFT OUTER JOIN tblRequestStatuses requeststatuses WITH (NOLOCK) on requests.irequeststatusid = requeststatuses.irequeststatusid LEFT OUTER JOIN tblReceivedModes receivedModes WITH (NOLOCK) ON requests.tiReceivedType = receivedModes.tiReceivedModeID LEFT OUTER JOIN tblDeliveryModes deliveryModes WITH (NOLOCK) ON requests.tiDeliveryType = deliveryModes.tiDeliveryModeID LEFT OUTER JOIN tblRequestTypes requestTypes WITH (NOLOCK) ON requests.tiRequestTypeID = requestTypes.tiRequestTypeID WHERE vcVisibleRequestID IN ('CFD-2022-20261', 'CFD-2021-14313','CFD-2021-12866' , 'CFD-2021-12905', 'CFD-2021-12651') AND office.OFFICE_CODE = 'CFD' AND requests.vcRequestStatus NOT IN ('Closed') GROUP BY requests.vcVisibleRequestID,requests.vcRequestStatus,requeststatuses.vcRequestStatus,requests.sdtReceivedDate, requests.sdtTargetDate, requests.sdtOriginalTargetDate, requests.vcDescription, requests.sdtRqtDescFromdate, requests.sdtRqtDescTodate, requests.sdtRequestedDate, office.OFFICE_CODE, requesterTypes.vcDescription, receivedModes.iLabelID, deliveryModes.iLabelID, requests.iRequestID, requestTypes.iLabelID, requests.vcVisibleRequestID, requests.tiOfficeID, office.OFFICE_ID + SSIS:Name="Value">SELECT (SELECT terminology.vcTerminology from tblTerminologyLookup terminology WHERE terminology.iLabelID = requestTypes.iLabelID and terminology.tiLocaleID = 1) as requestType, convert(varchar, requests.sdtRequestedDate,120) as receivedDate, requests.vcDescription as requestdescription, convert(varchar,requests.sdtRqtDescFromdate,120) as reqDescriptionFromDate, convert(varchar,requests.sdtRqtDescTodate,120) as reqDescriptionToDate, (SELECT terminology.vcTerminology from tblTerminologyLookup terminology WHERE terminology.iLabelID = deliveryModes.iLabelID and terminology.tiLocaleID = 1) as deliveryMode, (SELECT terminology.vcTerminology from tblTerminologyLookup terminology WHERE terminology.iLabelID = receivedModes.iLabelID and terminology.tiLocaleID = 1) as receivedMode, requesterTypes.vcDescription as category, requests.vcVisibleRequestID as filenumber FROM tblRequests requests WITH (NOLOCK) LEFT OUTER JOIN EC_OFFICE office WITH (NOLOCK) ON requests.tiOfficeID = office.OFFICE_ID LEFT OUTER JOIN tblRequesterTypes requesterTypes WITH (NOLOCK) ON requests.tiRequesterCategoryID = requesterTypes.tiRequesterTypeID LEFT OUTER JOIN tblRequestStatuses requeststatuses WITH (NOLOCK) on requests.irequeststatusid = requeststatuses.irequeststatusid LEFT OUTER JOIN tblReceivedModes receivedModes WITH (NOLOCK) ON requests.tiReceivedType = receivedModes.tiReceivedModeID LEFT OUTER JOIN tblDeliveryModes deliveryModes WITH (NOLOCK) ON requests.tiDeliveryType = deliveryModes.tiDeliveryModeID LEFT OUTER JOIN tblRequestTypes requestTypes WITH (NOLOCK) ON requests.tiRequestTypeID = requestTypes.tiRequestTypeID WHERE vcVisibleRequestID IN (@requestids) AND office.OFFICE_CODE = 'CFD' AND requests.vcRequestStatus NOT IN ('Closed') GROUP BY requests.vcVisibleRequestID,requests.vcRequestStatus,requeststatuses.vcRequestStatus,requests.sdtReceivedDate, requests.sdtTargetDate, requests.sdtOriginalTargetDate, requests.vcDescription, requests.sdtRqtDescFromdate, requests.sdtRqtDescTodate, requests.sdtRequestedDate, office.OFFICE_CODE, requesterTypes.vcDescription, receivedModes.iLabelID, deliveryModes.iLabelID, requests.iRequestID, requestTypes.iLabelID, requests.vcVisibleRequestID, requests.tiOfficeID, office.OFFICE_ID 18 @@ -58,7 +58,91 @@ 0 SELECT requests.vcVisibleRequestID as filenumber, requests.vcDescription as requestdescription, convert(varchar,requests.sdtRqtDescFromdate,120) as reqDescriptionFromDate, convert(varchar,requests.sdtRqtDescTodate,120) as reqDescriptionToDate, convert(varchar, requests.sdtReceivedDate,120) as requestProcessStart, convert(varchar,requests.sdtTargetDate,120) as dueDate, convert(varchar, (SELECT TOP 1 cfr.sdtDueDate FROM tblRequestForDocuments cfr WITH (NOLOCK) INNER JOIN tblProgramOffices programoffice WITH (NOLOCK) ON programoffice.tiProgramOfficeID = cfr.tiProgramOfficeID WHERE requests.iRequestID = cfr.iRequestID AND requests.tiOfficeID = programoffice.tiOfficeID AND office.OFFICE_ID = programoffice.tiOfficeID AND cfr.sdtDueDate IS NOT NULL ORDER BY cfr.sdtDueDate DESC),120) as cfrDueDate, convert(varchar,sum(distinct case when requests.IREQUESTID = reviewlog.IREQUESTID and reviewlog.IDOCID = documents.IDOCID then documents.SIPAGECOUNT when requests.IREQUESTID = redaction.IREQUESTID and redaction.IDOCID = ldocuments.IDOCID then ldocuments.SIPAGECOUNT else 0 end),120) as requestPageCount FROM tblRequests requests WITH (NOLOCK) LEFT OUTER JOIN EC_OFFICE office WITH (NOLOCK) ON requests.tiOfficeID = office.OFFICE_ID LEFT OUTER JOIN tblRequestStatuses requeststatuses WITH (NOLOCK) on requests.irequeststatusid = requeststatuses.irequeststatusid LEFT OUTER JOIN dbo.TBLdocumentreviewlog reviewlog WITH (NOLOCK) ON requests.IREQUESTID = reviewlog.IREQUESTID LEFT OUTER JOIN dbo.TBLRedactionlayers redaction WITH (NOLOCK) ON requests.IREQUESTID = redaction.IREQUESTID LEFT OUTER JOIN dbo.TBLDOCUMENTS documents WITH (NOLOCK) ON reviewlog.IDOCID = documents.IDOCID LEFT OUTER JOIN dbo.TBLDOCUMENTS ldocuments WITH (NOLOCK) ON redaction.IDOCID = ldocuments.IDOCID WHERE vcVisibleRequestID IN ('CFD-2022-20261', 'CFD-2021-14313','CFD-2021-12866' , 'CFD-2021-12905', 'CFD-2021-12651') AND office.OFFICE_CODE = 'CFD' AND requests.vcRequestStatus NOT IN ('Closed') GROUP BY requests.vcVisibleRequestID,requests.vcRequestStatus,requeststatuses.vcRequestStatus,requests.sdtReceivedDate, requests.sdtTargetDate, requests.sdtOriginalTargetDate, requests.vcDescription, requests.sdtRqtDescFromdate, requests.sdtRqtDescTodate, requests.sdtRequestedDate, office.OFFICE_CODE, requests.iRequestID, requests.vcVisibleRequestID, requests.tiOfficeID, office.OFFICE_ID + SSIS:Name="Value">SELECT requests.vcVisibleRequestID as filenumber, requests.vcDescription as requestdescription, convert(varchar,requests.sdtRqtDescFromdate,120) as reqDescriptionFromDate, convert(varchar,requests.sdtRqtDescTodate,120) as reqDescriptionToDate, convert(varchar, requests.sdtReceivedDate,120) as requestProcessStart, convert(varchar,requests.sdtTargetDate,120) as dueDate, convert(varchar, (SELECT TOP 1 cfr.sdtDueDate FROM tblRequestForDocuments cfr WITH (NOLOCK) INNER JOIN tblProgramOffices programoffice WITH (NOLOCK) ON programoffice.tiProgramOfficeID = cfr.tiProgramOfficeID WHERE requests.iRequestID = cfr.iRequestID AND requests.tiOfficeID = programoffice.tiOfficeID AND office.OFFICE_ID = programoffice.tiOfficeID AND cfr.sdtDueDate IS NOT NULL ORDER BY cfr.sdtDueDate DESC),120) as cfrDueDate, convert(varchar,sum(distinct case when requests.IREQUESTID = reviewlog.IREQUESTID and reviewlog.IDOCID = documents.IDOCID then documents.SIPAGECOUNT when requests.IREQUESTID = redaction.IREQUESTID and redaction.IDOCID = ldocuments.IDOCID then ldocuments.SIPAGECOUNT else 0 end),120) as requestPageCount , requests.vcRequestStatus, CONVERT(VARCHAR(MAX),(SELECT CONCAT('[',STRING_AGG( CONCAT('{"', destin_vcVisibleRequestID, '":"', ministry, '"}'),','),']') AS linkedRequests FROM( SELECT DISTINCT destin.vcVisibleRequestID AS destin_vcVisibleRequestID, office.OFFICE_CODE AS ministry FROM tblRequests origin JOIN tblRequestLinks link ON(origin.IREQUESTID = link.iRequestIDOrigin OR origin.IREQUESTID = link.iRequestIDDestin) JOIN tblRequests destin ON(link.iRequestIDOrigin = destin.IREQUESTID OR link.iRequestIDDestin = destin.IREQUESTID) JOIN EC_OFFICE office ON destin.tiOfficeID = office.OFFICE_ID WHERE origin.vcVisibleRequestID = requests.vcVisibleRequestID AND destin.vcVisibleRequestID != requests.vcVisibleRequestID ) AS destination_table),120) AS linkedRequests FROM tblRequests requests WITH (NOLOCK) LEFT OUTER JOIN EC_OFFICE office WITH (NOLOCK) ON requests.tiOfficeID = office.OFFICE_ID LEFT OUTER JOIN tblRequestStatuses requeststatuses WITH (NOLOCK) on requests.irequeststatusid = requeststatuses.irequeststatusid LEFT OUTER JOIN dbo.TBLdocumentreviewlog reviewlog WITH (NOLOCK) ON requests.IREQUESTID = reviewlog.IREQUESTID LEFT OUTER JOIN dbo.TBLRedactionlayers redaction WITH (NOLOCK) ON requests.IREQUESTID = redaction.IREQUESTID LEFT OUTER JOIN dbo.TBLDOCUMENTS documents WITH (NOLOCK) ON reviewlog.IDOCID = documents.IDOCID LEFT OUTER JOIN dbo.TBLDOCUMENTS ldocuments WITH (NOLOCK) ON redaction.IDOCID = ldocuments.IDOCID WHERE vcVisibleRequestID IN (@requestids) AND office.OFFICE_CODE = 'CFD' AND requests.vcRequestStatus NOT IN ('Closed') GROUP BY requests.vcVisibleRequestID,requests.vcRequestStatus,requeststatuses.vcRequestStatus,requests.sdtReceivedDate, requests.sdtTargetDate, requests.sdtOriginalTargetDate, requests.vcDescription, requests.sdtRqtDescFromdate, requests.sdtRqtDescTodate, requests.sdtRequestedDate, office.OFFICE_CODE, requests.iRequestID, requests.vcVisibleRequestID, requests.tiOfficeID, office.OFFICE_ID,requests.vcRequestStatus + 18 + + + + + {c8ef50d0-65c0-4b92-98d4-698f3276f13b} + + + 0 + 0 + 0 + SELECT loc.vcTerminology AS reason, reqextn.cApprovedStatus AS [status], convert(varchar, reqextn.sdtExtendedDate,120) AS extendedduedate, convert(varchar,reqextn.siExtensionDays) AS extensiondays, convert(varchar, reqextn.dtApprovedDate,120) AS decisiondate,req.vcVisibleRequestID as requestid FROM tblRequests req WITH (NOLOCK) INNER JOIN tblRequestExtensions reqextn WITH (NOLOCK) ON req.iRequestID = reqextn.iRequestID AND req.vcVisibleRequestID in (@requestids) INNER JOIN tblExtensions extn WITH (NOLOCK) ON reqextn.tiExtension = extn.tiExtension LEFT OUTER JOIN tblTerminologyLookup loc WITH (NOLOCK) ON loc.iLabelID = extn.iLabelID AND loc.tiLocaleID = 1; + 18 + + + + + {9e7edcc7-6ea5-4055-a767-fb256bb43c54} + + + 0 + 0 + 0 + SELECT email,address1,address2, city,zipcode,work1,work2,mobile,home,country,province, STRING_AGG(CAST(requestid as nvarchar(max)),', ') as requests FROM (SELECT 'test@test1.com' as email,requesters.vcAddress1 as address1, requesters.vcAddress2 as address2, requesters.vcCity as city, requesters.vcZipCode as zipcode, requesters.vcWork1 as work1, requesters.vcwork2 as work2, requesters.vcMobile as mobile,requesters.vcHome as home, (SELECT terminology.vcTerminology from tblTerminologyLookup terminology WHERE terminology.iLabelID = countries.iLabelID and terminology.tiLocaleID = 1) as country, (SELECT terminology.vcTerminology from tblTerminologyLookup terminology WHERE terminology.iLabelID = states.iLabelID and terminology.tiLocaleID = 1) as province, requests.vcVisibleRequestID as requestid FROM tblRequests requests WITH (NOLOCK) LEFT OUTER JOIN EC_OFFICE office WITH (NOLOCK) ON requests.tiOfficeID = office.OFFICE_ID LEFT OUTER JOIN tblRequesters requesters WITH (NOLOCK) ON requests.iRequesterID = requesters.iRequesterID LEFT OUTER JOIN tblCountries countries WITH (NOLOCK) ON requesters.siCountryID = countries.siCountryID LEFT OUTER JOIN tblStates states WITH (NOLOCK) ON requesters.siStateID = states.siStateID LEFT OUTER JOIN dbo.TBLREQUESTERCUSTOMFIELDS requestorfields WITH (NOLOCK) ON requesters.iRequesterID = requestorfields.IREQUESTERID WHERE office.OFFICE_CODE = 'CFD' AND requests.vcRequestStatus NOT IN ('Closed') ) AS T WHERE requestid in (@requestids) GROUP BY T.email,T.address1,T.address2, T.city,T.zipcode,T.work1,T.work2,T.mobile,T.home, T.country,T.province + 18 + + + + + {76cefc53-141a-4628-a42b-8d28087b43ab} + + + 0 + 0 + 0 + 'CFD-2021-12651','CFD-2023-30193' + 18 + + + + + {e20fa8c2-6427-496b-ae8b-0ecab48b1687} + + + 0 + 0 + 0 + SELECT vcVisibleRequestID , REPLACE(requestfields.CUSTOMFIELD33, CHAR(160), ' ') as subjectCode FROM tblRequests requests WITH (NOLOCK) LEFT OUTER JOIN dbo.TBLREQUESTCUSTOMFIELDS requestfields WITH (NOLOCK) ON requests.iRequestID = requestfields.iRequestID WHERE requests.vcVisibleRequestID in (@requestids) GROUP BY requests.vcVisibleRequestID, REPLACE(requestfields.CUSTOMFIELD33, CHAR(160), ' '); 18 From 621970a148eae776dc60ffcb78efd8b4b1332ac0 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Tue, 29 Aug 2023 12:02:19 -0700 Subject: [PATCH 075/234] Added help icon with link to FOI-Mod Help website to header component. WIP styling --- forms-flow-web/src/components/FOI/Header/FOIHeader.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/forms-flow-web/src/components/FOI/Header/FOIHeader.js b/forms-flow-web/src/components/FOI/Header/FOIHeader.js index e55898548..fe40c1f6f 100644 --- a/forms-flow-web/src/components/FOI/Header/FOIHeader.js +++ b/forms-flow-web/src/components/FOI/Header/FOIHeader.js @@ -18,7 +18,7 @@ import {SOCKETIO_CONNECT_URL, SOCKETIO_RECONNECTION_DELAY, SOCKETIO_RECONNECTION import { fetchFOIFullAssignedToList } from "../../../apiManager/services/FOI/foiMasterDataServices"; import {setFOIAssignedToListLoader} from "../../../actions/FOI/foiRequestActions"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faCog } from '@fortawesome/free-solid-svg-icons'; +import { faCog, faQuestion } from '@fortawesome/free-solid-svg-icons'; const FOIHeader = React.memo(({unauthorized=false}) => { @@ -140,6 +140,11 @@ const adminDashboard = (_e) => { } +
  • + + + +
  • From 89c250495053084058ed486a3da4534c5f076097 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Tue, 29 Aug 2023 13:31:03 -0700 Subject: [PATCH 076/234] Styling added. Ticket completed --- .../src/components/FOI/Header/FOIHeader.js | 2 +- .../src/components/FOI/Header/foiheader.scss | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/forms-flow-web/src/components/FOI/Header/FOIHeader.js b/forms-flow-web/src/components/FOI/Header/FOIHeader.js index fe40c1f6f..32b142de2 100644 --- a/forms-flow-web/src/components/FOI/Header/FOIHeader.js +++ b/forms-flow-web/src/components/FOI/Header/FOIHeader.js @@ -140,7 +140,7 @@ const adminDashboard = (_e) => {
  • } -
  • +
  • diff --git a/forms-flow-web/src/components/FOI/Header/foiheader.scss b/forms-flow-web/src/components/FOI/Header/foiheader.scss index 74f557f2c..014b47442 100644 --- a/forms-flow-web/src/components/FOI/Header/foiheader.scss +++ b/forms-flow-web/src/components/FOI/Header/foiheader.scss @@ -51,7 +51,7 @@ nav h2 { } .report-icon{ - padding-top: 0.5rem; + padding-top: 0.4rem; a { color: white; @@ -63,7 +63,7 @@ nav h2 { } .admin-icon { - padding: 10px; + padding: 0.5rem 10px 0px 0px; cursor: pointer; } @@ -79,6 +79,15 @@ nav h2 { max-height: 40px; } +.help-icon { + padding: 0.5rem 10px 0px 0px; + cursor: pointer; +} + +.fa-question { + color:white; +} + .foiheaderLogosection{ display: inline; } From 5e3fcc49a35d411c7fb392e6f94e04927e7dabea Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Tue, 29 Aug 2023 14:18:49 -0700 Subject: [PATCH 077/234] Added consistency in icon styling --- forms-flow-web/src/components/FOI/Header/FOIHeader.js | 2 +- forms-flow-web/src/components/FOI/Header/foiheader.scss | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/forms-flow-web/src/components/FOI/Header/FOIHeader.js b/forms-flow-web/src/components/FOI/Header/FOIHeader.js index 32b142de2..aa4056046 100644 --- a/forms-flow-web/src/components/FOI/Header/FOIHeader.js +++ b/forms-flow-web/src/components/FOI/Header/FOIHeader.js @@ -147,7 +147,7 @@ const adminDashboard = (_e) => {
  • - + diff --git a/forms-flow-web/src/components/FOI/Header/foiheader.scss b/forms-flow-web/src/components/FOI/Header/foiheader.scss index 014b47442..ab86c4cac 100644 --- a/forms-flow-web/src/components/FOI/Header/foiheader.scss +++ b/forms-flow-web/src/components/FOI/Header/foiheader.scss @@ -35,7 +35,7 @@ nav h2 { } .bell-icon { - padding-top: 0.5rem; + padding-top: 7.5px; .MuiBadge-badge{ visibility: visible !important; @@ -51,7 +51,7 @@ nav h2 { } .report-icon{ - padding-top: 0.4rem; + padding-top: 6px; a { color: white; @@ -63,7 +63,7 @@ nav h2 { } .admin-icon { - padding: 0.5rem 10px 0px 0px; + padding: 7px 10px 0px 0px; cursor: pointer; } @@ -80,7 +80,7 @@ nav h2 { } .help-icon { - padding: 0.5rem 10px 0px 0px; + padding: 7px 10px 0px 0px; cursor: pointer; } From d89ef1a3230b9d255c8838c1ab78ebbbc61fe2ea Mon Sep 17 00:00:00 2001 From: Milos Despotovic Date: Tue, 29 Aug 2023 15:36:39 -0700 Subject: [PATCH 078/234] Create a reclassify API resource endpoint --- .../request_api/resources/foidocument.py | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/request-management-api/request_api/resources/foidocument.py b/request-management-api/request_api/resources/foidocument.py index f095f9444..df0431793 100644 --- a/request-management-api/request_api/resources/foidocument.py +++ b/request-management-api/request_api/resources/foidocument.py @@ -23,10 +23,11 @@ from request_api.utils.util import cors_preflight, allowedorigins from request_api.exceptions import BusinessException, Error from request_api.services.documentservice import documentservice -from request_api.schemas.foidocument import CreateDocumentSchema, RenameDocumentSchema, ReplaceDocumentSchema +from request_api.schemas.foidocument import CreateDocumentSchema, RenameDocumentSchema, ReplaceDocumentSchema, ReclassifyDocumentSchema import json from marshmallow import Schema, fields, validate, ValidationError from flask_cors import cross_origin +import boto3 API = Namespace('FOIDocument', description='Endpoints for FOI Document management') @@ -103,6 +104,45 @@ def post(requestid, documentid, requesttype): except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 +@cors_preflight('POST,OPTIONS') +@API.route('/foidocument///documentid//reclassify') +class ReclassifyFOIDocument(Resource): + """Resource for reclassifying uploaded attachments of FOI requests.""" + + @staticmethod + @TRACER.trace() + @cross_origin(origins=allowedorigins()) + @auth.require + def post(requesttype, requestid, documentid): + try: + requestjson = request.get_json() + activedocuments = documentservice().getactiverequestdocuments(requestid, requesttype) + documentpath = 'no documentpath found' + if (requesttype == 'ministryrequest'): + for document in activedocuments: + if document['foiministrydocumentid'] == int(documentid): + documentpath = document['documentpath'] + else: + for document in activedocuments: + if document['foidocumentid'] == int(documentid): + documentpath = document['documentpath'] + + # move document in S3 + moveresult = documentservice().copyrequestdocumenttonewlocation(requestjson['category'], documentpath) + # save new version of document with updated documentpath + updatedata = {'category': requestjson['category'], 'documentpath': moveresult['documentpath']} + documentschema = ReclassifyDocumentSchema().load(updatedata) + if moveresult['status'] == 'success': + result = documentservice().createrequestdocumentversion(requestid, documentid, documentschema, AuthHelper.getuserid(), requesttype) + return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 + return {'status': False, 'message': 'Something went wrong' }, 500 + except ValidationError as err: + return {'status': False, 'message':err.messages}, 400 + except KeyError as err: + return {'status': False, 'message': 'KeyError'}, 400 + except BusinessException as exception: + return {'status': exception.status_code, 'message':exception.message}, 500 + @cors_preflight('POST,OPTIONS') @API.route('/foidocument///documentid//replace') class ReplaceFOIDocument(Resource): From 38f1ea3f95dcf30e38d3ce30d767c9823315b701 Mon Sep 17 00:00:00 2001 From: Milos Despotovic Date: Tue, 29 Aug 2023 15:37:44 -0700 Subject: [PATCH 079/234] Create schema for reclassifying --- request-management-api/request_api/schemas/foidocument.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/request-management-api/request_api/schemas/foidocument.py b/request-management-api/request_api/schemas/foidocument.py index 622b12f8a..01eb3b75f 100644 --- a/request-management-api/request_api/schemas/foidocument.py +++ b/request-management-api/request_api/schemas/foidocument.py @@ -16,6 +16,14 @@ class Meta: # pylint: disable=too-few-public-methods unknown = EXCLUDE filename = fields.Str(data_key="filename",required=True,allow_none=False) +class ReclassifyDocumentSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + category = fields.Str(data_key="category",required=True,allow_none=False) + documentpath = fields.Str(data_key="documentpath",required=True,allow_none=False, validate=[validate.Length(max=1000, error=MAX_EXCEPTION_MESSAGE)]) + class ReplaceDocumentSchema(Schema): class Meta: # pylint: disable=too-few-public-methods """Exclude unknown fields in the deserialized output.""" From 6ff2265b06782707a4043757d2c5985c02ca2d5d Mon Sep 17 00:00:00 2001 From: Milos Despotovic Date: Tue, 29 Aug 2023 15:43:27 -0700 Subject: [PATCH 080/234] Create service to copy document to new location in S3 --- .../request_api/services/documentservice.py | 17 +++++++++++++++++ .../services/external/storageservice.py | 12 ++++++++++++ 2 files changed, 29 insertions(+) diff --git a/request-management-api/request_api/services/documentservice.py b/request-management-api/request_api/services/documentservice.py index 54b2dbf69..8bdf49b50 100644 --- a/request-management-api/request_api/services/documentservice.py +++ b/request-management-api/request_api/services/documentservice.py @@ -51,6 +51,23 @@ def createrequestdocumentversion(self, requestid, documentid, documentschema, us else: return self.createrawdocumentversion(requestid, documentid, documentschema, userid) + def copyrequestdocumenttonewlocation(self, newcategory, documentpath): #documentpath is full url including https:// + # for old documentpath + baseurl = documentpath.split('/')[:3] + location = documentpath.split('/')[3:] # bucket and filename + bucket = location[0] + source = "/".join(location) # /bucket/filename + # for new document path, replace category name in path + newlocation = location + newlocation[3] = newcategory + filename = "/".join(newlocation[1:]) + newdocumentpath = "/".join(baseurl) + '/' + bucket + '/' + filename + moveresponse = storageservice().copy_file(source, bucket, filename) + if moveresponse['ResponseMetadata']['HTTPStatusCode'] == 200: + return {"status": "success", 'documentpath': newdocumentpath} + else: + return {"status": "error"} + def deleterequestdocument(self, requestid, documentid, userid, requesttype): documentschema = {'isactive':False} return self.createrequestdocumentversion(requestid, documentid, documentschema, userid, requesttype) diff --git a/request-management-api/request_api/services/external/storageservice.py b/request-management-api/request_api/services/external/storageservice.py index 7a917d096..f24e2b7fc 100644 --- a/request-management-api/request_api/services/external/storageservice.py +++ b/request-management-api/request_api/services/external/storageservice.py @@ -127,6 +127,18 @@ def bulk_upload(self, requestfilejson, category): file['filestatustransition']=filestatustransition if s3sourceuri is None else '' return requestfilejson + def copy_file(self, source, bucket, filename): + if(self.accesskey is None or self.secretkey is None or self.s3host is None): + return {'status': "Configuration Issue", 'message':"accesskey is None or secretkey is None or S3 host is None or formsbucket is None"}, 500 + docpathmapper = DocumentPathMapper().getdocumentpath("Attachments") + s3 = self.__get_s3client(None, docpathmapper) + response = s3.copy_object( + CopySource=source, # /Bucket-name/path/filename + Bucket=bucket, # Destination bucket + Key=filename # Destination path/filename + ) + return response + def retrieve_s3_presigned(self, filepath, category="attachments", bcgovcode=None): docpathmapper = DocumentPathMapper().getdocumentpath(category, bcgovcode) formsbucket = docpathmapper['bucket'] From 89eb06ec29394d346837b0901e57a5804e8c9303 Mon Sep 17 00:00:00 2001 From: Milos Despotovic Date: Tue, 29 Aug 2023 15:44:21 -0700 Subject: [PATCH 081/234] Create endpoint routes for reclassify requests --- forms-flow-web/src/apiManager/endpoints/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/forms-flow-web/src/apiManager/endpoints/index.js b/forms-flow-web/src/apiManager/endpoints/index.js index f16966d9b..508eabf1c 100644 --- a/forms-flow-web/src/apiManager/endpoints/index.js +++ b/forms-flow-web/src/apiManager/endpoints/index.js @@ -53,6 +53,8 @@ const API = { FOI_RENAME_ATTACHMENTS_RAWREQUEST: `${FOI_BASE_API_URL}/api/foidocument/rawrequest//documentid//rename`, FOI_RENAME_ATTACHMENTS_MINISTRYREQUEST: `${FOI_BASE_API_URL}/api/foidocument/ministryrequest//documentid//rename`, + FOI_RECLASSIFY_CATEGORY_RAWREQUEST: `${FOI_BASE_API_URL}/api/foidocument/rawrequest//documentid//reclassify`, + FOI_RECLASSIFY_CATEGORY_MINISTRYREQUEST: `${FOI_BASE_API_URL}/api/foidocument/ministryrequest//documentid//reclassify`, FOI_REPLACE_ATTACHMENT_RAWREQUEST: `${FOI_BASE_API_URL}/api/foidocument/rawrequest//documentid//replace`, FOI_REPLACE_ATTACHMENT_MINISTRYREQUEST: `${FOI_BASE_API_URL}/api/foidocument/ministryrequest//documentid//replace`, FOI_DELETE_ATTACHMENT_RAWREQUEST: `${FOI_BASE_API_URL}/api/foidocument/rawrequest//documentid//delete`, From 0fbd31b0a75131f65a5af3bf9a5b3369e35be0ca Mon Sep 17 00:00:00 2001 From: Milos Despotovic Date: Tue, 29 Aug 2023 15:45:45 -0700 Subject: [PATCH 082/234] Create saveNewCategory service --- .../services/FOI/foiAttachmentServices.js | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/forms-flow-web/src/apiManager/services/FOI/foiAttachmentServices.js b/forms-flow-web/src/apiManager/services/FOI/foiAttachmentServices.js index 80f24dda9..759157276 100644 --- a/forms-flow-web/src/apiManager/services/FOI/foiAttachmentServices.js +++ b/forms-flow-web/src/apiManager/services/FOI/foiAttachmentServices.js @@ -96,6 +96,34 @@ import { }; }; + export const saveNewCategory = (newCategory, documentId, requestId, ministryId, ...rest) => { + let apiUrl = ""; + if (ministryId != null) { + apiUrl = replaceUrl( + API.FOI_RECLASSIFY_CATEGORY_MINISTRYREQUEST + '', + "", ministryId); + } + else { + apiUrl = replaceUrl( + API.FOI_RECLASSIFY_CATEGORY_RAWREQUEST, + "", + requestId + ); + } + apiUrl = replaceUrl( + apiUrl, + "", + documentId + ); + + return (dispatch) => { + const data = { + category: newCategory + }; + postAttachment(dispatch, apiUrl, data, requestId, ministryId, "Error in reclassifying the file", rest); + }; + }; + export const replaceFOIRequestAttachment = (requestId, ministryId, documentId, data, ...rest) => { let apiUrl = ""; if (ministryId && documentId) { From 2b2d97e33966ace8b7ce5beba6ce05cbab5e4703 Mon Sep 17 00:00:00 2001 From: Milos Despotovic Date: Tue, 29 Aug 2023 15:47:40 -0700 Subject: [PATCH 083/234] Handle reclassify action in modal and refactor rendering --- .../Attachments/AttachmentModal.js | 50 +++++++++++++++++-- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js b/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js index eeabd15fd..3921f77dc 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js +++ b/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js @@ -15,6 +15,7 @@ import { makeStyles } from '@material-ui/core/styles'; import { MimeTypeList, MaxFileSizeInMB } from "../../../../constants/FOI/enum"; import { StateTransitionCategories, AttachmentCategories } from '../../../../constants/FOI/statusEnum'; import { TOTAL_RECORDS_UPLOAD_LIMIT } from "../../../../constants/constants"; +import { ClickableChip } from '../../Dashboard/utils'; const useStyles = makeStyles((theme) => ({ root: { @@ -49,6 +50,7 @@ export default function AttachmentModal({ attachment, attachmentsArray, handleRename, + handleReclassify, isMinistryCoordinator, uploadFor="attachment", maxNoFiles, @@ -139,6 +141,10 @@ export default function AttachmentModal({ } }; + const saveNewCategory = () => { + handleReclassify(attachment, tagValue); + } + const saveNewFilename = () => { if(validateFilename(newFilename)) { if(!containDuplicate(newFilename)) { @@ -247,6 +253,8 @@ export default function AttachmentModal({ return _message; case "rename": return {title: "Rename Attachment", body: ""}; + case "reclassify": + return {title: "Reclassify Attachment", body: ""} case "delete": if (uploadFor === 'record') { return {title: "Delete Record", body: <>Are you sure you want to delete this record?

    If you delete this record, the record will not appear in the redaction app for review by IAO.}; @@ -294,6 +302,27 @@ export default function AttachmentModal({ {message.body}
  • + {modalFor === 'reclassify' ? +
    +
    + Select the tag that you would like to reclassify this document with +
    +
    + {tagList.map(tag => + {handleTagChange(tag.name)}} + clicked={tagValue == tag.name} + /> + )} +
    +
    : '' + } { (['replaceattachment','replace','add'].includes(modalFor)) ? - : - + : '' + } + { + modalFor === 'rename' ? + : '' } + { + modalFor === 'reclassify' ? + : '' + } { modalFor === 'rename'? - : + : '' + } + { + modalFor !== 'rename' && modalFor !== 'reclassify' ? + : '' } : '' + } { - modalFor === 'rename'? + modalFor === 'rename' && : '' + } { - modalFor !== 'rename' && modalFor !== 'reclassify' ? + modalFor !== 'rename' && modalFor !== 'reclassify' && : '' + } : null} + + + + {modalFor === "add" && (
    +

    When uploading more than one {uploadFor}, all {uploadFor}s will have the same selected tag.....

    +
    )} +
      + {errorMessage + ? errorMessage.map((error) => ( +
    • +
      +

      {error}

      +
      +
    • + )) + : null} +
    + + ); +}; + +export default FileUploadForScanning; \ No newline at end of file diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/index.js b/forms-flow-web/src/components/FOI/customComponents/Records/index.js index 9b46bee74..166f00b7b 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/index.js @@ -51,7 +51,10 @@ import { DOC_REVIEWER_WEB_URL, RECORD_PROCESSING_HRS, OSS_S3_CHUNK_SIZE, DISABLE import {removeDuplicateFiles, getUpdatedRecords, sortByLastModified, getFiles, calculateDivisionFileSize, calculateTotalFileSize,calculateTotalUploadedFileSizeInKB,getReadableFileSize} from "./util" import { readUploadedFileAsBytes } from '../../../../helper/FOI/helper'; import { TOTAL_RECORDS_UPLOAD_LIMIT } from "../../../../constants/constants"; +import { isScanningTeam } from "../../../../helper/FOI/helper"; +import { MinistryNeedsScanning } from "../../../../constants/FOI/enum"; //import {convertBytesToMB} from "../../../../components/FOI/customComponents/FileUpload/util"; +import FOI_COMPONENT_CONSTANTS from "../../../../constants/FOI/foiComponentConstants"; const useStyles = makeStyles((_theme) => ({ @@ -156,7 +159,8 @@ export const RecordsLog = ({ ministryAssignedToList, isMinistryCoordinator, setRecordsUploading, - recordsTabSelect + recordsTabSelect, + requestType }) => { let recordsObj = useSelector( @@ -174,6 +178,9 @@ export const RecordsLog = ({ let isRecordsfetching = useSelector( (state) => state.foiRequests.isRecordsLoading ); + + let isScanningTeamMember = isScanningTeam(); + const classes = useStyles(); const [records, setRecords] = useState(recordsObj?.records); const [totalUploadedRecordSize, setTotalUploadedRecordSize] = useState(0); @@ -1128,7 +1135,7 @@ export const RecordsLog = ({ */} - {isMinistryCoordinator ? + {isMinistryCoordinator || (isScanningTeamMember && MinistryNeedsScanning.includes(bcgovcode) && requestType === FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL) ? : null} + + + +
      + {errorMessage + ? errorMessage.map((error) => ( +
    • +
      +

      {error}

      +
      +
    • + )) + : null} +
    + + ); +}; + +export default FileUploadForMSDPersonal; \ No newline at end of file diff --git a/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForScanning.js b/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForScanning.js index d00030e35..ce4e2e8f0 100644 --- a/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForScanning.js +++ b/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForScanning.js @@ -49,6 +49,7 @@ const FileUploadForScanning = ({ const [includeAttachments, setIncludeAttachments] = useState(true); const [searchValue, setSearchValue] = useState(""); const [additionalTagList, setAdditionalTagList] = useState([]); + const [showAdditionalTags, setShowAdditionalTags] = useState(false); const handleUploadBtnClick = (e) => { e.stopPropagation(); @@ -199,14 +200,19 @@ const FileUploadForScanning = ({ let newSectionArray = []; if(_keyword || _selectedSectionValue) { _sectionArray.map((section) => { - if(section.name === _selectedSectionValue) { - newSectionArray.unshift(section); - } - else if(_keyword && section.display.toLowerCase().includes(_keyword.toLowerCase())) { + if(_keyword && section.display.toLowerCase().includes(_keyword.toLowerCase())) { newSectionArray.push(section); + } else if(section.name === _selectedSectionValue) { + newSectionArray.unshift(section); } }); } + + if(newSectionArray.length > 0) { + setShowAdditionalTags(true); + } else { + setShowAdditionalTags(false); + } return newSectionArray; } @@ -296,7 +302,7 @@ const FileUploadForScanning = ({ />
    - )} - + )} )} From 4ac5a0025707b5c31683b372b27748bd2b48c8ae Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Wed, 6 Sep 2023 16:44:26 -0700 Subject: [PATCH 126/234] createfoiministryrequestfromobject1 --- .../FOIRequest/MinistryReview/BottomButtonGroup.js | 12 ++++++------ .../FOI/customComponents/ConfirmationModal/index.js | 2 +- .../request_api/resources/foirequest.py | 1 - .../request_api/schemas/foirequestwrapper.py | 6 +++--- .../request_api/services/events/state.py | 2 +- .../foirequest/requestserviceministrybuilder.py | 1 + 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/BottomButtonGroup.js b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/BottomButtonGroup.js index bb2ad24b9..1d2ef577e 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/BottomButtonGroup.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/BottomButtonGroup.js @@ -69,25 +69,25 @@ const BottomButtonGroup = React.memo( //State to manage approval data for Ministry Sign Off const [ministryApprovalState, setMinistryApprovalState] = useState({ - name: "", - title: "", - date: "" + approverName: "", + approverTitle: "", + approvedDate: "" }); const handleApprovalInputs = (event) => { if(event.target.name === "name") { setMinistryApprovalState((prevState) => { - return {...prevState, name: event.target.value} + return {...prevState, approverName: event.target.value} }) } if(event.target.name === "title") { setMinistryApprovalState((prevState) => { - return {...prevState, title: event.target.value} + return {...prevState, approverTitle: event.target.value} }) } if(event.target.name === "datePicker") { setMinistryApprovalState((prevState) => { - return {...prevState, date: event.target.value} + return {...prevState, approvedDate: event.target.value} }) } } diff --git a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/index.js b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/index.js index 96707bf6e..f13d7836b 100644 --- a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/index.js @@ -127,7 +127,7 @@ export default function ConfirmationModal({requestId, openModal, handleModal, st state.toLowerCase() === StateEnum.review.name.toLowerCase()) && !allowStateChange)) { return true; } - else if ((state.toLowerCase() === StateEnum.response.name.toLowerCase() && !(ministryApprovalState?.name && ministryApprovalState?.date && ministryApprovalState?.title))) { + else if ((state.toLowerCase() === StateEnum.response.name.toLowerCase() && !(ministryApprovalState?.approverName && ministryApprovalState?.approvedDate && ministryApprovalState?.approverTitle))) { return true; } return files.length === 0 diff --git a/request-management-api/request_api/resources/foirequest.py b/request-management-api/request_api/resources/foirequest.py index 7b7b8f1ee..bb9af10ce 100644 --- a/request-management-api/request_api/resources/foirequest.py +++ b/request-management-api/request_api/resources/foirequest.py @@ -177,7 +177,6 @@ def post(foirequestid,foiministryrequestid,actiontype = None,usertype = None): assigneename = getministryassigneename(ministryrequestschema) else: ministryrequestschema = FOIRequestMinistrySchema().load(request_json) - print(ministryrequestschema) result = requestservice().saveministryrequestversion(ministryrequestschema, foirequestid, foiministryrequestid,AuthHelper.getuserid(), usertype) if result.success == True: asyncio.ensure_future(eventservice().postevent(foiministryrequestid,"ministryrequest",AuthHelper.getuserid(),AuthHelper.getusername(), AuthHelper.isministrymember(),assigneename, ministryrequestschema)) diff --git a/request-management-api/request_api/schemas/foirequestwrapper.py b/request-management-api/request_api/schemas/foirequestwrapper.py index 21b11f7fb..483dc7dd0 100644 --- a/request-management-api/request_api/schemas/foirequestwrapper.py +++ b/request-management-api/request_api/schemas/foirequestwrapper.py @@ -152,9 +152,9 @@ class Meta: # pylint: disable=too-few-public-methods """Exclude unknown fields in the deserialized output.""" unknown = EXCLUDE - approvername = fields.Str(data_key="name", allow_none=False) - approvertitle = fields.Str(data_key="title", allow_none=False) - approveddate = fields.Date(data_key="date", allow_none=False) + approvername = fields.Str(data_key="approverName", allow_none=False) + approvertitle = fields.Str(data_key="approverTitle", allow_none=False) + approveddate = fields.Date(data_key="approvedDate", allow_none=False) class FOIRequestMinistrySchema(Schema): diff --git a/request-management-api/request_api/services/events/state.py b/request-management-api/request_api/services/events/state.py index d40bca32d..f935401b7 100644 --- a/request-management-api/request_api/services/events/state.py +++ b/request-management-api/request_api/services/events/state.py @@ -102,7 +102,7 @@ def __formatstate(self, state): def __commentmessage(self, state, username, ministryrequestschema): if state == "Response": - return f"{username} changed the state of the request to {self.__formatstate(state)}. Approved by {ministryrequestschema['ministrysignoffapproval']['approvername']}, {ministryrequestschema['approvertitle']} on {ministryrequestschema['ministrysignoffapproval']['approveddate']}" + return f"{username} changed the state of the request to {self.__formatstate(state)}. Approved by {ministryrequestschema['ministrysignoffapproval']['approvername']}, {ministryrequestschema['ministrysignoffapproval']['approvertitle']} on {ministryrequestschema['ministrysignoffapproval']['approveddate']}" return username+' changed the state of the request to '+self.__formatstate(state) def __notificationmessage(self, state, ministryrequestschema): diff --git a/request-management-api/request_api/services/foirequest/requestserviceministrybuilder.py b/request-management-api/request_api/services/foirequest/requestserviceministrybuilder.py index a0a236f2b..1e7419698 100644 --- a/request-management-api/request_api/services/foirequest/requestserviceministrybuilder.py +++ b/request-management-api/request_api/services/foirequest/requestserviceministrybuilder.py @@ -75,6 +75,7 @@ def createfoiministryrequestfromobject(self, ministryschema, requestschema, user else: foiministryrequest.assignedto = None if usertype == "iao" and 'assignedto' in requestschema and requestschema['assignedto'] in (None, '') else ministryschema["assignedto"] + foiministryrequest.ministrysignoffapproval = ministryschema["ministrysignoffapproval"] foiministryrequest.requeststatusid = requestdict['requeststatusid'] foiministryrequest.programareaid = requestdict['programareaid'] foiministryrequest.createdby = userid From 1bc911c95d35162f9f2c597ac4104cc854e6abc9 Mon Sep 17 00:00:00 2001 From: Milos Despotovic Date: Thu, 7 Sep 2023 09:05:50 -0700 Subject: [PATCH 127/234] Fix toLowerCase bug --- .../Attachments/AttachmentModal.js | 4 ++-- .../FOI/customComponents/Attachments/index.js | 24 +++++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js b/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js index f6b936a58..f64eb1510 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js +++ b/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js @@ -146,8 +146,8 @@ export default function AttachmentModal({ } useEffect(() => { - if (attachment && modalFor == "reclassify") { - setTagValue(attachment.category.toLowerCase()) + if (attachment && attachment.category && modalFor == "reclassify") { + setTagValue(attachment.category?.toLowerCase()) } }, [modalFor, attachment]) diff --git a/forms-flow-web/src/components/FOI/customComponents/Attachments/index.js b/forms-flow-web/src/components/FOI/customComponents/Attachments/index.js index ea0a8f10f..c8a034b9d 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Attachments/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/Attachments/index.js @@ -499,25 +499,29 @@ const Attachment = React.memo(({indexValue, attachment, handlePopupButtonClick, if (['personal', AttachmentLetterCategories.feeestimatefailed.name, AttachmentLetterCategories.feeestimatesuccessful.name, AttachmentLetterCategories.feeestimateletter.name, AttachmentLetterCategories.feeestimatepaymentreceipt.name, AttachmentLetterCategories.feeestimatepaymentcorrespondencesuccessful.name, AttachmentLetterCategories.feeestimatepaymentcorrespondencefailed.name].includes(attachment.category?.toLowerCase()) ) return true; } - const [disabled, setDisabled] = useState(isMinistryCoordinator && disableCategory()); - useEffect(() => { - if(attachment && attachment.filename) { - setDisabled(isMinistryCoordinator && disableCategory()) - } - }, [attachment]) - const disableAttachmentsTaggedBySystem = (attachment) => { let result = false; AttachmentCategories.categorys.forEach((category) => { - if (category.name.toLowerCase() === attachment.category.toLowerCase()) { - if (category.display.toLowerCase().includes('>') || category.display.toLowerCase().includes('applicant')) { + if (category.name?.toLowerCase() === attachment.category?.toLowerCase()) { + if (category.display?.toLowerCase().includes('>') || category.display?.toLowerCase().includes('applicant')) { result = true } } }) return result } - const reclassifyIsDisabled = disableAttachmentsTaggedBySystem(attachment) + + const [disabled, setDisabled] = useState(isMinistryCoordinator && disableCategory()); + const [reclassifyIsDisabled, setReclassifyIsDisabled] = useState(false); + useEffect(() => { + if(attachment && attachment.filename) { + setDisabled(isMinistryCoordinator && disableCategory()) + } + if (attachment && attachment.category) { + setReclassifyIsDisabled(disableAttachmentsTaggedBySystem(attachment)) + } + }, [attachment]) + const attachmenttitle = ()=>{ From 838763c21377ec8dcc424be98e9452edc3e151c9 Mon Sep 17 00:00:00 2001 From: Milos Despotovic Date: Thu, 7 Sep 2023 09:57:01 -0700 Subject: [PATCH 128/234] Fix toLowerCase bug --- .../components/FOI/FOIRequest/MinistryReview/MinistryReview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js index 215fe45d5..d0cd128f1 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js @@ -123,7 +123,7 @@ const MinistryReview = React.memo(({ userDetail }) => { let requestAttachments = useSelector( (state) => state.foiRequests.foiRequestAttachments.filter( attachment => { - if (isMinistryUser && attachment.category.toLowerCase() === 'applicant' && requestDetails.requestType.toLowerCase() === 'personal') return false + if (isMinistryUser && attachment?.category?.toLowerCase() === 'applicant' && requestDetails?.requestType?.toLowerCase() === 'personal') return false return ['feeassessed-onhold', 'fee estimate - payment receipt', 'response-onhold', 'fee balance outstanding - payment receipt'] .indexOf(attachment.category.toLowerCase()) === -1 } From 5b13434ff89246f3e449487b77b6b84de66af60e Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Thu, 7 Sep 2023 11:20:08 -0700 Subject: [PATCH 129/234] Revised solution in backend to incorporate new solution. New solution has signoffapproval data stored in a ministry request and the eventservices now listeins to Response state change and uses this approval data to creat a notif and comment. Testing complted. WIP Refactor --- .../request_api/resources/foirequest.py | 2 +- .../request_api/schemas/foirequestwrapper.py | 2 +- .../request_api/services/events/state.py | 59 ++++++++----------- .../request_api/services/eventservice.py | 8 +-- .../foirequest/requestservicecreate.py | 3 +- .../requestserviceministrybuilder.py | 6 +- 6 files changed, 37 insertions(+), 43 deletions(-) diff --git a/request-management-api/request_api/resources/foirequest.py b/request-management-api/request_api/resources/foirequest.py index bb9af10ce..275f452c6 100644 --- a/request-management-api/request_api/resources/foirequest.py +++ b/request-management-api/request_api/resources/foirequest.py @@ -179,7 +179,7 @@ def post(foirequestid,foiministryrequestid,actiontype = None,usertype = None): ministryrequestschema = FOIRequestMinistrySchema().load(request_json) result = requestservice().saveministryrequestversion(ministryrequestschema, foirequestid, foiministryrequestid,AuthHelper.getuserid(), usertype) if result.success == True: - asyncio.ensure_future(eventservice().postevent(foiministryrequestid,"ministryrequest",AuthHelper.getuserid(),AuthHelper.getusername(), AuthHelper.isministrymember(),assigneename, ministryrequestschema)) + asyncio.ensure_future(eventservice().postevent(foiministryrequestid,"ministryrequest",AuthHelper.getuserid(),AuthHelper.getusername(), AuthHelper.isministrymember(),assigneename)) metadata = json.dumps({"id": result.identifier, "ministries": result.args[0]}) requestservice().posteventtoworkflow(foiministryrequestid, ministryrequestschema, json.loads(metadata),usertype) return {'status': result.success, 'message':result.message,'id':result.identifier, 'ministryRequests': result.args[0]} , 200 diff --git a/request-management-api/request_api/schemas/foirequestwrapper.py b/request-management-api/request_api/schemas/foirequestwrapper.py index 483dc7dd0..efdb027ac 100644 --- a/request-management-api/request_api/schemas/foirequestwrapper.py +++ b/request-management-api/request_api/schemas/foirequestwrapper.py @@ -154,7 +154,7 @@ class Meta: # pylint: disable=too-few-public-methods unknown = EXCLUDE approvername = fields.Str(data_key="approverName", allow_none=False) approvertitle = fields.Str(data_key="approverTitle", allow_none=False) - approveddate = fields.Date(data_key="approvedDate", allow_none=False) + approveddate = fields.Str(data_key="approvedDate", allow_none=False) class FOIRequestMinistrySchema(Schema): diff --git a/request-management-api/request_api/services/events/state.py b/request-management-api/request_api/services/events/state.py index f935401b7..f317f6e7c 100644 --- a/request-management-api/request_api/services/events/state.py +++ b/request-management-api/request_api/services/events/state.py @@ -14,11 +14,11 @@ class stateevent: """ FOI Event management service """ - def createstatetransitionevent(self, requestid, requesttype, userid, username, ministryrequestschema): + def createstatetransitionevent(self, requestid, requesttype, userid, username): state = self.__haschanged(requestid, requesttype) if state is not None: - _commentresponse = self.__createcommentwrapper(requestid, state, requesttype, userid, username, ministryrequestschema) - _notificationresponse = self.__createnotification(requestid, state, requesttype, userid, ministryrequestschema) + _commentresponse = self.__createcommentwrapper(requestid, state, requesttype, userid, username) + _notificationresponse = self.__createnotification(requestid, state, requesttype, userid) _cfrresponse = self.__createcfrentry(state, requestid, userid) if _commentresponse.success == True and _notificationresponse.success == True and _cfrresponse.success == True: return DefaultMethodResult(True,'Comment posted',requestid) @@ -38,30 +38,33 @@ def __haschanged(self, requestid, requesttype): return newstate return None - def __createcommentwrapper(self, requestid, state, requesttype, userid, username, ministryrequestschema): + def __createcommentwrapper(self, requestid, state, requesttype, userid, username): if state == 'Archived': _openedministries = FOIMinistryRequest.getministriesopenedbyuid(requestid) for ministry in _openedministries: - response=self.__createcomment(ministry["ministryrequestid"], state, 'ministryrequest', userid, username, ministryrequestschema) + response=self.__createcomment(ministry["ministryrequestid"], state, 'ministryrequest', userid, username) else: - response=self.__createcomment(requestid, state, requesttype, userid, username, ministryrequestschema) + response=self.__createcomment(requestid, state, requesttype, userid, username) return response - def __createcomment(self, requestid, state, requesttype, userid, username, ministryrequestschema): - comment = self.__preparecomment(requestid, state, requesttype, username, ministryrequestschema) + def __createcomment(self, requestid, state, requesttype, userid, username): + comment = self.__preparecomment(requestid, state, requesttype, username) if requesttype == "ministryrequest": return commentservice().createministryrequestcomment(comment, userid, 2) else: return commentservice().createrawrequestcomment(comment, userid,2) - def __createnotification(self, requestid, state, requesttype, userid, ministryrequestschema): + def __createnotification(self, requestid, state, requesttype, userid): _notificationtype = "State" if state == 'Call For Records' and requesttype == "ministryrequest": foirequest = notificationservice().getrequest(requestid, requesttype) _notificationtype = "Group Members" if foirequest['assignedministryperson'] is None else "State" - notification = self.__preparenotification(state, ministryrequestschema) + notification = self.__preparenotification(state) + if state == "Response" and requesttype == "ministryrequest": + signgoffapproval = FOIMinistryRequest().getrequest(requestid)['ministrysignoffapproval'] + notification = notification + f". Approved by {signgoffapproval['approvername']}, {signgoffapproval['approvertitle']} on {signgoffapproval['approveddate']}" if state == 'Closed' or state == 'Archived' : notificationservice().dismissnotificationsbyrequestid(requestid, requesttype) if state == 'Archived': @@ -81,16 +84,16 @@ def __createnotification(self, requestid, state, requesttype, userid, ministryre return DefaultMethodResult(True,'Notification added',requestid) return DefaultMethodResult(True,'No change',requestid) - def __preparenotification(self, state, ministryrequestschema): - return self.__notificationmessage(state, ministryrequestschema) + def __preparenotification(self, state): + return self.__notificationmessage(state) def __preparegroupmembernotification(self, state, requestid): if state == 'Call For Records': return self.__notificationcfrmessage(requestid) return self.__groupmembernotificationmessage(state) - def __preparecomment(self, requestid, state,requesttype, username, ministryrequestschema): - comment = {"comment": self.__commentmessage(state, username, ministryrequestschema)} + def __preparecomment(self, requestid, state,requesttype, username): + comment = {"comment": self.__commentmessage(state, username, requesttype, requestid)} if requesttype == "ministryrequest": comment['ministryrequestid']= requestid else: @@ -100,14 +103,14 @@ def __preparecomment(self, requestid, state,requesttype, username, ministryreque def __formatstate(self, state): return "Open" if state == "Archived" else state - def __commentmessage(self, state, username, ministryrequestschema): - if state == "Response": - return f"{username} changed the state of the request to {self.__formatstate(state)}. Approved by {ministryrequestschema['ministrysignoffapproval']['approvername']}, {ministryrequestschema['ministrysignoffapproval']['approvertitle']} on {ministryrequestschema['ministrysignoffapproval']['approveddate']}" - return username+' changed the state of the request to '+self.__formatstate(state) + def __commentmessage(self, state, username, requesttype, requestid): + comment = username+' changed the state of the request to '+self.__formatstate(state) + if state == "Response" and requesttype == "ministryrequest": + signgoffapproval = FOIMinistryRequest().getrequest(requestid)['ministrysignoffapproval'] + comment = comment + f". Approved by {signgoffapproval['approvername']}, {signgoffapproval['approvertitle']} on {signgoffapproval['approveddate']}" + return comment - def __notificationmessage(self, state, ministryrequestschema): - if state == "Response": - return f"Moved to {self.__formatstate(state)} State. Approved by {ministryrequestschema['ministrysignoffapproval']['approvername']}, {ministryrequestschema['ministrysignoffapproval']['approvertitle']} on {ministryrequestschema['ministrysignoffapproval']['approveddate']}" + def __notificationmessage(self, state): return 'Moved to '+self.__formatstate(state)+ ' State' def __notificationcfrmessage(self, requestid): @@ -122,16 +125,4 @@ def __createcfrentry(self, state, ministryrequestid, userid): return DefaultMethodResult(True,'No action needed',ministryrequestid) def __groupmembernotificationmessage(self, state): - return 'New request is in '+state - - def create_signoff_approval_event(self, requestid, userid, username, approval): - print("[user] has changed the state of the request to response. Approved by [approver name/title] on [date approved]") - ministryrequest = FOIMinistryRequest().getrequest(requestid) - if ministryrequest.requeststatusid == 10: - comment = f"{username} has changed the state of the request to response. Approved by [approver name/title] on [date approved]" - commentservice().createministryrequestcomment(comment, userid, 2) - ## Create Notification - ## ADD A TRY EXCEPT FINALLY? - ## find ministry request with id, check if state is 10/ministry signoff if so -> create a comment and create a notification (copy code from createevent here??) - ## comment will come with custom message and type 2 id, gotta look into what notif is needed. - ##NEED TO GET THE APPROVAL OBJ HERE SOMEHOW \ No newline at end of file + return 'New request is in '+state \ No newline at end of file diff --git a/request-management-api/request_api/services/eventservice.py b/request-management-api/request_api/services/eventservice.py index dc72c8556..f6763c474 100644 --- a/request-management-api/request_api/services/eventservice.py +++ b/request-management-api/request_api/services/eventservice.py @@ -24,12 +24,12 @@ class eventservice: """ - async def postevent(self, requestid, requesttype, userid, username, isministryuser,assigneename='', ministryrequestschema=None): - self.posteventsync(requestid, requesttype, userid, username, isministryuser,assigneename, ministryrequestschema) + async def postevent(self, requestid, requesttype, userid, username, isministryuser,assigneename=''): + self.posteventsync(requestid, requesttype, userid, username, isministryuser,assigneename) - def posteventsync(self, requestid, requesttype, userid, username, isministryuser,assigneename='', ministryrequestschema=None): + def posteventsync(self, requestid, requesttype, userid, username, isministryuser,assigneename=''): try: - stateeventresponse = stateevent().createstatetransitionevent(requestid, requesttype, userid, username, ministryrequestschema) + stateeventresponse = stateevent().createstatetransitionevent(requestid, requesttype, userid, username) divisioneventresponse = divisionevent().createdivisionevent(requestid, requesttype, userid) assignmentresponse = assignmentevent().createassignmentevent(requestid, requesttype, userid, isministryuser,assigneename,username) if stateeventresponse.success == False or divisioneventresponse.success == False or assignmentresponse.success == False: diff --git a/request-management-api/request_api/services/foirequest/requestservicecreate.py b/request-management-api/request_api/services/foirequest/requestservicecreate.py index daa9f1333..478253e9f 100644 --- a/request-management-api/request_api/services/foirequest/requestservicecreate.py +++ b/request-management-api/request_api/services/foirequest/requestservicecreate.py @@ -66,13 +66,14 @@ def saverequestversion(self,foirequestschema, foirequestid , ministryid, userid) return result def saveministryrequestversion(self,ministryrequestschema, foirequestid , ministryid, userid, usertype = None): - _foirequest = FOIRequest().getrequest(foirequestid) + _foirequest = FOIRequest().getrequest(foirequestid) _foiministryrequest = FOIMinistryRequest().getrequestbyministryrequestid(ministryid) _foirequestapplicant = FOIRequestApplicantMapping().getrequestapplicants(foirequestid,_foirequest["version"]) _foirequestcontact = FOIRequestContactInformation().getrequestcontactinformation(foirequestid,_foirequest["version"]) _foirequestpersonalattrbs = FOIRequestPersonalAttribute().getrequestpersonalattributes(foirequestid,_foirequest["version"]) foiministryrequestarr = [] foirequest = requestserviceministrybuilder().createfoirequestfromobject(_foirequest, userid) + ##IS THERE A BUG IN THE BELOW CODE? FOIMINSTYREQUEST IS PASSED IN IN ARG FIELD OF MINISTRY REQUEST SCHEMA foiministryrequest = requestserviceministrybuilder().createfoiministryrequestfromobject(_foiministryrequest, ministryrequestschema, userid, usertype) foiministryrequestarr.append(foiministryrequest) foirequest.ministryRequests = foiministryrequestarr diff --git a/request-management-api/request_api/services/foirequest/requestserviceministrybuilder.py b/request-management-api/request_api/services/foirequest/requestserviceministrybuilder.py index 1e7419698..942d89c0f 100644 --- a/request-management-api/request_api/services/foirequest/requestserviceministrybuilder.py +++ b/request-management-api/request_api/services/foirequest/requestserviceministrybuilder.py @@ -36,6 +36,7 @@ def createfoirequestfromobject(self, foiobject, userid): return foirequest def createfoiministryrequestfromobject(self, ministryschema, requestschema, userid, usertype = None): + #BUG IN THIS CODE? MINISTRY SCHEMA IS ARG 1 BUT WHEN THIS IS USED IN REQUESTSERVICECREATE, FOIMINISTRY REQUEST IS PASSED IN AS ARG 1 requestdict = self.createfoiministryrequestfromobject1(ministryschema, requestschema) foiministryrequest = FOIMinistryRequest() foiministryrequest.foiministryrequestid = ministryschema["foiministryrequestid"] @@ -75,7 +76,7 @@ def createfoiministryrequestfromobject(self, ministryschema, requestschema, user else: foiministryrequest.assignedto = None if usertype == "iao" and 'assignedto' in requestschema and requestschema['assignedto'] in (None, '') else ministryschema["assignedto"] - foiministryrequest.ministrysignoffapproval = ministryschema["ministrysignoffapproval"] + foiministryrequest.ministrysignoffapproval = requestdict["ministrysignoffapproval"] foiministryrequest.requeststatusid = requestdict['requeststatusid'] foiministryrequest.programareaid = requestdict['programareaid'] foiministryrequest.createdby = userid @@ -115,7 +116,8 @@ def createfoiministryrequestfromobject1(self, ministryschema, requestschema): 'programareaid': ministryschema["programarea.programareaid"] if 'programarea.programareaid' in ministryschema else None, 'closedate': requestschema['closedate'] if 'closedate' in requestschema else None, 'closereasonid': requestschema['closereasonid'] if 'closereasonid' in requestschema else None, - 'linkedrequests': ministryschema['linkedrequests'] if 'linkedrequests' in ministryschema else None + 'linkedrequests': requestschema['linkedrequests'] if 'linkedrequests' in requestschema else None, + 'ministrysignoffapproval': requestschema['ministrysignoffapproval'] if 'ministrysignoffapproval' in requestschema else None, } def createfoirequestdocuments(self,requestschema, ministryrequestid, activeversion, userid): From 9b65257904449b0549f4c6640f9d274518c854c5 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Thu, 7 Sep 2023 12:49:04 -0700 Subject: [PATCH 130/234] Small refactoring completed --- .../FOI/customComponents/ConfirmationModal/index.js | 4 +--- .../components/FOI/customComponents/ConfirmationModal/util.js | 2 +- .../request_api/services/foirequest/requestservicecreate.py | 1 - .../services/foirequest/requestserviceministrybuilder.py | 1 - 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/index.js b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/index.js index f13d7836b..40de92363 100644 --- a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/index.js @@ -123,13 +123,11 @@ export default function ConfirmationModal({requestId, openModal, handleModal, st || (state.toLowerCase() === StateEnum.onhold.name.toLowerCase() && amountDue === 0) || (state.toLowerCase() === StateEnum.onhold.name.toLowerCase() && cfrStatus !== 'approved') || (state.toLowerCase() === StateEnum.onhold.name.toLowerCase() && cfrStatus === 'approved' && !saveRequestObject.email && !mailed) + || (state.toLowerCase() === StateEnum.response.name.toLowerCase() && !(ministryApprovalState?.approverName && ministryApprovalState?.approvedDate && ministryApprovalState?.approverTitle)) || ((state.toLowerCase() === StateEnum.deduplication.name.toLowerCase() || state.toLowerCase() === StateEnum.review.name.toLowerCase()) && !allowStateChange)) { return true; } - else if ((state.toLowerCase() === StateEnum.response.name.toLowerCase() && !(ministryApprovalState?.approverName && ministryApprovalState?.approvedDate && ministryApprovalState?.approverTitle))) { - return true; - } return files.length === 0 && ( ( diff --git a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js index 10eba78eb..5343aa99b 100644 --- a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js +++ b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js @@ -117,7 +117,7 @@ import { getFullnameList } from "../../../../helper/FOI/helper"; } case StateEnum.response.name.toLowerCase(): if (_saveRequestObject.requeststatusid === StateEnum.signoff.id) - return {title: "Ministry Sign Off", body: `Upload eApproval Logs to verify Ministry Approval and change the state.`}; + return {title: "Ministry Sign Off", body: `Upload eApproval Logs, and enter in required approval fields to verify Ministry Approval and then change the state.`}; else return {title: "Changing the state", body: `Are you sure you want to change Request #${_requestNumber} to ${StateEnum.response.name}?`}; default: diff --git a/request-management-api/request_api/services/foirequest/requestservicecreate.py b/request-management-api/request_api/services/foirequest/requestservicecreate.py index 478253e9f..6d4b2142f 100644 --- a/request-management-api/request_api/services/foirequest/requestservicecreate.py +++ b/request-management-api/request_api/services/foirequest/requestservicecreate.py @@ -73,7 +73,6 @@ def saveministryrequestversion(self,ministryrequestschema, foirequestid , minist _foirequestpersonalattrbs = FOIRequestPersonalAttribute().getrequestpersonalattributes(foirequestid,_foirequest["version"]) foiministryrequestarr = [] foirequest = requestserviceministrybuilder().createfoirequestfromobject(_foirequest, userid) - ##IS THERE A BUG IN THE BELOW CODE? FOIMINSTYREQUEST IS PASSED IN IN ARG FIELD OF MINISTRY REQUEST SCHEMA foiministryrequest = requestserviceministrybuilder().createfoiministryrequestfromobject(_foiministryrequest, ministryrequestschema, userid, usertype) foiministryrequestarr.append(foiministryrequest) foirequest.ministryRequests = foiministryrequestarr diff --git a/request-management-api/request_api/services/foirequest/requestserviceministrybuilder.py b/request-management-api/request_api/services/foirequest/requestserviceministrybuilder.py index 942d89c0f..f35af8e74 100644 --- a/request-management-api/request_api/services/foirequest/requestserviceministrybuilder.py +++ b/request-management-api/request_api/services/foirequest/requestserviceministrybuilder.py @@ -36,7 +36,6 @@ def createfoirequestfromobject(self, foiobject, userid): return foirequest def createfoiministryrequestfromobject(self, ministryschema, requestschema, userid, usertype = None): - #BUG IN THIS CODE? MINISTRY SCHEMA IS ARG 1 BUT WHEN THIS IS USED IN REQUESTSERVICECREATE, FOIMINISTRY REQUEST IS PASSED IN AS ARG 1 requestdict = self.createfoiministryrequestfromobject1(ministryschema, requestschema) foiministryrequest = FOIMinistryRequest() foiministryrequest.foiministryrequestid = ministryschema["foiministryrequestid"] From 4080f25c6814be94f3a606f586227374aa742564 Mon Sep 17 00:00:00 2001 From: Milos Despotovic Date: Thu, 7 Sep 2023 13:45:12 -0700 Subject: [PATCH 131/234] Update SQL query to return created_at date for current documentpath --- .../models/FOIMinistryRequestDocuments.py | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/request-management-api/request_api/models/FOIMinistryRequestDocuments.py b/request-management-api/request_api/models/FOIMinistryRequestDocuments.py index 51712605d..3acdb80f2 100644 --- a/request-management-api/request_api/models/FOIMinistryRequestDocuments.py +++ b/request-management-api/request_api/models/FOIMinistryRequestDocuments.py @@ -47,7 +47,29 @@ def getdocuments(cls,ministryrequestid,ministryrequestversion): @classmethod def getactivedocuments(cls,ministryrequestid): - sql = 'SELECT * FROM (SELECT DISTINCT ON (foiministrydocumentid) fmrd2.created_at, fmrd.foiministrydocumentid, fmrd.filename, fmrd.documentpath, fmrd.category, fmrd.isactive, fmrd.created_at as current_version_created_at, fmrd.createdby, fmrd.version FROM "FOIMinistryRequestDocuments" fmrd join "FOIMinistryRequestDocuments" fmrd2 on (fmrd2.foiministrydocumentid = fmrd.foiministrydocumentid and fmrd2.version = 1) where fmrd.foiministryrequest_id =:ministryrequestid ORDER BY fmrd.foiministrydocumentid, version DESC) AS list ORDER BY created_at DESC' + sql = ''' + WITH document AS ( + SELECT documentpath, min(created_at) AS created_at + FROM "FOIMinistryRequestDocuments" + GROUP BY documentpath + ) + SELECT * FROM ( + SELECT DISTINCT ON (foiministrydocumentid) + doc.created_at, + fmrd.foiministrydocumentid, + fmrd.filename, + fmrd.documentpath, + fmrd.category, + fmrd.isactive, + fmrd.created_at as current_version_created_at, + fmrd.createdby, + fmrd.version + FROM "FOIMinistryRequestDocuments" fmrd + JOIN document doc + on doc.documentpath = fmrd.documentpath + where fmrd.foiministryrequest_id =:ministryrequestid ORDER BY fmrd.foiministrydocumentid, version DESC) AS list + ORDER BY created_at DESC + ''' rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) documents = [] for row in rs: From 58215124bfcda61d372cf77066b4363cfc37aa48 Mon Sep 17 00:00:00 2001 From: divyav-aot Date: Thu, 7 Sep 2023 17:36:55 -0400 Subject: [PATCH 132/234] removed dependacny of recordsuploadformats.json from harms download --- .../FOI/customComponents/Records/util.js | 282 ++++++++++-------- .../services/records/recordservicegetter.py | 255 ++++++++++------ 2 files changed, 321 insertions(+), 216 deletions(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/util.js b/forms-flow-web/src/components/FOI/customComponents/Records/util.js index e1e452ddf..6e076792d 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/util.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/util.js @@ -1,142 +1,176 @@ //Remove duplicate records -export const removeDuplicateFiles = (recordList) => ( - recordList.filter(({isduplicate}) => !isduplicate) - ); - - // Helper function to sort files by lastmodified date -export const sortByLastModified = (files) => ( - files.sort((a, b) => new Date(a.lastmodified) - new Date(b.lastmodified)) - ); +export const removeDuplicateFiles = (recordList) => + recordList.filter(({ isduplicate }) => !isduplicate); + +// Helper function to sort files by lastmodified date +export const sortByLastModified = (files) => + files.sort((a, b) => new Date(a.lastmodified) - new Date(b.lastmodified)); // Helper function to sort attachments by lastmodified date -const sortAttachmentsByLastModified = (attachments) => ( - attachments.sort((a, b) => new Date(a?.attributes?.lastmodified) - new Date(b?.attributes?.lastmodified)) +const sortAttachmentsByLastModified = (attachments) => + attachments.sort( + (a, b) => + new Date(a?.attributes?.lastmodified) - + new Date(b?.attributes?.lastmodified) ); - + export const getPDFFilePath = (item, conversionFormats) => { - - let pdffilepath = item.s3uripath - let pdffilename = item.filename - - if (((item.isredactionready && conversionFormats.includes(item.attributes?.extension?.toLowerCase())) || - (item.attributes?.isattachment && item.attributes?.trigger === 'recordreplace'))) { - pdffilepath = pdffilepath.substr(0, pdffilepath.lastIndexOf(".")) + ".pdf"; - pdffilename += ".pdf"; - } - return [pdffilepath, pdffilename]; - } - - function arrangeAttachments(attachments, parentDocumentMasterId, conversionFormats) { - const attachmentsMap = {}; - const arrangedAttachments = []; - - // Create a map of attachments based on parentid - for (const attachment of attachments) { - const parentid = attachment.parentid; - if (!attachmentsMap[parentid]) { - attachmentsMap[parentid] = []; - } - attachmentsMap[parentid].push(attachment); + let pdffilepath = item.s3uripath; + let pdffilename = item.filename; + + console.log(`item = ${JSON.stringify(item)}`); + + if ( + (item.isredactionready && item.isconverted) || + (item.attributes?.isattachment && + item.attributes?.trigger === "recordreplace") + ) { + pdffilepath = pdffilepath.substr(0, pdffilepath.lastIndexOf(".")) + ".pdf"; + pdffilename += ".pdf"; + } + return [pdffilepath, pdffilename]; +}; + +function arrangeAttachments( + attachments, + parentDocumentMasterId, + conversionFormats +) { + const attachmentsMap = {}; + const arrangedAttachments = []; + + // Create a map of attachments based on parentid + for (const attachment of attachments) { + const parentid = attachment.parentid; + if (!attachmentsMap[parentid]) { + attachmentsMap[parentid] = []; } - - // Recursive function to arrange attachments - function arrangeChildren(parentid) { - const children = attachmentsMap[parentid]; - if (children) { - for (const child of children) { - arrangedAttachments.push(child); - arrangeChildren(child.documentmasterid); - } + attachmentsMap[parentid].push(attachment); + } + + // Recursive function to arrange attachments + function arrangeChildren(parentid) { + const children = attachmentsMap[parentid]; + if (children) { + for (const child of children) { + arrangedAttachments.push(child); + arrangeChildren(child.documentmasterid); } } - - // Start arranging attachments from the root level - arrangeChildren(parentDocumentMasterId); - getUpdatedRecords(arrangedAttachments, conversionFormats, true) - return getUpdatedRecords(arrangedAttachments, conversionFormats, true) - } - - // Get records with only necessary fields - export const getUpdatedRecords = (_records, conversionFormats, isattachment=false) =>{ - return _records.map(_record => { - const [filepath, filename] = getPDFFilePath(_record, conversionFormats) - const deduplicatedAttachments = _record?.attachments?.length > 0 ? removeDuplicateFiles(_record.attachments): [] - const sortedAttachments = sortAttachmentsByLastModified(deduplicatedAttachments) - const _recordObj = - { - recordid: _record.recordid, - filename: filename, - s3uripath: filepath, - filesize: _record.attributes.convertedfilesize || _record.attributes.filesize, - lastmodified: _record.attributes.lastmodified, - isduplicate: _record.isduplicate, - divisions: _record.attributes.divisions, - divisionids: _record.attributes.divisions.map(d => d.divisionid), - attachments: !isattachment ? arrangeAttachments(sortedAttachments, _record.documentmasterid, conversionFormats) : undefined - } - return _recordObj - - }) - } - - //Get files for specified divisionid with necessary fields - export const getFiles = (_records, _divisionid) => { - return _records.filter(_record => _record.divisionids.includes(_divisionid)).map(r => { - return ({ - recordid: r.recordid, - filename: r.filename, - s3uripath: r.s3uripath, - filesize: r.filesize, - lastmodified: r.lastmodified - }) - }) - } - - // calculate divisional total file size - export const calculateDivisionFileSize = (_files) => { - return _files.reduce((total, _file) => { - return total + +_file.filesize; - }, 0); - } - - // calculate final file size + + // Start arranging attachments from the root level + arrangeChildren(parentDocumentMasterId); + getUpdatedRecords(arrangedAttachments, conversionFormats, true); + return getUpdatedRecords(arrangedAttachments, conversionFormats, true); +} + +// Get records with only necessary fields +export const getUpdatedRecords = ( + _records, + conversionFormats, + isattachment = false +) => { + return _records.map((_record) => { + const [filepath, filename] = getPDFFilePath(_record, conversionFormats); + const deduplicatedAttachments = + _record?.attachments?.length > 0 + ? removeDuplicateFiles(_record.attachments) + : []; + const sortedAttachments = sortAttachmentsByLastModified( + deduplicatedAttachments + ); + const _recordObj = { + recordid: _record.recordid, + filename: filename, + s3uripath: filepath, + filesize: + _record.attributes.convertedfilesize || _record.attributes.filesize, + lastmodified: _record.attributes.lastmodified, + isduplicate: _record.isduplicate, + divisions: _record.attributes.divisions, + divisionids: _record.attributes.divisions.map((d) => d.divisionid), + attachments: !isattachment + ? arrangeAttachments( + sortedAttachments, + _record.documentmasterid, + conversionFormats + ) + : undefined, + }; + return _recordObj; + }); +}; + +//Get files for specified divisionid with necessary fields +export const getFiles = (_records, _divisionid) => { + return _records + .filter((_record) => _record.divisionids.includes(_divisionid)) + .map((r) => { + return { + recordid: r.recordid, + filename: r.filename, + s3uripath: r.s3uripath, + filesize: r.filesize, + lastmodified: r.lastmodified, + }; + }); +}; + +// calculate divisional total file size +export const calculateDivisionFileSize = (_files) => { + return _files.reduce((total, _file) => { + return total + +_file.filesize; + }, 0); +}; + +// calculate final file size export const calculateTotalFileSize = (divisions) => { - return divisions.reduce((total, division) => { - return total + division.divisionfilesize; - }, 0); - } + return divisions.reduce((total, division) => { + return total + division.divisionfilesize; + }, 0); +}; export const addDeduplicatedAttachmentsToRecords = (exporting) => { - for (let record of exporting) { - if (record.attachments) for (let attachment of record.attachments) { - if (!attachment.isduplicate) exporting.push(attachment); - } - } - return exporting; -} + for (let record of exporting) { + if (record.attachments) + for (let attachment of record.attachments) { + if (!attachment.isduplicate) exporting.push(attachment); + } + } + return exporting; +}; export const sortDivisionalFiles = (divisionMap) => { - return Array.from(divisionMap.values()).map(({ divisionid, divisionname, files, divisionfilesize }) => ({ - divisionid, - divisionname, - files: files.sort((a, b) => new Date(a.lastmodified) - new Date(b.lastmodified)), - divisionfilesize - })); -} + return Array.from(divisionMap.values()).map( + ({ divisionid, divisionname, files, divisionfilesize }) => ({ + divisionid, + divisionname, + files: files.sort( + (a, b) => new Date(a.lastmodified) - new Date(b.lastmodified) + ), + divisionfilesize, + }) + ); +}; export const calculateTotalUploadedFileSizeInKB = (records) => { - return records?.reduce((total, record) => { - return total + (record.attributes.convertedfilesize ? record.attributes.convertedfilesize : record.attributes.filesize); - }, 0); -} + return records?.reduce((total, record) => { + return ( + total + + (record.attributes.convertedfilesize + ? record.attributes.convertedfilesize + : record.attributes.filesize) + ); + }, 0); +}; export const getReadableFileSize = (mb) => { - if (mb < 1) { - return (mb * 1024).toFixed(4) + ' KB' - } else if (mb > 1000) { - return (mb/ 1024).toFixed(4) + ' GB' - } else { - return mb.toFixed(4) + ' MB' - } - } \ No newline at end of file + if (mb < 1) { + return (mb * 1024).toFixed(4) + " KB"; + } else if (mb > 1000) { + return (mb / 1024).toFixed(4) + " GB"; + } else { + return mb.toFixed(4) + " MB"; + } +}; diff --git a/request-management-api/request_api/services/records/recordservicegetter.py b/request-management-api/request_api/services/records/recordservicegetter.py index 9f335796f..b012e2a50 100644 --- a/request-management-api/request_api/services/records/recordservicegetter.py +++ b/request-management-api/request_api/services/records/recordservicegetter.py @@ -1,5 +1,4 @@ - -from os import stat, path,getenv +from os import stat, path, getenv from request_api.models.FOIRequestRecords import FOIRequestRecord from request_api.models.FOIMinistryRequests import FOIMinistryRequest from request_api.models.ProgramAreaDivisions import ProgramAreaDivision @@ -9,120 +8,174 @@ import uuid import logging import copy -from request_api.services.records.recordservicebase import recordservicebase +from request_api.services.records.recordservicebase import recordservicebase + class recordservicegetter(recordservicebase): - """ This class consolidates retrival of FOI Records for actors: iao and ministry. - """ + """This class consolidates retrival of FOI Records for actors: iao and ministry.""" def fetch(self, requestid, ministryrequestid): - result = {'dedupedfiles': 0, 'convertedfiles': 0, 'removedfiles': 0} - try: + result = {"dedupedfiles": 0, "convertedfiles": 0, "removedfiles": 0} + try: _metadata = FOIMinistryRequest.getmetadata(ministryrequestid) - divisions = ProgramAreaDivision.getprogramareadivisions(_metadata["programareaid"]) - uploadedrecords = FOIRequestRecord.fetch(requestid, ministryrequestid) + divisions = ProgramAreaDivision.getprogramareadivisions( + _metadata["programareaid"] + ) + uploadedrecords = FOIRequestRecord.fetch(requestid, ministryrequestid) batchids = [] resultrecords = [] - if len(uploadedrecords) > 0: - computingresponses, err = self.makedocreviewerrequest('GET', '/api/dedupestatus/{0}'.format(ministryrequestid)) - if err is None: - _convertedfiles, _dedupedfiles, _removedfiles = self.__getcomputingsummary(computingresponses) + if len(uploadedrecords) > 0: + computingresponses, err = self.makedocreviewerrequest( + "GET", "/api/dedupestatus/{0}".format(ministryrequestid) + ) + if err is None: + ( + _convertedfiles, + _dedupedfiles, + _removedfiles, + ) = self.__getcomputingsummary(computingresponses) for record in uploadedrecords: - _computingresponse = self.__getcomputingresponse(computingresponses, "recordid", record) - _record = self.__preparerecord(record,_computingresponse, computingresponses,divisions) - if not _record['attributes'].get('isportfolio', False): + _computingresponse = self.__getcomputingresponse( + computingresponses, "recordid", record + ) + _record = self.__preparerecord( + record, _computingresponse, computingresponses, divisions + ) + if not _record["attributes"].get("isportfolio", False): resultrecords.append(_record) if record["batchid"] not in batchids: - batchids.append(record["batchid"]) - if computingresponses not in (None, []) and len(computingresponses) > 0: - resultrecords = self.__handleduplicate(resultrecords) - result["convertedfiles"] = _convertedfiles + batchids.append(record["batchid"]) + if ( + computingresponses not in (None, []) + and len(computingresponses) > 0 + ): + resultrecords = self.__handleduplicate(resultrecords) + result["convertedfiles"] = _convertedfiles result["dedupedfiles"] = _dedupedfiles - result["removedfiles"] = _removedfiles - result['batchcount'] = len(batchids) + result["removedfiles"] = _removedfiles + result["batchcount"] = len(batchids) result["records"] = resultrecords except Exception as exp: - print("ERROR Happened while fetching records :.{0}".format(exp)) + print("ERROR Happened while fetching records :.{0}".format(exp)) logging.info(exp) raise exp - return result + return result - def __preparerecord(self, record, _computingresponse, computingresponses, divisions): + def __preparerecord( + self, record, _computingresponse, computingresponses, divisions + ): _record = self.__pstformat(record) if _computingresponse not in (None, []): documentmasterid = _computingresponse["documentmasterid"] - _record['isduplicate'] = _computingresponse['isduplicate'] - _record['attributes'] = self.__formatrecordattributes(_computingresponse['attributes'], divisions) - _record['isredactionready'] = _computingresponse['isredactionready'] - _record['trigger'] = _computingresponse['trigger'] - _record['documentmasterid'] = _computingresponse["documentmasterid"] - _record['outputdocumentmasterid'] = documentmasterid - _record['isselected'] = False + _record["isduplicate"] = _computingresponse["isduplicate"] + _record["attributes"] = self.__formatrecordattributes( + _computingresponse["attributes"], divisions + ) + _record["isredactionready"] = _computingresponse["isredactionready"] + _record["trigger"] = _computingresponse["trigger"] + _record["documentmasterid"] = _computingresponse["documentmasterid"] + _record["outputdocumentmasterid"] = documentmasterid + _record["isselected"] = False + _record["isconverted"] = self.__getisconverted(_computingresponse) _computingresponse_err = self.__getcomputingerror(_computingresponse) if _computingresponse_err is not None: - _record['failed'] = _computingresponse_err - _record['attachments'] = [] - if _computingresponse['isduplicate']: - _record['duplicatemasterid'] = _computingresponse['duplicatemasterid'] - _record['duplicateof'] = _computingresponse['duplicateof'] + _record["failed"] = _computingresponse_err + _record["attachments"] = [] + if _computingresponse["isduplicate"]: + _record["duplicatemasterid"] = _computingresponse["duplicatemasterid"] + _record["duplicateof"] = _computingresponse["duplicateof"] for attachment in _computingresponse["attachments"]: _attachement = self.__pstformat(attachment) - _attachement['isattachment'] = True - _attachement['s3uripath'] = attachment['filepath'] - _attachement['rootparentid'] = record["recordid"] - _attachement['rootdocumentmasterid'] = record["documentmasterid"] - _attachement['createdby'] = record['createdby'] - _attachement['isselected'] = False - _attachement['attributes'] = self.__formatrecordattributes(attachment['attributes'], divisions) + _attachement["isattachment"] = True + _attachement["s3uripath"] = attachment["filepath"] + _attachement["rootparentid"] = record["recordid"] + _attachement["rootdocumentmasterid"] = record["documentmasterid"] + _attachement["createdby"] = record["createdby"] + _attachement["isselected"] = False + _attachement["attributes"] = self.__formatrecordattributes( + attachment["attributes"], divisions + ) + _attachement["isconverted"] = self.__getisconverted(attachment) _computingresponse_err = self.__getcomputingerror(attachment) if _computingresponse_err is not None: - _attachement['failed'] = _computingresponse_err - _record['attachments'].append(_attachement) + _attachement["failed"] = _computingresponse_err + _record["attachments"].append(_attachement) else: - _record['attributes'] = self.__formatrecordattributes(_record['attributes'], divisions) - + _record["attributes"] = self.__formatrecordattributes( + _record["attributes"], divisions + ) + return _record - + def __formatrecordattributes(self, attributes, divisions): if isinstance(attributes, str): attributes = json.loads(attributes) - attribute_divisions = attributes.get('divisions', []) + attribute_divisions = attributes.get("divisions", []) for division in attribute_divisions: - _divisionname = self.__getdivisionname(divisions, division['divisionid']) - division['divisionname'] = _divisionname.replace(u"’", u"'") if _divisionname is not None else '' - return attributes - - def __handleduplicate(self, resultrecords): + _divisionname = self.__getdivisionname(divisions, division["divisionid"]) + division["divisionname"] = ( + _divisionname.replace("’", "'") if _divisionname is not None else "" + ) + return attributes + + def __handleduplicate(self, resultrecords): _resultrecords = copy.deepcopy(resultrecords) - for result in resultrecords: - if self.isvalid("isduplicate",result) and result["isduplicate"] == True and self.isvalid("duplicatemasterid",result): - _resultrecords = self.__mergeduplicatedivisions(resultrecords, result["duplicatemasterid"], result["documentmasterid"], self.__getrecorddivisions(result["attributes"])) + for result in resultrecords: + if ( + self.isvalid("isduplicate", result) + and result["isduplicate"] == True + and self.isvalid("duplicatemasterid", result) + ): + _resultrecords = self.__mergeduplicatedivisions( + resultrecords, + result["duplicatemasterid"], + result["documentmasterid"], + self.__getrecorddivisions(result["attributes"]), + ) if "attachments" in result: for attachment in result["attachments"]: - if self.isvalid("isduplicate",attachment) and attachment["isduplicate"] == True and self.isvalid("duplicatemasterid", attachment): - _resultrecords = self.__mergeduplicatedivisions(resultrecords, attachment["duplicatemasterid"], attachment["documentmasterid"], self.__getrecorddivisions(attachment["attributes"])) - return _resultrecords - + if ( + self.isvalid("isduplicate", attachment) + and attachment["isduplicate"] == True + and self.isvalid("duplicatemasterid", attachment) + ): + _resultrecords = self.__mergeduplicatedivisions( + resultrecords, + attachment["duplicatemasterid"], + attachment["documentmasterid"], + self.__getrecorddivisions(attachment["attributes"]), + ) + return _resultrecords - def __mergeduplicatedivisions(self, _resultrecords, duplicatemasterid, resultmasterid, divisions): + def __mergeduplicatedivisions( + self, _resultrecords, duplicatemasterid, resultmasterid, divisions + ): for entry in _resultrecords: - if "documentmasterid" in entry and int(entry["documentmasterid"]) == int(duplicatemasterid): + if "documentmasterid" in entry and int(entry["documentmasterid"]) == int( + duplicatemasterid + ): entattributes = entry["attributes"] - entattributes["divisions"] = self.__mergedivisions(entattributes, divisions, resultmasterid) + entattributes["divisions"] = self.__mergedivisions( + entattributes, divisions, resultmasterid + ) entry["attributes"] = entattributes if "attachments" in entry: for attachment in entry["attachments"]: - if "documentmasterid" in attachment and int(attachment["documentmasterid"]) == int(duplicatemasterid): + if "documentmasterid" in attachment and int( + attachment["documentmasterid"] + ) == int(duplicatemasterid): attattributes = attachment["attributes"] - attattributes["divisions"] = self.__mergedivisions(attachment["attributes"], divisions) + attattributes["divisions"] = self.__mergedivisions( + attachment["attributes"], divisions + ) attachment["attributes"] = attattributes return _resultrecords - + def __getrecorddivisions(self, _attributes): if isinstance(_attributes, str): - _attributes = json.loads(_attributes) - return _attributes.get('divisions', []) - + _attributes = json.loads(_attributes) + return _attributes.get("divisions", []) + def __mergedivisions(self, _attributes, divisions, resultmasterid=False): srcdivisions = self.__getrecorddivisions(_attributes) merged = srcdivisions @@ -139,7 +192,12 @@ def __mergedivisions(self, _attributes, divisions, resultmasterid=False): def __getcomputingresponse(self, response, filterby, data: any): if filterby == "recordid": - filtered_response = [x for x in response if x["recordid"] == data["recordid"] and x["filename"] == data["filename"]] + filtered_response = [ + x + for x in response + if x["recordid"] == data["recordid"] + and x["filename"] == data["filename"] + ] return filtered_response[0] if len(filtered_response) > 0 else [] elif filterby == "parentid": filtered_response = [x for x in response if x["isattachment"] == True] @@ -150,48 +208,61 @@ def __getcomputingresponse(self, response, filterby, data: any): def __getattachments(self, response, result, data): filtered, result = self.__attachments2(response, result, data) for subentry in result: - filtered, result = self.__attachments2(filtered, result, subentry["documentmasterid"]) + filtered, result = self.__attachments2( + filtered, result, subentry["documentmasterid"] + ) return result - + def __attachments2(self, response, result, data): filtered = [] for entry in response: - if entry["parentid"] not in [None, ""] and int(entry["parentid"]) == int(data): + if entry["parentid"] not in [None, ""] and int(entry["parentid"]) == int( + data + ): result.append(entry) else: filtered.append(entry) - return filtered, result + return filtered, result + + def __getisconverted(self, _computingresponse): + if _computingresponse["conversionstatus"] == "completed": + return True + return False def __getcomputingerror(self, computingresponse): - if computingresponse['conversionstatus'] == 'error': - return 'conversion' - elif computingresponse['deduplicationstatus'] == 'error': - return 'deduplication' - return None - + if computingresponse["conversionstatus"] == "error": + return "conversion" + elif computingresponse["deduplicationstatus"] == "error": + return "deduplication" + return None + def __getcomputingsummary(self, computingresponse): _convertedfiles = _dedupedfiles = _removedfiles = 0 for entry in computingresponse: if entry["conversionstatus"] == "completed": - _convertedfiles += 1 + _convertedfiles += 1 if entry["deduplicationstatus"] == "completed": - _dedupedfiles += 1 + _dedupedfiles += 1 if entry["isduplicate"] == True: - _removedfiles += 1 - if entry['isredactionready'] == False and (entry["conversionstatus"] != "completed" or entry["deduplicationstatus"] != "completed"): + _removedfiles += 1 + if entry["isredactionready"] == False and ( + entry["conversionstatus"] != "completed" + or entry["deduplicationstatus"] != "completed" + ): _dedupedfiles += 1 return _convertedfiles, _dedupedfiles, _removedfiles - + def __pstformat(self, record): if type(record["created_at"]) is str and "|" in record["created_at"]: return record - formatedcreateddate = maya.parse(record['created_at']).datetime(to_timezone='America/Vancouver', naive=False) - record['created_at'] = formatedcreateddate.strftime('%Y %b %d | %I:%M %p') + formatedcreateddate = maya.parse(record["created_at"]).datetime( + to_timezone="America/Vancouver", naive=False + ) + record["created_at"] = formatedcreateddate.strftime("%Y %b %d | %I:%M %p") return record - def __getdivisionname(self, divisions, divisionid): for division in divisions: - if division['divisionid'] == divisionid: - return division['name'] + if division["divisionid"] == divisionid: + return division["name"] return None From 3650da121d2f28a2561021c366913986e09450ab Mon Sep 17 00:00:00 2001 From: divyav-aot Date: Thu, 7 Sep 2023 18:56:04 -0400 Subject: [PATCH 133/234] removed dependacny of recordsuploadformats.json from harms download --- .../FOI/customComponents/Records/index.js | 2796 ++++++++++------- .../FOI/customComponents/Records/util.js | 26 +- 2 files changed, 1723 insertions(+), 1099 deletions(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/index.js b/forms-flow-web/src/components/FOI/customComponents/Records/index.js index 9b46bee74..f7ae983b1 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/index.js @@ -1,18 +1,53 @@ -import React, { useEffect, useState } from 'react' -import '../Attachments/attachments.scss' -import './records.scss' +import React, { useEffect, useState } from "react"; +import "../Attachments/attachments.scss"; +import "./records.scss"; import { useDispatch, useSelector } from "react-redux"; -import AttachmentModal from '../Attachments/AttachmentModal'; +import AttachmentModal from "../Attachments/AttachmentModal"; import Loading from "../../../../containers/Loading"; -import { getOSSHeaderDetails, saveFilesinS3, getFileFromS3, postFOIS3DocumentPreSignedUrl, getFOIS3DocumentPreSignedUrl, completeMultiPartUpload } from "../../../../apiManager/services/FOI/foiOSSServices"; -import { saveFOIRequestAttachmentsList, replaceFOIRequestAttachment, saveNewFilename, deleteFOIRequestAttachment } from "../../../../apiManager/services/FOI/foiAttachmentServices"; -import { fetchFOIRecords, saveFOIRecords, updateFOIRecords, retryFOIRecordProcessing,replaceFOIRecordProcessing, deleteReviewerRecords, getRecordFormats, triggerDownloadFOIRecordsForHarms, fetchPDFStitchedRecordForHarms, checkForRecordsChange } from "../../../../apiManager/services/FOI/foiRecordServices"; -import { StateTransitionCategories, AttachmentCategories } from '../../../../constants/FOI/statusEnum' -import { RecordsDownloadList, RecordDownloadCategory,MimeTypeList } from '../../../../constants/FOI/enum'; -import { addToFullnameList, getFullnameList, ConditionalComponent, isrecordtimeout } from '../../../../helper/FOI/helper'; +import { + getOSSHeaderDetails, + saveFilesinS3, + getFileFromS3, + postFOIS3DocumentPreSignedUrl, + getFOIS3DocumentPreSignedUrl, + completeMultiPartUpload, +} from "../../../../apiManager/services/FOI/foiOSSServices"; +import { + saveFOIRequestAttachmentsList, + replaceFOIRequestAttachment, + saveNewFilename, + deleteFOIRequestAttachment, +} from "../../../../apiManager/services/FOI/foiAttachmentServices"; +import { + fetchFOIRecords, + saveFOIRecords, + updateFOIRecords, + retryFOIRecordProcessing, + replaceFOIRecordProcessing, + deleteReviewerRecords, + getRecordFormats, + triggerDownloadFOIRecordsForHarms, + fetchPDFStitchedRecordForHarms, + checkForRecordsChange, +} from "../../../../apiManager/services/FOI/foiRecordServices"; +import { + StateTransitionCategories, + AttachmentCategories, +} from "../../../../constants/FOI/statusEnum"; +import { + RecordsDownloadList, + RecordDownloadCategory, + MimeTypeList, +} from "../../../../constants/FOI/enum"; +import { + addToFullnameList, + getFullnameList, + ConditionalComponent, + isrecordtimeout, +} from "../../../../helper/FOI/helper"; import Grid from "@material-ui/core/Grid"; import { makeStyles } from "@material-ui/core/styles"; -import clsx from "clsx" +import clsx from "clsx"; import Chip from "@material-ui/core/Chip"; import Divider from "@material-ui/core/Divider"; import Popover from "@material-ui/core/Popover"; @@ -20,40 +55,68 @@ import MoreHorizIcon from "@material-ui/icons/MoreHoriz"; import IconButton from "@material-ui/core/IconButton"; import MenuList from "@material-ui/core/MenuList"; import MenuItem from "@material-ui/core/MenuItem"; -import TextField from '@mui/material/TextField'; -import CircularProgress from '@mui/material/CircularProgress'; +import TextField from "@mui/material/TextField"; +import CircularProgress from "@mui/material/CircularProgress"; import { saveAs } from "file-saver"; import { downloadZip } from "client-zip"; -import AttachmentFilter from '../Attachments/AttachmentFilter'; -import Accordion from '@material-ui/core/Accordion'; -import { ClickableChip } from "../../Dashboard/utils"; +import AttachmentFilter from "../Attachments/AttachmentFilter"; +import Accordion from "@material-ui/core/Accordion"; +import { ClickableChip } from "../../Dashboard/utils"; import Stack from "@mui/material/Stack"; -import AccordionSummary from '@material-ui/core/AccordionSummary'; -import AccordionDetails from '@material-ui/core/AccordionDetails'; -import Typography from '@material-ui/core/Typography'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import AccordionSummary from "@material-ui/core/AccordionSummary"; +import AccordionDetails from "@material-ui/core/AccordionDetails"; +import Typography from "@material-ui/core/Typography"; +import ExpandMoreIcon from "@material-ui/icons/ExpandMore"; import SearchIcon from "@material-ui/icons/Search"; import InputAdornment from "@mui/material/InputAdornment"; import InputBase from "@mui/material/InputBase"; import Paper from "@mui/material/Paper"; -import Tooltip from '@mui/material/Tooltip'; +import Tooltip from "@mui/material/Tooltip"; import { toast } from "react-toastify"; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faCheckCircle, faClone, faTrashAlt, faTrashCan } from '@fortawesome/free-regular-svg-icons'; -import {faSpinner, faExclamationCircle, faBan, faArrowTurnUp, faHistory, faTrash, faPenToSquare, faLinkSlash } from '@fortawesome/free-solid-svg-icons';import Dialog from '@material-ui/core/Dialog'; -import DialogActions from '@material-ui/core/DialogActions'; -import DialogContent from '@material-ui/core/DialogContent'; -import DialogContentText from '@material-ui/core/DialogContentText'; -import DialogTitle from '@material-ui/core/DialogTitle'; -import CloseIcon from '@material-ui/icons/Close'; -import _ from 'lodash'; -import { DOC_REVIEWER_WEB_URL, RECORD_PROCESSING_HRS, OSS_S3_CHUNK_SIZE, DISABLE_REDACT_WEBLINK } from "../../../../constants/constants"; -import {removeDuplicateFiles, getUpdatedRecords, sortByLastModified, getFiles, calculateDivisionFileSize, calculateTotalFileSize,calculateTotalUploadedFileSizeInKB,getReadableFileSize} from "./util" -import { readUploadedFileAsBytes } from '../../../../helper/FOI/helper'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { + faCheckCircle, + faClone, + faTrashAlt, + faTrashCan, +} from "@fortawesome/free-regular-svg-icons"; +import { + faSpinner, + faExclamationCircle, + faBan, + faArrowTurnUp, + faHistory, + faTrash, + faPenToSquare, + faLinkSlash, +} from "@fortawesome/free-solid-svg-icons"; +import Dialog from "@material-ui/core/Dialog"; +import DialogActions from "@material-ui/core/DialogActions"; +import DialogContent from "@material-ui/core/DialogContent"; +import DialogContentText from "@material-ui/core/DialogContentText"; +import DialogTitle from "@material-ui/core/DialogTitle"; +import CloseIcon from "@material-ui/icons/Close"; +import _ from "lodash"; +import { + DOC_REVIEWER_WEB_URL, + RECORD_PROCESSING_HRS, + OSS_S3_CHUNK_SIZE, + DISABLE_REDACT_WEBLINK, +} from "../../../../constants/constants"; +import { + removeDuplicateFiles, + getUpdatedRecords, + sortByLastModified, + getFiles, + calculateDivisionFileSize, + calculateTotalFileSize, + calculateTotalUploadedFileSizeInKB, + getReadableFileSize, +} from "./util"; +import { readUploadedFileAsBytes } from "../../../../helper/FOI/helper"; import { TOTAL_RECORDS_UPLOAD_LIMIT } from "../../../../constants/constants"; //import {convertBytesToMB} from "../../../../components/FOI/customComponents/FileUpload/util"; - const useStyles = makeStyles((_theme) => ({ createButton: { margin: 0, @@ -82,70 +145,69 @@ const useStyles = makeStyles((_theme) => ({ marginBottom: "2em", }, recordLog: { - marginTop: "1em" + marginTop: "1em", }, heading: { - color: '#FFF', - fontSize: '16px !important', - fontWeight: 'bold !important', - flexDirection: 'row-reverse', + color: "#FFF", + fontSize: "16px !important", + fontWeight: "bold !important", + flexDirection: "row-reverse", }, actions: { - textAlign:'right' + textAlign: "right", }, createDate: { fontStyle: "italic", - fontSize: "14px" + fontSize: "14px", }, createBy: { fontStyle: "italic", fontSize: "14px", - display: "flex" + display: "flex", }, filename: { fontWeight: "bold", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", - maxWidth: "79%" + maxWidth: "79%", }, divider: { marginTop: "-2px", - marginBottom: "-5px" + marginBottom: "-5px", }, topDivider: { - paddingTop: '0px !important', + paddingTop: "0px !important", }, - recordStatus:{ - fontSize:"12px", - color:"#a7a1a1", - display:"flex" + recordStatus: { + fontSize: "12px", + color: "#a7a1a1", + display: "flex", }, statusIcons: { height: "20px", - paddingRight:"10px", + paddingRight: "10px", }, attachmentIcon: { height: "20px", - margin:"0 15px", - rotate:"90deg" + margin: "0 15px", + rotate: "90deg", }, - recordReports:{ + recordReports: { marginTop: "1em", fontSize: "14px", marginBottom: "0px", marginLeft: "15px", - fontWeight:'bold' + fontWeight: "bold", }, // reportText:{ // fontWeight:'bold' // }, fileSize: { - paddingLeft:'20px' - } + paddingLeft: "20px", + }, })); - export const RecordsLog = ({ divisions, requestNumber, @@ -156,14 +218,11 @@ export const RecordsLog = ({ ministryAssignedToList, isMinistryCoordinator, setRecordsUploading, - recordsTabSelect + recordsTabSelect, }) => { + let recordsObj = useSelector((state) => state.foiRequests.foiRequestRecords); - let recordsObj = useSelector( - (state) => state.foiRequests.foiRequestRecords - ); - - let pdfStitchStatus = useSelector( + let pdfStitchStatus = useSelector( (state) => state.foiRequests.foiPDFStitchStatusForHarms ); @@ -177,20 +236,24 @@ export const RecordsLog = ({ const classes = useStyles(); const [records, setRecords] = useState(recordsObj?.records); const [totalUploadedRecordSize, setTotalUploadedRecordSize] = useState(0); - useEffect(() => { - setRecords(recordsObj?.records) - let nonDuplicateRecords = recordsObj?.records?.filter(record => !record.isduplicate) - let totalUploadedSize= (calculateTotalUploadedFileSizeInKB(nonDuplicateRecords)/ (1024 * 1024)) + useEffect(() => { + setRecords(recordsObj?.records); + let nonDuplicateRecords = recordsObj?.records?.filter( + (record) => !record.isduplicate + ); + let totalUploadedSize = + calculateTotalUploadedFileSizeInKB(nonDuplicateRecords) / (1024 * 1024); setTotalUploadedRecordSize(parseFloat(totalUploadedSize.toFixed(4))); - dispatch(checkForRecordsChange(requestId, ministryId)) - }, [recordsObj]) + dispatch(checkForRecordsChange(requestId, ministryId)); + }, [recordsObj]); useEffect(() => { dispatch(getRecordFormats()); - }, []) - - const conversionFormats = useSelector((state) => state.foiRequests.conversionFormats) + }, []); + const conversionFormats = useSelector( + (state) => state.foiRequests.conversionFormats + ); useEffect(() => { if (recordsTabSelect && conversionFormats?.length < 1) { @@ -207,25 +270,36 @@ export const RecordsLog = ({ progress: undefined, } ); - - } - }, [recordsTabSelect, conversionFormats]) - - - - - const divisionFilters = [...new Map(recordsObj?.records?.reduce((acc, file) => [...acc, ...new Map(file?.attributes?.divisions?.map(division => [division?.divisionid, division]))], [])).values()] - if (divisionFilters?.length > 0) divisionFilters?.push( - {divisionid: -1, divisionname: "All"}, - {divisionid: -2, divisionname: "Errors"}, - {divisionid: -3, divisionname: "Incompatible"} - ) - + } + }, [recordsTabSelect, conversionFormats]); + + const divisionFilters = [ + ...new Map( + recordsObj?.records?.reduce( + (acc, file) => [ + ...acc, + ...new Map( + file?.attributes?.divisions?.map((division) => [ + division?.divisionid, + division, + ]) + ), + ], + [] + ) + ).values(), + ]; + if (divisionFilters?.length > 0) + divisionFilters?.push( + { divisionid: -1, divisionname: "All" }, + { divisionid: -2, divisionname: "Errors" }, + { divisionid: -3, divisionname: "Incompatible" } + ); const [openModal, setModal] = useState(false); const [deleteModalOpen, setDeleteModalOpen] = useState(false); const [divisionsModalOpen, setDivisionsModalOpen] = useState(false); - const [divisionModalTagValue, setDivisionModalTagValue] = useState(-1) + const [divisionModalTagValue, setDivisionModalTagValue] = useState(-1); const dispatch = useDispatch(); const [isAttachmentLoading, setAttachmentLoading] = useState(false); const [isRecordsLoading, setRecordsLoading] = useState(false); @@ -236,16 +310,16 @@ export const RecordsLog = ({ const [searchValue, setSearchValue] = useState(""); const [filterValue, setFilterValue] = useState(-1); const [fullnameList, setFullnameList] = useState(getFullnameList); - const [recordsDownloadList, setRecordsDownloadList] = useState(RecordsDownloadList); - const [currentDownload, setCurrentDownload] = useState(0) - const [isDownloadInProgress, setIsDownloadInProgress] = useState(false) - const [isDownloadReady, setIsDownloadReady] = useState(false) - const [isDownloadFailed, setIsDownloadFailed] = useState(false) - const [isAllSelected, setIsAllSelected] = useState(false) - + const [recordsDownloadList, setRecordsDownloadList] = + useState(RecordsDownloadList); + const [currentDownload, setCurrentDownload] = useState(0); + const [isDownloadInProgress, setIsDownloadInProgress] = useState(false); + const [isDownloadReady, setIsDownloadReady] = useState(false); + const [isDownloadFailed, setIsDownloadFailed] = useState(false); + const [isAllSelected, setIsAllSelected] = useState(false); useEffect(() => { - switch(pdfStitchStatus) { + switch (pdfStitchStatus) { case "started": case "pushedtostream": setIsDownloadInProgress(true); @@ -269,34 +343,45 @@ export const RecordsLog = ({ setIsDownloadFailed(false); break; } - }, [pdfStitchStatus, requestId, ministryId]) + }, [pdfStitchStatus, requestId, ministryId]); const addAttachments = () => { - setModalFor('add'); + setModalFor("add"); setMultipleFiles(true); setUpdateAttachment({}); setModal(true); - } + }; const dispatchRequestAttachment = (err) => { - if (!err) { + if (!err) { setAttachmentLoading(false); - dispatch(fetchFOIRecords(requestId, ministryId)) - + dispatch(fetchFOIRecords(requestId, ministryId)); } - } + }; const handleContinueModal = (value, fileInfoList, files) => { setModal(false); - if (modalFor === 'delete' && value) { + if (modalFor === "delete" && value) { var deleteRecords = []; var deleteAttachemnts = []; if (updateAttachment) { - deleteRecords.push((({ recordid, documentmasterid, s3uripath }) => ({ recordid, documentmasterid, filepath: s3uripath }))(updateAttachment)) + deleteRecords.push( + (({ recordid, documentmasterid, s3uripath }) => ({ + recordid, + documentmasterid, + filepath: s3uripath, + }))(updateAttachment) + ); } else { for (let record of records) { if (record.isselected) { - deleteRecords.push((({ recordid, documentmasterid, s3uripath }) => ({ recordid, documentmasterid, filepath: s3uripath }))(record)); + deleteRecords.push( + (({ recordid, documentmasterid, s3uripath }) => ({ + recordid, + documentmasterid, + filepath: s3uripath, + }))(record) + ); } else { for (let attachment of record.attachments) { if (attachment.isselected) { @@ -307,315 +392,452 @@ export const RecordsLog = ({ } } if (deleteRecords.length > 0) { - dispatch(updateFOIRecords(requestId, ministryId, {records: deleteRecords, isdelete: true}, (err, _res) => { - dispatchRequestAttachment(err); - })); + dispatch( + updateFOIRecords( + requestId, + ministryId, + { records: deleteRecords, isdelete: true }, + (err, _res) => { + dispatchRequestAttachment(err); + } + ) + ); } if (deleteAttachemnts.length > 0) { - dispatch(deleteReviewerRecords({filepaths: deleteAttachemnts, ministryrequestid: ministryId},(err, _res) => { - dispatchRequestAttachment(err); - })); + dispatch( + deleteReviewerRecords( + { filepaths: deleteAttachemnts, ministryrequestid: ministryId }, + (err, _res) => { + dispatchRequestAttachment(err); + } + ) + ); } - } - else if (files) { + } else if (files) { saveDocument(value, fileInfoList, files); } - } + }; const saveDocument = (value, fileInfoList, files) => { - if (value) { if (files.length !== 0) { - setRecordsUploading(true) - if (modalFor === 'replaceattachment') { - fileInfoList[0].filepath = updateAttachment.s3uripath.substr(0, updateAttachment.s3uripath.lastIndexOf(".")) + ".pdf"; + setRecordsUploading(true); + if (modalFor === "replaceattachment") { + fileInfoList[0].filepath = + updateAttachment.s3uripath.substr( + 0, + updateAttachment.s3uripath.lastIndexOf(".") + ) + ".pdf"; } - postFOIS3DocumentPreSignedUrl(ministryId, fileInfoList.map(file => ({...file, multipart: true})), 'records', bcgovcode, dispatch, async (err, res) => { - let _documents = []; - if (!err) { - var completed = 0; - let failed = []; - const toastID = toast.loading("Uploading files (" + completed + "/" + fileInfoList.length + ")") - for (let header of res) { - const _file = files.find(file => file.filename === header.filename); - const _fileInfo = fileInfoList.find(fileInfo => fileInfo.filename === header.filename); - var documentDetails; - if (modalFor === 'replace') { - documentDetails = { - filename: header.filename, - attributes:{ - divisions:replaceRecord['attributes']['divisions'], - lastmodified: _file.lastModifiedDate ? _file.lastModifiedDate : new Date(_file.lastModified), - filesize: _file.size - }, - replacementof:replaceRecord['replacementof'] == null || replaceRecord['replacementof']==''? replaceRecord['recordid'] : replaceRecord['replacementof'] , - s3uripath: header.filepathdb, - trigger: 'recordreplace', - service: 'deduplication' - } - } else if (modalFor === 'replaceattachment') { - documentDetails = { - ...updateAttachment, - s3uripath: header.filepathdb, - trigger: 'recordreplace', - service: 'deduplication' - } - documentDetails.attributes.convertedfilesize = _file.size - documentDetails.attributes.trigger = 'recordreplace' - delete documentDetails.outputdocumentmasterid - } else { - documentDetails = { - s3uripath: header.filepathdb, - filename: header.filename, - attributes:{ - divisions:[{divisionid: _fileInfo.divisionid}], - lastmodified: _file.lastModifiedDate ? _file.lastModifiedDate : new Date(_file.lastModified), - filesize: _file.size - } + postFOIS3DocumentPreSignedUrl( + ministryId, + fileInfoList.map((file) => ({ ...file, multipart: true })), + "records", + bcgovcode, + dispatch, + async (err, res) => { + let _documents = []; + if (!err) { + var completed = 0; + let failed = []; + const toastID = toast.loading( + "Uploading files (" + + completed + + "/" + + fileInfoList.length + + ")" + ); + for (let header of res) { + const _file = files.find( + (file) => file.filename === header.filename + ); + const _fileInfo = fileInfoList.find( + (fileInfo) => fileInfo.filename === header.filename + ); + var documentDetails; + if (modalFor === "replace") { + documentDetails = { + filename: header.filename, + attributes: { + divisions: replaceRecord["attributes"]["divisions"], + lastmodified: _file.lastModifiedDate + ? _file.lastModifiedDate + : new Date(_file.lastModified), + filesize: _file.size, + }, + replacementof: + replaceRecord["replacementof"] == null || + replaceRecord["replacementof"] == "" + ? replaceRecord["recordid"] + : replaceRecord["replacementof"], + s3uripath: header.filepathdb, + trigger: "recordreplace", + service: "deduplication", + }; + } else if (modalFor === "replaceattachment") { + documentDetails = { + ...updateAttachment, + s3uripath: header.filepathdb, + trigger: "recordreplace", + service: "deduplication", + }; + documentDetails.attributes.convertedfilesize = _file.size; + documentDetails.attributes.trigger = "recordreplace"; + delete documentDetails.outputdocumentmasterid; + } else { + documentDetails = { + s3uripath: header.filepathdb, + filename: header.filename, + attributes: { + divisions: [{ divisionid: _fileInfo.divisionid }], + lastmodified: _file.lastModifiedDate + ? _file.lastModifiedDate + : new Date(_file.lastModified), + filesize: _file.size, + }, + }; } - } - let bytes = await readUploadedFileAsBytes(_file) - const CHUNK_SIZE = OSS_S3_CHUNK_SIZE; - const totalChunks = Math.ceil(bytes.byteLength / CHUNK_SIZE); - let parts = []; - for (let chunk = 0; chunk < totalChunks; chunk++) { - let CHUNK = bytes.slice(chunk * CHUNK_SIZE, (chunk + 1) * CHUNK_SIZE); - let response = await saveFilesinS3({filepath: header.filepaths[chunk]}, CHUNK, dispatch, (_err, _res) => { - if (_err) { + let bytes = await readUploadedFileAsBytes(_file); + const CHUNK_SIZE = OSS_S3_CHUNK_SIZE; + const totalChunks = Math.ceil(bytes.byteLength / CHUNK_SIZE); + let parts = []; + for (let chunk = 0; chunk < totalChunks; chunk++) { + let CHUNK = bytes.slice( + chunk * CHUNK_SIZE, + (chunk + 1) * CHUNK_SIZE + ); + let response = await saveFilesinS3( + { filepath: header.filepaths[chunk] }, + CHUNK, + dispatch, + (_err, _res) => { + if (_err) { + failed.push(header.filename); + } + } + ); + if (response.status === 200) { + parts.push({ + PartNumber: chunk + 1, + ETag: response.headers.etag, + }); + } else { failed.push(header.filename); } - }) - if (response.status === 200) { - parts.push({PartNumber: chunk + 1, ETag: response.headers.etag}) - } else { - failed.push(header.filename); } + await completeMultiPartUpload( + { + uploadid: header.uploadid, + filepath: header.filepathdb, + parts: parts, + }, + ministryId, + "records", + bcgovcode, + dispatch, + (_err, _res) => { + if (!_err && _res.ResponseMetadata.HTTPStatusCode === 200) { + completed++; + toast.update(toastID, { + render: + "Uploading files (" + + completed + + "/" + + fileInfoList.length + + ")", + isLoading: true, + }); + _documents.push(documentDetails); + } else { + failed.push(header.filename); + } + } + ); } - await completeMultiPartUpload({uploadid: header.uploadid, filepath: header.filepathdb, parts: parts}, ministryId, 'records', bcgovcode, dispatch, (_err, _res) => { - if (!_err && _res.ResponseMetadata.HTTPStatusCode === 200) { - completed++; - toast.update(toastID, { - render: "Uploading files (" + completed + "/" + fileInfoList.length + ")", - isLoading: true, - }) - _documents.push(documentDetails); + if (_documents.length > 0) { + if (modalFor === "replace" || modalFor == "replaceattachment") { + if (modalFor === "replaceattachment") { + dispatch( + retryFOIRecordProcessing( + requestId, + ministryId, + { records: _documents }, + (err, _res) => { + dispatchRequestAttachment(err); + } + ) + ); + } + + if (modalFor === "replace") { + dispatch( + replaceFOIRecordProcessing( + requestId, + ministryId, + replaceRecord.recordid, + { records: _documents }, + (err, _res) => { + dispatchRequestAttachment(err); + } + ) + ); + } } else { - failed.push(header.filename); + dispatch( + saveFOIRecords( + requestId, + ministryId, + { records: _documents }, + (err, _res) => { + dispatchRequestAttachment(err); + } + ) + ); } - }) - } - if (_documents.length > 0) { - if (modalFor === 'replace' || modalFor == 'replaceattachment') { - - if (modalFor === 'replaceattachment'){ - dispatch(retryFOIRecordProcessing(requestId, ministryId, {records: _documents},(err, _res) => { - dispatchRequestAttachment(err); - })); } - - if (modalFor === 'replace'){ - dispatch(replaceFOIRecordProcessing(requestId, ministryId,replaceRecord.recordid, {records: _documents},(err, _res) => { - dispatchRequestAttachment(err); - })); } - - } - else { - dispatch(saveFOIRecords(requestId, ministryId, {records: _documents},(err, _res) => { - dispatchRequestAttachment(err); - })); } + var toastOptions = { + render: + failed.length > 0 + ? "The following " + + failed.length + + " file uploads failed\n- " + + failed.join("\n- ") + : fileInfoList.length + " Files successfully saved", + type: failed.length > 0 ? "error" : "success", + }; + toast.update(toastID, { + ...toastOptions, + className: "file-upload-toast", + isLoading: false, + autoClose: 3000, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + closeButton: true, + }); + setRecordsUploading(false); } - var toastOptions = { - render: failed.length > 0 ? - "The following " + failed.length + " file uploads failed\n- " + failed.join("\n- ") : - fileInfoList.length + ' Files successfully saved', - type: failed.length > 0 ? "error" : "success", - } - toast.update(toastID, { - ...toastOptions, - className: "file-upload-toast", - isLoading: false, - autoClose: 3000, - hideProgressBar: true, - closeOnClick: true, - pauseOnHover: true, - draggable: true, - closeButton: true - }); - setRecordsUploading(false) } - }) + ); } } - } + }; - const downloadDocument = (file, isPDF = false,originalfile = false) => { - var s3filepath = !originalfile ? file.s3uripath: (!file.isattachment ? file.originalfile:file.s3uripath); - var filename = !originalfile ? file.filename:(!file.isattachment ?file.originalfilename: file.filename); + const downloadDocument = (file, isPDF = false, originalfile = false) => { + var s3filepath = !originalfile + ? file.s3uripath + : !file.isattachment + ? file.originalfile + : file.s3uripath; + var filename = !originalfile + ? file.filename + : !file.isattachment + ? file.originalfilename + : file.filename; if (isPDF) { s3filepath = s3filepath.substr(0, s3filepath.lastIndexOf(".")) + ".pdf"; filename = filename + ".pdf"; } - const toastID = toast.loading("Downloading file (0%)") - getFOIS3DocumentPreSignedUrl(s3filepath.split('/').slice(4).join('/'), ministryId, dispatch, (err, res) => { - if (!err) { - getFileFromS3({filepath: res}, (_err, response) => { - let blob = new Blob([response.data], {type: "application/octet-stream"}); - saveAs(blob, filename) - toast.update(toastID, { - render: _err ? "File download failed" : "Download complete", - type: _err ? "error" : "success", - className: "file-upload-toast", - isLoading: false, - autoClose: 3000, - hideProgressBar: true, - closeOnClick: true, - pauseOnHover: true, - draggable: true, - closeButton: true - }); - }, (progressEvent) => { - toast.update(toastID, { - render: "Downloading file (" + Math.floor(progressEvent.loaded / progressEvent.total * 100) + "%)", - isLoading: true, - }) - }); - } - }, 'records', bcgovcode); - } + const toastID = toast.loading("Downloading file (0%)"); + getFOIS3DocumentPreSignedUrl( + s3filepath.split("/").slice(4).join("/"), + ministryId, + dispatch, + (err, res) => { + if (!err) { + getFileFromS3( + { filepath: res }, + (_err, response) => { + let blob = new Blob([response.data], { + type: "application/octet-stream", + }); + saveAs(blob, filename); + toast.update(toastID, { + render: _err ? "File download failed" : "Download complete", + type: _err ? "error" : "success", + className: "file-upload-toast", + isLoading: false, + autoClose: 3000, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + closeButton: true, + }); + }, + (progressEvent) => { + toast.update(toastID, { + render: + "Downloading file (" + + Math.floor( + (progressEvent.loaded / progressEvent.total) * 100 + ) + + "%)", + isLoading: true, + }); + } + ); + } + }, + "records", + bcgovcode + ); + }; const handleDownloadChange = (e) => { //if clicked on harms - if (e.target.value === 1 && ["not started", "error"].includes(pdfStitchStatus)) { - toast.info("In progress. You will be notified when the records are ready for download.", { - position: "top-right", - autoClose: 3000, - hideProgressBar: true, - closeOnClick: true, - pauseOnHover: true, - draggable: true, - progress: undefined, - theme: "colored", - backgroundColor: "#FFA500" - }); - downloadLinearHarmsDocuments() + if ( + e.target.value === 1 && + ["not started", "error"].includes(pdfStitchStatus) + ) { + toast.info( + "In progress. You will be notified when the records are ready for download.", + { + position: "top-right", + autoClose: 3000, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "colored", + backgroundColor: "#FFA500", + } + ); + downloadLinearHarmsDocuments(); } //if clicked on harms and stitching is complete else if (e.target.value === 1 && pdfStitchStatus === "completed") { - const s3filepath = pdfStitchedRecord?.finalpackagepath - const filename = requestNumber + ".zip" + const s3filepath = pdfStitchedRecord?.finalpackagepath; + const filename = requestNumber + ".zip"; try { downloadZipFile(s3filepath, filename); - } - catch (error) { - console.log(error) - toastError() + } catch (error) { + console.log(error); + toastError(); } } setCurrentDownload(e.target.value); - } + }; const downloadZipFile = async (s3filepath, filename) => { - const toastID = toast.loading("Downloading file (0%)") - const response = await getFOIS3DocumentPreSignedUrl(s3filepath.split('/').slice(4).join('/'), ministryId, dispatch, null, 'records', bcgovcode) - await getFileFromS3({filepath: response.data}, (_err, res) => { - let blob = new Blob([res.data], {type: "application/octet-stream"}); - saveAs(blob, filename) - toast.update(toastID, { - render: _err ? "File download failed" : "Download complete", - type: _err ? "error" : "success", - className: "file-upload-toast", - isLoading: false, - autoClose: 3000, - hideProgressBar: true, - closeOnClick: true, - pauseOnHover: true, - draggable: true, - closeButton: true - }); - }, (progressEvent) => { - toast.update(toastID, { - render: "Downloading file (" + Math.floor(progressEvent.loaded / progressEvent.total * 100) + "%)", - isLoading: true, - }) + const toastID = toast.loading("Downloading file (0%)"); + const response = await getFOIS3DocumentPreSignedUrl( + s3filepath.split("/").slice(4).join("/"), + ministryId, + dispatch, + null, + "records", + bcgovcode + ); + await getFileFromS3( + { filepath: response.data }, + (_err, res) => { + let blob = new Blob([res.data], { type: "application/octet-stream" }); + saveAs(blob, filename); + toast.update(toastID, { + render: _err ? "File download failed" : "Download complete", + type: _err ? "error" : "success", + className: "file-upload-toast", + isLoading: false, + autoClose: 3000, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + closeButton: true, }); - } + }, + (progressEvent) => { + toast.update(toastID, { + render: + "Downloading file (" + + Math.floor((progressEvent.loaded / progressEvent.total) * 100) + + "%)", + isLoading: true, + }); + } + ); + }; const downloadLinearHarmsDocuments = () => { - try{ - + try { const message = createMessageForHarms(recordsObj?.records); - dispatch(triggerDownloadFOIRecordsForHarms(requestId, ministryId, message,(err, _res) => { - if (err) { - toastError() - } - else { - setIsDownloadInProgress(true); - setIsDownloadReady(false); - setIsDownloadFailed(false); - } - dispatchRequestAttachment(err); - })); - + dispatch( + triggerDownloadFOIRecordsForHarms( + requestId, + ministryId, + message, + (err, _res) => { + if (err) { + toastError(); + } else { + setIsDownloadInProgress(true); + setIsDownloadReady(false); + setIsDownloadFailed(false); + } + dispatchRequestAttachment(err); + } + ) + ); } catch (error) { - console.log(error) - toastError() + console.log(error); + toastError(); } - - } + }; const createMessageForHarms = (recordList) => { - const message = { - "category":RecordDownloadCategory.harms, - "requestnumber":requestNumber, - "bcgovcode":bcgovcode, - "attributes":[] + category: RecordDownloadCategory.harms, + requestnumber: requestNumber, + bcgovcode: bcgovcode, + attributes: [], }; //remove duplicate records(except duplicate attachments) const deduplicatedRecords = removeDuplicateFiles(recordList); //get only relevent fields - const updatedrecords = getUpdatedRecords(deduplicatedRecords, conversionFormats); + const updatedrecords = getUpdatedRecords(deduplicatedRecords); //sort records and attachments based on lastmodified asc let sortedRecords = sortByLastModified(updatedrecords); //get all the divisions in the records - const divisionObj= {} + const divisionObj = {}; for (const _record of sortedRecords) { - for (const _division of _record.divisions) { - divisionObj[_division.divisionid] = _division.divisionname - } + for (const _division of _record.divisions) { + divisionObj[_division.divisionid] = _division.divisionname; + } } - // arrange the records and its attachments. - const recordsArray = [] + // arrange the records and its attachments. + const recordsArray = []; for (const _record of sortedRecords) { - recordsArray.push(_record) - if (_record.attachments) { - const _filteredAttachments = _record.attachments.filter(r => !r.isduplicate) - recordsArray.push(..._filteredAttachments) - } + recordsArray.push(_record); + if (_record.attachments) { + const _filteredAttachments = _record.attachments.filter( + (r) => !r.isduplicate + ); + recordsArray.push(..._filteredAttachments); + } } //form the final attributes for the message - const attributes = [] + const attributes = []; for (const _key in divisionObj) { - const files = getFiles(recordsArray, +_key) - const attribute = { - divisionid: +_key, - divisionname: divisionObj[_key], - files: files, - divisionfilesize: calculateDivisionFileSize(files) - } - attributes.push(attribute) + const files = getFiles(recordsArray, +_key); + const attribute = { + divisionid: +_key, + divisionname: divisionObj[_key], + files: files, + divisionfilesize: calculateDivisionFileSize(files), + }; + attributes.push(attribute); } message.attributes = attributes; message.totalfilesize = calculateTotalFileSize(attributes); - //keeping this for testing purpose. - console.log(`message = ${JSON.stringify(message)}`); return message; - } - + }; const toastError = (error) => { toast.error( @@ -633,46 +855,68 @@ export const RecordsLog = ({ setIsDownloadInProgress(false); setIsDownloadReady(false); setIsDownloadFailed(true); - - } + }; const downloadAllDocuments = async () => { let blobs = []; var completed = 0; let failed = 0; - var exporting = records.filter(record => !record.isduplicate) + var exporting = records.filter((record) => !record.isduplicate); for (let record of exporting) { - if (record.attachments) for (let attachment of record.attachments) { - if (!attachment.isduplicate) exporting.push(attachment); - } + if (record.attachments) + for (let attachment of record.attachments) { + if (!attachment.isduplicate) exporting.push(attachment); + } } - const toastID = toast.loading("Exporting files (" + completed + "/" + exporting.length + ")") + const toastID = toast.loading( + "Exporting files (" + completed + "/" + exporting.length + ")" + ); try { for (let record of exporting) { - var filepath = record.s3uripath - var filename = record.filename - if (record.isredactionready && conversionFormats.includes(record.attributes?.extension?.toLowerCase())) { + var filepath = record.s3uripath; + var filename = record.filename; + if ( + record.isredactionready && + conversionFormats.includes( + record.attributes?.extension?.toLowerCase() + ) + ) { filepath = filepath.substr(0, filepath.lastIndexOf(".")) + ".pdf"; filename += ".pdf"; } - const response = await getFOIS3DocumentPreSignedUrl(filepath.split('/').slice(4).join('/'), ministryId, dispatch, null, 'records', bcgovcode) - await getFileFromS3({filepath: response.data}, (_err, res) => { - let blob = new Blob([res.data], {type: "application/octet-stream"}); - blobs.push({name: filename, lastModified: res.headers['last-modified'], input: blob}) + const response = await getFOIS3DocumentPreSignedUrl( + filepath.split("/").slice(4).join("/"), + ministryId, + dispatch, + null, + "records", + bcgovcode + ); + await getFileFromS3({ filepath: response.data }, (_err, res) => { + let blob = new Blob([res.data], { type: "application/octet-stream" }); + blobs.push({ + name: filename, + lastModified: res.headers["last-modified"], + input: blob, + }); completed++; toast.update(toastID, { - render: "Exporting files (" + completed + "/" + exporting.length + ")", + render: + "Exporting files (" + completed + "/" + exporting.length + ")", isLoading: true, - }) + }); }); } } catch (error) { - console.log(error) + console.log(error); } var toastOptions = { - render: failed > 0 ? failed.length + " file(s) failed to download" : exporting.length + ' Files exported', + render: + failed > 0 + ? failed.length + " file(s) failed to download" + : exporting.length + " Files exported", type: failed > 0 ? "error" : "success", - } + }; toast.update(toastID, { ...toastOptions, isLoading: false, @@ -681,54 +925,83 @@ export const RecordsLog = ({ closeOnClick: true, pauseOnHover: true, draggable: true, - closeButton: true + closeButton: true, }); - const zipfile = await downloadZip(blobs).blob() - var currentFilter = divisionFilters.find(division => division.divisionid === filterValue).divisionname; + const zipfile = await downloadZip(blobs).blob(); + var currentFilter = divisionFilters.find( + (division) => division.divisionid === filterValue + ).divisionname; saveAs(zipfile, requestNumber + " Records - " + currentFilter + ".zip"); - } + }; const retryDocument = (record) => { - record.trigger = 'recordretry'; - record.service = record.failed ? record.failed : 'all'; + record.trigger = "recordretry"; + record.service = record.failed ? record.failed : "all"; if (record.isattachment) { - var parentRecord = recordsObj?.records?.find(r => r.recordid = record.rootparentid); + var parentRecord = recordsObj?.records?.find( + (r) => (r.recordid = record.rootparentid) + ); record.attributes.divisions = parentRecord.attributes.divisions; record.attributes.batch = parentRecord.attributes.batch; - record.attributes.extension = record['s3uripath'].substr(record['s3uripath'].lastIndexOf("."), record['s3uripath'].length); + record.attributes.extension = record["s3uripath"].substr( + record["s3uripath"].lastIndexOf("."), + record["s3uripath"].length + ); record.attributes.incompatible = false; record.attributes.isattachment = true; } - if (record.service === 'deduplication') { - record['s3uripath'] = record['s3uripath'].substr(0, record['s3uripath'].lastIndexOf(".")) + ".pdf"; + if (record.service === "deduplication") { + record["s3uripath"] = + record["s3uripath"].substr(0, record["s3uripath"].lastIndexOf(".")) + + ".pdf"; } - dispatch(retryFOIRecordProcessing(requestId, ministryId, {records: [record]},(err, _res) => { - dispatchRequestAttachment(err); - })); - } + dispatch( + retryFOIRecordProcessing( + requestId, + ministryId, + { records: [record] }, + (err, _res) => { + dispatchRequestAttachment(err); + } + ) + ); + }; const removeAttachments = () => { setDeleteModalOpen(false); - var attachments = records?.reduce((acc, record) => {return record.attachments ? acc.concat(record.attachments.map(a => a.filepath)) : acc}, []); - dispatch(deleteReviewerRecords({filepaths: attachments, ministryrequestid :ministryId},(err, _res) => { - dispatchRequestAttachment(err); - })); - } + var attachments = records?.reduce((acc, record) => { + return record.attachments + ? acc.concat(record.attachments.map((a) => a.filepath)) + : acc; + }, []); + dispatch( + deleteReviewerRecords( + { filepaths: attachments, ministryrequestid: ministryId }, + (err, _res) => { + dispatchRequestAttachment(err); + } + ) + ); + }; - const hasDocumentsToExport = records?.filter(record => !(isMinistryCoordinator && record.category == 'personal')).length > 0; - const hasDocumentsToDownload = records?.filter(record => record.category !== 'personal').length > 0; + const hasDocumentsToExport = + records?.filter( + (record) => !(isMinistryCoordinator && record.category == "personal") + ).length > 0; + const hasDocumentsToDownload = + records?.filter((record) => record.category !== "personal").length > 0; const handlePopupButtonClick = (action, _record) => { setUpdateAttachment(_record); setMultipleFiles(false); switch (action) { case "replace": - setreplaceRecord(_record) + setreplaceRecord(_record); setModalFor("replace"); setModal(true); break; case "replaceattachment": - setreplaceRecord(_record) + setreplaceRecord(_record); setModalFor("replaceattachment"); setModal(true); break; @@ -752,131 +1025,164 @@ export const RecordsLog = ({ setModal(false); break; case "delete": - setModalFor("delete") - setModal(true) + setModalFor("delete"); + setModal(true); break; case "retry": retryDocument(_record); - setModal(false) + setModal(false); break; default: setModal(false); break; } - } + }; const handleRename = (_record, newFilename) => { setModal(false); if (updateAttachment.filename !== newFilename) { - const documentId = ministryId ? updateAttachment.foiministrydocumentid : updateAttachment.foidocumentid; - dispatch(saveNewFilename(newFilename, documentId, requestId, ministryId, (err, _res) => { - if (!err) { - setAttachmentLoading(false); - } - })); + const documentId = ministryId + ? updateAttachment.foiministrydocumentid + : updateAttachment.foidocumentid; + dispatch( + saveNewFilename( + newFilename, + documentId, + requestId, + ministryId, + (err, _res) => { + if (!err) { + setAttachmentLoading(false); + } + } + ) + ); } - } + }; const getFullname = (userId) => { let user; - if(fullnameList) { - user = fullnameList.find(u => u.username === userId); + if (fullnameList) { + user = fullnameList.find((u) => u.username === userId); return user && user.fullname ? user.fullname : userId; } else { - - if(iaoassignedToList.length > 0) { + if (iaoassignedToList.length > 0) { addToFullnameList(iaoassignedToList, "iao"); setFullnameList(getFullnameList()); } - if(ministryAssignedToList.length > 0) { + if (ministryAssignedToList.length > 0) { addToFullnameList(iaoassignedToList, bcgovcode); setFullnameList(getFullnameList()); } - user = fullnameList.find(u => u.username === userId); + user = fullnameList.find((u) => u.username === userId); return user && user.fullname ? user.fullname : userId; } }; - const getRequestNumber = ()=> { - if (requestNumber) - return `Request #${requestNumber}`; + const getRequestNumber = () => { + if (requestNumber) return `Request #${requestNumber}`; return `Request #U-00${requestId}`; - } - + }; // const onFilterChange = (filterValue) => { - // let _filteredRecords = filterValue === "" ? - // records.records : - // records.records.filter(r => - // r.filename.toLowerCase().includes(filterValue?.toLowerCase()) || - // r.createdby.toLowerCase().includes(filterValue?.toLowerCase()) || - // r.attributes?.findIndex(a => a.divisionname.toLowerCase() === filterValue?.toLowerCase().trim()) > -1 - // ); - // setRecords(_filteredRecords) + // let _filteredRecords = filterValue === "" ? + // records.records : + // records.records.filter(r => + // r.filename.toLowerCase().includes(filterValue?.toLowerCase()) || + // r.createdby.toLowerCase().includes(filterValue?.toLowerCase()) || + // r.attributes?.findIndex(a => a.divisionname.toLowerCase() === filterValue?.toLowerCase().trim()) > -1 + // ); + // setRecords(_filteredRecords) // } const getreplacementfiletypes = () => { + var replacefileextensions = [...MimeTypeList.recordsLog]; - var replacefileextensions = [...MimeTypeList.recordsLog] - - let _filename = replaceRecord?.originalfilename === '' ? replaceRecord.filename : replaceRecord.originalfilename ; - let fileextension = _filename?.split('.').pop(); + let _filename = + replaceRecord?.originalfilename === "" + ? replaceRecord.filename + : replaceRecord.originalfilename; + let fileextension = _filename?.split(".").pop(); - switch(fileextension) - { + switch (fileextension) { case "docx" || "doc": - replacefileextensions = ["application/pdf" ,'application/vnd.openxmlformats-officedocument.wordprocessingml.document', "application/msword",'.doc', '.docx']; + replacefileextensions = [ + "application/pdf", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "application/msword", + ".doc", + ".docx", + ]; break; case "xlsx" || "xls": - replacefileextensions = ["application/pdf" ,'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-excel','.xls', '.xlsx']; + replacefileextensions = [ + "application/pdf", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "application/vnd.ms-excel", + ".xls", + ".xlsx", + ]; + break; + case "msg" || "eml": + replacefileextensions = ["application/pdf", ".msg", ".eml"]; + break; + case "pdf": + replacefileextensions = ["application/pdf"]; break; - case "msg" || "eml" : - replacefileextensions = ["application/pdf" ,'.msg','.eml']; - break; - case "pdf" : - replacefileextensions = ["application/pdf"]; - break; default: - replacefileextensions = [...MimeTypeList.recordsLog] + replacefileextensions = [...MimeTypeList.recordsLog]; break; } return replacefileextensions; - } - + }; React.useEffect(() => { - if (divisionFilters.findIndex(f => f.divisionid === filterValue) > -1) { - setRecords(searchAttachments(_.cloneDeep(recordsObj.records), filterValue, searchValue)); + if (divisionFilters.findIndex((f) => f.divisionid === filterValue) > -1) { + setRecords( + searchAttachments( + _.cloneDeep(recordsObj.records), + filterValue, + searchValue + ) + ); } else { setFilterValue(-1); } - },[filterValue, searchValue, recordsObj]) + }, [filterValue, searchValue, recordsObj]); - const searchAttachments = (_recordsArray, _filterValue, _keywordValue) => { + const searchAttachments = (_recordsArray, _filterValue, _keywordValue) => { var filterFunction = (r) => { if (r.attachments?.length > 0) { - r.attachments = r.attachments.filter(filterFunction) + r.attachments = r.attachments.filter(filterFunction); return r.attachments.length > 0; } else { - return (r.filename.toLowerCase().includes(_keywordValue?.toLowerCase()) || - r.createdby.toLowerCase().includes(_keywordValue?.toLowerCase())) && - ( - _filterValue === -3 ? r.attributes?.incompatible : - _filterValue === -2 ? !r.isredactionready && !r.attributes?.incompatible && (r.failed || isrecordtimeout(r.created_at, RECORD_PROCESSING_HRS) == true): - _filterValue > -1 ? r.attributes?.divisions?.findIndex(a => a.divisionid === _filterValue) > -1 : - true - ) + return ( + (r.filename.toLowerCase().includes(_keywordValue?.toLowerCase()) || + r.createdby.toLowerCase().includes(_keywordValue?.toLowerCase())) && + (_filterValue === -3 + ? r.attributes?.incompatible + : _filterValue === -2 + ? !r.isredactionready && + !r.attributes?.incompatible && + (r.failed || + isrecordtimeout(r.created_at, RECORD_PROCESSING_HRS) == true) + : _filterValue > -1 + ? r.attributes?.divisions?.findIndex( + (a) => a.divisionid === _filterValue + ) > -1 + : true) + ); } - } - setIsAllSelected(false) - return _recordsArray?.filter(filterFunction) - } - + }; + setIsAllSelected(false); + return _recordsArray?.filter(filterFunction); + }; + const handleSelectRecord = (record, e) => { const newRecords = records.map((r, i) => { if (record.isattachment) { @@ -901,7 +1207,7 @@ export const RecordsLog = ({ }); r.attachments = newAttachments; if (!e.target.checked) { - r.isselected = false + r.isselected = false; } return r; } else { @@ -922,8 +1228,8 @@ export const RecordsLog = ({ } }); setRecords(newRecords); - setIsAllSelected(checkIsAllSelected()) - } + setIsAllSelected(checkIsAllSelected()); + }; const handleSelectAll = (e) => { const newRecords = records.map((r, i) => { @@ -936,10 +1242,10 @@ export const RecordsLog = ({ r.attachments = newAttachments; } return r; - }) + }); setRecords(newRecords); - setIsAllSelected(checkIsAllSelected()) - } + setIsAllSelected(checkIsAllSelected()); + }; const checkIsAllSelected = () => { for (let record of records) { @@ -951,7 +1257,7 @@ export const RecordsLog = ({ } } return true; - } + }; const checkIsAnySelected = () => { for (let record of records) { @@ -963,8 +1269,8 @@ export const RecordsLog = ({ } } return false; - } - + }; + function intersection(setA, setB) { const _intersection = new Set(); for (const elem of setB) { @@ -974,7 +1280,7 @@ export const RecordsLog = ({ } return _intersection; } - + const isUpdateDivisionsDisabled = () => { var count = 0; var selectedDivision = new Set(); @@ -985,13 +1291,18 @@ export const RecordsLog = ({ } count++; if (selectedDivision.size === 0) { - record.attributes.divisions.forEach(d => selectedDivision.add(d.divisionid)); + record.attributes.divisions.forEach((d) => + selectedDivision.add(d.divisionid) + ); } else { - let int = intersection(selectedDivision, new Set(record.attributes.divisions.map(d => d.divisionid))); + let int = intersection( + selectedDivision, + new Set(record.attributes.divisions.map((d) => d.divisionid)) + ); if (int.size === 0) { return true; } else { - selectedDivision = int + selectedDivision = int; } } } @@ -1003,39 +1314,81 @@ export const RecordsLog = ({ } // setDivisionToUpdate() return count === 0; - } + }; const updateDivisions = () => { - setDivisionModalTagValue(-1) - setDivisionsModalOpen(false) + setDivisionModalTagValue(-1); + setDivisionsModalOpen(false); var updateRecords = []; for (let record of records) { if (record.isselected) { - if (record.attributes.divisions.length > 1 && record.attributes.divisions[0].divisionid !== filterValue) { + if ( + record.attributes.divisions.length > 1 && + record.attributes.divisions[0].divisionid !== filterValue + ) { if (filterValue < 0) { - throw new Error("invalid filter value - need to select a single division") + throw new Error( + "invalid filter value - need to select a single division" + ); } - for (var i = 1; i < record.attributes.divisions.length; i++) { + for (var i = 1; i < record.attributes.divisions.length; i++) { if (record.attributes.divisions[i].divisionid === filterValue) { - let updateRecord = records.find(r => r.documentmasterid === record.attributes.divisions[i].duplicatemasterid); - updateRecords.push((({ recordid, documentmasterid, s3uripath }) => ({ recordid, documentmasterid, filepath: s3uripath }))(updateRecord)); - updateRecords = updateRecords.concat(updateRecord.attachments?.map(a => - (({ documentmasterid, s3uripath }) => ({ documentmasterid, filepath: s3uripath }))(a) - )); + let updateRecord = records.find( + (r) => + r.documentmasterid === + record.attributes.divisions[i].duplicatemasterid + ); + updateRecords.push( + (({ recordid, documentmasterid, s3uripath }) => ({ + recordid, + documentmasterid, + filepath: s3uripath, + }))(updateRecord) + ); + updateRecords = updateRecords.concat( + updateRecord.attachments?.map((a) => + (({ documentmasterid, s3uripath }) => ({ + documentmasterid, + filepath: s3uripath, + }))(a) + ) + ); } } } else { - updateRecords.push((({ recordid, documentmasterid, s3uripath }) => ({ recordid, documentmasterid, filepath: s3uripath }))(record)); - updateRecords = updateRecords.concat(record.attachments?.map(a => - (({ documentmasterid, s3uripath }) => ({ documentmasterid, filepath: s3uripath }))(a) - )); + updateRecords.push( + (({ recordid, documentmasterid, s3uripath }) => ({ + recordid, + documentmasterid, + filepath: s3uripath, + }))(record) + ); + updateRecords = updateRecords.concat( + record.attachments?.map((a) => + (({ documentmasterid, s3uripath }) => ({ + documentmasterid, + filepath: s3uripath, + }))(a) + ) + ); } } } - dispatch(updateFOIRecords(requestId, ministryId, {records: updateRecords, divisions:[{divisionid: divisionModalTagValue}], isdelete: false}, (err, _res) => { - dispatchRequestAttachment(err); - })); - } + dispatch( + updateFOIRecords( + requestId, + ministryId, + { + records: updateRecords, + divisions: [{ divisionid: divisionModalTagValue }], + isdelete: false, + }, + (err, _res) => { + dispatchRequestAttachment(err); + } + ) + ); + }; return (
    @@ -1057,63 +1410,78 @@ export const RecordsLog = ({ {getRequestNumber()} - + - {hasDocumentsToDownload && + {hasDocumentsToDownload && ( - // {/* */} - // {/* */} - // record.isredactionready ? - // : - // record.failed ? - // : - // - // - // }} - InputLabelProps={{ shrink: false }} - select - name="download" - value={currentDownload} - onChange={handleDownloadChange} - placeholder="Download" - variant="outlined" - size="small" - fullWidth - > - {recordsDownloadList.map((item, index) => { - - if (item.id !=0) { - return ( + className="download-dropdown custom-select-wrapper foi-download-button" + id="download" + label={currentDownload === 0 ? "Download" : ""} + inputProps={{ "aria-labelledby": "download-label" }} + // InputProps={{ + // startAdornment: isDownloadInProgress && + // {/* */} + // {/* */} + // record.isredactionready ? + // : + // record.failed ? + // : + // + // + // }} + InputLabelProps={{ shrink: false }} + select + name="download" + value={currentDownload} + onChange={handleDownloadChange} + placeholder="Download" + variant="outlined" + size="small" + fullWidth + > + {recordsDownloadList.map((item, index) => { + if (item.id != 0) { + return ( - { - !item.disabled && (isDownloadReady ? - : - isDownloadFailed ? - : - isDownloadInProgress ? :null) + disabled={ + item.disabled || conversionFormats?.length < 1 } + sx={{ display: "flex" }} + > + {!item.disabled && + (isDownloadReady ? ( + + ) : isDownloadFailed ? ( + + ) : isDownloadInProgress ? ( + + ) : null)} {item.label} - // - ) - } - - } )} + // + ); + } + })} - } - + )} {/* @@ -1128,29 +1496,38 @@ export const RecordsLog = ({ */} - {isMinistryCoordinator ? + {isMinistryCoordinator ? ( : - (records?.length > 0 && DISABLE_REDACT_WEBLINK?.toLowerCase() =='false' && - - ) - } - - + + + ) + )} + > - -
    Total Uploaded Size : {getReadableFileSize(totalUploadedRecordSize)}
    -
    Total Upload Limit : {getReadableFileSize(TOTAL_RECORDS_UPLOAD_LIMIT)}
    + +
    + Total Uploaded Size :{" "} + {getReadableFileSize(totalUploadedRecordSize)} +
    +
    + Total Upload Limit :{" "} + {getReadableFileSize(TOTAL_RECORDS_UPLOAD_LIMIT)} +
    - + Files Uploaded: - {recordsObj.dedupedfiles} + + {recordsObj.dedupedfiles} + Duplicates Removed: - {recordsObj.removedfiles} + + {recordsObj.removedfiles} + Files Converted to PDF: - {recordsObj.convertedfiles} - + + {recordsObj.convertedfiles} + - - Batches Uploaded: - {recordsObj.batchcount ? recordsObj.batchcount : 0} + + Batches Uploaded: + + {recordsObj.batchcount ? recordsObj.batchcount : 0} + {/* @@ -1210,7 +1600,10 @@ export const RecordsLog = ({ className={classes.divider} > - + {setSearchValue(e.target.value.trim())}} + onChange={(e) => { + setSearchValue(e.target.value.trim()); + }} sx={{ color: "#38598A", }} @@ -1263,7 +1658,9 @@ export const RecordsLog = ({ aria-label="Search Icon" className="search-icon" > - Filter Records ... + + Filter Records ... + @@ -1277,11 +1674,11 @@ export const RecordsLog = ({ sx={{ border: "1px solid #38598A", color: "#38598A", - maxWidth:"100%", + maxWidth: "100%", paddingTop: "8px", borderTopLeftRadius: 0, borderTopRightRadius: 0, - borderTop: "none" + borderTop: "none", }} alignItems="center" justifyContent="flex-start" @@ -1291,19 +1688,35 @@ export const RecordsLog = ({ xs={12} elevation={0} > - {divisionFilters.map(division => + {divisionFilters.map((division) => ( {setFilterValue(division.divisionid === filterValue ? -1 : division.divisionid)}} + onClick={(e) => { + setFilterValue( + division.divisionid === filterValue + ? -1 + : division.divisionid + ); + }} clicked={filterValue == division.divisionid} /> - )} + ))} - Remove Attachments
    }> + Remove Attachments + } + > - To update divisions:
      -
    • at least one record must be selected
    • -
    • all records selected must be tagged to the same division
    • -
    • and all records selected must be finished processing
    • -
    : -
    Update Divisions
    + + To update divisions:{" "} +
      +
    • at least one record must be selected
    • +
    • + all records selected must be tagged to the same + division +
    • +
    • + {" "} + and all records selected must be finished processing +
    • +
    + + ) : ( +
    Update Divisions
    + ) } - sx={{fontSize: "11px"}} + sx={{ fontSize: "11px" }} >
    - Delete}> + Delete}> @@ -1382,24 +1833,35 @@ export const RecordsLog = ({ justify="flex-start" alignItems="flex-start" className={classes.recordLog} - > - { - isRecordsfetching === "completed" && records?.length > 0 ? - records?.map((record, i) => - - ) :
    {isRecordsfetching === "inprogress" ? "Records loading is in progress, please wait!" : (isRecordsfetching === "completed" && ( records?.length === 0 || records === null || records === undefined) ? "No records are available to list, please confirm whether records are uploaded or not" : (isRecordsfetching === "error" ? "Error fetching records, please try again.": {isRecordsfetching}) )}
    - - } + > + {isRecordsfetching === "completed" && records?.length > 0 ? ( + records?.map((record, i) => ( + + )) + ) : ( +
    + {isRecordsfetching === "inprogress" + ? "Records loading is in progress, please wait!" + : isRecordsfetching === "completed" && + (records?.length === 0 || + records === null || + records === undefined) + ? "No records are available to list, please confirm whether records are uploaded or not" + : isRecordsfetching === "error" + ? "Error fetching records, please try again." + : { isRecordsfetching }} +
    + )} @@ -1416,32 +1878,50 @@ export const RecordsLog = ({ isMinistryCoordinator={isMinistryCoordinator} uploadFor={"record"} bcgovcode={bcgovcode} - divisions={divisions.filter(d => d.divisionname.toLowerCase() !== 'communications')} + divisions={divisions.filter( + (d) => d.divisionname.toLowerCase() !== "communications" + )} totalUploadedRecordSize={totalUploadedRecordSize} replacementfiletypes={getreplacementfiletypes()} />
    {setDeleteModalOpen(false); setDivisionModalTagValue(-1)}} + onClose={() => { + setDeleteModalOpen(false); + setDivisionModalTagValue(-1); + }} aria-labelledby="state-change-dialog-title" aria-describedby="state-change-dialog-description" - maxWidth={'md'} + maxWidth={"md"} fullWidth={true} // id="state-change-dialog" > -

    Remove Attachments

    - {setDeleteModalOpen(false); setDivisionModalTagValue(-1)}}> - Close - - -
    - - +

    Remove Attachments

    + { + setDeleteModalOpen(false); + setDivisionModalTagValue(-1); + }} + > + Close + + + + + - Are you sure you want to delete the attachments from this request?

    - This will remove all attachments from the redaction app. + Are you sure you want to delete the attachments from this + request?

    + + This will remove all attachments from the redaction app. +
    @@ -1452,7 +1932,13 @@ export const RecordsLog = ({ > Continue - @@ -1464,72 +1950,118 @@ export const RecordsLog = ({ onClose={() => setDivisionsModalOpen(false)} aria-labelledby="state-change-dialog-title" aria-describedby="state-change-dialog-description" - maxWidth={'md'} + maxWidth={"md"} fullWidth={true} // id="state-change-dialog" > -

    Update Divisions

    - setDivisionsModalOpen(false)}> - Close - - -
    - - - {(records.filter(r=>(r.isselected && r.attributes.divisions.length > 1)).length > 0 && filterValue < 0) ? - <> - You have selected a record that was provided by more than one division.

    - To change the division you must first filter the Records Log by the division that you want to no longer be associated with the selected records. -


    - : +

    Update Divisions

    + setDivisionsModalOpen(false)} + > + Close + + + + + + {records.filter( + (r) => r.isselected && r.attributes.divisions.length > 1 + ).length > 0 && filterValue < 0 ? ( <> - - Select the divisions that corresponds to the records you have selected.

    - This will update the divisions on all records you have selected both in the gathering records log and the redaction app. -




    - - {divisions.filter(division => { - if (division.divisionname.toLowerCase() === 'communications') { - return false; - } else if (filterValue > -1 && filterValue !== division.divisionid) { - return true; - } else if (records.filter(r => r.isselected)[0]?.attributes.divisions[0].divisionid !== division.divisionid) { - return true; - } else { - return false; - } - }).map(division => - {setDivisionModalTagValue(division.divisionid)}} - clicked={divisionModalTagValue === division.divisionid} - /> - )} - - } + + You have selected a record that was provided by more + than one division.

    + To change the division you must first filter the Records + Log by the division that you want to no longer be + associated with the selected records. +
    +

    + + ) : ( + <> + + Select the divisions that corresponds to the records you + have selected.

    + This will update the divisions on all records you have + selected both in the gathering records log and the + redaction app. +
    +

    +

    + + {divisions + .filter((division) => { + if ( + division.divisionname.toLowerCase() === + "communications" + ) { + return false; + } else if ( + filterValue > -1 && + filterValue !== division.divisionid + ) { + return true; + } else if ( + records.filter((r) => r.isselected)[0]?.attributes + .divisions[0].divisionid !== division.divisionid + ) { + return true; + } else { + return false; + } + }) + .map((division) => ( + { + setDivisionModalTagValue(division.divisionid); + }} + clicked={ + divisionModalTagValue === division.divisionid + } + /> + ))} + + + )}
    @@ -1540,7 +2072,10 @@ export const RecordsLog = ({ > Continue - @@ -1550,437 +2085,538 @@ export const RecordsLog = ({ )}
    ); -} - - -const Attachment = React.memo(({indexValue, record, handlePopupButtonClick, getFullname, isMinistryCoordinator,ministryId,handleSelectRecord}) => { - - const classes = useStyles(); - const [disabled, setDisabled] = useState(false); - const [isRetry, setRetry] = useState(false); - // useEffect(() => { - // if(record && record.filename) { - // setDisabled(isMinistryCoordinator && record.category == 'personal') - // } - // }, [record]) - - const getCategory = (category) => { - return AttachmentCategories.categorys.find(element => element.name === category); - } - - const handleSelect = (e) => { - handleSelectRecord(record, e); - } +}; + +const Attachment = React.memo( + ({ + indexValue, + record, + handlePopupButtonClick, + getFullname, + isMinistryCoordinator, + ministryId, + handleSelectRecord, + }) => { + const classes = useStyles(); + const [disabled, setDisabled] = useState(false); + const [isRetry, setRetry] = useState(false); + // useEffect(() => { + // if(record && record.filename) { + // setDisabled(isMinistryCoordinator && record.category == 'personal') + // } + // }, [record]) + + const getCategory = (category) => { + return AttachmentCategories.categorys.find( + (element) => element.name === category + ); + }; - const recordtitle = ()=>{ + const handleSelect = (e) => { + handleSelectRecord(record, e); + }; - if (disabled) - { - return ( -
    - {record.filename} -
    - ) - } + const recordtitle = () => { + if (disabled) { + return ( +
    {record.filename}
    + ); + } - if(record.filename.toLowerCase().indexOf('.eml') > 0 || record.filename.toLowerCase().indexOf('.msg') > 0 || record.filename.toLowerCase().indexOf('.txt') > 0) - { - return ( -
    handlePopupButtonClick("download", record)} - > - {record.filename} -
    - ) + if ( + record.filename.toLowerCase().indexOf(".eml") > 0 || + record.filename.toLowerCase().indexOf(".msg") > 0 || + record.filename.toLowerCase().indexOf(".txt") > 0 + ) { + return ( +
    handlePopupButtonClick("download", record)} + > + {record.filename} +
    + ); + } else { + return ( +
    { + opendocumentintab(record, ministryId); + }} + className="record-name viewrecord" + > + {record.filename} +
    + ); + } + }; - } - else{ - return ( -
    { - opendocumentintab(record,ministryId); - }} - className="record-name viewrecord" + return ( + <> + - {record.filename} -
    - ) - } - - - } - - return ( - <> - - - - - {record.isattachment && } - { - record.isduplicate ? - : - record.attributes?.incompatible && record.attributes?.trigger !== 'recordreplace' ? - : - record.isredactionready ? - : - record.failed ? - : - isrecordtimeout(record.created_at, RECORD_PROCESSING_HRS) == true && isRetry == false ? - : - - } - {record.filename} - {record?.attributes?.filesize > 0 ? (record?.attributes?.filesize / 1024).toFixed(2) : 0} KB - - + + {record.isattachment && ( + + )} + {record.isduplicate ? ( + + ) : record.attributes?.incompatible && + record.attributes?.trigger !== "recordreplace" ? ( + + ) : record.isredactionready ? ( + + ) : record.failed ? ( + + ) : isrecordtimeout(record.created_at, RECORD_PROCESSING_HRS) == + true && isRetry == false ? ( + + ) : ( + + )} + + {record.filename}{" "} + + + {record?.attributes?.filesize > 0 + ? (record?.attributes?.filesize / 1024).toFixed(2) + : 0}{" "} + KB + + + - { - record.isduplicate ? - Duplicate of {record.duplicateof}: - record.attributes?.incompatible && record.attributes?.trigger !== 'recordreplace' ? - Incompatible File Type: - (record.failed && record.isredactionready) || (record.attributes?.trigger === 'recordreplace' && record.attributes?.isattachment) ? - Record Manually Replaced Due to Error: - record.attributes?.isattachment ? - Attachment of {record.attachmentof}: - record.isredactionready ? - Ready for Redaction: - record.failed ? - Error during {record.failed}: - isrecordtimeout(record.created_at, RECORD_PROCESSING_HRS) == true && isRetry == false ? - Error due to timeout: + className={classes.recordStatus} + > + {record.isduplicate ? ( + Duplicate of {record.duplicateof} + ) : record.attributes?.incompatible && + record.attributes?.trigger !== "recordreplace" ? ( + Incompatible File Type + ) : (record.failed && record.isredactionready) || + (record.attributes?.trigger === "recordreplace" && + record.attributes?.isattachment) ? ( + Record Manually Replaced Due to Error + ) : record.attributes?.isattachment ? ( + + Attachment of {record.attachmentof} + + ) : record.isredactionready ? ( + Ready for Redaction + ) : record.failed ? ( + Error during {record.failed} + ) : isrecordtimeout(record.created_at, RECORD_PROCESSING_HRS) == + true && isRetry == false ? ( + Error due to timeout + ) : ( Deduplication & file conversion in progress - } + )} - - - - - {record.attributes?.divisions?.map((division, i) => - - )} + - -
    + + {record.attributes?.divisions?.map((division, i) => ( + + ))} + + - {getFullname(record.createdby)} -
    -
    - -
    + {getFullname(record.createdby)} +
    +
    + - {record.created_at} - +
    {record.created_at}
    +
    - - - - + + + + - - {record.attachments?.map((attachment, i) => - - )} - - ); -}) - -const opendocumentintab =(record,ministryId)=> -{ - let relativedocpath = record.documentpath.split('/').slice(4).join('/') - let url =`/foidocument?id=${ministryId}&filepath=${relativedocpath}`; - window.open(url, '_blank').focus(); -} - -const AttachmentPopup = React.memo(({indexValue, record, handlePopupButtonClick, disabled,ministryId, setRetry}) => { - const ref = React.useRef(); - const closeTooltip = () => ref.current && ref ? ref.current.close():{}; - - - const handleRename = () => { - closeTooltip(); - handlePopupButtonClick("rename", record); - } - - const handleReplace = () => { - closeTooltip(); - handlePopupButtonClick("replace", record); - } - - const handleReplaceAttachment = () => { - closeTooltip(); - handlePopupButtonClick("replaceattachment", record); + {record.attachments?.map((attachment, i) => ( + + ))} + + ); } +); + +const opendocumentintab = (record, ministryId) => { + let relativedocpath = record.documentpath.split("/").slice(4).join("/"); + let url = `/foidocument?id=${ministryId}&filepath=${relativedocpath}`; + window.open(url, "_blank").focus(); +}; + +const AttachmentPopup = React.memo( + ({ + indexValue, + record, + handlePopupButtonClick, + disabled, + ministryId, + setRetry, + }) => { + const ref = React.useRef(); + const closeTooltip = () => (ref.current && ref ? ref.current.close() : {}); + + const handleRename = () => { + closeTooltip(); + handlePopupButtonClick("rename", record); + }; - const handleDownload = () =>{ - closeTooltip(); - handlePopupButtonClick("download", record); - } + const handleReplace = () => { + closeTooltip(); + handlePopupButtonClick("replace", record); + }; - const handleDownloadPDF = () =>{ - closeTooltip(); - handlePopupButtonClick("downloadPDF", record); - } + const handleReplaceAttachment = () => { + closeTooltip(); + handlePopupButtonClick("replaceattachment", record); + }; - const handleDownloadoriginal = () =>{ - closeTooltip(); - handlePopupButtonClick("downloadoriginal", record); - } + const handleDownload = () => { + closeTooltip(); + handlePopupButtonClick("download", record); + }; - const handleView =()=>{ - closeTooltip(); - opendocumentintab(record,ministryId); - } + const handleDownloadPDF = () => { + closeTooltip(); + handlePopupButtonClick("downloadPDF", record); + }; - const handleDelete = () => { - closeTooltip(); - handlePopupButtonClick("delete", record); - }; + const handleDownloadoriginal = () => { + closeTooltip(); + handlePopupButtonClick("downloadoriginal", record); + }; - const handleRetry = () => { - setRetry(true) - closeTooltip(); - handlePopupButtonClick("retry", record); - }; + const handleView = () => { + closeTooltip(); + opendocumentintab(record, ministryId); + }; - const transitionStates = [ - "statetransition", - StateTransitionCategories.cfrreview.name, - StateTransitionCategories.cfrfeeassessed.name, - StateTransitionCategories.signoffresponse.name, - StateTransitionCategories.harmsreview.name - ]; + const handleDelete = () => { + closeTooltip(); + handlePopupButtonClick("delete", record); + }; - const showReplace = (category) => { - return transitionStates.includes(category.toLowerCase()); - } - const [popoverOpen, setPopoverOpen] = useState(false); - const [anchorPosition, setAnchorPosition] = useState(null); + const handleRetry = () => { + setRetry(true); + closeTooltip(); + handlePopupButtonClick("retry", record); + }; - const ReplaceMenu = () => { - return ( - { - handleReplace(); - setPopoverOpen(false); - }} - > - Replace - - ) - } + const transitionStates = [ + "statetransition", + StateTransitionCategories.cfrreview.name, + StateTransitionCategories.cfrfeeassessed.name, + StateTransitionCategories.signoffresponse.name, + StateTransitionCategories.harmsreview.name, + ]; - const DeleteMenu = () => { + const showReplace = (category) => { + return transitionStates.includes(category.toLowerCase()); + }; + const [popoverOpen, setPopoverOpen] = useState(false); + const [anchorPosition, setAnchorPosition] = useState(null); - return ( - { - handleDelete(); - setPopoverOpen(false); - }} - > - Delete - - ) + const ReplaceMenu = () => { + return ( + { + handleReplace(); + setPopoverOpen(false); + }} + > + Replace + + ); + }; - } + const DeleteMenu = () => { + return ( + { + handleDelete(); + setPopoverOpen(false); + }} + > + Delete + + ); + }; - // const AddMenuItems = () => { - // if (showReplace(record.category)) - // return () - // return () - // } + // const AddMenuItems = () => { + // if (showReplace(record.category)) + // return () + // return () + // } - const ActionsPopover = ({RestrictViewInBrowser, record}) => { - const conversionFormats = useSelector((state) => state.foiRequests.conversionFormats) - return ( - setPopoverOpen(false)} - > - -{!RestrictViewInBrowser ? - { - handleView(); - setPopoverOpen(false); - }} - > - View - - :""} - { (!record.attributes?.isattachment || record.attributes?.isattachment === undefined) && { - handleReplace(); - setPopoverOpen(false); - }} - > - Replace Manually - } - { record.attributes?.isattachment && { - handleReplaceAttachment() - setPopoverOpen(false); - }} - > - Replace Attachment - } - {record.originalfile!='' && record.originalfile!=undefined && { - handleDownloadoriginal(); - setPopoverOpen(false); - }} - > - Download Original - + const ActionsPopover = ({ RestrictViewInBrowser, record }) => { + const conversionFormats = useSelector( + (state) => state.foiRequests.conversionFormats + ); + return ( + { - handleDownloadPDF(); - setPopoverOpen(false); - }} - > - {(record.attributes?.isattachment && record.attributes?.trigger === 'recordreplace') ? 'Download Replaced' : 'Download Converted'} - } - { + open={popoverOpen} + anchorOrigin={{ + vertical: "top", + horizontal: "left", + }} + transformOrigin={{ + vertical: "top", + horizontal: "left", + }} + onClose={() => setPopoverOpen(false)} + > + + {!RestrictViewInBrowser ? ( + { + handleView(); + setPopoverOpen(false); + }} + > + View + + ) : ( + "" + )} + {(!record.attributes?.isattachment || + record.attributes?.isattachment === undefined) && ( + { + handleReplace(); + setPopoverOpen(false); + }} + > + Replace Manually + + )} + {record.attributes?.isattachment && ( + { + handleReplaceAttachment(); + setPopoverOpen(false); + }} + > + Replace Attachment + + )} + {record.originalfile != "" && record.originalfile != undefined && ( + { + handleDownloadoriginal(); + setPopoverOpen(false); + }} + > + Download Original + + )} + {((record.isredactionready && + conversionFormats.includes( + record.attributes?.extension?.toLowerCase() + )) || + (record.attributes?.isattachment && + record.attributes?.trigger === "recordreplace")) && ( + { + handleDownloadPDF(); + setPopoverOpen(false); + }} + > + {record.attributes?.isattachment && + record.attributes?.trigger === "recordreplace" + ? "Download Replaced" + : "Download Converted"} + + )} + { handleDownload(); setPopoverOpen(false); - }} - > - {record.originalfile!='' && record.originalfile!=undefined ? "Download Replaced" : record.attributes?.isattachment ? "Download Original" : "Download" } - - {!record.isattachment && } - {!record.isredactionready && (record.failed || isrecordtimeout(record.created_at, RECORD_PROCESSING_HRS) == true) && { - handleRetry(); - setPopoverOpen(false); - }} - > - Re-Try - } + }} + > + {record.originalfile != "" && record.originalfile != undefined + ? "Download Replaced" + : record.attributes?.isattachment + ? "Download Original" + : "Download"} + + {!record.isattachment && } + {!record.isredactionready && + (record.failed || + isrecordtimeout(record.created_at, RECORD_PROCESSING_HRS) == + true) && ( + { + handleRetry(); + setPopoverOpen(false); + }} + > + Re-Try + + )} - {/* {record.category === "personal" ? ( + {/* {record.category === "personal" ? ( "" ) : } */} - - - ); - }; + + + ); + }; - return ( - <> - { - setPopoverOpen(true); - setAnchorPosition( - e?.currentTarget?.getBoundingClientRect() - ); - }} - > - - - - - ); -}) + return ( + <> + { + setPopoverOpen(true); + setAnchorPosition(e?.currentTarget?.getBoundingClientRect()); + }} + > + + + + + ); + } +); -export default RecordsLog +export default RecordsLog; diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/util.js b/forms-flow-web/src/components/FOI/customComponents/Records/util.js index 6e076792d..82773a95f 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/util.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/util.js @@ -14,7 +14,7 @@ const sortAttachmentsByLastModified = (attachments) => new Date(b?.attributes?.lastmodified) ); -export const getPDFFilePath = (item, conversionFormats) => { +export const getPDFFilePath = (item) => { let pdffilepath = item.s3uripath; let pdffilename = item.filename; @@ -31,11 +31,7 @@ export const getPDFFilePath = (item, conversionFormats) => { return [pdffilepath, pdffilename]; }; -function arrangeAttachments( - attachments, - parentDocumentMasterId, - conversionFormats -) { +function arrangeAttachments(attachments, parentDocumentMasterId) { const attachmentsMap = {}; const arrangedAttachments = []; @@ -61,18 +57,14 @@ function arrangeAttachments( // Start arranging attachments from the root level arrangeChildren(parentDocumentMasterId); - getUpdatedRecords(arrangedAttachments, conversionFormats, true); - return getUpdatedRecords(arrangedAttachments, conversionFormats, true); + getUpdatedRecords(arrangedAttachments, true); + return getUpdatedRecords(arrangedAttachments, true); } // Get records with only necessary fields -export const getUpdatedRecords = ( - _records, - conversionFormats, - isattachment = false -) => { +export const getUpdatedRecords = (_records, isattachment = false) => { return _records.map((_record) => { - const [filepath, filename] = getPDFFilePath(_record, conversionFormats); + const [filepath, filename] = getPDFFilePath(_record); const deduplicatedAttachments = _record?.attachments?.length > 0 ? removeDuplicateFiles(_record.attachments) @@ -91,11 +83,7 @@ export const getUpdatedRecords = ( divisions: _record.attributes.divisions, divisionids: _record.attributes.divisions.map((d) => d.divisionid), attachments: !isattachment - ? arrangeAttachments( - sortedAttachments, - _record.documentmasterid, - conversionFormats - ) + ? arrangeAttachments(sortedAttachments, _record.documentmasterid) : undefined, }; return _recordObj; From 76a01fa381a76f892c9936d99161fb0ab48e8722 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 8 Sep 2023 12:02:13 -0700 Subject: [PATCH 134/234] fix bug with showing applicant category docs for ministry if there are 2 or more --- .../FOI/FOIRequest/MinistryReview/MinistryReview.js | 4 ---- .../request_api/services/documentservice.py | 3 +-- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js index d0cd128f1..ef13a0b07 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js @@ -53,7 +53,6 @@ import Loading from "../../../../containers/Loading"; import ExtensionDetails from "./ExtensionDetails"; import clsx from "clsx"; import { getMinistryBottomTextMap, alertUser, getHeaderText } from "./utils"; -import { isMinistryLogin } from '../../../../helper/FOI/helper'; import DivisionalTracking from "../DivisionalTracking"; import HomeIcon from '@mui/icons-material/Home'; import { RecordsLog } from '../../customComponents/Records'; @@ -107,8 +106,6 @@ const MinistryReview = React.memo(({ userDetail }) => { const [_currentrequestStatus, setcurrentrequestStatus] = React.useState(""); const [_tabStatus, settabStatus] = React.useState(requestState); - const userGroups = userDetail?.groups?.map(group => group.slice(1)); - let isMinistryUser = isMinistryLogin(userGroups); //gets the request detail from the store const IsDivisionalCoordinator = () => { return userDetail?.role?.includes("DivisionalCoordinator"); @@ -123,7 +120,6 @@ const MinistryReview = React.memo(({ userDetail }) => { let requestAttachments = useSelector( (state) => state.foiRequests.foiRequestAttachments.filter( attachment => { - if (isMinistryUser && attachment?.category?.toLowerCase() === 'applicant' && requestDetails?.requestType?.toLowerCase() === 'personal') return false return ['feeassessed-onhold', 'fee estimate - payment receipt', 'response-onhold', 'fee balance outstanding - payment receipt'] .indexOf(attachment.category.toLowerCase()) === -1 } diff --git a/request-management-api/request_api/services/documentservice.py b/request-management-api/request_api/services/documentservice.py index bb9d1c023..e82b37b20 100644 --- a/request-management-api/request_api/services/documentservice.py +++ b/request-management-api/request_api/services/documentservice.py @@ -37,8 +37,7 @@ def getrequestdocumentsbyrole(self, requestid, requesttype, isministrymember): for document in documents: if document["category"] == "personal": document["documentpath"] = "" - if metaobj["requesttype"].lower() == RequestType.GENERAL.value.lower() and document["category"] == 'applicant': - documents.remove(document) + documents = [d for d in documents if d["category"] == 'applicant'] return documents def createrequestdocument(self, requestid, documentschema, userid, requesttype): From 615bd951e5b8f1a30152005f7ff6acf4b8b0bf74 Mon Sep 17 00:00:00 2001 From: nkan-aot <96087745+nkan-aot@users.noreply.github.com> Date: Fri, 8 Sep 2023 15:31:30 -0700 Subject: [PATCH 135/234] fix wrong equality sign --- request-management-api/request_api/services/documentservice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/request-management-api/request_api/services/documentservice.py b/request-management-api/request_api/services/documentservice.py index e82b37b20..45fa72ae8 100644 --- a/request-management-api/request_api/services/documentservice.py +++ b/request-management-api/request_api/services/documentservice.py @@ -37,7 +37,7 @@ def getrequestdocumentsbyrole(self, requestid, requesttype, isministrymember): for document in documents: if document["category"] == "personal": document["documentpath"] = "" - documents = [d for d in documents if d["category"] == 'applicant'] + documents = [d for d in documents if d["category"] != 'applicant'] return documents def createrequestdocument(self, requestid, documentschema, userid, requesttype): From 6264406cc0abefc591ff29e61bb52091870ab55e Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Fri, 8 Sep 2023 15:45:44 -0700 Subject: [PATCH 136/234] Added validation for download harms records which ensures that harms cannot be downloaded untill all records are redaction ready (isredactionready === true). --- .../src/components/FOI/customComponents/Records/index.js | 9 ++++++++- forms-flow-web/src/constants/FOI/enum.js | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/index.js b/forms-flow-web/src/components/FOI/customComponents/Records/index.js index 9b46bee74..fc06c171d 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/index.js @@ -194,7 +194,6 @@ export const RecordsLog = ({ useEffect(() => { if (recordsTabSelect && conversionFormats?.length < 1) { - console.log("match"); toast.error( "Temporarily unable to save your request. Please try again in a few minutes.", { @@ -1037,6 +1036,10 @@ export const RecordsLog = ({ })); } + const enableHarmsDonwnload = () => { + return !recordsObj.records.every(record => record.isredactionready); + } + return (
    {isAttachmentLoading ? ( @@ -1088,6 +1091,10 @@ export const RecordsLog = ({ > {recordsDownloadList.map((item, index) => { + if (item.id === 1) { + item.disabled = enableHarmsDonwnload(); + } + if (item.id !=0) { return ( Date: Fri, 8 Sep 2023 16:12:25 -0700 Subject: [PATCH 137/234] rewrite code --- .../request_api/services/documentservice.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/request-management-api/request_api/services/documentservice.py b/request-management-api/request_api/services/documentservice.py index 45fa72ae8..3fd2c72d1 100644 --- a/request-management-api/request_api/services/documentservice.py +++ b/request-management-api/request_api/services/documentservice.py @@ -34,10 +34,13 @@ def getrequestdocumentsbyrole(self, requestid, requesttype, isministrymember): documents = self.getactiverequestdocuments(requestid, requesttype) if isministrymember: metaobj = FOIMinistryRequest.getmetadata(requestid) + filtereddocuments = [] for document in documents: if document["category"] == "personal": document["documentpath"] = "" - documents = [d for d in documents if d["category"] != 'applicant'] + if document["category"] != "applicant": + filtereddocuments.append(document) + return filtereddocuments return documents def createrequestdocument(self, requestid, documentschema, userid, requesttype): From 950a6829b4ac358acb6a4f8489c4dd7a7cf34540 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Mon, 11 Sep 2023 11:36:40 -0700 Subject: [PATCH 138/234] Ticket completed. Users cannot download harms package until isredactionready is true or if isredactionready is false can download harms only if a timeout/failed error exists --- .../src/components/FOI/customComponents/Records/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/index.js b/forms-flow-web/src/components/FOI/customComponents/Records/index.js index fc06c171d..8902b5c7f 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/index.js @@ -1037,7 +1037,7 @@ export const RecordsLog = ({ } const enableHarmsDonwnload = () => { - return !recordsObj.records.every(record => record.isredactionready); + return !recordsObj.records.every(record => record.isredactionready || (!record.isredactionready && (record.failed || isrecordtimeout(record.created_at, RECORD_PROCESSING_HRS)))); } return ( From 37e2d82f9b3140c14a9f14fd8b44da17048ec4e5 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Mon, 11 Sep 2023 17:03:04 -0700 Subject: [PATCH 139/234] Added processing icon to greyed out Donwload Harms to let user know that download harms is pending --- .../FOI/customComponents/Records/index.js | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/index.js b/forms-flow-web/src/components/FOI/customComponents/Records/index.js index 8902b5c7f..9fe7eb8bc 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/index.js @@ -1090,9 +1090,28 @@ export const RecordsLog = ({ fullWidth > {recordsDownloadList.map((item, index) => { - if (item.id === 1) { - item.disabled = enableHarmsDonwnload(); + item.disabled = enableHarmsDonwnload() + return ( + + { + item.disabled ? + : + !item.disabled && (isDownloadReady ? + : + isDownloadFailed ? + : + isDownloadInProgress ? :null) + } + {item.label} + + ) } if (item.id !=0) { @@ -1113,7 +1132,6 @@ export const RecordsLog = ({ } {item.label} - // ) } From 85d26d02c78dbd7c4d4fd79f8d255da100c666a0 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Mon, 11 Sep 2023 17:40:24 -0700 Subject: [PATCH 140/234] REfactored and added tooltip messsage when download harms is not enabled --- .../FOI/customComponents/Records/index.js | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/index.js b/forms-flow-web/src/components/FOI/customComponents/Records/index.js index 9fe7eb8bc..4e8581eaa 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/index.js @@ -1036,10 +1036,16 @@ export const RecordsLog = ({ })); } + //function to manage download for harms option const enableHarmsDonwnload = () => { return !recordsObj.records.every(record => record.isredactionready || (!record.isredactionready && (record.failed || isrecordtimeout(record.created_at, RECORD_PROCESSING_HRS)))); } + //useEffect to manage enabling and disabling of download for harms package + useEffect(() => { + recordsDownloadList[1].disabled = enableHarmsDonwnload(); + }, [recordsObj]) + return (
    {isAttachmentLoading ? ( @@ -1090,9 +1096,10 @@ export const RecordsLog = ({ fullWidth > {recordsDownloadList.map((item, index) => { - if (item.id === 1) { - item.disabled = enableHarmsDonwnload() + if (item.id === 1 && item.disabled) { return ( + File conversion and deduplication in progress
    } key={item.id}> +
    - { - item.disabled ? - : - !item.disabled && (isDownloadReady ? - : - isDownloadFailed ? - : - isDownloadInProgress ? :null) - } + {item.label} +
    + ) } From 107db26276ccbbafede7b8113c717d319c368f08 Mon Sep 17 00:00:00 2001 From: divyav-aot Date: Tue, 12 Sep 2023 16:41:20 -0400 Subject: [PATCH 141/234] Remove the dependency on recordformats.json for displaying the record list --- .../FOI/customComponents/Records/index.js | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/index.js b/forms-flow-web/src/components/FOI/customComponents/Records/index.js index f7ae983b1..90ba4e89b 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/index.js @@ -875,12 +875,7 @@ export const RecordsLog = ({ for (let record of exporting) { var filepath = record.s3uripath; var filename = record.filename; - if ( - record.isredactionready && - conversionFormats.includes( - record.attributes?.extension?.toLowerCase() - ) - ) { + if (record.isredactionready && record.isconverted) { filepath = filepath.substr(0, filepath.lastIndexOf(".")) + ".pdf"; filename += ".pdf"; } @@ -1446,9 +1441,7 @@ export const RecordsLog = ({ className="download-menu-item" key={item.id} value={index} - disabled={ - item.disabled || conversionFormats?.length < 1 - } + disabled={item.disabled} sx={{ display: "flex" }} > {!item.disabled && @@ -2477,9 +2470,6 @@ const AttachmentPopup = React.memo( // } const ActionsPopover = ({ RestrictViewInBrowser, record }) => { - const conversionFormats = useSelector( - (state) => state.foiRequests.conversionFormats - ); return ( )} - {((record.isredactionready && - conversionFormats.includes( - record.attributes?.extension?.toLowerCase() - )) || + {((record.isredactionready && record.isconverted) || (record.attributes?.isattachment && record.attributes?.trigger === "recordreplace")) && ( Date: Tue, 12 Sep 2023 15:06:45 -0700 Subject: [PATCH 142/234] Removed get all divisions endpoint from foi-flow api as its no longer used. NEW SOLUTION FOR SOURCE OF TRUTH = docreviewer backend getdocuments uses bcgovcode to gather divisons for recrods based on bcgovcode from foi-flow backend. More efficient call than gathering all divs. Ticket completed --- .../resources/foiflowmasterdata.py | 20 ------------------- .../tests/restapi/test_foimasterdata_api.py | 6 +----- 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/request-management-api/request_api/resources/foiflowmasterdata.py b/request-management-api/request_api/resources/foiflowmasterdata.py index 5a1a8bf09..3e04fbda3 100644 --- a/request-management-api/request_api/resources/foiflowmasterdata.py +++ b/request-management-api/request_api/resources/foiflowmasterdata.py @@ -33,8 +33,6 @@ from request_api.services.extensionreasonservice import extensionreasonservice from request_api.services.cacheservice import cacheservice from request_api.services.subjectcodeservice import subjectcodeservice -from request_api.services.programareadivisionservice import programareadivisionservice -from request_api.services.recordservice import recordservice import json import request_api import requests @@ -186,24 +184,6 @@ def get(bcgovcode): return jsondata , 200 except BusinessException: return "Error happened while accessing divisions" , 500 - -@cors_preflight('GET,OPTIONS') -@API.route('/foiflow/divisions') -class FOIFlowDivisions(Resource): - """Retrieves all active divisions. - """ - @staticmethod - @TRACER.trace() - @cross_origin(origins=allowedorigins()) - @auth.require - @auth.ismemberofgroups(getrequiredmemberships()) - def get(): - try: - division_data = programareadivisionservice().getallprogramareadivisions() - json_response= json.dumps(division_data) - return json_response, 200 - except BusinessException: - return "Error happened while accessing divisions", 500 @cors_preflight('GET,OPTIONS') @API.route('/foiflow/closereasons') diff --git a/request-management-api/tests/restapi/test_foimasterdata_api.py b/request-management-api/tests/restapi/test_foimasterdata_api.py index 7f5a1ef05..93d49c533 100644 --- a/request-management-api/tests/restapi/test_foimasterdata_api.py +++ b/request-management-api/tests/restapi/test_foimasterdata_api.py @@ -55,8 +55,4 @@ def test_post_fois3storagerequests(app, client): def test_get_extensionreasons(app, client): response = client.get('/api/foiflow/extensionreasons', headers=factory_user_auth_header(app, client), content_type='application/json') - assert response.status_code == 200 - -def test_get_all_divisions(app, client): - response = client.get('/api/foiflow/divisions', headers=factory_user_auth_header(app, client), content_type='application/json') - assert response.status_code == 200 \ No newline at end of file + assert response.status_code == 200 \ No newline at end of file From f99ae1d723e443dbbfd49d6f81f3030027654c77 Mon Sep 17 00:00:00 2001 From: Richard Qi Date: Tue, 12 Sep 2023 16:13:07 -0700 Subject: [PATCH 143/234] api integration --- .../src/actions/FOI/foiActionConstants.js | 2 + .../src/actions/FOI/foiRequestActions.js | 12 ++ .../src/apiManager/endpoints/index.js | 2 + .../services/FOI/foiMasterDataServices.js | 53 ++++++ .../components/FOI/FOIRequest/FOIRequest.js | 5 + .../MinistryReview/MinistryReview.js | 9 +- .../MinistryReview/RequestTracking.js | 10 +- .../Attachments/AttachmentModal.js | 159 +++++++++++------- ...canning.js => FileUploadForMCFPersonal.js} | 31 ++-- .../FileUpload/FileUploadForMSDPersonal.js | 24 +-- .../src/modules/FOI/foiRequestsReducer.js | 4 + 11 files changed, 215 insertions(+), 96 deletions(-) rename forms-flow-web/src/components/FOI/customComponents/FileUpload/{FileUploadForScanning.js => FileUploadForMCFPersonal.js} (94%) diff --git a/forms-flow-web/src/actions/FOI/foiActionConstants.js b/forms-flow-web/src/actions/FOI/foiActionConstants.js index 5b7e2a4eb..7f26ec933 100644 --- a/forms-flow-web/src/actions/FOI/foiActionConstants.js +++ b/forms-flow-web/src/actions/FOI/foiActionConstants.js @@ -40,6 +40,8 @@ const FOI_ACTION_CONSTANTS = { FOI_REQUEST_DESCRIPTION_HISTORY: "FOI_REQUEST_DESCRIPTION_HISTORY", FOI_MINISTRY_REQUESTSLIST: "FOI_MINISTRY_REQUESTSLIST", FOI_MINISTRY_DIVISIONALSTAGES: "FOI_MINISTRY_DIVISIONALSTAGES", + FOI_PERSONAL_DIVISIONS_SECTIONS: "FOI_PERSONAL_DIVISIONS_SECTIONS", + FOI_PERSONAL_SECTIONS: "FOI_PERSONAL_SECTIONS", FOI_WATCHER_LIST: "FOI_WATCHER_LIST", diff --git a/forms-flow-web/src/actions/FOI/foiRequestActions.js b/forms-flow-web/src/actions/FOI/foiRequestActions.js index e57d07d58..205ec8bbd 100644 --- a/forms-flow-web/src/actions/FOI/foiRequestActions.js +++ b/forms-flow-web/src/actions/FOI/foiRequestActions.js @@ -252,6 +252,18 @@ export const setFOIMinistryDivisionalStages = (data) => dispatch => { payload:data }) } +export const setFOIPersonalDivisionsAndSections = (data) => dispatch => { + dispatch({ + type:FOI_ACTION_CONSTANTS.FOI_PERSONAL_DIVISIONS_SECTIONS, + payload:data + }) +} +export const setFOIPersonalSections = (data) => dispatch => { + dispatch({ + type:FOI_ACTION_CONSTANTS.FOI_PERSONAL_SECTIONS, + payload:data + }) +} export const setFOIWatcherList = (data) => dispatch => { dispatch({ type:FOI_ACTION_CONSTANTS.FOI_WATCHER_LIST, diff --git a/forms-flow-web/src/apiManager/endpoints/index.js b/forms-flow-web/src/apiManager/endpoints/index.js index f16966d9b..b2832f35c 100644 --- a/forms-flow-web/src/apiManager/endpoints/index.js +++ b/forms-flow-web/src/apiManager/endpoints/index.js @@ -26,6 +26,8 @@ const API = { FOI_RAW_REQUEST_DESCRIPTION: `${FOI_BASE_API_URL}/api/foiaudit/rawrequest//description`, FOI_MINISTRY_REQUEST_DESCRIPTION: `${FOI_BASE_API_URL}/api/foiaudit/ministryrequest//description`, FOI_MINISTRY_DIVISIONALSTAGES: `${FOI_BASE_API_URL}/api/foiflow/divisions/`, + FOI_PERSONAL_DIVISIONS_SECTIONS: `${FOI_BASE_API_URL}/api/foiflow/divisions//true/divisionsandsections`, + FOI_PERSONAL_SECTIONS: `${FOI_BASE_API_URL}/api/foiflow/divisions//true/sections`, FOI_POST_RAW_REQUEST_WATCHERS: `${FOI_BASE_API_URL}/api/foiwatcher/rawrequest`, FOI_GET_RAW_REQUEST_WATCHERS: `${FOI_BASE_API_URL}/api/foiwatcher/rawrequest/`, FOI_POST_MINISTRY_REQUEST_WATCHERS: `${FOI_BASE_API_URL}/api/foiwatcher/ministryrequest`, diff --git a/forms-flow-web/src/apiManager/services/FOI/foiMasterDataServices.js b/forms-flow-web/src/apiManager/services/FOI/foiMasterDataServices.js index 7f602ceaa..fab69149f 100644 --- a/forms-flow-web/src/apiManager/services/FOI/foiMasterDataServices.js +++ b/forms-flow-web/src/apiManager/services/FOI/foiMasterDataServices.js @@ -16,6 +16,8 @@ import { setFOIDeliveryModeList, setFOIReceivedModeList, setFOIMinistryDivisionalStages, + setFOIPersonalDivisionsAndSections, + setFOIPersonalSections, setClosingReasons, setFOISubjectCodeList, setCommentTagListLoader, @@ -361,6 +363,57 @@ import { }; }; + export const fetchFOIPersonalDivisionsAndSections = (bcgovcode) => { + switch(bcgovcode) { + case "MCF": + const apiUrlMCF = replaceUrl(API.FOI_PERSONAL_SECTIONS, "", bcgovcode); + return (dispatch) => { + httpGETRequest(apiUrlMCF, {}, UserService.getToken()) + .then((res) => { + if (res.data) { + const foiPersonalSections = res.data; + dispatch(setFOIPersonalSections({})); + dispatch(setFOIPersonalSections(foiPersonalSections)); + dispatch(setFOILoader(false)); + } else { + console.log(`Error while fetching ministry(${bcgovcode}) divisional stage master data`, res); + dispatch(serviceActionError(res)); + dispatch(setFOILoader(false)); + } + }) + .catch((error) => { + console.log(`Error while fetching ministry(${bcgovcode}) divisional stage master data`, error); + dispatch(serviceActionError(error)); + dispatch(setFOILoader(false)); + }); + }; + case "MSD": + const apiUrlMSD = replaceUrl(API.FOI_PERSONAL_DIVISIONS_SECTIONS, "", bcgovcode); + return (dispatch) => { + httpGETRequest(apiUrlMSD, {}, UserService.getToken()) + .then((res) => { + if (res.data) { + const foiPersonalDivisionsAndSections = res.data; + dispatch(setFOIPersonalDivisionsAndSections({})); + dispatch(setFOIPersonalDivisionsAndSections(foiPersonalDivisionsAndSections)); + dispatch(setFOILoader(false)); + } else { + console.log(`Error while fetching ministry(${bcgovcode}) divisional stage master data`, res); + dispatch(serviceActionError(res)); + dispatch(setFOILoader(false)); + } + }) + .catch((error) => { + console.log(`Error while fetching ministry(${bcgovcode}) divisional stage master data`, error); + dispatch(serviceActionError(error)); + dispatch(setFOILoader(false)); + }); + }; + default: + break; + } + }; + export const fetchFOISubjectCodeList = () => { const firstSubjectCode = { "subjectcodeid": 0, "name": "Select Subject Code (if required)" }; return (dispatch) => { diff --git a/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js b/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js index 4258e4bf5..065d64417 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js @@ -23,6 +23,7 @@ import { fetchClosingReasonList, fetchFOIMinistryAssignedToList, fetchFOISubjectCodeList, + fetchFOIPersonalDivisionsAndSections, } from "../../../apiManager/services/FOI/foiMasterDataServices"; import { fetchFOIRequestDetailsWrapper, @@ -283,6 +284,10 @@ const FOIRequest = React.memo(({ userDetail }) => { axisBannerCheck(); setIsIAORestricted(isRequestRestricted(requestDetails,ministryId)); } + + if(MinistryNeedsScanning.includes(bcgovcode.replaceAll('"', '')) && requestDetails.requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL) { + dispatch(fetchFOIPersonalDivisionsAndSections(bcgovcode.replaceAll('"', ''))); + } }, [requestDetails]); diff --git a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js index ba79f16e5..98a0df32a 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js @@ -15,7 +15,10 @@ import { fetchFOIRequestDescriptionList } from "../../../../apiManager/services/FOI/foiRequestServices"; -import { fetchFOIMinistryAssignedToList } from "../../../../apiManager/services/FOI/foiMasterDataServices"; +import { + fetchFOIMinistryAssignedToList, + fetchFOIPersonalDivisionsAndSections +} from "../../../../apiManager/services/FOI/foiMasterDataServices"; import { fetchFOIRequestAttachmentsList, @@ -235,6 +238,10 @@ const MinistryReview = React.memo(({ userDetail }) => { settabStatus(requestDetails.currentState); setIsMinistryRestricted(requestDetails.ministryrestricteddetails?.isrestricted); } + + if(MinistryNeedsScanning.includes(bcgovcode.replaceAll('"', '')) && requestDetails.requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL) { + dispatch(fetchFOIPersonalDivisionsAndSections(bcgovcode.replaceAll('"', ''))); + } }, [requestDetails, unSavedRequest]); useEffect(() => { diff --git a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/RequestTracking.js b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/RequestTracking.js index 44f66cd96..bc0898d3a 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/RequestTracking.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/RequestTracking.js @@ -3,20 +3,16 @@ import Card from '@material-ui/core/Card'; import CardContent from '@material-ui/core/CardContent'; import DivisionalStages from './Divisions/DivisionalStages'; import { useDispatch, useSelector } from "react-redux"; -import { - fetchFOIMinistryDivisionalStages - } from "../../../../apiManager/services/FOI/foiMasterDataServices"; +import { fetchFOIMinistryDivisionalStages } from "../../../../apiManager/services/FOI/foiMasterDataServices"; const RequestTracking = React.memo(({pubmindivstagestomain,existingDivStages,ministrycode,createMinistrySaveRequestObject,requestStartDate, setHasReceivedDate}) => { const dispatch = useDispatch(); useEffect(() => { if(ministrycode) { - - dispatch(fetchFOIMinistryDivisionalStages(ministrycode)); + dispatch(fetchFOIMinistryDivisionalStages(ministrycode)); } - - },[ministrycode,dispatch]) + },[ministrycode,dispatch]) let divisionalstages = useSelector(state=> state.foiRequests.foiMinistryDivisionalStages); diff --git a/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js b/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js index a9902f6ac..6c26ed266 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js +++ b/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js @@ -11,7 +11,7 @@ import TextField from '@material-ui/core/TextField'; import '../ConfirmationModal/confirmationmodal.scss'; import './attachmentmodal.scss'; import FileUpload from '../FileUpload'; -import FileUploadForScanning from '../FileUpload/FileUploadForScanning'; +import FileUploadForMCFPersonal from '../FileUpload/FileUploadForMCFPersonal'; import FileUploadForMSDPersonal from '../FileUpload/FileUploadForMSDPersonal'; import { makeStyles } from '@material-ui/core/styles'; import { MimeTypeList, MaxFileSizeInMB } from "../../../../constants/FOI/enum"; @@ -190,7 +190,14 @@ export default function AttachmentModal({ if (modalFor === 'replace' || modalFor === "replaceattachment") { fileStatusTransition = attachment?.category; } else if (uploadFor === "record") { - fileStatusTransition = divisions.find(division => division.divisionid === tagValue).divisionname; + console.log("divisions", divisions); + if(bcgovcode == "MCF" && requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL) { + fileStatusTransition = MCFSections?.sections?.find(division => division.divisionid === tagValue).name; + } else if(bcgovcode == "MSD" && requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL) { + fileStatusTransition = MSDSections?.sections?.find(division => division.divisionid === tagValue).name; + } else { + fileStatusTransition = divisions.find(division => division.divisionid === tagValue).divisionname; + } } else { fileStatusTransition = tagValue } @@ -384,6 +391,9 @@ export default function AttachmentModal({ {"name": 100, "display": "100100100"}, ]; + const MCFSections = useSelector((state) => state.foiRequests.foiPersonalSections); + const MSDSections = useSelector((state) => state.foiRequests.foiPersonalDivisionsAndSections); + return (
    { (['replaceattachment','replace','add'].includes(modalFor)) ? - // - // - + (requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL) ? + (bcgovcode == "MCF") ? + + : + (bcgovcode == "MSD") ? + + : + + : + : } diff --git a/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForScanning.js b/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMCFPersonal.js similarity index 94% rename from forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForScanning.js rename to forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMCFPersonal.js index ce4e2e8f0..ce956dc37 100644 --- a/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForScanning.js +++ b/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMCFPersonal.js @@ -21,7 +21,7 @@ import InputBase from "@mui/material/InputBase"; import IconButton from "@material-ui/core/IconButton"; import _ from 'lodash'; -const FileUploadForScanning = ({ +const FileUploadForMCFPersonal = ({ multipleFiles, mimeTypes, maxFileSize, @@ -51,6 +51,9 @@ const FileUploadForScanning = ({ const [additionalTagList, setAdditionalTagList] = useState([]); const [showAdditionalTags, setShowAdditionalTags] = useState(false); + console.log("tagList", tagList); + console.log("otherTagList", otherTagList); + const handleUploadBtnClick = (e) => { e.stopPropagation(); fileInputField.current.click(); @@ -200,9 +203,9 @@ const FileUploadForScanning = ({ let newSectionArray = []; if(_keyword || _selectedSectionValue) { _sectionArray.map((section) => { - if(_keyword && section.display.toLowerCase().includes(_keyword.toLowerCase())) { + if(_keyword && section.name.toLowerCase().includes(_keyword.toLowerCase())) { newSectionArray.push(section); - } else if(section.name === _selectedSectionValue) { + } else if(section.divisionid === _selectedSectionValue) { newSectionArray.unshift(section); } }); @@ -229,14 +232,14 @@ const FileUploadForScanning = ({
    {tagList.map(tag => {handleTagChange(tag.name)}} - clicked={tagValue == tag.name} + onClick={()=>{handleTagChange(tag.divisionid)}} + clicked={tagValue == tag.divisionid} /> )}
    @@ -325,14 +328,14 @@ const FileUploadForScanning = ({ > {additionalTagList.map(tag => {handleTagChange(tag.name)}} - clicked={tagValue == tag.name} + onClick={()=>{handleTagChange(tag.divisionid)}} + clicked={tagValue == tag.divisionid} /> )} )} @@ -401,4 +404,4 @@ const FileUploadForScanning = ({ ); }; -export default FileUploadForScanning; \ No newline at end of file +export default FileUploadForMCFPersonal; \ No newline at end of file diff --git a/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMSDPersonal.js b/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMSDPersonal.js index 21164b77d..d7c5dd673 100644 --- a/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMSDPersonal.js +++ b/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMSDPersonal.js @@ -202,9 +202,9 @@ const FileUploadForMSDPersonal = ({ let newSectionArray = []; if(_keyword || _selectedSectionValue) { _sectionArray.map((section) => { - if(_keyword && section.display.toLowerCase().includes(_keyword.toLowerCase())) { + if(_keyword && section.name.toLowerCase().includes(_keyword.toLowerCase())) { newSectionArray.push(section); - } else if(section.name === _selectedSectionValue) { + } else if(section.divisionid === _selectedSectionValue) { newSectionArray.unshift(section); } }); @@ -242,14 +242,14 @@ const FileUploadForMSDPersonal = ({
    {tagList.map(tag => {handleParentTagChange(tag.name)}} - clicked={tag.name == tagValue || (showChildTags && tag.name === parentTagValue)} + onClick={()=>{handleParentTagChange(tag.divisionid)}} + clicked={tag.divisionid == tagValue || (showChildTags && tag.divisionid === parentTagValue)} /> )}
    @@ -339,14 +339,14 @@ const FileUploadForMSDPersonal = ({ > {additionalTagList.map(tag => {handleTagChange(tag.name)}} - clicked={tagValue == tag.name} + onClick={()=>{handleTagChange(tag.divisionid)}} + clicked={tagValue == tag.divisionid} /> )} )} diff --git a/forms-flow-web/src/modules/FOI/foiRequestsReducer.js b/forms-flow-web/src/modules/FOI/foiRequestsReducer.js index 24702d299..7317d7a17 100644 --- a/forms-flow-web/src/modules/FOI/foiRequestsReducer.js +++ b/forms-flow-web/src/modules/FOI/foiRequestsReducer.js @@ -186,6 +186,10 @@ const foiRequests = (state = initialState, action) => { return { ...state, foiRequestDescriptionHistoryList: action.payload }; case FOI_ACTION_CONSTANTS.FOI_MINISTRY_DIVISIONALSTAGES: return { ...state, foiMinistryDivisionalStages: action.payload }; + case FOI_ACTION_CONSTANTS.FOI_PERSONAL_DIVISIONS_SECTIONS: + return { ...state, foiPersonalDivisionsAndSections: action.payload }; + case FOI_ACTION_CONSTANTS.FOI_PERSONAL_SECTIONS: + return { ...state, foiPersonalSections: action.payload }; case FOI_ACTION_CONSTANTS.FOI_WATCHER_LIST: return { ...state, foiWatcherList: action.payload }; case FOI_ACTION_CONSTANTS.CLOSING_REASONS: From 939772f61ab8709a97e6d933af661aac7094034e Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Tue, 12 Sep 2023 17:14:25 -0700 Subject: [PATCH 144/234] #4292 MSD personal Divsions, Sections --- .../07a6d930b276_.MSD personal sections.py | 29 +++++++++++++++++++ .../bdef238edb41_MSD Personal Divisions.py | 29 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 request-management-api/migrations/versions/07a6d930b276_.MSD personal sections.py create mode 100644 request-management-api/migrations/versions/bdef238edb41_MSD Personal Divisions.py diff --git a/request-management-api/migrations/versions/07a6d930b276_.MSD personal sections.py b/request-management-api/migrations/versions/07a6d930b276_.MSD personal sections.py new file mode 100644 index 000000000..54e51c604 --- /dev/null +++ b/request-management-api/migrations/versions/07a6d930b276_.MSD personal sections.py @@ -0,0 +1,29 @@ +"""MSD Personal Sections for SDD + +Revision ID: 07a6d930b276 +Revises: bdef238edb41 +Create Date: 2023-09-12 16:49:26.208024 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '07a6d930b276' +down_revision = 'bdef238edb41' +branch_labels = None +depends_on = None + + +def upgrade(): + sections = ['GA KEY PLAYER','GA KEY PLAYER AHR','GA SPOUSE','GA SPOUSE AHR','GA DEPENDENT','GA DEPENDENT AHR','FM1 KEY PLAYER','FM1 KEY PLAYER AHR','FM2 RESPONDENT','FM2 RESPONDENT AHR','TPA','FM3','FM3 AHR','FM4','FM4 AHR','FM5','FM5 AHR','GA 2 DEPENDENT','GA 2 DEPENDENT AHR','GA 2 SPOUSE','GA 2 SPOUSE AHR','GA 3 SPOUSE','GA 3 SPOUSE AHR','GA KEY PLAYER PHYSICAL','GA SPOUSE PHYSICAL','GAZ SPOUSE PHYSICAL','GA3 SPOUSE PHYSICAL','GA DEPENDENT PHYSICAL','GAZ DEPENDENT PHYSICAL','FM1 KEY PLAYER PHYSICAL','FM2 RESPONDENT PHYSICAL','FM3 PHYSICAL','FM4 PHYSICAL','FMS PHYSICAL'] + sortorder = 1 + + for section in sections: + op.execute('INSERT INTO public."ProgramAreaDivisions"(programareaid, name, isactive, created_at, createdby, sortorder, issection, specifictopersonalrequests,parentid)\ + VALUES ((SELECT programareaid FROM public."ProgramAreas" WHERE iaocode =\'MSD\'), \''+section+'\', TRUE, NOW(), \'system\', '+ str(sortorder) +',TRUE, TRUE, (SELECT divisionid FROM public."ProgramAreaDivisions" WHERE name =\'SDD Document Tracking\' and isactive=true and programareaid in (SELECT programareaid FROM public."ProgramAreas" WHERE iaocode =\'MSD\' LIMIT 1)) );') + sortorder = sortorder+1 + +def downgrade(): + op.execute('DELETE FROM public."ProgramAreaDivisions" WHERE programareaid in (SELECT programareaid FROM public."ProgramAreas" WHERE iaocode =\'MSD\') AND issection=TRUE and specifictopersonalrequests=TRUE and parentid in (SELECT divisionid FROM public."ProgramAreaDivisions" WHERE name =\'SDD Document Tracking\' and isactive=true and programareaid in (SELECT programareaid FROM public."ProgramAreas" WHERE iaocode =\'MSD\' LIMIT 1))') diff --git a/request-management-api/migrations/versions/bdef238edb41_MSD Personal Divisions.py b/request-management-api/migrations/versions/bdef238edb41_MSD Personal Divisions.py new file mode 100644 index 000000000..a2f496f76 --- /dev/null +++ b/request-management-api/migrations/versions/bdef238edb41_MSD Personal Divisions.py @@ -0,0 +1,29 @@ +"""MSD Personals SDD DOcument Division + +Revision ID: bdef238edb41 +Revises: 1e9a62e22f88 +Create Date: 2023-09-12 15:23:54.743381 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'bdef238edb41' +down_revision = '1e9a62e22f88' +branch_labels = None +depends_on = None + + +def upgrade(): + divisons = ["SDD Document Tracking"] + sortorder = 1 + + for division in divisons: + op.execute('INSERT INTO public."ProgramAreaDivisions"(programareaid, name, isactive, created_at, createdby, sortorder, issection, specifictopersonalrequests)\ + VALUES ((SELECT programareaid FROM public."ProgramAreas" WHERE iaocode =\'MSD\'), \''+division+'\', TRUE, NOW(), \'system\', '+ str(sortorder) +',FALSE, TRUE );') + sortorder = sortorder+1 + +def downgrade(): + op.execute('DELETE FROM public."ProgramAreaDivisions" WHERE programareaid in (SELECT programareaid FROM public."ProgramAreas" WHERE iaocode =\'MSD\') AND issection=FALSE and specifictopersonalrequests=TRUE') \ No newline at end of file From 0e2523ce77bb9484b51806c63ea5d65c00b590a0 Mon Sep 17 00:00:00 2001 From: Richard Qi Date: Tue, 12 Sep 2023 23:19:02 -0700 Subject: [PATCH 145/234] show sections for record page for MSD/MCF personal --- .../request_api/services/records/recordservicegetter.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/request-management-api/request_api/services/records/recordservicegetter.py b/request-management-api/request_api/services/records/recordservicegetter.py index 9f335796f..df121e6f3 100644 --- a/request-management-api/request_api/services/records/recordservicegetter.py +++ b/request-management-api/request_api/services/records/recordservicegetter.py @@ -19,7 +19,13 @@ def fetch(self, requestid, ministryrequestid): result = {'dedupedfiles': 0, 'convertedfiles': 0, 'removedfiles': 0} try: _metadata = FOIMinistryRequest.getmetadata(ministryrequestid) - divisions = ProgramAreaDivision.getprogramareadivisions(_metadata["programareaid"]) + if _metadata["requesttype"] == "personal" and _metadata["programareaid"] == 4: + divisions = ProgramAreaDivision.getpersonalrequestsprogramareasections(_metadata["programareaid"]) + elif _metadata["requesttype"] == "personal" and _metadata["programareaid"] == 18: + divisions = ProgramAreaDivision.getpersonalrequestsdivisionsandsections(_metadata["programareaid"]) + else: + divisions = ProgramAreaDivision.getprogramareadivisions(_metadata["programareaid"]) + uploadedrecords = FOIRequestRecord.fetch(requestid, ministryrequestid) batchids = [] resultrecords = [] From 10b06cb4112898353d149b4cca77590d1e666cb1 Mon Sep 17 00:00:00 2001 From: Richard Qi Date: Wed, 13 Sep 2023 09:00:10 -0700 Subject: [PATCH 146/234] simplify divisions/section retriving for records --- .../request_api/models/ProgramAreaDivisions.py | 6 ++++++ .../request_api/services/records/recordservicegetter.py | 7 +------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/request-management-api/request_api/models/ProgramAreaDivisions.py b/request-management-api/request_api/models/ProgramAreaDivisions.py index 9305653c5..19d752ed8 100644 --- a/request-management-api/request_api/models/ProgramAreaDivisions.py +++ b/request-management-api/request_api/models/ProgramAreaDivisions.py @@ -32,6 +32,12 @@ def getprogramareadivisions(cls,programareaid): division_schema = ProgramAreaDivisionSchema(many=True) query = db.session.query(ProgramAreaDivision).filter(ProgramAreaDivision.programareaid == programareaid, ProgramAreaDivision.isactive == True, ProgramAreaDivision.issection == False,or_(ProgramAreaDivision.specifictopersonalrequests == None,ProgramAreaDivision.specifictopersonalrequests == False)) return division_schema.dump(query) + + @classmethod + def getallprogramareatags(cls,programareaid): + division_schema = ProgramAreaDivisionSchema(many=True) + query = db.session.query(ProgramAreaDivision).filter(ProgramAreaDivision.programareaid == programareaid, ProgramAreaDivision.isactive == True) + return division_schema.dump(query) @classmethod def getpersonalspecificprogramareadivisions(cls,programareaid): diff --git a/request-management-api/request_api/services/records/recordservicegetter.py b/request-management-api/request_api/services/records/recordservicegetter.py index df121e6f3..01528775b 100644 --- a/request-management-api/request_api/services/records/recordservicegetter.py +++ b/request-management-api/request_api/services/records/recordservicegetter.py @@ -19,12 +19,7 @@ def fetch(self, requestid, ministryrequestid): result = {'dedupedfiles': 0, 'convertedfiles': 0, 'removedfiles': 0} try: _metadata = FOIMinistryRequest.getmetadata(ministryrequestid) - if _metadata["requesttype"] == "personal" and _metadata["programareaid"] == 4: - divisions = ProgramAreaDivision.getpersonalrequestsprogramareasections(_metadata["programareaid"]) - elif _metadata["requesttype"] == "personal" and _metadata["programareaid"] == 18: - divisions = ProgramAreaDivision.getpersonalrequestsdivisionsandsections(_metadata["programareaid"]) - else: - divisions = ProgramAreaDivision.getprogramareadivisions(_metadata["programareaid"]) + divisions = ProgramAreaDivision.getallprogramareatags(_metadata["programareaid"]) uploadedrecords = FOIRequestRecord.fetch(requestid, ministryrequestid) batchids = [] From 4d438065e39fb7d6a3370da4308689a910d23504 Mon Sep 17 00:00:00 2001 From: Richard Qi Date: Wed, 13 Sep 2023 10:03:12 -0700 Subject: [PATCH 147/234] bug fix - records always show loading screen --- .../components/FOI/FOIRequest/MinistryReview/MinistryReview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js index 98a0df32a..378bb5e13 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js @@ -820,7 +820,7 @@ const MinistryReview = React.memo(({ userDetail }) => { [classes.hidden]: !tabLinksStatuses.Records.display, })} > - {!isAttachmentListLoading && originalDivisions?.length > 0 ? ( + {!isAttachmentListLoading && (originalDivisions?.length > 0 || (MinistryNeedsScanning.includes(bcgovcode.replaceAll('"', '')) && requestDetails?.requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL)) ? ( <> {url.indexOf("records") > -1 ? ( From 1e006308a991b5357b6e736f5ede896fa54594d3 Mon Sep 17 00:00:00 2001 From: Richard Qi Date: Wed, 13 Sep 2023 11:11:48 -0700 Subject: [PATCH 148/234] MSD personal --- .../FOI/customComponents/Attachments/AttachmentModal.js | 1 + .../customComponents/FileUpload/FileUploadForMCFPersonal.js | 3 --- .../customComponents/FileUpload/FileUploadForMSDPersonal.js | 3 +++ 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js b/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js index 358b91480..ee7d06e53 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js +++ b/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js @@ -407,6 +407,7 @@ export default function AttachmentModal({ const MCFSections = useSelector((state) => state.foiRequests.foiPersonalSections); const MSDSections = useSelector((state) => state.foiRequests.foiPersonalDivisionsAndSections); + console.log("MSDSections", MSDSections); return (
    diff --git a/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMCFPersonal.js b/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMCFPersonal.js index ce956dc37..433122eda 100644 --- a/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMCFPersonal.js +++ b/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMCFPersonal.js @@ -51,9 +51,6 @@ const FileUploadForMCFPersonal = ({ const [additionalTagList, setAdditionalTagList] = useState([]); const [showAdditionalTags, setShowAdditionalTags] = useState(false); - console.log("tagList", tagList); - console.log("otherTagList", otherTagList); - const handleUploadBtnClick = (e) => { e.stopPropagation(); fileInputField.current.click(); diff --git a/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMSDPersonal.js b/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMSDPersonal.js index d7c5dd673..df0d080c2 100644 --- a/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMSDPersonal.js +++ b/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMSDPersonal.js @@ -53,6 +53,9 @@ const FileUploadForMSDPersonal = ({ const [showChildTags, setShowChildTags] = useState(false); const [showAdditionalTags, setShowAdditionalTags] = useState(false); + console.log("tagList", tagList); + console.log("subTagList", subTagList); + const handleUploadBtnClick = (e) => { e.stopPropagation(); fileInputField.current.click(); From 1f4cd3eaffb3c1c1125f553ba83460fb89593989 Mon Sep 17 00:00:00 2001 From: divyav-aot Date: Wed, 13 Sep 2023 15:09:14 -0400 Subject: [PATCH 149/234] removed log --- .../src/components/FOI/customComponents/Records/util.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/util.js b/forms-flow-web/src/components/FOI/customComponents/Records/util.js index 82773a95f..e6ba9615f 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/util.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/util.js @@ -18,8 +18,6 @@ export const getPDFFilePath = (item) => { let pdffilepath = item.s3uripath; let pdffilename = item.filename; - console.log(`item = ${JSON.stringify(item)}`); - if ( (item.isredactionready && item.isconverted) || (item.attributes?.isattachment && From db1e5f8794cdfd6581626b43b1a934ad16bfe9f3 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Wed, 13 Sep 2023 17:19:43 -0700 Subject: [PATCH 150/234] Fixed sorting bug in get division stages. there was a bug if programareadivisions returned if some divisions had a sort order and others sort order was none, this lead to a type error in the sort function. --- .../request_api/services/divisionstageservice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/request-management-api/request_api/services/divisionstageservice.py b/request-management-api/request_api/services/divisionstageservice.py index 8ddfa5dec..6b6176c99 100644 --- a/request-management-api/request_api/services/divisionstageservice.py +++ b/request-management-api/request_api/services/divisionstageservice.py @@ -9,7 +9,7 @@ def getdivisionandstages(self, bcgovcode): divisionstages = [] programarea = ProgramArea.getprogramarea(bcgovcode) divisions = ProgramAreaDivision.getprogramareadivisions(programarea['programareaid']) - divisions.sort(key=lambda item: (item['sortorder'], item['name'])) + divisions.sort(key=lambda item: (item["sortorder"] is None, item["name"])) for division in divisions: divisionstages.append({"divisionid": division['divisionid'], "name": self.escapestr(division['name'])}) return {"divisions": divisionstages, "stages": self.getstages()} From 412563270ef38f536ce251e6f7ee868e7f71b8b2 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Wed, 13 Sep 2023 17:51:51 -0700 Subject: [PATCH 151/234] Added additional functionality so that if sortorder is equal to 0 or none sort by name. Bug fixed --- .../request_api/services/divisionstageservice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/request-management-api/request_api/services/divisionstageservice.py b/request-management-api/request_api/services/divisionstageservice.py index 6b6176c99..bb9caaad9 100644 --- a/request-management-api/request_api/services/divisionstageservice.py +++ b/request-management-api/request_api/services/divisionstageservice.py @@ -9,7 +9,7 @@ def getdivisionandstages(self, bcgovcode): divisionstages = [] programarea = ProgramArea.getprogramarea(bcgovcode) divisions = ProgramAreaDivision.getprogramareadivisions(programarea['programareaid']) - divisions.sort(key=lambda item: (item["sortorder"] is None, item["name"])) + divisions.sort(key=lambda item: (item["sortorder"] in (None, 0), item["name"])) for division in divisions: divisionstages.append({"divisionid": division['divisionid'], "name": self.escapestr(division['name'])}) return {"divisions": divisionstages, "stages": self.getstages()} From 1e0512d058a25a2fe9caccdb251bb40b314fad29 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Fri, 15 Sep 2023 12:27:00 -0700 Subject: [PATCH 152/234] While testing in dev sorting issue was encoureted. Adjusted logic so that if sort order exists it takes prcedence over name --- .../request_api/services/divisionstageservice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/request-management-api/request_api/services/divisionstageservice.py b/request-management-api/request_api/services/divisionstageservice.py index bb9caaad9..ead4c02df 100644 --- a/request-management-api/request_api/services/divisionstageservice.py +++ b/request-management-api/request_api/services/divisionstageservice.py @@ -9,7 +9,7 @@ def getdivisionandstages(self, bcgovcode): divisionstages = [] programarea = ProgramArea.getprogramarea(bcgovcode) divisions = ProgramAreaDivision.getprogramareadivisions(programarea['programareaid']) - divisions.sort(key=lambda item: (item["sortorder"] in (None, 0), item["name"])) + divisions.sort(key=lambda item: (item["sortorder"] if item["sortorder"] not in (None,0) else float('inf'), item["name"])) for division in divisions: divisionstages.append({"divisionid": division['divisionid'], "name": self.escapestr(division['name'])}) return {"divisions": divisionstages, "stages": self.getstages()} From a4f77858364f4c1dc3bc4af7319a7ddbd055e660 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Fri, 15 Sep 2023 16:43:27 -0700 Subject: [PATCH 153/234] tooltip created cause the disable menu item to be clickable leading to some bugs. Added onClick prevent default to stop tooltip to be clickable (and keep on hover properties --- .../src/components/FOI/customComponents/Records/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/index.js b/forms-flow-web/src/components/FOI/customComponents/Records/index.js index 9e4a82130..e88732b90 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/index.js @@ -1444,7 +1444,7 @@ export const RecordsLog = ({ if (item.id === 1 && item.disabled) { return ( File conversion and deduplication in progress
    } key={item.id}> -
    +
    event.preventDefault()}> Date: Mon, 18 Sep 2023 10:53:33 -0700 Subject: [PATCH 154/234] removed tooltip as ticket did not require it as mandatory. Icon is shown if download for harms is pending file dedupe and conversion. Refactored code for readability. --- .../FOI/customComponents/Records/index.js | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/index.js b/forms-flow-web/src/components/FOI/customComponents/Records/index.js index e88732b90..07306c026 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/index.js @@ -1441,24 +1441,6 @@ export const RecordsLog = ({ fullWidth > {recordsDownloadList.map((item, index) => { - if (item.id === 1 && item.disabled) { - return ( - File conversion and deduplication in progress
    } key={item.id}> -
    event.preventDefault()}> - - - {item.label} - -
    - - ) - } if (item.id != 0) { return ( - {!item.disabled && + {!item.disabled ? (isDownloadReady ? ( - ) : null)} + ) : null) : (item.id === 1 && ())} {item.label} ); From 8544b117afcf8fd91ad0e1242c9d37aeb4d98274 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Mon, 18 Sep 2023 14:00:57 -0700 Subject: [PATCH 155/234] Small changes to PR 4459. When divisions are updated in foi admin, if sort order is 0 it will be treated as None as 0 holds not impact on sortorder front end and logic. Adjusted sort order logic as well to account for change --- .../request_api/models/ProgramAreaDivisions.py | 4 +++- .../request_api/services/divisionstageservice.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/request-management-api/request_api/models/ProgramAreaDivisions.py b/request-management-api/request_api/models/ProgramAreaDivisions.py index 2952d2a93..11bcbd637 100644 --- a/request-management-api/request_api/models/ProgramAreaDivisions.py +++ b/request-management-api/request_api/models/ProgramAreaDivisions.py @@ -53,9 +53,11 @@ def disableprogramareadivision(cls, divisionid, userid): def updateprogramareadivision(cls, divisionid, programareadivision, userid): dbquery = db.session.query(ProgramAreaDivision) division = dbquery.filter_by(divisionid=divisionid, isactive=True) + # Below code ensures that sort order DB column does not contain 0 which has no impact on the sortorder + sortorder = programareadivision["sortorder"] if programareadivision["sortorder"] != 0 else None if(division.count() > 0) : division.update({ProgramAreaDivision.programareaid:programareadivision["programareaid"], ProgramAreaDivision.name:programareadivision["name"], - ProgramAreaDivision.isactive:True, ProgramAreaDivision.sortorder:programareadivision["sortorder"], + ProgramAreaDivision.isactive:True, ProgramAreaDivision.sortorder:sortorder, ProgramAreaDivision.updatedby:userid, ProgramAreaDivision.updated_at:datetime2.now()}, synchronize_session = False) db.session.commit() return DefaultMethodResult(True,'Division updated successfully',divisionid) diff --git a/request-management-api/request_api/services/divisionstageservice.py b/request-management-api/request_api/services/divisionstageservice.py index ead4c02df..8b7c11b2f 100644 --- a/request-management-api/request_api/services/divisionstageservice.py +++ b/request-management-api/request_api/services/divisionstageservice.py @@ -9,7 +9,7 @@ def getdivisionandstages(self, bcgovcode): divisionstages = [] programarea = ProgramArea.getprogramarea(bcgovcode) divisions = ProgramAreaDivision.getprogramareadivisions(programarea['programareaid']) - divisions.sort(key=lambda item: (item["sortorder"] if item["sortorder"] not in (None,0) else float('inf'), item["name"])) + divisions.sort(key=lambda item: (item["sortorder"] if item["sortorder"] is not None else float('inf'), item["name"])) for division in divisions: divisionstages.append({"divisionid": division['divisionid'], "name": self.escapestr(division['name'])}) return {"divisions": divisionstages, "stages": self.getstages()} From 3d9e2a8504967c0c06eed14061bb891ef58f5537 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Mon, 18 Sep 2023 17:03:17 -0700 Subject: [PATCH 156/234] #4362 MSD CFD request statuses --- .../src/constants/FOI/statusEnum.js | 5 ++- .../aacdbca19a47_MSD_CFD_reqstatuses.py | 41 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 request-management-api/migrations/versions/aacdbca19a47_MSD_CFD_reqstatuses.py diff --git a/forms-flow-web/src/constants/FOI/statusEnum.js b/forms-flow-web/src/constants/FOI/statusEnum.js index ebb741504..434ddf667 100644 --- a/forms-flow-web/src/constants/FOI/statusEnum.js +++ b/forms-flow-web/src/constants/FOI/statusEnum.js @@ -4,6 +4,7 @@ const StateList = Object.freeze({ redirect: [{status: "Redirect", isSelected: false}, {status:"Intake in Progress", isSelected: false}, {status: "Closed", isSelected: false}], open: [{status: "Open", isSelected: false}, {status: "Call For Records", isSelected: false}, {status:"Peer Review", isSelected: false},{status: "Closed", isSelected: false}], callforrecords: [{status: "Call For Records", isSelected: false}, {status: "Open", isSelected: false}, {status: "Closed", isSelected: false}], + callforrecordscfdmsdpersonal: [{status: "Call For Records", isSelected: false}, {status: "Open", isSelected: false}, {status: "Tagging", isSelected: false},{status: "Ready to Scan", isSelected: false}, {status: "Closed", isSelected: false}], feeassessed: [{status: "Fee Estimate", isSelected: false}, {status: "On Hold", isSelected: false}, {status: "Call For Records", isSelected: false}, {status: "Closed", isSelected: false}], feeassessedforpersonal: [{status: "Fee Estimate", isSelected: false}, {status: "Call For Records", isSelected: false}, {status: "Closed", isSelected: false}], onhold: [{status: "On Hold", isSelected: false}, {status: "Call For Records", isSelected: false}, {status: "Closed", isSelected: false}], @@ -54,7 +55,9 @@ const StateEnum = Object.freeze({ harms: {name: "Harms Assessment", id: 13}, response: {name: "Response", id: 14}, archived: {name: "Archived", id: 15}, - peerreview: {name: "Peer Review", id: 16} + peerreview: {name: "Peer Review", id: 16}, + tagging: {name: "Tagging", id: 17}, + readytoscan: {name: "Ready to Scan", id: 18} }); const StateTransitionCategories = Object.freeze({ diff --git a/request-management-api/migrations/versions/aacdbca19a47_MSD_CFD_reqstatuses.py b/request-management-api/migrations/versions/aacdbca19a47_MSD_CFD_reqstatuses.py new file mode 100644 index 000000000..57e2f8f8d --- /dev/null +++ b/request-management-api/migrations/versions/aacdbca19a47_MSD_CFD_reqstatuses.py @@ -0,0 +1,41 @@ +"""MSD CFD Req. status for personal + +Revision ID: aacdbca19a47 +Revises: 07a6d930b276 +Create Date: 2023-09-18 16:40:31.999882 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.sql import column, table +from sqlalchemy.sql.sqltypes import Boolean, String + + +# revision identifiers, used by Alembic. +revision = 'aacdbca19a47' +down_revision = '07a6d930b276' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + requeststatus_table = table('FOIRequestStatuses', + column('name',String), + column('description',String), + column('isactive',Boolean), + ) + op.bulk_insert( + requeststatus_table, + [ + {'name':'Tagging','description':'Tagging','isactive':True}, + {'name':'Ready to Scan','description':'Ready to Scan','isactive':True} + + ] + ) # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.execute('delete from public."FOIRequestStatuses" where name in (\'Tagging\',\'Ready to Scan\');') + # ### end Alembic commands ### \ No newline at end of file From 407937aca2cf8f3a71559b920b3e613c4a3c9e50 Mon Sep 17 00:00:00 2001 From: Richard Qi Date: Tue, 19 Sep 2023 00:18:32 -0700 Subject: [PATCH 157/234] MSD personal --- .../components/FOI/FOIRequest/FOIRequest.js | 4 +- .../MinistryReview/MinistryReview.js | 8 +- .../MinistryReview/RequestTracking.js | 35 ++++-- .../Attachments/AttachmentModal.js | 117 +----------------- .../FileUpload/FileUploadForMSDPersonal.js | 70 ++++++++--- ...b94062ebea9_more_msd_personal_divisions.py | 29 +++++ 6 files changed, 118 insertions(+), 145 deletions(-) create mode 100644 request-management-api/migrations/versions/6b94062ebea9_more_msd_personal_divisions.py diff --git a/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js b/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js index 065d64417..d26248517 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js @@ -230,6 +230,7 @@ const FOIRequest = React.memo(({ userDetail }) => { document.title = requestDetails.axisRequestId || requestDetails.idNumber || headerText; const dispatch = useDispatch(); const [isIAORestricted, setIsIAORestricted] = useState(false); + const [isMCFMSDPersonal, setIsMCFMSDPersonal] = useState(MinistryNeedsScanning.includes(bcgovcode.replaceAll('"', '')) && requestDetails.requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL); useEffect(() => { if (window.location.href.indexOf("comments") > -1) { @@ -287,6 +288,7 @@ const FOIRequest = React.memo(({ userDetail }) => { if(MinistryNeedsScanning.includes(bcgovcode.replaceAll('"', '')) && requestDetails.requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL) { dispatch(fetchFOIPersonalDivisionsAndSections(bcgovcode.replaceAll('"', ''))); + setIsMCFMSDPersonal(true); } }, [requestDetails]); @@ -761,7 +763,7 @@ const FOIRequest = React.memo(({ userDetail }) => { return (requestState !== StateEnum.intakeinprogress.name && requestState !== StateEnum.unopened.name && requestState !== StateEnum.open.name && - (requestDetails?.divisions?.length > 0 || (MinistryNeedsScanning.includes(bcgovcode.replaceAll('"', '')) && requestDetails?.requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL)) && + (requestDetails?.divisions?.length > 0 || isMCFMSDPersonal) && DISABLE_GATHERINGRECORDS_TAB?.toLowerCase() =='false' ); } diff --git a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js index 378bb5e13..c681188d2 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js @@ -219,7 +219,7 @@ const MinistryReview = React.memo(({ userDetail }) => { const [originalDivisions, setOriginalDivisions] = React.useState([]) const [hasReceivedDate, setHasReceivedDate] = React.useState(true); const [isMinistryRestricted, setIsMinistryRestricted] = useState(false); - + const [isMCFMSDPersonal, setIsMCFMSDPersonal] = useState(MinistryNeedsScanning.includes(bcgovcode.replaceAll('"', '')) && requestDetails.requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL); let ministryassignedtousername = "Unassigned"; useEffect(() => { @@ -241,6 +241,7 @@ const MinistryReview = React.memo(({ userDetail }) => { if(MinistryNeedsScanning.includes(bcgovcode.replaceAll('"', '')) && requestDetails.requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL) { dispatch(fetchFOIPersonalDivisionsAndSections(bcgovcode.replaceAll('"', ''))); + setIsMCFMSDPersonal(true); } }, [requestDetails, unSavedRequest]); @@ -524,6 +525,7 @@ const MinistryReview = React.memo(({ userDetail }) => { createMinistrySaveRequestObject={createMinistrySaveRequestObject} requestStartDate = {requestDetails?.requestProcessStart} setHasReceivedDate={setHasReceivedDate} + isMCFMSDPersonal={isMCFMSDPersonal} /> ); @@ -584,7 +586,7 @@ const MinistryReview = React.memo(({ userDetail }) => { ? `(${requestNotes.length})` : ""}
    - {(originalDivisions?.length > 0 || (MinistryNeedsScanning.includes(bcgovcode.replaceAll('"', '')) && requestDetails?.requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL)) && DISABLE_GATHERINGRECORDS_TAB?.toLowerCase() =='false' &&
    0 || isMCFMSDPersonal) && DISABLE_GATHERINGRECORDS_TAB?.toLowerCase() =='false' &&
    { [classes.hidden]: !tabLinksStatuses.Records.display, })} > - {!isAttachmentListLoading && (originalDivisions?.length > 0 || (MinistryNeedsScanning.includes(bcgovcode.replaceAll('"', '')) && requestDetails?.requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL)) ? ( + {!isAttachmentListLoading && (originalDivisions?.length > 0 || isMCFMSDPersonal) ? ( <> {url.indexOf("records") > -1 ? ( diff --git a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/RequestTracking.js b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/RequestTracking.js index bc0898d3a..4fbd7c47e 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/RequestTracking.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/RequestTracking.js @@ -4,7 +4,15 @@ import CardContent from '@material-ui/core/CardContent'; import DivisionalStages from './Divisions/DivisionalStages'; import { useDispatch, useSelector } from "react-redux"; import { fetchFOIMinistryDivisionalStages } from "../../../../apiManager/services/FOI/foiMasterDataServices"; -const RequestTracking = React.memo(({pubmindivstagestomain,existingDivStages,ministrycode,createMinistrySaveRequestObject,requestStartDate, setHasReceivedDate}) => { +const RequestTracking = React.memo(({ + pubmindivstagestomain, + existingDivStages, + ministrycode, + createMinistrySaveRequestObject, + requestStartDate, + setHasReceivedDate, + isMCFMSDPersonal +}) => { const dispatch = useDispatch(); useEffect(() => { @@ -14,12 +22,18 @@ const RequestTracking = React.memo(({pubmindivstagestomain,existingDivStages,min } },[ministrycode,dispatch]) - - let divisionalstages = useSelector(state=> state.foiRequests.foiMinistryDivisionalStages); - - const popselecteddivstages = (selectedMinDivstages) => { - pubmindivstagestomain(selectedMinDivstages) - } + let divisionalstages = useSelector(state=> state.foiRequests.foiMinistryDivisionalStages); + let MSDSections = useSelector((state) => state.foiRequests.foiPersonalDivisionsAndSections); + + if(isMCFMSDPersonal) { + if(ministrycode == "MSD" && MSDSections?.divisions?.length > 0) { + divisionalstages.divisions = MSDSections.divisions; + } + } + + const popselecteddivstages = (selectedMinDivstages) => { + pubmindivstagestomain(selectedMinDivstages) + } return( @@ -31,7 +45,12 @@ const RequestTracking = React.memo(({pubmindivstagestomain,existingDivStages,min
    { - divisionalstages!=undefined && Object.entries(divisionalstages).length >0 && divisionalstages.divisions.length >0 ? : Divisional stages does not exists for this ministry + divisionalstages!=undefined + && Object.entries(divisionalstages).length > 0 + && divisionalstages.divisions + && divisionalstages.divisions.length > 0 + && divisionalstages.stages + && divisionalstages.stages.length > 0 ? : Divisional stages does not exists for this ministry }
    diff --git a/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js b/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js index ee7d06e53..6937787a4 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js +++ b/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js @@ -97,7 +97,6 @@ export default function AttachmentModal({ const [extension, setExtension] = useState(""); const [errorMessage, setErrorMessage] = useState(); const [tagValue, setTagValue] = useState(uploadFor === 'record' ? "" : "general"); - const [parentTagValue, setParentTagValue] = useState(10); const attchmentFileNameList = attachmentsArray.map(_file => _file.filename.toLowerCase()); const totalRecordUploadLimit= TOTAL_RECORDS_UPLOAD_LIMIT ; @@ -297,114 +296,6 @@ export default function AttachmentModal({ } } - - - const tagL = [ - {"name": 1, "display": "111"}, - {"name": 2, "display": "222"}, - {"name": 3, "display": "333"}, - {"name": 4, "display": "444"}, - {"name": 5, "display": "555"}, - {"name": 6, "display": "666"}, - {"name": 7, "display": "777"}, - {"name": 8, "display": "888"}, - {"name": 9, "display": "999"}, - {"name": 10, "display": "101010"}, - {"name": 11, "display": "111111"}, - {"name": 12, "display": "121212"}, - {"name": 13, "display": "131313"}, - {"name": 14, "display": "141414"}, - {"name": 15, "display": "151515"}, - {"name": 16, "display": "161616"}, - {"name": 17, "display": "171717"}, - {"name": 18, "display": "181818"}, - {"name": 19, "display": "191919"}, - {"name": 20, "display": "202020"}, - {"name": 21, "display": "212121"}, - {"name": 22, "display": "222222"}, - {"name": 23, "display": "232323"}, - {"name": 24, "display": "242424"}, - {"name": 25, "display": "252525"}, - {"name": 26, "display": "262626"}, - {"name": 27, "display": "272727"}, - {"name": 28, "display": "282828"}, - {"name": 29, "display": "292929"}, - {"name": 30, "display": "303030"}, - {"name": 31, "display": "313131"}, - {"name": 32, "display": "323232"}, - {"name": 33, "display": "333333"}, - {"name": 34, "display": "343434"}, - {"name": 35, "display": "353535"}, - {"name": 36, "display": "363636"}, - {"name": 37, "display": "373737"}, - {"name": 38, "display": "383838"}, - {"name": 39, "display": "393939"}, - {"name": 40, "display": "404040"}, - ]; - - const otherTagL = [ - {"name": 41, "display": "414141"}, - {"name": 42, "display": "424242"}, - {"name": 43, "display": "434343"}, - {"name": 44, "display": "444444"}, - {"name": 45, "display": "454545"}, - {"name": 46, "display": "464646"}, - {"name": 47, "display": "474747"}, - {"name": 48, "display": "484848"}, - {"name": 49, "display": "494949"}, - {"name": 50, "display": "505050"}, - {"name": 51, "display": "515151"}, - {"name": 52, "display": "525252"}, - {"name": 53, "display": "535353"}, - {"name": 54, "display": "545454"}, - {"name": 55, "display": "555555"}, - {"name": 56, "display": "565656"}, - {"name": 57, "display": "575757"}, - {"name": 58, "display": "585858"}, - {"name": 59, "display": "595959"}, - {"name": 60, "display": "606060"}, - {"name": 61, "display": "616161"}, - {"name": 62, "display": "626262"}, - {"name": 63, "display": "636363"}, - {"name": 64, "display": "646464"}, - {"name": 65, "display": "656565"}, - {"name": 66, "display": "666666"}, - {"name": 67, "display": "676767"}, - {"name": 68, "display": "686868"}, - {"name": 69, "display": "696969"}, - {"name": 70, "display": "707070"}, - {"name": 71, "display": "717171"}, - {"name": 72, "display": "727272"}, - {"name": 73, "display": "737373"}, - {"name": 74, "display": "747474"}, - {"name": 75, "display": "757575"}, - {"name": 76, "display": "767676"}, - {"name": 77, "display": "777777"}, - {"name": 78, "display": "787878"}, - {"name": 79, "display": "797979"}, - {"name": 80, "display": "808080"}, - {"name": 81, "display": "818181"}, - {"name": 82, "display": "828282"}, - {"name": 83, "display": "838383"}, - {"name": 84, "display": "848484"}, - {"name": 85, "display": "858585"}, - {"name": 86, "display": "868686"}, - {"name": 87, "display": "878787"}, - {"name": 88, "display": "888888"}, - {"name": 89, "display": "898989"}, - {"name": 90, "display": "909090"}, - {"name": 91, "display": "919191"}, - {"name": 92, "display": "929292"}, - {"name": 93, "display": "939393"}, - {"name": 94, "display": "949494"}, - {"name": 95, "display": "959595"}, - {"name": 96, "display": "969696"}, - {"name": 97, "display": "979797"}, - {"name": 98, "display": "989898"}, - {"name": 99, "display": "999999"}, - {"name": 100, "display": "100100100"}, - ]; - const MCFSections = useSelector((state) => state.foiRequests.foiPersonalSections); const MSDSections = useSelector((state) => state.foiRequests.foiPersonalDivisionsAndSections); console.log("MSDSections", MSDSections); @@ -478,7 +369,7 @@ export default function AttachmentModal({ totalRecordUploadLimit={totalRecordUploadLimit} /> : - (bcgovcode == "MSD") ? + (bcgovcode == "MSD" && MSDSections?.divisions?.length > 0) ? { e.stopPropagation(); @@ -221,20 +220,30 @@ const FileUploadForMSDPersonal = ({ return newSectionArray; } + let searchResult = -1; + let divs = []; React.useEffect(() => { - setAdditionalTagList(searchSections(subTagList, searchValue, tagValue)); - },[searchValue, subTagList, tagValue]) - - const handleParentTagChange = (_tagValue) => { - setAdditionalTagList([]); - if (_tagValue === parentTagValue) { - setShowChildTags(true); - handleTagChange(""); - } else { - setShowChildTags(false); - handleTagChange(_tagValue); + if(divisions?.length > 0) { + divs = []; + divisions.forEach(division => { + if(division.display == "SDD Document Tracking") { + setShowChildTags(true); + } else { + divs.push( + { + divisionid: division.name, + divisionname: division.display, + } + ); + } + }); + setNewDivisions(divs); } - } + },[divisions]) + + React.useEffect(() => { + setAdditionalTagList(searchSections(otherTagList, searchValue, tagValue)); + },[searchValue, otherTagList, tagValue]) return ( <> @@ -242,6 +251,27 @@ const FileUploadForMSDPersonal = ({
    Select the name of the section of records you are uploading. Once you have selected the section name you will be able to select the respective documents from your computer.
    +
    + Personals Divisional Tracking: +
    +
    + {newDivisions.map(tag => + {handleTagChange(tag.divisionid)}} + clicked={tag.divisionid == tagValue} + /> + )} +
    + {showChildTags === true && (<> +
    + SDD Document Tracking: +
    {tagList.map(tag => {handleParentTagChange(tag.divisionid)}} - clicked={tag.divisionid == tagValue || (showChildTags && tag.divisionid === parentTagValue)} + onClick={()=>{handleTagChange(tag.divisionid)}} + clicked={tag.divisionid == tagValue} /> )}
    - {showChildTags === true && (
    +
    )} -
    )} +
    )}
    )} {modalFor === "add" && (

    Please drag and drop or add records associated with the section name you have selected above. All records upload will show under the selected section in the redaction application.

    diff --git a/request-management-api/migrations/versions/6b94062ebea9_more_msd_personal_divisions.py b/request-management-api/migrations/versions/6b94062ebea9_more_msd_personal_divisions.py new file mode 100644 index 000000000..225b330ed --- /dev/null +++ b/request-management-api/migrations/versions/6b94062ebea9_more_msd_personal_divisions.py @@ -0,0 +1,29 @@ +"""More MSD Personal Divisions + +Revision ID: 6b94062ebea9 +Revises: 07a6d930b276 +Create Date: 2023-09-15 16:15:44.118182 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '6b94062ebea9' +down_revision = '07a6d930b276' +branch_labels = None +depends_on = None + + +def upgrade(): + divisons = ["FASB", "ELMSD", "PLMS", "ISD", "SHRC"] + sortorder = 2 + + for division in divisons: + op.execute('INSERT INTO public."ProgramAreaDivisions"(programareaid, name, isactive, created_at, createdby, sortorder, issection, specifictopersonalrequests)\ + VALUES ((SELECT programareaid FROM public."ProgramAreas" WHERE iaocode =\'MSD\'), \''+division+'\', TRUE, NOW(), \'system\', '+ str(sortorder) +',FALSE, TRUE );') + sortorder = sortorder+1 + +def downgrade(): + op.execute('DELETE FROM public."ProgramAreaDivisions" WHERE programareaid in (SELECT programareaid FROM public."ProgramAreas" WHERE iaocode =\'MSD\') AND issection=FALSE and specifictopersonalrequests=TRUE and sortorder > 1') \ No newline at end of file From caf9b78c4352172eaedf6416a42c44cffcd16c7c Mon Sep 17 00:00:00 2001 From: divyav-aot Date: Tue, 19 Sep 2023 13:43:57 -0400 Subject: [PATCH 158/234] Fix related to bulk delete --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5d90a726b..ddcff0ebb 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ request-management-api/request_api/__pycache__/__init__.cpython-38.pyc request-management-api/request_api/models/__pycache__/*.* request-management-api/migrations/__pycache__/*.* request-management-api/.env +notification-manager/env/* *.pyc */__pycache__/* From 43566b951b60a9d0d3abff024a6dc96da17fcb96 Mon Sep 17 00:00:00 2001 From: divyav-aot Date: Tue, 19 Sep 2023 13:45:04 -0400 Subject: [PATCH 159/234] Fix related to bulk delete --- .../FOI/customComponents/Records/index.js | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/index.js b/forms-flow-web/src/components/FOI/customComponents/Records/index.js index 07306c026..568d178ec 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/index.js @@ -384,9 +384,11 @@ export const RecordsLog = ({ }))(record) ); } else { - for (let attachment of record.attachments) { - if (attachment.isselected) { - deleteAttachemnts.push(attachment.filepath); + if (record?.attachments) { + for (let attachment of record.attachments) { + if (attachment.isselected) { + deleteAttachemnts.push(attachment.filepath); + } } } } @@ -1388,8 +1390,13 @@ export const RecordsLog = ({ //function to manage download for harms option const enableHarmsDonwnload = () => { - return !recordsObj.records.every(record => record.isredactionready || record.failed || isrecordtimeout(record.created_at, RECORD_PROCESSING_HRS)); - } + return !recordsObj.records.every( + (record) => + record.isredactionready || + record.failed || + isrecordtimeout(record.created_at, RECORD_PROCESSING_HRS) + ); + }; return (
    @@ -1450,8 +1457,8 @@ export const RecordsLog = ({ disabled={item.disabled} sx={{ display: "flex" }} > - {!item.disabled ? - (isDownloadReady ? ( + {!item.disabled ? ( + isDownloadReady ? ( - ) : null) : (item.id === 1 && ())} + ) : null + ) : ( + item.id === 1 && ( + + ) + )} {item.label} ); From f72459268268fb777141fa209a875701a75942c5 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Tue, 19 Sep 2023 10:56:55 -0700 Subject: [PATCH 160/234] #4362 updated code migrations head --- .../migrations/versions/aacdbca19a47_MSD_CFD_reqstatuses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/request-management-api/migrations/versions/aacdbca19a47_MSD_CFD_reqstatuses.py b/request-management-api/migrations/versions/aacdbca19a47_MSD_CFD_reqstatuses.py index 57e2f8f8d..7b1b104d2 100644 --- a/request-management-api/migrations/versions/aacdbca19a47_MSD_CFD_reqstatuses.py +++ b/request-management-api/migrations/versions/aacdbca19a47_MSD_CFD_reqstatuses.py @@ -13,7 +13,7 @@ # revision identifiers, used by Alembic. revision = 'aacdbca19a47' -down_revision = '07a6d930b276' +down_revision = '6b94062ebea9' branch_labels = None depends_on = None From 1f7931fadcb46ab01235fb6c7c8091c4bc24b9ec Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Tue, 19 Sep 2023 15:18:54 -0700 Subject: [PATCH 161/234] Backend work completed. WIP FE + Testing of BE --- .../FOI/Admin/Divisions/Divisions.jsx | 11 ++++++++ .../models/ProgramAreaDivisions.py | 26 ++++++++++++++++--- .../request_api/resources/foiadmin.py | 14 ++++------ .../schemas/foiprogramareadivision.py | 13 +++++++++- .../services/programareadivisionservice.py | 11 ++++++++ 5 files changed, 62 insertions(+), 13 deletions(-) diff --git a/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx b/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx index af1397eb4..8bc1ce2c7 100644 --- a/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx +++ b/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx @@ -195,6 +195,17 @@ const Divisions = ({userDetail}) => { headerName: "Code", width: 75, }, + { + field: "issection", + headerName: "Section?", + width: 75, + }, + { + field: "parentid", + headerName: "Parent Division", + flex: 1, + renderCell: (params) => <>{params.value ? params.value : "-"}, + }, { field: "sortorder", headerName: "Sort Order", diff --git a/request-management-api/request_api/models/ProgramAreaDivisions.py b/request-management-api/request_api/models/ProgramAreaDivisions.py index 9305653c5..5caa7894f 100644 --- a/request-management-api/request_api/models/ProgramAreaDivisions.py +++ b/request-management-api/request_api/models/ProgramAreaDivisions.py @@ -26,6 +26,12 @@ def getallprogramareadivisons(cls): division_schema = ProgramAreaDivisionSchema(many=True) query = db.session.query(ProgramAreaDivision).filter_by(isactive=True,issection=False).all() return division_schema.dump(query) + + @classmethod + def getallprogramareadivisonsandsections(cls): + division_schema = ProgramAreaDivisionSchema(many=True) + query = db.session.query(ProgramAreaDivision).filter_by(isactive=True).all() + return division_schema.dump(query) @classmethod def getprogramareadivisions(cls,programareaid): @@ -54,7 +60,15 @@ def getpersonalrequestsdivisionsandsections(cls,programareaid): @classmethod def createprogramareadivision(cls, programareadivision)->DefaultMethodResult: created_at = datetime2.now().isoformat() - newprogramareadivision = ProgramAreaDivision(programareaid=programareadivision["programareaid"], name=programareadivision["name"], isactive=True, created_at=created_at) + newprogramareadivision = ProgramAreaDivision( + programareaid=programareadivision["programareaid"], + name=programareadivision["name"], + isactive=True, + created_at=created_at, + issection=programareadivision["issection"], + parentid=programareadivision["parentid"], + specifictopersonalrequests=programareadivision["specifictopersonalrequests"] + ) db.session.add(newprogramareadivision) db.session.commit() return DefaultMethodResult(True,'Division added successfully',newprogramareadivision.divisionid) @@ -77,7 +91,9 @@ def updateprogramareadivision(cls, divisionid, programareadivision, userid): if(division.count() > 0) : division.update({ProgramAreaDivision.programareaid:programareadivision["programareaid"], ProgramAreaDivision.name:programareadivision["name"], ProgramAreaDivision.isactive:True, ProgramAreaDivision.sortorder:programareadivision["sortorder"], - ProgramAreaDivision.updatedby:userid, ProgramAreaDivision.updated_at:datetime2.now()}, synchronize_session = False) + ProgramAreaDivision.issection:programareadivision["issection"], ProgramAreaDivision.parentid:programareadivision["parentid"], + ProgramAreaDivision.specifictopersonalrequests:programareadivision["specifictopersonalrequests"], ProgramAreaDivision.updatedby:userid, + ProgramAreaDivision.updated_at:datetime2.now()}, synchronize_session = False) db.session.commit() return DefaultMethodResult(True,'Division updated successfully',divisionid) else: @@ -90,7 +106,11 @@ def getdivisionbynameandprogramarea(cls, programareadivision): division = dbquery.filter_by(programareaid=programareadivision["programareaid"], isactive=True, name=programareadivision["name"]) return division_schema.dump(division) - + @classmethod + def getparentdivisionforsection(cls, divisionparentid): + division_schema = ProgramAreaDivisionSchema(many=True) + query = db.session.query(ProgramAreaDivision).filter_by(divisionid=divisionparentid).all() + return division_schema.dump(query) class ProgramAreaDivisionSchema(ma.Schema): class Meta: diff --git a/request-management-api/request_api/resources/foiadmin.py b/request-management-api/request_api/resources/foiadmin.py index eb32e28d9..27858c8e4 100644 --- a/request-management-api/request_api/resources/foiadmin.py +++ b/request-management-api/request_api/resources/foiadmin.py @@ -39,7 +39,7 @@ @cors_preflight('GET,OPTIONS') @API.route('/foiadmin/divisions') class FOIProgramAreaDivisions(Resource): - """Retrieves all FOI program area divisions""" + """Retrieves all FOI program area divisions/sections""" @staticmethod @TRACER.trace() @@ -48,7 +48,7 @@ class FOIProgramAreaDivisions(Resource): @auth.isfoiadmin() def get(): try: - result = programareadivisionservice().getallprogramareadivisions() + result = programareadivisionservice().getallprogramareadivisonsandsections() return json.dumps(result), 200 except KeyError as err: return {'status': False, 'message':err.messages}, 400 @@ -58,7 +58,7 @@ def get(): @cors_preflight('POST,OPTIONS') @API.route('/foiadmin/division') class CreateFOIProgramAreaDivision(Resource): - """Creates FOI program area division""" + """Creates FOI program area division/section""" @staticmethod @TRACER.trace() @@ -70,8 +70,6 @@ def post(): requestjson = request.get_json() programareadivisionschema = FOIProgramAreaDivisionSchema().load(requestjson) result = programareadivisionservice().createprogramareadivision(programareadivisionschema) - # if result.success == True: - # asyncio.ensure_future(); return {'status': result.success, 'message':result.message, 'id':result.identifier}, 200 except KeyError as err: return {'status': False, 'message':err.messages}, 400 @@ -82,7 +80,7 @@ def post(): @cors_preflight('PUT,OPTIONS') @API.route('/foiadmin/division/') class UpdateFOIProgramAreaDivision(Resource): - """Updates FOI program area division""" + """Updates FOI program area division/section""" @staticmethod @TRACER.trace() @@ -94,8 +92,6 @@ def put(divisionid): requestjson = request.get_json() programareadivisionschema = FOIProgramAreaDivisionSchema().load(requestjson) result = programareadivisionservice().updateprogramareadivision(divisionid, programareadivisionschema, AuthHelper.getuserid()) - # if result.success == True: - # asyncio.ensure_future(); return {'status': result.success, 'message':result.message, 'id':result.identifier}, 200 except KeyError as err: return {'status': False, 'message':err.messages}, 400 @@ -106,7 +102,7 @@ def put(divisionid): @cors_preflight('PUT,OPTIONS') @API.route('/foiadmin/division//disable') class DisableFOIProgramAreaDivision(Resource): - """Disables FOI program area division""" + """Disables FOI program area division/section""" @staticmethod @TRACER.trace() @auth.require diff --git a/request-management-api/request_api/schemas/foiprogramareadivision.py b/request-management-api/request_api/schemas/foiprogramareadivision.py index 4137ec13c..e105244af 100644 --- a/request-management-api/request_api/schemas/foiprogramareadivision.py +++ b/request-management-api/request_api/schemas/foiprogramareadivision.py @@ -1,4 +1,5 @@ -from marshmallow import EXCLUDE, Schema, fields +from marshmallow import EXCLUDE, Schema, fields, validates_schema, ValidationError +from request_api.services.programareadivisionservice import programareadivisionservice class FOIProgramAreaDivisionSchema(Schema): class Meta: # pylint: disable=too-few-public-methods @@ -9,3 +10,13 @@ class Meta: # pylint: disable=too-few-public-methods name = fields.Str(data_key="name") isactive = fields.Bool(data_key="isactive",allow_none=True) sortorder = fields.Int(data_key="sortorder",allow_none=True) + issection = fields.Bool(data_key="issection", allow_none=False) + parentid = fields.Int(data_key="parentid", allow_none=True) + specifictopersonalrequests = fields.Bool(data_key="specifictopersonalrequests", allow_none=True) + + @validates_schema + def validate_parentid(self, data): + parentdivision = programareadivisionservice().getparentdivisionforsection(data.parentid) + print(parentdivision) + if data.parentid is not None and len(parentdivision) <= 0: + raise ValidationError("parentid provided does not return a valid parent division") \ No newline at end of file diff --git a/request-management-api/request_api/services/programareadivisionservice.py b/request-management-api/request_api/services/programareadivisionservice.py index 913f6d7d0..51695b513 100644 --- a/request-management-api/request_api/services/programareadivisionservice.py +++ b/request-management-api/request_api/services/programareadivisionservice.py @@ -12,6 +12,12 @@ def getallprogramareadivisions(self): divisions = ProgramAreaDivision.getallprogramareadivisons() return self.__prepareprogramareas(divisions) + def getallprogramareadivisonsandsections(self): + """ Returns all active program area divisions and sections + """ + divisions = ProgramAreaDivision.getallprogramareadivisonsandsections() + return self.__prepareprogramareas(divisions) + def createprogramareadivision(self, data): """ Creates a program area division """ @@ -31,6 +37,11 @@ def disableprogramareadivision(self, divisionid,userid): return DefaultMethodResult(False,'Division is currently tagged to various records and cannot be disabled', divisionid) return ProgramAreaDivision.disableprogramareadivision(divisionid,userid) + def getparentdivisionforsection(self, divisionparentid): + """ Returns the parent division associated with a section + """ + return ProgramAreaDivision.getparentdivisionforsection(divisionparentid) + def __prepareprogramareas(self, data): """ Join program area name with division on programareaid """ From 28f4e7e6f34a14d878f8039831420f54c7ba0527 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Tue, 19 Sep 2023 15:41:26 -0700 Subject: [PATCH 162/234] FE work started --- .../src/components/FOI/Admin/Divisions/Divisions.jsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx b/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx index 8bc1ce2c7..b6427d6aa 100644 --- a/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx +++ b/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx @@ -198,12 +198,13 @@ const Divisions = ({userDetail}) => { { field: "issection", headerName: "Section?", - width: 75, + width: 100, }, { field: "parentid", - headerName: "Parent Division", - flex: 1, + headerName: "Parent Division ID", + width: 150, + align: "center", renderCell: (params) => <>{params.value ? params.value : "-"}, }, { From e7d2c2c2594ef611dee3037f29af547c385d9127 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Tue, 19 Sep 2023 15:57:22 -0700 Subject: [PATCH 163/234] #4362 new states for CFD,MSD persoanl requests --- .../FOI/FOIRequest/BottomButtonGroup/index.js | 2 ++ .../FOI/FOIRequest/MinistryReview/MinistryReview.js | 6 ++++++ .../src/components/FOI/FOIRequest/TabbedContainer.scss | 7 ++++++- forms-flow-web/src/components/FOI/FOIRequest/utils.js | 4 ++++ .../FOI/customComponents/ConfirmationModal/util.js | 4 ++++ .../components/FOI/customComponents/StateDropDown.js | 10 ++++++++++ .../components/FOI/customComponents/statedropdown.scss | 8 ++++++++ forms-flow-web/src/constants/FOI/statusEnum.js | 4 +++- 8 files changed, 43 insertions(+), 2 deletions(-) diff --git a/forms-flow-web/src/components/FOI/FOIRequest/BottomButtonGroup/index.js b/forms-flow-web/src/components/FOI/FOIRequest/BottomButtonGroup/index.js index 466f640b7..00eb20ab9 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/BottomButtonGroup/index.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/BottomButtonGroup/index.js @@ -320,6 +320,8 @@ const BottomButtonGroup = React.memo( case StateEnum.deduplication.name: case StateEnum.harms.name: case StateEnum.response.name: + case StateEnum.tagging.name: + case StateEnum.readytoscan.name: case StateEnum.peerreview.name: const status = Object.values(StateEnum).find( (statusValue) => statusValue.name === currentSelectedStatus diff --git a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js index c681188d2..cc0fca126 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js @@ -394,6 +394,12 @@ const MinistryReview = React.memo(({ userDetail }) => { case StateEnum.peerreview.name: foitabheaderBG = "foitabheadercollection foitabheaderPeerreviewBG"; break; + case StateEnum.tagging.name: + foitabheaderBG = "foitabheadercollection foitabheaderTaggingBG"; + break; + case StateEnum.readytoscan.name: + foitabheaderBG = "foitabheadercollection foitabheaderReadytoScanBG"; + break; default: foitabheaderBG = "foitabheadercollection foitabheaderdefaultBG"; break; diff --git a/forms-flow-web/src/components/FOI/FOIRequest/TabbedContainer.scss b/forms-flow-web/src/components/FOI/FOIRequest/TabbedContainer.scss index 755e511e5..56296e684 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/TabbedContainer.scss +++ b/forms-flow-web/src/components/FOI/FOIRequest/TabbedContainer.scss @@ -128,7 +128,12 @@ .foitabheaderPeerreviewBG{ background-color: #096DD1; } - + .foitabheaderTaggingBG{ + background-color: #6f2852; + } + .foitabheaderReadytoScanBG{ + background-color: #e88cb7; + } .foileftpanelheader { diff --git a/forms-flow-web/src/components/FOI/FOIRequest/utils.js b/forms-flow-web/src/components/FOI/FOIRequest/utils.js index a6c33f112..eec51e3d8 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/utils.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/utils.js @@ -129,6 +129,10 @@ export const getTabBG = (_tabStatus, _requestState) => { return "foitabheadercollection foitabheaderResponseBG"; case StateEnum.peerreview.name: return "foitabheadercollection foitabheaderPeerreviewBG"; + case StateEnum.tagging.name: + return "foitabheadercollection foitabheaderTaggingBG"; + case StateEnum.readytoscan.name: + return "foitabheadercollection foitabheaderReadytoScanBG"; default: return "foitabheadercollection foitabheaderdefaultBG"; } diff --git a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js index 608e64fc8..6d4ad0ec6 100644 --- a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js +++ b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js @@ -50,6 +50,10 @@ import { getFullnameList } from "../../../../helper/FOI/helper"; return {title: "Changing the state", body: "Are you sure you want to change the state to Intake in Progress?"}; case StateEnum.peerreview.name.toLowerCase(): return {title: "Changing the state", body: "Are you sure you want to change the state to Peer Review?"}; + case StateEnum.tagging.name.toLowerCase(): + return {title: "Changing the state", body: "Are you sure you want to change the state to Tagging?"}; + case StateEnum.readytoscan.name.toLowerCase(): + return {title: "Changing the state", body: "Are you sure you want to change the state to Ready to Scan?"}; case StateEnum.open.name.toLowerCase(): return {title: "Changing the state", body: "Are you sure you want to Open this request?"}; case StateEnum.closed.name.toLowerCase(): diff --git a/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js b/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js index 4a1e4b315..bdc953935 100644 --- a/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js +++ b/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js @@ -29,6 +29,10 @@ const StateDropDown = ({ const cfrFeeData = useSelector((reduxState) => reduxState.foiRequests.foiRequestCFRForm.feedata); const cfrStatus = useSelector((reduxState) => reduxState.foiRequests.foiRequestCFRForm.status); + let requestDetails = useSelector( + (state) => state.foiRequests.foiRequestDetail + ); + React.useEffect(() => { if (requestState && requestState !== status) { setStatus(requestState); @@ -126,7 +130,13 @@ const StateDropDown = ({ case StateEnum.callforrecords.name.toLowerCase(): if (_isMinistryCoordinator && personalRequest) return _stateList.callforrecordsforpersonal; + if(personalIAO && (requestDetails.bcgovcode.toLowerCase() === "mcf" || requestDetails.bcgovcode.toLowerCase() === "msd")) + return _stateList.callforrecordscfdmsdpersonal return _stateList.callforrecords; + case StateEnum.tagging.name.toLowerCase(): + return _stateList.tagging; + case StateEnum.readytoscan.name.toLowerCase(): + return _stateList.readytoscan; case StateEnum.review.name.toLowerCase(): return _stateList.review; case StateEnum.onhold.name.toLowerCase(): diff --git a/forms-flow-web/src/components/FOI/customComponents/statedropdown.scss b/forms-flow-web/src/components/FOI/customComponents/statedropdown.scss index f21192490..ac289315b 100644 --- a/forms-flow-web/src/components/FOI/customComponents/statedropdown.scss +++ b/forms-flow-web/src/components/FOI/customComponents/statedropdown.scss @@ -88,4 +88,12 @@ .peerreview { background-color: #096DD1; +} + +.tagging { + background-color: #6f2852; +} + +.readytoscan{ + background-color: #e88cb7; } \ No newline at end of file diff --git a/forms-flow-web/src/constants/FOI/statusEnum.js b/forms-flow-web/src/constants/FOI/statusEnum.js index 434ddf667..0c5ba3434 100644 --- a/forms-flow-web/src/constants/FOI/statusEnum.js +++ b/forms-flow-web/src/constants/FOI/statusEnum.js @@ -5,6 +5,8 @@ const StateList = Object.freeze({ open: [{status: "Open", isSelected: false}, {status: "Call For Records", isSelected: false}, {status:"Peer Review", isSelected: false},{status: "Closed", isSelected: false}], callforrecords: [{status: "Call For Records", isSelected: false}, {status: "Open", isSelected: false}, {status: "Closed", isSelected: false}], callforrecordscfdmsdpersonal: [{status: "Call For Records", isSelected: false}, {status: "Open", isSelected: false}, {status: "Tagging", isSelected: false},{status: "Ready to Scan", isSelected: false}, {status: "Closed", isSelected: false}], + tagging :[{status: "Tagging", isSelected: true},{status: "Call For Records", isSelected: false}, {status: "Ready to Scan", isSelected: false},{status: "Records Review", isSelected: false}, {status: "Closed", isSelected: false}], + readytoscan : [{status: "Ready to Scan", isSelected: true},{status: "Call For Records", isSelected: false}, {status: "Tagging", isSelected: false},{status: "Records Review", isSelected: false}, {status: "Closed", isSelected: false}], feeassessed: [{status: "Fee Estimate", isSelected: false}, {status: "On Hold", isSelected: false}, {status: "Call For Records", isSelected: false}, {status: "Closed", isSelected: false}], feeassessedforpersonal: [{status: "Fee Estimate", isSelected: false}, {status: "Call For Records", isSelected: false}, {status: "Closed", isSelected: false}], onhold: [{status: "On Hold", isSelected: false}, {status: "Call For Records", isSelected: false}, {status: "Closed", isSelected: false}], @@ -41,7 +43,7 @@ const MinistryStateList = Object.freeze({ const StateEnum = Object.freeze({ open: {name: "Open", id: 1}, callforrecords: {name: "Call For Records", id: 2}, - callforrecordsoverdue: {name: "Call For Records Overdue", id: 17}, + callforrecordsoverdue: {name: "Call For Records Overdue", id: -100}, closed: {name: "Closed", id: 3}, redirect: {name: "Redirect", id: 4}, unopened: {name: "Unopened", id: 5}, From 7c0213bde32b910f364c157c265862c7cdb74df0 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Tue, 19 Sep 2023 16:25:16 -0700 Subject: [PATCH 164/234] BE testing completed. WIP FE and merging in changes from dev-AA-4292 --- .../src/components/FOI/Admin/Divisions/Divisions.jsx | 2 +- .../request_api/schemas/foiprogramareadivision.py | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx b/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx index b6427d6aa..c0f0bc051 100644 --- a/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx +++ b/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx @@ -202,7 +202,7 @@ const Divisions = ({userDetail}) => { }, { field: "parentid", - headerName: "Parent Division ID", + headerName: "Parent Division", width: 150, align: "center", renderCell: (params) => <>{params.value ? params.value : "-"}, diff --git a/request-management-api/request_api/schemas/foiprogramareadivision.py b/request-management-api/request_api/schemas/foiprogramareadivision.py index e105244af..e141cf409 100644 --- a/request-management-api/request_api/schemas/foiprogramareadivision.py +++ b/request-management-api/request_api/schemas/foiprogramareadivision.py @@ -15,8 +15,7 @@ class Meta: # pylint: disable=too-few-public-methods specifictopersonalrequests = fields.Bool(data_key="specifictopersonalrequests", allow_none=True) @validates_schema - def validate_parentid(self, data): - parentdivision = programareadivisionservice().getparentdivisionforsection(data.parentid) - print(parentdivision) - if data.parentid is not None and len(parentdivision) <= 0: + def validate_parentid(self, data, **kwargs): + parentdivision = programareadivisionservice().getparentdivisionforsection(data["parentid"]) + if data["parentid"] is not None and len(parentdivision) <= 0: raise ValidationError("parentid provided does not return a valid parent division") \ No newline at end of file From d3cdc117a81f55b771ddac15e47c69d961aa50b3 Mon Sep 17 00:00:00 2001 From: Richard Qi Date: Tue, 19 Sep 2023 23:47:03 -0700 Subject: [PATCH 165/234] 1. update division 2. remove duplicate CFD personal sections --- .../Attachments/AttachmentModal.js | 1 - .../customComponents/Records/MCFPersonal.js | 182 ++++++++++++++ .../customComponents/Records/MSDPersonal.js | 228 ++++++++++++++++++ .../FOI/customComponents/Records/index.js | 139 +++++++---- .../1e9a62e22f88_CFD_sections_bulkinsert.py | 2 +- 5 files changed, 499 insertions(+), 53 deletions(-) create mode 100644 forms-flow-web/src/components/FOI/customComponents/Records/MCFPersonal.js create mode 100644 forms-flow-web/src/components/FOI/customComponents/Records/MSDPersonal.js diff --git a/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js b/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js index 6937787a4..f1f72113f 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js +++ b/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js @@ -298,7 +298,6 @@ export default function AttachmentModal({ const MCFSections = useSelector((state) => state.foiRequests.foiPersonalSections); const MSDSections = useSelector((state) => state.foiRequests.foiPersonalDivisionsAndSections); - console.log("MSDSections", MSDSections); return (
    diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/MCFPersonal.js b/forms-flow-web/src/components/FOI/customComponents/Records/MCFPersonal.js new file mode 100644 index 000000000..7b7b3ce58 --- /dev/null +++ b/forms-flow-web/src/components/FOI/customComponents/Records/MCFPersonal.js @@ -0,0 +1,182 @@ +import React, { useState, useEffect } from "react"; +import { useSelector } from "react-redux"; +import { + ClickableChip +} from "../../Dashboard/utils"; +import "../FileUpload/FileUpload.scss"; +import Grid from "@material-ui/core/Grid"; +import Paper from "@mui/material/Paper"; +import SearchIcon from "@material-ui/icons/Search"; +import InputAdornment from "@mui/material/InputAdornment"; +import InputBase from "@mui/material/InputBase"; +import IconButton from "@material-ui/core/IconButton"; +import _ from 'lodash'; + +const MCFPersonal = ({ + setNewDivision, + tagValue, + divisionModalTagValue +}) => { + + const [searchValue, setSearchValue] = useState(""); + const [showAdditionalTags, setShowAdditionalTags] = useState(false); + const [additionalTagList, setAdditionalTagList] = useState([]); + + const MCFSections = useSelector((state) => state.foiRequests.foiPersonalSections); + const [tagList, setTagList] = useState(MCFSections?.sections?.slice(0, 30)); + const [otherTagList, setOtherTagList] = useState(MCFSections?.sections?.slice(31)); + + useEffect(() => { + setTagList(MCFSections?.sections?.slice(0, 30)); + setOtherTagList(MCFSections?.sections?.slice(31)); + },[MCFSections]) + + useEffect(() => { + setAdditionalTagList(searchSections(otherTagList, searchValue, tagValue)); + },[searchValue, otherTagList, tagValue]) + + const searchSections = (_sectionArray, _keyword, _selectedSectionValue) => { + let newSectionArray = []; + if(_keyword || _selectedSectionValue) { + _sectionArray.map((section) => { + if(_keyword && section.name.toLowerCase().includes(_keyword.toLowerCase())) { + newSectionArray.push(section); + } else if(section.divisionid === _selectedSectionValue) { + newSectionArray.unshift(section); + } + }); + } + + if(newSectionArray.length > 0) { + setShowAdditionalTags(true); + } else { + setShowAdditionalTags(false); + } + return newSectionArray; + } + + return ( + <> +
    +
    + {tagList.filter(div => { + return div.divisionid !== tagValue; + }).map(tag => + {setNewDivision(tag.divisionid)}} + clicked={divisionModalTagValue == tag.divisionid} + /> + )} +
    +
    + + + + + {setSearchValue(e.target.value.trim())}} + sx={{ + color: "#38598A", + }} + startAdornment={ + + + Search any additional sections here + + + + } + fullWidth + /> + + + {showAdditionalTags === true && ( + {additionalTagList.filter(div => { + return div.divisionid !== tagValue; + }).map(tag => + {setNewDivision(tag.divisionid)}} + clicked={divisionModalTagValue == tag.divisionid} + /> + )} + )} + +
    +
    + + ); +}; + +export default MCFPersonal; \ No newline at end of file diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/MSDPersonal.js b/forms-flow-web/src/components/FOI/customComponents/Records/MSDPersonal.js new file mode 100644 index 000000000..7eacfb2be --- /dev/null +++ b/forms-flow-web/src/components/FOI/customComponents/Records/MSDPersonal.js @@ -0,0 +1,228 @@ +import React, { useState, useEffect } from "react"; +import { useSelector } from "react-redux"; +import { + ClickableChip +} from "../../Dashboard/utils"; +import "../FileUpload/FileUpload.scss"; +import Grid from "@material-ui/core/Grid"; +import Paper from "@mui/material/Paper"; +import SearchIcon from "@material-ui/icons/Search"; +import InputAdornment from "@mui/material/InputAdornment"; +import InputBase from "@mui/material/InputBase"; +import IconButton from "@material-ui/core/IconButton"; +import _ from 'lodash'; + +const MSDPersonal = ({ + setNewDivision, + tagValue, + divisionModalTagValue, + divisions = [] +}) => { + const [searchValue, setSearchValue] = useState(""); + const [additionalTagList, setAdditionalTagList] = useState([]); + const [showChildTags, setShowChildTags] = useState(false); + const [showAdditionalTags, setShowAdditionalTags] = useState(false); + const [newDivisions, setNewDivisions] = useState([]); + + const MSDSections = useSelector((state) => state.foiRequests.foiPersonalDivisionsAndSections); + const [tagList, setTagList] = useState(MSDSections?.divisions[0]?.sections?.slice(0, 10)); + const [otherTagList, setOtherTagList] = useState(MSDSections?.divisions[0]?.sections?.slice(11)); + + useEffect(() => { + setTagList(MSDSections?.divisions[0]?.sections?.slice(0, 10)); + setOtherTagList(MSDSections?.divisions[0]?.sections?.slice(11)); + },[MSDSections]) + + const searchSections = (_sectionArray, _keyword, _selectedSectionValue) => { + let newSectionArray = []; + if(_keyword || _selectedSectionValue) { + _sectionArray.map((section) => { + if(_keyword && section.name.toLowerCase().includes(_keyword.toLowerCase())) { + newSectionArray.push(section); + } else if(section.divisionid === _selectedSectionValue) { + newSectionArray.unshift(section); + } + }); + } + + if(newSectionArray.length > 0) { + setShowAdditionalTags(true); + } else { + setShowAdditionalTags(false); + } + return newSectionArray; + } + + let divs = []; + useEffect(() => { + if(divisions?.length > 0) { + divs = []; + divisions.forEach(division => { + if(division.display == "SDD Document Tracking") { + setShowChildTags(true); + } else { + divs.push( + { + divisionid: division.name, + divisionname: division.display, + } + ); + } + }); + setNewDivisions(divs); + } + },[divisions]) + + useEffect(() => { + setAdditionalTagList(searchSections(otherTagList, searchValue, tagValue)); + },[searchValue, otherTagList, tagValue]) + + return ( + <> +
    +
    + Personals Divisional Tracking: +
    +
    + {newDivisions.filter(div => { + return div.divisionid !== tagValue; + }).map(tag => + {setNewDivision(tag.divisionid)}} + clicked={tag.divisionid == divisionModalTagValue} + /> + )} +
    + {showChildTags === true && (<> +
    + SDD Document Tracking: +
    +
    + {tagList.filter(div => { + return div.divisionid !== tagValue; + }).map(tag => + {setNewDivision(tag.divisionid)}} + clicked={tag.divisionid == divisionModalTagValue} + /> + )} +
    +
    + + + + + {setSearchValue(e.target.value.trim())}} + sx={{ + color: "#38598A", + }} + value={searchValue} + startAdornment={ + + + Search any additional sections here + + + + } + fullWidth + /> + + + {showAdditionalTags === true && ( + {additionalTagList.filter(div => { + return div.divisionid !== tagValue; + }).map(tag => + {setNewDivision(tag.divisionid)}} + clicked={divisionModalTagValue == tag.divisionid} + /> + )} + )} + +
    )} +
    + + ); +}; + +export default MSDPersonal; \ No newline at end of file diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/index.js b/forms-flow-web/src/components/FOI/customComponents/Records/index.js index 1e70d5030..77b1d6559 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/index.js @@ -55,6 +55,8 @@ import { isScanningTeam } from "../../../../helper/FOI/helper"; import { MinistryNeedsScanning } from "../../../../constants/FOI/enum"; //import {convertBytesToMB} from "../../../../components/FOI/customComponents/FileUpload/util"; import FOI_COMPONENT_CONSTANTS from "../../../../constants/FOI/foiComponentConstants"; +import MCFPersonal from './MCFPersonal'; +import MSDPersonal from './MSDPersonal'; const useStyles = makeStyles((_theme) => ({ @@ -182,6 +184,13 @@ export const RecordsLog = ({ (state) => state.foiRequests.isRecordsLoading ); + const tagList = divisions.filter(d => d.divisionname.toLowerCase() !== 'communications').map(division => { + return { + name: division.divisionid, + display: division.divisionname, + } + }); + const classes = useStyles(); const [records, setRecords] = useState(recordsObj?.records); const [totalUploadedRecordSize, setTotalUploadedRecordSize] = useState(0); @@ -1484,62 +1493,90 @@ export const RecordsLog = ({ Close - + - + {(records.filter(r=>(r.isselected && r.attributes.divisions.length > 1)).length > 0 && filterValue < 0) ? - <> +
    You have selected a record that was provided by more than one division.

    To change the division you must first filter the Records Log by the division that you want to no longer be associated with the selected records. -


    - : + +
    + : <> - - Select the divisions that corresponds to the records you have selected.

    - This will update the divisions on all records you have selected both in the gathering records log and the redaction app. -




    - - {divisions.filter(division => { - if (division.divisionname.toLowerCase() === 'communications') { - return false; - } else if (filterValue > -1 && filterValue !== division.divisionid) { - return true; - } else if (records.filter(r => r.isselected)[0]?.attributes.divisions[0].divisionid !== division.divisionid) { - return true; - } else { - return false; - } - }).map(division => - {setDivisionModalTagValue(division.divisionid)}} - clicked={divisionModalTagValue === division.divisionid} - /> - )} - - } +
    + Select the divisions that corresponds to the records you have selected.

    + This will update the divisions on all records you have selected both in the gathering records log and the redaction app. +
    + + {(requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL) ? + (bcgovcode == "MCF") ? + r.isselected)[0]?.attributes.divisions[0].divisionid} + divisionModalTagValue={divisionModalTagValue} + /> + : + (bcgovcode == "MSD") ? + r.isselected)[0]?.attributes.divisions[0].divisionid} + divisionModalTagValue={divisionModalTagValue} + divisions={tagList} + /> + : +
    + {divisions.filter(division => { + if (division.divisionname.toLowerCase() === 'communications') { + return false; + } else if (filterValue > -1 && filterValue !== division.divisionid) { + return true; + } else if (records.filter(r => r.isselected)[0]?.attributes.divisions[0].divisionid !== division.divisionid) { + return true; + } else { + return false; + } + }).map(division => + {setDivisionModalTagValue(division.divisionid)}} + clicked={divisionModalTagValue === division.divisionid} + /> + )} +
    + : +
    + {divisions.filter(division => { + if (division.divisionname.toLowerCase() === 'communications') { + return false; + } else if (filterValue > -1 && filterValue !== division.divisionid) { + return true; + } else if (records.filter(r => r.isselected)[0]?.attributes.divisions[0].divisionid !== division.divisionid) { + return true; + } else { + return false; + } + }).map(division => + {setDivisionModalTagValue(division.divisionid)}} + clicked={divisionModalTagValue === division.divisionid} + /> + )} +
    } + }
    diff --git a/request-management-api/migrations/versions/1e9a62e22f88_CFD_sections_bulkinsert.py b/request-management-api/migrations/versions/1e9a62e22f88_CFD_sections_bulkinsert.py index 23298700c..d59e12d7e 100644 --- a/request-management-api/migrations/versions/1e9a62e22f88_CFD_sections_bulkinsert.py +++ b/request-management-api/migrations/versions/1e9a62e22f88_CFD_sections_bulkinsert.py @@ -19,7 +19,7 @@ def upgrade(): - sections = ["COVER SHEET", "INSIDE FRONT COVER", "PERSONAL HISTORY AND RECORDING", "INTAKE AND INVESTIGATION", "INTAKE ASSESSMENT AND RESPONSE", "INTAKE AND INCIDENT SERVICE REQUEST AND MEMO", "LEGAL AND AGREEMENTS", "FINANCE", "FINANCIAL DOCUMENTATION", "EXTERNAL ASSESSMENTS", "MEDICAL", "INTERNAL ASSESSMENTS", "REVIEWS", "RISK ASSESSMENT", "ASSESSMENTS & REPORTS", "DOCUMENT SECTION", "ADOPTION", "GENERAL CORRESPONDENCE", "ADR", "EDUCATION, EMPLOYMENT AND TRAINING", "CASE NOTES (BLACK BOOK) NOTES", "INSIDE BACK COVER", "ACCOUNTABILITY", "ACCOUNTABILITY TRAINING RECRUITMENT", "ACCURATE DUPLICATION", "ACTIVITY FORMS", "ACTIVITY FORMS AND COURT DOCUMENTS", "ACTIVITY FORMS AND FINANCIAL DOCUMENTATION", "ACTIVITY FORMS AND PURCHASE ORDERS", "ADOPTION", "ADOPTIVE PARENTS BACKGROUND", "ADR", "AGREEMENT AND APPROVAL DOCUMENTATION", "AGREEMENTS", "APPROVAL AND LICENSING", "ASSESSMENTS & REFERRALS", "ASSESSMENTS & REPORTS", "ASSESSMENTS & REPORTS & REFERRALS", "ASSESSMENTS EXTERNAL", "ASSESSMENTS INTERNAL", "AUTHORIZATION", "BLACK BOOK NOTES", "CAREGIVER INFORMATION FOR CHILDREN NOT IN CARE", "CASE CONFERENCE", "CASE NOTES", "CASE NOTES (BLACK BOOK) NOTES", "CASE NOTES BB NOTES", "CASE NOTES INSIDE BACK COVER", "CHANGES FORMS AND FINANCIAL", "CHARGE CARD", "CHILD AND BIRTH FAMILY BACKGROUND", "CHILD IN CARE INFORMATION", "COLLABORATIVE PLANNING AND DECISION MAKING", "CONSENTS AND AGREEMENTS", "CONTENTS", "CONTRACT SELECTION AND APPROVAL", "CONTRACT SERVICE PROVIDER", "CONTRACTS AND FINANCIAL DOCUMENTATION", "CORRESPONDENCE", "COURT DOCUMENTS", "COURT ORDERS", "COVER PAGE", "CRITICAL INCIDENTS REPORTABLES INVESTIGATIONS", "CS UNREG", "CULTURAL PLANNING AND ROOTS", "CULTURAL SECTION", "CYMH", "DOCS", "DOCS 1", "DOCS 2", "DOCS 3", "DOCS 4", "DOCS 5", "DOCS 6", "DOCS 7", "DOCS 8", "DOCS 9", "DOCUMENT SECTION", "DOCUMENTATION", "DOCUMENTATION SECTION", "DOCUMENTS SECTION", "EDUCATION, EMPLOYMENT AND TRAINING", "EXCEPTIONS", "EXTERNAL ASSESSMENTS", "EXTERNAL ASSESSMENTS AND REPORTS", "FAMILY GROUP", "FAMILY MEDIATION", "FILE CARD", "FILE STATUS", "FILE SUMMARY", "FINANCE", "FINANCIAL", "FINANCIAL DOCUMENTATION", "FINANCIAL LIVING EXPENSES", "FINANCIAL PROGRAM EXPENSES", "FS CASE SNAPSHOT REPORT", "GENERAL CORRESPONDENCE", "INCIDENTS", "INCIDENTS AND SERVICE REQUESTS", "INSIDE BACK COVER", "INSIDE FRONT COVER", "INTAKE ASSESSMENT AND INVESTIGATION", "INTAKE AND AGREEMENTS", "INTAKE AND ASSESSMENTS", "INTAKE AND INCIDENT SERVICE REQUEST AND MEMO", "INTAKE AND INVESTIGATION", "INTAKE ASSESSMENT AND RESPONSE", "INTERNAL ASSESSMENTS", "INTERNAL ASSESSMENTS AND SERVICE PLANS", "INVESTIGATIONS AND INCIDENTS", "LEGAL", "LEGAL AND AGREEMENTS", "LOOSE DOCS", "MAPLES", "MEDIATION", "MEDICAL", "MEDICAL CORRESPONDENCE AND AUTHORIZATION", "MISC DOCS", "NOTE PAD SCREENS", "NOTIFICATION AND FINANCIAL DOCUMENT SECTION", "OUT OF CARE SERVICES", "PERSONAL HISTORY AND RECORDING", "PHYSICAL FILE SUMMARY REPORT", "PHYSICAL ICM", "PLACEMENT SLIPS", "PLANNING", "PLANNING INFORMATION", "POST ADOPTION ASSISTANCE", "POST PLACEMENT DOCUMENTATION", "PRACTITIONER CASE NOTES", "PROGRESS NOTES", "PROGRESS REPORT", "PROTOCOL AND INCIDENTS", "PURCHASE FORMS", "RECEIPTS", "RECONSIDERATION", "RECORD STATS", "RECORDINGS", "REFERRAL", "REGISTRATION CHANGE FORMS AND FINANCIAL DOCUMENT SECTION", "RELIEF CAREGIVER DOCUMENTATION", "REPORTS", "REPORTS AND ASSESSMENTS EXTERNAL", "REVIEWS", "RISK ASSESSMENT", "RUNNING RECORDS", "SAFETY ASSESSMENT", "SCREENING ASSESSMENT", "SERVICE REQUESTS", "SERVICES FOR CHILDREN NOT IN CARE", "SOCIAL WORKER NOTES", "SUPERVISION ORDERS", "TRANSITION PLANNING", "VULNERABILITY ASSESSMENT", "WORKER CASE NOTES", "WORKER NOTES", "YOUNG OFFENDERS ACT"] + sections = ["COVER SHEET", "INSIDE FRONT COVER", "PERSONAL HISTORY AND RECORDING", "INTAKE AND INVESTIGATION", "INTAKE ASSESSMENT AND RESPONSE", "INTAKE AND INCIDENT SERVICE REQUEST AND MEMO", "LEGAL AND AGREEMENTS", "FINANCE", "FINANCIAL DOCUMENTATION", "EXTERNAL ASSESSMENTS", "MEDICAL", "INTERNAL ASSESSMENTS", "REVIEWS", "RISK ASSESSMENT", "ASSESSMENTS & REPORTS", "DOCUMENT SECTION", "ADOPTION", "GENERAL CORRESPONDENCE", "ADR", "EDUCATION, EMPLOYMENT AND TRAINING", "CASE NOTES (BLACK BOOK) NOTES", "INSIDE BACK COVER", "ACCOUNTABILITY", "ACCOUNTABILITY TRAINING RECRUITMENT", "ACCURATE DUPLICATION", "ACTIVITY FORMS", "ACTIVITY FORMS AND COURT DOCUMENTS", "ACTIVITY FORMS AND FINANCIAL DOCUMENTATION", "ACTIVITY FORMS AND PURCHASE ORDERS", "ADOPTIVE PARENTS BACKGROUND", "AGREEMENT AND APPROVAL DOCUMENTATION", "AGREEMENTS", "APPROVAL AND LICENSING", "ASSESSMENTS & REFERRALS", "ASSESSMENTS & REPORTS & REFERRALS", "ASSESSMENTS EXTERNAL", "ASSESSMENTS INTERNAL", "AUTHORIZATION", "BLACK BOOK NOTES", "CAREGIVER INFORMATION FOR CHILDREN NOT IN CARE", "CASE CONFERENCE", "CASE NOTES", "CASE NOTES BB NOTES", "CASE NOTES INSIDE BACK COVER", "CHANGES FORMS AND FINANCIAL", "CHARGE CARD", "CHILD AND BIRTH FAMILY BACKGROUND", "CHILD IN CARE INFORMATION", "COLLABORATIVE PLANNING AND DECISION MAKING", "CONSENTS AND AGREEMENTS", "CONTENTS", "CONTRACT SELECTION AND APPROVAL", "CONTRACT SERVICE PROVIDER", "CONTRACTS AND FINANCIAL DOCUMENTATION", "CORRESPONDENCE", "COURT DOCUMENTS", "COURT ORDERS", "COVER PAGE", "CRITICAL INCIDENTS REPORTABLES INVESTIGATIONS", "CS UNREG", "CULTURAL PLANNING AND ROOTS", "CULTURAL SECTION", "CYMH", "DOCS", "DOCS 1", "DOCS 2", "DOCS 3", "DOCS 4", "DOCS 5", "DOCS 6", "DOCS 7", "DOCS 8", "DOCS 9", "DOCUMENTATION", "DOCUMENTATION SECTION", "DOCUMENTS SECTION", "EXCEPTIONS", "EXTERNAL ASSESSMENTS AND REPORTS", "FAMILY GROUP", "FAMILY MEDIATION", "FILE CARD", "FILE STATUS", "FILE SUMMARY", "FINANCIAL", "FINANCIAL LIVING EXPENSES", "FINANCIAL PROGRAM EXPENSES", "FS CASE SNAPSHOT REPORT", "INCIDENTS", "INCIDENTS AND SERVICE REQUESTS", "INTAKE ASSESSMENT AND INVESTIGATION", "INTAKE AND AGREEMENTS", "INTAKE AND ASSESSMENTS", "INTERNAL ASSESSMENTS AND SERVICE PLANS", "INVESTIGATIONS AND INCIDENTS", "LEGAL", "LOOSE DOCS", "MAPLES", "MEDIATION", "MEDICAL CORRESPONDENCE AND AUTHORIZATION", "MISC DOCS", "NOTE PAD SCREENS", "NOTIFICATION AND FINANCIAL DOCUMENT SECTION", "OUT OF CARE SERVICES", "PHYSICAL FILE SUMMARY REPORT", "PHYSICAL ICM", "PLACEMENT SLIPS", "PLANNING", "PLANNING INFORMATION", "POST ADOPTION ASSISTANCE", "POST PLACEMENT DOCUMENTATION", "PRACTITIONER CASE NOTES", "PROGRESS NOTES", "PROGRESS REPORT", "PROTOCOL AND INCIDENTS", "PURCHASE FORMS", "RECEIPTS", "RECONSIDERATION", "RECORD STATS", "RECORDINGS", "REFERRAL", "REGISTRATION CHANGE FORMS AND FINANCIAL DOCUMENT SECTION", "RELIEF CAREGIVER DOCUMENTATION", "REPORTS", "REPORTS AND ASSESSMENTS EXTERNAL", "RUNNING RECORDS", "SAFETY ASSESSMENT", "SCREENING ASSESSMENT", "SERVICE REQUESTS", "SERVICES FOR CHILDREN NOT IN CARE", "SOCIAL WORKER NOTES", "SUPERVISION ORDERS", "TRANSITION PLANNING", "VULNERABILITY ASSESSMENT", "WORKER CASE NOTES", "WORKER NOTES", "YOUNG OFFENDERS ACT"] sortorder = 1 for section in sections: From ea1cadb23bb0ba4da8ff64ab63f2fd74ec518d2e Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Wed, 20 Sep 2023 09:20:42 -0700 Subject: [PATCH 166/234] #4362 Ministry Tagging, ReadytoScan req. status --- forms-flow-web/src/constants/FOI/statusEnum.js | 2 ++ .../request_api/models/FOIMinistryRequests.py | 10 +++++----- .../request_api/models/FOIRequestNotificationUsers.py | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/forms-flow-web/src/constants/FOI/statusEnum.js b/forms-flow-web/src/constants/FOI/statusEnum.js index 0c5ba3434..ed103f6e2 100644 --- a/forms-flow-web/src/constants/FOI/statusEnum.js +++ b/forms-flow-web/src/constants/FOI/statusEnum.js @@ -38,6 +38,8 @@ const MinistryStateList = Object.freeze({ response: [{status: "Response", isSelected: false}], closed: [{status: "Closed", isSelected: false}], peerreview: [{status: "Peer Review", isSelected: false}], + tagging :[{status: "Tagging", isSelected: true}], + readytoscan : [{status: "Ready to Scan", isSelected: true}] }); const StateEnum = Object.freeze({ diff --git a/request-management-api/request_api/models/FOIMinistryRequests.py b/request-management-api/request_api/models/FOIMinistryRequests.py index b93d646c6..cf923b327 100644 --- a/request-management-api/request_api/models/FOIMinistryRequests.py +++ b/request-management-api/request_api/models/FOIMinistryRequests.py @@ -162,11 +162,11 @@ def getrequests(cls, group = None): if group is None: _ministryrequestids = _session.query(distinct(FOIMinistryRequest.foiministryrequestid)).filter(FOIMinistryRequest.isactive == True).all() elif (group == IAOTeamWithKeycloackGroup.flex.value): - _ministryrequestids = _session.query(distinct(FOIMinistryRequest.foiministryrequestid)).filter(and_(FOIMinistryRequest.isactive == True), and_(and_(FOIMinistryRequest.assignedgroup == group),and_(FOIMinistryRequest.requeststatusid.in_([1,2,3,12,13,7,8,9,10,11,14,16])))).all() + _ministryrequestids = _session.query(distinct(FOIMinistryRequest.foiministryrequestid)).filter(and_(FOIMinistryRequest.isactive == True), and_(and_(FOIMinistryRequest.assignedgroup == group),and_(FOIMinistryRequest.requeststatusid.in_([1,2,3,12,13,7,8,9,10,11,14,16,17,18])))).all() elif (group in ProcessingTeamWithKeycloackGroup.list()): - _ministryrequestids = _session.query(distinct(FOIMinistryRequest.foiministryrequestid)).filter(and_(FOIMinistryRequest.isactive == True), and_(and_(FOIMinistryRequest.assignedgroup == group),and_(FOIMinistryRequest.requeststatusid.in_([1,2,3,7,8,9,10,11,14,16])))).all() + _ministryrequestids = _session.query(distinct(FOIMinistryRequest.foiministryrequestid)).filter(and_(FOIMinistryRequest.isactive == True), and_(and_(FOIMinistryRequest.assignedgroup == group),and_(FOIMinistryRequest.requeststatusid.in_([1,2,3,7,8,9,10,11,14,16,17,18])))).all() else: - _ministryrequestids = _session.query(distinct(FOIMinistryRequest.foiministryrequestid)).filter(and_(FOIMinistryRequest.isactive == True), or_(and_(FOIMinistryRequest.assignedgroup == group),and_(FOIMinistryRequest.assignedministrygroup == group,or_(FOIMinistryRequest.requeststatusid.in_([2,7,9,8,10,11,12,13,14,16]))))).all() + _ministryrequestids = _session.query(distinct(FOIMinistryRequest.foiministryrequestid)).filter(and_(FOIMinistryRequest.isactive == True), or_(and_(FOIMinistryRequest.assignedgroup == group),and_(FOIMinistryRequest.assignedministrygroup == group,or_(FOIMinistryRequest.requeststatusid.in_([2,7,9,8,10,11,12,13,14,16,17,18]))))).all() _requests = [] ministryrequest_schema = FOIMinistryRequestSchema() @@ -691,7 +691,7 @@ def getgroupfilters(cls, groups): FOIMinistryRequest.assignedgroup == group, and_( FOIMinistryRequest.assignedministrygroup == group, - FOIMinistryRequest.requeststatusid.in_([2,7,9,8,10,11,12,13,14,16]) + FOIMinistryRequest.requeststatusid.in_([2,7,9,8,10,11,12,13,14,16,17,18]) ) ) ) @@ -1115,7 +1115,7 @@ def advancedsearch(cls, params, userid, isministryrestrictedfilemanager = False) groupfilter.append(FOIMinistryRequest.assignedministrygroup == group) #ministry advanced search show cfr onwards - statefilter = FOIMinistryRequest.requeststatusid.in_([2,3,7,9,8,10,11,12,13,14,16]) + statefilter = FOIMinistryRequest.requeststatusid.in_([2,3,7,9,8,10,11,12,13,14,16,17,18]) ministry_queue = FOIMinistryRequest.advancedsearchsubquery(params, iaoassignee, ministryassignee, userid, 'Ministry', False, isministryrestrictedfilemanager).filter(and_(or_(*groupfilter), statefilter)) diff --git a/request-management-api/request_api/models/FOIRequestNotificationUsers.py b/request-management-api/request_api/models/FOIRequestNotificationUsers.py index 382d7ef56..e35dbda75 100644 --- a/request-management-api/request_api/models/FOIRequestNotificationUsers.py +++ b/request-management-api/request_api/models/FOIRequestNotificationUsers.py @@ -272,7 +272,7 @@ def getgroupfilters(cls, groups): FOIRequests.assignedgroup == group, and_( FOIRequests.assignedministrygroup == group, - FOIRequests.requeststatusid.in_([2,7,9,8,10,11,12,13,14]) + FOIRequests.requeststatusid.in_([2,7,9,8,10,11,12,13,14,17,18]) ) ) ) From 169e5541ff929f7b9154a86d7c18cc7b9c16730b Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Wed, 20 Sep 2023 11:13:02 -0700 Subject: [PATCH 167/234] #4362 tab color update for scan, tagging --- .../src/components/FOI/FOIRequest/TabbedContainer.scss | 4 ++-- .../src/components/FOI/customComponents/statedropdown.scss | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/forms-flow-web/src/components/FOI/FOIRequest/TabbedContainer.scss b/forms-flow-web/src/components/FOI/FOIRequest/TabbedContainer.scss index 56296e684..ab5c63f57 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/TabbedContainer.scss +++ b/forms-flow-web/src/components/FOI/FOIRequest/TabbedContainer.scss @@ -129,10 +129,10 @@ background-color: #096DD1; } .foitabheaderTaggingBG{ - background-color: #6f2852; + background-color: #9B1048; } .foitabheaderReadytoScanBG{ - background-color: #e88cb7; + background-color: #A2096C; } .foileftpanelheader diff --git a/forms-flow-web/src/components/FOI/customComponents/statedropdown.scss b/forms-flow-web/src/components/FOI/customComponents/statedropdown.scss index ac289315b..184b146e6 100644 --- a/forms-flow-web/src/components/FOI/customComponents/statedropdown.scss +++ b/forms-flow-web/src/components/FOI/customComponents/statedropdown.scss @@ -90,10 +90,12 @@ background-color: #096DD1; } + + .tagging { - background-color: #6f2852; + background-color: #9B1048; } .readytoscan{ - background-color: #e88cb7; + background-color: #A2096C; } \ No newline at end of file From c87b8788eb8a272745225f6a0cfd0f48d6d9d961 Mon Sep 17 00:00:00 2001 From: Shivakumar Date: Thu, 21 Sep 2023 09:44:52 -0700 Subject: [PATCH 168/234] Added group email to the email template --- .../ContactApplicant/util.tsx | 1 + .../email/templates/templateservice.py | 3 ++ .../services/external/keycloakadminservice.py | 32 ++++++++++++++++++- .../foirequest/requestservicegetter.py | 2 ++ 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/ContactApplicant/util.tsx b/forms-flow-web/src/components/FOI/customComponents/ContactApplicant/util.tsx index 2656d0826..887e3c27b 100644 --- a/forms-flow-web/src/components/FOI/customComponents/ContactApplicant/util.tsx +++ b/forms-flow-web/src/components/FOI/customComponents/ContactApplicant/util.tsx @@ -21,6 +21,7 @@ export const getTemplateVariables = (requestDetails: any, templateInfo: any) => {name: "{{assignedToFirstName}}", value: requestDetails.assignedToFirstName || ""}, {name: "{{assignedToLastName}}", value: requestDetails.assignedToLastName || ""}, {name: "{{assignedGroup}}", value: requestDetails.assignedGroup}, + {name: "{{groupEmail}}", value: requestDetails.assignedGroupEmail}, {name: "{{ffaurl}}", value: requestDetails.ffaurl}, ]; diff --git a/request-management-api/request_api/services/email/templates/templateservice.py b/request-management-api/request_api/services/email/templates/templateservice.py index 49e6b7faf..83ed27d80 100644 --- a/request-management-api/request_api/services/email/templates/templateservice.py +++ b/request-management-api/request_api/services/email/templates/templateservice.py @@ -8,6 +8,7 @@ import logging from flask import current_app from request_api.services.email.templates.templatefilters import init_filters +from request_api.services.external.keycloakadminservice import KeycloakAdminService init_filters() @@ -73,6 +74,7 @@ def __gettemplate(self, templatename): def __generatetemplate(self, dynamictemplatevalues, emailtemplatehtml, title): dynamictemplatevalues["ffaurl"] = current_app.config['FOI_FFA_URL'] + dynamictemplatevalues["groupEmail"] = KeycloakAdminService.getgroupdetails(dynamictemplatevalues["assignedTo"])["groupEmailAddress"] headerfooterhtml = storageservice().downloadtemplate('/TEMPLATES/EMAILS/header_footer_template.html') if(emailtemplatehtml is None): raise ValueError('No template found') @@ -83,6 +85,7 @@ def __generatetemplate(self, dynamictemplatevalues, emailtemplatehtml, title): if dynamictemplatevalues["assignedTo"] == None: dynamictemplatevalues["assignedToFirstName"] = "" dynamictemplatevalues["assignedToLastName"] = "" + dynamictemplatevalues["groupEmail"] = "" contenttemplate = Template(emailtemplatehtml) content = contenttemplate.render(dynamictemplatevalues) diff --git a/request-management-api/request_api/services/external/keycloakadminservice.py b/request-management-api/request_api/services/external/keycloakadminservice.py index 31e8b1e20..9ffe3ccc9 100644 --- a/request-management-api/request_api/services/external/keycloakadminservice.py +++ b/request-management-api/request_api/services/external/keycloakadminservice.py @@ -18,6 +18,8 @@ class KeycloakAdminService: cache_redis_url = os.getenv('CACHE_REDISURL') kctokenexpiry = os.getenv('KC_SRC_ACC_TOKEN_EXPIRY',1800) + #Constants + PRIMARY_GROUP_EMAIL_INDEX = 0 #index of the email address in the group information array def get_token(self): _accesstoken=None @@ -80,7 +82,35 @@ def getgroupsandmembers(self, allowedgroups = None): for group in allowedgroups: group["members"] = self.getgroupmembersbyid(group["id"]) return allowedgroups - + + # INPUT: groupid (string) - intake group id from the keyclock request + # OUTPUT - group information (Array of strings) e.g. [email address] + # Description: This method will fetch the group information from the keycloak server + # by passing the group id as part of the parameters. The group information contains + # the email address of the group. + def getgroupinformation(self, groupid): + if groupid is None or groupid == '': + return None + groupurl ='{0}/auth/admin/realms/{1}/groups/{2}'.format(self.keycloakhost,self.keycloakrealm,groupid) + groupresponse = requests.get(groupurl, headers=self.getheaders()) + if groupresponse.status_code == 200 and groupresponse.content != '': + return groupresponse.json() + return None + + # INPUT: groupname (string) - intake group name from the keyclock request + # OUTPUT - group information (Array of strings) e.g. [email address] + # Description: This method first extracts the group id from the group name. + # The group id is then passed as a parameter to the getgroupinformation method + # to fetch the group information. The group information contains the email address + def getgroupdetails(self, groupname): + group = self.getallgroups() + for entry in group: + if entry["name"] == groupname: + groupinfo = self.getgroupinformation(entry['id']) + if groupinfo is not None: + return groupinfo + return None + def getgroupmembersbyid(self, groupid): groupurl ='{0}/auth/admin/realms/{1}/groups/{2}/members'.format(self.keycloakhost,self.keycloakrealm,groupid) groupresponse = requests.get(groupurl, headers=self.getheaders()) diff --git a/request-management-api/request_api/services/foirequest/requestservicegetter.py b/request-management-api/request_api/services/foirequest/requestservicegetter.py index 59c516ed7..543f49dbf 100644 --- a/request-management-api/request_api/services/foirequest/requestservicegetter.py +++ b/request-management-api/request_api/services/foirequest/requestservicegetter.py @@ -14,6 +14,7 @@ from request_api.services.subjectcodeservice import subjectcodeservice from request_api.services.programareaservice import programareaservice from request_api.utils.commons.datetimehandler import datetimehandler +from request_api.services.external.keycloakadminservice import KeycloakAdminService class requestservicegetter: """ This class consolidates retrival of FOI request for actors: iao and ministry. @@ -142,6 +143,7 @@ def __preparebaseinfo(self,request,foiministryrequestid,requestministry,requestm 'receivedmodeid':request['receivedmode.receivedmodeid'], 'receivedMode':request['receivedmode.name'], 'assignedGroup': requestministry["assignedgroup"], + 'assignedGroupEmail': KeycloakAdminService().getgroupdetails(requestministry["assignedgroup"])['attributes']['groupEmailAddress'][KeycloakAdminService.PRIMARY_GROUP_EMAIL_INDEX], 'assignedTo': requestministry["assignedto"], 'idNumber':requestministry["filenumber"], 'axisRequestId': requestministry["axisrequestid"], From 1397038bcafcf7e19c9ec39471164ec1c0f2ac7f Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Thu, 21 Sep 2023 13:08:52 -0700 Subject: [PATCH 169/234] FE work for creation of divisions/sections completed. FE update divisions/sections WIP + testing + refactor --- .../Admin/Divisions/CreateDivisionModal.jsx | 19 ++- .../Admin/Divisions/CreateSectionModal.jsx | 136 ++++++++++++++++++ .../FOI/Admin/Divisions/Divisions.jsx | 29 ++++ .../models/ProgramAreaDivisions.py | 4 +- 4 files changed, 183 insertions(+), 5 deletions(-) create mode 100644 forms-flow-web/src/components/FOI/Admin/Divisions/CreateSectionModal.jsx diff --git a/forms-flow-web/src/components/FOI/Admin/Divisions/CreateDivisionModal.jsx b/forms-flow-web/src/components/FOI/Admin/Divisions/CreateDivisionModal.jsx index 5106c0978..175a5233b 100644 --- a/forms-flow-web/src/components/FOI/Admin/Divisions/CreateDivisionModal.jsx +++ b/forms-flow-web/src/components/FOI/Admin/Divisions/CreateDivisionModal.jsx @@ -16,7 +16,13 @@ const CreateDivisionModal = ({ showModal, closeModal, }) => { - const [division, setDivision] = useState(null); + const [division, setDivision] = useState({ + name: "", + programareaid: null, + issection: false, + parentid: null, + specifictopersonalrequests: null, + }); let programAreas = useSelector((state) => state.foiRequests.foiAdminProgramAreaList); const handleSave = () => { @@ -29,7 +35,13 @@ const CreateDivisionModal = ({ }; useEffect(() => { - setDivision(null); + setDivision({ + name: "", + programareaid: null, + issection: false, + parentid: null, + specifictopersonalrequests: null, + }); }, [showModal]); return ( @@ -39,6 +51,7 @@ const CreateDivisionModal = ({ - + Program Area + setSection({ ...section, programareaid: event.target.value }) + } + fullWidth + > + {programAreas && + programAreas.map((area, index) => ( + + {area.name} + + ))} + + setSection({...section, specifictopersonalrequests: !section.specifictopersonalrequests})} />} label="Specific to Personal Request" /> + + Parent Division + + + + + + + +
    + + ); +}; + +export default CreateSectionModal; \ No newline at end of file diff --git a/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx b/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx index c0f0bc051..6ed1ff238 100644 --- a/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx +++ b/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx @@ -5,6 +5,7 @@ import SearchBar from "../customComponents"; import CreateDivisionModal from "./CreateDivisionModal"; import DisableDivisionModal from "./DisableDivisionModal"; import EditDivisionModal from "./EditDivisionModal"; +import CreateSectionModal from "./CreateSectionModal"; import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; import { DataGrid } from "@mui/x-data-grid"; @@ -32,6 +33,7 @@ const Divisions = ({userDetail}) => { const [searchResults, setSearchResults] = useState(null); const [selectedDivision, setSelectedDivision] = useState(null); const [showCreateModal, setShowCreateModal] = useState(false); + const [showCreateSectionModal, setShowCreateSectionModal] = useState(false); const [showDisableModal, setShowDisableModal] = useState(false); const [showEditModal, setShowEditModal] = useState(false); const dispatch = useDispatch(); @@ -56,6 +58,9 @@ const Divisions = ({userDetail}) => { createProgramAreaDivision({ name: data.name, programareaid: data.programareaid, + issection: data.issection, + parentid: data.parentid, + specifictopersonalrequests: data.specifictopersonalrequests }, (err, res) => { if (!err && res) { @@ -164,6 +169,10 @@ const Divisions = ({userDetail}) => { setShowCreateModal(true); }; + const openCreateSectionModal = () => { + setShowCreateSectionModal(true) + } + const openDisableDivisionModal = (data) => { setSelectedDivision(data); setShowDisableModal(true); @@ -207,6 +216,13 @@ const Divisions = ({userDetail}) => { align: "center", renderCell: (params) => <>{params.value ? params.value : "-"}, }, + { + field: "specifictopersonalrequests", + headerName: "Personal Requests", + width: 150, + align: "center", + // renderCell: (params) => <>{params.value !== null ? params.value : "-"}, + }, { field: "sortorder", headerName: "Sort Order", @@ -280,6 +296,13 @@ const Divisions = ({userDetail}) => { > New Division + @@ -315,6 +338,12 @@ const Divisions = ({userDetail}) => { showModal={showCreateModal} closeModal={() => setShowCreateModal(false)} /> + setShowCreateSectionModal(false)} + /> Date: Thu, 21 Sep 2023 13:39:56 -0700 Subject: [PATCH 170/234] Update to assignedGroupEmail --- .../components/FOI/customComponents/ContactApplicant/util.tsx | 2 +- .../request_api/services/email/templates/templateservice.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/ContactApplicant/util.tsx b/forms-flow-web/src/components/FOI/customComponents/ContactApplicant/util.tsx index 887e3c27b..cb379edec 100644 --- a/forms-flow-web/src/components/FOI/customComponents/ContactApplicant/util.tsx +++ b/forms-flow-web/src/components/FOI/customComponents/ContactApplicant/util.tsx @@ -21,7 +21,7 @@ export const getTemplateVariables = (requestDetails: any, templateInfo: any) => {name: "{{assignedToFirstName}}", value: requestDetails.assignedToFirstName || ""}, {name: "{{assignedToLastName}}", value: requestDetails.assignedToLastName || ""}, {name: "{{assignedGroup}}", value: requestDetails.assignedGroup}, - {name: "{{groupEmail}}", value: requestDetails.assignedGroupEmail}, + {name: "{{assignedGroupEmail}}", value: requestDetails.assignedGroupEmail}, {name: "{{ffaurl}}", value: requestDetails.ffaurl}, ]; diff --git a/request-management-api/request_api/services/email/templates/templateservice.py b/request-management-api/request_api/services/email/templates/templateservice.py index 83ed27d80..f280b49d3 100644 --- a/request-management-api/request_api/services/email/templates/templateservice.py +++ b/request-management-api/request_api/services/email/templates/templateservice.py @@ -74,7 +74,6 @@ def __gettemplate(self, templatename): def __generatetemplate(self, dynamictemplatevalues, emailtemplatehtml, title): dynamictemplatevalues["ffaurl"] = current_app.config['FOI_FFA_URL'] - dynamictemplatevalues["groupEmail"] = KeycloakAdminService.getgroupdetails(dynamictemplatevalues["assignedTo"])["groupEmailAddress"] headerfooterhtml = storageservice().downloadtemplate('/TEMPLATES/EMAILS/header_footer_template.html') if(emailtemplatehtml is None): raise ValueError('No template found') @@ -85,7 +84,7 @@ def __generatetemplate(self, dynamictemplatevalues, emailtemplatehtml, title): if dynamictemplatevalues["assignedTo"] == None: dynamictemplatevalues["assignedToFirstName"] = "" dynamictemplatevalues["assignedToLastName"] = "" - dynamictemplatevalues["groupEmail"] = "" + dynamictemplatevalues["assignedGroupEmail"] = "" contenttemplate = Template(emailtemplatehtml) content = contenttemplate.render(dynamictemplatevalues) From 9ac61c3e872e80d7d4fbc7bb1c48180c6c1511b3 Mon Sep 17 00:00:00 2001 From: Shivakumar Date: Thu, 21 Sep 2023 15:21:16 -0700 Subject: [PATCH 171/234] Remove keycloak import --- .../request_api/services/email/templates/templateservice.py | 1 - 1 file changed, 1 deletion(-) diff --git a/request-management-api/request_api/services/email/templates/templateservice.py b/request-management-api/request_api/services/email/templates/templateservice.py index f280b49d3..ef9d64125 100644 --- a/request-management-api/request_api/services/email/templates/templateservice.py +++ b/request-management-api/request_api/services/email/templates/templateservice.py @@ -8,7 +8,6 @@ import logging from flask import current_app from request_api.services.email.templates.templatefilters import init_filters -from request_api.services.external.keycloakadminservice import KeycloakAdminService init_filters() From a0694b239f687450f7b194f81d7624f7da762af5 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Thu, 21 Sep 2023 17:23:58 -0700 Subject: [PATCH 172/234] Update division work completed. WIP testing of api call to backend from FE for update + refactor + styling adjustments --- .../Admin/Divisions/CreateSectionModal.jsx | 6 +- .../FOI/Admin/Divisions/Divisions.jsx | 5 +- .../FOI/Admin/Divisions/EditDivisionModal.jsx | 84 +++++++++++++++++-- 3 files changed, 85 insertions(+), 10 deletions(-) diff --git a/forms-flow-web/src/components/FOI/Admin/Divisions/CreateSectionModal.jsx b/forms-flow-web/src/components/FOI/Admin/Divisions/CreateSectionModal.jsx index 53887425c..dd4a3b883 100644 --- a/forms-flow-web/src/components/FOI/Admin/Divisions/CreateSectionModal.jsx +++ b/forms-flow-web/src/components/FOI/Admin/Divisions/CreateSectionModal.jsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from "react"; -import { useDispatch, useSelector } from "react-redux"; +import { useSelector } from "react-redux"; import Dialog from "@material-ui/core/Dialog"; import DialogActions from "@material-ui/core/DialogActions"; import DialogContent from "@material-ui/core/DialogContent"; @@ -31,10 +31,10 @@ const CreateSectionModal = ({ //useEffect to manage filtering of dropdown for parent divisions useEffect(() => { if (section.programareaid && section.specifictopersonalrequests) { - let filteredDivisions = divisions.filter(division => division.programareaid === section.programareaid && division.specifictopersonalrequests); + let filteredDivisions = divisions.filter(division => division.programareaid === section.programareaid && division.specifictopersonalrequests && !division.issection); return setParentDivisions(filteredDivisions); } else if (section.programareaid && !section.specifictopersonalrequests) { - let filteredDivisions = divisions.filter(division => division.programareaid === section.programareaid && !division.specifictopersonalrequests); + let filteredDivisions = divisions.filter(division => division.programareaid === section.programareaid && !division.specifictopersonalrequests && !division.issection); return setParentDivisions(filteredDivisions); } else { let filteredDivisions = divisions.filter(division => !division.issection); diff --git a/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx b/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx index 6ed1ff238..0ee26e8db 100644 --- a/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx +++ b/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx @@ -101,6 +101,9 @@ const Divisions = ({userDetail}) => { name: data.name, programareaid: data.programareaid, sortorder: data.sortorder, + issection: data.issection, + parentid: data.parentid, + specifictopersonalrequests: data.specifictopersonalrequests }, data.divisionid, (err, res) => { @@ -221,7 +224,7 @@ const Divisions = ({userDetail}) => { headerName: "Personal Requests", width: 150, align: "center", - // renderCell: (params) => <>{params.value !== null ? params.value : "-"}, + renderCell: (params) => <>{params.value !== null ? params.value : "-"}, }, { field: "sortorder", diff --git a/forms-flow-web/src/components/FOI/Admin/Divisions/EditDivisionModal.jsx b/forms-flow-web/src/components/FOI/Admin/Divisions/EditDivisionModal.jsx index 4c33face6..1007e8704 100644 --- a/forms-flow-web/src/components/FOI/Admin/Divisions/EditDivisionModal.jsx +++ b/forms-flow-web/src/components/FOI/Admin/Divisions/EditDivisionModal.jsx @@ -7,6 +7,8 @@ import InputLabel from "@material-ui/core/InputLabel"; import MenuItem from "@material-ui/core/MenuItem"; import Select from "@material-ui/core/Select"; import TextField from "@material-ui/core/TextField"; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Checkbox from '@mui/material/Checkbox'; const EditDivisionModal = ({ initialDivision, @@ -15,7 +17,16 @@ const EditDivisionModal = ({ showModal, closeModal, }) => { - const [division, setDivision] = useState(initialDivision); + const [division, setDivision] = useState({ + name: "", + programareaid: null, + issection: false, + parentid: null, + specifictopersonalrequests: null, + }); + const [parentDivisions, setParentDivisions] = useState(divisions); + + console.log(division) const handleSave = () => { saveDivision(division); @@ -25,10 +36,34 @@ const EditDivisionModal = ({ const handleClose = () => { closeModal(); }; + + const handleSectionSelection = () => { + setDivision({...division, issection: !division.issection, specifictopersonalrequests: null, parentid: null}) + } + + useEffect(() => { + setDivision(initialDivision || { + name: "", + programareaid: null, + issection: false, + parentid: null, + specifictopersonalrequests: null, + }); + }, [showModal]); + //useEffect to manage filtering of dropdown for parent divisions useEffect(() => { - setDivision(initialDivision); - }, [initialDivision, showModal]); + if (division.programareaid && division.specifictopersonalrequests) { + let filteredDivisions = divisions.filter(item => item.programareaid === division.programareaid && item.specifictopersonalrequests && item.divisionid !== division.divisionid && !division.issection); + return setParentDivisions(filteredDivisions); + } else if (division.programareaid && !division.specifictopersonalrequests) { + let filteredDivisions = divisions.filter(item => item.programareaid === division.programareaid && !item.specifictopersonalrequests && item.divisionid !== division.divisionid && !division.issection); + return setParentDivisions(filteredDivisions); + } else { + let filteredDivisions = divisions.filter(item => !item.issection && item.divisionid !== division.divisionid); + return setParentDivisions(filteredDivisions); + } + }, [division]) return ( <> @@ -36,24 +71,25 @@ const EditDivisionModal = ({ Edit Division setDivision({ ...division, name: event.target.value }) } fullWidth /> - + Program Area + setDivision({ ...division, parentid: event.target.value }) + } + fullWidth + > + {parentDivisions && + parentDivisions.map((item, index) => ( + + {item.name} + + ))} +
    From 341bb51442b5c0b5d04b017a97279495ec514151 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Mon, 25 Sep 2023 15:10:37 -0700 Subject: [PATCH 180/234] Created a migration to create new data row to hold Section 5 Pending Status --- .../migrations/versions/a79cd809e85e_.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 request-management-api/migrations/versions/a79cd809e85e_.py diff --git a/request-management-api/migrations/versions/a79cd809e85e_.py b/request-management-api/migrations/versions/a79cd809e85e_.py new file mode 100644 index 000000000..940461bf1 --- /dev/null +++ b/request-management-api/migrations/versions/a79cd809e85e_.py @@ -0,0 +1,24 @@ +"""Adding Section 5 Pending FOI Status to FOIRequestStatuses Table + +Revision ID: a79cd809e85e +Revises: b51a0f2635c1 +Create Date: 2023-09-25 14:37:29.208839 + +""" +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'a79cd809e85e' +down_revision = 'b51a0f2635c1' +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute('INSERT INTO public."FOIRequestStatuses"(requeststatusid, name, description, isactive) VALUES (19, \'Section 5 Pending\', \'Section 5 Pending (Personal)\', true);commit;') + + + +def downgrade(): + op.execute('DELETE FROM public."FOIRequestStatuses" WHERE requeststatusid = 19;commit;') From c9a7a67725a849f398731edefd8fb8232b738b93 Mon Sep 17 00:00:00 2001 From: Shivakumar Date: Mon, 25 Sep 2023 15:12:29 -0700 Subject: [PATCH 181/234] Update group email exception handling --- .../services/external/keycloakadminservice.py | 16 ++++++++++++++++ .../services/foirequest/requestservicegetter.py | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/request-management-api/request_api/services/external/keycloakadminservice.py b/request-management-api/request_api/services/external/keycloakadminservice.py index 9ffe3ccc9..5ec6af21e 100644 --- a/request-management-api/request_api/services/external/keycloakadminservice.py +++ b/request-management-api/request_api/services/external/keycloakadminservice.py @@ -82,6 +82,7 @@ def getgroupsandmembers(self, allowedgroups = None): for group in allowedgroups: group["members"] = self.getgroupmembersbyid(group["id"]) return allowedgroups + # INPUT: groupid (string) - intake group id from the keyclock request # OUTPUT - group information (Array of strings) e.g. [email address] @@ -111,6 +112,21 @@ def getgroupdetails(self, groupname): return groupinfo return None + # INPUT: groupname (string) - intake group name from the keyclock request + # OUTPUT - group email address (string) + # Description: This method calls the getgroupdetails method to fetch the group information. + # The group information contains the email address of the group. If the group information + # does not have the email address in attributes, then an empty string is returned. + def processgroupEmail(self, groupname): + try: + groupinfo = self.getgroupdetails(groupname) + if groupinfo is not None: + if "attributes" in groupinfo and "groupEmailAddress" in groupinfo["attributes"]: + return groupinfo["attributes"]["groupEmailAddress"][KeycloakAdminService.PRIMARY_GROUP_EMAIL_INDEX] + return '' + except KeyError: + return '' + def getgroupmembersbyid(self, groupid): groupurl ='{0}/auth/admin/realms/{1}/groups/{2}/members'.format(self.keycloakhost,self.keycloakrealm,groupid) groupresponse = requests.get(groupurl, headers=self.getheaders()) diff --git a/request-management-api/request_api/services/foirequest/requestservicegetter.py b/request-management-api/request_api/services/foirequest/requestservicegetter.py index 543f49dbf..e5ae2c249 100644 --- a/request-management-api/request_api/services/foirequest/requestservicegetter.py +++ b/request-management-api/request_api/services/foirequest/requestservicegetter.py @@ -143,7 +143,7 @@ def __preparebaseinfo(self,request,foiministryrequestid,requestministry,requestm 'receivedmodeid':request['receivedmode.receivedmodeid'], 'receivedMode':request['receivedmode.name'], 'assignedGroup': requestministry["assignedgroup"], - 'assignedGroupEmail': KeycloakAdminService().getgroupdetails(requestministry["assignedgroup"])['attributes']['groupEmailAddress'][KeycloakAdminService.PRIMARY_GROUP_EMAIL_INDEX], + 'assignedGroupEmail': KeycloakAdminService().processgroupEmail(requestministry["assignedgroup"]), 'assignedTo': requestministry["assignedto"], 'idNumber':requestministry["filenumber"], 'axisRequestId': requestministry["axisrequestid"], From 21a4d18d182c2e9f38a3868379104205ab864eda Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Mon, 25 Sep 2023 15:45:19 -0700 Subject: [PATCH 182/234] #4292 python 3.10.8 on main docker --- request-management-api/dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/request-management-api/dockerfile b/request-management-api/dockerfile index a94c918a0..b419472d2 100644 --- a/request-management-api/dockerfile +++ b/request-management-api/dockerfile @@ -2,7 +2,7 @@ # FROM python:3.8 # Necessary to pull images from bcgov and not hit Dockerhub quotas. -FROM artifacts.developer.gov.bc.ca/docker-remote/python:3.8.5-buster +FROM artifacts.developer.gov.bc.ca/docker-remote/python:3.10.8-buster EXPOSE 6402 # Keeps Python from generating .pyc files in the container From 72c3070145e5e12324169116dbe7152f0e131880 Mon Sep 17 00:00:00 2001 From: Richard Qi Date: Mon, 25 Sep 2023 16:26:51 -0700 Subject: [PATCH 183/234] Remove debugging code --- forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx | 1 - .../FOI/customComponents/Attachments/AttachmentModal.js | 1 - 2 files changed, 2 deletions(-) diff --git a/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx b/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx index f6ec06ed0..11a4247cb 100644 --- a/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx +++ b/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx @@ -197,7 +197,6 @@ const Divisions = ({userDetail}) => { if (divisionObj.programareaid && !divisionObj.specifictopersonalrequests) { return filteredDivisions.filter(division => division.programareaid === divisionObj.programareaid && !division.specifictopersonalrequests); } - console.log(filteredDivisions) return filteredDivisions; } diff --git a/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js b/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js index 77d88c3b9..d48e3330a 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js +++ b/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js @@ -201,7 +201,6 @@ export default function AttachmentModal({ if (modalFor === 'replace' || modalFor === "replaceattachment") { fileStatusTransition = attachment?.category; } else if (uploadFor === "record") { - console.log("divisions", divisions); if(bcgovcode == "MCF" && requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL) { fileStatusTransition = MCFSections?.sections?.find(division => division.divisionid === tagValue).name; } else if(bcgovcode == "MSD" && requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL) { From 560dd8cf091f8f10ca615d9f53cb9b92ce8b1d6e Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Mon, 25 Sep 2023 16:41:13 -0700 Subject: [PATCH 184/234] #4446 gitignore for datamigrations --- .gitignore | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.gitignore b/.gitignore index cc7aa8897..895e25840 100644 --- a/.gitignore +++ b/.gitignore @@ -92,3 +92,21 @@ axisintegrationapi/MCS.FOI.AXISIntegration/MCS.FOI.AXISIntegrationWebAPI/*.user axisintegrationapi/MCS.FOI.AXISIntegration/MCS.FOI.AXISIntegrationWebAPI/Properties/PublishProfiles/FolderProfile.pubxml axisintegrationapi/MCS.FOI.AXISIntegration/MCS.FOI.AXISIntegrationWebAPI/Properties/PublishProfiles/FolderProfile.pubxml.user apps/ + +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/*/bin/* +datamigrations/FOIMOD.CFD.DocMigration.AXIS.DAL/*/bin/* +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/.vs/* +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/obj/* +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/bin/* +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/obj/* + +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/obj/* +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/obj/* +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.S3Uploader/obj/* +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/obj/* +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/obj/* +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/obj/* +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/obj/* +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/Debug/* +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.Tests/obj/* +datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/appsettings.dev.json From 6360b2b5bbfaabc469580706be5d94da124e149f Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Mon, 25 Sep 2023 16:48:49 -0700 Subject: [PATCH 185/234] FE Work wIP --- .../FOI/customComponents/ConfirmationModal/util.js | 2 ++ forms-flow-web/src/constants/FOI/statusEnum.js | 8 +++++--- request-management-api/request_api/resources/request.py | 3 +-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js index 5343aa99b..5e558b080 100644 --- a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js +++ b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js @@ -56,6 +56,8 @@ import { getFullnameList } from "../../../../helper/FOI/helper"; return {title: "Close Request", body: ""}; case StateEnum.redirect.name.toLowerCase(): return {title: "Redirect Request", body: "Are you sure you want to Redirect this request?"}; + case StateEnum.section5Pending.name.toLowerCase(): + return {title: "Changing the state", body: "Are you sure you want to change the state to Section 5 Pending?"} case StateEnum.callforrecords.name.toLowerCase(): return {title: "Changing the state", body: `Are you sure you want to change Request #${_requestNumber} to ${StateEnum.callforrecords.name}?`}; case StateEnum.review.name.toLowerCase(): diff --git a/forms-flow-web/src/constants/FOI/statusEnum.js b/forms-flow-web/src/constants/FOI/statusEnum.js index 7a42ce6e2..a3c4ed622 100644 --- a/forms-flow-web/src/constants/FOI/statusEnum.js +++ b/forms-flow-web/src/constants/FOI/statusEnum.js @@ -1,6 +1,6 @@ const StateList = Object.freeze({ unopened: [{status: "Unopened", isSelected: false}, {status:"Intake in Progress", isSelected: false}], - intakeinprogress: [{status:"Intake in Progress", isSelected: false}, {status: "Open", isSelected: false}, {status:"Peer Review", isSelected: false}, {status: "Redirect", isSelected: false}, {status: "Closed", isSelected: false}], + intakeinprogress: [{status:"Intake in Progress", isSelected: false}, {status: "Open", isSelected: false}, {status:"Peer Review", isSelected: false}, {status: "Redirect", isSelected: false}, {status: "Section 5 Pending", isSelected: false}, {status: "Closed", isSelected: false}], redirect: [{status: "Redirect", isSelected: false}, {status:"Intake in Progress", isSelected: false}, {status: "Closed", isSelected: false}], open: [{status: "Open", isSelected: false}, {status: "Call For Records", isSelected: false}, {status:"Peer Review", isSelected: false},{status: "Closed", isSelected: false}], callforrecords: [{status: "Call For Records", isSelected: false}, {status: "Open", isSelected: false}, {status: "Closed", isSelected: false}], @@ -15,7 +15,8 @@ const StateList = Object.freeze({ response: [{status: "Response", isSelected: false}, {status: "On Hold", isSelected: false}, {status: "Records Review", isSelected: false}, {status:"Peer Review", isSelected: false}, {status: "Closed", isSelected: false}], responseforpersonal: [{status: "Response", isSelected: false}, {status: "Records Review", isSelected: false}, {status:"Peer Review", isSelected: false}, {status: "Closed", isSelected: false}], //peerreview: [{status:"Peer Review", isSelected: false},{status:"Intake in Progress", isSelected: false}, {status: "Open", isSelected: false},{status: "Records Review", isSelected: false},{status: "Consult", isSelected: false},{status: "Response", isSelected: false}], - peerreview: [{status:"Peer Review", isSelected: false}] + peerreview: [{status:"Peer Review", isSelected: false}], + section5Pending: [{status: "Section 5 Pending", isSelected: false}, {status: "Open", isSelected: false}, {status:"Peer Review", isSelected: false}, {status: "Redirect", isSelected: false}, {status: "Closed", isSelected: false}], }); const MinistryStateList = Object.freeze({ @@ -54,7 +55,8 @@ const StateEnum = Object.freeze({ harms: {name: "Harms Assessment", id: 13}, response: {name: "Response", id: 14}, archived: {name: "Archived", id: 15}, - peerreview: {name: "Peer Review", id: 16} + peerreview: {name: "Peer Review", id: 16}, + section5Pending: {name: "Section 5 Pending", id: 19} }); const StateTransitionCategories = Object.freeze({ diff --git a/request-management-api/request_api/resources/request.py b/request-management-api/request_api/resources/request.py index 7c76f84df..2bc5089f8 100644 --- a/request-management-api/request_api/resources/request.py +++ b/request-management-api/request_api/resources/request.py @@ -94,8 +94,7 @@ def post(requestid=None, actiontype=None): assigneelastname = requestdata['assigneelastname'] if int(requestid) and str(requestid) != "-1" : - status = rawrequestservice().getstatus(updaterequest) - rawrequest = rawrequestservice().getrawrequest(requestid) + status = rawrequestservice().getstatus(updaterequest) result = rawrequestservice().saverawrequestversion(updaterequest,requestid,assigneegroup,assignee,status,AuthHelper.getuserid(),assigneefirstname,assigneemiddlename,assigneelastname, actiontype) assignee = '' if(actiontype == 'assignee'): From ddc8894c0a439eace18e3a362e9f9f9f283ae800 Mon Sep 17 00:00:00 2001 From: Milos Despotovic Date: Tue, 26 Sep 2023 11:57:23 -0700 Subject: [PATCH 186/234] Add On-Hold - Application Fee state --- .../components/FOI/FOIRequest/BottomButtonGroup/index.js | 1 + .../FOI/customComponents/ConfirmationModal/util.js | 2 ++ .../src/components/FOI/customComponents/StateDropDown.js | 2 ++ .../components/FOI/customComponents/statedropdown.scss | 4 ++++ forms-flow-web/src/constants/FOI/statusEnum.js | 9 ++++++--- 5 files changed, 15 insertions(+), 3 deletions(-) diff --git a/forms-flow-web/src/components/FOI/FOIRequest/BottomButtonGroup/index.js b/forms-flow-web/src/components/FOI/FOIRequest/BottomButtonGroup/index.js index 466f640b7..341800097 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/BottomButtonGroup/index.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/BottomButtonGroup/index.js @@ -321,6 +321,7 @@ const BottomButtonGroup = React.memo( case StateEnum.harms.name: case StateEnum.response.name: case StateEnum.peerreview.name: + case StateEnum.onholdapplicationfee.name: const status = Object.values(StateEnum).find( (statusValue) => statusValue.name === currentSelectedStatus ); diff --git a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js index 5343aa99b..77305318f 100644 --- a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js +++ b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js @@ -120,6 +120,8 @@ import { getFullnameList } from "../../../../helper/FOI/helper"; return {title: "Ministry Sign Off", body: `Upload eApproval Logs, and enter in required approval fields to verify Ministry Approval and then change the state.`}; else return {title: "Changing the state", body: `Are you sure you want to change Request #${_requestNumber} to ${StateEnum.response.name}?`}; + case StateEnum.onholdapplicationfee.name.toLowerCase(): + return {title: "Changing the state", body: `Are you sure you want to change Request #${_requestNumber} to ${StateEnum.onholdapplicationfee.name}?`}; default: return {title: "", body: ""}; } diff --git a/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js b/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js index 4a1e4b315..24dffeecd 100644 --- a/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js +++ b/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js @@ -154,6 +154,8 @@ const StateDropDown = ({ else { return _stateList.response.filter(val => val.status.toLowerCase() !== StateEnum.onhold.name.toLowerCase()); } + case StateEnum.onholdapplicationfee.name.toLowerCase(): + return _stateList.onholdapplicationfee; default: return []; diff --git a/forms-flow-web/src/components/FOI/customComponents/statedropdown.scss b/forms-flow-web/src/components/FOI/customComponents/statedropdown.scss index f21192490..14f113fe5 100644 --- a/forms-flow-web/src/components/FOI/customComponents/statedropdown.scss +++ b/forms-flow-web/src/components/FOI/customComponents/statedropdown.scss @@ -62,6 +62,10 @@ background-color: #595959; } +.on-hold-applicationfee { + background-color: #8C3601; +} + .harmsassessment { background-color: #832AB7; } diff --git a/forms-flow-web/src/constants/FOI/statusEnum.js b/forms-flow-web/src/constants/FOI/statusEnum.js index 7a42ce6e2..ef8947d85 100644 --- a/forms-flow-web/src/constants/FOI/statusEnum.js +++ b/forms-flow-web/src/constants/FOI/statusEnum.js @@ -1,6 +1,6 @@ const StateList = Object.freeze({ unopened: [{status: "Unopened", isSelected: false}, {status:"Intake in Progress", isSelected: false}], - intakeinprogress: [{status:"Intake in Progress", isSelected: false}, {status: "Open", isSelected: false}, {status:"Peer Review", isSelected: false}, {status: "Redirect", isSelected: false}, {status: "Closed", isSelected: false}], + intakeinprogress: [{status:"Intake in Progress", isSelected: false}, {status: "Open", isSelected: false}, {status:"Peer Review", isSelected: false}, {status: "Redirect", isSelected: false}, {status: "On-Hold - Application Fee", isSelected: false}, {status: "Closed", isSelected: false}], redirect: [{status: "Redirect", isSelected: false}, {status:"Intake in Progress", isSelected: false}, {status: "Closed", isSelected: false}], open: [{status: "Open", isSelected: false}, {status: "Call For Records", isSelected: false}, {status:"Peer Review", isSelected: false},{status: "Closed", isSelected: false}], callforrecords: [{status: "Call For Records", isSelected: false}, {status: "Open", isSelected: false}, {status: "Closed", isSelected: false}], @@ -15,7 +15,8 @@ const StateList = Object.freeze({ response: [{status: "Response", isSelected: false}, {status: "On Hold", isSelected: false}, {status: "Records Review", isSelected: false}, {status:"Peer Review", isSelected: false}, {status: "Closed", isSelected: false}], responseforpersonal: [{status: "Response", isSelected: false}, {status: "Records Review", isSelected: false}, {status:"Peer Review", isSelected: false}, {status: "Closed", isSelected: false}], //peerreview: [{status:"Peer Review", isSelected: false},{status:"Intake in Progress", isSelected: false}, {status: "Open", isSelected: false},{status: "Records Review", isSelected: false},{status: "Consult", isSelected: false},{status: "Response", isSelected: false}], - peerreview: [{status:"Peer Review", isSelected: false}] + peerreview: [{status:"Peer Review", isSelected: false}], + onholdapplicationfee: [{status: "On-Hold - Application Fee", isSelected: false}, {status: "Open", isSelected: false}, {status:"Peer Review", isSelected: false}, {status: "Redirect", isSelected: false}, {status: "Closed", isSelected: false}] }); const MinistryStateList = Object.freeze({ @@ -37,6 +38,7 @@ const MinistryStateList = Object.freeze({ peerreview: [{status: "Peer Review", isSelected: false}], }); +// This corresponds to rows in the FOIRequestStatuses table on the backend const StateEnum = Object.freeze({ open: {name: "Open", id: 1}, callforrecords: {name: "Call For Records", id: 2}, @@ -54,7 +56,8 @@ const StateEnum = Object.freeze({ harms: {name: "Harms Assessment", id: 13}, response: {name: "Response", id: 14}, archived: {name: "Archived", id: 15}, - peerreview: {name: "Peer Review", id: 16} + peerreview: {name: "Peer Review", id: 16}, + onholdapplicationfee: {name: "On-Hold - Application Fee", id: 18} }); const StateTransitionCategories = Object.freeze({ From 2422819f818af1238ad040a96a9a40e5e455356e Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Tue, 26 Sep 2023 12:11:33 -0700 Subject: [PATCH 187/234] conditonals adjusted so minsitry signoff only required if current state is ministry sign off and next state is response --- .../FOI/customComponents/ConfirmationModal/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/index.js b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/index.js index 40de92363..a0ffb50ff 100644 --- a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/index.js @@ -123,7 +123,7 @@ export default function ConfirmationModal({requestId, openModal, handleModal, st || (state.toLowerCase() === StateEnum.onhold.name.toLowerCase() && amountDue === 0) || (state.toLowerCase() === StateEnum.onhold.name.toLowerCase() && cfrStatus !== 'approved') || (state.toLowerCase() === StateEnum.onhold.name.toLowerCase() && cfrStatus === 'approved' && !saveRequestObject.email && !mailed) - || (state.toLowerCase() === StateEnum.response.name.toLowerCase() && !(ministryApprovalState?.approverName && ministryApprovalState?.approvedDate && ministryApprovalState?.approverTitle)) + || ((state.toLowerCase() === StateEnum.response.name.toLowerCase() && currentState?.toLowerCase() === StateEnum.signoff.name.toLowerCase()) && !(ministryApprovalState?.approverName && ministryApprovalState?.approvedDate && ministryApprovalState?.approverTitle)) || ((state.toLowerCase() === StateEnum.deduplication.name.toLowerCase() || state.toLowerCase() === StateEnum.review.name.toLowerCase()) && !allowStateChange)) { return true; @@ -226,7 +226,7 @@ export default function ConfirmationModal({requestId, openModal, handleModal, st
    ); } - else if (currentState?.toLowerCase() !== StateEnum.closed.name.toLowerCase() && (state.toLowerCase() === StateEnum.response.name.toLowerCase() && saveRequestObject.requeststatusid === StateEnum.signoff.id)) { + else if (currentState?.toLowerCase() === StateEnum.signoff.name.toLowerCase() && state.toLowerCase() === StateEnum.response.name.toLowerCase() && saveRequestObject.requeststatusid === StateEnum.signoff.id) { return (
    From b523e67ff964404bb6a6bee17cd63f96b0f9601d Mon Sep 17 00:00:00 2001 From: Milos Despotovic Date: Tue, 26 Sep 2023 14:00:19 -0700 Subject: [PATCH 188/234] Add migration for On-Hold - Application Fee row --- ...7_add_on_hold_application_fee_state_to_.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 request-management-api/migrations/versions/1491b3126887_add_on_hold_application_fee_state_to_.py diff --git a/request-management-api/migrations/versions/1491b3126887_add_on_hold_application_fee_state_to_.py b/request-management-api/migrations/versions/1491b3126887_add_on_hold_application_fee_state_to_.py new file mode 100644 index 000000000..368532898 --- /dev/null +++ b/request-management-api/migrations/versions/1491b3126887_add_on_hold_application_fee_state_to_.py @@ -0,0 +1,28 @@ +"""Add On-Hold Application-Fee state to FOIRequestStatuses + +Revision ID: 1491b3126887 +Revises: aacdbca19a47 +Create Date: 2023-09-26 12:46:12.187207 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '1491b3126887' +down_revision = 'aacdbca19a47' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + sql = '''INSERT INTO "FOIRequestStatuses" (requeststatusid, name, description, isactive) VALUES (19, 'On-Hold - Application Fee', 'On Hold for Application Fee', true)''' + op.execute(sql) + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + sql = '''DELETE FROM "FOIRequestStatuses" WHERE requeststatusid = 19''' + op.execute(sql) \ No newline at end of file From f20eabbf24f941ef7de82ab4df8f0360994c22a2 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Tue, 26 Sep 2023 14:11:52 -0700 Subject: [PATCH 189/234] #4446 python upgrade 10.2 marshal api --- request-management-api/dockerfile | 2 +- request-management-api/dockerfile.local | 2 +- request-management-api/requirements.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/request-management-api/dockerfile b/request-management-api/dockerfile index a94c918a0..b419472d2 100644 --- a/request-management-api/dockerfile +++ b/request-management-api/dockerfile @@ -2,7 +2,7 @@ # FROM python:3.8 # Necessary to pull images from bcgov and not hit Dockerhub quotas. -FROM artifacts.developer.gov.bc.ca/docker-remote/python:3.8.5-buster +FROM artifacts.developer.gov.bc.ca/docker-remote/python:3.10.8-buster EXPOSE 6402 # Keeps Python from generating .pyc files in the container diff --git a/request-management-api/dockerfile.local b/request-management-api/dockerfile.local index da79e4624..a3f3052a8 100644 --- a/request-management-api/dockerfile.local +++ b/request-management-api/dockerfile.local @@ -1,5 +1,5 @@ # For more information, please refer to https://aka.ms/vscode-docker-python -FROM python:3.8 +FROM python:3.10.8 EXPOSE 6402 # Keeps Python from generating .pyc files in the container diff --git a/request-management-api/requirements.txt b/request-management-api/requirements.txt index a0bf246b8..abc1dec44 100644 --- a/request-management-api/requirements.txt +++ b/request-management-api/requirements.txt @@ -67,7 +67,7 @@ aws-requests-auth==0.4.3 holidays==0.12 Flask-SocketIO==5.1.0 Flask-Login==0.5.0 -eventlet==0.31.0 +eventlet==0.33.3 uritemplate.py==3.0.2 urllib3==1.26.15 ndg-httpsclient From bbe2b4707ef2640b7761a31a737fe753e7c225db Mon Sep 17 00:00:00 2001 From: Milos Despotovic Date: Tue, 26 Sep 2023 14:27:53 -0700 Subject: [PATCH 190/234] Update css for onholdapplicationfee --- .../src/components/FOI/FOIRequest/TabbedContainer.scss | 2 +- forms-flow-web/src/components/FOI/FOIRequest/utils.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/forms-flow-web/src/components/FOI/FOIRequest/TabbedContainer.scss b/forms-flow-web/src/components/FOI/FOIRequest/TabbedContainer.scss index ab5c63f57..7e7a2eeb2 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/TabbedContainer.scss +++ b/forms-flow-web/src/components/FOI/FOIRequest/TabbedContainer.scss @@ -69,7 +69,7 @@ background-color: #C45303; } - .foitabheaderIntakeInProgressBG { + .foitabheaderIntakeInProgressBG, .foitabheaderOnHoldApplicationFeeBG { background-color: #8C3601; } diff --git a/forms-flow-web/src/components/FOI/FOIRequest/utils.js b/forms-flow-web/src/components/FOI/FOIRequest/utils.js index eec51e3d8..6efb1b8d2 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/utils.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/utils.js @@ -133,6 +133,8 @@ export const getTabBG = (_tabStatus, _requestState) => { return "foitabheadercollection foitabheaderTaggingBG"; case StateEnum.readytoscan.name: return "foitabheadercollection foitabheaderReadytoScanBG"; + case StateEnum.onholdapplicationfee.name: + return "foitabheadercollection foitabheaderOnHoldApplicationFeeBG"; default: return "foitabheadercollection foitabheaderdefaultBG"; } From bde75b49374466f45a6d39b8f516f4822c3a04d8 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Tue, 26 Sep 2023 16:32:50 -0700 Subject: [PATCH 191/234] Adjusted divison disable so that parent divisions cannot be deleted if exists as parent for section --- .../src/components/FOI/Admin/Divisions/Divisions.jsx | 1 - .../request_api/models/ProgramAreaDivisions.py | 4 ++-- .../request_api/schemas/foiprogramareadivision.py | 9 +-------- .../services/programareadivisionservice.py | 12 +++++++----- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx b/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx index f6ec06ed0..11a4247cb 100644 --- a/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx +++ b/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx @@ -197,7 +197,6 @@ const Divisions = ({userDetail}) => { if (divisionObj.programareaid && !divisionObj.specifictopersonalrequests) { return filteredDivisions.filter(division => division.programareaid === divisionObj.programareaid && !division.specifictopersonalrequests); } - console.log(filteredDivisions) return filteredDivisions; } diff --git a/request-management-api/request_api/models/ProgramAreaDivisions.py b/request-management-api/request_api/models/ProgramAreaDivisions.py index 187d4af92..113b340e1 100644 --- a/request-management-api/request_api/models/ProgramAreaDivisions.py +++ b/request-management-api/request_api/models/ProgramAreaDivisions.py @@ -115,9 +115,9 @@ def getdivisionbynameandprogramarea(cls, programareadivision): return division_schema.dump(division) @classmethod - def getparentdivisionforsection(cls, divisionparentid): + def getparentdivisions(cls, divisonid): division_schema = ProgramAreaDivisionSchema(many=True) - query = db.session.query(ProgramAreaDivision).filter_by(divisionid=divisionparentid, issection=False, isactive=True).all() + query = db.session.query(ProgramAreaDivision).filter_by(parentid=divisonid, issection=True, isactive=True).all() return division_schema.dump(query) class ProgramAreaDivisionSchema(ma.Schema): diff --git a/request-management-api/request_api/schemas/foiprogramareadivision.py b/request-management-api/request_api/schemas/foiprogramareadivision.py index e141cf409..659d3417f 100644 --- a/request-management-api/request_api/schemas/foiprogramareadivision.py +++ b/request-management-api/request_api/schemas/foiprogramareadivision.py @@ -1,5 +1,4 @@ from marshmallow import EXCLUDE, Schema, fields, validates_schema, ValidationError -from request_api.services.programareadivisionservice import programareadivisionservice class FOIProgramAreaDivisionSchema(Schema): class Meta: # pylint: disable=too-few-public-methods @@ -12,10 +11,4 @@ class Meta: # pylint: disable=too-few-public-methods sortorder = fields.Int(data_key="sortorder",allow_none=True) issection = fields.Bool(data_key="issection", allow_none=False) parentid = fields.Int(data_key="parentid", allow_none=True) - specifictopersonalrequests = fields.Bool(data_key="specifictopersonalrequests", allow_none=True) - - @validates_schema - def validate_parentid(self, data, **kwargs): - parentdivision = programareadivisionservice().getparentdivisionforsection(data["parentid"]) - if data["parentid"] is not None and len(parentdivision) <= 0: - raise ValidationError("parentid provided does not return a valid parent division") \ No newline at end of file + specifictopersonalrequests = fields.Bool(data_key="specifictopersonalrequests", allow_none=True) \ No newline at end of file diff --git a/request-management-api/request_api/services/programareadivisionservice.py b/request-management-api/request_api/services/programareadivisionservice.py index 51695b513..a01da0f74 100644 --- a/request-management-api/request_api/services/programareadivisionservice.py +++ b/request-management-api/request_api/services/programareadivisionservice.py @@ -33,14 +33,16 @@ def disableprogramareadivision(self, divisionid,userid): """ # Validation to see if division id exists in any records. If so deletion cannot be completed. records = recordservice().get_all_records_by_divisionid(divisionid) - if len(records) > 0: - return DefaultMethodResult(False,'Division is currently tagged to various records and cannot be disabled', divisionid) + parentdivisons = ProgramAreaDivision.getparentdivisions(divisionid) + print(parentdivisons) + if len(records) > 0 or len(parentdivisons) > 0: + return DefaultMethodResult(False,'Division is currently tagged to various records or sections and cannot be disabled', divisionid) return ProgramAreaDivision.disableprogramareadivision(divisionid,userid) - def getparentdivisionforsection(self, divisionparentid): - """ Returns the parent division associated with a section + def getparentdivisions(self, divisionid): + """ Returns all parent division for a given divisionid """ - return ProgramAreaDivision.getparentdivisionforsection(divisionparentid) + return ProgramAreaDivision.getparentdivisions(divisionid) def __prepareprogramareas(self, data): """ Join program area name with division on programareaid From d1dc62b00b243e405e47cbe06a1ee26de48ea79e Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Tue, 26 Sep 2023 16:47:56 -0700 Subject: [PATCH 192/234] Adjusted migration file --- request-management-api/migrations/versions/a79cd809e85e_.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/request-management-api/migrations/versions/a79cd809e85e_.py b/request-management-api/migrations/versions/a79cd809e85e_.py index 940461bf1..8556cf373 100644 --- a/request-management-api/migrations/versions/a79cd809e85e_.py +++ b/request-management-api/migrations/versions/a79cd809e85e_.py @@ -1,7 +1,7 @@ """Adding Section 5 Pending FOI Status to FOIRequestStatuses Table Revision ID: a79cd809e85e -Revises: b51a0f2635c1 +Revises: aacdbca19a47 Create Date: 2023-09-25 14:37:29.208839 """ @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. revision = 'a79cd809e85e' -down_revision = 'b51a0f2635c1' +down_revision = 'aacdbca19a47' branch_labels = None depends_on = None From bf7519ea5d1159774a5c1633cafd189935180bc4 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Tue, 26 Sep 2023 17:03:28 -0700 Subject: [PATCH 193/234] Adjusted disable logic so that parent divisions cannot be deleted if they are parentid of sections --- .../src/components/FOI/Admin/Divisions/Divisions.jsx | 2 +- .../request_api/models/ProgramAreaDivisions.py | 2 +- .../services/programareadivisionservice.py | 11 +++++------ 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx b/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx index 11a4247cb..bb4052e2c 100644 --- a/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx +++ b/forms-flow-web/src/components/FOI/Admin/Divisions/Divisions.jsx @@ -152,7 +152,7 @@ const Divisions = ({userDetail}) => { }); } else { toast.error( - "Unable to disable division. Please try again in a few minutes and ensure that the division is not tagged to any FOI Request records.", + "Unable to disable division. Please try again in a few minutes and ensure that the division is not associated to any FOI Request records or sections.", { position: "top-right", autoClose: 4500, diff --git a/request-management-api/request_api/models/ProgramAreaDivisions.py b/request-management-api/request_api/models/ProgramAreaDivisions.py index 113b340e1..16c535135 100644 --- a/request-management-api/request_api/models/ProgramAreaDivisions.py +++ b/request-management-api/request_api/models/ProgramAreaDivisions.py @@ -115,7 +115,7 @@ def getdivisionbynameandprogramarea(cls, programareadivision): return division_schema.dump(division) @classmethod - def getparentdivisions(cls, divisonid): + def getchilddivisions(cls, divisonid): division_schema = ProgramAreaDivisionSchema(many=True) query = db.session.query(ProgramAreaDivision).filter_by(parentid=divisonid, issection=True, isactive=True).all() return division_schema.dump(query) diff --git a/request-management-api/request_api/services/programareadivisionservice.py b/request-management-api/request_api/services/programareadivisionservice.py index a01da0f74..c3b07c505 100644 --- a/request-management-api/request_api/services/programareadivisionservice.py +++ b/request-management-api/request_api/services/programareadivisionservice.py @@ -33,16 +33,15 @@ def disableprogramareadivision(self, divisionid,userid): """ # Validation to see if division id exists in any records. If so deletion cannot be completed. records = recordservice().get_all_records_by_divisionid(divisionid) - parentdivisons = ProgramAreaDivision.getparentdivisions(divisionid) - print(parentdivisons) - if len(records) > 0 or len(parentdivisons) > 0: + childdivisions = ProgramAreaDivision.getchilddivisions(divisionid) + if len(records) > 0 or len(childdivisions) > 0: return DefaultMethodResult(False,'Division is currently tagged to various records or sections and cannot be disabled', divisionid) return ProgramAreaDivision.disableprogramareadivision(divisionid,userid) - def getparentdivisions(self, divisionid): - """ Returns all parent division for a given divisionid + def getchilddivisions(self, divisionid): + """ Returns all child divisions/sections for a given divisionid """ - return ProgramAreaDivision.getparentdivisions(divisionid) + return ProgramAreaDivision.getchilddivisions(divisionid) def __prepareprogramareas(self, data): """ Join program area name with division on programareaid From 0c33a99ae9e46118ede28d5fa351726cd25405ac Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Tue, 26 Sep 2023 18:11:22 -0700 Subject: [PATCH 194/234] Adjustments made to FE to use Section 5 Pending State. WIP Testing --- .../FOI/FOIRequest/BottomButtonGroup/index.js | 1 + .../components/FOI/FOIRequest/TabbedContainer.scss | 2 +- .../src/components/FOI/FOIRequest/utils.js | 4 +++- .../FOI/customComponents/ConfirmationModal/util.js | 4 ++-- .../FOI/customComponents/StateDropDown.js | 13 +++++++++++-- .../FOI/customComponents/statedropdown.scss | 2 +- forms-flow-web/src/constants/FOI/statusEnum.js | 7 ++++--- 7 files changed, 23 insertions(+), 10 deletions(-) diff --git a/forms-flow-web/src/components/FOI/FOIRequest/BottomButtonGroup/index.js b/forms-flow-web/src/components/FOI/FOIRequest/BottomButtonGroup/index.js index 00eb20ab9..cf2ecd168 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/BottomButtonGroup/index.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/BottomButtonGroup/index.js @@ -323,6 +323,7 @@ const BottomButtonGroup = React.memo( case StateEnum.tagging.name: case StateEnum.readytoscan.name: case StateEnum.peerreview.name: + case StateEnum.section5pending.name: const status = Object.values(StateEnum).find( (statusValue) => statusValue.name === currentSelectedStatus ); diff --git a/forms-flow-web/src/components/FOI/FOIRequest/TabbedContainer.scss b/forms-flow-web/src/components/FOI/FOIRequest/TabbedContainer.scss index ab5c63f57..33dac36f3 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/TabbedContainer.scss +++ b/forms-flow-web/src/components/FOI/FOIRequest/TabbedContainer.scss @@ -69,7 +69,7 @@ background-color: #C45303; } - .foitabheaderIntakeInProgressBG { + .foitabheaderIntakeInProgressBG, .foitabheaderSection5Pending { background-color: #8C3601; } diff --git a/forms-flow-web/src/components/FOI/FOIRequest/utils.js b/forms-flow-web/src/components/FOI/FOIRequest/utils.js index eec51e3d8..1fa71ef0e 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/utils.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/utils.js @@ -132,7 +132,9 @@ export const getTabBG = (_tabStatus, _requestState) => { case StateEnum.tagging.name: return "foitabheadercollection foitabheaderTaggingBG"; case StateEnum.readytoscan.name: - return "foitabheadercollection foitabheaderReadytoScanBG"; + return "foitabheadercollection foitabheaderReadytoScanBG"; + case StateEnum.section5pending.name: + return "foitabheadercollection foitabheaderSection5Pending"; default: return "foitabheadercollection foitabheaderdefaultBG"; } diff --git a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js index 3ed3fa7df..dd20989bd 100644 --- a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js +++ b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js @@ -60,8 +60,8 @@ import { getFullnameList } from "../../../../helper/FOI/helper"; return {title: "Close Request", body: ""}; case StateEnum.redirect.name.toLowerCase(): return {title: "Redirect Request", body: "Are you sure you want to Redirect this request?"}; - case StateEnum.section5Pending.name.toLowerCase(): - return {title: "Changing the state", body: "Are you sure you want to change the state to Section 5 Pending?"} + case StateEnum.section5pending.name.toLowerCase(): + return {title: "Changing the state", body: `Are you sure you want to change Request #${_requestNumber} to ${StateEnum.section5pending.name}?`}; case StateEnum.callforrecords.name.toLowerCase(): return {title: "Changing the state", body: `Are you sure you want to change Request #${_requestNumber} to ${StateEnum.callforrecords.name}?`}; case StateEnum.review.name.toLowerCase(): diff --git a/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js b/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js index bdc953935..7297f326a 100644 --- a/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js +++ b/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js @@ -101,7 +101,11 @@ const StateDropDown = ({ case StateEnum.unopened.name.toLowerCase(): return _stateList.unopened; case StateEnum.intakeinprogress.name.toLowerCase(): - return _stateList.intakeinprogress; + if (personalIAO) { + return _stateList.intakeinprogressforpersonals; + } else { + return _stateList.intakeinprogress; + } case StateEnum.peerreview.name.toLowerCase(): if(!isMinistryCoordinator){ //const currentStatusVersion = stateTransition[0]?.version; @@ -164,7 +168,12 @@ const StateDropDown = ({ else { return _stateList.response.filter(val => val.status.toLowerCase() !== StateEnum.onhold.name.toLowerCase()); } - + case StateEnum.section5pending.name.toLowerCase(): + if (personalIAO) { + return _stateList.section5pending; + } + break + default: return []; } diff --git a/forms-flow-web/src/components/FOI/customComponents/statedropdown.scss b/forms-flow-web/src/components/FOI/customComponents/statedropdown.scss index 184b146e6..a2538dc0d 100644 --- a/forms-flow-web/src/components/FOI/customComponents/statedropdown.scss +++ b/forms-flow-web/src/components/FOI/customComponents/statedropdown.scss @@ -34,7 +34,7 @@ background-color: #C45303; } -.intakeinprogress { +.intakeinprogress, .section5pending { background-color: #8C3601; } diff --git a/forms-flow-web/src/constants/FOI/statusEnum.js b/forms-flow-web/src/constants/FOI/statusEnum.js index 199477f5c..9ad9dd67a 100644 --- a/forms-flow-web/src/constants/FOI/statusEnum.js +++ b/forms-flow-web/src/constants/FOI/statusEnum.js @@ -1,6 +1,7 @@ const StateList = Object.freeze({ unopened: [{status: "Unopened", isSelected: false}, {status:"Intake in Progress", isSelected: false}], - intakeinprogress: [{status:"Intake in Progress", isSelected: false}, {status: "Open", isSelected: false}, {status:"Peer Review", isSelected: false}, {status: "Redirect", isSelected: false}, {status: "Section 5 Pending", isSelected: false}, {status: "Closed", isSelected: false}], + intakeinprogress: [{status:"Intake in Progress", isSelected: false}, {status: "Open", isSelected: false}, {status:"Peer Review", isSelected: false}, {status: "Redirect", isSelected: false}, {status: "Closed", isSelected: false}], + intakeinprogressforpersonals: [{status:"Intake in Progress", isSelected: false}, {status: "Open", isSelected: false}, {status:"Peer Review", isSelected: false}, {status: "Redirect", isSelected: false}, {status: "Section 5 Pending", isSelected: false}, {status: "Closed", isSelected: false}], redirect: [{status: "Redirect", isSelected: false}, {status:"Intake in Progress", isSelected: false}, {status: "Closed", isSelected: false}], open: [{status: "Open", isSelected: false}, {status: "Call For Records", isSelected: false}, {status:"Peer Review", isSelected: false},{status: "Closed", isSelected: false}], callforrecords: [{status: "Call For Records", isSelected: false}, {status: "Open", isSelected: false}, {status: "Closed", isSelected: false}], @@ -19,7 +20,7 @@ const StateList = Object.freeze({ responseforpersonal: [{status: "Response", isSelected: false}, {status: "Records Review", isSelected: false}, {status:"Peer Review", isSelected: false}, {status: "Closed", isSelected: false}], //peerreview: [{status:"Peer Review", isSelected: false},{status:"Intake in Progress", isSelected: false}, {status: "Open", isSelected: false},{status: "Records Review", isSelected: false},{status: "Consult", isSelected: false},{status: "Response", isSelected: false}], peerreview: [{status:"Peer Review", isSelected: false}], - section5Pending: [{status: "Section 5 Pending", isSelected: false}, {status: "Open", isSelected: false}, {status:"Peer Review", isSelected: false}, {status: "Redirect", isSelected: false}, {status: "Closed", isSelected: false}], + section5pending: [{status: "Section 5 Pending", isSelected: false}, {status: "Open", isSelected: false}, {status:"Peer Review", isSelected: false}, {status: "Redirect", isSelected: false}, {status: "Closed", isSelected: false}], }); const MinistryStateList = Object.freeze({ @@ -63,7 +64,7 @@ const StateEnum = Object.freeze({ peerreview: {name: "Peer Review", id: 16}, tagging: {name: "Tagging", id: 17}, readytoscan: {name: "Ready to Scan", id: 18}, - section5Pending: {name: "Section 5 Pending", id: 19}, + section5pending: {name: "Section 5 Pending", id: 19}, }); const StateTransitionCategories = Object.freeze({ From a7da6f0e778e8a973e12ec306172e9797b557571 Mon Sep 17 00:00:00 2001 From: Milos Despotovic Date: Wed, 27 Sep 2023 11:03:41 -0700 Subject: [PATCH 195/234] Change order of attachment tags --- .../src/constants/FOI/statusEnum.js | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/forms-flow-web/src/constants/FOI/statusEnum.js b/forms-flow-web/src/constants/FOI/statusEnum.js index 7a42ce6e2..c3ec27418 100644 --- a/forms-flow-web/src/constants/FOI/statusEnum.js +++ b/forms-flow-web/src/constants/FOI/statusEnum.js @@ -180,10 +180,10 @@ const AttachmentCategories = Object.freeze({ type: ["tag"], }, { - name: "recordsreview", - tags: ["recordsreview"], - display: "Records Review", - bgcolor: "#04596C", + name: "harms", + tags: ["harms"], + display: "Harms", + bgcolor: "#832AB7", type: ["tag"], }, { @@ -194,24 +194,17 @@ const AttachmentCategories = Object.freeze({ type: ["tag"], }, { - name: "response", - tags: ["response"], - display: "Response", - bgcolor: "#020A80", - type: ["tag"], - }, - { - name: "harms", - tags: ["harms"], - display: "Harms", - bgcolor: "#832AB7", + name: "recordsreview", + tags: ["recordsreview"], + display: "Records Review", + bgcolor: "#04596C", type: ["tag"], }, { - name: "oipc", - tags: ["oipc"], - display: "OIPC", - bgcolor: "#595959", + name: "consult", + tags: ["consult"], + display: "Consult", + bgcolor: "#7A3A9C", type: ["tag"], }, { @@ -221,18 +214,25 @@ const AttachmentCategories = Object.freeze({ bgcolor: "#1A1A1A", type: ["tag"], }, - { - name: "ministrysignoff", - tags: ["ministrysignoff"], - display: "Ministry Sign Off", - bgcolor: "#4B296B", + { + name: "ministrysignoff", + tags: ["ministrysignoff"], + display: "Ministry Sign Off", + bgcolor: "#4B296B", + type: ["tag"], + }, + { + name: "response", + tags: ["response"], + display: "Response", + bgcolor: "#020A80", type: ["tag"], }, { - name: "consult", - tags: ["consult"], - display: "Consult", - bgcolor: "#7A3A9C", + name: "oipc", + tags: ["oipc"], + display: "OIPC", + bgcolor: "#595959", type: ["tag"], }, { // transition: Response -> On hold From 0752fff7090e8a6797f973083ce8ad2252b44eab Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Wed, 27 Sep 2023 12:59:12 -0700 Subject: [PATCH 196/234] Testing of state changes with Section 5 Pending completed. Adjust migraiton file as well too build on top of ticket 4368 --- .../migrations/versions/a79cd809e85e_.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/request-management-api/migrations/versions/a79cd809e85e_.py b/request-management-api/migrations/versions/a79cd809e85e_.py index 8556cf373..4f8bb6fc1 100644 --- a/request-management-api/migrations/versions/a79cd809e85e_.py +++ b/request-management-api/migrations/versions/a79cd809e85e_.py @@ -1,7 +1,7 @@ """Adding Section 5 Pending FOI Status to FOIRequestStatuses Table Revision ID: a79cd809e85e -Revises: aacdbca19a47 +Revises: 1491b3126887 Create Date: 2023-09-25 14:37:29.208839 """ @@ -10,15 +10,15 @@ # revision identifiers, used by Alembic. revision = 'a79cd809e85e' -down_revision = 'aacdbca19a47' +down_revision = '1491b3126887' branch_labels = None depends_on = None def upgrade(): - op.execute('INSERT INTO public."FOIRequestStatuses"(requeststatusid, name, description, isactive) VALUES (19, \'Section 5 Pending\', \'Section 5 Pending (Personal)\', true);commit;') + op.execute('INSERT INTO public."FOIRequestStatuses"(requeststatusid, name, description, isactive) VALUES (20, \'Section 5 Pending\', \'Section 5 Pending (Personal)\', true);commit;') def downgrade(): - op.execute('DELETE FROM public."FOIRequestStatuses" WHERE requeststatusid = 19;commit;') + op.execute('DELETE FROM public."FOIRequestStatuses" WHERE requeststatusid = 20;commit;') From baebd0b088fa814132702ebdf40827e17c83a118 Mon Sep 17 00:00:00 2001 From: Richard Qi Date: Wed, 27 Sep 2023 13:05:59 -0700 Subject: [PATCH 197/234] 1. update for UX assurance 2. show record table for MSD personal only if division selected --- .../src/components/FOI/FOIRequest/FOIRequest.js | 8 +++++--- .../FOI/FOIRequest/MinistryReview/MinistryReview.js | 11 ++++++----- .../FOI/FOIRequest/MinistryReview/RequestTracking.js | 9 +++------ .../customComponents/Attachments/AttachmentModal.js | 10 +++++----- .../FOI/customComponents/Records/MCFPersonal.js | 9 +++++---- .../FOI/customComponents/Records/MSDPersonal.js | 9 +++++---- forms-flow-web/src/constants/FOI/enum.js | 5 +++++ 7 files changed, 34 insertions(+), 27 deletions(-) diff --git a/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js b/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js index d26248517..2c463b12a 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js @@ -230,7 +230,7 @@ const FOIRequest = React.memo(({ userDetail }) => { document.title = requestDetails.axisRequestId || requestDetails.idNumber || headerText; const dispatch = useDispatch(); const [isIAORestricted, setIsIAORestricted] = useState(false); - const [isMCFMSDPersonal, setIsMCFMSDPersonal] = useState(MinistryNeedsScanning.includes(bcgovcode.replaceAll('"', '')) && requestDetails.requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL); + const [isMCFPersonal, setIsMCFPersonal] = useState(bcgovcode.replaceAll('"', '') == "MCF" && requestDetails.requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL); useEffect(() => { if (window.location.href.indexOf("comments") > -1) { @@ -288,7 +288,9 @@ const FOIRequest = React.memo(({ userDetail }) => { if(MinistryNeedsScanning.includes(bcgovcode.replaceAll('"', '')) && requestDetails.requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL) { dispatch(fetchFOIPersonalDivisionsAndSections(bcgovcode.replaceAll('"', ''))); - setIsMCFMSDPersonal(true); + if(bcgovcode.replaceAll('"', '') == "MCF") { + setIsMCFPersonal(true); + } } }, [requestDetails]); @@ -763,7 +765,7 @@ const FOIRequest = React.memo(({ userDetail }) => { return (requestState !== StateEnum.intakeinprogress.name && requestState !== StateEnum.unopened.name && requestState !== StateEnum.open.name && - (requestDetails?.divisions?.length > 0 || isMCFMSDPersonal) && + (requestDetails?.divisions?.length > 0 || isMCFPersonal) && DISABLE_GATHERINGRECORDS_TAB?.toLowerCase() =='false' ); } diff --git a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js index cc0fca126..bfa3672b4 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js @@ -219,7 +219,7 @@ const MinistryReview = React.memo(({ userDetail }) => { const [originalDivisions, setOriginalDivisions] = React.useState([]) const [hasReceivedDate, setHasReceivedDate] = React.useState(true); const [isMinistryRestricted, setIsMinistryRestricted] = useState(false); - const [isMCFMSDPersonal, setIsMCFMSDPersonal] = useState(MinistryNeedsScanning.includes(bcgovcode.replaceAll('"', '')) && requestDetails.requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL); + const [isMCFPersonal, setIsMCFPersonal] = useState(bcgovcode.replaceAll('"', '') == "MCF" && requestDetails.requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL); let ministryassignedtousername = "Unassigned"; useEffect(() => { @@ -241,7 +241,9 @@ const MinistryReview = React.memo(({ userDetail }) => { if(MinistryNeedsScanning.includes(bcgovcode.replaceAll('"', '')) && requestDetails.requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL) { dispatch(fetchFOIPersonalDivisionsAndSections(bcgovcode.replaceAll('"', ''))); - setIsMCFMSDPersonal(true); + if(bcgovcode.replaceAll('"', '') == "MCF") { + setIsMCFPersonal(true); + } } }, [requestDetails, unSavedRequest]); @@ -531,7 +533,6 @@ const MinistryReview = React.memo(({ userDetail }) => { createMinistrySaveRequestObject={createMinistrySaveRequestObject} requestStartDate = {requestDetails?.requestProcessStart} setHasReceivedDate={setHasReceivedDate} - isMCFMSDPersonal={isMCFMSDPersonal} /> ); @@ -592,7 +593,7 @@ const MinistryReview = React.memo(({ userDetail }) => { ? `(${requestNotes.length})` : ""}
    - {(originalDivisions?.length > 0 || isMCFMSDPersonal) && DISABLE_GATHERINGRECORDS_TAB?.toLowerCase() =='false' &&
    0 || isMCFPersonal) && DISABLE_GATHERINGRECORDS_TAB?.toLowerCase() =='false' &&
    { [classes.hidden]: !tabLinksStatuses.Records.display, })} > - {!isAttachmentListLoading && (originalDivisions?.length > 0 || isMCFMSDPersonal) ? ( + {!isAttachmentListLoading && (originalDivisions?.length > 0 || isMCFPersonal) ? ( <> {url.indexOf("records") > -1 ? ( diff --git a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/RequestTracking.js b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/RequestTracking.js index 4fbd7c47e..1f8b1198d 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/RequestTracking.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/RequestTracking.js @@ -10,8 +10,7 @@ const RequestTracking = React.memo(({ ministrycode, createMinistrySaveRequestObject, requestStartDate, - setHasReceivedDate, - isMCFMSDPersonal + setHasReceivedDate }) => { const dispatch = useDispatch(); @@ -25,10 +24,8 @@ const RequestTracking = React.memo(({ let divisionalstages = useSelector(state=> state.foiRequests.foiMinistryDivisionalStages); let MSDSections = useSelector((state) => state.foiRequests.foiPersonalDivisionsAndSections); - if(isMCFMSDPersonal) { - if(ministrycode == "MSD" && MSDSections?.divisions?.length > 0) { - divisionalstages.divisions = MSDSections.divisions; - } + if(ministrycode == "MSD" && MSDSections?.divisions?.length > 0) { + divisionalstages.divisions = MSDSections.divisions; } const popselecteddivstages = (selectedMinDivstages) => { diff --git a/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js b/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js index d48e3330a..add1a2c13 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js +++ b/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js @@ -14,7 +14,7 @@ import FileUpload from '../FileUpload'; import FileUploadForMCFPersonal from '../FileUpload/FileUploadForMCFPersonal'; import FileUploadForMSDPersonal from '../FileUpload/FileUploadForMSDPersonal'; import { makeStyles } from '@material-ui/core/styles'; -import { MimeTypeList, MaxFileSizeInMB } from "../../../../constants/FOI/enum"; +import { MimeTypeList, MaxFileSizeInMB, MCFPopularSections, MSDPopularSections } from "../../../../constants/FOI/enum"; import { StateTransitionCategories, AttachmentCategories } from '../../../../constants/FOI/statusEnum'; import { TOTAL_RECORDS_UPLOAD_LIMIT } from "../../../../constants/constants"; import FOI_COMPONENT_CONSTANTS from "../../../../constants/FOI/foiComponentConstants"; @@ -356,8 +356,8 @@ export default function AttachmentModal({ updateFilesCb={updateFilesCb} modalFor={modalFor} uploadFor={uploadFor} - tagList={MCFSections?.sections?.slice(0, 30)} - otherTagList={MCFSections?.sections?.slice(31)} + tagList={MCFSections?.sections?.slice(0, MCFPopularSections-1)} + otherTagList={MCFSections?.sections?.slice(MCFPopularSections)} handleTagChange={handleTagChange} tagValue={tagValue} maxNumberOfFiles={maxNoFiles} @@ -379,8 +379,8 @@ export default function AttachmentModal({ modalFor={modalFor} uploadFor={uploadFor} divisions={tagList} - tagList={MSDSections?.divisions[0]?.sections?.slice(0, 10)} - otherTagList={MSDSections?.divisions[0]?.sections?.slice(11)} + tagList={MSDSections?.divisions[0]?.sections?.slice(0, MSDPopularSections-1)} + otherTagList={MSDSections?.divisions[0]?.sections?.slice(MSDPopularSections)} handleTagChange={handleTagChange} tagValue={tagValue} maxNumberOfFiles={maxNoFiles} diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/MCFPersonal.js b/forms-flow-web/src/components/FOI/customComponents/Records/MCFPersonal.js index 7b7b3ce58..2f278c8f5 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/MCFPersonal.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/MCFPersonal.js @@ -11,6 +11,7 @@ import InputAdornment from "@mui/material/InputAdornment"; import InputBase from "@mui/material/InputBase"; import IconButton from "@material-ui/core/IconButton"; import _ from 'lodash'; +import { MCFPopularSections } from "../../../../constants/FOI/enum"; const MCFPersonal = ({ setNewDivision, @@ -23,12 +24,12 @@ const MCFPersonal = ({ const [additionalTagList, setAdditionalTagList] = useState([]); const MCFSections = useSelector((state) => state.foiRequests.foiPersonalSections); - const [tagList, setTagList] = useState(MCFSections?.sections?.slice(0, 30)); - const [otherTagList, setOtherTagList] = useState(MCFSections?.sections?.slice(31)); + const [tagList, setTagList] = useState(MCFSections?.sections?.slice(0, MCFPopularSections-1)); + const [otherTagList, setOtherTagList] = useState(MCFSections?.sections?.slice(MCFPopularSections)); useEffect(() => { - setTagList(MCFSections?.sections?.slice(0, 30)); - setOtherTagList(MCFSections?.sections?.slice(31)); + setTagList(MCFSections?.sections?.slice(0, MCFPopularSections-1)); + setOtherTagList(MCFSections?.sections?.slice(MCFPopularSections)); },[MCFSections]) useEffect(() => { diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/MSDPersonal.js b/forms-flow-web/src/components/FOI/customComponents/Records/MSDPersonal.js index 7eacfb2be..906fa80b3 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/MSDPersonal.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/MSDPersonal.js @@ -11,6 +11,7 @@ import InputAdornment from "@mui/material/InputAdornment"; import InputBase from "@mui/material/InputBase"; import IconButton from "@material-ui/core/IconButton"; import _ from 'lodash'; +import { MSDPopularSections } from "../../../../constants/FOI/enum"; const MSDPersonal = ({ setNewDivision, @@ -25,12 +26,12 @@ const MSDPersonal = ({ const [newDivisions, setNewDivisions] = useState([]); const MSDSections = useSelector((state) => state.foiRequests.foiPersonalDivisionsAndSections); - const [tagList, setTagList] = useState(MSDSections?.divisions[0]?.sections?.slice(0, 10)); - const [otherTagList, setOtherTagList] = useState(MSDSections?.divisions[0]?.sections?.slice(11)); + const [tagList, setTagList] = useState(MSDSections?.divisions[0]?.sections?.slice(0, MSDPopularSections-1)); + const [otherTagList, setOtherTagList] = useState(MSDSections?.divisions[0]?.sections?.slice(MSDPopularSections)); useEffect(() => { - setTagList(MSDSections?.divisions[0]?.sections?.slice(0, 10)); - setOtherTagList(MSDSections?.divisions[0]?.sections?.slice(11)); + setTagList(MSDSections?.divisions[0]?.sections?.slice(0, MSDPopularSections-1)); + setOtherTagList(MSDSections?.divisions[0]?.sections?.slice(MSDPopularSections)); },[MSDSections]) const searchSections = (_sectionArray, _keyword, _selectedSectionValue) => { diff --git a/forms-flow-web/src/constants/FOI/enum.js b/forms-flow-web/src/constants/FOI/enum.js index 0862d2ac7..2e2ab8ace 100644 --- a/forms-flow-web/src/constants/FOI/enum.js +++ b/forms-flow-web/src/constants/FOI/enum.js @@ -73,6 +73,9 @@ const MinistryNeedsScanning = [ "MSD" ] +const MCFPopularSections = 23 +const MSDPopularSections = 11 + const RecordsDownloadList = [ {id: 0, "label": "Download", disabled: true }, {id: 1, "label": "Download for Harms", disabled: true }, @@ -95,6 +98,8 @@ extensionStatusLabel, KCProcessingTeams, KCScanningTeams, MinistryNeedsScanning, +MCFPopularSections, +MSDPopularSections, RecordsDownloadList, RecordDownloadCategory }; \ No newline at end of file From 07cf4f2fd3da6c43c996111fe5d19863ce20d9d1 Mon Sep 17 00:00:00 2001 From: Milos Despotovic Date: Thu, 28 Sep 2023 15:14:35 -0700 Subject: [PATCH 198/234] Add createpaymentreminderevent to postreminderevent --- request-management-api/request_api/services/eventservice.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/request-management-api/request_api/services/eventservice.py b/request-management-api/request_api/services/eventservice.py index f6763c474..b15c3f545 100644 --- a/request-management-api/request_api/services/eventservice.py +++ b/request-management-api/request_api/services/eventservice.py @@ -59,8 +59,9 @@ def postreminderevent(self): cfreventresponse = cfrdateevent().createdueevent() legislativeeventresponse = legislativedateevent().createdueevent() divisioneventresponse = divisiondateevent().createdueevent() - if cfreventresponse.success == False or legislativeeventresponse.success == False or divisioneventresponse.success == False: - current_app.logger.error("FOI Notification failed for reminder event response=%s ; legislative response=%s ; division response=%s" % (cfreventresponse.message, legislativeeventresponse.message, divisioneventresponse.message,)) + paymentremindereventresponse = paymentevent().createpaymentreminderevent() + if cfreventresponse.success == False or legislativeeventresponse.success == False or divisioneventresponse.success == False or paymentremindereventresponse.success == False: + current_app.logger.error("FOI Notification failed for reminder event response=%s ; legislative response=%s ; division response=%s ; payment response=%s" % (cfreventresponse.message, legislativeeventresponse.message, divisioneventresponse.message, paymentremindereventresponse.message)) return DefaultMethodResult(False,'Due reminder notifications failed',cfreventresponse.identifier) return DefaultMethodResult(True,'Due reminder notifications created',cfreventresponse.identifier) except BusinessException as exception: From 37f9a7fd322c346022f1eb1d19e3e75f2e16a2f8 Mon Sep 17 00:00:00 2001 From: Milos Despotovic Date: Thu, 28 Sep 2023 15:15:17 -0700 Subject: [PATCH 199/234] Create paymentreminderevent service --- .../request_api/services/events/payment.py | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/request-management-api/request_api/services/events/payment.py b/request-management-api/request_api/services/events/payment.py index cae00fec2..78a543428 100644 --- a/request-management-api/request_api/services/events/payment.py +++ b/request-management-api/request_api/services/events/payment.py @@ -4,6 +4,7 @@ from request_api.services.commentservice import commentservice from request_api.services.notificationservice import notificationservice from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from request_api.models.FOIRawRequests import FOIRawRequest from request_api.models.FOIRequestStatus import FOIRequestStatus import json from request_api.models.default_method_result import DefaultMethodResult @@ -13,6 +14,10 @@ from dateutil.parser import parse from pytz import timezone from request_api.utils.enums import PaymentEventType +from request_api.services.commons.duecalculator import duecalculator +from request_api.utils.commons.datetimehandler import datetimehandler +from request_api.exceptions import BusinessException +from flask import current_app class paymentevent: """ FOI Event management service @@ -34,6 +39,33 @@ def createpaymentexpiredevent(self, requestid): else: return DefaultMethodResult(False,'Unable to post Payment Expiry notification',requestid) + def createpaymentreminderevent(self): + try: + _today = datetimehandler().gettoday() + + # notificationservice().dismissremindernotification("ministryrequest", self.__notificationtype()) + # ca_holidays = duecalculator.getholidays() + eventtype = PaymentEventType.reminder.value + _onholdrequests = FOIRawRequest.getonholdapplicationfeerequests() + for entry in _onholdrequests: + _reminderdate = datetimehandler().formatdate(entry['reminder_date']) + if _reminderdate == _today: + self.__createnotificationforrawrequest(entry['requestid'], eventtype) + self.__createcommentforrawrequest(entry['requestid'], eventtype) + pass + return DefaultMethodResult(True,'Payment reminder notifications created',_today) + except BusinessException as exception: + current_app.logger.error("%s,%s" % ('Payment reminder Notification Error', exception.message)) + return DefaultMethodResult(False,'Payment reminder notifications failed', _today) + + def __createcommentforrawrequest(self, requestid, eventtype): + comment = self.__preparecomment(requestid, eventtype) + return commentservice().createrawrequestcomment(comment, "System", 2) + + def __createnotificationforrawrequest(self, requestid, eventtype): + notification = self.__preparenotification(requestid, eventtype) + return notificationservice().createnotification({"message" : notification}, requestid, "rawrequest", "Payment", "System") + def __createcomment(self, requestid, eventtype): comment = self.__preparecomment(requestid, eventtype) return commentservice().createministryrequestcomment(comment, self.__defaultuserid(), 2) @@ -54,10 +86,15 @@ def __preparecomment(self, requestid, eventtype): comment = {"comment": "Applicant has paid outstanding fee. Response package can be released."} elif eventtype == PaymentEventType.depositpaid.value: comment = {"comment": "Applicant has paid deposit. New LDD is " + FOIMinistryRequest.getduedate(requestid).strftime("%m/%d/%Y")} + elif eventtype == PaymentEventType.reminder.value: + comment = {"comment": f"Request {requestid} - 20 business days has passed awaiting payment, you can consider closing the request as abandoned"} else: comment = None if comment is not None: - comment['ministryrequestid']= requestid + if eventtype == PaymentEventType.reminder.value: + comment['requestid'] = requestid + else: + comment['ministryrequestid']= requestid return comment def __notificationmessage(self, requestid, eventtype): @@ -69,6 +106,8 @@ def __notificationmessage(self, requestid, eventtype): return "Applicant has paid outstanding fee. Response package can be released." elif eventtype == PaymentEventType.depositpaid.value: return "Applicant has paid deposit. New LDD is " + FOIMinistryRequest.getduedate(requestid).strftime("%m/%d/%Y") + elif eventtype == PaymentEventType.reminder.value: + return "20 business days has passed awaiting payment, you can consider closing the request as abandoned" else: return None From d51453bce371b15ee2a4e5e003b784103363e56c Mon Sep 17 00:00:00 2001 From: Milos Despotovic Date: Thu, 28 Sep 2023 15:17:25 -0700 Subject: [PATCH 200/234] Create method to get onhold applications --- .../request_api/models/FOIRawRequests.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/request-management-api/request_api/models/FOIRawRequests.py b/request-management-api/request_api/models/FOIRawRequests.py index 85823f4e3..cb4270974 100644 --- a/request-management-api/request_api/models/FOIRawRequests.py +++ b/request-management-api/request_api/models/FOIRawRequests.py @@ -361,6 +361,25 @@ def getassignmenttransition(cls,requestid): db.session.close() return assignments + @classmethod + def getonholdapplicationfeerequests(cls): + onholdapplicationfeerequests = [] + try: + sql = '''SELECT * FROM (SELECT DISTINCT ON (requestid) requestid, (updated_at + INTERVAL '20 days') as reminder_date, status FROM public."FOIRawRequests" + ORDER BY requestid ASC, version DESC) r + WHERE r.status = 'On-Hold - Application Fee' + ''' + rs = db.session.execute(text(sql)) + for row in rs: + if row.status == 'On-Hold - Application Fee': + onholdapplicationfeerequests.append(row) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return onholdapplicationfeerequests + @classmethod def getversionforrequest(cls,requestid): return db.session.query(FOIRawRequest.version).filter_by(requestid=requestid).order_by(FOIRawRequest.version.desc()).first() From 640aedc1f20c5ce568c1f80b09c58b618257a92a Mon Sep 17 00:00:00 2001 From: Milos Despotovic Date: Thu, 28 Sep 2023 15:17:52 -0700 Subject: [PATCH 201/234] Add comment --- request-management-api/request_api/models/FOIRawRequests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/request-management-api/request_api/models/FOIRawRequests.py b/request-management-api/request_api/models/FOIRawRequests.py index cb4270974..6c863ad46 100644 --- a/request-management-api/request_api/models/FOIRawRequests.py +++ b/request-management-api/request_api/models/FOIRawRequests.py @@ -362,7 +362,7 @@ def getassignmenttransition(cls,requestid): return assignments @classmethod - def getonholdapplicationfeerequests(cls): + def getonholdapplicationfeerequests(cls): # with the reminder date onholdapplicationfeerequests = [] try: sql = '''SELECT * FROM (SELECT DISTINCT ON (requestid) requestid, (updated_at + INTERVAL '20 days') as reminder_date, status FROM public."FOIRawRequests" From 5567e1fedbeb11a6c8d1ac6bbb44ba38c6d62958 Mon Sep 17 00:00:00 2001 From: Milos Despotovic Date: Thu, 28 Sep 2023 15:18:06 -0700 Subject: [PATCH 202/234] Add payment reminder enum --- request-management-api/request_api/utils/enums.py | 1 + 1 file changed, 1 insertion(+) diff --git a/request-management-api/request_api/utils/enums.py b/request-management-api/request_api/utils/enums.py index 3e0c8d8a2..f21d807ab 100644 --- a/request-management-api/request_api/utils/enums.py +++ b/request-management-api/request_api/utils/enums.py @@ -125,6 +125,7 @@ class PaymentEventType(Enum): expired = "EXPIRED" outstandingpaid = "OUTSTANDINGPAID" depositpaid = "DEPOSITPAID" + reminder = "REMINDER" class CommentType(Enum): """Authorization header types.""" From cd72b4bdf3f5dd016f61adf9d05378f970534123 Mon Sep 17 00:00:00 2001 From: Richard Qi Date: Thu, 28 Sep 2023 15:59:30 -0700 Subject: [PATCH 203/234] fix modal title --- .../Attachments/AttachmentModal.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js b/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js index add1a2c13..a3820ee4a 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js +++ b/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js @@ -19,6 +19,7 @@ import { StateTransitionCategories, AttachmentCategories } from '../../../../con import { TOTAL_RECORDS_UPLOAD_LIMIT } from "../../../../constants/constants"; import FOI_COMPONENT_CONSTANTS from "../../../../constants/FOI/foiComponentConstants"; import { ClickableChip } from '../../Dashboard/utils'; +import { MinistryNeedsScanning } from "../../../constants/FOI/enum"; const useStyles = makeStyles((theme) => ({ root: { @@ -99,11 +100,16 @@ export default function AttachmentModal({ const [tagValue, setTagValue] = useState(uploadFor === 'record' ? "" : "general"); const attchmentFileNameList = attachmentsArray.map(_file => _file.filename.toLowerCase()); const totalRecordUploadLimit= TOTAL_RECORDS_UPLOAD_LIMIT ; + const [isMCFMSDPersonal, setIsMCFMSDPersonal] = useState(MinistryNeedsScanning.includes(bcgovcode) && requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL); useEffect(() => { parseFileName(attachment); }, [attachment]) + useEffect(() => { + setIsMCFMSDPersonal(MinistryNeedsScanning.includes(bcgovcode) && requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL); + }, [bcgovcode, requestType]) + const parseFileName = (_attachment) => { setNewFilename(""); setExtension(""); @@ -230,8 +236,13 @@ export default function AttachmentModal({ const getMessage = () => { switch(modalFor.toLowerCase()) { case "add": - return {title: "Add Attachment", body: ""}; - + if(isMCFMSDPersonal) { + return {title: "Add Scanned Records", body: ""}; + } + else + { + return {title: "Add Attachment", body: ""}; + } case "replaceattachment": if (uploadFor === 'record') { _message = {title: "Replace Records", body:<>Replace the existing record with a reformatted or updated version of the same record.

    The original file that was uploaded will still be available for download. } From 0960663c8743f824a7f828c8eb9fa00959bf15d8 Mon Sep 17 00:00:00 2001 From: Richard Qi Date: Thu, 28 Sep 2023 22:10:32 -0700 Subject: [PATCH 204/234] bug fix --- .../FOI/customComponents/Attachments/AttachmentModal.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js b/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js index a3820ee4a..130ada26a 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js +++ b/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js @@ -14,12 +14,11 @@ import FileUpload from '../FileUpload'; import FileUploadForMCFPersonal from '../FileUpload/FileUploadForMCFPersonal'; import FileUploadForMSDPersonal from '../FileUpload/FileUploadForMSDPersonal'; import { makeStyles } from '@material-ui/core/styles'; -import { MimeTypeList, MaxFileSizeInMB, MCFPopularSections, MSDPopularSections } from "../../../../constants/FOI/enum"; +import { MimeTypeList, MaxFileSizeInMB, MCFPopularSections, MSDPopularSections, MinistryNeedsScanning } from "../../../../constants/FOI/enum"; import { StateTransitionCategories, AttachmentCategories } from '../../../../constants/FOI/statusEnum'; import { TOTAL_RECORDS_UPLOAD_LIMIT } from "../../../../constants/constants"; import FOI_COMPONENT_CONSTANTS from "../../../../constants/FOI/foiComponentConstants"; import { ClickableChip } from '../../Dashboard/utils'; -import { MinistryNeedsScanning } from "../../../constants/FOI/enum"; const useStyles = makeStyles((theme) => ({ root: { From 86420e80474805b36f4a9e6435dd6877a3ffd7d9 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Fri, 29 Sep 2023 16:29:19 -0700 Subject: [PATCH 205/234] Notifiation and comment logic tested and completed. Refactor code WIP --- .../request_api/models/FOIRawRequests.py | 19 ++++++ .../services/events/section5pending.py | 65 +++++++++++++++++++ .../request_api/services/eventservice.py | 8 ++- .../notifications/notificationconfig.py | 4 +- 4 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 request-management-api/request_api/services/events/section5pending.py diff --git a/request-management-api/request_api/models/FOIRawRequests.py b/request-management-api/request_api/models/FOIRawRequests.py index 85823f4e3..fe565db56 100644 --- a/request-management-api/request_api/models/FOIRawRequests.py +++ b/request-management-api/request_api/models/FOIRawRequests.py @@ -981,6 +981,25 @@ def getmetadata(cls,requestid): finally: db.session.close() return requestdetails + + @classmethod + def getlatestsection5pendings(cls): + section5pendings = [] + try: + sql = """SELECT * FROM + (SELECT DISTINCT ON (requestid) requestid, created_at, version, status, to_char(created_at + INTERVAL '10 days', 'YYYY-MM-DD') as duedate, axisrequestid + FROM public."FOIRawRequests" + ORDER BY requestid ASC, version DESC) foireqs + WHERE foireqs.status = 'Section 5 Pending';""" + rs = db.session.execute(text(sql)) + for row in rs: + section5pendings.append({"requestid": row["requestid"], "duedate": row["duedate"], "version": row["version"], "statusname": row["status"], "created_at": row["created_at"], "axisrequestid": ["axisrequestid"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return section5pendings class FOIRawRequestSchema(ma.Schema): class Meta: diff --git a/request-management-api/request_api/services/events/section5pending.py b/request-management-api/request_api/services/events/section5pending.py new file mode 100644 index 000000000..9df1ce5d2 --- /dev/null +++ b/request-management-api/request_api/services/events/section5pending.py @@ -0,0 +1,65 @@ +from os import stat +from re import VERBOSE +from request_api.services.commons.duecalculator import duecalculator +from request_api.services.notificationservice import notificationservice +from request_api.services.commentservice import commentservice +from request_api.models.FOIRawRequests import FOIRawRequest +from request_api.models.default_method_result import DefaultMethodResult +from enum import Enum +from request_api.exceptions import BusinessException +from datetime import datetime +from flask import current_app +from dateutil.parser import parse + +class section5pendingevent(duecalculator): + """ FOI Event management service for section 5 pending state + """ + + def createdueevent(self): + try: + _today = self.gettoday() + notificationservice().dismissremindernotification("rawrequest", self.__notificationtype()) + # ca_holidays = self.getholidays() + section5pendings = FOIRawRequest.getlatestsection5pendings() + print(len(section5pendings)) + for entry in section5pendings: + duedate = self.formatduedate(entry['duedate']) + message = None + if _today == duedate: + message = self.__passeddueremindermessage() + #WAT IS THIS DOING? + # elif self.getpreviousbusinessday(entry['duedate'], ca_holidays) == _today: + # message = self.__passeddueremindermessage() + self.__createnotification(message, entry['requestid']) + self.__createcomment(entry, message) + return DefaultMethodResult(True,'Section 5 Pending passed due notification created',_today) + except BusinessException as exception: + current_app.logger.error("%s,%s" % ('Section 5 Pending passed due notification Error', exception.message)) + return DefaultMethodResult(False,'Section 5 Pending passed due notification failed',_today) + + def __createnotification(self, message, requestid): + if message is not None: + return notificationservice().createremindernotification({"message" : message}, requestid, "rawrequest", self.__notificationtype(), self.__defaultuserid()) + + def __createcomment(self, entry, message): + if message is not None: + _comment = self.__preparecomment(entry, message) + return commentservice().createrawrequestcomment(_comment, self.__defaultuserid(), 2) + + def __preparecomment(self, foirequest, message): + _comment = dict() + _comment['comment'] = message + _comment['requestid'] = foirequest["requestid"] + _comment['version'] = foirequest["version"] + _comment['taggedusers'] = None + _comment['parentcommentid'] = None + return _comment + + def __passeddueremindermessage(self): + return "10 business days has passed awaiting section 5, you can consider closing the request as abandoned" + + def __notificationtype(self): + return "Section 5 Pending Reminder" + + def __defaultuserid(self): + return "System" diff --git a/request-management-api/request_api/services/eventservice.py b/request-management-api/request_api/services/eventservice.py index f6763c474..86d814fcc 100644 --- a/request-management-api/request_api/services/eventservice.py +++ b/request-management-api/request_api/services/eventservice.py @@ -13,6 +13,7 @@ from request_api.services.events.cfrfeeform import cfrfeeformevent from request_api.services.events.payment import paymentevent from request_api.services.events.email import emailevent +from request_api.services.events.section5pending import section5pendingevent from request_api.models.default_method_result import DefaultMethodResult from request_api.exceptions import BusinessException from request_api.utils.enums import PaymentEventType @@ -58,9 +59,10 @@ def postreminderevent(self): try: cfreventresponse = cfrdateevent().createdueevent() legislativeeventresponse = legislativedateevent().createdueevent() - divisioneventresponse = divisiondateevent().createdueevent() - if cfreventresponse.success == False or legislativeeventresponse.success == False or divisioneventresponse.success == False: - current_app.logger.error("FOI Notification failed for reminder event response=%s ; legislative response=%s ; division response=%s" % (cfreventresponse.message, legislativeeventresponse.message, divisioneventresponse.message,)) + divisioneventresponse = divisiondateevent().createdueevent() + section5pendingresponse = section5pendingevent().createdueevent() + if cfreventresponse.success == False or legislativeeventresponse.success == False or divisioneventresponse.success == False or section5pendingresponse == False: + current_app.logger.error("FOI Notification failed for reminder event response=%s ; legislative response=%s ; division response=%s ; section5pending response=%s" % (cfreventresponse.message, legislativeeventresponse.message, divisioneventresponse.message, section5pendingresponse.message)) return DefaultMethodResult(False,'Due reminder notifications failed',cfreventresponse.identifier) return DefaultMethodResult(True,'Due reminder notifications created',cfreventresponse.identifier) except BusinessException as exception: diff --git a/request-management-api/request_api/services/notifications/notificationconfig.py b/request-management-api/request_api/services/notifications/notificationconfig.py index 48a2c0e6c..522ba1749 100644 --- a/request-management-api/request_api/services/notifications/notificationconfig.py +++ b/request-management-api/request_api/services/notifications/notificationconfig.py @@ -41,7 +41,9 @@ def getnotificationtypeid(self, notificationtype): elif notificationtype == "Email Failure": return 16 elif notificationtype == "Payment": - return 17 + return 17 + elif notificationtype == "Section 5 Pending Reminder": + return 20 return 0 def getnotificationusertypeid(self, notificationusertype): From 4a1662b84476abb481960e6ece368f429d4e42aa Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Tue, 3 Oct 2023 10:08:10 -0700 Subject: [PATCH 206/234] Code clear + migration file adjustment to upgrade from migraiton done on ticket 4369 --- request-management-api/migrations/versions/a79cd809e85e_.py | 4 +++- .../request_api/services/events/section5pending.py | 5 ----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/request-management-api/migrations/versions/a79cd809e85e_.py b/request-management-api/migrations/versions/a79cd809e85e_.py index 4f8bb6fc1..607788802 100644 --- a/request-management-api/migrations/versions/a79cd809e85e_.py +++ b/request-management-api/migrations/versions/a79cd809e85e_.py @@ -1,4 +1,4 @@ -"""Adding Section 5 Pending FOI Status to FOIRequestStatuses Table +"""Adding Section 5 Pending FOI Status to FOIRequestStatuses Table and NotificaitonTypes Table Revision ID: a79cd809e85e Revises: 1491b3126887 @@ -17,8 +17,10 @@ def upgrade(): op.execute('INSERT INTO public."FOIRequestStatuses"(requeststatusid, name, description, isactive) VALUES (20, \'Section 5 Pending\', \'Section 5 Pending (Personal)\', true);commit;') + op.execute('INSERT INTO public."NotificationTypes"(notificationtypeid, name, description, isactive) VALUES (20, \'Section 5 Pending Reminder\', \'Section 5 Pending Reminder\', true);commit;') def downgrade(): op.execute('DELETE FROM public."FOIRequestStatuses" WHERE requeststatusid = 20;commit;') + op.execute('DELETE FROM public."NotificationTypes" WHERE notificationtypeid = 20;commit;') diff --git a/request-management-api/request_api/services/events/section5pending.py b/request-management-api/request_api/services/events/section5pending.py index 9df1ce5d2..3a76ad2b7 100644 --- a/request-management-api/request_api/services/events/section5pending.py +++ b/request-management-api/request_api/services/events/section5pending.py @@ -19,17 +19,12 @@ def createdueevent(self): try: _today = self.gettoday() notificationservice().dismissremindernotification("rawrequest", self.__notificationtype()) - # ca_holidays = self.getholidays() section5pendings = FOIRawRequest.getlatestsection5pendings() - print(len(section5pendings)) for entry in section5pendings: duedate = self.formatduedate(entry['duedate']) message = None if _today == duedate: message = self.__passeddueremindermessage() - #WAT IS THIS DOING? - # elif self.getpreviousbusinessday(entry['duedate'], ca_holidays) == _today: - # message = self.__passeddueremindermessage() self.__createnotification(message, entry['requestid']) self.__createcomment(entry, message) return DefaultMethodResult(True,'Section 5 Pending passed due notification created',_today) From aae06ba7e5df33e18dc896e619f77e2292e9a00e Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Tue, 3 Oct 2023 10:57:54 -0700 Subject: [PATCH 207/234] Adjusted id for section5pending in stateenums/FE --- forms-flow-web/src/constants/FOI/statusEnum.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forms-flow-web/src/constants/FOI/statusEnum.js b/forms-flow-web/src/constants/FOI/statusEnum.js index 8b2baebab..5a0aeebbd 100644 --- a/forms-flow-web/src/constants/FOI/statusEnum.js +++ b/forms-flow-web/src/constants/FOI/statusEnum.js @@ -64,7 +64,7 @@ const StateEnum = Object.freeze({ peerreview: {name: "Peer Review", id: 16}, tagging: {name: "Tagging", id: 17}, readytoscan: {name: "Ready to Scan", id: 18}, - section5pending: {name: "Section 5 Pending", id: 19}, + section5pending: {name: "Section 5 Pending", id: 20}, }); const StateTransitionCategories = Object.freeze({ From da1eff335725846eb4da66cfacce9f5d1c78df3d Mon Sep 17 00:00:00 2001 From: Richard Qi Date: Tue, 3 Oct 2023 12:03:14 -0700 Subject: [PATCH 208/234] Bug fix #4351 #4303 #4292 --- .../FOI/FOIRequest/BottomButtonGroup/index.js | 26 +++++++++++++++++++ forms-flow-web/src/constants/FOI/enum.js | 6 ++--- forms-flow-web/src/helper/FOI/helper.js | 4 +-- .../07a6d930b276_.MSD personal sections.py | 2 +- 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/forms-flow-web/src/components/FOI/FOIRequest/BottomButtonGroup/index.js b/forms-flow-web/src/components/FOI/FOIRequest/BottomButtonGroup/index.js index 00eb20ab9..bd5846279 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/BottomButtonGroup/index.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/BottomButtonGroup/index.js @@ -1,4 +1,5 @@ import React, { useEffect, useState } from "react"; +import { useSelector } from "react-redux"; import "./bottombuttongroup.scss"; import { makeStyles } from "@material-ui/core/styles"; import { useDispatch } from "react-redux"; @@ -20,6 +21,7 @@ import { ConditionalComponent } from "../../../../helper/FOI/helper"; import { StateEnum } from "../../../../constants/FOI/statusEnum"; +import { KCScanningTeam } from "../../../../constants/FOI/enum"; import { dueDateCalculation, getRequestState, returnToQueue } from "./utils"; import { handleBeforeUnload } from "../utils"; import { setFOILoader } from '../../../../actions/FOI/foiRequestActions' @@ -92,6 +94,11 @@ const BottomButtonGroup = React.memo( const [axisSyncModalOpen, setAxisSyncModalOpen] = useState(false); + //get the assignedTo master data + const assignedToList = useSelector( + (state) => state.foiRequests.foiAssignedToList + ); + const handleClosingDateChange = (cDate) => { setClosingDate(cDate); }; @@ -172,6 +179,25 @@ const BottomButtonGroup = React.memo( saveRequestObject.requeststatusid && saveRequestObject.currentState ) { + //scanning team - MSD/CFD personal + if(currentSelectedStatus === StateEnum.readytoscan.name) { + saveRequestObject.assignedGroup = KCScanningTeam; + + if (assignedToList && assignedToList.length > 0) { + let assignedTeam = assignedToList.find(team => team.name === KCScanningTeam); + if (assignedTeam && saveRequestObject.assignedTo && !assignedTeam.members.find(member => member.username === saveRequestObject.assignedTo)) { + saveRequestObject.assignedTo = ""; + saveRequestObject.assignedToFirstName = ""; + saveRequestObject.assignedToLastName = ""; + saveRequestObject.assignedToName = ""; + } + } else { + saveRequestObject.assignedTo = ""; + saveRequestObject.assignedToFirstName = ""; + saveRequestObject.assignedToLastName = ""; + saveRequestObject.assignedToName = ""; + } + } saveRequestModal(); } else { saveRequestObject.requeststatusid = StateEnum.open.id; diff --git a/forms-flow-web/src/constants/FOI/enum.js b/forms-flow-web/src/constants/FOI/enum.js index 2e2ab8ace..f7ddd3364 100644 --- a/forms-flow-web/src/constants/FOI/enum.js +++ b/forms-flow-web/src/constants/FOI/enum.js @@ -64,9 +64,7 @@ const KCProcessingTeams = [ "Coordinated Response Unit", ] -const KCScanningTeams = [ - "Scanning Team", -] +const KCScanningTeam = "Scanning Team" const MinistryNeedsScanning = [ "MCF", @@ -96,7 +94,7 @@ MaxNumberOfFiles, extensionStatusId, extensionStatusLabel, KCProcessingTeams, -KCScanningTeams, +KCScanningTeam, MinistryNeedsScanning, MCFPopularSections, MSDPopularSections, diff --git a/forms-flow-web/src/helper/FOI/helper.js b/forms-flow-web/src/helper/FOI/helper.js index c6dde6a46..f565f1c98 100644 --- a/forms-flow-web/src/helper/FOI/helper.js +++ b/forms-flow-web/src/helper/FOI/helper.js @@ -5,7 +5,7 @@ import { format, utcToZonedTime } from 'date-fns-tz'; import MINISTRYGROUPS from '../../constants/FOI/foiministrygroupConstants'; import { SESSION_SECURITY_KEY, SESSION_LIFETIME } from "../../constants/constants"; import { toast } from "react-toastify"; -import { KCProcessingTeams, KCScanningTeams } from "../../constants/FOI/enum"; +import { KCProcessingTeams, KCScanningTeam } from "../../constants/FOI/enum"; import _ from 'lodash'; let isBetween = require("dayjs/plugin/isBetween"); @@ -191,7 +191,7 @@ const isProcessingTeam = (userGroups) => { const isScanningTeam = (userGroups) => { return userGroups?.some((userGroup) => - KCScanningTeams.includes(userGroup.replace("/", "")) + userGroup.replace("/", "") == KCScanningTeam ); }; diff --git a/request-management-api/migrations/versions/07a6d930b276_.MSD personal sections.py b/request-management-api/migrations/versions/07a6d930b276_.MSD personal sections.py index 54e51c604..c4d48b57b 100644 --- a/request-management-api/migrations/versions/07a6d930b276_.MSD personal sections.py +++ b/request-management-api/migrations/versions/07a6d930b276_.MSD personal sections.py @@ -17,7 +17,7 @@ def upgrade(): - sections = ['GA KEY PLAYER','GA KEY PLAYER AHR','GA SPOUSE','GA SPOUSE AHR','GA DEPENDENT','GA DEPENDENT AHR','FM1 KEY PLAYER','FM1 KEY PLAYER AHR','FM2 RESPONDENT','FM2 RESPONDENT AHR','TPA','FM3','FM3 AHR','FM4','FM4 AHR','FM5','FM5 AHR','GA 2 DEPENDENT','GA 2 DEPENDENT AHR','GA 2 SPOUSE','GA 2 SPOUSE AHR','GA 3 SPOUSE','GA 3 SPOUSE AHR','GA KEY PLAYER PHYSICAL','GA SPOUSE PHYSICAL','GAZ SPOUSE PHYSICAL','GA3 SPOUSE PHYSICAL','GA DEPENDENT PHYSICAL','GAZ DEPENDENT PHYSICAL','FM1 KEY PLAYER PHYSICAL','FM2 RESPONDENT PHYSICAL','FM3 PHYSICAL','FM4 PHYSICAL','FMS PHYSICAL'] + sections = ['GA KEY PLAYER','GA KEY PLAYER AHR','GA SPOUSE','GA SPOUSE AHR','GA DEPENDENT','GA DEPENDENT AHR','FM1 KEY PLAYER','FM1 KEY PLAYER AHR','FM2 RESPONDENT','FM2 RESPONDENT AHR','TPA','FM3','FM3 AHR','FM4','FM4 AHR','FM5','FM5 AHR','GA 2 DEPENDENT','GA 2 DEPENDENT AHR','GA 2 SPOUSE','GA 2 SPOUSE AHR','GA 3 SPOUSE','GA 3 SPOUSE AHR','GA KEY PLAYER PHYSICAL','GA SPOUSE PHYSICAL','GA2 SPOUSE PHYSICAL','GA3 SPOUSE PHYSICAL','GA DEPENDENT PHYSICAL','GA2 DEPENDENT PHYSICAL','FM1 KEY PLAYER PHYSICAL','FM2 RESPONDENT PHYSICAL','FM3 PHYSICAL','FM4 PHYSICAL','FM5 PHYSICAL'] sortorder = 1 for section in sections: From b038fedb44b839e5c6122053a820ab09c68bb140 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Tue, 3 Oct 2023 15:11:00 -0700 Subject: [PATCH 209/234] Adjusted sort function for sections and divisions to account for None sort order value which was causing code to break --- .../request_api/services/divisionstageservice.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/request-management-api/request_api/services/divisionstageservice.py b/request-management-api/request_api/services/divisionstageservice.py index 092b55c0e..c62a8637c 100644 --- a/request-management-api/request_api/services/divisionstageservice.py +++ b/request-management-api/request_api/services/divisionstageservice.py @@ -18,7 +18,7 @@ def getalldivisionsandsections(self, bcgovcode): divisionstages = [] programarea = ProgramArea.getprogramarea(bcgovcode) divisions = ProgramAreaDivision.getallprogramareatags(programarea['programareaid']) - divisions.sort(key=lambda item: (item['sortorder'], item['name'])) + divisions.sort(key=lambda item: (item["sortorder"] if item["sortorder"] is not None else float('inf'), item["name"])) for division in divisions: divisionstages.append({"divisionid": division['divisionid'], "name": self.escapestr(division['name']),"sortorder": division['sortorder'],"issection":division['issection']}) return {"divisions": divisionstages, "stages": self.getstages()} @@ -27,7 +27,7 @@ def getpersonalspecificdivisionandstages(self, bcgovcode): divisionstages = [] programarea = ProgramArea.getprogramarea(bcgovcode) divisions = ProgramAreaDivision.getpersonalspecificprogramareadivisions(programarea['programareaid']) - divisions.sort(key=lambda item: (item['sortorder'], item['name'])) + divisions.sort(key=lambda item: (item["sortorder"] if item["sortorder"] is not None else float('inf'), item["name"])) for division in divisions: divisionstages.append({"divisionid": division['divisionid'], "name": self.escapestr(division['name']),"sortorder": division['sortorder'], "issection":division['issection']}) return {"divisions": divisionstages, "stages": self.getstages()} @@ -36,7 +36,7 @@ def getpersonalspecificprogramareasections(self, bcgovcode): programareasections = [] programarea = ProgramArea.getprogramarea(bcgovcode) _sections = ProgramAreaDivision.getpersonalrequestsprogramareasections(programarea['programareaid']) - _sections.sort(key=lambda item: (item['sortorder'], item['name'])) + _sections.sort(key=lambda item: (item["sortorder"] if item["sortorder"] is not None else float('inf'), item["name"])) for _section in _sections: programareasections.append({"divisionid": _section['divisionid'], "name": self.escapestr(_section['name']),"sortorder":_section['sortorder'],"issection":_section['issection']}) return {"sections": programareasections} @@ -46,7 +46,7 @@ def getpersonalspecificdivisionsandsections(self, bcgovcode): programarea = ProgramArea.getprogramarea(bcgovcode) divisions = ProgramAreaDivision.getpersonalspecificprogramareadivisions(programarea['programareaid']) sections = ProgramAreaDivision.getpersonalrequestsdivisionsandsections(programarea['programareaid']) - divisions.sort(key=lambda item: (item['sortorder'], item['name'])) + divisions.sort(key=lambda item: (item["sortorder"] if item["sortorder"] is not None else float('inf'), item["name"])) for _division in divisions: divisionid = _division['divisionid'] _sections = [] From 8f3c219b2e496eabc69156f7f24968569ebaa982 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Thu, 5 Oct 2023 17:32:45 -0700 Subject: [PATCH 210/234] #4362 stopping the clock for scanning, tagging --- .../src/components/FOI/Dashboard/Ministry/Queue.js | 4 ++-- forms-flow-web/src/components/FOI/Dashboard/utils.js | 6 +++--- .../request_api/models/FOIMinistryRequests.py | 6 +++--- .../request_api/services/requestservice.py | 2 +- request-management-api/request_api/utils/enums.py | 2 ++ 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/forms-flow-web/src/components/FOI/Dashboard/Ministry/Queue.js b/forms-flow-web/src/components/FOI/Dashboard/Ministry/Queue.js index 7f775e9bb..176940645 100644 --- a/forms-flow-web/src/components/FOI/Dashboard/Ministry/Queue.js +++ b/forms-flow-web/src/components/FOI/Dashboard/Ministry/Queue.js @@ -90,7 +90,7 @@ const Queue = ({ userDetail, tableInfo }) => { function getRecordsDue(params) { let receivedDateString = params.row.cfrduedate; const currentStatus = params.row.currentState; - if (currentStatus.toLowerCase() === StateEnum.onhold.name.toLowerCase()) { + if (currentStatus.toLowerCase() === StateEnum.onhold.name.toLowerCase() || currentStatus.toLowerCase() === StateEnum.tagging.name.toLowerCase() || currentStatus.toLowerCase() === StateEnum.readytoscan.name.toLowerCase()) { return "N/A"; } else if(!receivedDateString) { return ""; @@ -102,7 +102,7 @@ const Queue = ({ userDetail, tableInfo }) => { function getLDD(params) { let receivedDateString = params.row.duedate; const currentStatus = params.row.currentState; - if (currentStatus.toLowerCase() === StateEnum.onhold.name.toLowerCase()) { + if (currentStatus.toLowerCase() === StateEnum.onhold.name.toLowerCase() || currentStatus.toLowerCase() === StateEnum.tagging.name.toLowerCase() || currentStatus.toLowerCase() === StateEnum.readytoscan.name.toLowerCase()) { return "N/A"; } else if(!receivedDateString) { return ""; diff --git a/forms-flow-web/src/components/FOI/Dashboard/utils.js b/forms-flow-web/src/components/FOI/Dashboard/utils.js index 0bb6d1a6a..1a4693aaf 100644 --- a/forms-flow-web/src/components/FOI/Dashboard/utils.js +++ b/forms-flow-web/src/components/FOI/Dashboard/utils.js @@ -132,7 +132,7 @@ export const onBehalfFullName = (params) => { export const getRecordsDue = (params) => { let receivedDateString = params.row.cfrduedate; const currentStatus = params.row.currentState; - if (currentStatus.toLowerCase() === StateEnum.onhold.name.toLowerCase()) { + if (currentStatus.toLowerCase() === StateEnum.onhold.name.toLowerCase() || currentStatus.toLowerCase() === StateEnum.tagging.name.toLowerCase() || currentStatus.toLowerCase() === StateEnum.readytoscan.name.toLowerCase()) { return "N/A"; } else if(!receivedDateString) { return ""; @@ -144,7 +144,7 @@ export const getRecordsDue = (params) => { export const getLDD = (params) => { let receivedDateString = params.row.duedate; const currentStatus = params.row.currentState; - if (currentStatus.toLowerCase() === StateEnum.onhold.name.toLowerCase()) { + if (currentStatus.toLowerCase() === StateEnum.onhold.name.toLowerCase() || currentStatus.toLowerCase() === StateEnum.tagging.name.toLowerCase() || currentStatus.toLowerCase() === StateEnum.readytoscan.name.toLowerCase()) { return "N/A"; } else if(!receivedDateString) { return ""; @@ -157,7 +157,7 @@ export const getDaysLeft = (params) => { const receivedDateString = params.row.duedate; if ( - [StateEnum.onhold.name.toLowerCase(), StateEnum.closed.name.toLowerCase()].includes(params.row.currentState.toLowerCase()) + [StateEnum.onhold.name.toLowerCase(), StateEnum.tagging.name.toLowerCase(), StateEnum.readytoscan.name.toLowerCase(),StateEnum.closed.name.toLowerCase()].includes(params.row.currentState.toLowerCase()) ) { return "N/A"; } else if(!receivedDateString) { diff --git a/request-management-api/request_api/models/FOIMinistryRequests.py b/request-management-api/request_api/models/FOIMinistryRequests.py index cd6e2a8e9..85b9bc418 100644 --- a/request-management-api/request_api/models/FOIMinistryRequests.py +++ b/request-management-api/request_api/models/FOIMinistryRequests.py @@ -283,7 +283,7 @@ def getlastoffholddate(cls, ministryrequestid): recent_offhold_index = None offhold_indicator = False for entry in desc_transitions: - if entry["status"] == StateName.onhold.value: + if (entry["status"] == StateName.onhold.value or entry["status"] == StateName.readytoscan.value or entry["status"] == StateName.tagging.value): onhold_occurance = onhold_occurance + 1 if onhold_occurance > 1: recent_offhold_index = index @@ -737,7 +737,7 @@ def getupcominglegislativeduerecords(cls): upcomingduerecords = [] try: sql = """select distinct on (filenumber) filenumber, to_char(duedate, 'YYYY-MM-DD') as duedate, foiministryrequestid, version, foirequest_id, created_at, createdby from "FOIMinistryRequests" fpa - where isactive = true and duedate is not null and requeststatusid not in (5,6,4,11,3,15) + where isactive = true and duedate is not null and requeststatusid not in (5,6,4,11,3,15,17,18) and duedate between NOW() - INTERVAL '7 DAY' AND NOW() + INTERVAL '7 DAY' order by filenumber , version desc;""" rs = db.session.execute(text(sql)) @@ -760,7 +760,7 @@ def getupcomingdivisionduerecords(cls): from "FOIMinistryRequestDivisions" frd inner join (select distinct on (fpa.foiministryrequestid) foiministryrequestid, version as foiministryrequestversion, axisrequestid, filenumber, foirequest_id, requeststatusid from "FOIMinistryRequests" fpa - order by fpa.foiministryrequestid , fpa.version desc) fma on frd.foiministryrequest_id = fma.foiministryrequestid and frd.foiministryrequestversion_id = fma.foiministryrequestversion and fma.requeststatusid not in (5,6,4,11,3,15) + order by fpa.foiministryrequestid , fpa.version desc) fma on frd.foiministryrequest_id = fma.foiministryrequestid and frd.foiministryrequestversion_id = fma.foiministryrequestversion and fma.requeststatusid not in (5,6,4,11,3,15,17,18) inner join "ProgramAreaDivisions" pad2 on frd.divisionid = pad2.divisionid inner join "ProgramAreaDivisionStages" pads on frd.stageid = pads.stageid and frd.stageid in (5, 7, 9) and frd.divisionduedate between NOW() - INTERVAL '7 DAY' AND NOW() + INTERVAL '7 DAY' diff --git a/request-management-api/request_api/services/requestservice.py b/request-management-api/request_api/services/requestservice.py index 83d6bf039..d81ecea99 100644 --- a/request-management-api/request_api/services/requestservice.py +++ b/request-management-api/request_api/services/requestservice.py @@ -62,7 +62,7 @@ def updateduedate(self, requestid, ministryrequestid, offholddate, foirequestsch foirequest = self.getrequest(requestid, ministryrequestid) currentstatus = foirequest["stateTransition"][0]["status"] if "stateTransition" in foirequest and len(foirequest["stateTransition"]) > 1 else None #Check for Off Hold - if currentstatus not in (None, "") and currentstatus == StateName.onhold.value and nextstatename != StateName.response.value: + if currentstatus not in (None, "") and (currentstatus == StateName.onhold.value or currentstatus == StateName.tagging.value or currentstatus == StateName.readytoscan.value) and nextstatename != StateName.response.value: skipcalculation = self.__skipduedatecalculation(ministryrequestid, offholddate) #Skip multiple off hold in a day if skipcalculation == True: diff --git a/request-management-api/request_api/utils/enums.py b/request-management-api/request_api/utils/enums.py index 3e0c8d8a2..dd24a34b6 100644 --- a/request-management-api/request_api/utils/enums.py +++ b/request-management-api/request_api/utils/enums.py @@ -156,6 +156,8 @@ class StateName(Enum): deduplication = "Deduplication" harmsassessment = "Harms Assessment" response = "Response" + tagging = "Tagging" + readytoscan ="Ready to Scan" class CacheUrls(Enum): keycloakusers= "/api/foiassignees" From dfd9b4fc2ebc18a7a3fcd011fd5b9f7c8f7188ee Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Thu, 5 Oct 2023 20:45:04 -0700 Subject: [PATCH 211/234] #4362 CFD MSD Stop clock messaging on pop up --- .../FOI/customComponents/ConfirmationModal/util.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js index 0e4f62a2b..55cfeeedd 100644 --- a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js +++ b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js @@ -50,10 +50,10 @@ import { getFullnameList } from "../../../../helper/FOI/helper"; return {title: "Changing the state", body: "Are you sure you want to change the state to Intake in Progress?"}; case StateEnum.peerreview.name.toLowerCase(): return {title: "Changing the state", body: "Are you sure you want to change the state to Peer Review?"}; - case StateEnum.tagging.name.toLowerCase(): - return {title: "Changing the state", body: "Are you sure you want to change the state to Tagging?"}; + case StateEnum.tagging.name.toLowerCase(): + return {title: "Changing the state", body: <>Are you sure you want to change the state to Tagging?
    This action will stop the clock.}; case StateEnum.readytoscan.name.toLowerCase(): - return {title: "Changing the state", body: "Are you sure you want to change the state to Ready to Scan?"}; + return {title: "Changing the state", body: <>Are you sure you want to change the state to Ready to Scan?
    This action will stop the clock.}; case StateEnum.open.name.toLowerCase(): return {title: "Changing the state", body: "Are you sure you want to Open this request?"}; case StateEnum.closed.name.toLowerCase(): From 8b609f00498da432694f70b997d461b7f9d0dd9f Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Fri, 6 Oct 2023 12:26:00 -0700 Subject: [PATCH 212/234] getonholdtransition update #4362 --- .../services/foirequest/requestservicegetter.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/request-management-api/request_api/services/foirequest/requestservicegetter.py b/request-management-api/request_api/services/foirequest/requestservicegetter.py index e5ae2c249..3d9e9f5a8 100644 --- a/request-management-api/request_api/services/foirequest/requestservicegetter.py +++ b/request-management-api/request_api/services/foirequest/requestservicegetter.py @@ -210,14 +210,15 @@ def getdivisions(self, ministrydivisions): def getonholdtransition(self, foiministryrequestid): - onholddate = None + onholddate = None transitions = FOIMinistryRequest.getrequeststatusById(foiministryrequestid) for entry in transitions: - if entry['requeststatusid'] == 11: - onholddate = datetimehandler().convert_to_pst(entry['created_at'],'%Y-%m-%d') - else: + if (entry['requeststatusid'] == 11 or entry['requeststatusid'] == 17 or entry['requeststatusid'] == 18): + onholddate = datetimehandler().convert_to_pst(entry['created_at'],'%Y-%m-%d') + else: if onholddate is not None: break + return onholddate def getministryrequest(self, foiministryrequestid): From 9f7f418394cea61ba8cfd2fd498cfab4c9b4b9dd Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Fri, 6 Oct 2023 15:37:29 -0700 Subject: [PATCH 213/234] Bug Fixed. Validation for divisions being attributed/tagged to records had a small issue. Validation to find all records with assosiated dvision reutrns all versions which gave incorrect view of divisions attached to records or not --- .../request_api/models/FOIRequestRecords.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/request-management-api/request_api/models/FOIRequestRecords.py b/request-management-api/request_api/models/FOIRequestRecords.py index 444a3dc12..7ece06643 100644 --- a/request-management-api/request_api/models/FOIRequestRecords.py +++ b/request-management-api/request_api/models/FOIRequestRecords.py @@ -131,10 +131,12 @@ def getrecordsbyid(cls, recordids): def get_all_records_by_divisionid(cls, divisionid): records = [] try: - sql = """SELECT * FROM public."FOIRequestRecords" WHERE cast(attributes::json -> 'divisions' as text) like '%{"divisionid": """+ divisionid +"""}%' and isactive = 'true'""" + sql = """SELECT * FROM (SELECT DISTINCT ON (recordid) recordid, version, foirequestid, ministryrequestid, filename, attributes, isactive, replacementof + FROM public."FOIRequestRecords" ORDER BY recordid ASC, version DESC) records + WHERE cast(records.attributes::json -> 'divisions' as text) like '%{"divisionid": """+ divisionid +"""}%' and isactive = 'true'""" result = db.session.execute(text(sql)) for row in result: - records.append({"recordid": row["recordid"], "foirequestid": row["foirequestid"], "ministryrequestid": row["ministryrequestid"], "filename": row["filename"], "s3uripath": row["s3uripath"], "attributes": row["attributes"], "isactive": row["isactive"], "createdby": row["createdby"], "created_at": row["created_at"]}) + records.append({"recordid": row["recordid"], "foirequestid": row["foirequestid"], "ministryrequestid": row["ministryrequestid"], "filename": row["filename"], "attributes": row["attributes"], "isactive": row["isactive"], "replacementof": row["replacementof"]}) except Exception as ex: logging.error(ex) raise ex From da0427c317623d1d27cefd1888979377450272fb Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Fri, 6 Oct 2023 16:36:17 -0700 Subject: [PATCH 214/234] Adjusted SQL to see if divisionid: id in divisions array object (more specific than before as divisions array object can hold more inputs --- request-management-api/request_api/models/FOIRequestRecords.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/request-management-api/request_api/models/FOIRequestRecords.py b/request-management-api/request_api/models/FOIRequestRecords.py index 7ece06643..857e6e1f8 100644 --- a/request-management-api/request_api/models/FOIRequestRecords.py +++ b/request-management-api/request_api/models/FOIRequestRecords.py @@ -133,7 +133,7 @@ def get_all_records_by_divisionid(cls, divisionid): try: sql = """SELECT * FROM (SELECT DISTINCT ON (recordid) recordid, version, foirequestid, ministryrequestid, filename, attributes, isactive, replacementof FROM public."FOIRequestRecords" ORDER BY recordid ASC, version DESC) records - WHERE cast(records.attributes::json -> 'divisions' as text) like '%{"divisionid": """+ divisionid +"""}%' and isactive = 'true'""" + WHERE cast(records.attributes::json -> 'divisions' as text) like '%{%"divisionid": """+ divisionid +"""%}%' and isactive = 'true'""" result = db.session.execute(text(sql)) for row in result: records.append({"recordid": row["recordid"], "foirequestid": row["foirequestid"], "ministryrequestid": row["ministryrequestid"], "filename": row["filename"], "attributes": row["attributes"], "isactive": row["isactive"], "replacementof": row["replacementof"]}) From 4b9b112e4bae3bb990a78838ea6bbcaf68686196 Mon Sep 17 00:00:00 2001 From: Abin Antony <78570775+abin-aot@users.noreply.github.com> Date: Fri, 6 Oct 2023 19:31:45 -0700 Subject: [PATCH 215/234] Revert "#4362 - Scanning, Tagging Clock Stop" --- .../src/components/FOI/Dashboard/Ministry/Queue.js | 4 ++-- forms-flow-web/src/components/FOI/Dashboard/utils.js | 6 +++--- .../FOI/customComponents/ConfirmationModal/util.js | 6 +++--- .../request_api/models/FOIMinistryRequests.py | 6 +++--- .../services/foirequest/requestservicegetter.py | 9 ++++----- .../request_api/services/requestservice.py | 2 +- request-management-api/request_api/utils/enums.py | 2 -- 7 files changed, 16 insertions(+), 19 deletions(-) diff --git a/forms-flow-web/src/components/FOI/Dashboard/Ministry/Queue.js b/forms-flow-web/src/components/FOI/Dashboard/Ministry/Queue.js index 176940645..7f775e9bb 100644 --- a/forms-flow-web/src/components/FOI/Dashboard/Ministry/Queue.js +++ b/forms-flow-web/src/components/FOI/Dashboard/Ministry/Queue.js @@ -90,7 +90,7 @@ const Queue = ({ userDetail, tableInfo }) => { function getRecordsDue(params) { let receivedDateString = params.row.cfrduedate; const currentStatus = params.row.currentState; - if (currentStatus.toLowerCase() === StateEnum.onhold.name.toLowerCase() || currentStatus.toLowerCase() === StateEnum.tagging.name.toLowerCase() || currentStatus.toLowerCase() === StateEnum.readytoscan.name.toLowerCase()) { + if (currentStatus.toLowerCase() === StateEnum.onhold.name.toLowerCase()) { return "N/A"; } else if(!receivedDateString) { return ""; @@ -102,7 +102,7 @@ const Queue = ({ userDetail, tableInfo }) => { function getLDD(params) { let receivedDateString = params.row.duedate; const currentStatus = params.row.currentState; - if (currentStatus.toLowerCase() === StateEnum.onhold.name.toLowerCase() || currentStatus.toLowerCase() === StateEnum.tagging.name.toLowerCase() || currentStatus.toLowerCase() === StateEnum.readytoscan.name.toLowerCase()) { + if (currentStatus.toLowerCase() === StateEnum.onhold.name.toLowerCase()) { return "N/A"; } else if(!receivedDateString) { return ""; diff --git a/forms-flow-web/src/components/FOI/Dashboard/utils.js b/forms-flow-web/src/components/FOI/Dashboard/utils.js index 1a4693aaf..0bb6d1a6a 100644 --- a/forms-flow-web/src/components/FOI/Dashboard/utils.js +++ b/forms-flow-web/src/components/FOI/Dashboard/utils.js @@ -132,7 +132,7 @@ export const onBehalfFullName = (params) => { export const getRecordsDue = (params) => { let receivedDateString = params.row.cfrduedate; const currentStatus = params.row.currentState; - if (currentStatus.toLowerCase() === StateEnum.onhold.name.toLowerCase() || currentStatus.toLowerCase() === StateEnum.tagging.name.toLowerCase() || currentStatus.toLowerCase() === StateEnum.readytoscan.name.toLowerCase()) { + if (currentStatus.toLowerCase() === StateEnum.onhold.name.toLowerCase()) { return "N/A"; } else if(!receivedDateString) { return ""; @@ -144,7 +144,7 @@ export const getRecordsDue = (params) => { export const getLDD = (params) => { let receivedDateString = params.row.duedate; const currentStatus = params.row.currentState; - if (currentStatus.toLowerCase() === StateEnum.onhold.name.toLowerCase() || currentStatus.toLowerCase() === StateEnum.tagging.name.toLowerCase() || currentStatus.toLowerCase() === StateEnum.readytoscan.name.toLowerCase()) { + if (currentStatus.toLowerCase() === StateEnum.onhold.name.toLowerCase()) { return "N/A"; } else if(!receivedDateString) { return ""; @@ -157,7 +157,7 @@ export const getDaysLeft = (params) => { const receivedDateString = params.row.duedate; if ( - [StateEnum.onhold.name.toLowerCase(), StateEnum.tagging.name.toLowerCase(), StateEnum.readytoscan.name.toLowerCase(),StateEnum.closed.name.toLowerCase()].includes(params.row.currentState.toLowerCase()) + [StateEnum.onhold.name.toLowerCase(), StateEnum.closed.name.toLowerCase()].includes(params.row.currentState.toLowerCase()) ) { return "N/A"; } else if(!receivedDateString) { diff --git a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js index 55cfeeedd..0e4f62a2b 100644 --- a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js +++ b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js @@ -50,10 +50,10 @@ import { getFullnameList } from "../../../../helper/FOI/helper"; return {title: "Changing the state", body: "Are you sure you want to change the state to Intake in Progress?"}; case StateEnum.peerreview.name.toLowerCase(): return {title: "Changing the state", body: "Are you sure you want to change the state to Peer Review?"}; - case StateEnum.tagging.name.toLowerCase(): - return {title: "Changing the state", body: <>Are you sure you want to change the state to Tagging?
    This action will stop the clock.}; + case StateEnum.tagging.name.toLowerCase(): + return {title: "Changing the state", body: "Are you sure you want to change the state to Tagging?"}; case StateEnum.readytoscan.name.toLowerCase(): - return {title: "Changing the state", body: <>Are you sure you want to change the state to Ready to Scan?
    This action will stop the clock.}; + return {title: "Changing the state", body: "Are you sure you want to change the state to Ready to Scan?"}; case StateEnum.open.name.toLowerCase(): return {title: "Changing the state", body: "Are you sure you want to Open this request?"}; case StateEnum.closed.name.toLowerCase(): diff --git a/request-management-api/request_api/models/FOIMinistryRequests.py b/request-management-api/request_api/models/FOIMinistryRequests.py index 85b9bc418..cd6e2a8e9 100644 --- a/request-management-api/request_api/models/FOIMinistryRequests.py +++ b/request-management-api/request_api/models/FOIMinistryRequests.py @@ -283,7 +283,7 @@ def getlastoffholddate(cls, ministryrequestid): recent_offhold_index = None offhold_indicator = False for entry in desc_transitions: - if (entry["status"] == StateName.onhold.value or entry["status"] == StateName.readytoscan.value or entry["status"] == StateName.tagging.value): + if entry["status"] == StateName.onhold.value: onhold_occurance = onhold_occurance + 1 if onhold_occurance > 1: recent_offhold_index = index @@ -737,7 +737,7 @@ def getupcominglegislativeduerecords(cls): upcomingduerecords = [] try: sql = """select distinct on (filenumber) filenumber, to_char(duedate, 'YYYY-MM-DD') as duedate, foiministryrequestid, version, foirequest_id, created_at, createdby from "FOIMinistryRequests" fpa - where isactive = true and duedate is not null and requeststatusid not in (5,6,4,11,3,15,17,18) + where isactive = true and duedate is not null and requeststatusid not in (5,6,4,11,3,15) and duedate between NOW() - INTERVAL '7 DAY' AND NOW() + INTERVAL '7 DAY' order by filenumber , version desc;""" rs = db.session.execute(text(sql)) @@ -760,7 +760,7 @@ def getupcomingdivisionduerecords(cls): from "FOIMinistryRequestDivisions" frd inner join (select distinct on (fpa.foiministryrequestid) foiministryrequestid, version as foiministryrequestversion, axisrequestid, filenumber, foirequest_id, requeststatusid from "FOIMinistryRequests" fpa - order by fpa.foiministryrequestid , fpa.version desc) fma on frd.foiministryrequest_id = fma.foiministryrequestid and frd.foiministryrequestversion_id = fma.foiministryrequestversion and fma.requeststatusid not in (5,6,4,11,3,15,17,18) + order by fpa.foiministryrequestid , fpa.version desc) fma on frd.foiministryrequest_id = fma.foiministryrequestid and frd.foiministryrequestversion_id = fma.foiministryrequestversion and fma.requeststatusid not in (5,6,4,11,3,15) inner join "ProgramAreaDivisions" pad2 on frd.divisionid = pad2.divisionid inner join "ProgramAreaDivisionStages" pads on frd.stageid = pads.stageid and frd.stageid in (5, 7, 9) and frd.divisionduedate between NOW() - INTERVAL '7 DAY' AND NOW() + INTERVAL '7 DAY' diff --git a/request-management-api/request_api/services/foirequest/requestservicegetter.py b/request-management-api/request_api/services/foirequest/requestservicegetter.py index 3d9e9f5a8..e5ae2c249 100644 --- a/request-management-api/request_api/services/foirequest/requestservicegetter.py +++ b/request-management-api/request_api/services/foirequest/requestservicegetter.py @@ -210,15 +210,14 @@ def getdivisions(self, ministrydivisions): def getonholdtransition(self, foiministryrequestid): - onholddate = None + onholddate = None transitions = FOIMinistryRequest.getrequeststatusById(foiministryrequestid) for entry in transitions: - if (entry['requeststatusid'] == 11 or entry['requeststatusid'] == 17 or entry['requeststatusid'] == 18): - onholddate = datetimehandler().convert_to_pst(entry['created_at'],'%Y-%m-%d') - else: + if entry['requeststatusid'] == 11: + onholddate = datetimehandler().convert_to_pst(entry['created_at'],'%Y-%m-%d') + else: if onholddate is not None: break - return onholddate def getministryrequest(self, foiministryrequestid): diff --git a/request-management-api/request_api/services/requestservice.py b/request-management-api/request_api/services/requestservice.py index d81ecea99..83d6bf039 100644 --- a/request-management-api/request_api/services/requestservice.py +++ b/request-management-api/request_api/services/requestservice.py @@ -62,7 +62,7 @@ def updateduedate(self, requestid, ministryrequestid, offholddate, foirequestsch foirequest = self.getrequest(requestid, ministryrequestid) currentstatus = foirequest["stateTransition"][0]["status"] if "stateTransition" in foirequest and len(foirequest["stateTransition"]) > 1 else None #Check for Off Hold - if currentstatus not in (None, "") and (currentstatus == StateName.onhold.value or currentstatus == StateName.tagging.value or currentstatus == StateName.readytoscan.value) and nextstatename != StateName.response.value: + if currentstatus not in (None, "") and currentstatus == StateName.onhold.value and nextstatename != StateName.response.value: skipcalculation = self.__skipduedatecalculation(ministryrequestid, offholddate) #Skip multiple off hold in a day if skipcalculation == True: diff --git a/request-management-api/request_api/utils/enums.py b/request-management-api/request_api/utils/enums.py index dd24a34b6..3e0c8d8a2 100644 --- a/request-management-api/request_api/utils/enums.py +++ b/request-management-api/request_api/utils/enums.py @@ -156,8 +156,6 @@ class StateName(Enum): deduplication = "Deduplication" harmsassessment = "Harms Assessment" response = "Response" - tagging = "Tagging" - readytoscan ="Ready to Scan" class CacheUrls(Enum): keycloakusers= "/api/foiassignees" From 35549b0ee47004e23f7fcfc7d3186102b18ccafd Mon Sep 17 00:00:00 2001 From: Richard Qi Date: Mon, 9 Oct 2023 23:28:37 -0700 Subject: [PATCH 216/234] show selected divisions + sections --- .../Attachments/AttachmentModal.js | 3 +- .../FileUpload/FileUploadForMCFPersonal.js | 23 +++++++++++++++ .../customComponents/Records/MCFPersonal.js | 29 ++++++++++++++++++- .../FOI/customComponents/Records/index.js | 1 + 4 files changed, 54 insertions(+), 2 deletions(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js b/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js index 130ada26a..1590e0f4f 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js +++ b/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js @@ -207,7 +207,7 @@ export default function AttachmentModal({ fileStatusTransition = attachment?.category; } else if (uploadFor === "record") { if(bcgovcode == "MCF" && requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL) { - fileStatusTransition = MCFSections?.sections?.find(division => division.divisionid === tagValue).name; + fileStatusTransition = divisions.find(division => division.divisionid === tagValue)?.divisionname || MCFSections?.sections?.find(division => division.divisionid === tagValue)?.name; } else if(bcgovcode == "MSD" && requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL) { fileStatusTransition = MSDSections?.sections?.find(division => division.divisionid === tagValue).name; } else { @@ -366,6 +366,7 @@ export default function AttachmentModal({ updateFilesCb={updateFilesCb} modalFor={modalFor} uploadFor={uploadFor} + divisions={tagList} tagList={MCFSections?.sections?.slice(0, MCFPopularSections-1)} otherTagList={MCFSections?.sections?.slice(MCFPopularSections)} handleTagChange={handleTagChange} diff --git a/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMCFPersonal.js b/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMCFPersonal.js index 433122eda..90c781b09 100644 --- a/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMCFPersonal.js +++ b/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMCFPersonal.js @@ -35,6 +35,7 @@ const FileUploadForMCFPersonal = ({ modalFor, handleTagChange, tagValue, + divisions = [], tagList = [], otherTagList = [], isMinistryCoordinator, @@ -226,6 +227,28 @@ const FileUploadForMCFPersonal = ({
    Select the name of the section of records you are uploading. Once you have selected the section name you will be able to select the respective documents from your computer.
    + {divisions.length > 0 && (<> +
    + Personals Divisional Tracking: +
    +
    + {divisions.map(tag => + {handleTagChange(tag.name)}} + clicked={tagValue == tag.name} + /> + )} +
    + )} +
    + Sections: +
    {tagList.map(tag => { const [searchValue, setSearchValue] = useState(""); @@ -59,6 +60,32 @@ const MCFPersonal = ({ return ( <>
    + + {divisions.length > 0 && divisions.filter(div => div.divisionid !== tagValue).length > 0 && (<> +
    + Personals Divisional Tracking: +
    +
    + {divisions.filter(div => { + return div.divisionid !== tagValue; + }).map(tag => + {setNewDivision(tag.divisionid)}} + clicked={divisionModalTagValue == tag.divisionid} + /> + )} +
    +
    + Sections: +
    + )} +
    {tagList.filter(div => { return div.divisionid !== tagValue; diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/index.js b/forms-flow-web/src/components/FOI/customComponents/Records/index.js index 643b99c15..4d7cc47f9 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/index.js @@ -2015,6 +2015,7 @@ export const RecordsLog = ({ setNewDivision={setDivisionModalTagValue} tagValue={records.filter(r => r.isselected)[0]?.attributes.divisions[0].divisionid} divisionModalTagValue={divisionModalTagValue} + divisions={divisions} /> : (bcgovcode == "MSD") ? From f47803cd27297f13c84634580ab33993aebf40d7 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Tue, 10 Oct 2023 10:31:06 -0700 Subject: [PATCH 217/234] Adjusted code to fix possible bug due to % placement --- request-management-api/request_api/models/FOIRequestRecords.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/request-management-api/request_api/models/FOIRequestRecords.py b/request-management-api/request_api/models/FOIRequestRecords.py index 857e6e1f8..a0ad9eebb 100644 --- a/request-management-api/request_api/models/FOIRequestRecords.py +++ b/request-management-api/request_api/models/FOIRequestRecords.py @@ -133,7 +133,8 @@ def get_all_records_by_divisionid(cls, divisionid): try: sql = """SELECT * FROM (SELECT DISTINCT ON (recordid) recordid, version, foirequestid, ministryrequestid, filename, attributes, isactive, replacementof FROM public."FOIRequestRecords" ORDER BY recordid ASC, version DESC) records - WHERE cast(records.attributes::json -> 'divisions' as text) like '%{%"divisionid": """+ divisionid +"""%}%' and isactive = 'true'""" + WHERE cast(records.attributes::json -> 'divisions' as text) like '%{"divisionid": """+ divisionid +"""}%' and isactive = 'true' OR cast(records.attributes::json -> 'divisions' as text) like '%{"divisionid": """+ divisionid +""", %}%' and isactive = 'true' + """ result = db.session.execute(text(sql)) for row in result: records.append({"recordid": row["recordid"], "foirequestid": row["foirequestid"], "ministryrequestid": row["ministryrequestid"], "filename": row["filename"], "attributes": row["attributes"], "isactive": row["isactive"], "replacementof": row["replacementof"]}) From 30ad612611b44f073a46f56366db908183492f8d Mon Sep 17 00:00:00 2001 From: Richard Qi Date: Tue, 10 Oct 2023 13:27:56 -0700 Subject: [PATCH 218/234] 1. get personal divisions 2. scanning team can only select sections 3. ministry can only select divisions --- .../src/apiManager/endpoints/index.js | 1 + .../services/FOI/foiMasterDataServices.js | 24 +++++++++++++++++++ .../MinistryReview/MinistryReview.js | 1 + .../MinistryReview/RequestTracking.js | 14 +++++++---- .../FileUpload/FileUploadForMCFPersonal.js | 10 +++----- .../customComponents/Records/MCFPersonal.js | 13 ++++------ .../FOI/customComponents/Records/index.js | 1 + 7 files changed, 45 insertions(+), 19 deletions(-) diff --git a/forms-flow-web/src/apiManager/endpoints/index.js b/forms-flow-web/src/apiManager/endpoints/index.js index 9bb4b9655..81994417e 100644 --- a/forms-flow-web/src/apiManager/endpoints/index.js +++ b/forms-flow-web/src/apiManager/endpoints/index.js @@ -28,6 +28,7 @@ const API = { FOI_MINISTRY_DIVISIONALSTAGES: `${FOI_BASE_API_URL}/api/foiflow/divisions/`, FOI_PERSONAL_DIVISIONS_SECTIONS: `${FOI_BASE_API_URL}/api/foiflow/divisions//true/divisionsandsections`, FOI_PERSONAL_SECTIONS: `${FOI_BASE_API_URL}/api/foiflow/divisions//true/sections`, + FOI_PERSONAL_DIVISIONS: `${FOI_BASE_API_URL}/api/foiflow/divisions//true/divisions`, FOI_POST_RAW_REQUEST_WATCHERS: `${FOI_BASE_API_URL}/api/foiwatcher/rawrequest`, FOI_GET_RAW_REQUEST_WATCHERS: `${FOI_BASE_API_URL}/api/foiwatcher/rawrequest/`, FOI_POST_MINISTRY_REQUEST_WATCHERS: `${FOI_BASE_API_URL}/api/foiwatcher/ministryrequest`, diff --git a/forms-flow-web/src/apiManager/services/FOI/foiMasterDataServices.js b/forms-flow-web/src/apiManager/services/FOI/foiMasterDataServices.js index fab69149f..b8eb8d385 100644 --- a/forms-flow-web/src/apiManager/services/FOI/foiMasterDataServices.js +++ b/forms-flow-web/src/apiManager/services/FOI/foiMasterDataServices.js @@ -414,6 +414,30 @@ import { } }; + export const fetchFOIPersonalDivisions = (bcgovcode) => { + const apiUrl = replaceUrl(API.FOI_PERSONAL_DIVISIONS, "", bcgovcode); + return (dispatch) => { + httpGETRequest(apiUrl, {}, UserService.getToken()) + .then((res) => { + if (res.data) { + const foiMinistryDivisionalStages = res.data; + dispatch(setFOIMinistryDivisionalStages({})); + dispatch(setFOIMinistryDivisionalStages(foiMinistryDivisionalStages)); + dispatch(setFOILoader(false)); + } else { + console.log(`Error while fetching ministry(${bcgovcode}) divisional stage master data`, res); + dispatch(serviceActionError(res)); + dispatch(setFOILoader(false)); + } + }) + .catch((error) => { + console.log(`Error while fetching ministry(${bcgovcode}) divisional stage master data`, error); + dispatch(serviceActionError(error)); + dispatch(setFOILoader(false)); + }); + }; + }; + export const fetchFOISubjectCodeList = () => { const firstSubjectCode = { "subjectcodeid": 0, "name": "Select Subject Code (if required)" }; return (dispatch) => { diff --git a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js index bfa3672b4..3161beb15 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js @@ -533,6 +533,7 @@ const MinistryReview = React.memo(({ userDetail }) => { createMinistrySaveRequestObject={createMinistrySaveRequestObject} requestStartDate = {requestDetails?.requestProcessStart} setHasReceivedDate={setHasReceivedDate} + requestType={requestDetails.requestType} /> ); diff --git a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/RequestTracking.js b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/RequestTracking.js index 1f8b1198d..92b6d180d 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/RequestTracking.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/RequestTracking.js @@ -3,21 +3,27 @@ import Card from '@material-ui/core/Card'; import CardContent from '@material-ui/core/CardContent'; import DivisionalStages from './Divisions/DivisionalStages'; import { useDispatch, useSelector } from "react-redux"; -import { fetchFOIMinistryDivisionalStages } from "../../../../apiManager/services/FOI/foiMasterDataServices"; +import FOI_COMPONENT_CONSTANTS from "../../../../constants/FOI/foiComponentConstants"; +import { fetchFOIMinistryDivisionalStages, fetchFOIPersonalDivisions } from "../../../../apiManager/services/FOI/foiMasterDataServices"; const RequestTracking = React.memo(({ pubmindivstagestomain, existingDivStages, ministrycode, createMinistrySaveRequestObject, requestStartDate, - setHasReceivedDate + setHasReceivedDate, + requestType }) => { const dispatch = useDispatch(); - useEffect(() => { + useEffect(() => { if(ministrycode) { - dispatch(fetchFOIMinistryDivisionalStages(ministrycode)); + if(ministrycode == "MCF" && requestType == FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL) { + dispatch(fetchFOIPersonalDivisions(ministrycode)); + } else { + dispatch(fetchFOIMinistryDivisionalStages(ministrycode)); + } } },[ministrycode,dispatch]) diff --git a/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMCFPersonal.js b/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMCFPersonal.js index 90c781b09..8fb287e9b 100644 --- a/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMCFPersonal.js +++ b/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMCFPersonal.js @@ -227,10 +227,7 @@ const FileUploadForMCFPersonal = ({
    Select the name of the section of records you are uploading. Once you have selected the section name you will be able to select the respective documents from your computer.
    - {divisions.length > 0 && (<> -
    - Personals Divisional Tracking: -
    + {divisions.length > 0 && isMinistryCoordinator && (<>
    {divisions.map(tag => )} -
    - Sections: -
    + {!isMinistryCoordinator && (<>
    {tagList.map(tag => )}
    + )}
    )} {modalFor === "add" && (

    Please drag and drop or add records associated with the section name you have selected above. All records upload will show under the selected section in the redaction application.

    diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/MCFPersonal.js b/forms-flow-web/src/components/FOI/customComponents/Records/MCFPersonal.js index 2ced8e743..371abf4dc 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/MCFPersonal.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/MCFPersonal.js @@ -17,7 +17,8 @@ const MCFPersonal = ({ setNewDivision, tagValue, divisionModalTagValue, - divisions=[] + divisions=[], + isMinistryCoordinator }) => { const [searchValue, setSearchValue] = useState(""); @@ -61,10 +62,7 @@ const MCFPersonal = ({ <>
    - {divisions.length > 0 && divisions.filter(div => div.divisionid !== tagValue).length > 0 && (<> -
    - Personals Divisional Tracking: -
    + {isMinistryCoordinator && divisions.length > 0 && divisions.filter(div => div.divisionid !== tagValue).length > 0 && (<>
    {divisions.filter(div => { return div.divisionid !== tagValue; @@ -81,11 +79,9 @@ const MCFPersonal = ({ /> )}
    -
    - Sections: -
    )} + {!isMinistryCoordinator && (<>
    {tagList.filter(div => { return div.divisionid !== tagValue; @@ -202,6 +198,7 @@ const MCFPersonal = ({ )}
    + )}
    ); diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/index.js b/forms-flow-web/src/components/FOI/customComponents/Records/index.js index 4d7cc47f9..8c850a75f 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/index.js @@ -2016,6 +2016,7 @@ export const RecordsLog = ({ tagValue={records.filter(r => r.isselected)[0]?.attributes.divisions[0].divisionid} divisionModalTagValue={divisionModalTagValue} divisions={divisions} + isMinistryCoordinator={isMinistryCoordinator} /> : (bcgovcode == "MSD") ? From 9038b3bb101a588782c7ddc829198f4dc8e11741 Mon Sep 17 00:00:00 2001 From: Richard Qi Date: Tue, 10 Oct 2023 23:30:59 -0700 Subject: [PATCH 219/234] can only edit records uploaded by the same team --- .../FOI/customComponents/Records/index.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/index.js b/forms-flow-web/src/components/FOI/customComponents/Records/index.js index 8c850a75f..9453cdc79 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/index.js @@ -256,6 +256,11 @@ export const RecordsLog = ({ const [records, setRecords] = useState(recordsObj?.records); const [totalUploadedRecordSize, setTotalUploadedRecordSize] = useState(0); const [isScanningTeamMember, setIsScanningTeamMember] = useState(isScanningTeam(userGroups)); + const [ministryCode, setMinistryCode] = useState(bcgovcode.replaceAll('"', '').toUpperCase()); + const [isMCFPersonal, setIsMCFPersonal] = useState(ministryCode == "MCF" && requestType === FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL); + + const MCFSections = useSelector((state) => state.foiRequests.foiPersonalSections); + useEffect(() => { setRecords(recordsObj?.records) let nonDuplicateRecords = recordsObj?.records?.filter(record => !record.isduplicate) @@ -1328,6 +1333,14 @@ export const RecordsLog = ({ } } // setDivisionToUpdate() + + if(isMCFPersonal && selectedDivision.size > 0) { + if(divisions.find(d => selectedDivision.has(d.divisionid))) { + return(!isMinistryCoordinator); + } else { + return(isMinistryCoordinator); + } + } return count === 0; }; @@ -1813,6 +1826,11 @@ export const RecordsLog = ({ {" "} and all records selected must be finished processing + {isMCFPersonal && (<> +
  • + you can only edit records uploaded by your team +
  • + )}
    ) : ( From 33155f87abb2d0af64249b1869ef47ad02cf19c0 Mon Sep 17 00:00:00 2001 From: Richard Qi Date: Wed, 11 Oct 2023 09:18:42 -0700 Subject: [PATCH 220/234] update message --- .../src/components/FOI/customComponents/Records/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/index.js b/forms-flow-web/src/components/FOI/customComponents/Records/index.js index 9453cdc79..2779c1e30 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/index.js @@ -1828,7 +1828,7 @@ export const RecordsLog = ({ {isMCFPersonal && (<>
  • - you can only edit records uploaded by your team + all records selected must be uploaded by your own team
  • )} From 4d6059a66d5dd62150074880e4d955b6ca05272a Mon Sep 17 00:00:00 2001 From: Richard Qi Date: Thu, 12 Oct 2023 14:57:54 -0700 Subject: [PATCH 221/234] update title for ministry user --- .../FOI/customComponents/Attachments/AttachmentModal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js b/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js index 1590e0f4f..77204dfe1 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js +++ b/forms-flow-web/src/components/FOI/customComponents/Attachments/AttachmentModal.js @@ -235,7 +235,7 @@ export default function AttachmentModal({ const getMessage = () => { switch(modalFor.toLowerCase()) { case "add": - if(isMCFMSDPersonal) { + if(isMCFMSDPersonal && !isMinistryCoordinator) { return {title: "Add Scanned Records", body: ""}; } else From 52196efb87bcc23a841ddd36475a4fb9e360f6f9 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 13 Oct 2023 10:25:50 -0700 Subject: [PATCH 222/234] remove set records loader when fetching sections --- .../src/apiManager/services/FOI/foiRecordServices.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/forms-flow-web/src/apiManager/services/FOI/foiRecordServices.js b/forms-flow-web/src/apiManager/services/FOI/foiRecordServices.js index f423ec2f4..b729be708 100644 --- a/forms-flow-web/src/apiManager/services/FOI/foiRecordServices.js +++ b/forms-flow-web/src/apiManager/services/FOI/foiRecordServices.js @@ -170,22 +170,18 @@ export const fetchRedactedSections = (ministryId, ...rest) => { ministryId ); return (dispatch) => { - dispatch(setRecordsLoader("inprogress")); httpGETRequest(apiUrl, {}, UserService.getToken()) .then((res) => { if (res.data) { - dispatch(setRecordsLoader("completed")); done(null, res.data); } else { console.log("Error in fetching redacted sections", res); dispatch(serviceActionError(res)); - dispatch(setRecordsLoader("error")); } }) .catch((error) => { console.log("Error in fetching redacted section", error); dispatch(serviceActionError(error)); - dispatch(setRecordsLoader("error")); done(error); }); }; From 2ec6694a01bfd152204433c9f8e781fbb86ba5b4 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Fri, 13 Oct 2023 15:30:13 -0700 Subject: [PATCH 223/234] #4362 updated review statelist --- .../src/components/FOI/customComponents/StateDropDown.js | 2 ++ forms-flow-web/src/constants/FOI/statusEnum.js | 1 + 2 files changed, 3 insertions(+) diff --git a/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js b/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js index bdc953935..52f6f4e66 100644 --- a/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js +++ b/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js @@ -138,6 +138,8 @@ const StateDropDown = ({ case StateEnum.readytoscan.name.toLowerCase(): return _stateList.readytoscan; case StateEnum.review.name.toLowerCase(): + if(personalIAO && (requestDetails.bcgovcode.toLowerCase() === "mcf" || requestDetails.bcgovcode.toLowerCase() === "msd")) + return _stateList.reviewcfdmsdpersonal return _stateList.review; case StateEnum.onhold.name.toLowerCase(): return _stateList.onhold; diff --git a/forms-flow-web/src/constants/FOI/statusEnum.js b/forms-flow-web/src/constants/FOI/statusEnum.js index faacf3482..0a00f89e1 100644 --- a/forms-flow-web/src/constants/FOI/statusEnum.js +++ b/forms-flow-web/src/constants/FOI/statusEnum.js @@ -14,6 +14,7 @@ const StateList = Object.freeze({ harms: [{status: "Harms Assessment", isSelected: false}, {status: "Closed", isSelected: false}], consult: [{status: "Consult", isSelected: false}, {status: "Records Review", isSelected: false}, {status: "Ministry Sign Off", isSelected: false}, {status:"Peer Review", isSelected: false}, {status: "Closed", isSelected: false}], review: [{status: "Records Review", isSelected: false}, {status: "Call For Records", isSelected: false}, {status: "Consult", isSelected: false}, {status: "Ministry Sign Off", isSelected: false}, {status:"Peer Review", isSelected: false}, {status: "Response", isSelected: false}, {status: "Closed", isSelected: false}], + reviewcfdmsdpersonal: [{status: "Records Review", isSelected: false}, {status: "Call For Records", isSelected: false},{status: "Tagging", isSelected: false}, {status: "Consult", isSelected: false}, {status: "Ministry Sign Off", isSelected: false}, {status:"Peer Review", isSelected: false}, {status: "Response", isSelected: false}, {status: "Closed", isSelected: false}], signoff: [{status: "Ministry Sign Off", isSelected: false}, {status: "Closed", isSelected: false}], response: [{status: "Response", isSelected: false}, {status: "On Hold", isSelected: false}, {status: "Records Review", isSelected: false}, {status:"Peer Review", isSelected: false}, {status: "Closed", isSelected: false}], responseforpersonal: [{status: "Response", isSelected: false}, {status: "Records Review", isSelected: false}, {status:"Peer Review", isSelected: false}, {status: "Closed", isSelected: false}], From 71fe9a5906fca1fdcbe6f82b2156b4e0f8ed8c2e Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Tue, 17 Oct 2023 16:38:51 -0700 Subject: [PATCH 224/234] Adjusted KeyError exceptions to not access a value that does not exist. Will now look into other areas in request management api for exception handling with error objects + apply changes to docreviewer api --- .../request_api/resources/foiadmin.py | 18 ++++---- .../request_api/resources/foicfrfee.py | 8 ++-- .../request_api/resources/foicomment.py | 20 ++++----- .../request_api/resources/foidocument.py | 24 +++++----- .../request_api/resources/foiemail.py | 8 ++-- .../request_api/resources/foiextension.py | 24 +++++----- .../request_api/resources/foinotification.py | 16 +++---- .../request_api/resources/foipayment.py | 12 ++--- .../request_api/resources/foirecord.py | 44 +++++++++---------- .../request_api/resources/foirequest.py | 20 ++++----- .../request_api/resources/foiwatcher.py | 24 +++++----- .../request_api/resources/foiworkflow.py | 4 +- .../request_api/services/rawrequestservice.py | 2 +- .../request_api/services/workflowservice.py | 4 +- 14 files changed, 114 insertions(+), 114 deletions(-) diff --git a/request-management-api/request_api/resources/foiadmin.py b/request-management-api/request_api/resources/foiadmin.py index 27858c8e4..8e07a70dc 100644 --- a/request-management-api/request_api/resources/foiadmin.py +++ b/request-management-api/request_api/resources/foiadmin.py @@ -50,9 +50,9 @@ def get(): try: result = programareadivisionservice().getallprogramareadivisonsandsections() return json.dumps(result), 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 - except BusinessException as exception: + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 + except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @cors_preflight('POST,OPTIONS') @@ -71,8 +71,8 @@ def post(): programareadivisionschema = FOIProgramAreaDivisionSchema().load(requestjson) result = programareadivisionservice().createprogramareadivision(programareadivisionschema) return {'status': result.success, 'message':result.message, 'id':result.identifier}, 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -93,8 +93,8 @@ def put(divisionid): programareadivisionschema = FOIProgramAreaDivisionSchema().load(requestjson) result = programareadivisionservice().updateprogramareadivision(divisionid, programareadivisionschema, AuthHelper.getuserid()) return {'status': result.success, 'message':result.message, 'id':result.identifier}, 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -114,8 +114,8 @@ def put(divisionid): if result.success != True: return {'status': result.success, 'message': result.message, 'id':result.identifier}, 400 return {'status': result.success, 'message':result.message, 'id':result.identifier}, 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foicfrfee.py b/request-management-api/request_api/resources/foicfrfee.py index e8dad2f91..1305b20df 100644 --- a/request-management-api/request_api/resources/foicfrfee.py +++ b/request-management-api/request_api/resources/foicfrfee.py @@ -59,7 +59,7 @@ def post(requestid, ministryrequestid): logging.error(verr) return {'status': False, 'message':verr.messages}, 400 except KeyError as err: - logging.error(err) + logging.error(type(err)) return {'status': False, 'message': EXCEPTION_MESSAGE_BAD_REQUEST}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -89,7 +89,7 @@ def post(requestid, ministryrequestid): logging.error(verr) return {'status': False, 'message':verr.messages}, 400 except KeyError as err: - logging.error(err) + logging.error(type(err)) return {'status': False, 'message': EXCEPTION_MESSAGE_BAD_REQUEST}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -109,7 +109,7 @@ def get(requestid): try: result = {"current": cfrfeeservice().getcfrfee(requestid), "history": cfrfeeservice().getcfrfeehistory(requestid)} return json.dumps(result), 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foicomment.py b/request-management-api/request_api/resources/foicomment.py index f68db9c50..de88e08da 100644 --- a/request-management-api/request_api/resources/foicomment.py +++ b/request-management-api/request_api/resources/foicomment.py @@ -54,8 +54,8 @@ def post(): if result.success == True: asyncio.ensure_future(eventservice().postcommentevent(result.identifier, "ministryrequest", AuthHelper.getuserid())) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -77,8 +77,8 @@ def post(): if result.success == True: asyncio.ensure_future(eventservice().postcommentevent(result.identifier, "rawrequest", AuthHelper.getuserid())) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -108,8 +108,8 @@ def get(requesttype, requestid): return json.dumps(result), 200 else: return {'status': 401, 'message':'Restricted Request'} , 401 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -136,8 +136,8 @@ def put(requesttype, commentid): if result.success == True: asyncio.ensure_future(eventservice().postcommentevent(result.identifier, requesttype, AuthHelper.getuserid(), True)) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -166,8 +166,8 @@ def put(requesttype, commentid): if result.success == True: asyncio.ensure_future(eventservice().postcommentevent(commentid, requesttype, AuthHelper.getuserid(), existingtaggedusers=result.args[0])) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foidocument.py b/request-management-api/request_api/resources/foidocument.py index f9f5b629f..f7763a0bc 100644 --- a/request-management-api/request_api/resources/foidocument.py +++ b/request-management-api/request_api/resources/foidocument.py @@ -50,8 +50,8 @@ def get(requestid, requesttype): try: result = documentservice().getrequestdocumentsbyrole(requestid, requesttype, AuthHelper.isministrymember()) return json.dumps(result), 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -75,8 +75,8 @@ def post(requestid, requesttype): return {'status': result.success, 'message':result.message} , 200 except ValidationError as err: return {'status': False, 'message':err.messages}, 400 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -99,8 +99,8 @@ def post(requestid, documentid, requesttype): return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except ValidationError as err: return {'status': False, 'message':err.messages}, 400 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -137,8 +137,8 @@ def post(requesttype, requestid, documentid): return {'status': False, 'message': "Something went wrong moving the document's location" }, 500 except ValidationError as err: return {'status': False, 'message':err.messages}, 400 - except KeyError as err: - return {'status': False, 'message': err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -160,8 +160,8 @@ def post(requestid, documentid, requesttype): return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except ValidationError as err: return {'status': False, 'message':err.messages}, 400 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -180,7 +180,7 @@ def post(requestid, documentid, requesttype): try: result = documentservice().deleterequestdocument(requestid, documentid, AuthHelper.getuserid(), requesttype) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foiemail.py b/request-management-api/request_api/resources/foiemail.py index 0b2aa80b1..ca9cab211 100644 --- a/request-management-api/request_api/resources/foiemail.py +++ b/request-management-api/request_api/resources/foiemail.py @@ -51,8 +51,8 @@ def post(requestid, ministryrequestid, servicename): return json.dumps(result), 200 if result["success"] == True else 500 except ValueError as err: return {'status': 500, 'message':err.messages}, 500 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -72,8 +72,8 @@ def post(requestid, ministryrequestid, servicename): return json.dumps(result), 200 if result["success"] == True else 500 except ValueError as err: return {'status': 500, 'message':err.messages}, 500 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foiextension.py b/request-management-api/request_api/resources/foiextension.py index 86c9ef0d6..faab268a7 100644 --- a/request-management-api/request_api/resources/foiextension.py +++ b/request-management-api/request_api/resources/foiextension.py @@ -52,8 +52,8 @@ def get(requestid): try: extensionrecords = extensionservice().getrequestextensions(requestid) return json.dumps(extensionrecords), 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -71,8 +71,8 @@ def get(extensionid): try: extensionrecord = extensionservice().getrequestextension(extensionid) return json.dumps(extensionrecord), 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -96,8 +96,8 @@ def post(requestid, ministryrequestid): eventservice().posteventforextension(ministryrequestid, result.identifier, AuthHelper.getuserid(), AuthHelper.getusername(), "add") newduedate, = result.args return {'status': result.success, 'message':result.message,'id':result.identifier, 'newduedate': newduedate or None} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -121,8 +121,8 @@ def post(ministryrequestid): if len(result.args) > 0: eventservice().posteventforaxisextension(ministryrequestid, result.args[0], AuthHelper.getuserid(), AuthHelper.getusername(), "add") return {'status': result.success, 'message':result.message} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -145,8 +145,8 @@ def post(requestid, ministryrequestid, extensionid): # posteventforextension moved to createrequestextensionversion to generate the comments before updating the ministry table with new due date newduedate = result.args[-1] if len(result.args) > 0 else None return {'status': result.success, 'message':result.message,'id':result.identifier, 'newduedate': newduedate or None} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -167,7 +167,7 @@ def post(requestid, ministryrequestid, extensionid): eventservice().posteventforextension(ministryrequestid, extensionid, AuthHelper.getuserid(), AuthHelper.getusername(), "delete") newduedate = result.args[-1] if len(result.args) > 0 else None return {'status': result.success, 'message':result.message,'id':result.identifier, 'newduedate': newduedate or None} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foinotification.py b/request-management-api/request_api/resources/foinotification.py index a95fef873..1e597096d 100644 --- a/request-management-api/request_api/resources/foinotification.py +++ b/request-management-api/request_api/resources/foinotification.py @@ -47,8 +47,8 @@ def get(): return json.dumps(result), 200 except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -72,8 +72,8 @@ def delete(type=None,idnumber=None,notficationid=None): return {'status': result.success, 'message':result.message,'id':result.identifier} , 500 except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -92,8 +92,8 @@ def post(): reminderresponse = eventservice().postreminderevent() respcode = 200 if reminderresponse.success == True else 500 return {'status': reminderresponse.success, 'message':reminderresponse.message,'id': reminderresponse.identifier} , respcode - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -113,7 +113,7 @@ def post(request_id: int, ministry_request_id: int): reminderresponse = eventservice().postpaymentexpiryevent(ministry_request_id) respcode = 200 if reminderresponse.success == True else 500 return {'status': reminderresponse.success, 'message':reminderresponse.message,'id': reminderresponse.identifier} , respcode - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foipayment.py b/request-management-api/request_api/resources/foipayment.py index d5e7be7b5..da3d73399 100644 --- a/request-management-api/request_api/resources/foipayment.py +++ b/request-management-api/request_api/resources/foipayment.py @@ -48,8 +48,8 @@ def post(requestid, ministryrequestid): paymentschema = FOIRequestPaymentSchema().load(requestjson) result = paymentservice().createpayment(requestid, ministryrequestid, paymentschema) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -69,8 +69,8 @@ def post(requestid, ministryrequestid): paymentschema = FOIRequestPaymentSchema().load(requestjson) result = paymentservice().cancelpayment(requestid, ministryrequestid, paymentschema) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -88,8 +88,8 @@ def get(requestid, ministryrequestid): try: result = paymentservice().getpayment(requestid, ministryrequestid) return json.dumps(result), 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foirecord.py b/request-management-api/request_api/resources/foirecord.py index e53181c80..b5220904c 100644 --- a/request-management-api/request_api/resources/foirecord.py +++ b/request-management-api/request_api/resources/foirecord.py @@ -46,8 +46,8 @@ def get(requestid, ministryrequestid): try: result = recordservice().fetch(requestid, ministryrequestid) return json.dumps(result), 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except Exception as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -69,8 +69,8 @@ def post(requestid, ministryrequestid): response = recordservice().create(requestid, ministryrequestid, recordschema, AuthHelper.getuserid()) respcode = 200 if response.success == True else 500 return {'status': response.success, 'message':response.message,'data': response.args[0]} , respcode - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -91,8 +91,8 @@ def post(requestid, ministryrequestid): data = FOIRequestRecordUpdateSchema().load(requestjson) result = recordservice().update(requestid, ministryrequestid, data, AuthHelper.getuserid()) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as err: - return {'status': False, 'message':err['messages']}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -112,8 +112,8 @@ def post(requestid, ministryrequestid): recordschema = FOIRequestBulkRetryRecordSchema().load(requestjson, unknown=INCLUDE) result = recordservice().retry(requestid, ministryrequestid, recordschema) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -134,8 +134,8 @@ def post(requestid, ministryrequestid,recordid): result = recordservice().replace(requestid, ministryrequestid,recordid, recordschema,AuthHelper.getuserid()) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -157,8 +157,8 @@ def post(requestid, ministryrequestid, recordstype): response = recordservice().triggerpdfstitchservice(requestid, ministryrequestid, recordschema, AuthHelper.getuserid()) respcode = 200 if response.success == True else 500 return {'status': response.success, 'message':response.message,'id':response.identifier}, respcode - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -177,8 +177,8 @@ def get(requestid, ministryrequestid, recordstype): try: result = recordservice().getpdfstitchpackagetodownload(ministryrequestid, recordstype.lower()) return json.dumps(result), 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -198,15 +198,15 @@ def get(requestid, ministryrequestid, recordstype): result = recordservice().getpdfstichstatus(ministryrequestid, recordstype.lower()) #("getpdfstichstatus result == ", result) return result, 200 - except KeyError as err: - print("KeyError == ", err.messages) - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + print("KeyError") + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: print("BusinessException == ", exception.message) return {'status': exception.status_code, 'message':exception.message}, 500 except Exception as error: print("Exception error == ", error) - return {'status': False, 'message':error.message}, 500 + return {'status': False, 'message': f"{error=}"}, 500 @cors_preflight('GET,OPTIONS') @API.route('/foirecord//ministryrequest///recrodschanged') @@ -224,12 +224,12 @@ def get(requestid, ministryrequestid, recordstype): result = recordservice().isrecordschanged(ministryrequestid, recordstype.lower()) #print("records changed == ", result) return result, 200 - except KeyError as err: - print("KeyError == ", err.messages) - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + print("KeyError") + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: print("BusinessException == ", exception.message) return {'status': exception.status_code, 'message':exception.message}, 500 except Exception as error: print("Exception error == ", error) - return {'status': False, 'message':error.message}, 500 \ No newline at end of file + return {'status': False, 'message': f"{error=}"}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foirequest.py b/request-management-api/request_api/resources/foirequest.py index 275f452c6..470beb8a5 100644 --- a/request-management-api/request_api/resources/foirequest.py +++ b/request-management-api/request_api/resources/foirequest.py @@ -72,8 +72,8 @@ def get(foirequestid,foiministryrequestid,usertype = None): return jsondata , statuscode except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -118,8 +118,8 @@ def post(): return {'status': result.success, 'message':result.message,'id':result.identifier, 'ministryRequests': result.args[0]} , 200 except ValidationError as err: return {'status': False, 'message':err.messages}, 400 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -147,8 +147,8 @@ def post(foirequestid,foiministryrequestid): return {'status': False, 'message':EXCEPTION_MESSAGE_NOTFOUND_REQUEST,'id':foirequestid} , 404 except ValidationError as err: return {'status': False, 'message':err.messages}, 400 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -187,8 +187,8 @@ def post(foirequestid,foiministryrequestid,actiontype = None,usertype = None): return {'status': False, 'message':EXCEPTION_MESSAGE_NOTFOUND_REQUEST,'id':foirequestid} , 404 except ValidationError as err: return {'status': False, 'message':err.messages}, 400 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -246,8 +246,8 @@ def get(foirequestid, foiministryrequestid): return jsondata , statuscode except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foiwatcher.py b/request-management-api/request_api/resources/foiwatcher.py index 70a16e0f3..78750acbd 100644 --- a/request-management-api/request_api/resources/foiwatcher.py +++ b/request-management-api/request_api/resources/foiwatcher.py @@ -49,8 +49,8 @@ def get(requestid): return json.dumps(result), 200 except ValueError as err: return {'status': 500, 'message':err.messages}, 500 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -72,8 +72,8 @@ def post(): if result.success == True: eventservice().posteventforwatcher(requestjson["requestid"], requestjson, "rawrequest",AuthHelper.getuserid(), AuthHelper.getusername()) return {'status': result.success, 'message':result.message} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -91,8 +91,8 @@ def put(requestid): try: result = watcherservice().disablerawrequestwatchers(requestid, AuthHelper.getuserid()) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -110,8 +110,8 @@ def get(ministryrequestid): try: result = watcherservice().getministryrequestwatchers(ministryrequestid,AuthHelper.isministrymember()) return json.dumps(result), 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -133,8 +133,8 @@ def post(): if result.success == True: eventservice().posteventforwatcher(requestjson["ministryrequestid"], requestjson, "ministryrequest", AuthHelper.getuserid(), AuthHelper.getusername()) return {'status': result.success, 'message':result.message} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -152,7 +152,7 @@ def put(ministryrequestid): try: result = watcherservice().disableministryrequestwatchers(ministryrequestid, AuthHelper.getuserid()) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foiworkflow.py b/request-management-api/request_api/resources/foiworkflow.py index ccb2a0fa7..006ac55cc 100644 --- a/request-management-api/request_api/resources/foiworkflow.py +++ b/request-management-api/request_api/resources/foiworkflow.py @@ -47,8 +47,8 @@ def post(requesttype, requestid): return json.dumps({"message": str(response)}), 200 except ValueError as err: return {'status': 500, 'message':err.messages}, 500 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/services/rawrequestservice.py b/request-management-api/request_api/services/rawrequestservice.py index 594932595..c72d3ccf1 100644 --- a/request-management-api/request_api/services/rawrequestservice.py +++ b/request-management-api/request_api/services/rawrequestservice.py @@ -68,7 +68,7 @@ def saverawrequest(self, requestdatajson, sourceofsubmission, userid,notes): try: workflowservice().createinstance(redispubservice.foirequestqueueredischannel, json_data) except Exception as ex: - logging.error("Unable to create instance", ex) + logging.error("Unable to create instance") asyncio.ensure_future(redispubservice.publishrequest(json_data)) return result diff --git a/request-management-api/request_api/services/workflowservice.py b/request-management-api/request_api/services/workflowservice.py index 14bd0f35a..e3faaf143 100644 --- a/request-management-api/request_api/services/workflowservice.py +++ b/request-management-api/request_api/services/workflowservice.py @@ -2,7 +2,7 @@ import os import json from enum import Enum -from request_api.exceptions import BusinessException +from request_api.exceptions import BusinessException, Error from request_api.utils.redispublisher import RedisPublisherService from request_api.services.external.bpmservice import MessageType, bpmservice, ProcessDefinitionKey from request_api.services.cfrfeeservice import cfrfeeservice @@ -26,7 +26,7 @@ class workflowservice: def createinstance(self, definitionkey, message): response = bpmservice().createinstance(definitionkey, json.loads(message)) if response is None: - raise BusinessException("Unable to create instance for key"+ definitionkey) + raise BusinessException(Error.INVALID_INPUT) return response def postunopenedevent(self, id, wfinstanceid, requestsschema, status, ministries=None): From 5690628b43e6636fd2212ec844100fb70a861f6e Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Tue, 17 Oct 2023 17:15:38 -0700 Subject: [PATCH 225/234] Validation exceptions take a message arg and therefore .messages is not a valid k:v pairs for validation exceptions --- request-management-api/request_api/resources/foicfrfee.py | 4 ++-- .../request_api/resources/foidocument.py | 8 ++++---- .../request_api/resources/foirequest.py | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/request-management-api/request_api/resources/foicfrfee.py b/request-management-api/request_api/resources/foicfrfee.py index 1305b20df..8c925f4e7 100644 --- a/request-management-api/request_api/resources/foicfrfee.py +++ b/request-management-api/request_api/resources/foicfrfee.py @@ -57,7 +57,7 @@ def post(requestid, ministryrequestid): return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except ValidationError as verr: logging.error(verr) - return {'status': False, 'message':verr.messages}, 400 + return {'status': False, 'message': verr}, 400 except KeyError as err: logging.error(type(err)) return {'status': False, 'message': EXCEPTION_MESSAGE_BAD_REQUEST}, 400 @@ -87,7 +87,7 @@ def post(requestid, ministryrequestid): return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except ValidationError as verr: logging.error(verr) - return {'status': False, 'message':verr.messages}, 400 + return {'status': False, 'message': verr}, 400 except KeyError as err: logging.error(type(err)) return {'status': False, 'message': EXCEPTION_MESSAGE_BAD_REQUEST}, 400 diff --git a/request-management-api/request_api/resources/foidocument.py b/request-management-api/request_api/resources/foidocument.py index f7763a0bc..73b528770 100644 --- a/request-management-api/request_api/resources/foidocument.py +++ b/request-management-api/request_api/resources/foidocument.py @@ -74,7 +74,7 @@ def post(requestid, requesttype): result = documentservice().createrequestdocument(requestid, documentschema, AuthHelper.getuserid(), requesttype) return {'status': result.success, 'message':result.message} , 200 except ValidationError as err: - return {'status': False, 'message':err.messages}, 400 + return {'status': False, 'message': err}, 400 except KeyError as error: return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: @@ -98,7 +98,7 @@ def post(requestid, documentid, requesttype): result = documentservice().createrequestdocumentversion(requestid, documentid, documentschema, AuthHelper.getuserid(), requesttype) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except ValidationError as err: - return {'status': False, 'message':err.messages}, 400 + return {'status': False, 'message': err}, 400 except KeyError as error: return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: @@ -136,7 +136,7 @@ def post(requesttype, requestid, documentid): return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 return {'status': False, 'message': "Something went wrong moving the document's location" }, 500 except ValidationError as err: - return {'status': False, 'message':err.messages}, 400 + return {'status': False, 'message': err}, 400 except KeyError as error: return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: @@ -159,7 +159,7 @@ def post(requestid, documentid, requesttype): result = documentservice().createrequestdocumentversion(requestid, documentid, documentschema, AuthHelper.getuserid(), requesttype) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except ValidationError as err: - return {'status': False, 'message':err.messages}, 400 + return {'status': False, 'message': err}, 400 except KeyError as error: return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: diff --git a/request-management-api/request_api/resources/foirequest.py b/request-management-api/request_api/resources/foirequest.py index 470beb8a5..89b8366ef 100644 --- a/request-management-api/request_api/resources/foirequest.py +++ b/request-management-api/request_api/resources/foirequest.py @@ -117,7 +117,7 @@ def post(): return {'status': result.success, 'message':result.message,'id':result.identifier, 'ministryRequests': result.args[0]} , 200 except ValidationError as err: - return {'status': False, 'message':err.messages}, 400 + return {'status': False, 'message': err}, 400 except KeyError as error: return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: @@ -146,7 +146,7 @@ def post(foirequestid,foiministryrequestid): else: return {'status': False, 'message':EXCEPTION_MESSAGE_NOTFOUND_REQUEST,'id':foirequestid} , 404 except ValidationError as err: - return {'status': False, 'message':err.messages}, 400 + return {'status': False, 'message': err}, 400 except KeyError as error: return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: @@ -186,7 +186,7 @@ def post(foirequestid,foiministryrequestid,actiontype = None,usertype = None): else: return {'status': False, 'message':EXCEPTION_MESSAGE_NOTFOUND_REQUEST,'id':foirequestid} , 404 except ValidationError as err: - return {'status': False, 'message':err.messages}, 400 + return {'status': False, 'message': err}, 400 except KeyError as error: return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: @@ -224,7 +224,7 @@ def put(foirequestid): else: return {'status': False, 'message':EXCEPTION_MESSAGE_NOTFOUND_REQUEST,'id':foirequestid} , 404 except ValidationError as err: - return {'status': False, 'message':err.messages}, 40 + return {'status': False, 'message': err}, 40 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 From 736f1e147fd1cf3f407ebee3b179484860159642 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Tue, 17 Oct 2023 17:40:53 -0700 Subject: [PATCH 226/234] Adjusted handling for keyerror messages --- .../request_api/resources/foiadmin.py | 8 +++---- .../request_api/resources/foicfrfee.py | 2 +- .../request_api/resources/foicomment.py | 10 ++++----- .../request_api/resources/foidocument.py | 12 +++++----- .../request_api/resources/foiemail.py | 4 ++-- .../request_api/resources/foiextension.py | 12 +++++----- .../request_api/resources/foinotification.py | 8 +++---- .../request_api/resources/foipayment.py | 6 ++--- .../request_api/resources/foirecord.py | 22 +++++++++---------- .../request_api/resources/foirequest.py | 10 ++++----- .../request_api/resources/foiwatcher.py | 12 +++++----- .../request_api/resources/foiworkflow.py | 2 +- 12 files changed, 54 insertions(+), 54 deletions(-) diff --git a/request-management-api/request_api/resources/foiadmin.py b/request-management-api/request_api/resources/foiadmin.py index 8e07a70dc..e13742530 100644 --- a/request-management-api/request_api/resources/foiadmin.py +++ b/request-management-api/request_api/resources/foiadmin.py @@ -51,7 +51,7 @@ def get(): result = programareadivisionservice().getallprogramareadivisonsandsections() return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -72,7 +72,7 @@ def post(): result = programareadivisionservice().createprogramareadivision(programareadivisionschema) return {'status': result.success, 'message':result.message, 'id':result.identifier}, 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -94,7 +94,7 @@ def put(divisionid): result = programareadivisionservice().updateprogramareadivision(divisionid, programareadivisionschema, AuthHelper.getuserid()) return {'status': result.success, 'message':result.message, 'id':result.identifier}, 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -115,7 +115,7 @@ def put(divisionid): return {'status': result.success, 'message': result.message, 'id':result.identifier}, 400 return {'status': result.success, 'message':result.message, 'id':result.identifier}, 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foicfrfee.py b/request-management-api/request_api/resources/foicfrfee.py index 8c925f4e7..37308c685 100644 --- a/request-management-api/request_api/resources/foicfrfee.py +++ b/request-management-api/request_api/resources/foicfrfee.py @@ -110,6 +110,6 @@ def get(requestid): result = {"current": cfrfeeservice().getcfrfee(requestid), "history": cfrfeeservice().getcfrfeehistory(requestid)} return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foicomment.py b/request-management-api/request_api/resources/foicomment.py index de88e08da..866030b20 100644 --- a/request-management-api/request_api/resources/foicomment.py +++ b/request-management-api/request_api/resources/foicomment.py @@ -55,7 +55,7 @@ def post(): asyncio.ensure_future(eventservice().postcommentevent(result.identifier, "ministryrequest", AuthHelper.getuserid())) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -78,7 +78,7 @@ def post(): asyncio.ensure_future(eventservice().postcommentevent(result.identifier, "rawrequest", AuthHelper.getuserid())) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -109,7 +109,7 @@ def get(requesttype, requestid): else: return {'status': 401, 'message':'Restricted Request'} , 401 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -137,7 +137,7 @@ def put(requesttype, commentid): asyncio.ensure_future(eventservice().postcommentevent(result.identifier, requesttype, AuthHelper.getuserid(), True)) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -167,7 +167,7 @@ def put(requesttype, commentid): asyncio.ensure_future(eventservice().postcommentevent(commentid, requesttype, AuthHelper.getuserid(), existingtaggedusers=result.args[0])) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foidocument.py b/request-management-api/request_api/resources/foidocument.py index 73b528770..bf59f1f76 100644 --- a/request-management-api/request_api/resources/foidocument.py +++ b/request-management-api/request_api/resources/foidocument.py @@ -51,7 +51,7 @@ def get(requestid, requesttype): result = documentservice().getrequestdocumentsbyrole(requestid, requesttype, AuthHelper.isministrymember()) return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -76,7 +76,7 @@ def post(requestid, requesttype): except ValidationError as err: return {'status': False, 'message': err}, 400 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -100,7 +100,7 @@ def post(requestid, documentid, requesttype): except ValidationError as err: return {'status': False, 'message': err}, 400 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -138,7 +138,7 @@ def post(requesttype, requestid, documentid): except ValidationError as err: return {'status': False, 'message': err}, 400 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -161,7 +161,7 @@ def post(requestid, documentid, requesttype): except ValidationError as err: return {'status': False, 'message': err}, 400 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -181,6 +181,6 @@ def post(requestid, documentid, requesttype): result = documentservice().deleterequestdocument(requestid, documentid, AuthHelper.getuserid(), requesttype) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foiemail.py b/request-management-api/request_api/resources/foiemail.py index ca9cab211..0c590be23 100644 --- a/request-management-api/request_api/resources/foiemail.py +++ b/request-management-api/request_api/resources/foiemail.py @@ -52,7 +52,7 @@ def post(requestid, ministryrequestid, servicename): except ValueError as err: return {'status': 500, 'message':err.messages}, 500 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -73,7 +73,7 @@ def post(requestid, ministryrequestid, servicename): except ValueError as err: return {'status': 500, 'message':err.messages}, 500 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foiextension.py b/request-management-api/request_api/resources/foiextension.py index faab268a7..b985cc477 100644 --- a/request-management-api/request_api/resources/foiextension.py +++ b/request-management-api/request_api/resources/foiextension.py @@ -53,7 +53,7 @@ def get(requestid): extensionrecords = extensionservice().getrequestextensions(requestid) return json.dumps(extensionrecords), 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -72,7 +72,7 @@ def get(extensionid): extensionrecord = extensionservice().getrequestextension(extensionid) return json.dumps(extensionrecord), 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -97,7 +97,7 @@ def post(requestid, ministryrequestid): newduedate, = result.args return {'status': result.success, 'message':result.message,'id':result.identifier, 'newduedate': newduedate or None} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -122,7 +122,7 @@ def post(ministryrequestid): eventservice().posteventforaxisextension(ministryrequestid, result.args[0], AuthHelper.getuserid(), AuthHelper.getusername(), "add") return {'status': result.success, 'message':result.message} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -146,7 +146,7 @@ def post(requestid, ministryrequestid, extensionid): newduedate = result.args[-1] if len(result.args) > 0 else None return {'status': result.success, 'message':result.message,'id':result.identifier, 'newduedate': newduedate or None} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -168,6 +168,6 @@ def post(requestid, ministryrequestid, extensionid): newduedate = result.args[-1] if len(result.args) > 0 else None return {'status': result.success, 'message':result.message,'id':result.identifier, 'newduedate': newduedate or None} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foinotification.py b/request-management-api/request_api/resources/foinotification.py index 1e597096d..4967f31ab 100644 --- a/request-management-api/request_api/resources/foinotification.py +++ b/request-management-api/request_api/resources/foinotification.py @@ -48,7 +48,7 @@ def get(): except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -73,7 +73,7 @@ def delete(type=None,idnumber=None,notficationid=None): except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -93,7 +93,7 @@ def post(): respcode = 200 if reminderresponse.success == True else 500 return {'status': reminderresponse.success, 'message':reminderresponse.message,'id': reminderresponse.identifier} , respcode except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -114,6 +114,6 @@ def post(request_id: int, ministry_request_id: int): respcode = 200 if reminderresponse.success == True else 500 return {'status': reminderresponse.success, 'message':reminderresponse.message,'id': reminderresponse.identifier} , respcode except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foipayment.py b/request-management-api/request_api/resources/foipayment.py index da3d73399..b637d0772 100644 --- a/request-management-api/request_api/resources/foipayment.py +++ b/request-management-api/request_api/resources/foipayment.py @@ -49,7 +49,7 @@ def post(requestid, ministryrequestid): result = paymentservice().createpayment(requestid, ministryrequestid, paymentschema) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -70,7 +70,7 @@ def post(requestid, ministryrequestid): result = paymentservice().cancelpayment(requestid, ministryrequestid, paymentschema) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -89,7 +89,7 @@ def get(requestid, ministryrequestid): result = paymentservice().getpayment(requestid, ministryrequestid) return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foirecord.py b/request-management-api/request_api/resources/foirecord.py index b5220904c..a146b1a4f 100644 --- a/request-management-api/request_api/resources/foirecord.py +++ b/request-management-api/request_api/resources/foirecord.py @@ -47,7 +47,7 @@ def get(requestid, ministryrequestid): result = recordservice().fetch(requestid, ministryrequestid) return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except Exception as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -70,7 +70,7 @@ def post(requestid, ministryrequestid): respcode = 200 if response.success == True else 500 return {'status': response.success, 'message':response.message,'data': response.args[0]} , respcode except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -92,7 +92,7 @@ def post(requestid, ministryrequestid): result = recordservice().update(requestid, ministryrequestid, data, AuthHelper.getuserid()) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -113,7 +113,7 @@ def post(requestid, ministryrequestid): result = recordservice().retry(requestid, ministryrequestid, recordschema) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -135,7 +135,7 @@ def post(requestid, ministryrequestid,recordid): return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -158,7 +158,7 @@ def post(requestid, ministryrequestid, recordstype): respcode = 200 if response.success == True else 500 return {'status': response.success, 'message':response.message,'id':response.identifier}, respcode except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -178,7 +178,7 @@ def get(requestid, ministryrequestid, recordstype): result = recordservice().getpdfstitchpackagetodownload(ministryrequestid, recordstype.lower()) return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -199,8 +199,8 @@ def get(requestid, ministryrequestid, recordstype): #("getpdfstichstatus result == ", result) return result, 200 except KeyError as error: - print("KeyError") - return {'status': False, 'message': f"{error=}"}, 400 + print(str(type(error).__name__)) + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: print("BusinessException == ", exception.message) return {'status': exception.status_code, 'message':exception.message}, 500 @@ -225,8 +225,8 @@ def get(requestid, ministryrequestid, recordstype): #print("records changed == ", result) return result, 200 except KeyError as error: - print("KeyError") - return {'status': False, 'message': f"{error=}"}, 400 + print(str(type(error).__name__)) + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: print("BusinessException == ", exception.message) return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foirequest.py b/request-management-api/request_api/resources/foirequest.py index 89b8366ef..bb69a6b98 100644 --- a/request-management-api/request_api/resources/foirequest.py +++ b/request-management-api/request_api/resources/foirequest.py @@ -73,7 +73,7 @@ def get(foirequestid,foiministryrequestid,usertype = None): except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -119,7 +119,7 @@ def post(): except ValidationError as err: return {'status': False, 'message': err}, 400 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -148,7 +148,7 @@ def post(foirequestid,foiministryrequestid): except ValidationError as err: return {'status': False, 'message': err}, 400 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -188,7 +188,7 @@ def post(foirequestid,foiministryrequestid,actiontype = None,usertype = None): except ValidationError as err: return {'status': False, 'message': err}, 400 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -247,7 +247,7 @@ def get(foirequestid, foiministryrequestid): except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foiwatcher.py b/request-management-api/request_api/resources/foiwatcher.py index 78750acbd..f96cb43c5 100644 --- a/request-management-api/request_api/resources/foiwatcher.py +++ b/request-management-api/request_api/resources/foiwatcher.py @@ -50,7 +50,7 @@ def get(requestid): except ValueError as err: return {'status': 500, 'message':err.messages}, 500 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -73,7 +73,7 @@ def post(): eventservice().posteventforwatcher(requestjson["requestid"], requestjson, "rawrequest",AuthHelper.getuserid(), AuthHelper.getusername()) return {'status': result.success, 'message':result.message} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -92,7 +92,7 @@ def put(requestid): result = watcherservice().disablerawrequestwatchers(requestid, AuthHelper.getuserid()) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -111,7 +111,7 @@ def get(ministryrequestid): result = watcherservice().getministryrequestwatchers(ministryrequestid,AuthHelper.isministrymember()) return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -134,7 +134,7 @@ def post(): eventservice().posteventforwatcher(requestjson["ministryrequestid"], requestjson, "ministryrequest", AuthHelper.getuserid(), AuthHelper.getusername()) return {'status': result.success, 'message':result.message} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -153,6 +153,6 @@ def put(ministryrequestid): result = watcherservice().disableministryrequestwatchers(ministryrequestid, AuthHelper.getuserid()) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foiworkflow.py b/request-management-api/request_api/resources/foiworkflow.py index 006ac55cc..71e5b68f8 100644 --- a/request-management-api/request_api/resources/foiworkflow.py +++ b/request-management-api/request_api/resources/foiworkflow.py @@ -48,7 +48,7 @@ def post(requesttype, requestid): except ValueError as err: return {'status': 500, 'message':err.messages}, 500 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 From 6c3976cb24c7e2d5b87d80694067efdff6ee3d59 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Wed, 18 Oct 2023 14:19:03 -0700 Subject: [PATCH 227/234] Foi Flow exception handling fixed. WIP DocReviewer --- .../request_api/resources/foicfrfee.py | 8 ++++---- .../request_api/resources/foicomment.py | 4 ++-- .../request_api/resources/foidocument.py | 8 ++++---- .../request_api/resources/foiemail.py | 4 ++-- .../request_api/resources/foirecord.py | 6 +++--- .../request_api/resources/foirequest.py | 12 ++++++------ .../request_api/resources/foiwatcher.py | 2 +- .../request_api/resources/foiworkflow.py | 2 +- .../request_api/utils/redispublisher.py | 2 +- 9 files changed, 24 insertions(+), 24 deletions(-) diff --git a/request-management-api/request_api/resources/foicfrfee.py b/request-management-api/request_api/resources/foicfrfee.py index 37308c685..c8e36e801 100644 --- a/request-management-api/request_api/resources/foicfrfee.py +++ b/request-management-api/request_api/resources/foicfrfee.py @@ -57,9 +57,9 @@ def post(requestid, ministryrequestid): return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except ValidationError as verr: logging.error(verr) - return {'status': False, 'message': verr}, 400 + return {'status': False, 'message': str(verr)}, 400 except KeyError as err: - logging.error(type(err)) + logging.error(str(type(err).__name__)) return {'status': False, 'message': EXCEPTION_MESSAGE_BAD_REQUEST}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -87,9 +87,9 @@ def post(requestid, ministryrequestid): return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except ValidationError as verr: logging.error(verr) - return {'status': False, 'message': verr}, 400 + return {'status': False, 'message': str(verr)}, 400 except KeyError as err: - logging.error(type(err)) + logging.error(str(type(err).__name__)) return {'status': False, 'message': EXCEPTION_MESSAGE_BAD_REQUEST}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foicomment.py b/request-management-api/request_api/resources/foicomment.py index 866030b20..31ee32336 100644 --- a/request-management-api/request_api/resources/foicomment.py +++ b/request-management-api/request_api/resources/foicomment.py @@ -184,7 +184,7 @@ def get(requestid=None): result = commentservice().createcommenttagginguserlist("rawrequest",requestid) return json.dumps(result), 200 except ValueError: - return {'status': 500, 'message':"Invalid Request"}, 400 + return {'status': 500, 'message':"Invalid Request"}, 500 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -200,6 +200,6 @@ def get(ministryrequestid=None): result = commentservice().createcommenttagginguserlist("ministryrequest",ministryrequestid) return json.dumps(result), 200 except ValueError: - return {'status': 500, 'message':"Invalid Request"}, 400 + return {'status': 500, 'message':"Invalid Request"}, 500 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foidocument.py b/request-management-api/request_api/resources/foidocument.py index bf59f1f76..29e0099b3 100644 --- a/request-management-api/request_api/resources/foidocument.py +++ b/request-management-api/request_api/resources/foidocument.py @@ -74,7 +74,7 @@ def post(requestid, requesttype): result = documentservice().createrequestdocument(requestid, documentschema, AuthHelper.getuserid(), requesttype) return {'status': result.success, 'message':result.message} , 200 except ValidationError as err: - return {'status': False, 'message': err}, 400 + return {'status': False, 'message': str(err)}, 400 except KeyError as error: return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: @@ -98,7 +98,7 @@ def post(requestid, documentid, requesttype): result = documentservice().createrequestdocumentversion(requestid, documentid, documentschema, AuthHelper.getuserid(), requesttype) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except ValidationError as err: - return {'status': False, 'message': err}, 400 + return {'status': False, 'message': str(err)}, 400 except KeyError as error: return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: @@ -136,7 +136,7 @@ def post(requesttype, requestid, documentid): return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 return {'status': False, 'message': "Something went wrong moving the document's location" }, 500 except ValidationError as err: - return {'status': False, 'message': err}, 400 + return {'status': False, 'message': str(err)}, 400 except KeyError as error: return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: @@ -159,7 +159,7 @@ def post(requestid, documentid, requesttype): result = documentservice().createrequestdocumentversion(requestid, documentid, documentschema, AuthHelper.getuserid(), requesttype) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except ValidationError as err: - return {'status': False, 'message': err}, 400 + return {'status': False, 'message': str(err)}, 400 except KeyError as error: return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: diff --git a/request-management-api/request_api/resources/foiemail.py b/request-management-api/request_api/resources/foiemail.py index 0c590be23..143b280b1 100644 --- a/request-management-api/request_api/resources/foiemail.py +++ b/request-management-api/request_api/resources/foiemail.py @@ -50,7 +50,7 @@ def post(requestid, ministryrequestid, servicename): result = emailservice().send(servicename.upper(), requestid, ministryrequestid, emailschema) return json.dumps(result), 200 if result["success"] == True else 500 except ValueError as err: - return {'status': 500, 'message':err.messages}, 500 + return {'status': 500, 'message': str(err)}, 500 except KeyError as error: return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: @@ -71,7 +71,7 @@ def post(requestid, ministryrequestid, servicename): result = emailservice().acknowledge(servicename.upper(), requestid, ministryrequestid) return json.dumps(result), 200 if result["success"] == True else 500 except ValueError as err: - return {'status': 500, 'message':err.messages}, 500 + return {'status': 500, 'message': str(err)}, 500 except KeyError as error: return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: diff --git a/request-management-api/request_api/resources/foirecord.py b/request-management-api/request_api/resources/foirecord.py index a146b1a4f..e90e9ae34 100644 --- a/request-management-api/request_api/resources/foirecord.py +++ b/request-management-api/request_api/resources/foirecord.py @@ -49,7 +49,7 @@ def get(requestid, ministryrequestid): except KeyError as error: return {'status': False, 'message': str(type(error).__name__)}, 400 except Exception as exception: - return {'status': exception.status_code, 'message':exception.message}, 500 + return {'status': False, 'message': str(exception)}, 500 @cors_preflight('POST,OPTIONS') @API.route('/foirecord//ministryrequest/') @@ -206,7 +206,7 @@ def get(requestid, ministryrequestid, recordstype): return {'status': exception.status_code, 'message':exception.message}, 500 except Exception as error: print("Exception error == ", error) - return {'status': False, 'message': f"{error=}"}, 500 + return {'status': False, 'message': str(error)}, 500 @cors_preflight('GET,OPTIONS') @API.route('/foirecord//ministryrequest///recrodschanged') @@ -232,4 +232,4 @@ def get(requestid, ministryrequestid, recordstype): return {'status': exception.status_code, 'message':exception.message}, 500 except Exception as error: print("Exception error == ", error) - return {'status': False, 'message': f"{error=}"}, 500 \ No newline at end of file + return {'status': False, 'message': str(error)}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foirequest.py b/request-management-api/request_api/resources/foirequest.py index bb69a6b98..e36f9d250 100644 --- a/request-management-api/request_api/resources/foirequest.py +++ b/request-management-api/request_api/resources/foirequest.py @@ -117,7 +117,7 @@ def post(): return {'status': result.success, 'message':result.message,'id':result.identifier, 'ministryRequests': result.args[0]} , 200 except ValidationError as err: - return {'status': False, 'message': err}, 400 + return {'status': False, 'message': str(err)}, 400 except KeyError as error: return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: @@ -146,7 +146,7 @@ def post(foirequestid,foiministryrequestid): else: return {'status': False, 'message':EXCEPTION_MESSAGE_NOTFOUND_REQUEST,'id':foirequestid} , 404 except ValidationError as err: - return {'status': False, 'message': err}, 400 + return {'status': False, 'message': str(err)}, 400 except KeyError as error: return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: @@ -186,7 +186,7 @@ def post(foirequestid,foiministryrequestid,actiontype = None,usertype = None): else: return {'status': False, 'message':EXCEPTION_MESSAGE_NOTFOUND_REQUEST,'id':foirequestid} , 404 except ValidationError as err: - return {'status': False, 'message': err}, 400 + return {'status': False, 'message': str(err)}, 400 except KeyError as error: return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: @@ -224,7 +224,7 @@ def put(foirequestid): else: return {'status': False, 'message':EXCEPTION_MESSAGE_NOTFOUND_REQUEST,'id':foirequestid} , 404 except ValidationError as err: - return {'status': False, 'message': err}, 40 + return {'status': False, 'message': str(err)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -271,7 +271,7 @@ def post(ministryrequestid=None,type=None): else: return {'status': result.success, 'message':result.message,'id':result.identifier} , 500 except ValueError: - return {'status': 500, 'message':"Invalid Request"}, 400 + return {'status': 500, 'message':"Invalid Request"}, 500 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -287,6 +287,6 @@ def get(ministryrequestid,usertype=None): try : return FOIRequest.get(requestservice().getrequestid(ministryrequestid), ministryrequestid, usertype) except ValueError: - return {'status': 500, 'message':"Invalid Request"}, 400 + return {'status': 500, 'message':"Invalid Request"}, 500 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foiwatcher.py b/request-management-api/request_api/resources/foiwatcher.py index f96cb43c5..065ba1154 100644 --- a/request-management-api/request_api/resources/foiwatcher.py +++ b/request-management-api/request_api/resources/foiwatcher.py @@ -48,7 +48,7 @@ def get(requestid): result = watcherservice().getrawrequestwatchers(requestid) return json.dumps(result), 200 except ValueError as err: - return {'status': 500, 'message':err.messages}, 500 + return {'status': 500, 'message': str(err)}, 500 except KeyError as error: return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: diff --git a/request-management-api/request_api/resources/foiworkflow.py b/request-management-api/request_api/resources/foiworkflow.py index 71e5b68f8..e22631ce1 100644 --- a/request-management-api/request_api/resources/foiworkflow.py +++ b/request-management-api/request_api/resources/foiworkflow.py @@ -46,7 +46,7 @@ def post(requesttype, requestid): response = workflowservice().syncwfinstance(requesttype, requestid, True) return json.dumps({"message": str(response)}), 200 except ValueError as err: - return {'status': 500, 'message':err.messages}, 500 + return {'status': 500, 'message': str(err)}, 500 except KeyError as error: return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: diff --git a/request-management-api/request_api/utils/redispublisher.py b/request-management-api/request_api/utils/redispublisher.py index dab04e31a..0cb8a1ff8 100644 --- a/request-management-api/request_api/utils/redispublisher.py +++ b/request-management-api/request_api/utils/redispublisher.py @@ -22,7 +22,7 @@ def publishcommment(self, message): logging.info(message) self.publishtoredischannel(self.foicommentqueueredischannel, message) except Exception as ex: - current_app.logger.error("%s,%s" % ('Unable to get user details', ex.message)) + current_app.logger.error("%s,%s" % ('Unable to get user details', ex)) raise ex def publishtoredischannel(self, channel , message): From 475267f3ce2db5e5921e4b77241481c9a50496a9 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Wed, 18 Oct 2023 16:22:34 -0700 Subject: [PATCH 228/234] Revised exception handling after comments/discussion with Nick Kan --- .../request_api/resources/foiadmin.py | 9 ++++---- .../request_api/resources/foicfrfee.py | 15 ++++++------ .../request_api/resources/foicomment.py | 11 +++++---- .../request_api/resources/foidocument.py | 15 ++++++------ .../request_api/resources/foiemail.py | 5 ++-- .../request_api/resources/foiextension.py | 13 ++++++----- .../request_api/resources/foinotification.py | 9 ++++---- .../request_api/resources/foipayment.py | 7 +++--- .../request_api/resources/foirecord.py | 23 ++++++++++--------- .../request_api/resources/foirequest.py | 11 +++++---- .../request_api/resources/foiwatcher.py | 13 ++++++----- .../request_api/resources/foiworkflow.py | 3 ++- .../request_api/services/rawrequestservice.py | 2 +- .../request_api/services/workflowservice.py | 2 +- request-management-api/request_api/tracer.py | 2 +- 15 files changed, 76 insertions(+), 64 deletions(-) diff --git a/request-management-api/request_api/resources/foiadmin.py b/request-management-api/request_api/resources/foiadmin.py index e13742530..8f075807b 100644 --- a/request-management-api/request_api/resources/foiadmin.py +++ b/request-management-api/request_api/resources/foiadmin.py @@ -30,6 +30,7 @@ API = Namespace('FOIAdmin', description='Endpoints for FOI admin management') TRACER = Tracer.get_instance() +CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " """Custom exception messages """ @@ -51,7 +52,7 @@ def get(): result = programareadivisionservice().getallprogramareadivisonsandsections() return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -72,7 +73,7 @@ def post(): result = programareadivisionservice().createprogramareadivision(programareadivisionschema) return {'status': result.success, 'message':result.message, 'id':result.identifier}, 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -94,7 +95,7 @@ def put(divisionid): result = programareadivisionservice().updateprogramareadivision(divisionid, programareadivisionschema, AuthHelper.getuserid()) return {'status': result.success, 'message':result.message, 'id':result.identifier}, 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -115,7 +116,7 @@ def put(divisionid): return {'status': result.success, 'message': result.message, 'id':result.identifier}, 400 return {'status': result.success, 'message':result.message, 'id':result.identifier}, 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foicfrfee.py b/request-management-api/request_api/resources/foicfrfee.py index c8e36e801..44877f076 100644 --- a/request-management-api/request_api/resources/foicfrfee.py +++ b/request-management-api/request_api/resources/foicfrfee.py @@ -36,6 +36,7 @@ """Custom exception messages """ EXCEPTION_MESSAGE_BAD_REQUEST='Bad Request' +CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('POST,OPTIONS') @API.route('/foicfrfee/foirequest//ministryrequest/') @@ -58,9 +59,9 @@ def post(requestid, ministryrequestid): except ValidationError as verr: logging.error(verr) return {'status': False, 'message': str(verr)}, 400 - except KeyError as err: - logging.error(str(type(err).__name__)) - return {'status': False, 'message': EXCEPTION_MESSAGE_BAD_REQUEST}, 400 + except KeyError as error: + logging.error(CUSTOM_KEYERROR_MESSAGE + str(error)) + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -88,9 +89,9 @@ def post(requestid, ministryrequestid): except ValidationError as verr: logging.error(verr) return {'status': False, 'message': str(verr)}, 400 - except KeyError as err: - logging.error(str(type(err).__name__)) - return {'status': False, 'message': EXCEPTION_MESSAGE_BAD_REQUEST}, 400 + except KeyError as error: + logging.error(CUSTOM_KEYERROR_MESSAGE + str(error)) + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -110,6 +111,6 @@ def get(requestid): result = {"current": cfrfeeservice().getcfrfee(requestid), "history": cfrfeeservice().getcfrfeehistory(requestid)} return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foicomment.py b/request-management-api/request_api/resources/foicomment.py index 31ee32336..4f88917ba 100644 --- a/request-management-api/request_api/resources/foicomment.py +++ b/request-management-api/request_api/resources/foicomment.py @@ -35,6 +35,7 @@ """Custom exception messages """ EXCEPTION_MESSAGE_BAD_REQUEST='Bad Request' +CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('POST,OPTIONS') @API.route('/foicomment/ministryrequest') @@ -55,7 +56,7 @@ def post(): asyncio.ensure_future(eventservice().postcommentevent(result.identifier, "ministryrequest", AuthHelper.getuserid())) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -78,7 +79,7 @@ def post(): asyncio.ensure_future(eventservice().postcommentevent(result.identifier, "rawrequest", AuthHelper.getuserid())) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -109,7 +110,7 @@ def get(requesttype, requestid): else: return {'status': 401, 'message':'Restricted Request'} , 401 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -137,7 +138,7 @@ def put(requesttype, commentid): asyncio.ensure_future(eventservice().postcommentevent(result.identifier, requesttype, AuthHelper.getuserid(), True)) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -167,7 +168,7 @@ def put(requesttype, commentid): asyncio.ensure_future(eventservice().postcommentevent(commentid, requesttype, AuthHelper.getuserid(), existingtaggedusers=result.args[0])) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foidocument.py b/request-management-api/request_api/resources/foidocument.py index 29e0099b3..83e0d6ccd 100644 --- a/request-management-api/request_api/resources/foidocument.py +++ b/request-management-api/request_api/resources/foidocument.py @@ -31,7 +31,8 @@ API = Namespace('FOIDocument', description='Endpoints for FOI Document management') -TRACER = Tracer.get_instance() +TRACER = Tracer.get_instance() +CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('GET,OPTIONS') @@ -51,7 +52,7 @@ def get(requestid, requesttype): result = documentservice().getrequestdocumentsbyrole(requestid, requesttype, AuthHelper.isministrymember()) return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -76,7 +77,7 @@ def post(requestid, requesttype): except ValidationError as err: return {'status': False, 'message': str(err)}, 400 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -100,7 +101,7 @@ def post(requestid, documentid, requesttype): except ValidationError as err: return {'status': False, 'message': str(err)}, 400 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -138,7 +139,7 @@ def post(requesttype, requestid, documentid): except ValidationError as err: return {'status': False, 'message': str(err)}, 400 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -161,7 +162,7 @@ def post(requestid, documentid, requesttype): except ValidationError as err: return {'status': False, 'message': str(err)}, 400 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -181,6 +182,6 @@ def post(requestid, documentid, requesttype): result = documentservice().deleterequestdocument(requestid, documentid, AuthHelper.getuserid(), requesttype) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foiemail.py b/request-management-api/request_api/resources/foiemail.py index 143b280b1..c175cc6f3 100644 --- a/request-management-api/request_api/resources/foiemail.py +++ b/request-management-api/request_api/resources/foiemail.py @@ -32,6 +32,7 @@ API = Namespace('FOIEmail', description='Endpoints for FOI EMAIL management') TRACER = Tracer.get_instance() +CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('POST,OPTIONS') @API.route('/foiemail//ministryrequest//') @@ -52,7 +53,7 @@ def post(requestid, ministryrequestid, servicename): except ValueError as err: return {'status': 500, 'message': str(err)}, 500 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -73,7 +74,7 @@ def post(requestid, ministryrequestid, servicename): except ValueError as err: return {'status': 500, 'message': str(err)}, 500 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foiextension.py b/request-management-api/request_api/resources/foiextension.py index b985cc477..ddb8280ac 100644 --- a/request-management-api/request_api/resources/foiextension.py +++ b/request-management-api/request_api/resources/foiextension.py @@ -37,6 +37,7 @@ """Custom exception messages """ EXCEPTION_MESSAGE_BAD_REQUEST='Bad Request' +CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('GET,OPTIONS') @API.route('/foiextension/ministryrequest/') @@ -53,7 +54,7 @@ def get(requestid): extensionrecords = extensionservice().getrequestextensions(requestid) return json.dumps(extensionrecords), 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -72,7 +73,7 @@ def get(extensionid): extensionrecord = extensionservice().getrequestextension(extensionid) return json.dumps(extensionrecord), 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -97,7 +98,7 @@ def post(requestid, ministryrequestid): newduedate, = result.args return {'status': result.success, 'message':result.message,'id':result.identifier, 'newduedate': newduedate or None} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -122,7 +123,7 @@ def post(ministryrequestid): eventservice().posteventforaxisextension(ministryrequestid, result.args[0], AuthHelper.getuserid(), AuthHelper.getusername(), "add") return {'status': result.success, 'message':result.message} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -146,7 +147,7 @@ def post(requestid, ministryrequestid, extensionid): newduedate = result.args[-1] if len(result.args) > 0 else None return {'status': result.success, 'message':result.message,'id':result.identifier, 'newduedate': newduedate or None} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -168,6 +169,6 @@ def post(requestid, ministryrequestid, extensionid): newduedate = result.args[-1] if len(result.args) > 0 else None return {'status': result.success, 'message':result.message,'id':result.identifier, 'newduedate': newduedate or None} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foinotification.py b/request-management-api/request_api/resources/foinotification.py index 4967f31ab..f5a3bf637 100644 --- a/request-management-api/request_api/resources/foinotification.py +++ b/request-management-api/request_api/resources/foinotification.py @@ -30,6 +30,7 @@ API = Namespace('FOINotification', description='Endpoints for FOI notification management') TRACER = Tracer.get_instance() +CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('GET,OPTIONS') @API.route('/foinotifications') @@ -48,7 +49,7 @@ def get(): except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -73,7 +74,7 @@ def delete(type=None,idnumber=None,notficationid=None): except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -93,7 +94,7 @@ def post(): respcode = 200 if reminderresponse.success == True else 500 return {'status': reminderresponse.success, 'message':reminderresponse.message,'id': reminderresponse.identifier} , respcode except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -114,6 +115,6 @@ def post(request_id: int, ministry_request_id: int): respcode = 200 if reminderresponse.success == True else 500 return {'status': reminderresponse.success, 'message':reminderresponse.message,'id': reminderresponse.identifier} , respcode except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foipayment.py b/request-management-api/request_api/resources/foipayment.py index b637d0772..b2670c6ff 100644 --- a/request-management-api/request_api/resources/foipayment.py +++ b/request-management-api/request_api/resources/foipayment.py @@ -31,6 +31,7 @@ API = Namespace('FOIPayment', description='Endpoints for FOI Payment management') TRACER = Tracer.get_instance() +CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('POST,OPTIONS') @API.route('/foipayment//ministryrequest/') @@ -49,7 +50,7 @@ def post(requestid, ministryrequestid): result = paymentservice().createpayment(requestid, ministryrequestid, paymentschema) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -70,7 +71,7 @@ def post(requestid, ministryrequestid): result = paymentservice().cancelpayment(requestid, ministryrequestid, paymentschema) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -89,7 +90,7 @@ def get(requestid, ministryrequestid): result = paymentservice().getpayment(requestid, ministryrequestid) return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foirecord.py b/request-management-api/request_api/resources/foirecord.py index e90e9ae34..71fa93b55 100644 --- a/request-management-api/request_api/resources/foirecord.py +++ b/request-management-api/request_api/resources/foirecord.py @@ -30,6 +30,7 @@ API = Namespace('FOIWatcher', description='Endpoints for FOI record management') TRACER = Tracer.get_instance() +CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('GET,OPTIONS') @API.route('/foirecord//ministryrequest/') @@ -47,7 +48,7 @@ def get(requestid, ministryrequestid): result = recordservice().fetch(requestid, ministryrequestid) return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except Exception as exception: return {'status': False, 'message': str(exception)}, 500 @@ -70,7 +71,7 @@ def post(requestid, ministryrequestid): respcode = 200 if response.success == True else 500 return {'status': response.success, 'message':response.message,'data': response.args[0]} , respcode except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -92,7 +93,7 @@ def post(requestid, ministryrequestid): result = recordservice().update(requestid, ministryrequestid, data, AuthHelper.getuserid()) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -113,7 +114,7 @@ def post(requestid, ministryrequestid): result = recordservice().retry(requestid, ministryrequestid, recordschema) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -135,7 +136,7 @@ def post(requestid, ministryrequestid,recordid): return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -158,7 +159,7 @@ def post(requestid, ministryrequestid, recordstype): respcode = 200 if response.success == True else 500 return {'status': response.success, 'message':response.message,'id':response.identifier}, respcode except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -178,7 +179,7 @@ def get(requestid, ministryrequestid, recordstype): result = recordservice().getpdfstitchpackagetodownload(ministryrequestid, recordstype.lower()) return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -199,8 +200,8 @@ def get(requestid, ministryrequestid, recordstype): #("getpdfstichstatus result == ", result) return result, 200 except KeyError as error: - print(str(type(error).__name__)) - return {'status': False, 'message': str(type(error).__name__)}, 400 + print(CUSTOM_KEYERROR_MESSAGE + str(error)) + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: print("BusinessException == ", exception.message) return {'status': exception.status_code, 'message':exception.message}, 500 @@ -225,8 +226,8 @@ def get(requestid, ministryrequestid, recordstype): #print("records changed == ", result) return result, 200 except KeyError as error: - print(str(type(error).__name__)) - return {'status': False, 'message': str(type(error).__name__)}, 400 + print(CUSTOM_KEYERROR_MESSAGE + str(error)) + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: print("BusinessException == ", exception.message) return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foirequest.py b/request-management-api/request_api/resources/foirequest.py index e36f9d250..290cd2ccc 100644 --- a/request-management-api/request_api/resources/foirequest.py +++ b/request-management-api/request_api/resources/foirequest.py @@ -36,6 +36,7 @@ API = Namespace('FOIRequests', description='Endpoints for FOI request management') TRACER = Tracer.get_instance() EXCEPTION_MESSAGE_NOTFOUND_REQUEST='Record not found' +CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('GET,OPTIONS') @@ -73,7 +74,7 @@ def get(foirequestid,foiministryrequestid,usertype = None): except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -119,7 +120,7 @@ def post(): except ValidationError as err: return {'status': False, 'message': str(err)}, 400 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -148,7 +149,7 @@ def post(foirequestid,foiministryrequestid): except ValidationError as err: return {'status': False, 'message': str(err)}, 400 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -188,7 +189,7 @@ def post(foirequestid,foiministryrequestid,actiontype = None,usertype = None): except ValidationError as err: return {'status': False, 'message': str(err)}, 400 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -247,7 +248,7 @@ def get(foirequestid, foiministryrequestid): except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foiwatcher.py b/request-management-api/request_api/resources/foiwatcher.py index 065ba1154..bde512c42 100644 --- a/request-management-api/request_api/resources/foiwatcher.py +++ b/request-management-api/request_api/resources/foiwatcher.py @@ -32,6 +32,7 @@ API = Namespace('FOIWatcher', description='Endpoints for FOI watcher management') TRACER = Tracer.get_instance() +CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('GET,OPTIONS') @API.route('/foiwatcher/rawrequest/') @@ -50,7 +51,7 @@ def get(requestid): except ValueError as err: return {'status': 500, 'message': str(err)}, 500 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -73,7 +74,7 @@ def post(): eventservice().posteventforwatcher(requestjson["requestid"], requestjson, "rawrequest",AuthHelper.getuserid(), AuthHelper.getusername()) return {'status': result.success, 'message':result.message} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -92,7 +93,7 @@ def put(requestid): result = watcherservice().disablerawrequestwatchers(requestid, AuthHelper.getuserid()) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -111,7 +112,7 @@ def get(ministryrequestid): result = watcherservice().getministryrequestwatchers(ministryrequestid,AuthHelper.isministrymember()) return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -134,7 +135,7 @@ def post(): eventservice().posteventforwatcher(requestjson["ministryrequestid"], requestjson, "ministryrequest", AuthHelper.getuserid(), AuthHelper.getusername()) return {'status': result.success, 'message':result.message} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -153,6 +154,6 @@ def put(ministryrequestid): result = watcherservice().disableministryrequestwatchers(ministryrequestid, AuthHelper.getuserid()) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foiworkflow.py b/request-management-api/request_api/resources/foiworkflow.py index e22631ce1..a58503a73 100644 --- a/request-management-api/request_api/resources/foiworkflow.py +++ b/request-management-api/request_api/resources/foiworkflow.py @@ -29,6 +29,7 @@ API = Namespace('FOIWorkflow', description='Endpoints for FOI workflow management') TRACER = Tracer.get_instance() +CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('POST,OPTIONS') @API.route('/foiworkflow///sync') @@ -48,7 +49,7 @@ def post(requesttype, requestid): except ValueError as err: return {'status': 500, 'message': str(err)}, 500 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/services/rawrequestservice.py b/request-management-api/request_api/services/rawrequestservice.py index c72d3ccf1..6abaf1ff1 100644 --- a/request-management-api/request_api/services/rawrequestservice.py +++ b/request-management-api/request_api/services/rawrequestservice.py @@ -68,7 +68,7 @@ def saverawrequest(self, requestdatajson, sourceofsubmission, userid,notes): try: workflowservice().createinstance(redispubservice.foirequestqueueredischannel, json_data) except Exception as ex: - logging.error("Unable to create instance") + logging.error(ex) asyncio.ensure_future(redispubservice.publishrequest(json_data)) return result diff --git a/request-management-api/request_api/services/workflowservice.py b/request-management-api/request_api/services/workflowservice.py index e3faaf143..f4b6f81a0 100644 --- a/request-management-api/request_api/services/workflowservice.py +++ b/request-management-api/request_api/services/workflowservice.py @@ -26,7 +26,7 @@ class workflowservice: def createinstance(self, definitionkey, message): response = bpmservice().createinstance(definitionkey, json.loads(message)) if response is None: - raise BusinessException(Error.INVALID_INPUT) + raise Exception("Unable to create instance for key"+ definitionkey) return response def postunopenedevent(self, id, wfinstanceid, requestsschema, status, ministries=None): diff --git a/request-management-api/request_api/tracer.py b/request-management-api/request_api/tracer.py index 80815410b..e88efcee9 100644 --- a/request-management-api/request_api/tracer.py +++ b/request-management-api/request_api/tracer.py @@ -34,7 +34,7 @@ def get_instance(): def __init__(self): """Virtually private constructor.""" if Tracer.__instance is not None: - raise BusinessException('Attempt made to create multiple tracing instances') + raise Exception('Attempt made to create multiple tracing instances') api_tracer = ApiTracer() Tracer.__instance = ApiTracing(api_tracer.tracer) From ac22b4bde2d0110744a2088f24c940e9d3423036 Mon Sep 17 00:00:00 2001 From: Aman Hundal Date: Mon, 23 Oct 2023 09:16:09 -0700 Subject: [PATCH 229/234] Revert "Ticket 4539. Fix/Adjustment to exception handling in request-management api" --- .../request_api/resources/foiadmin.py | 19 ++++---- .../request_api/resources/foicfrfee.py | 21 ++++----- .../request_api/resources/foicomment.py | 25 +++++----- .../request_api/resources/foidocument.py | 35 +++++++------- .../request_api/resources/foiemail.py | 13 +++-- .../request_api/resources/foiextension.py | 25 +++++----- .../request_api/resources/foinotification.py | 17 ++++--- .../request_api/resources/foipayment.py | 13 +++-- .../request_api/resources/foirecord.py | 47 +++++++++---------- .../request_api/resources/foirequest.py | 33 +++++++------ .../request_api/resources/foiwatcher.py | 27 +++++------ .../request_api/resources/foiworkflow.py | 7 ++- .../request_api/services/rawrequestservice.py | 2 +- .../request_api/services/workflowservice.py | 4 +- request-management-api/request_api/tracer.py | 2 +- .../request_api/utils/redispublisher.py | 2 +- 16 files changed, 140 insertions(+), 152 deletions(-) diff --git a/request-management-api/request_api/resources/foiadmin.py b/request-management-api/request_api/resources/foiadmin.py index 8f075807b..27858c8e4 100644 --- a/request-management-api/request_api/resources/foiadmin.py +++ b/request-management-api/request_api/resources/foiadmin.py @@ -30,7 +30,6 @@ API = Namespace('FOIAdmin', description='Endpoints for FOI admin management') TRACER = Tracer.get_instance() -CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " """Custom exception messages """ @@ -51,9 +50,9 @@ def get(): try: result = programareadivisionservice().getallprogramareadivisonsandsections() return json.dumps(result), 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 - except BusinessException as exception: + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 + except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @cors_preflight('POST,OPTIONS') @@ -72,8 +71,8 @@ def post(): programareadivisionschema = FOIProgramAreaDivisionSchema().load(requestjson) result = programareadivisionservice().createprogramareadivision(programareadivisionschema) return {'status': result.success, 'message':result.message, 'id':result.identifier}, 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -94,8 +93,8 @@ def put(divisionid): programareadivisionschema = FOIProgramAreaDivisionSchema().load(requestjson) result = programareadivisionservice().updateprogramareadivision(divisionid, programareadivisionschema, AuthHelper.getuserid()) return {'status': result.success, 'message':result.message, 'id':result.identifier}, 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -115,8 +114,8 @@ def put(divisionid): if result.success != True: return {'status': result.success, 'message': result.message, 'id':result.identifier}, 400 return {'status': result.success, 'message':result.message, 'id':result.identifier}, 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foicfrfee.py b/request-management-api/request_api/resources/foicfrfee.py index 44877f076..e8dad2f91 100644 --- a/request-management-api/request_api/resources/foicfrfee.py +++ b/request-management-api/request_api/resources/foicfrfee.py @@ -36,7 +36,6 @@ """Custom exception messages """ EXCEPTION_MESSAGE_BAD_REQUEST='Bad Request' -CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('POST,OPTIONS') @API.route('/foicfrfee/foirequest//ministryrequest/') @@ -58,10 +57,10 @@ def post(requestid, ministryrequestid): return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except ValidationError as verr: logging.error(verr) - return {'status': False, 'message': str(verr)}, 400 - except KeyError as error: - logging.error(CUSTOM_KEYERROR_MESSAGE + str(error)) - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + return {'status': False, 'message':verr.messages}, 400 + except KeyError as err: + logging.error(err) + return {'status': False, 'message': EXCEPTION_MESSAGE_BAD_REQUEST}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -88,10 +87,10 @@ def post(requestid, ministryrequestid): return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except ValidationError as verr: logging.error(verr) - return {'status': False, 'message': str(verr)}, 400 - except KeyError as error: - logging.error(CUSTOM_KEYERROR_MESSAGE + str(error)) - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + return {'status': False, 'message':verr.messages}, 400 + except KeyError as err: + logging.error(err) + return {'status': False, 'message': EXCEPTION_MESSAGE_BAD_REQUEST}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -110,7 +109,7 @@ def get(requestid): try: result = {"current": cfrfeeservice().getcfrfee(requestid), "history": cfrfeeservice().getcfrfeehistory(requestid)} return json.dumps(result), 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foicomment.py b/request-management-api/request_api/resources/foicomment.py index 4f88917ba..f68db9c50 100644 --- a/request-management-api/request_api/resources/foicomment.py +++ b/request-management-api/request_api/resources/foicomment.py @@ -35,7 +35,6 @@ """Custom exception messages """ EXCEPTION_MESSAGE_BAD_REQUEST='Bad Request' -CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('POST,OPTIONS') @API.route('/foicomment/ministryrequest') @@ -55,8 +54,8 @@ def post(): if result.success == True: asyncio.ensure_future(eventservice().postcommentevent(result.identifier, "ministryrequest", AuthHelper.getuserid())) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -78,8 +77,8 @@ def post(): if result.success == True: asyncio.ensure_future(eventservice().postcommentevent(result.identifier, "rawrequest", AuthHelper.getuserid())) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -109,8 +108,8 @@ def get(requesttype, requestid): return json.dumps(result), 200 else: return {'status': 401, 'message':'Restricted Request'} , 401 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -137,8 +136,8 @@ def put(requesttype, commentid): if result.success == True: asyncio.ensure_future(eventservice().postcommentevent(result.identifier, requesttype, AuthHelper.getuserid(), True)) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -167,8 +166,8 @@ def put(requesttype, commentid): if result.success == True: asyncio.ensure_future(eventservice().postcommentevent(commentid, requesttype, AuthHelper.getuserid(), existingtaggedusers=result.args[0])) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -185,7 +184,7 @@ def get(requestid=None): result = commentservice().createcommenttagginguserlist("rawrequest",requestid) return json.dumps(result), 200 except ValueError: - return {'status': 500, 'message':"Invalid Request"}, 500 + return {'status': 500, 'message':"Invalid Request"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -201,6 +200,6 @@ def get(ministryrequestid=None): result = commentservice().createcommenttagginguserlist("ministryrequest",ministryrequestid) return json.dumps(result), 200 except ValueError: - return {'status': 500, 'message':"Invalid Request"}, 500 + return {'status': 500, 'message':"Invalid Request"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foidocument.py b/request-management-api/request_api/resources/foidocument.py index 83e0d6ccd..f9f5b629f 100644 --- a/request-management-api/request_api/resources/foidocument.py +++ b/request-management-api/request_api/resources/foidocument.py @@ -31,8 +31,7 @@ API = Namespace('FOIDocument', description='Endpoints for FOI Document management') -TRACER = Tracer.get_instance() -CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " +TRACER = Tracer.get_instance() @cors_preflight('GET,OPTIONS') @@ -51,8 +50,8 @@ def get(requestid, requesttype): try: result = documentservice().getrequestdocumentsbyrole(requestid, requesttype, AuthHelper.isministrymember()) return json.dumps(result), 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -75,9 +74,9 @@ def post(requestid, requesttype): result = documentservice().createrequestdocument(requestid, documentschema, AuthHelper.getuserid(), requesttype) return {'status': result.success, 'message':result.message} , 200 except ValidationError as err: - return {'status': False, 'message': str(err)}, 400 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + return {'status': False, 'message':err.messages}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -99,9 +98,9 @@ def post(requestid, documentid, requesttype): result = documentservice().createrequestdocumentversion(requestid, documentid, documentschema, AuthHelper.getuserid(), requesttype) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except ValidationError as err: - return {'status': False, 'message': str(err)}, 400 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + return {'status': False, 'message':err.messages}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -137,9 +136,9 @@ def post(requesttype, requestid, documentid): return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 return {'status': False, 'message': "Something went wrong moving the document's location" }, 500 except ValidationError as err: - return {'status': False, 'message': str(err)}, 400 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + return {'status': False, 'message':err.messages}, 400 + except KeyError as err: + return {'status': False, 'message': err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -160,9 +159,9 @@ def post(requestid, documentid, requesttype): result = documentservice().createrequestdocumentversion(requestid, documentid, documentschema, AuthHelper.getuserid(), requesttype) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except ValidationError as err: - return {'status': False, 'message': str(err)}, 400 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + return {'status': False, 'message':err.messages}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -181,7 +180,7 @@ def post(requestid, documentid, requesttype): try: result = documentservice().deleterequestdocument(requestid, documentid, AuthHelper.getuserid(), requesttype) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foiemail.py b/request-management-api/request_api/resources/foiemail.py index c175cc6f3..0b2aa80b1 100644 --- a/request-management-api/request_api/resources/foiemail.py +++ b/request-management-api/request_api/resources/foiemail.py @@ -32,7 +32,6 @@ API = Namespace('FOIEmail', description='Endpoints for FOI EMAIL management') TRACER = Tracer.get_instance() -CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('POST,OPTIONS') @API.route('/foiemail//ministryrequest//') @@ -51,9 +50,9 @@ def post(requestid, ministryrequestid, servicename): result = emailservice().send(servicename.upper(), requestid, ministryrequestid, emailschema) return json.dumps(result), 200 if result["success"] == True else 500 except ValueError as err: - return {'status': 500, 'message': str(err)}, 500 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + return {'status': 500, 'message':err.messages}, 500 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -72,9 +71,9 @@ def post(requestid, ministryrequestid, servicename): result = emailservice().acknowledge(servicename.upper(), requestid, ministryrequestid) return json.dumps(result), 200 if result["success"] == True else 500 except ValueError as err: - return {'status': 500, 'message': str(err)}, 500 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + return {'status': 500, 'message':err.messages}, 500 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foiextension.py b/request-management-api/request_api/resources/foiextension.py index ddb8280ac..86c9ef0d6 100644 --- a/request-management-api/request_api/resources/foiextension.py +++ b/request-management-api/request_api/resources/foiextension.py @@ -37,7 +37,6 @@ """Custom exception messages """ EXCEPTION_MESSAGE_BAD_REQUEST='Bad Request' -CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('GET,OPTIONS') @API.route('/foiextension/ministryrequest/') @@ -53,8 +52,8 @@ def get(requestid): try: extensionrecords = extensionservice().getrequestextensions(requestid) return json.dumps(extensionrecords), 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -72,8 +71,8 @@ def get(extensionid): try: extensionrecord = extensionservice().getrequestextension(extensionid) return json.dumps(extensionrecord), 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -97,8 +96,8 @@ def post(requestid, ministryrequestid): eventservice().posteventforextension(ministryrequestid, result.identifier, AuthHelper.getuserid(), AuthHelper.getusername(), "add") newduedate, = result.args return {'status': result.success, 'message':result.message,'id':result.identifier, 'newduedate': newduedate or None} , 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -122,8 +121,8 @@ def post(ministryrequestid): if len(result.args) > 0: eventservice().posteventforaxisextension(ministryrequestid, result.args[0], AuthHelper.getuserid(), AuthHelper.getusername(), "add") return {'status': result.success, 'message':result.message} , 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -146,8 +145,8 @@ def post(requestid, ministryrequestid, extensionid): # posteventforextension moved to createrequestextensionversion to generate the comments before updating the ministry table with new due date newduedate = result.args[-1] if len(result.args) > 0 else None return {'status': result.success, 'message':result.message,'id':result.identifier, 'newduedate': newduedate or None} , 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -168,7 +167,7 @@ def post(requestid, ministryrequestid, extensionid): eventservice().posteventforextension(ministryrequestid, extensionid, AuthHelper.getuserid(), AuthHelper.getusername(), "delete") newduedate = result.args[-1] if len(result.args) > 0 else None return {'status': result.success, 'message':result.message,'id':result.identifier, 'newduedate': newduedate or None} , 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foinotification.py b/request-management-api/request_api/resources/foinotification.py index f5a3bf637..a95fef873 100644 --- a/request-management-api/request_api/resources/foinotification.py +++ b/request-management-api/request_api/resources/foinotification.py @@ -30,7 +30,6 @@ API = Namespace('FOINotification', description='Endpoints for FOI notification management') TRACER = Tracer.get_instance() -CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('GET,OPTIONS') @API.route('/foinotifications') @@ -48,8 +47,8 @@ def get(): return json.dumps(result), 200 except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -73,8 +72,8 @@ def delete(type=None,idnumber=None,notficationid=None): return {'status': result.success, 'message':result.message,'id':result.identifier} , 500 except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -93,8 +92,8 @@ def post(): reminderresponse = eventservice().postreminderevent() respcode = 200 if reminderresponse.success == True else 500 return {'status': reminderresponse.success, 'message':reminderresponse.message,'id': reminderresponse.identifier} , respcode - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -114,7 +113,7 @@ def post(request_id: int, ministry_request_id: int): reminderresponse = eventservice().postpaymentexpiryevent(ministry_request_id) respcode = 200 if reminderresponse.success == True else 500 return {'status': reminderresponse.success, 'message':reminderresponse.message,'id': reminderresponse.identifier} , respcode - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foipayment.py b/request-management-api/request_api/resources/foipayment.py index b2670c6ff..d5e7be7b5 100644 --- a/request-management-api/request_api/resources/foipayment.py +++ b/request-management-api/request_api/resources/foipayment.py @@ -31,7 +31,6 @@ API = Namespace('FOIPayment', description='Endpoints for FOI Payment management') TRACER = Tracer.get_instance() -CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('POST,OPTIONS') @API.route('/foipayment//ministryrequest/') @@ -49,8 +48,8 @@ def post(requestid, ministryrequestid): paymentschema = FOIRequestPaymentSchema().load(requestjson) result = paymentservice().createpayment(requestid, ministryrequestid, paymentschema) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -70,8 +69,8 @@ def post(requestid, ministryrequestid): paymentschema = FOIRequestPaymentSchema().load(requestjson) result = paymentservice().cancelpayment(requestid, ministryrequestid, paymentschema) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -89,8 +88,8 @@ def get(requestid, ministryrequestid): try: result = paymentservice().getpayment(requestid, ministryrequestid) return json.dumps(result), 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foirecord.py b/request-management-api/request_api/resources/foirecord.py index 71fa93b55..e53181c80 100644 --- a/request-management-api/request_api/resources/foirecord.py +++ b/request-management-api/request_api/resources/foirecord.py @@ -30,7 +30,6 @@ API = Namespace('FOIWatcher', description='Endpoints for FOI record management') TRACER = Tracer.get_instance() -CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('GET,OPTIONS') @API.route('/foirecord//ministryrequest/') @@ -47,10 +46,10 @@ def get(requestid, ministryrequestid): try: result = recordservice().fetch(requestid, ministryrequestid) return json.dumps(result), 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except Exception as exception: - return {'status': False, 'message': str(exception)}, 500 + return {'status': exception.status_code, 'message':exception.message}, 500 @cors_preflight('POST,OPTIONS') @API.route('/foirecord//ministryrequest/') @@ -70,8 +69,8 @@ def post(requestid, ministryrequestid): response = recordservice().create(requestid, ministryrequestid, recordschema, AuthHelper.getuserid()) respcode = 200 if response.success == True else 500 return {'status': response.success, 'message':response.message,'data': response.args[0]} , respcode - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -92,8 +91,8 @@ def post(requestid, ministryrequestid): data = FOIRequestRecordUpdateSchema().load(requestjson) result = recordservice().update(requestid, ministryrequestid, data, AuthHelper.getuserid()) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err['messages']}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -113,8 +112,8 @@ def post(requestid, ministryrequestid): recordschema = FOIRequestBulkRetryRecordSchema().load(requestjson, unknown=INCLUDE) result = recordservice().retry(requestid, ministryrequestid, recordschema) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -135,8 +134,8 @@ def post(requestid, ministryrequestid,recordid): result = recordservice().replace(requestid, ministryrequestid,recordid, recordschema,AuthHelper.getuserid()) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -158,8 +157,8 @@ def post(requestid, ministryrequestid, recordstype): response = recordservice().triggerpdfstitchservice(requestid, ministryrequestid, recordschema, AuthHelper.getuserid()) respcode = 200 if response.success == True else 500 return {'status': response.success, 'message':response.message,'id':response.identifier}, respcode - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -178,8 +177,8 @@ def get(requestid, ministryrequestid, recordstype): try: result = recordservice().getpdfstitchpackagetodownload(ministryrequestid, recordstype.lower()) return json.dumps(result), 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -199,15 +198,15 @@ def get(requestid, ministryrequestid, recordstype): result = recordservice().getpdfstichstatus(ministryrequestid, recordstype.lower()) #("getpdfstichstatus result == ", result) return result, 200 - except KeyError as error: - print(CUSTOM_KEYERROR_MESSAGE + str(error)) - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + print("KeyError == ", err.messages) + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: print("BusinessException == ", exception.message) return {'status': exception.status_code, 'message':exception.message}, 500 except Exception as error: print("Exception error == ", error) - return {'status': False, 'message': str(error)}, 500 + return {'status': False, 'message':error.message}, 500 @cors_preflight('GET,OPTIONS') @API.route('/foirecord//ministryrequest///recrodschanged') @@ -225,12 +224,12 @@ def get(requestid, ministryrequestid, recordstype): result = recordservice().isrecordschanged(ministryrequestid, recordstype.lower()) #print("records changed == ", result) return result, 200 - except KeyError as error: - print(CUSTOM_KEYERROR_MESSAGE + str(error)) - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + print("KeyError == ", err.messages) + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: print("BusinessException == ", exception.message) return {'status': exception.status_code, 'message':exception.message}, 500 except Exception as error: print("Exception error == ", error) - return {'status': False, 'message': str(error)}, 500 \ No newline at end of file + return {'status': False, 'message':error.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foirequest.py b/request-management-api/request_api/resources/foirequest.py index 290cd2ccc..275f452c6 100644 --- a/request-management-api/request_api/resources/foirequest.py +++ b/request-management-api/request_api/resources/foirequest.py @@ -36,7 +36,6 @@ API = Namespace('FOIRequests', description='Endpoints for FOI request management') TRACER = Tracer.get_instance() EXCEPTION_MESSAGE_NOTFOUND_REQUEST='Record not found' -CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('GET,OPTIONS') @@ -73,8 +72,8 @@ def get(foirequestid,foiministryrequestid,usertype = None): return jsondata , statuscode except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -118,9 +117,9 @@ def post(): return {'status': result.success, 'message':result.message,'id':result.identifier, 'ministryRequests': result.args[0]} , 200 except ValidationError as err: - return {'status': False, 'message': str(err)}, 400 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + return {'status': False, 'message':err.messages}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -147,9 +146,9 @@ def post(foirequestid,foiministryrequestid): else: return {'status': False, 'message':EXCEPTION_MESSAGE_NOTFOUND_REQUEST,'id':foirequestid} , 404 except ValidationError as err: - return {'status': False, 'message': str(err)}, 400 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + return {'status': False, 'message':err.messages}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -187,9 +186,9 @@ def post(foirequestid,foiministryrequestid,actiontype = None,usertype = None): else: return {'status': False, 'message':EXCEPTION_MESSAGE_NOTFOUND_REQUEST,'id':foirequestid} , 404 except ValidationError as err: - return {'status': False, 'message': str(err)}, 400 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + return {'status': False, 'message':err.messages}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -225,7 +224,7 @@ def put(foirequestid): else: return {'status': False, 'message':EXCEPTION_MESSAGE_NOTFOUND_REQUEST,'id':foirequestid} , 404 except ValidationError as err: - return {'status': False, 'message': str(err)}, 400 + return {'status': False, 'message':err.messages}, 40 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -247,8 +246,8 @@ def get(foirequestid, foiministryrequestid): return jsondata , statuscode except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -272,7 +271,7 @@ def post(ministryrequestid=None,type=None): else: return {'status': result.success, 'message':result.message,'id':result.identifier} , 500 except ValueError: - return {'status': 500, 'message':"Invalid Request"}, 500 + return {'status': 500, 'message':"Invalid Request"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -288,6 +287,6 @@ def get(ministryrequestid,usertype=None): try : return FOIRequest.get(requestservice().getrequestid(ministryrequestid), ministryrequestid, usertype) except ValueError: - return {'status': 500, 'message':"Invalid Request"}, 500 + return {'status': 500, 'message':"Invalid Request"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foiwatcher.py b/request-management-api/request_api/resources/foiwatcher.py index bde512c42..70a16e0f3 100644 --- a/request-management-api/request_api/resources/foiwatcher.py +++ b/request-management-api/request_api/resources/foiwatcher.py @@ -32,7 +32,6 @@ API = Namespace('FOIWatcher', description='Endpoints for FOI watcher management') TRACER = Tracer.get_instance() -CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('GET,OPTIONS') @API.route('/foiwatcher/rawrequest/') @@ -49,9 +48,9 @@ def get(requestid): result = watcherservice().getrawrequestwatchers(requestid) return json.dumps(result), 200 except ValueError as err: - return {'status': 500, 'message': str(err)}, 500 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + return {'status': 500, 'message':err.messages}, 500 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -73,8 +72,8 @@ def post(): if result.success == True: eventservice().posteventforwatcher(requestjson["requestid"], requestjson, "rawrequest",AuthHelper.getuserid(), AuthHelper.getusername()) return {'status': result.success, 'message':result.message} , 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -92,8 +91,8 @@ def put(requestid): try: result = watcherservice().disablerawrequestwatchers(requestid, AuthHelper.getuserid()) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -111,8 +110,8 @@ def get(ministryrequestid): try: result = watcherservice().getministryrequestwatchers(ministryrequestid,AuthHelper.isministrymember()) return json.dumps(result), 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -134,8 +133,8 @@ def post(): if result.success == True: eventservice().posteventforwatcher(requestjson["ministryrequestid"], requestjson, "ministryrequest", AuthHelper.getuserid(), AuthHelper.getusername()) return {'status': result.success, 'message':result.message} , 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -153,7 +152,7 @@ def put(ministryrequestid): try: result = watcherservice().disableministryrequestwatchers(ministryrequestid, AuthHelper.getuserid()) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foiworkflow.py b/request-management-api/request_api/resources/foiworkflow.py index a58503a73..ccb2a0fa7 100644 --- a/request-management-api/request_api/resources/foiworkflow.py +++ b/request-management-api/request_api/resources/foiworkflow.py @@ -29,7 +29,6 @@ API = Namespace('FOIWorkflow', description='Endpoints for FOI workflow management') TRACER = Tracer.get_instance() -CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('POST,OPTIONS') @API.route('/foiworkflow///sync') @@ -47,9 +46,9 @@ def post(requesttype, requestid): response = workflowservice().syncwfinstance(requesttype, requestid, True) return json.dumps({"message": str(response)}), 200 except ValueError as err: - return {'status': 500, 'message': str(err)}, 500 - except KeyError as error: - return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + return {'status': 500, 'message':err.messages}, 500 + except KeyError as err: + return {'status': False, 'message':err.messages}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/services/rawrequestservice.py b/request-management-api/request_api/services/rawrequestservice.py index 6abaf1ff1..594932595 100644 --- a/request-management-api/request_api/services/rawrequestservice.py +++ b/request-management-api/request_api/services/rawrequestservice.py @@ -68,7 +68,7 @@ def saverawrequest(self, requestdatajson, sourceofsubmission, userid,notes): try: workflowservice().createinstance(redispubservice.foirequestqueueredischannel, json_data) except Exception as ex: - logging.error(ex) + logging.error("Unable to create instance", ex) asyncio.ensure_future(redispubservice.publishrequest(json_data)) return result diff --git a/request-management-api/request_api/services/workflowservice.py b/request-management-api/request_api/services/workflowservice.py index f4b6f81a0..14bd0f35a 100644 --- a/request-management-api/request_api/services/workflowservice.py +++ b/request-management-api/request_api/services/workflowservice.py @@ -2,7 +2,7 @@ import os import json from enum import Enum -from request_api.exceptions import BusinessException, Error +from request_api.exceptions import BusinessException from request_api.utils.redispublisher import RedisPublisherService from request_api.services.external.bpmservice import MessageType, bpmservice, ProcessDefinitionKey from request_api.services.cfrfeeservice import cfrfeeservice @@ -26,7 +26,7 @@ class workflowservice: def createinstance(self, definitionkey, message): response = bpmservice().createinstance(definitionkey, json.loads(message)) if response is None: - raise Exception("Unable to create instance for key"+ definitionkey) + raise BusinessException("Unable to create instance for key"+ definitionkey) return response def postunopenedevent(self, id, wfinstanceid, requestsschema, status, ministries=None): diff --git a/request-management-api/request_api/tracer.py b/request-management-api/request_api/tracer.py index e88efcee9..80815410b 100644 --- a/request-management-api/request_api/tracer.py +++ b/request-management-api/request_api/tracer.py @@ -34,7 +34,7 @@ def get_instance(): def __init__(self): """Virtually private constructor.""" if Tracer.__instance is not None: - raise Exception('Attempt made to create multiple tracing instances') + raise BusinessException('Attempt made to create multiple tracing instances') api_tracer = ApiTracer() Tracer.__instance = ApiTracing(api_tracer.tracer) diff --git a/request-management-api/request_api/utils/redispublisher.py b/request-management-api/request_api/utils/redispublisher.py index 0cb8a1ff8..dab04e31a 100644 --- a/request-management-api/request_api/utils/redispublisher.py +++ b/request-management-api/request_api/utils/redispublisher.py @@ -22,7 +22,7 @@ def publishcommment(self, message): logging.info(message) self.publishtoredischannel(self.foicommentqueueredischannel, message) except Exception as ex: - current_app.logger.error("%s,%s" % ('Unable to get user details', ex)) + current_app.logger.error("%s,%s" % ('Unable to get user details', ex.message)) raise ex def publishtoredischannel(self, channel , message): From 8284a976d755398155cf0ffb15c50346c88093dc Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Tue, 17 Oct 2023 16:38:51 -0700 Subject: [PATCH 230/234] cherry picked and merged commit 71fe9a5906fca1fdcbe6f82b2156b4e0f8ed8c2e to local branch --- .../request_api/resources/foiadmin.py | 18 +++--- .../request_api/resources/foicfrfee.py | 8 +-- .../request_api/resources/foicomment.py | 20 +++---- .../request_api/resources/foidocument.py | 58 +++++++++++++++---- .../request_api/resources/foiemail.py | 8 +-- .../request_api/resources/foiextension.py | 24 ++++---- .../request_api/resources/foinotification.py | 16 ++--- .../request_api/resources/foipayment.py | 12 ++-- .../request_api/resources/foirecord.py | 44 +++++++------- .../request_api/resources/foirequest.py | 20 +++---- .../request_api/resources/foiwatcher.py | 24 ++++---- .../request_api/resources/foiworkflow.py | 4 +- .../request_api/services/rawrequestservice.py | 2 +- .../request_api/services/workflowservice.py | 4 +- 14 files changed, 150 insertions(+), 112 deletions(-) diff --git a/request-management-api/request_api/resources/foiadmin.py b/request-management-api/request_api/resources/foiadmin.py index 864157ec4..2e83e7cf5 100644 --- a/request-management-api/request_api/resources/foiadmin.py +++ b/request-management-api/request_api/resources/foiadmin.py @@ -50,9 +50,9 @@ def get(): try: result = programareadivisionservice().getallprogramareadivisions() return json.dumps(result), 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 - except BusinessException as exception: + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 + except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @cors_preflight('POST,OPTIONS') @@ -73,8 +73,8 @@ def post(): # if result.success == True: # asyncio.ensure_future(); return {'status': result.success, 'message':result.message, 'id':result.identifier}, 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -97,8 +97,8 @@ def put(divisionid): # if result.success == True: # asyncio.ensure_future(); return {'status': result.success, 'message':result.message, 'id':result.identifier}, 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -118,8 +118,8 @@ def put(divisionid): # if result.success == True: # asyncio.ensure_future(); return {'status': result.success, 'message':result.message, 'id':result.identifier}, 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foicfrfee.py b/request-management-api/request_api/resources/foicfrfee.py index e8dad2f91..1305b20df 100644 --- a/request-management-api/request_api/resources/foicfrfee.py +++ b/request-management-api/request_api/resources/foicfrfee.py @@ -59,7 +59,7 @@ def post(requestid, ministryrequestid): logging.error(verr) return {'status': False, 'message':verr.messages}, 400 except KeyError as err: - logging.error(err) + logging.error(type(err)) return {'status': False, 'message': EXCEPTION_MESSAGE_BAD_REQUEST}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -89,7 +89,7 @@ def post(requestid, ministryrequestid): logging.error(verr) return {'status': False, 'message':verr.messages}, 400 except KeyError as err: - logging.error(err) + logging.error(type(err)) return {'status': False, 'message': EXCEPTION_MESSAGE_BAD_REQUEST}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -109,7 +109,7 @@ def get(requestid): try: result = {"current": cfrfeeservice().getcfrfee(requestid), "history": cfrfeeservice().getcfrfeehistory(requestid)} return json.dumps(result), 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foicomment.py b/request-management-api/request_api/resources/foicomment.py index f68db9c50..de88e08da 100644 --- a/request-management-api/request_api/resources/foicomment.py +++ b/request-management-api/request_api/resources/foicomment.py @@ -54,8 +54,8 @@ def post(): if result.success == True: asyncio.ensure_future(eventservice().postcommentevent(result.identifier, "ministryrequest", AuthHelper.getuserid())) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -77,8 +77,8 @@ def post(): if result.success == True: asyncio.ensure_future(eventservice().postcommentevent(result.identifier, "rawrequest", AuthHelper.getuserid())) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -108,8 +108,8 @@ def get(requesttype, requestid): return json.dumps(result), 200 else: return {'status': 401, 'message':'Restricted Request'} , 401 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -136,8 +136,8 @@ def put(requesttype, commentid): if result.success == True: asyncio.ensure_future(eventservice().postcommentevent(result.identifier, requesttype, AuthHelper.getuserid(), True)) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -166,8 +166,8 @@ def put(requesttype, commentid): if result.success == True: asyncio.ensure_future(eventservice().postcommentevent(commentid, requesttype, AuthHelper.getuserid(), existingtaggedusers=result.args[0])) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foidocument.py b/request-management-api/request_api/resources/foidocument.py index f095f9444..725b00103 100644 --- a/request-management-api/request_api/resources/foidocument.py +++ b/request-management-api/request_api/resources/foidocument.py @@ -49,8 +49,8 @@ def get(requestid, requesttype): try: result = documentservice().getrequestdocumentsbyrole(requestid, requesttype, AuthHelper.isministrymember()) return json.dumps(result), 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -74,8 +74,8 @@ def post(requestid, requesttype): return {'status': result.success, 'message':result.message} , 200 except ValidationError as err: return {'status': False, 'message':err.messages}, 400 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -98,11 +98,49 @@ def post(requestid, documentid, requesttype): return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except ValidationError as err: return {'status': False, 'message':err.messages}, 400 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 +@cors_preflight('POST,OPTIONS') +@API.route('/foidocument///documentid//reclassify') +class ReclassifyFOIDocument(Resource): + """Resource for reclassifying uploaded attachments of FOI requests.""" + + @staticmethod + @TRACER.trace() + @cross_origin(origins=allowedorigins()) + @auth.require + def post(requesttype, requestid, documentid): + try: + requestjson = request.get_json() + documentschema = ReclassifyDocumentSchema().load(requestjson) + activedocuments = documentservice().getactiverequestdocuments(requestid, requesttype) + documentpath = 'no documentpath found' + if (requesttype == 'ministryrequest'): + for document in activedocuments: + if document['foiministrydocumentid'] == int(documentid): + documentpath = document['documentpath'] + else: + for document in activedocuments: + if document['foidocumentid'] == int(documentid): + documentpath = document['documentpath'] + + # move document in S3 + moveresult = documentservice().copyrequestdocumenttonewlocation(documentschema['category'], documentpath) + # save new version of document with updated documentpath + if moveresult['status'] == 'success': + result = documentservice().createrequestdocumentversion(requestid, documentid, documentschema, AuthHelper.getuserid(), requesttype) + return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 + return {'status': False, 'message': "Something went wrong moving the document's location" }, 500 + except ValidationError as err: + return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 + except BusinessException as exception: + return {'status': exception.status_code, 'message':exception.message}, 500 + @cors_preflight('POST,OPTIONS') @API.route('/foidocument///documentid//replace') class ReplaceFOIDocument(Resource): @@ -121,8 +159,8 @@ def post(requestid, documentid, requesttype): return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except ValidationError as err: return {'status': False, 'message':err.messages}, 400 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -141,7 +179,7 @@ def post(requestid, documentid, requesttype): try: result = documentservice().deleterequestdocument(requestid, documentid, AuthHelper.getuserid(), requesttype) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foiemail.py b/request-management-api/request_api/resources/foiemail.py index 0b2aa80b1..ca9cab211 100644 --- a/request-management-api/request_api/resources/foiemail.py +++ b/request-management-api/request_api/resources/foiemail.py @@ -51,8 +51,8 @@ def post(requestid, ministryrequestid, servicename): return json.dumps(result), 200 if result["success"] == True else 500 except ValueError as err: return {'status': 500, 'message':err.messages}, 500 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -72,8 +72,8 @@ def post(requestid, ministryrequestid, servicename): return json.dumps(result), 200 if result["success"] == True else 500 except ValueError as err: return {'status': 500, 'message':err.messages}, 500 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foiextension.py b/request-management-api/request_api/resources/foiextension.py index 86c9ef0d6..faab268a7 100644 --- a/request-management-api/request_api/resources/foiextension.py +++ b/request-management-api/request_api/resources/foiextension.py @@ -52,8 +52,8 @@ def get(requestid): try: extensionrecords = extensionservice().getrequestextensions(requestid) return json.dumps(extensionrecords), 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -71,8 +71,8 @@ def get(extensionid): try: extensionrecord = extensionservice().getrequestextension(extensionid) return json.dumps(extensionrecord), 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -96,8 +96,8 @@ def post(requestid, ministryrequestid): eventservice().posteventforextension(ministryrequestid, result.identifier, AuthHelper.getuserid(), AuthHelper.getusername(), "add") newduedate, = result.args return {'status': result.success, 'message':result.message,'id':result.identifier, 'newduedate': newduedate or None} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -121,8 +121,8 @@ def post(ministryrequestid): if len(result.args) > 0: eventservice().posteventforaxisextension(ministryrequestid, result.args[0], AuthHelper.getuserid(), AuthHelper.getusername(), "add") return {'status': result.success, 'message':result.message} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -145,8 +145,8 @@ def post(requestid, ministryrequestid, extensionid): # posteventforextension moved to createrequestextensionversion to generate the comments before updating the ministry table with new due date newduedate = result.args[-1] if len(result.args) > 0 else None return {'status': result.success, 'message':result.message,'id':result.identifier, 'newduedate': newduedate or None} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -167,7 +167,7 @@ def post(requestid, ministryrequestid, extensionid): eventservice().posteventforextension(ministryrequestid, extensionid, AuthHelper.getuserid(), AuthHelper.getusername(), "delete") newduedate = result.args[-1] if len(result.args) > 0 else None return {'status': result.success, 'message':result.message,'id':result.identifier, 'newduedate': newduedate or None} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foinotification.py b/request-management-api/request_api/resources/foinotification.py index a95fef873..1e597096d 100644 --- a/request-management-api/request_api/resources/foinotification.py +++ b/request-management-api/request_api/resources/foinotification.py @@ -47,8 +47,8 @@ def get(): return json.dumps(result), 200 except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -72,8 +72,8 @@ def delete(type=None,idnumber=None,notficationid=None): return {'status': result.success, 'message':result.message,'id':result.identifier} , 500 except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -92,8 +92,8 @@ def post(): reminderresponse = eventservice().postreminderevent() respcode = 200 if reminderresponse.success == True else 500 return {'status': reminderresponse.success, 'message':reminderresponse.message,'id': reminderresponse.identifier} , respcode - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -113,7 +113,7 @@ def post(request_id: int, ministry_request_id: int): reminderresponse = eventservice().postpaymentexpiryevent(ministry_request_id) respcode = 200 if reminderresponse.success == True else 500 return {'status': reminderresponse.success, 'message':reminderresponse.message,'id': reminderresponse.identifier} , respcode - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foipayment.py b/request-management-api/request_api/resources/foipayment.py index d5e7be7b5..da3d73399 100644 --- a/request-management-api/request_api/resources/foipayment.py +++ b/request-management-api/request_api/resources/foipayment.py @@ -48,8 +48,8 @@ def post(requestid, ministryrequestid): paymentschema = FOIRequestPaymentSchema().load(requestjson) result = paymentservice().createpayment(requestid, ministryrequestid, paymentschema) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -69,8 +69,8 @@ def post(requestid, ministryrequestid): paymentschema = FOIRequestPaymentSchema().load(requestjson) result = paymentservice().cancelpayment(requestid, ministryrequestid, paymentschema) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -88,8 +88,8 @@ def get(requestid, ministryrequestid): try: result = paymentservice().getpayment(requestid, ministryrequestid) return json.dumps(result), 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foirecord.py b/request-management-api/request_api/resources/foirecord.py index e53181c80..b5220904c 100644 --- a/request-management-api/request_api/resources/foirecord.py +++ b/request-management-api/request_api/resources/foirecord.py @@ -46,8 +46,8 @@ def get(requestid, ministryrequestid): try: result = recordservice().fetch(requestid, ministryrequestid) return json.dumps(result), 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except Exception as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -69,8 +69,8 @@ def post(requestid, ministryrequestid): response = recordservice().create(requestid, ministryrequestid, recordschema, AuthHelper.getuserid()) respcode = 200 if response.success == True else 500 return {'status': response.success, 'message':response.message,'data': response.args[0]} , respcode - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -91,8 +91,8 @@ def post(requestid, ministryrequestid): data = FOIRequestRecordUpdateSchema().load(requestjson) result = recordservice().update(requestid, ministryrequestid, data, AuthHelper.getuserid()) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as err: - return {'status': False, 'message':err['messages']}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -112,8 +112,8 @@ def post(requestid, ministryrequestid): recordschema = FOIRequestBulkRetryRecordSchema().load(requestjson, unknown=INCLUDE) result = recordservice().retry(requestid, ministryrequestid, recordschema) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -134,8 +134,8 @@ def post(requestid, ministryrequestid,recordid): result = recordservice().replace(requestid, ministryrequestid,recordid, recordschema,AuthHelper.getuserid()) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -157,8 +157,8 @@ def post(requestid, ministryrequestid, recordstype): response = recordservice().triggerpdfstitchservice(requestid, ministryrequestid, recordschema, AuthHelper.getuserid()) respcode = 200 if response.success == True else 500 return {'status': response.success, 'message':response.message,'id':response.identifier}, respcode - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -177,8 +177,8 @@ def get(requestid, ministryrequestid, recordstype): try: result = recordservice().getpdfstitchpackagetodownload(ministryrequestid, recordstype.lower()) return json.dumps(result), 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -198,15 +198,15 @@ def get(requestid, ministryrequestid, recordstype): result = recordservice().getpdfstichstatus(ministryrequestid, recordstype.lower()) #("getpdfstichstatus result == ", result) return result, 200 - except KeyError as err: - print("KeyError == ", err.messages) - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + print("KeyError") + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: print("BusinessException == ", exception.message) return {'status': exception.status_code, 'message':exception.message}, 500 except Exception as error: print("Exception error == ", error) - return {'status': False, 'message':error.message}, 500 + return {'status': False, 'message': f"{error=}"}, 500 @cors_preflight('GET,OPTIONS') @API.route('/foirecord//ministryrequest///recrodschanged') @@ -224,12 +224,12 @@ def get(requestid, ministryrequestid, recordstype): result = recordservice().isrecordschanged(ministryrequestid, recordstype.lower()) #print("records changed == ", result) return result, 200 - except KeyError as err: - print("KeyError == ", err.messages) - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + print("KeyError") + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: print("BusinessException == ", exception.message) return {'status': exception.status_code, 'message':exception.message}, 500 except Exception as error: print("Exception error == ", error) - return {'status': False, 'message':error.message}, 500 \ No newline at end of file + return {'status': False, 'message': f"{error=}"}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foirequest.py b/request-management-api/request_api/resources/foirequest.py index 4ccb5aff3..ec1d67fb1 100644 --- a/request-management-api/request_api/resources/foirequest.py +++ b/request-management-api/request_api/resources/foirequest.py @@ -72,8 +72,8 @@ def get(foirequestid,foiministryrequestid,usertype = None): return jsondata , statuscode except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -118,8 +118,8 @@ def post(): return {'status': result.success, 'message':result.message,'id':result.identifier, 'ministryRequests': result.args[0]} , 200 except ValidationError as err: return {'status': False, 'message':err.messages}, 400 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -147,8 +147,8 @@ def post(foirequestid,foiministryrequestid): return {'status': False, 'message':EXCEPTION_MESSAGE_NOTFOUND_REQUEST,'id':foirequestid} , 404 except ValidationError as err: return {'status': False, 'message':err.messages}, 400 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -187,8 +187,8 @@ def post(foirequestid,foiministryrequestid,actiontype = None,usertype = None): return {'status': False, 'message':EXCEPTION_MESSAGE_NOTFOUND_REQUEST,'id':foirequestid} , 404 except ValidationError as err: return {'status': False, 'message':err.messages}, 400 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -246,8 +246,8 @@ def get(foirequestid, foiministryrequestid): return jsondata , statuscode except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foiwatcher.py b/request-management-api/request_api/resources/foiwatcher.py index 70a16e0f3..78750acbd 100644 --- a/request-management-api/request_api/resources/foiwatcher.py +++ b/request-management-api/request_api/resources/foiwatcher.py @@ -49,8 +49,8 @@ def get(requestid): return json.dumps(result), 200 except ValueError as err: return {'status': 500, 'message':err.messages}, 500 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -72,8 +72,8 @@ def post(): if result.success == True: eventservice().posteventforwatcher(requestjson["requestid"], requestjson, "rawrequest",AuthHelper.getuserid(), AuthHelper.getusername()) return {'status': result.success, 'message':result.message} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -91,8 +91,8 @@ def put(requestid): try: result = watcherservice().disablerawrequestwatchers(requestid, AuthHelper.getuserid()) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -110,8 +110,8 @@ def get(ministryrequestid): try: result = watcherservice().getministryrequestwatchers(ministryrequestid,AuthHelper.isministrymember()) return json.dumps(result), 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -133,8 +133,8 @@ def post(): if result.success == True: eventservice().posteventforwatcher(requestjson["ministryrequestid"], requestjson, "ministryrequest", AuthHelper.getuserid(), AuthHelper.getusername()) return {'status': result.success, 'message':result.message} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -152,7 +152,7 @@ def put(ministryrequestid): try: result = watcherservice().disableministryrequestwatchers(ministryrequestid, AuthHelper.getuserid()) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foiworkflow.py b/request-management-api/request_api/resources/foiworkflow.py index ccb2a0fa7..006ac55cc 100644 --- a/request-management-api/request_api/resources/foiworkflow.py +++ b/request-management-api/request_api/resources/foiworkflow.py @@ -47,8 +47,8 @@ def post(requesttype, requestid): return json.dumps({"message": str(response)}), 200 except ValueError as err: return {'status': 500, 'message':err.messages}, 500 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + except KeyError as error: + return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/services/rawrequestservice.py b/request-management-api/request_api/services/rawrequestservice.py index 594932595..c72d3ccf1 100644 --- a/request-management-api/request_api/services/rawrequestservice.py +++ b/request-management-api/request_api/services/rawrequestservice.py @@ -68,7 +68,7 @@ def saverawrequest(self, requestdatajson, sourceofsubmission, userid,notes): try: workflowservice().createinstance(redispubservice.foirequestqueueredischannel, json_data) except Exception as ex: - logging.error("Unable to create instance", ex) + logging.error("Unable to create instance") asyncio.ensure_future(redispubservice.publishrequest(json_data)) return result diff --git a/request-management-api/request_api/services/workflowservice.py b/request-management-api/request_api/services/workflowservice.py index 14bd0f35a..e3faaf143 100644 --- a/request-management-api/request_api/services/workflowservice.py +++ b/request-management-api/request_api/services/workflowservice.py @@ -2,7 +2,7 @@ import os import json from enum import Enum -from request_api.exceptions import BusinessException +from request_api.exceptions import BusinessException, Error from request_api.utils.redispublisher import RedisPublisherService from request_api.services.external.bpmservice import MessageType, bpmservice, ProcessDefinitionKey from request_api.services.cfrfeeservice import cfrfeeservice @@ -26,7 +26,7 @@ class workflowservice: def createinstance(self, definitionkey, message): response = bpmservice().createinstance(definitionkey, json.loads(message)) if response is None: - raise BusinessException("Unable to create instance for key"+ definitionkey) + raise BusinessException(Error.INVALID_INPUT) return response def postunopenedevent(self, id, wfinstanceid, requestsschema, status, ministries=None): From 0ff1d2f0972ea8ab21e59d44340a17bd0830ff1b Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Tue, 17 Oct 2023 17:15:38 -0700 Subject: [PATCH 231/234] Validation exceptions take a message arg and therefore .messages is not a valid k:v pairs for validation exceptions --- request-management-api/request_api/resources/foicfrfee.py | 4 ++-- .../request_api/resources/foidocument.py | 8 ++++---- .../request_api/resources/foirequest.py | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/request-management-api/request_api/resources/foicfrfee.py b/request-management-api/request_api/resources/foicfrfee.py index 1305b20df..8c925f4e7 100644 --- a/request-management-api/request_api/resources/foicfrfee.py +++ b/request-management-api/request_api/resources/foicfrfee.py @@ -57,7 +57,7 @@ def post(requestid, ministryrequestid): return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except ValidationError as verr: logging.error(verr) - return {'status': False, 'message':verr.messages}, 400 + return {'status': False, 'message': verr}, 400 except KeyError as err: logging.error(type(err)) return {'status': False, 'message': EXCEPTION_MESSAGE_BAD_REQUEST}, 400 @@ -87,7 +87,7 @@ def post(requestid, ministryrequestid): return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except ValidationError as verr: logging.error(verr) - return {'status': False, 'message':verr.messages}, 400 + return {'status': False, 'message': verr}, 400 except KeyError as err: logging.error(type(err)) return {'status': False, 'message': EXCEPTION_MESSAGE_BAD_REQUEST}, 400 diff --git a/request-management-api/request_api/resources/foidocument.py b/request-management-api/request_api/resources/foidocument.py index 725b00103..7df6c32dc 100644 --- a/request-management-api/request_api/resources/foidocument.py +++ b/request-management-api/request_api/resources/foidocument.py @@ -73,7 +73,7 @@ def post(requestid, requesttype): result = documentservice().createrequestdocument(requestid, documentschema, AuthHelper.getuserid(), requesttype) return {'status': result.success, 'message':result.message} , 200 except ValidationError as err: - return {'status': False, 'message':err.messages}, 400 + return {'status': False, 'message': err}, 400 except KeyError as error: return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: @@ -97,7 +97,7 @@ def post(requestid, documentid, requesttype): result = documentservice().createrequestdocumentversion(requestid, documentid, documentschema, AuthHelper.getuserid(), requesttype) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except ValidationError as err: - return {'status': False, 'message':err.messages}, 400 + return {'status': False, 'message': err}, 400 except KeyError as error: return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: @@ -135,7 +135,7 @@ def post(requesttype, requestid, documentid): return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 return {'status': False, 'message': "Something went wrong moving the document's location" }, 500 except ValidationError as err: - return {'status': False, 'message':err.messages}, 400 + return {'status': False, 'message': err}, 400 except KeyError as error: return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: @@ -158,7 +158,7 @@ def post(requestid, documentid, requesttype): result = documentservice().createrequestdocumentversion(requestid, documentid, documentschema, AuthHelper.getuserid(), requesttype) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except ValidationError as err: - return {'status': False, 'message':err.messages}, 400 + return {'status': False, 'message': err}, 400 except KeyError as error: return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: diff --git a/request-management-api/request_api/resources/foirequest.py b/request-management-api/request_api/resources/foirequest.py index ec1d67fb1..c7d87aa6c 100644 --- a/request-management-api/request_api/resources/foirequest.py +++ b/request-management-api/request_api/resources/foirequest.py @@ -117,7 +117,7 @@ def post(): return {'status': result.success, 'message':result.message,'id':result.identifier, 'ministryRequests': result.args[0]} , 200 except ValidationError as err: - return {'status': False, 'message':err.messages}, 400 + return {'status': False, 'message': err}, 400 except KeyError as error: return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: @@ -146,7 +146,7 @@ def post(foirequestid,foiministryrequestid): else: return {'status': False, 'message':EXCEPTION_MESSAGE_NOTFOUND_REQUEST,'id':foirequestid} , 404 except ValidationError as err: - return {'status': False, 'message':err.messages}, 400 + return {'status': False, 'message': err}, 400 except KeyError as error: return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: @@ -186,7 +186,7 @@ def post(foirequestid,foiministryrequestid,actiontype = None,usertype = None): else: return {'status': False, 'message':EXCEPTION_MESSAGE_NOTFOUND_REQUEST,'id':foirequestid} , 404 except ValidationError as err: - return {'status': False, 'message':err.messages}, 400 + return {'status': False, 'message': err}, 400 except KeyError as error: return {'status': False, 'message': f"{error=}"}, 400 except BusinessException as exception: @@ -224,7 +224,7 @@ def put(foirequestid): else: return {'status': False, 'message':EXCEPTION_MESSAGE_NOTFOUND_REQUEST,'id':foirequestid} , 404 except ValidationError as err: - return {'status': False, 'message':err.messages}, 40 + return {'status': False, 'message': err}, 40 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 From af60d65f55d40dfc3753fbd965c0fc243fc1e2a3 Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Tue, 17 Oct 2023 17:40:53 -0700 Subject: [PATCH 232/234] Adjusted handling for keyerror messages --- .../request_api/resources/foiadmin.py | 8 +++---- .../request_api/resources/foicfrfee.py | 2 +- .../request_api/resources/foicomment.py | 10 ++++----- .../request_api/resources/foidocument.py | 12 +++++----- .../request_api/resources/foiemail.py | 4 ++-- .../request_api/resources/foiextension.py | 12 +++++----- .../request_api/resources/foinotification.py | 8 +++---- .../request_api/resources/foipayment.py | 6 ++--- .../request_api/resources/foirecord.py | 22 +++++++++---------- .../request_api/resources/foirequest.py | 10 ++++----- .../request_api/resources/foiwatcher.py | 12 +++++----- .../request_api/resources/foiworkflow.py | 2 +- 12 files changed, 54 insertions(+), 54 deletions(-) diff --git a/request-management-api/request_api/resources/foiadmin.py b/request-management-api/request_api/resources/foiadmin.py index 2e83e7cf5..98fff07f2 100644 --- a/request-management-api/request_api/resources/foiadmin.py +++ b/request-management-api/request_api/resources/foiadmin.py @@ -51,7 +51,7 @@ def get(): result = programareadivisionservice().getallprogramareadivisions() return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -74,7 +74,7 @@ def post(): # asyncio.ensure_future(); return {'status': result.success, 'message':result.message, 'id':result.identifier}, 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -98,7 +98,7 @@ def put(divisionid): # asyncio.ensure_future(); return {'status': result.success, 'message':result.message, 'id':result.identifier}, 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -119,7 +119,7 @@ def put(divisionid): # asyncio.ensure_future(); return {'status': result.success, 'message':result.message, 'id':result.identifier}, 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foicfrfee.py b/request-management-api/request_api/resources/foicfrfee.py index 8c925f4e7..37308c685 100644 --- a/request-management-api/request_api/resources/foicfrfee.py +++ b/request-management-api/request_api/resources/foicfrfee.py @@ -110,6 +110,6 @@ def get(requestid): result = {"current": cfrfeeservice().getcfrfee(requestid), "history": cfrfeeservice().getcfrfeehistory(requestid)} return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foicomment.py b/request-management-api/request_api/resources/foicomment.py index de88e08da..866030b20 100644 --- a/request-management-api/request_api/resources/foicomment.py +++ b/request-management-api/request_api/resources/foicomment.py @@ -55,7 +55,7 @@ def post(): asyncio.ensure_future(eventservice().postcommentevent(result.identifier, "ministryrequest", AuthHelper.getuserid())) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -78,7 +78,7 @@ def post(): asyncio.ensure_future(eventservice().postcommentevent(result.identifier, "rawrequest", AuthHelper.getuserid())) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -109,7 +109,7 @@ def get(requesttype, requestid): else: return {'status': 401, 'message':'Restricted Request'} , 401 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -137,7 +137,7 @@ def put(requesttype, commentid): asyncio.ensure_future(eventservice().postcommentevent(result.identifier, requesttype, AuthHelper.getuserid(), True)) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -167,7 +167,7 @@ def put(requesttype, commentid): asyncio.ensure_future(eventservice().postcommentevent(commentid, requesttype, AuthHelper.getuserid(), existingtaggedusers=result.args[0])) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foidocument.py b/request-management-api/request_api/resources/foidocument.py index 7df6c32dc..7e90db779 100644 --- a/request-management-api/request_api/resources/foidocument.py +++ b/request-management-api/request_api/resources/foidocument.py @@ -50,7 +50,7 @@ def get(requestid, requesttype): result = documentservice().getrequestdocumentsbyrole(requestid, requesttype, AuthHelper.isministrymember()) return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -75,7 +75,7 @@ def post(requestid, requesttype): except ValidationError as err: return {'status': False, 'message': err}, 400 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -99,7 +99,7 @@ def post(requestid, documentid, requesttype): except ValidationError as err: return {'status': False, 'message': err}, 400 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -137,7 +137,7 @@ def post(requesttype, requestid, documentid): except ValidationError as err: return {'status': False, 'message': err}, 400 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -160,7 +160,7 @@ def post(requestid, documentid, requesttype): except ValidationError as err: return {'status': False, 'message': err}, 400 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -180,6 +180,6 @@ def post(requestid, documentid, requesttype): result = documentservice().deleterequestdocument(requestid, documentid, AuthHelper.getuserid(), requesttype) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foiemail.py b/request-management-api/request_api/resources/foiemail.py index ca9cab211..0c590be23 100644 --- a/request-management-api/request_api/resources/foiemail.py +++ b/request-management-api/request_api/resources/foiemail.py @@ -52,7 +52,7 @@ def post(requestid, ministryrequestid, servicename): except ValueError as err: return {'status': 500, 'message':err.messages}, 500 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -73,7 +73,7 @@ def post(requestid, ministryrequestid, servicename): except ValueError as err: return {'status': 500, 'message':err.messages}, 500 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foiextension.py b/request-management-api/request_api/resources/foiextension.py index faab268a7..b985cc477 100644 --- a/request-management-api/request_api/resources/foiextension.py +++ b/request-management-api/request_api/resources/foiextension.py @@ -53,7 +53,7 @@ def get(requestid): extensionrecords = extensionservice().getrequestextensions(requestid) return json.dumps(extensionrecords), 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -72,7 +72,7 @@ def get(extensionid): extensionrecord = extensionservice().getrequestextension(extensionid) return json.dumps(extensionrecord), 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -97,7 +97,7 @@ def post(requestid, ministryrequestid): newduedate, = result.args return {'status': result.success, 'message':result.message,'id':result.identifier, 'newduedate': newduedate or None} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -122,7 +122,7 @@ def post(ministryrequestid): eventservice().posteventforaxisextension(ministryrequestid, result.args[0], AuthHelper.getuserid(), AuthHelper.getusername(), "add") return {'status': result.success, 'message':result.message} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -146,7 +146,7 @@ def post(requestid, ministryrequestid, extensionid): newduedate = result.args[-1] if len(result.args) > 0 else None return {'status': result.success, 'message':result.message,'id':result.identifier, 'newduedate': newduedate or None} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -168,6 +168,6 @@ def post(requestid, ministryrequestid, extensionid): newduedate = result.args[-1] if len(result.args) > 0 else None return {'status': result.success, 'message':result.message,'id':result.identifier, 'newduedate': newduedate or None} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foinotification.py b/request-management-api/request_api/resources/foinotification.py index 1e597096d..4967f31ab 100644 --- a/request-management-api/request_api/resources/foinotification.py +++ b/request-management-api/request_api/resources/foinotification.py @@ -48,7 +48,7 @@ def get(): except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -73,7 +73,7 @@ def delete(type=None,idnumber=None,notficationid=None): except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -93,7 +93,7 @@ def post(): respcode = 200 if reminderresponse.success == True else 500 return {'status': reminderresponse.success, 'message':reminderresponse.message,'id': reminderresponse.identifier} , respcode except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -114,6 +114,6 @@ def post(request_id: int, ministry_request_id: int): respcode = 200 if reminderresponse.success == True else 500 return {'status': reminderresponse.success, 'message':reminderresponse.message,'id': reminderresponse.identifier} , respcode except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foipayment.py b/request-management-api/request_api/resources/foipayment.py index da3d73399..b637d0772 100644 --- a/request-management-api/request_api/resources/foipayment.py +++ b/request-management-api/request_api/resources/foipayment.py @@ -49,7 +49,7 @@ def post(requestid, ministryrequestid): result = paymentservice().createpayment(requestid, ministryrequestid, paymentschema) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -70,7 +70,7 @@ def post(requestid, ministryrequestid): result = paymentservice().cancelpayment(requestid, ministryrequestid, paymentschema) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -89,7 +89,7 @@ def get(requestid, ministryrequestid): result = paymentservice().getpayment(requestid, ministryrequestid) return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foirecord.py b/request-management-api/request_api/resources/foirecord.py index b5220904c..a146b1a4f 100644 --- a/request-management-api/request_api/resources/foirecord.py +++ b/request-management-api/request_api/resources/foirecord.py @@ -47,7 +47,7 @@ def get(requestid, ministryrequestid): result = recordservice().fetch(requestid, ministryrequestid) return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except Exception as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -70,7 +70,7 @@ def post(requestid, ministryrequestid): respcode = 200 if response.success == True else 500 return {'status': response.success, 'message':response.message,'data': response.args[0]} , respcode except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -92,7 +92,7 @@ def post(requestid, ministryrequestid): result = recordservice().update(requestid, ministryrequestid, data, AuthHelper.getuserid()) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -113,7 +113,7 @@ def post(requestid, ministryrequestid): result = recordservice().retry(requestid, ministryrequestid, recordschema) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -135,7 +135,7 @@ def post(requestid, ministryrequestid,recordid): return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -158,7 +158,7 @@ def post(requestid, ministryrequestid, recordstype): respcode = 200 if response.success == True else 500 return {'status': response.success, 'message':response.message,'id':response.identifier}, respcode except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -178,7 +178,7 @@ def get(requestid, ministryrequestid, recordstype): result = recordservice().getpdfstitchpackagetodownload(ministryrequestid, recordstype.lower()) return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -199,8 +199,8 @@ def get(requestid, ministryrequestid, recordstype): #("getpdfstichstatus result == ", result) return result, 200 except KeyError as error: - print("KeyError") - return {'status': False, 'message': f"{error=}"}, 400 + print(str(type(error).__name__)) + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: print("BusinessException == ", exception.message) return {'status': exception.status_code, 'message':exception.message}, 500 @@ -225,8 +225,8 @@ def get(requestid, ministryrequestid, recordstype): #print("records changed == ", result) return result, 200 except KeyError as error: - print("KeyError") - return {'status': False, 'message': f"{error=}"}, 400 + print(str(type(error).__name__)) + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: print("BusinessException == ", exception.message) return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foirequest.py b/request-management-api/request_api/resources/foirequest.py index c7d87aa6c..f161841f5 100644 --- a/request-management-api/request_api/resources/foirequest.py +++ b/request-management-api/request_api/resources/foirequest.py @@ -73,7 +73,7 @@ def get(foirequestid,foiministryrequestid,usertype = None): except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -119,7 +119,7 @@ def post(): except ValidationError as err: return {'status': False, 'message': err}, 400 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -148,7 +148,7 @@ def post(foirequestid,foiministryrequestid): except ValidationError as err: return {'status': False, 'message': err}, 400 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -188,7 +188,7 @@ def post(foirequestid,foiministryrequestid,actiontype = None,usertype = None): except ValidationError as err: return {'status': False, 'message': err}, 400 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -247,7 +247,7 @@ def get(foirequestid, foiministryrequestid): except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foiwatcher.py b/request-management-api/request_api/resources/foiwatcher.py index 78750acbd..f96cb43c5 100644 --- a/request-management-api/request_api/resources/foiwatcher.py +++ b/request-management-api/request_api/resources/foiwatcher.py @@ -50,7 +50,7 @@ def get(requestid): except ValueError as err: return {'status': 500, 'message':err.messages}, 500 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -73,7 +73,7 @@ def post(): eventservice().posteventforwatcher(requestjson["requestid"], requestjson, "rawrequest",AuthHelper.getuserid(), AuthHelper.getusername()) return {'status': result.success, 'message':result.message} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -92,7 +92,7 @@ def put(requestid): result = watcherservice().disablerawrequestwatchers(requestid, AuthHelper.getuserid()) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -111,7 +111,7 @@ def get(ministryrequestid): result = watcherservice().getministryrequestwatchers(ministryrequestid,AuthHelper.isministrymember()) return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -134,7 +134,7 @@ def post(): eventservice().posteventforwatcher(requestjson["ministryrequestid"], requestjson, "ministryrequest", AuthHelper.getuserid(), AuthHelper.getusername()) return {'status': result.success, 'message':result.message} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -153,6 +153,6 @@ def put(ministryrequestid): result = watcherservice().disableministryrequestwatchers(ministryrequestid, AuthHelper.getuserid()) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foiworkflow.py b/request-management-api/request_api/resources/foiworkflow.py index 006ac55cc..71e5b68f8 100644 --- a/request-management-api/request_api/resources/foiworkflow.py +++ b/request-management-api/request_api/resources/foiworkflow.py @@ -48,7 +48,7 @@ def post(requesttype, requestid): except ValueError as err: return {'status': 500, 'message':err.messages}, 500 except KeyError as error: - return {'status': False, 'message': f"{error=}"}, 400 + return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 From 159bf0f0eaba18b1be8eba0162bdd10d79d2598f Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Wed, 18 Oct 2023 14:19:03 -0700 Subject: [PATCH 233/234] Foi Flow exception handling fixed. WIP DocReviewer --- .../request_api/resources/foicfrfee.py | 8 ++++---- .../request_api/resources/foicomment.py | 4 ++-- .../request_api/resources/foidocument.py | 8 ++++---- .../request_api/resources/foiemail.py | 4 ++-- .../request_api/resources/foirecord.py | 6 +++--- .../request_api/resources/foirequest.py | 12 ++++++------ .../request_api/resources/foiwatcher.py | 2 +- .../request_api/resources/foiworkflow.py | 2 +- .../request_api/utils/redispublisher.py | 2 +- 9 files changed, 24 insertions(+), 24 deletions(-) diff --git a/request-management-api/request_api/resources/foicfrfee.py b/request-management-api/request_api/resources/foicfrfee.py index 37308c685..c8e36e801 100644 --- a/request-management-api/request_api/resources/foicfrfee.py +++ b/request-management-api/request_api/resources/foicfrfee.py @@ -57,9 +57,9 @@ def post(requestid, ministryrequestid): return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except ValidationError as verr: logging.error(verr) - return {'status': False, 'message': verr}, 400 + return {'status': False, 'message': str(verr)}, 400 except KeyError as err: - logging.error(type(err)) + logging.error(str(type(err).__name__)) return {'status': False, 'message': EXCEPTION_MESSAGE_BAD_REQUEST}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -87,9 +87,9 @@ def post(requestid, ministryrequestid): return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except ValidationError as verr: logging.error(verr) - return {'status': False, 'message': verr}, 400 + return {'status': False, 'message': str(verr)}, 400 except KeyError as err: - logging.error(type(err)) + logging.error(str(type(err).__name__)) return {'status': False, 'message': EXCEPTION_MESSAGE_BAD_REQUEST}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foicomment.py b/request-management-api/request_api/resources/foicomment.py index 866030b20..31ee32336 100644 --- a/request-management-api/request_api/resources/foicomment.py +++ b/request-management-api/request_api/resources/foicomment.py @@ -184,7 +184,7 @@ def get(requestid=None): result = commentservice().createcommenttagginguserlist("rawrequest",requestid) return json.dumps(result), 200 except ValueError: - return {'status': 500, 'message':"Invalid Request"}, 400 + return {'status': 500, 'message':"Invalid Request"}, 500 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -200,6 +200,6 @@ def get(ministryrequestid=None): result = commentservice().createcommenttagginguserlist("ministryrequest",ministryrequestid) return json.dumps(result), 200 except ValueError: - return {'status': 500, 'message':"Invalid Request"}, 400 + return {'status': 500, 'message':"Invalid Request"}, 500 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foidocument.py b/request-management-api/request_api/resources/foidocument.py index 7e90db779..decf80ddb 100644 --- a/request-management-api/request_api/resources/foidocument.py +++ b/request-management-api/request_api/resources/foidocument.py @@ -73,7 +73,7 @@ def post(requestid, requesttype): result = documentservice().createrequestdocument(requestid, documentschema, AuthHelper.getuserid(), requesttype) return {'status': result.success, 'message':result.message} , 200 except ValidationError as err: - return {'status': False, 'message': err}, 400 + return {'status': False, 'message': str(err)}, 400 except KeyError as error: return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: @@ -97,7 +97,7 @@ def post(requestid, documentid, requesttype): result = documentservice().createrequestdocumentversion(requestid, documentid, documentschema, AuthHelper.getuserid(), requesttype) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except ValidationError as err: - return {'status': False, 'message': err}, 400 + return {'status': False, 'message': str(err)}, 400 except KeyError as error: return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: @@ -135,7 +135,7 @@ def post(requesttype, requestid, documentid): return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 return {'status': False, 'message': "Something went wrong moving the document's location" }, 500 except ValidationError as err: - return {'status': False, 'message': err}, 400 + return {'status': False, 'message': str(err)}, 400 except KeyError as error: return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: @@ -158,7 +158,7 @@ def post(requestid, documentid, requesttype): result = documentservice().createrequestdocumentversion(requestid, documentid, documentschema, AuthHelper.getuserid(), requesttype) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except ValidationError as err: - return {'status': False, 'message': err}, 400 + return {'status': False, 'message': str(err)}, 400 except KeyError as error: return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: diff --git a/request-management-api/request_api/resources/foiemail.py b/request-management-api/request_api/resources/foiemail.py index 0c590be23..143b280b1 100644 --- a/request-management-api/request_api/resources/foiemail.py +++ b/request-management-api/request_api/resources/foiemail.py @@ -50,7 +50,7 @@ def post(requestid, ministryrequestid, servicename): result = emailservice().send(servicename.upper(), requestid, ministryrequestid, emailschema) return json.dumps(result), 200 if result["success"] == True else 500 except ValueError as err: - return {'status': 500, 'message':err.messages}, 500 + return {'status': 500, 'message': str(err)}, 500 except KeyError as error: return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: @@ -71,7 +71,7 @@ def post(requestid, ministryrequestid, servicename): result = emailservice().acknowledge(servicename.upper(), requestid, ministryrequestid) return json.dumps(result), 200 if result["success"] == True else 500 except ValueError as err: - return {'status': 500, 'message':err.messages}, 500 + return {'status': 500, 'message': str(err)}, 500 except KeyError as error: return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: diff --git a/request-management-api/request_api/resources/foirecord.py b/request-management-api/request_api/resources/foirecord.py index a146b1a4f..e90e9ae34 100644 --- a/request-management-api/request_api/resources/foirecord.py +++ b/request-management-api/request_api/resources/foirecord.py @@ -49,7 +49,7 @@ def get(requestid, ministryrequestid): except KeyError as error: return {'status': False, 'message': str(type(error).__name__)}, 400 except Exception as exception: - return {'status': exception.status_code, 'message':exception.message}, 500 + return {'status': False, 'message': str(exception)}, 500 @cors_preflight('POST,OPTIONS') @API.route('/foirecord//ministryrequest/') @@ -206,7 +206,7 @@ def get(requestid, ministryrequestid, recordstype): return {'status': exception.status_code, 'message':exception.message}, 500 except Exception as error: print("Exception error == ", error) - return {'status': False, 'message': f"{error=}"}, 500 + return {'status': False, 'message': str(error)}, 500 @cors_preflight('GET,OPTIONS') @API.route('/foirecord//ministryrequest///recrodschanged') @@ -232,4 +232,4 @@ def get(requestid, ministryrequestid, recordstype): return {'status': exception.status_code, 'message':exception.message}, 500 except Exception as error: print("Exception error == ", error) - return {'status': False, 'message': f"{error=}"}, 500 \ No newline at end of file + return {'status': False, 'message': str(error)}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foirequest.py b/request-management-api/request_api/resources/foirequest.py index f161841f5..037e89a55 100644 --- a/request-management-api/request_api/resources/foirequest.py +++ b/request-management-api/request_api/resources/foirequest.py @@ -117,7 +117,7 @@ def post(): return {'status': result.success, 'message':result.message,'id':result.identifier, 'ministryRequests': result.args[0]} , 200 except ValidationError as err: - return {'status': False, 'message': err}, 400 + return {'status': False, 'message': str(err)}, 400 except KeyError as error: return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: @@ -146,7 +146,7 @@ def post(foirequestid,foiministryrequestid): else: return {'status': False, 'message':EXCEPTION_MESSAGE_NOTFOUND_REQUEST,'id':foirequestid} , 404 except ValidationError as err: - return {'status': False, 'message': err}, 400 + return {'status': False, 'message': str(err)}, 400 except KeyError as error: return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: @@ -186,7 +186,7 @@ def post(foirequestid,foiministryrequestid,actiontype = None,usertype = None): else: return {'status': False, 'message':EXCEPTION_MESSAGE_NOTFOUND_REQUEST,'id':foirequestid} , 404 except ValidationError as err: - return {'status': False, 'message': err}, 400 + return {'status': False, 'message': str(err)}, 400 except KeyError as error: return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: @@ -224,7 +224,7 @@ def put(foirequestid): else: return {'status': False, 'message':EXCEPTION_MESSAGE_NOTFOUND_REQUEST,'id':foirequestid} , 404 except ValidationError as err: - return {'status': False, 'message': err}, 40 + return {'status': False, 'message': str(err)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -271,7 +271,7 @@ def post(ministryrequestid=None,type=None): else: return {'status': result.success, 'message':result.message,'id':result.identifier} , 500 except ValueError: - return {'status': 500, 'message':"Invalid Request"}, 400 + return {'status': 500, 'message':"Invalid Request"}, 500 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -287,6 +287,6 @@ def get(ministryrequestid,usertype=None): try : return FOIRequest.get(requestservice().getrequestid(ministryrequestid), ministryrequestid, usertype) except ValueError: - return {'status': 500, 'message':"Invalid Request"}, 400 + return {'status': 500, 'message':"Invalid Request"}, 500 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foiwatcher.py b/request-management-api/request_api/resources/foiwatcher.py index f96cb43c5..065ba1154 100644 --- a/request-management-api/request_api/resources/foiwatcher.py +++ b/request-management-api/request_api/resources/foiwatcher.py @@ -48,7 +48,7 @@ def get(requestid): result = watcherservice().getrawrequestwatchers(requestid) return json.dumps(result), 200 except ValueError as err: - return {'status': 500, 'message':err.messages}, 500 + return {'status': 500, 'message': str(err)}, 500 except KeyError as error: return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: diff --git a/request-management-api/request_api/resources/foiworkflow.py b/request-management-api/request_api/resources/foiworkflow.py index 71e5b68f8..e22631ce1 100644 --- a/request-management-api/request_api/resources/foiworkflow.py +++ b/request-management-api/request_api/resources/foiworkflow.py @@ -46,7 +46,7 @@ def post(requesttype, requestid): response = workflowservice().syncwfinstance(requesttype, requestid, True) return json.dumps({"message": str(response)}), 200 except ValueError as err: - return {'status': 500, 'message':err.messages}, 500 + return {'status': 500, 'message': str(err)}, 500 except KeyError as error: return {'status': False, 'message': str(type(error).__name__)}, 400 except BusinessException as exception: diff --git a/request-management-api/request_api/utils/redispublisher.py b/request-management-api/request_api/utils/redispublisher.py index dab04e31a..0cb8a1ff8 100644 --- a/request-management-api/request_api/utils/redispublisher.py +++ b/request-management-api/request_api/utils/redispublisher.py @@ -22,7 +22,7 @@ def publishcommment(self, message): logging.info(message) self.publishtoredischannel(self.foicommentqueueredischannel, message) except Exception as ex: - current_app.logger.error("%s,%s" % ('Unable to get user details', ex.message)) + current_app.logger.error("%s,%s" % ('Unable to get user details', ex)) raise ex def publishtoredischannel(self, channel , message): From 91cea4b89f972fe724fb96e37d7dcab8a2c8b06d Mon Sep 17 00:00:00 2001 From: Aman-Hundal Date: Wed, 18 Oct 2023 16:22:34 -0700 Subject: [PATCH 234/234] Revised exception handling after comments/discussion with Nick Kan --- .../request_api/resources/foiadmin.py | 9 ++++---- .../request_api/resources/foicfrfee.py | 15 ++++++------ .../request_api/resources/foicomment.py | 11 +++++---- .../request_api/resources/foidocument.py | 15 ++++++------ .../request_api/resources/foiemail.py | 5 ++-- .../request_api/resources/foiextension.py | 13 ++++++----- .../request_api/resources/foinotification.py | 9 ++++---- .../request_api/resources/foipayment.py | 7 +++--- .../request_api/resources/foirecord.py | 23 ++++++++++--------- .../request_api/resources/foirequest.py | 11 +++++---- .../request_api/resources/foiwatcher.py | 13 ++++++----- .../request_api/resources/foiworkflow.py | 3 ++- .../request_api/services/rawrequestservice.py | 2 +- .../request_api/services/workflowservice.py | 2 +- request-management-api/request_api/tracer.py | 2 +- 15 files changed, 76 insertions(+), 64 deletions(-) diff --git a/request-management-api/request_api/resources/foiadmin.py b/request-management-api/request_api/resources/foiadmin.py index 98fff07f2..218fd9156 100644 --- a/request-management-api/request_api/resources/foiadmin.py +++ b/request-management-api/request_api/resources/foiadmin.py @@ -30,6 +30,7 @@ API = Namespace('FOIAdmin', description='Endpoints for FOI admin management') TRACER = Tracer.get_instance() +CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " """Custom exception messages """ @@ -51,7 +52,7 @@ def get(): result = programareadivisionservice().getallprogramareadivisions() return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -74,7 +75,7 @@ def post(): # asyncio.ensure_future(); return {'status': result.success, 'message':result.message, 'id':result.identifier}, 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -98,7 +99,7 @@ def put(divisionid): # asyncio.ensure_future(); return {'status': result.success, 'message':result.message, 'id':result.identifier}, 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -119,7 +120,7 @@ def put(divisionid): # asyncio.ensure_future(); return {'status': result.success, 'message':result.message, 'id':result.identifier}, 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foicfrfee.py b/request-management-api/request_api/resources/foicfrfee.py index c8e36e801..44877f076 100644 --- a/request-management-api/request_api/resources/foicfrfee.py +++ b/request-management-api/request_api/resources/foicfrfee.py @@ -36,6 +36,7 @@ """Custom exception messages """ EXCEPTION_MESSAGE_BAD_REQUEST='Bad Request' +CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('POST,OPTIONS') @API.route('/foicfrfee/foirequest//ministryrequest/') @@ -58,9 +59,9 @@ def post(requestid, ministryrequestid): except ValidationError as verr: logging.error(verr) return {'status': False, 'message': str(verr)}, 400 - except KeyError as err: - logging.error(str(type(err).__name__)) - return {'status': False, 'message': EXCEPTION_MESSAGE_BAD_REQUEST}, 400 + except KeyError as error: + logging.error(CUSTOM_KEYERROR_MESSAGE + str(error)) + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -88,9 +89,9 @@ def post(requestid, ministryrequestid): except ValidationError as verr: logging.error(verr) return {'status': False, 'message': str(verr)}, 400 - except KeyError as err: - logging.error(str(type(err).__name__)) - return {'status': False, 'message': EXCEPTION_MESSAGE_BAD_REQUEST}, 400 + except KeyError as error: + logging.error(CUSTOM_KEYERROR_MESSAGE + str(error)) + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -110,6 +111,6 @@ def get(requestid): result = {"current": cfrfeeservice().getcfrfee(requestid), "history": cfrfeeservice().getcfrfeehistory(requestid)} return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foicomment.py b/request-management-api/request_api/resources/foicomment.py index 31ee32336..4f88917ba 100644 --- a/request-management-api/request_api/resources/foicomment.py +++ b/request-management-api/request_api/resources/foicomment.py @@ -35,6 +35,7 @@ """Custom exception messages """ EXCEPTION_MESSAGE_BAD_REQUEST='Bad Request' +CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('POST,OPTIONS') @API.route('/foicomment/ministryrequest') @@ -55,7 +56,7 @@ def post(): asyncio.ensure_future(eventservice().postcommentevent(result.identifier, "ministryrequest", AuthHelper.getuserid())) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -78,7 +79,7 @@ def post(): asyncio.ensure_future(eventservice().postcommentevent(result.identifier, "rawrequest", AuthHelper.getuserid())) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -109,7 +110,7 @@ def get(requesttype, requestid): else: return {'status': 401, 'message':'Restricted Request'} , 401 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -137,7 +138,7 @@ def put(requesttype, commentid): asyncio.ensure_future(eventservice().postcommentevent(result.identifier, requesttype, AuthHelper.getuserid(), True)) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -167,7 +168,7 @@ def put(requesttype, commentid): asyncio.ensure_future(eventservice().postcommentevent(commentid, requesttype, AuthHelper.getuserid(), existingtaggedusers=result.args[0])) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foidocument.py b/request-management-api/request_api/resources/foidocument.py index decf80ddb..b661c394f 100644 --- a/request-management-api/request_api/resources/foidocument.py +++ b/request-management-api/request_api/resources/foidocument.py @@ -30,7 +30,8 @@ API = Namespace('FOIDocument', description='Endpoints for FOI Document management') -TRACER = Tracer.get_instance() +TRACER = Tracer.get_instance() +CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('GET,OPTIONS') @@ -50,7 +51,7 @@ def get(requestid, requesttype): result = documentservice().getrequestdocumentsbyrole(requestid, requesttype, AuthHelper.isministrymember()) return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -75,7 +76,7 @@ def post(requestid, requesttype): except ValidationError as err: return {'status': False, 'message': str(err)}, 400 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -99,7 +100,7 @@ def post(requestid, documentid, requesttype): except ValidationError as err: return {'status': False, 'message': str(err)}, 400 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -137,7 +138,7 @@ def post(requesttype, requestid, documentid): except ValidationError as err: return {'status': False, 'message': str(err)}, 400 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -160,7 +161,7 @@ def post(requestid, documentid, requesttype): except ValidationError as err: return {'status': False, 'message': str(err)}, 400 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -180,6 +181,6 @@ def post(requestid, documentid, requesttype): result = documentservice().deleterequestdocument(requestid, documentid, AuthHelper.getuserid(), requesttype) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foiemail.py b/request-management-api/request_api/resources/foiemail.py index 143b280b1..c175cc6f3 100644 --- a/request-management-api/request_api/resources/foiemail.py +++ b/request-management-api/request_api/resources/foiemail.py @@ -32,6 +32,7 @@ API = Namespace('FOIEmail', description='Endpoints for FOI EMAIL management') TRACER = Tracer.get_instance() +CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('POST,OPTIONS') @API.route('/foiemail//ministryrequest//') @@ -52,7 +53,7 @@ def post(requestid, ministryrequestid, servicename): except ValueError as err: return {'status': 500, 'message': str(err)}, 500 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -73,7 +74,7 @@ def post(requestid, ministryrequestid, servicename): except ValueError as err: return {'status': 500, 'message': str(err)}, 500 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foiextension.py b/request-management-api/request_api/resources/foiextension.py index b985cc477..ddb8280ac 100644 --- a/request-management-api/request_api/resources/foiextension.py +++ b/request-management-api/request_api/resources/foiextension.py @@ -37,6 +37,7 @@ """Custom exception messages """ EXCEPTION_MESSAGE_BAD_REQUEST='Bad Request' +CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('GET,OPTIONS') @API.route('/foiextension/ministryrequest/') @@ -53,7 +54,7 @@ def get(requestid): extensionrecords = extensionservice().getrequestextensions(requestid) return json.dumps(extensionrecords), 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -72,7 +73,7 @@ def get(extensionid): extensionrecord = extensionservice().getrequestextension(extensionid) return json.dumps(extensionrecord), 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -97,7 +98,7 @@ def post(requestid, ministryrequestid): newduedate, = result.args return {'status': result.success, 'message':result.message,'id':result.identifier, 'newduedate': newduedate or None} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -122,7 +123,7 @@ def post(ministryrequestid): eventservice().posteventforaxisextension(ministryrequestid, result.args[0], AuthHelper.getuserid(), AuthHelper.getusername(), "add") return {'status': result.success, 'message':result.message} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -146,7 +147,7 @@ def post(requestid, ministryrequestid, extensionid): newduedate = result.args[-1] if len(result.args) > 0 else None return {'status': result.success, 'message':result.message,'id':result.identifier, 'newduedate': newduedate or None} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -168,6 +169,6 @@ def post(requestid, ministryrequestid, extensionid): newduedate = result.args[-1] if len(result.args) > 0 else None return {'status': result.success, 'message':result.message,'id':result.identifier, 'newduedate': newduedate or None} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foinotification.py b/request-management-api/request_api/resources/foinotification.py index 4967f31ab..f5a3bf637 100644 --- a/request-management-api/request_api/resources/foinotification.py +++ b/request-management-api/request_api/resources/foinotification.py @@ -30,6 +30,7 @@ API = Namespace('FOINotification', description='Endpoints for FOI notification management') TRACER = Tracer.get_instance() +CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('GET,OPTIONS') @API.route('/foinotifications') @@ -48,7 +49,7 @@ def get(): except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -73,7 +74,7 @@ def delete(type=None,idnumber=None,notficationid=None): except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -93,7 +94,7 @@ def post(): respcode = 200 if reminderresponse.success == True else 500 return {'status': reminderresponse.success, 'message':reminderresponse.message,'id': reminderresponse.identifier} , respcode except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -114,6 +115,6 @@ def post(request_id: int, ministry_request_id: int): respcode = 200 if reminderresponse.success == True else 500 return {'status': reminderresponse.success, 'message':reminderresponse.message,'id': reminderresponse.identifier} , respcode except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foipayment.py b/request-management-api/request_api/resources/foipayment.py index b637d0772..b2670c6ff 100644 --- a/request-management-api/request_api/resources/foipayment.py +++ b/request-management-api/request_api/resources/foipayment.py @@ -31,6 +31,7 @@ API = Namespace('FOIPayment', description='Endpoints for FOI Payment management') TRACER = Tracer.get_instance() +CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('POST,OPTIONS') @API.route('/foipayment//ministryrequest/') @@ -49,7 +50,7 @@ def post(requestid, ministryrequestid): result = paymentservice().createpayment(requestid, ministryrequestid, paymentschema) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -70,7 +71,7 @@ def post(requestid, ministryrequestid): result = paymentservice().cancelpayment(requestid, ministryrequestid, paymentschema) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -89,7 +90,7 @@ def get(requestid, ministryrequestid): result = paymentservice().getpayment(requestid, ministryrequestid) return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foirecord.py b/request-management-api/request_api/resources/foirecord.py index e90e9ae34..71fa93b55 100644 --- a/request-management-api/request_api/resources/foirecord.py +++ b/request-management-api/request_api/resources/foirecord.py @@ -30,6 +30,7 @@ API = Namespace('FOIWatcher', description='Endpoints for FOI record management') TRACER = Tracer.get_instance() +CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('GET,OPTIONS') @API.route('/foirecord//ministryrequest/') @@ -47,7 +48,7 @@ def get(requestid, ministryrequestid): result = recordservice().fetch(requestid, ministryrequestid) return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except Exception as exception: return {'status': False, 'message': str(exception)}, 500 @@ -70,7 +71,7 @@ def post(requestid, ministryrequestid): respcode = 200 if response.success == True else 500 return {'status': response.success, 'message':response.message,'data': response.args[0]} , respcode except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -92,7 +93,7 @@ def post(requestid, ministryrequestid): result = recordservice().update(requestid, ministryrequestid, data, AuthHelper.getuserid()) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -113,7 +114,7 @@ def post(requestid, ministryrequestid): result = recordservice().retry(requestid, ministryrequestid, recordschema) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -135,7 +136,7 @@ def post(requestid, ministryrequestid,recordid): return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -158,7 +159,7 @@ def post(requestid, ministryrequestid, recordstype): respcode = 200 if response.success == True else 500 return {'status': response.success, 'message':response.message,'id':response.identifier}, respcode except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -178,7 +179,7 @@ def get(requestid, ministryrequestid, recordstype): result = recordservice().getpdfstitchpackagetodownload(ministryrequestid, recordstype.lower()) return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -199,8 +200,8 @@ def get(requestid, ministryrequestid, recordstype): #("getpdfstichstatus result == ", result) return result, 200 except KeyError as error: - print(str(type(error).__name__)) - return {'status': False, 'message': str(type(error).__name__)}, 400 + print(CUSTOM_KEYERROR_MESSAGE + str(error)) + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: print("BusinessException == ", exception.message) return {'status': exception.status_code, 'message':exception.message}, 500 @@ -225,8 +226,8 @@ def get(requestid, ministryrequestid, recordstype): #print("records changed == ", result) return result, 200 except KeyError as error: - print(str(type(error).__name__)) - return {'status': False, 'message': str(type(error).__name__)}, 400 + print(CUSTOM_KEYERROR_MESSAGE + str(error)) + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: print("BusinessException == ", exception.message) return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foirequest.py b/request-management-api/request_api/resources/foirequest.py index 037e89a55..8d33fd0f6 100644 --- a/request-management-api/request_api/resources/foirequest.py +++ b/request-management-api/request_api/resources/foirequest.py @@ -36,6 +36,7 @@ API = Namespace('FOIRequests', description='Endpoints for FOI request management') TRACER = Tracer.get_instance() EXCEPTION_MESSAGE_NOTFOUND_REQUEST='Record not found' +CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('GET,OPTIONS') @@ -73,7 +74,7 @@ def get(foirequestid,foiministryrequestid,usertype = None): except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -119,7 +120,7 @@ def post(): except ValidationError as err: return {'status': False, 'message': str(err)}, 400 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -148,7 +149,7 @@ def post(foirequestid,foiministryrequestid): except ValidationError as err: return {'status': False, 'message': str(err)}, 400 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -188,7 +189,7 @@ def post(foirequestid,foiministryrequestid,actiontype = None,usertype = None): except ValidationError as err: return {'status': False, 'message': str(err)}, 400 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -247,7 +248,7 @@ def get(foirequestid, foiministryrequestid): except ValueError: return {'status': 500, 'message':"Invalid Request Id"}, 500 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/resources/foiwatcher.py b/request-management-api/request_api/resources/foiwatcher.py index 065ba1154..bde512c42 100644 --- a/request-management-api/request_api/resources/foiwatcher.py +++ b/request-management-api/request_api/resources/foiwatcher.py @@ -32,6 +32,7 @@ API = Namespace('FOIWatcher', description='Endpoints for FOI watcher management') TRACER = Tracer.get_instance() +CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('GET,OPTIONS') @API.route('/foiwatcher/rawrequest/') @@ -50,7 +51,7 @@ def get(requestid): except ValueError as err: return {'status': 500, 'message': str(err)}, 500 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -73,7 +74,7 @@ def post(): eventservice().posteventforwatcher(requestjson["requestid"], requestjson, "rawrequest",AuthHelper.getuserid(), AuthHelper.getusername()) return {'status': result.success, 'message':result.message} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -92,7 +93,7 @@ def put(requestid): result = watcherservice().disablerawrequestwatchers(requestid, AuthHelper.getuserid()) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -111,7 +112,7 @@ def get(ministryrequestid): result = watcherservice().getministryrequestwatchers(ministryrequestid,AuthHelper.isministrymember()) return json.dumps(result), 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -134,7 +135,7 @@ def post(): eventservice().posteventforwatcher(requestjson["ministryrequestid"], requestjson, "ministryrequest", AuthHelper.getuserid(), AuthHelper.getusername()) return {'status': result.success, 'message':result.message} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -153,6 +154,6 @@ def put(ministryrequestid): result = watcherservice().disableministryrequestwatchers(ministryrequestid, AuthHelper.getuserid()) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/request-management-api/request_api/resources/foiworkflow.py b/request-management-api/request_api/resources/foiworkflow.py index e22631ce1..a58503a73 100644 --- a/request-management-api/request_api/resources/foiworkflow.py +++ b/request-management-api/request_api/resources/foiworkflow.py @@ -29,6 +29,7 @@ API = Namespace('FOIWorkflow', description='Endpoints for FOI workflow management') TRACER = Tracer.get_instance() +CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('POST,OPTIONS') @API.route('/foiworkflow///sync') @@ -48,7 +49,7 @@ def post(requesttype, requestid): except ValueError as err: return {'status': 500, 'message': str(err)}, 500 except KeyError as error: - return {'status': False, 'message': str(type(error).__name__)}, 400 + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 diff --git a/request-management-api/request_api/services/rawrequestservice.py b/request-management-api/request_api/services/rawrequestservice.py index c72d3ccf1..6abaf1ff1 100644 --- a/request-management-api/request_api/services/rawrequestservice.py +++ b/request-management-api/request_api/services/rawrequestservice.py @@ -68,7 +68,7 @@ def saverawrequest(self, requestdatajson, sourceofsubmission, userid,notes): try: workflowservice().createinstance(redispubservice.foirequestqueueredischannel, json_data) except Exception as ex: - logging.error("Unable to create instance") + logging.error(ex) asyncio.ensure_future(redispubservice.publishrequest(json_data)) return result diff --git a/request-management-api/request_api/services/workflowservice.py b/request-management-api/request_api/services/workflowservice.py index e3faaf143..f4b6f81a0 100644 --- a/request-management-api/request_api/services/workflowservice.py +++ b/request-management-api/request_api/services/workflowservice.py @@ -26,7 +26,7 @@ class workflowservice: def createinstance(self, definitionkey, message): response = bpmservice().createinstance(definitionkey, json.loads(message)) if response is None: - raise BusinessException(Error.INVALID_INPUT) + raise Exception("Unable to create instance for key"+ definitionkey) return response def postunopenedevent(self, id, wfinstanceid, requestsschema, status, ministries=None): diff --git a/request-management-api/request_api/tracer.py b/request-management-api/request_api/tracer.py index 80815410b..e88efcee9 100644 --- a/request-management-api/request_api/tracer.py +++ b/request-management-api/request_api/tracer.py @@ -34,7 +34,7 @@ def get_instance(): def __init__(self): """Virtually private constructor.""" if Tracer.__instance is not None: - raise BusinessException('Attempt made to create multiple tracing instances') + raise Exception('Attempt made to create multiple tracing instances') api_tracer = ApiTracer() Tracer.__instance = ApiTracing(api_tracer.tracer)