diff --git a/.github/workflows/katalon-ci.yaml b/.github/workflows/katalon-ci.yaml index 15879c74e..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@v2 + 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@v2 + 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@v2 + 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@v2 + uses: actions/upload-artifact@v3.0 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 }}' diff --git a/.gitignore b/.gitignore index cc7aa8897..8860508ee 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,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__/* @@ -92,3 +93,22 @@ 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 + +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 diff --git a/README.md b/README.md index e83caf170..908fe8af6 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,59 @@ Freedom of Information modernization. ## Installation +### For mac +#### Run the microservices in docker +1. Clone this 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 +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 + +#### 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. 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 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/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..6e06785c4 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.sln @@ -0,0 +1,79 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33213.308 +MinimumVisualStudioVersion = 10.0.40219.1 +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("{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("{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("{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("{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("{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("{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("{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 +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 + 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 + {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 + {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 + 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..fa9ad3da9 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration.csproj @@ -0,0 +1,29 @@ + + + + Exe + 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 new file mode 100644 index 000000000..7bd47164a --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/Program.cs @@ -0,0 +1,57 @@ +// 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.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; +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; + +SystemSettings.FOIFLOWConnectionString = configurationbuilder.GetSection("FOIFLOWConfiguration:FOIFLOWConnectionString").Value; + + + +SqlConnection axissqlConnection = new SqlConnection(SystemSettings.AXISConnectionString); +OdbcConnection odbcConnection = new OdbcConnection(SystemSettings.FOIFLOWConnectionString); + +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, 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 new file mode 100644 index 000000000..2e9102a54 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.ConsoleApp.DocMigration/appsettings.json @@ -0,0 +1,23 @@ +{ + "S3Configuration": { + "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_Bucket": "dev-forms-foirequests-e", + "AttachmentTag": "applicant" + + }, + "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.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..d6d289528 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/AXISDALTest.cs @@ -0,0 +1,50 @@ +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 = 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(requeststomigrate); + Assert.IsNotNull(correspondencelogs); + + } + + + [TestMethod] + public void GetRecordsTest() + { + + DocumentsDAL documentsDAL = new DocumentsDAL(conn); + var correspondencelogs = documentsDAL.GetRecordsByRequest("NGD-2015-50137"); + 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..a8136f018 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests/FOIMOD.CFD.DocMigration.AXIS.DAL.UnitTests.csproj @@ -0,0 +1,32 @@ + + + + net7.0 + enable + enable + + false + + + + + + + + + + + + + + + + + + + + Always + + + + 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.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/CorrespondenceLogMigration.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/CorrespondenceLogMigration.cs new file mode 100644 index 000000000..4bfc869ec --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/CorrespondenceLogMigration.cs @@ -0,0 +1,130 @@ +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 +{ + 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); + AttachmentsDAL attachmentsDAL = new AttachmentsDAL((OdbcConnection)destinationfoiflowQLConnection); + DocMigrationPDFStitcher docMigrationPDFStitcher = new DocMigrationPDFStitcher(); + List? correspondencelogs = documentsDAL.GetCorrespondenceLogDocuments(this.RequestsToMigrate); + foreach (DocumentToMigrate attachment in correspondencelogs) + { + if (string.IsNullOrEmpty(attachment.EmailTo) && string.IsNullOrEmpty(attachment.EmailContent)) + { + var files = FilePathUtils.GetFileDetailsFromdelimitedstring(attachment.EmailAttachmentDelimitedString); + 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"; + + + 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); + + } + + } + + } + + } + else + { + //Create EMAIL MSG PDF and sticth with the attachment documents + List documentToMigrateEmail = new List(); + 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 }); + + + 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); + + } + } + + } + } + + } + + } +} \ 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..05fc1d8ec --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.BAL/FOIMOD.CFD.DocMigration.BAL.csproj @@ -0,0 +1,20 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + + + + 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..71ab5e1e5 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/DocumentsDAL.cs @@ -0,0 +1,181 @@ +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 + { + + private SqlConnection sqlConnection; + public DocumentsDAL(SqlConnection _sqlConnection) + { + sqlConnection = _sqlConnection; + } + + private string correspondencelog = @"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))"; + + + 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) { + + case DocumentTypeFromAXIS.CorrespondenceLog: + query =string.Format(correspondencelog,requestnumber); + break; + case DocumentTypeFromAXIS.RequestRecords: + query = string.Format(recordsbyrequestid, requestnumber); + break; + default: + break; + + + } + + return query; + } + + public List? GetCorrespondenceLogDocuments(string cs_requestnumbers) + { + List documentToMigrates = null; + using (SqlDataAdapter sqlSelectCommand = new(getQueryByType(DocumentTypeFromAXIS.CorrespondenceLog, cs_requestnumbers), 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) + { + + throw ex; + } + catch (Exception e) + { + throw; + } + finally + { + sqlConnection.Close(); + } + + } + 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.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..943ae8c7a --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.DAL/FOIMOD.CFD.DocMigration.AXIS.DAL.csproj @@ -0,0 +1,17 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + 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..46e71506c --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/AttachmentsDAL.cs @@ -0,0 +1,43 @@ +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; + throw; + } + return result; + } + + + + } +} \ 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..b01aaa0fc --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.FOIFLOW.DAL/FOIMOD.CFD.DocMigration.FOIFLOW.DAL.csproj @@ -0,0 +1,13 @@ + + + + net7.0 + enable + enable + + + + + + + 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/AXISSource/AXISDocument.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/AXISSource/AXISDocument.cs new file mode 100644 index 000000000..4fd236ede --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/AXISSource/AXISDocument.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.AXISSource +{ + public class AXISDocument + { + public string? AXISRequestNumber { get; set; } + 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? EmailFrom { 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..66c77e564 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/Document/DocumentToMigrate.cs @@ -0,0 +1,18 @@ +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; } + + public string? EmailAttachmentDelimitedString { get; set; } + + } +} 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..61a68c177 --- /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 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..cfadb03dd --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/FOIMOD.CFD.DocMigration.Models.csproj @@ -0,0 +1,9 @@ + + + + net7.0 + enable + 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..5bca03986 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Models/SystemSettings.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 +{ + 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 FOIFLOWConnectionString { get; set; } + + public static string RequestToMigrate { get; set; } + + public static string FileServerRoot { get; set; } + + public static string CorrespondenceLogBaseFolder { get; set; } + + 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.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..26683b729 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/FOIMOD.CFD.DocMigration.Utils.UnitTests.csproj @@ -0,0 +1,35 @@ + + + + net7.0 + enable + enable + + false + + + + + + + + + + + + + + + + + + + + + + + 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 new file mode 100644 index 000000000..6fa1682cb --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/S3UploadTest.cs @@ -0,0 +1,147 @@ + + +using Amazon.Runtime; +using Amazon.S3; +using FOIMOD.CFD.DocMigration.Models; +using FOIMOD.CFD.DocMigration.Models.Document; +using Microsoft.Extensions.Configuration; + +namespace FOIMOD.CFD.DocMigration.Utils.UnitTests +{ + [TestClass] + public class S3UploadTest + { + 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 + + }; + } + + + [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)) + { + 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 }; + var result = docMigrationS3Client.UploadFileAsync(uploadFile).Result; + Assert.IsNotNull(result); + Assert.IsTrue(result.IsSuccessStatusCode); + } + } + + [TestMethod] + public void PDFMerge_UploadTest() + { + 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()) + { + 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() { 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 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() { 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; + + + 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); + + using var client = new HttpClient(); + + 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 }; + 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\\"; + } + + + } +} \ 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/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": "" + + } + + +} 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 000000000..31d2528d7 Binary files /dev/null and b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/samples/DOCX1.pdf differ 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 000000000..1b329ca52 Binary files /dev/null and b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/samples/cat1.pdf differ 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..8ab03f55f --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/DocMigrationPDFStitcher.cs @@ -0,0 +1,114 @@ +using FOIMOD.CFD.DocMigration.Models; +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 + { + + private Stream mergeddocstream = null; + private Stream createemailpdfmemorystream = null; + public DocMigrationPDFStitcher() + { + + } + + public void Dispose() + { + + mergeddocstream.Close(); + mergeddocstream.Dispose(); + } + + public Stream MergePDFs(List pdfpages) + { + var _pdfpages = pdfpages.OrderBy(p => p.PageSequenceNumber).ToArray(); + using (PdfDocument pdfdocument = new PdfDocument()) + { + foreach (DocumentToMigrate pDFDocToMerge in _pdfpages) + { + 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) + { + pdfdocument.AddPage(page); + } + } + mergeddocstream = new MemoryStream(); + pdfdocument.Save(mergeddocstream); + } + return mergeddocstream; + } + + + + + 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(@" + + + + + + + + + + + + + + + + + + + + + + +
Emailed To{0}
Date{1}
Subject{2}
Attachments + {3} +
Message{4}
", emailTo, emaildate, emailsubject, attachmentlist,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/DocMigrationS3Client.cs b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/DocMigrationS3Client.cs new file mode 100644 index 000000000..8438d47ce --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/DocMigrationS3Client.cs @@ -0,0 +1,43 @@ +using Amazon; +using Amazon.S3; +using Amazon.S3.Model; +using FOIMOD.CFD.DocMigration.Models.FOIFLOWDestination; + +namespace FOIMOD.CFD.DocMigration.Utils +{ + public class DocMigrationS3Client + { + private readonly IAmazonS3 _s3Client; + + public DocMigrationS3Client(IAmazonS3 s3Client) + { + _s3Client = s3Client; + } + + private string GetPresignedURL(IAmazonS3 s3, string fileName, HttpVerb method) + { + AWSConfigsS3.UseSignatureVersion4 = true; + GetPreSignedUrlRequest request = new() + { + Key = fileName, + Verb = method, + Expires = DateTime.Now.AddHours(1), + Protocol = Protocol.HTTPS, + }; + return s3.GetPreSignedURL(request); + } + + public async Task 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..c41595220 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/FOIMOD.CFD.DocMigration.Utils.csproj @@ -0,0 +1,19 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + + + 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..9ed84c456 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils/FillePathUtils.cs @@ -0,0 +1,42 @@ +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('|'); + result = new List(); + if (files != null && files.Length > 0) + { + + 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] }); + } + + } + } + 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] }); + } + } + + } + + return result; + + } + } +} 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/ApplicantRequestMappings.dtsx b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/ApplicantRequestMappings.dtsx new file mode 100644 index 000000000..9f0a03d27 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/ApplicantRequestMappings.dtsx @@ -0,0 +1,648 @@ + + + 8 + + + + + + + + + + + + + + + + + + REPLACE( @[$Project::foirequestsquery] , "@requestids", @[$Project::requestidstomigrate] ) + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + \ No newline at end of file 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/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/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.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..b1b17101b --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj @@ -0,0 +1,450 @@ + + + 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+sBAAAAAxNy8IYXQUikuyRHfwqVBQAAAAACAAAAAAADZgAAwAAAABAAAAA0WsvjZzR76ItQUOsvjFPxAAAAAASAAACgAAAAEAAAAB6WqM0e3bF/ytcNb+00Vj+IAAAAbsMnsLvWvIvySgXvXtMoEl3R8G67DLCHIBL3kR/GH2MUqZpdgHf+GA623nwneseHO7/k1WEwNtQKoj3RJSAzVR2kKCBf/WuRo7OE7YHbUgHk4tAJ5+pg9H5AVaFycQMdmyhXf3aH5TbvKirS4dBijjsR1DVko71vw8v5SeKsbxBGdvk7myw8hBQAAABx9sgy9GQ3TLvYi2NH3xvLCs5wfg== + 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 + + + + + + + {6B1FFF75-7F3F-438B-9C8E-FB5B50CBB326} + FOIRequests + 1 + 0 + 36 + + + {2215EF75-9178-4019-A65C-548BCB848EB3} + 8 + + + 1 + + + + + + {2A065F0D-C74A-4625-A968-B48E416F3285} + Package1 + 1 + 0 + 49 + + + {959785B0-CF2F-46BD-B18F-F60A275D3B69} + 8 + + + 1 + + + + + + {6E7CF625-11EC-4619-ACB0-07C38F8367D2} + ApplicantRequestMappings + 1 + 0 + 64 + + + {E74162C7-CB89-4371-98DA-C1E08F361BDA} + 8 + + + 1 + + + + + + {F6BAB303-5655-462B-B60C-AC54FEE248D6} + FOIRequestContactInformation + 1 + 0 + 71 + + + {D8F0E47C-87C0-4352-B380-D36429864036} + 8 + + + 1 + + + + + + {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 + 15 + + + {FECD18DF-8B81-43E0-812E-6647F05F22D6} + 8 + + + 1 + + + + + + + + + + + + + Development + + bin + + + + + SQLServer2019 + false + + + + + + + + LastModifiedTime + LastModifiedTime + 2023-07-28T21:54:01.1505194Z + + + + + + \ 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..fb5695dc9 --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration.dtproj.user @@ -0,0 +1,26 @@ + + + + + Development + + + false + + + false + + + false + true + + + LastModifiedTime + LastModifiedTime + 2023-07-28T21:54:01.1525191Z + + + + + + \ No newline at end of file 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 new file mode 100644 index 000000000..6c371fb3b --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIMinistryRequests.dtsx @@ -0,0 +1,576 @@ + + + 8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + REPLACE(@[$Project::foiministryrequestquery], "@requestids", @[$Project::requestidstomigrate] ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + \ No newline at end of file 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; + } + } + } +}]]> + TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAgAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1v +ZGUuDQ0KJAAAAAAAAABQRQAATAEDACh3ymQAAAAAAAAAAOAAIiALATAAABgAAAAIAAAAAAAACjYA +AAAgAAAAQAAAAAAAEAAgAAAAAgAABAAAAAAAAAAGAAAAAAAAAACAAAAAAgAAAAAAAAMAYIUAABAA +ABAAAAAAEAAAEAAAAAAAABAAAAAAAAAAAAAAALg1AABPAAAAAEAAAHgEAAAAAAAAAAAAAAAAAAAA +AAAAAGAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAIAAACAAAAAAAAAAAAAAACCAAAEgAAAAAAAAAAAAAAC50ZXh0AAAAEBYAAAAgAAAAGAAAAAIA +AAAAAAAAAAAAAAAAACAAAGAucnNyYwAAAHgEAAAAQAAAAAYAAAAaAAAAAAAAAAAAAAAAAABAAABA +LnJlbG9jAAAMAAAAAGAAAAACAAAAIAAAAAAAAAAAAAAAAAAAQAAAQgAAAAAAAAAAAAAAAAAAAADs +NQAAAAAAAEgAAAACAAUAICUAAOAPAAABAAAAAAAAAAA1AAC4AAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAABMwAgAkBAAAAQAAEQIoEAAACm8RAAAKcgEAAHBvEgAACm8T +AAAKbxQAAAoKBigKAAAGDAggAS2ifDVJCCC1tWFGNRsIIBODox07UwEAAAggtbVhRjsJAQAAONsC +AAAIICNmg1A7IwEAAAggcsoGVzuFAAAACCABLaJ8O48AAAA4tQIAAAggWDP6rzUjCCDM0yWNO7YA +AAAIIHJJbZg7gQAAAAggWDP6ry46OIoCAAAIIAzRx+s7vQAAAAggx/uw9y4NCCCs6Xb6Lm44agIA +AAZyGwAAcCgVAAAKOtcAAAA4VQIAAAZyTQAAcCgVAAAKOuYAAAA4QAIAAAZydQAAcCgVAAAKOvUA +AAA4KwIAAAZyvQAAcCgVAAAKOgQBAAA4FgIAAAZyFQEAcCgVAAAKOhMBAAA4AQIAAAZybQEAcCgV +AAAKOiIBAAA47AEAAAZy5wEAcCgVAAAKOjEBAAA41wEAAAZyFQIAcCgVAAAKOkABAAA4wgEAAAZy +OQIAcCgVAAAKOk8BAAA4rQEAAAZyjQIAcCgVAAAKOlsBAAA4mAEAAAZy4QIAcCgVAAAKOmcBAAA4 +gwEAAAIoEAAACm8RAAAKcgEAAHBvEgAACnJXAwBwbxYAAAo4fgEAAAIoEAAACm8RAAAKcgEAAHBv +EgAACnJbAwBwbxYAAAo4WgEAAAIoEAAACm8RAAAKcgEAAHBvEgAACnJfAwBwbxYAAAo4NgEAAAIo +EAAACm8RAAAKcgEAAHBvEgAACnJlAwBwbxYAAAo4EgEAAAIoEAAACm8RAAAKcgEAAHBvEgAACnJp +AwBwbxYAAAo47gAAAAIoEAAACm8RAAAKcgEAAHBvEgAACnJtAwBwbxYAAAo4ygAAAAIoEAAACm8R +AAAKcgEAAHBvEgAACnJzAwBwbxYAAAo4pgAAAAIoEAAACm8RAAAKcgEAAHBvEgAACnJ3AwBwbxYA +AAo4ggAAAAIoEAAACm8RAAAKcgEAAHBvEgAACnJ7AwBwbxYAAAorYQIoEAAACm8RAAAKcgEAAHBv +EgAACnJ/AwBwbxYAAAorQAIoEAAACm8RAAAKcgEAAHBvEgAACnKDAwBwbxYAAAorHwIoEAAACm8R +AAAKcgEAAHBvEgAACnKHAwBwbxYAAAoCKBAAAApvEQAACnKLAwBwbxIAAApvEwAACm8UAAAKCwdy +pQMAcCgVAAAKLRwHcqkDAHAoFQAACi0wB3KtAwBwKBUAAAotRCtjAigQAAAKbxEAAApyiwMAcG8S +AAAKcnMDAHBvFgAACithAigQAAAKbxEAAApyiwMAcG8SAAAKcncDAHBvFgAACitAAigQAAAKbxEA +AApyiwMAcG8SAAAKcnsDAHBvFgAACisfAigQAAAKbxEAAApyiwMAcG8SAAAKcocDAHBvFgAACgIo +EAAAChZvFwAACioeAigYAAAKKh4CKBkAAAoqrn4BAAAELR5ysQMAcNADAAACKBoAAApvGwAACnMc +AAAKgAEAAAR+AQAABCoafgIAAAQqHgKAAgAABCoafgMAAAQqHgIoHQAACioucwgAAAaAAwAABCoA +ABMwAgAsAAAAAgAAEQIsJyDFnRyBChYLKxQCB28eAAAKBmEgkwEAAVoKBxdYCwcCbx8AAAoy4wYq +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 new file mode 100644 index 000000000..cbd3779dd --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/FOIRequests.dtsx @@ -0,0 +1,368 @@ + + + 8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + REPLACE( @[$Project::foirequestsquery] ,"@requestids", @[$Project::requestidstomigrate] ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + \ 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..4d9e8eddd --- /dev/null +++ b/datamigrations/FOIMOD.CFD.ETL.DataMigration/FOIMOD.CFD.ETL.DataMigration/Project.params @@ -0,0 +1,150 @@ + + + + + {ed0ced49-9631-4b81-8877-2fbb3b5a9c50} + + + 0 + 0 + 0 + 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 + + + + + {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 (@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 + + + + + {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 , 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 + + + \ 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 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 diff --git a/forms-flow-web/public/index.html b/forms-flow-web/public/index.html index 4a5e866ee..b5c3fe1e9 100644 --- a/forms-flow-web/public/index.html +++ b/forms-flow-web/public/index.html @@ -1,27 +1,6 @@ - (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 42eba95a5..111ee62ec 100644 --- a/forms-flow-web/src/apiManager/endpoints/index.js +++ b/forms-flow-web/src/apiManager/endpoints/index.js @@ -29,6 +29,9 @@ 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_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`, @@ -56,6 +59,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`, 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) { diff --git a/forms-flow-web/src/apiManager/services/FOI/foiMasterDataServices.js b/forms-flow-web/src/apiManager/services/FOI/foiMasterDataServices.js index 7f602ceaa..b8eb8d385 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,81 @@ 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 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/Admin/Divisions/CreateDivisionModal.jsx b/forms-flow-web/src/components/FOI/Admin/Divisions/CreateDivisionModal.jsx index 5106c0978..1bf8b2dd1 100644 --- a/forms-flow-web/src/components/FOI/Admin/Divisions/CreateDivisionModal.jsx +++ b/forms-flow-web/src/components/FOI/Admin/Divisions/CreateDivisionModal.jsx @@ -8,6 +8,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 CreateDivisionModal = ({ @@ -16,7 +18,13 @@ const CreateDivisionModal = ({ showModal, closeModal, }) => { - const [division, setDivision] = useState(null); + const [division, setDivision] = useState({ + name: "", + programareaid: null, + issection: false, + parentid: null, + specifictopersonalrequests: false, + }); let programAreas = useSelector((state) => state.foiRequests.foiAdminProgramAreaList); const handleSave = () => { @@ -29,7 +37,13 @@ const CreateDivisionModal = ({ }; useEffect(() => { - setDivision(null); + setDivision({ + name: "", + programareaid: null, + issection: false, + parentid: null, + specifictopersonalrequests: false, + }); }, [showModal]); return ( @@ -39,6 +53,7 @@ const CreateDivisionModal = ({ - + Program Area +
+ setDivision({...division, specifictopersonalrequests: !division.specifictopersonalrequests})} />} label="Specific to Personal Request" /> +
+ + + + + ); +}; + +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 f17670026..bb4052e2c 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) { @@ -96,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) => { @@ -144,10 +152,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 associated to any FOI Request records or sections.", { position: "top-right", - autoClose: 3000, + autoClose: 4500, hideProgressBar: true, closeOnClick: true, pauseOnHover: true, @@ -164,6 +172,10 @@ const Divisions = ({userDetail}) => { setShowCreateModal(true); }; + const openCreateSectionModal = () => { + setShowCreateSectionModal(true) + } + const openDisableDivisionModal = (data) => { setSelectedDivision(data); setShowDisableModal(true); @@ -174,6 +186,20 @@ const Divisions = ({userDetail}) => { setShowEditModal(true); }; + const filterParentDivisions = (divisionObj, divisionsArr, operation) => { + let filteredDivisions = divisionsArr.filter(division => !division.issection); + if(operation === "EDIT") { + filteredDivisions = filteredDivisions.filter(division => division.divisionid !== divisionObj.divisionid); + } + if (divisionObj.programareaid && divisionObj.specifictopersonalrequests) { + return filteredDivisions.filter(division => division.programareaid === divisionObj.programareaid && division.specifictopersonalrequests); + } + if (divisionObj.programareaid && !divisionObj.specifictopersonalrequests) { + return filteredDivisions.filter(division => division.programareaid === divisionObj.programareaid && !division.specifictopersonalrequests); + } + return filteredDivisions; + } + const columns = [ { field: "divisionid", @@ -195,6 +221,24 @@ const Divisions = ({userDetail}) => { headerName: "Code", width: 75, }, + { + field: "issection", + headerName: "Section?", + width: 100, + }, + { + field: "parentid", + headerName: "Parent Division", + width: 150, + align: "center", + renderCell: (params) => <>{params.value ? params.value : "-"}, + }, + { + field: "specifictopersonalrequests", + headerName: "Personal Requests", + width: 150, + align: "center", + }, { field: "sortorder", headerName: "Sort Order", @@ -259,15 +303,23 @@ const Divisions = ({userDetail}) => { setSearchResults={setSearchResults} /> - + + @@ -303,6 +355,13 @@ const Divisions = ({userDetail}) => { showModal={showCreateModal} closeModal={() => setShowCreateModal(false)} /> + setShowCreateSectionModal(false)} + /> { setShowEditModal(false)} 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..9b0790dee 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, @@ -14,8 +16,16 @@ const EditDivisionModal = ({ saveDivision, showModal, closeModal, + filterParentDivisions, }) => { - const [division, setDivision] = useState(initialDivision); + const [division, setDivision] = useState({ + name: "", + programareaid: null, + issection: false, + parentid: null, + specifictopersonalrequests: false, + }); + const [parentDivisions, setParentDivisions] = useState(divisions); const handleSave = () => { saveDivision(division); @@ -25,35 +35,47 @@ const EditDivisionModal = ({ const handleClose = () => { closeModal(); }; + + useEffect(() => { + setDivision(initialDivision || { + name: "", + programareaid: null, + issection: false, + parentid: null, + specifictopersonalrequests: false, + }); + }, [showModal]); + //useEffect to manage filtering of dropdown for parent divisions useEffect(() => { - setDivision(initialDivision); - }, [initialDivision, showModal]); + setParentDivisions(filterParentDivisions(division, divisions, "EDIT")) + }, [division]) return ( <> - Edit Division + {division.issection ? "Edit Section" : "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} + + ))} + + } + { + modalFor === 'rename' && - : + } + { + modalFor !== 'rename' && modalFor !== 'reclassify' && 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 c03bcdb7d..c8a034b9d 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Attachments/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/Attachments/index.js @@ -4,7 +4,7 @@ import { useDispatch } from "react-redux"; import AttachmentModal from './AttachmentModal'; import Loading from "../../../../containers/Loading"; import { saveFilesinS3, getFileFromS3, postFOIS3DocumentPreSignedUrl, getFOIS3DocumentPreSignedUrl, completeMultiPartUpload } from "../../../../apiManager/services/FOI/foiOSSServices"; -import { saveFOIRequestAttachmentsList, replaceFOIRequestAttachment, saveNewFilename, deleteFOIRequestAttachment } from "../../../../apiManager/services/FOI/foiAttachmentServices"; +import { saveFOIRequestAttachmentsList, replaceFOIRequestAttachment, saveNewFilename, deleteFOIRequestAttachment, saveNewCategory } from "../../../../apiManager/services/FOI/foiAttachmentServices"; import { StateTransitionCategories, AttachmentCategories, AttachmentLetterCategories } from '../../../../constants/FOI/statusEnum' import { addToFullnameList, getFullnameList, ConditionalComponent } from '../../../../helper/FOI/helper'; import Grid from "@material-ui/core/Grid"; @@ -126,6 +126,9 @@ export const AttachmentSection = ({ const documentId = ministryId ? updateAttachment.foiministrydocumentid : updateAttachment.foidocumentid; dispatch(deleteFOIRequestAttachment(requestId, ministryId, documentId, {})); } + else if (modalFor === 'reclassify') { + // do nothing + } else if (files) { setFileCount(files.length); saveDocument(value, fileInfoList, files); @@ -306,6 +309,10 @@ export const AttachmentSection = ({ setModalFor("rename"); setModal(true); break; + case "reclassify": + setModalFor("reclassify"); + setModal(true); + break; case "download": downloadDocument(_attachment); setModalFor("download"); @@ -334,6 +341,19 @@ export const AttachmentSection = ({ } } + const handleReclassify = (_attachment, newCategory) => { + setModal(false); + + if (updateAttachment.category !== newCategory) { + const documentId = ministryId ? updateAttachment.foiministrydocumentid : updateAttachment.foidocumentid; + dispatch(saveNewCategory(newCategory, documentId, requestId, ministryId, (err, _res) => { + if (!err) { + setAttachmentLoading(false); + } + })); + } + } + const getFullname = (userId) => { let user; @@ -460,6 +480,7 @@ export const AttachmentSection = ({ attachment={updateAttachment} attachmentsArray={attachmentsArray} handleRename={handleRename} + handleReclassify={handleReclassify} maxNoFiles={10} isMinistryCoordinator={isMinistryCoordinator} /> @@ -478,13 +499,30 @@ 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 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')) { + result = true + } + } + }) + return result + } + 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 = ()=>{ if (disabled) @@ -551,6 +589,7 @@ const Attachment = React.memo(({indexValue, attachment, handlePopupButtonClick, attachment={attachment} handlePopupButtonClick={handlePopupButtonClick} disabled={disabled} + reclassifyIsDisabled={reclassifyIsDisabled} ministryId={ministryId} /> @@ -611,7 +650,7 @@ const opendocumentintab =(attachment,ministryId)=> window.open(url, '_blank').focus(); } -const AttachmentPopup = React.memo(({indexValue, attachment, handlePopupButtonClick, disabled,ministryId}) => { +const AttachmentPopup = React.memo(({indexValue, attachment, handlePopupButtonClick, disabled, reclassifyIsDisabled, ministryId}) => { const ref = React.useRef(); const closeTooltip = () => ref.current && ref ? ref.current.close():{}; @@ -620,6 +659,11 @@ const AttachmentPopup = React.memo(({indexValue, attachment, handlePopupButtonCl handlePopupButtonClick("rename", attachment); } + const handleReclassify = () => { + closeTooltip(); + handlePopupButtonClick("reclassify", attachment); + } + const handleReplace = () => { closeTooltip(); handlePopupButtonClick("replace", attachment); @@ -750,6 +794,17 @@ const AttachmentPopup = React.memo(({indexValue, attachment, handlePopupButtonCl > Download + + { + handleReclassify(); + setPopoverOpen(false); + }} + disabled={reclassifyIsDisabled} + > + Reclassify + + { handleRename(); diff --git a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/MinistryApprovalModal.jsx b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/MinistryApprovalModal.jsx new file mode 100644 index 000000000..56b10e205 --- /dev/null +++ b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/MinistryApprovalModal.jsx @@ -0,0 +1,39 @@ +import TextField from '@mui/material/TextField'; + +const MinistryApprovalModal = (props) => { + const {handleApprovalInputs} = props; + + return ( +
+ {handleApprovalInputs(event)}}/> + {handleApprovalInputs(event)}}/> + {handleApprovalInputs(event)}} + inputProps={{style: {width: "200px",}}} + InputLabelProps={{ shrink: true, required: true}} + size="small" + /> +
+ ); +} + +export default MinistryApprovalModal; \ No newline at end of file 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..a0ffb50ff 100644 --- a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/index.js @@ -17,6 +17,7 @@ import { useSelector } from "react-redux"; import './confirmationmodal.scss'; import { StateEnum, StateTransitionCategories } from '../../../../constants/FOI/statusEnum'; import FileUpload from '../FileUpload' +import MinistryApprovalModal from './MinistryApprovalModal'; import { formatDate, calculateDaysRemaining, ConditionalComponent, isMinistryLogin } from "../../../../helper/FOI/helper"; import { MimeTypeList, MaxFileSizeInMB, MaxNumberOfFiles } from "../../../../constants/FOI/enum"; import { getMessage, getAssignedTo, getMinistryGroup, getSelectedMinistry, getSelectedMinistryAssignedTo, getProcessingTeams, getUpdatedAssignedTo } from './util'; @@ -55,7 +56,7 @@ const useStyles = makeStyles((theme) => ({ })); export default function ConfirmationModal({requestId, openModal, handleModal, state, saveRequestObject, - handleClosingDateChange, handleClosingReasonChange, attachmentsArray }) { + handleClosingDateChange, handleClosingReasonChange, attachmentsArray, handleApprovalInputs, ministryApprovalState}) { const classes = useStyles(); const processingTeamList = useSelector(reduxstate=> reduxstate.foiRequests.foiProcessingTeamList); const selectedMinistries = saveRequestObject?.selectedMinistries?.map(ministry => ministry.code); @@ -79,7 +80,7 @@ export default function ConfirmationModal({requestId, openModal, handleModal, st const user = useSelector((reduxState) => reduxState.user.userDetail); const userGroups = user?.groups?.map(group => group.slice(1)); let isMinistry = isMinistryLogin(userGroups); - + const cfrStatus = useSelector((reduxState) => reduxState.foiRequests.foiRequestCFRForm.status); const cfrFeeData = useSelector((reduxState) => reduxState.foiRequests.foiRequestCFRForm.feedata); const actualsFeeDataFields = [ @@ -100,13 +101,13 @@ 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,9 +119,11 @@ 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) + || ((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; @@ -178,7 +181,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 = () => { @@ -202,9 +205,6 @@ export default function ConfirmationModal({requestId, openModal, handleModal, st ( (state.toLowerCase() === StateEnum.review.name.toLowerCase() && (!isAnyAmountPaid || !isMinistry)) || - (state.toLowerCase() === StateEnum.response.name.toLowerCase() - && saveRequestObject.requeststatusid === StateEnum.signoff.id) - || (state.toLowerCase() === StateEnum.onhold.name.toLowerCase() && (saveRequestObject.requeststatusid === StateEnum.feeassessed.id || saveRequestObject.requeststatusid === StateEnum.response.id) && saveRequestObject.email @@ -226,6 +226,26 @@ export default function ConfirmationModal({requestId, openModal, handleModal, st ); } + else if (currentState?.toLowerCase() === StateEnum.signoff.name.toLowerCase() && state.toLowerCase() === StateEnum.response.name.toLowerCase() && saveRequestObject.requeststatusid === StateEnum.signoff.id) { + return ( +
+ + +
+ ) + } + 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 +271,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..20cef4232 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} }; @@ -50,12 +50,18 @@ 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(): 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 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(): @@ -77,7 +83,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(): @@ -111,9 +123,11 @@ import { getFullnameList } from "../../../../helper/FOI/helper"; } case StateEnum.response.name.toLowerCase(): if (_saveRequestObject.requeststatusid === StateEnum.signoff.id) - return {title: "Ministry Sign Off", body: `Upload E-Approval log or completed sign form (if required) to 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}?`}; + 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/ContactApplicant/index.tsx b/forms-flow-web/src/components/FOI/customComponents/ContactApplicant/index.tsx index c9b911522..9921b8e69 100644 --- a/forms-flow-web/src/components/FOI/customComponents/ContactApplicant/index.tsx +++ b/forms-flow-web/src/components/FOI/customComponents/ContactApplicant/index.tsx @@ -406,6 +406,7 @@ export const ContactApplicant = ({ existingDocuments={files} attachment={{}} handleRename={undefined} + handleReclassify={undefined} isMinistryCoordinator={false} uploadFor={"email"} maxNoFiles={10} 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..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,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: "{{assignedGroupEmail}}", value: requestDetails.assignedGroupEmail}, {name: "{{ffaurl}}", value: requestDetails.ffaurl}, ]; diff --git a/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUpload.scss b/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUpload.scss index 55ee4c05d..3e29a8a80 100644 --- a/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUpload.scss +++ b/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUpload.scss @@ -6,6 +6,14 @@ align-items: center; background-color: white; } +.file-upload-container-scanning { + position: relative; + padding: 0px 35px 0px 35px; + display: flex; + flex-direction: column; + align-items: center; + background-color: white; +} .drag-and-drop-text { margin-top: 15% !important; margin-left: 20%; @@ -140,4 +148,13 @@ line-height: 1rem; font-size: 12px; } +} +.tag-message-container-scanning { + margin: 5px 0px 20px 9%; + display: block; + p { + line-height: 1rem; + font-size: 12px; + } + padding-right: 10px; } \ No newline at end of file diff --git a/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMCFPersonal.js b/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMCFPersonal.js new file mode 100644 index 000000000..8fb287e9b --- /dev/null +++ b/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMCFPersonal.js @@ -0,0 +1,423 @@ +import React, { useRef, useState, useEffect } from "react"; +import FilePreviewContainer from "./FilePreviewContainer"; +import { countOccurrences, + generateNewFileName, + getErrorMessage, + allowedFileType, + allowedFileSize, + convertNestedObjectToArray, + convertBytesToMB } from "./util"; +import { + ClickableChip +} from "../../Dashboard/utils"; +import Stack from "@mui/material/Stack"; +import "./FileUpload.scss"; +import clsx from "clsx"; +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 FileUploadForMCFPersonal = ({ + multipleFiles, + mimeTypes, + maxFileSize, + totalFileSize, + updateFilesCb, + attchmentFileNameList, + attachment, + customFormat = {}, + existingDocuments = [], + maxNumberOfFiles, + modalFor, + handleTagChange, + tagValue, + divisions = [], + tagList = [], + otherTagList = [], + isMinistryCoordinator, + uploadFor="attachment", + totalUploadedRecordSize, + totalRecordUploadLimit +}) => { + const fileInputField = useRef(null); + const [files, setFiles] = useState({ ...existingDocuments }); + const [totalFileSizeCalculated, setTotalFileSize] = useState(0); + const [errorMessage, setErrorMessage] = useState([]); + const [includeAttachments, setIncludeAttachments] = useState(true); + const [searchValue, setSearchValue] = useState(""); + const [additionalTagList, setAdditionalTagList] = useState([]); + const [showAdditionalTags, setShowAdditionalTags] = useState(false); + + const handleUploadBtnClick = (e) => { + e.stopPropagation(); + fileInputField.current.click(); + }; + const handleUploadAreaClick = () => { + if (Object.entries(files).length === 0) + fileInputField.current.click(); + }; + + const handleDuplicateFiles = (file) => { + let exists = false; + let duplicateFileName = ""; + if (Object.entries(files).length > 0) { + exists = Object.keys(files).some((k) => { + return k.toLowerCase() === file.name.toLowerCase(); + }); + } + if (exists) { + duplicateFileName = file.name; + } + else if (attchmentFileNameList) { + let countFileOccurrences = countOccurrences(file.name, attchmentFileNameList); + if (countFileOccurrences > 0 && multipleFiles) { + duplicateFileName = file.name; + } + else if (countFileOccurrences > 0 && !multipleFiles && (attachment == null || (attachment && attachment.filename.toLowerCase() !== file.name.toLowerCase()))) { + const filename = file.name.split('.'); + const newFileName = generateNewFileName(`${filename[0]}(${countFileOccurrences}).${filename[1]}`, file.name, attachment && attachment.filename, attchmentFileNameList); + file.filename = newFileName; + files[file.name] = file; + } + else { + files[file.name] = file; + } + } + else { + files[file.name] = file; + } + return duplicateFileName; + } + + const addNewFiles = (newFiles) => { + let _totalFileSizeInMB = totalFileSizeCalculated; + let _duplicateFiles = []; + let _typeErrorFiles = []; + let _overSizedFiles = []; + let removeFileSize = 0; + let recordUploadLimitReached = false; + for (let file of newFiles) { + file.filename = file.name; + const sizeInMB = convertBytesToMB(file.size); + _totalFileSizeInMB += parseFloat(sizeInMB); + if (allowedFileType(file, mimeTypes)) { + if (allowedFileSize(_totalFileSizeInMB, multipleFiles, totalFileSize)) { + if (sizeInMB <= maxFileSize) { + if (totalUploadedRecordSize > 0) { + if (_totalFileSizeInMB + totalUploadedRecordSize <= totalRecordUploadLimit) { + recordUploadLimitReached = false; + const duplicateFileName = handleDuplicateFiles(file); + _duplicateFiles.push(duplicateFileName); + } else { + recordUploadLimitReached = true; + _totalFileSizeInMB -= parseFloat(sizeInMB); + } + } else { + const duplicateFileName = handleDuplicateFiles(file); + _duplicateFiles.push(duplicateFileName); + } + } else { + _totalFileSizeInMB -= parseFloat(sizeInMB); + _overSizedFiles.push(file.name); + } + } else { + removeFileSize = convertBytesToMB(file.size); + } + } + else { + _typeErrorFiles.push(file.name); + } + } + setTotalFileSize(_totalFileSizeInMB); + let errMsg = getErrorMessage(_duplicateFiles, _typeErrorFiles, _overSizedFiles, maxFileSize, multipleFiles, mimeTypes, recordUploadLimitReached, totalRecordUploadLimit); + setErrorMessage(errMsg); + return [{...files}, _totalFileSizeInMB, removeFileSize, errMsg]; + }; + + const callUpdateFilesCb = (_files, errMsg) => { + const filesAsArray = convertNestedObjectToArray(_files); + updateFilesCb(filesAsArray, errMsg); + }; + + const validateFiles = (newFiles, totalFiles) => { + + if (multipleFiles && maxNumberOfFiles && (newFiles.length > maxNumberOfFiles || totalFiles > maxNumberOfFiles)) { + setErrorMessage([`A maximum of ${maxNumberOfFiles} files can be uploaded at one time. Only ${maxNumberOfFiles} files have been added on this upload window, please upload additional files separately`]); + } else if (!multipleFiles && totalFiles > 1) { + return + } else if (newFiles.length) { + let updatedFilesDetails = addNewFiles(newFiles); + if (multipleFiles && updatedFilesDetails[1] > totalFileSize) { + setTotalFileSize(updatedFilesDetails[1] - parseFloat(updatedFilesDetails[2])) + setErrorMessage([`The total size of all files uploaded can not exceed ${totalFileSize}MB. Please upload additional files separately.`]); + } + setFiles(updatedFilesDetails[0]); + callUpdateFilesCb(updatedFilesDetails[0]); + } + } + + const handleNewFileUpload = (e) => { + const { files: newFiles } = e.target; + const totalFiles = Object.entries(files).length + newFiles.length; + validateFiles(newFiles, totalFiles); + }; + const fileDrop = (e) => { + e.preventDefault(); + const newFiles = e.dataTransfer.files; + const totalNoOfFiles = Object.entries(files).length + newFiles.length; + validateFiles(newFiles, totalNoOfFiles); + } + const removeFile = (fileName) => { + const _file = files[fileName]; + const sizeInMB = (_file.size / (1024*1024)).toFixed(2); + setTotalFileSize(totalFileSizeCalculated - parseFloat(sizeInMB)) + delete files[fileName]; + setFiles({ ...files }); + callUpdateFilesCb({ ...files }); + setErrorMessage([]); + }; + const dragOver = (e) => { + e.preventDefault(); + } + + const dragEnter = (e) => { + e.preventDefault(); + } + + const dragLeave = (e) => { + e.preventDefault(); + } + + const showDragandDrop = () => { + if (Object.entries(files).length === 0) + return "Drag and drop attachments, or click Add Files" + } + + 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; + } + + React.useEffect(() => { + setAdditionalTagList(searchSections(otherTagList, searchValue, tagValue)); + },[searchValue, otherTagList, tagValue]) + + return ( + <> + {(modalFor === "add" && (uploadFor === "attachment" || uploadFor === 'record')) && (
+
+ 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 && isMinistryCoordinator && (<> +
+ {divisions.map(tag => + {handleTagChange(tag.name)}} + clicked={tagValue == tag.name} + /> + )} +
+ )} + {!isMinistryCoordinator && (<> +
+ {tagList.map(tag => + {handleTagChange(tag.divisionid)}} + clicked={tagValue == tag.divisionid} + /> + )} +
+
+ + + + + {setSearchValue(e.target.value.trim())}} + sx={{ + color: "#38598A", + }} + startAdornment={ + + + Search any additional sections here + + + + } + fullWidth + /> + + + {showAdditionalTags === true && ( + {additionalTagList.map(tag => + {handleTagChange(tag.divisionid)}} + clicked={tagValue == tag.divisionid} + /> + )} + )} + +
+ )} +
)} + {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.

+
)} +
+
+
+ {Object.entries(files).length === 0 ? ( +
+

{showDragandDrop()}

+
+ ) : ( + + )} +
+
+ +
+
+ {(Object.entries(files).length === 0 && !multipleFiles) || multipleFiles ? + : null} +
+
+
+
    + {errorMessage + ? errorMessage.map((error) => ( +
  • +
    +

    {error}

    +
    +
  • + )) + : null} +
+ + ); +}; + +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 new file mode 100644 index 000000000..0b53a5033 --- /dev/null +++ b/forms-flow-web/src/components/FOI/customComponents/FileUpload/FileUploadForMSDPersonal.js @@ -0,0 +1,451 @@ +import React, { useRef, useState, useEffect } from "react"; +import FilePreviewContainer from "./FilePreviewContainer"; +import { countOccurrences, + generateNewFileName, + getErrorMessage, + allowedFileType, + allowedFileSize, + convertNestedObjectToArray, + convertBytesToMB } from "./util"; +import { + ClickableChip +} from "../../Dashboard/utils"; +import Stack from "@mui/material/Stack"; +import "./FileUpload.scss"; +import clsx from "clsx"; +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 FileUploadForMSDPersonal = ({ + multipleFiles, + mimeTypes, + maxFileSize, + totalFileSize, + updateFilesCb, + attchmentFileNameList, + attachment, + customFormat = {}, + existingDocuments = [], + maxNumberOfFiles, + modalFor, + handleTagChange, + tagValue, + divisions = [], + tagList = [], + otherTagList = [], + isMinistryCoordinator, + uploadFor="attachment", + totalUploadedRecordSize, + totalRecordUploadLimit +}) => { + const fileInputField = useRef(null); + const [files, setFiles] = useState({ ...existingDocuments }); + const [totalFileSizeCalculated, setTotalFileSize] = useState(0); + const [errorMessage, setErrorMessage] = useState([]); + const [includeAttachments, setIncludeAttachments] = useState(true); + const [searchValue, setSearchValue] = useState(""); + const [additionalTagList, setAdditionalTagList] = useState([]); + const [showChildTags, setShowChildTags] = useState(false); + const [showAdditionalTags, setShowAdditionalTags] = useState(false); + const [newDivisions, setNewDivisions] = useState([]); + + + const handleUploadBtnClick = (e) => { + e.stopPropagation(); + fileInputField.current.click(); + }; + const handleUploadAreaClick = () => { + if (Object.entries(files).length === 0) + fileInputField.current.click(); + }; + + const handleDuplicateFiles = (file) => { + let exists = false; + let duplicateFileName = ""; + if (Object.entries(files).length > 0) { + exists = Object.keys(files).some((k) => { + return k.toLowerCase() === file.name.toLowerCase(); + }); + } + if (exists) { + duplicateFileName = file.name; + } + else if (attchmentFileNameList) { + let countFileOccurrences = countOccurrences(file.name, attchmentFileNameList); + if (countFileOccurrences > 0 && multipleFiles) { + duplicateFileName = file.name; + } + else if (countFileOccurrences > 0 && !multipleFiles && (attachment == null || (attachment && attachment.filename.toLowerCase() !== file.name.toLowerCase()))) { + const filename = file.name.split('.'); + const newFileName = generateNewFileName(`${filename[0]}(${countFileOccurrences}).${filename[1]}`, file.name, attachment && attachment.filename, attchmentFileNameList); + file.filename = newFileName; + files[file.name] = file; + } + else { + files[file.name] = file; + } + } + else { + files[file.name] = file; + } + return duplicateFileName; + } + + const addNewFiles = (newFiles) => { + let _totalFileSizeInMB = totalFileSizeCalculated; + let _duplicateFiles = []; + let _typeErrorFiles = []; + let _overSizedFiles = []; + let removeFileSize = 0; + let recordUploadLimitReached = false; + for (let file of newFiles) { + file.filename = file.name; + const sizeInMB = convertBytesToMB(file.size); + _totalFileSizeInMB += parseFloat(sizeInMB); + if (allowedFileType(file, mimeTypes)) { + if (allowedFileSize(_totalFileSizeInMB, multipleFiles, totalFileSize)) { + if (sizeInMB <= maxFileSize) { + if (totalUploadedRecordSize > 0) { + if (_totalFileSizeInMB + totalUploadedRecordSize <= totalRecordUploadLimit) { + recordUploadLimitReached = false; + const duplicateFileName = handleDuplicateFiles(file); + _duplicateFiles.push(duplicateFileName); + } else { + recordUploadLimitReached = true; + _totalFileSizeInMB -= parseFloat(sizeInMB); + } + } else { + const duplicateFileName = handleDuplicateFiles(file); + _duplicateFiles.push(duplicateFileName); + } + } else { + _totalFileSizeInMB -= parseFloat(sizeInMB); + _overSizedFiles.push(file.name); + } + } else { + removeFileSize = convertBytesToMB(file.size); + } + } + else { + _typeErrorFiles.push(file.name); + } + } + setTotalFileSize(_totalFileSizeInMB); + let errMsg = getErrorMessage(_duplicateFiles, _typeErrorFiles, _overSizedFiles, maxFileSize, multipleFiles, mimeTypes, recordUploadLimitReached, totalRecordUploadLimit); + setErrorMessage(errMsg); + return [{...files}, _totalFileSizeInMB, removeFileSize, errMsg]; + }; + + const callUpdateFilesCb = (_files, errMsg) => { + const filesAsArray = convertNestedObjectToArray(_files); + updateFilesCb(filesAsArray, errMsg); + }; + + const validateFiles = (newFiles, totalFiles) => { + + if (multipleFiles && maxNumberOfFiles && (newFiles.length > maxNumberOfFiles || totalFiles > maxNumberOfFiles)) { + setErrorMessage([`A maximum of ${maxNumberOfFiles} files can be uploaded at one time. Only ${maxNumberOfFiles} files have been added on this upload window, please upload additional files separately`]); + } else if (!multipleFiles && totalFiles > 1) { + return + } else if (newFiles.length) { + let updatedFilesDetails = addNewFiles(newFiles); + if (multipleFiles && updatedFilesDetails[1] > totalFileSize) { + setTotalFileSize(updatedFilesDetails[1] - parseFloat(updatedFilesDetails[2])) + setErrorMessage([`The total size of all files uploaded can not exceed ${totalFileSize}MB. Please upload additional files separately.`]); + } + setFiles(updatedFilesDetails[0]); + callUpdateFilesCb(updatedFilesDetails[0]); + } + } + + const handleNewFileUpload = (e) => { + const { files: newFiles } = e.target; + const totalFiles = Object.entries(files).length + newFiles.length; + validateFiles(newFiles, totalFiles); + }; + const fileDrop = (e) => { + e.preventDefault(); + const newFiles = e.dataTransfer.files; + const totalNoOfFiles = Object.entries(files).length + newFiles.length; + validateFiles(newFiles, totalNoOfFiles); + } + const removeFile = (fileName) => { + const _file = files[fileName]; + const sizeInMB = (_file.size / (1024*1024)).toFixed(2); + setTotalFileSize(totalFileSizeCalculated - parseFloat(sizeInMB)) + delete files[fileName]; + setFiles({ ...files }); + callUpdateFilesCb({ ...files }); + setErrorMessage([]); + }; + const dragOver = (e) => { + e.preventDefault(); + } + + const dragEnter = (e) => { + e.preventDefault(); + } + + const dragLeave = (e) => { + e.preventDefault(); + } + + const showDragandDrop = () => { + if (Object.entries(files).length === 0) + return "Drag and drop attachments, or click Add Files" + } + + 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 searchResult = -1; + let divs = []; + React.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]) + + React.useEffect(() => { + setAdditionalTagList(searchSections(otherTagList, searchValue, tagValue)); + },[searchValue, otherTagList, tagValue]) + + return ( + <> + {(modalFor === "add" && (uploadFor === "attachment" || uploadFor === 'record')) && (
+
+ 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 => + {handleTagChange(tag.divisionid)}} + clicked={tag.divisionid == tagValue} + /> + )} +
+
+ + + + + {setSearchValue(e.target.value.trim())}} + sx={{ + color: "#38598A", + }} + value={searchValue} + startAdornment={ + + + Search any additional sections here + + + + } + fullWidth + /> + + + {showAdditionalTags === true && ( + {additionalTagList.map(tag => + {handleTagChange(tag.divisionid)}} + clicked={tagValue == tag.divisionid} + /> + )} + )} + +
)} +
)} + {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.

+
)} +
+
+
+ {Object.entries(files).length === 0 ? ( +
+

{showDragandDrop()}

+
+ ) : ( + + )} +
+
+ +
+
+ {(Object.entries(files).length === 0 && !multipleFiles) || multipleFiles ? + : 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/Records/MCFPersonal.js b/forms-flow-web/src/components/FOI/customComponents/Records/MCFPersonal.js new file mode 100644 index 000000000..371abf4dc --- /dev/null +++ b/forms-flow-web/src/components/FOI/customComponents/Records/MCFPersonal.js @@ -0,0 +1,207 @@ +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'; +import { MCFPopularSections } from "../../../../constants/FOI/enum"; + +const MCFPersonal = ({ + setNewDivision, + tagValue, + divisionModalTagValue, + divisions=[], + isMinistryCoordinator +}) => { + + 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, MCFPopularSections-1)); + const [otherTagList, setOtherTagList] = useState(MCFSections?.sections?.slice(MCFPopularSections)); + + useEffect(() => { + setTagList(MCFSections?.sections?.slice(0, MCFPopularSections-1)); + setOtherTagList(MCFSections?.sections?.slice(MCFPopularSections)); + },[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 ( + <> +
+ + {isMinistryCoordinator && divisions.length > 0 && divisions.filter(div => div.divisionid !== tagValue).length > 0 && (<> +
+ {divisions.filter(div => { + return div.divisionid !== tagValue; + }).map(tag => + {setNewDivision(tag.divisionid)}} + clicked={divisionModalTagValue == tag.divisionid} + /> + )} +
+ )} + + {!isMinistryCoordinator && (<> +
+ {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..906fa80b3 --- /dev/null +++ b/forms-flow-web/src/components/FOI/customComponents/Records/MSDPersonal.js @@ -0,0 +1,229 @@ +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'; +import { MSDPopularSections } from "../../../../constants/FOI/enum"; + +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, MSDPopularSections-1)); + const [otherTagList, setOtherTagList] = useState(MSDSections?.divisions[0]?.sections?.slice(MSDPopularSections)); + + useEffect(() => { + setTagList(MSDSections?.divisions[0]?.sections?.slice(0, MSDPopularSections-1)); + setOtherTagList(MSDSections?.divisions[0]?.sections?.slice(MSDPopularSections)); + },[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 fe72e72d2..825ce3d26 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/index.js @@ -5,13 +5,19 @@ import { useDispatch, useSelector } from "react-redux"; import AttachmentModal from "../Attachments/AttachmentModal"; import Loading from "../../../../containers/Loading"; import { + getOSSHeaderDetails, saveFilesinS3, getFileFromS3, postFOIS3DocumentPreSignedUrl, getFOIS3DocumentPreSignedUrl, completeMultiPartUpload, } from "../../../../apiManager/services/FOI/foiOSSServices"; -import { saveNewFilename } from "../../../../apiManager/services/FOI/foiAttachmentServices"; +import { + saveFOIRequestAttachmentsList, + replaceFOIRequestAttachment, + saveNewFilename, + deleteFOIRequestAttachment, +} from "../../../../apiManager/services/FOI/foiAttachmentServices"; import { fetchFOIRecords, saveFOIRecords, @@ -39,6 +45,7 @@ import { import { addToFullnameList, getFullnameList, + ConditionalComponent, isrecordtimeout, } from "../../../../helper/FOI/helper"; import Grid from "@material-ui/core/Grid"; @@ -55,6 +62,14 @@ import TextField from "@mui/material/TextField"; import { saveAs } from "file-saver"; import { downloadZip } from "client-zip"; import { ClickableChip } from "../../Dashboard/utils"; +import CircularProgress from "@mui/material/CircularProgress"; +import AttachmentFilter from "../Attachments/AttachmentFilter"; +import Accordion from "@material-ui/core/Accordion"; +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 SearchIcon from "@material-ui/icons/Search"; import InputAdornment from "@mui/material/InputAdornment"; import InputBase from "@mui/material/InputBase"; @@ -62,12 +77,18 @@ import Paper from "@mui/material/Paper"; import Tooltip from "@mui/material/Tooltip"; import { toast } from "react-toastify"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faCheckCircle, faClone } from "@fortawesome/free-regular-svg-icons"; +import { + faCheckCircle, + faClone, + faTrashAlt, + faTrashCan, +} from "@fortawesome/free-regular-svg-icons"; import { faSpinner, faExclamationCircle, faBan, faArrowTurnUp, + faHistory, faTrash, faPenToSquare, faLinkSlash, @@ -97,6 +118,12 @@ import { } 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"; +import MCFPersonal from './MCFPersonal'; +import MSDPersonal from './MSDPersonal'; const useStyles = makeStyles((_theme) => ({ createButton: { @@ -196,8 +223,15 @@ export const RecordsLog = ({ ministryAssignedToList, isMinistryCoordinator, setRecordsUploading, + recordsTabSelect, + requestType }) => { - let recordsObj = useSelector((state) => state.foiRequests.foiRequestRecords); + const user = useSelector((state) => state.user.userDetail); + const userGroups = user?.groups?.map(group => group.slice(1)); + + let recordsObj = useSelector( + (state) => state.foiRequests.foiRequestRecords + ); let pdfStitchStatus = useSelector( (state) => state.foiRequests.foiPDFStitchStatusForHarms @@ -222,18 +256,31 @@ export const RecordsLog = ({ let isRecordsfetching = useSelector( (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); - useEffect(() => { - setRecords(recordsObj?.records); - let nonDuplicateRecords = recordsObj?.records?.filter( - (record) => !record.isduplicate - ); - let totalUploadedSize = - calculateTotalUploadedFileSizeInKB(nonDuplicateRecords) / (1024 * 1024); + 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) + let totalUploadedSize= (calculateTotalUploadedFileSizeInKB(nonDuplicateRecords)/ (1024 * 1024)) setTotalUploadedRecordSize(parseFloat(totalUploadedSize.toFixed(4))); dispatch(checkForRecordsChange(requestId, ministryId)); + //To manage enabling and disabling of download for harms package + recordsDownloadList[1].disabled = enableHarmsDonwnload(); }, [recordsObj]); useEffect(() => { @@ -243,6 +290,24 @@ export const RecordsLog = ({ const conversionFormats = useSelector( (state) => state.foiRequests.conversionFormats ); + + useEffect(() => { + if (recordsTabSelect && conversionFormats?.length < 1) { + 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, + } + ); + } + }, [recordsTabSelect, conversionFormats]); + const divisionFilters = [ ...new Map( recordsObj?.records?.reduce( @@ -418,9 +483,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); + } } } } @@ -978,12 +1045,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"; } @@ -1416,6 +1478,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; }; @@ -1493,6 +1563,16 @@ 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 (
{isAttachmentLoading ? ( @@ -1521,6 +1601,17 @@ export const RecordsLog = ({ id="download" label={currentDownload === 0 ? "Download" : ""} inputProps={{ "aria-labelledby": "download-label" }} + // InputProps={{ + // startAdornment: isDownloadInProgress && + // {/* */} + // {/* */} + // record.isredactionready ? + // : + // record.failed ? + // : + // + // + // }} InputLabelProps={{ shrink: false }} select name="download" @@ -1565,7 +1656,6 @@ export const RecordsLog = ({ ) : null} {item.label} - // ); } })} @@ -1573,18 +1663,19 @@ export const RecordsLog = ({ )} - {isMinistryCoordinator ? ( + {isMinistryCoordinator || (isScanningTeamMember && MinistryNeedsScanning.includes(bcgovcode.replaceAll('"', '')) && requestType === FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL) ? - ) : ( + : ( records?.length > 0 && - DISABLE_REDACT_WEBLINK?.toLowerCase() == "false" && ( + DISABLE_REDACT_WEBLINK?.toLowerCase() == "false" && ) - )} + } + {isMCFPersonal && (<> +
  • + all records selected must be uploaded by your own team +
  • + )}
    ) : ( @@ -1955,6 +2051,7 @@ export const RecordsLog = ({ )} totalUploadedRecordSize={totalUploadedRecordSize} replacementfiletypes={getreplacementfiletypes()} + requestType={requestType} />
    -

    Update Divisions

    - setDivisionsModalOpen(false)} - > - Close - - +

    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. + + + {(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} + divisions={divisions} + isMinistryCoordinator={isMinistryCoordinator} + /> + : + (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} + /> + )} +
    } + }
    @@ -2236,7 +2317,7 @@ const Attachment = React.memo( { - 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")) && ( ( - 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); + +export const getPDFFilePath = (item) => { + let pdffilepath = item.s3uripath; + let pdffilename = item.filename; + + 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) { + 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, true); + return getUpdatedRecords(arrangedAttachments, true); +} + +// Get records with only necessary fields +export const getUpdatedRecords = (_records, isattachment = false) => { + return _records.map((_record) => { + const [filepath, filename] = getPDFFilePath(_record); + 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) + : 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/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js b/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js index 4cf1c6068..90f957c96 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); @@ -97,11 +101,15 @@ 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; - 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; } @@ -126,8 +134,16 @@ 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(): + if(personalIAO && (requestDetails.bcgovcode.toLowerCase() === "mcf" || requestDetails.bcgovcode.toLowerCase() === "msd")) + return _stateList.reviewcfdmsdpersonal return _stateList.review; case StateEnum.onhold.name.toLowerCase(): return _stateList.onhold; @@ -154,6 +170,13 @@ 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 + case StateEnum.onholdapplicationfee.name.toLowerCase(): + return _stateList.onholdapplicationfee; default: return []; @@ -167,7 +190,6 @@ const StateDropDown = ({ return isValidationError || requestState === StateEnum.unopened.name; }; const statusList = getStatusList(); - const menuItems = statusList.length > 0 && statusList.map((item, index) => { diff --git a/forms-flow-web/src/components/FOI/customComponents/statedropdown.scss b/forms-flow-web/src/components/FOI/customComponents/statedropdown.scss index f21192490..ae7e7f1b1 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; } @@ -62,6 +62,10 @@ background-color: #595959; } +.on-hold-applicationfee { + background-color: #8C3601; +} + .harmsassessment { background-color: #832AB7; } @@ -88,4 +92,14 @@ .peerreview { background-color: #096DD1; +} + + + +.tagging { + background-color: #9B1048; +} + +.readytoscan{ + background-color: #A2096C; } \ No newline at end of file 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/forms-flow-web/src/constants/FOI/enum.js b/forms-flow-web/src/constants/FOI/enum.js index 7135e233b..ce2622e6e 100644 --- a/forms-flow-web/src/constants/FOI/enum.js +++ b/forms-flow-web/src/constants/FOI/enum.js @@ -155,9 +155,19 @@ const KCProcessingTeams = [ "Coordinated Response Unit", ]; +const KCScanningTeam = "Scanning Team" + +const MinistryNeedsScanning = [ + "MCF", + "MSD" +] + +const MCFPopularSections = 23 +const MSDPopularSections = 11 + const RecordsDownloadList = [ { id: 0, label: "Download", disabled: true }, - { id: 1, label: "Download for Harms", disabled: false }, + { id: 1, label: "Download for Harms", disabled: true }, { id: 2, label: "Download Redline for Sign Off", disabled: true }, { id: 3, label: "Download Final Package", disabled: true }, ]; @@ -179,13 +189,17 @@ const RecordDownloadStatus = Object.freeze({ }); export { - MimeTypeList, - MaxFileSizeInMB, - MaxNumberOfFiles, - extensionStatusId, - extensionStatusLabel, - KCProcessingTeams, - RecordsDownloadList, - RecordDownloadCategory, - RecordDownloadStatus, +MimeTypeList, +MaxFileSizeInMB, +MaxNumberOfFiles, +extensionStatusId, +extensionStatusLabel, +KCProcessingTeams, +KCScanningTeam, +MinistryNeedsScanning, +MCFPopularSections, +MSDPopularSections, +RecordsDownloadList, +RecordDownloadCategory, +RecordDownloadStatus, }; 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/forms-flow-web/src/constants/FOI/statusEnum.js b/forms-flow-web/src/constants/FOI/statusEnum.js index ebb741504..8242d858c 100644 --- a/forms-flow-web/src/constants/FOI/statusEnum.js +++ b/forms-flow-web/src/constants/FOI/statusEnum.js @@ -1,9 +1,13 @@ 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}], + 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}], + 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}], @@ -11,11 +15,14 @@ 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}], //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}], + 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({ @@ -35,12 +42,15 @@ 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}] }); +// 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}, - 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}, @@ -54,7 +64,11 @@ 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}, + onholdapplicationfee: {name: "On-Hold - Application Fee", id: 19}, + section5pending: {name: "Section 5 Pending", id: 20}, }); const StateTransitionCategories = Object.freeze({ @@ -180,10 +194,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 +208,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,11 +228,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: "oipc", + tags: ["oipc"], + display: "OIPC", + bgcolor: "#595959", type: ["tag"], }, { // transition: Response -> On hold 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'; diff --git a/forms-flow-web/src/helper/FOI/helper.js b/forms-flow-web/src/helper/FOI/helper.js index 01c381593..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 } from "../../constants/FOI/enum"; +import { KCProcessingTeams, KCScanningTeam } from "../../constants/FOI/enum"; import _ from 'lodash'; let isBetween = require("dayjs/plugin/isBetween"); @@ -182,12 +182,19 @@ const isMinistryLogin = (userGroups) => { userGroups?.includes(group) ); }; + const isProcessingTeam = (userGroups) => { return userGroups?.some((userGroup) => KCProcessingTeams.includes(userGroup.replace("/", "")) ); }; +const isScanningTeam = (userGroups) => { + return userGroups?.some((userGroup) => + userGroup.replace("/", "") == KCScanningTeam + ); +}; + const isFoiAdmin = (userGroups) => { return ( userGroups?.map((userGroup) => userGroup.replace("/", "")).indexOf("FOI Admin") !== -1 @@ -492,6 +499,7 @@ export { isRequestWatcherOrMinistryAssignee, formatDateInPst, isProcessingTeam, + isScanningTeam, isFlexTeam, isIntakeTeam, encrypt, diff --git a/forms-flow-web/src/modules/FOI/foiRequestsReducer.js b/forms-flow-web/src/modules/FOI/foiRequestsReducer.js index ebb503f4b..8dd27296b 100644 --- a/forms-flow-web/src/modules/FOI/foiRequestsReducer.js +++ b/forms-flow-web/src/modules/FOI/foiRequestsReducer.js @@ -241,6 +241,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: 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/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/migrations/versions/07a6d930b276_.MSD personal sections.py b/request-management-api/migrations/versions/07a6d930b276_.MSD personal sections.py new file mode 100644 index 000000000..c4d48b57b --- /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','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: + 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/0ab7d9683c9a_requesttypecolmn_divisions.py b/request-management-api/migrations/versions/0ab7d9683c9a_requesttypecolmn_divisions.py new file mode 100644 index 000000000..f5f3a752a --- /dev/null +++ b/request-management-api/migrations/versions/0ab7d9683c9a_requesttypecolmn_divisions.py @@ -0,0 +1,24 @@ +""" Adding specifictopersonalrequests boolean column to distinguish between section/dvisions for personal vs general requests + +Revision ID: 0ab7d9683c9a +Revises: 7341bc012ca9 +Create Date: 2023-08-30 15:59:55.514768 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '0ab7d9683c9a' +down_revision = '7341bc012ca9' +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column('ProgramAreaDivisions', sa.Column('specifictopersonalrequests', sa.Boolean, nullable=True)) + + +def downgrade(): + op.drop_column('ProgramAreaDivisions', 'specifictopersonalrequests') 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 diff --git a/request-management-api/migrations/versions/1e9a62e22f88_CFD_sections_bulkinsert.py b/request-management-api/migrations/versions/1e9a62e22f88_CFD_sections_bulkinsert.py new file mode 100644 index 000000000..d59e12d7e --- /dev/null +++ b/request-management-api/migrations/versions/1e9a62e22f88_CFD_sections_bulkinsert.py @@ -0,0 +1,31 @@ +""" This script is for bulk inserting sections for CFD personals + +Revision ID: 1e9a62e22f88 +Revises: 0ab7d9683c9a +Create Date: 2023-08-31 15:01:54.846544 + +""" +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 = '1e9a62e22f88' +down_revision = '0ab7d9683c9a' +branch_labels = None +depends_on = None + + +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", "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: + op.execute('INSERT INTO public."ProgramAreaDivisions"(programareaid, name, isactive, created_at, createdby, sortorder, issection, specifictopersonalrequests)\ + VALUES ((SELECT programareaid FROM public."ProgramAreas" WHERE iaocode =\'CFD\'), \''+section+'\', TRUE, NOW(), \'system\', '+ str(sortorder) +',TRUE, TRUE );') + sortorder = sortorder+1 + +def downgrade(): + op.execute('DELETE FROM public."ProgramAreaDivisions" WHERE programareaid in (SELECT programareaid FROM public."ProgramAreas" WHERE iaocode =\'CFD\') AND issection=TRUE and specifictopersonalrequests=TRUE') 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..9613a53c1 --- /dev/null +++ b/request-management-api/migrations/versions/2e43869513c2_migrationcolumns.py @@ -0,0 +1,34 @@ +"""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)) + op.add_column('FOIRequestExtensions', 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') + op.drop_column('FOIRequestExtensions', 'migrationreference') 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/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 diff --git a/request-management-api/migrations/versions/7341bc012ca9_defaultsectionfalse_noncfdmsd.py b/request-management-api/migrations/versions/7341bc012ca9_defaultsectionfalse_noncfdmsd.py new file mode 100644 index 000000000..b474caad3 --- /dev/null +++ b/request-management-api/migrations/versions/7341bc012ca9_defaultsectionfalse_noncfdmsd.py @@ -0,0 +1,24 @@ +"""Migration script for setting issection default value for divisions - non CFD, MSD + +Revision ID: 7341bc012ca9 +Revises: ca122cd9b012 +Create Date: 2023-08-30 13:57:08.435322 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '7341bc012ca9' +down_revision = 'ca122cd9b012' +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute('UPDATE public."ProgramAreaDivisions" SET issection=FALSE WHERE programareaid not in (SELECT programareaid FROM public."ProgramAreas" WHERE iaocode =\'CFD\' or iaocode =\'MSD\');') + + +def downgrade(): + op.execute('UPDATE public."ProgramAreaDivisions" SET issection=NULL WHERE programareaid not in (SELECT programareaid FROM public."ProgramAreas" WHERE iaocode =\'CFD\' or iaocode =\'MSD\');') 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') diff --git a/request-management-api/migrations/versions/a79cd809e85e_.py b/request-management-api/migrations/versions/a79cd809e85e_.py new file mode 100644 index 000000000..607788802 --- /dev/null +++ b/request-management-api/migrations/versions/a79cd809e85e_.py @@ -0,0 +1,26 @@ +"""Adding Section 5 Pending FOI Status to FOIRequestStatuses Table and NotificaitonTypes Table + +Revision ID: a79cd809e85e +Revises: 1491b3126887 +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 = '1491b3126887' +branch_labels = None +depends_on = None + + +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/migrations/versions/aacdbca19a47_MSD_CFD_reqstatuses.py b/request-management-api/migrations/versions/aacdbca19a47_MSD_CFD_reqstatuses.py new file mode 100644 index 000000000..7b1b104d2 --- /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 = '6b94062ebea9' +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 diff --git a/request-management-api/migrations/versions/b51a0f2635c1_.py b/request-management-api/migrations/versions/b51a0f2635c1_.py new file mode 100644 index 000000000..13334a2ba --- /dev/null +++ b/request-management-api/migrations/versions/b51a0f2635c1_.py @@ -0,0 +1,24 @@ +"""empty message + +Revision ID: b51a0f2635c1 +Revises: d43db71d53e5 +Create Date: 2023-09-06 10:53:17.474765 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'b51a0f2635c1' +down_revision = 'd43db71d53e5' +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column('FOIMinistryRequests', sa.Column('ministrysignoffapproval', sa.JSON(), nullable=True)) + + +def downgrade(): + op.drop_column('FOIMinistryRequests', 'ministrysignoffapproval') 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 diff --git a/request-management-api/migrations/versions/ca122cd9b012_sectionscolumns.py b/request-management-api/migrations/versions/ca122cd9b012_sectionscolumns.py new file mode 100644 index 000000000..0d77972aa --- /dev/null +++ b/request-management-api/migrations/versions/ca122cd9b012_sectionscolumns.py @@ -0,0 +1,27 @@ +""" Adding 2 columns issection and parentid for capturing sections and its heirarchy under division - CFD, MSD +Task #4292 + +Revision ID: ca122cd9b012 +Revises: b51a0f2635c1 +Create Date: 2023-08-30 13:26:20.287595 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'ca122cd9b012' +down_revision = 'b51a0f2635c1' +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column('ProgramAreaDivisions', sa.Column('issection', sa.Boolean(), nullable=True,default=False)) + op.add_column('ProgramAreaDivisions', sa.Column('parentid', sa.Integer(), nullable=True)) + + +def downgrade(): + op.drop_column('ProgramAreaDivisions', 'issection') + op.drop_column('ProgramAreaDivisions', 'parentid') 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 ### diff --git a/request-management-api/migrations/versions/e25110cba030_.py b/request-management-api/migrations/versions/e25110cba030_.py new file mode 100644 index 000000000..79f46f8b1 --- /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 =\'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;') + diff --git a/request-management-api/request_api/models/FOIMinistryRequestDocuments.py b/request-management-api/request_api/models/FOIMinistryRequestDocuments.py index 8fac2dad1..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) foiministrydocumentid, filename, documentpath, category, isactive, created_at , createdby, version FROM "FOIMinistryRequestDocuments" where foiministryrequest_id =:ministryrequestid ORDER BY 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: @@ -121,13 +143,13 @@ def getlatestreceiptdocumentforemail(cls, ministryrequestid, category): @classmethod def deActivateministrydocumentsversion(cls, foiministrydocumentid, currentversion, userid)->DefaultMethodResult: - db.session.query(FOIMinistryRequestDocument).filter(FOIMinistryRequestDocument.foiministrydocumentid == foiministrydocumentid, FOIMinistryRequestDocument.version != currentversion).update({"isactive": False, "updated_at": datetime.now(),"updatedby": userid}, synchronize_session=False) + db.session.query(FOIMinistryRequestDocument).filter(FOIMinistryRequestDocument.foiministrydocumentid == foiministrydocumentid, FOIMinistryRequestDocument.version == currentversion).update({"isactive": False, "updated_at": datetime.now(),"updatedby": userid}, synchronize_session=False) db.session.commit() return DefaultMethodResult(True,'Ministry Document Updated',foiministrydocumentid) @classmethod def deActivateministrydocumentsversionbyministry(cls, ministryid, ministryversion, userid)->DefaultMethodResult: - db.session.query(FOIMinistryRequestDocument).filter(FOIMinistryRequestDocument.foiministryrequest_id == ministryid, FOIMinistryRequestDocument.foiministryrequestversion_id != ministryversion).update({"isactive": False, "updated_at": datetime.now(),"updatedby": userid}, synchronize_session=False) + db.session.query(FOIMinistryRequestDocument).filter(FOIMinistryRequestDocument.foiministryrequest_id == ministryid, FOIMinistryRequestDocument.foiministryrequestversion_id == ministryversion).update({"isactive": False, "updated_at": datetime.now(),"updatedby": userid}, synchronize_session=False) db.session.commit() return DefaultMethodResult(True,'Documents Updated for the ministry',ministryid) diff --git a/request-management-api/request_api/models/FOIMinistryRequests.py b/request-management-api/request_api/models/FOIMinistryRequests.py index ab9c12077..cd6e2a8e9 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,8 @@ 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) + ministrysignoffapproval = db.Column(JSON, unique=False, nullable=True) #ForeignKey References @@ -160,11 +163,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() @@ -183,7 +186,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 +199,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 @@ -687,7 +692,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]) ) ) ) @@ -1111,7 +1116,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)) @@ -1302,5 +1307,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', 'ministrysignoffapproval', 'identityverified','originalldd') diff --git a/request-management-api/request_api/models/FOIRawRequestDocuments.py b/request-management-api/request_api/models/FOIRawRequestDocuments.py index f4eaaba38..465e3a2cf 100644 --- a/request-management-api/request_api/models/FOIRawRequestDocuments.py +++ b/request-management-api/request_api/models/FOIRawRequestDocuments.py @@ -37,7 +37,7 @@ class FOIRawRequestDocument(db.Model): def getdocuments(cls,requestid, requestversion): documents = [] try: - sql = 'SELECT * FROM (SELECT DISTINCT ON (foidocumentid) foidocumentid, filename, documentpath, category, isactive, created_at , createdby FROM "FOIRawRequestDocuments" where foirequest_id =:requestid and foirequestversion_id = :requestversion ORDER BY foidocumentid, version DESC) AS list ORDER BY created_at DESC' + sql = 'SELECT * FROM (SELECT DISTINCT ON (foidocumentid) raw2.created_at, raw.created_at as current_version_created_at, raw.foidocumentid, raw.filename, raw.documentpath, raw.category, raw.isactive, raw.createdby FROM "FOIRawRequestDocuments" raw join "FOIRawRequestDocuments" raw2 on (raw.foirequest_id = raw2.foirequest_id and raw2.version = 1) where raw.foirequest_id = :requestid and raw.foirequestversion_id = :requestversion and raw.isactive = true ORDER BY raw.foidocumentid DESC) AS list ORDER BY created_at DESC' rs = db.session.execute(text(sql), {'requestid': requestid, 'requestversion': requestversion}) for row in rs: @@ -77,7 +77,7 @@ def createdocumentversion(cls,requestid,requestversion, document, userid): @classmethod def deActivaterawdocumentsversion(cls, documentid, currentversion, userid)->DefaultMethodResult: - db.session.query(FOIRawRequestDocument).filter(FOIRawRequestDocument.foidocumentid == documentid, FOIRawRequestDocument.version != currentversion).update({"isactive": False, "updated_at": datetime.now(),"updatedby": userid}, synchronize_session=False) + db.session.query(FOIRawRequestDocument).filter(FOIRawRequestDocument.foidocumentid == documentid, FOIRawRequestDocument.version == currentversion).update({"isactive": False, "updated_at": datetime.now(),"updatedby": userid}, synchronize_session=False) db.session.commit() return DefaultMethodResult(True,'Raw Request Document Updated',documentid) diff --git a/request-management-api/request_api/models/FOIRawRequests.py b/request-management-api/request_api/models/FOIRawRequests.py index 85823f4e3..257af5811 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): # 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" + 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() @@ -981,6 +1000,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/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]) ) ) ) diff --git a/request-management-api/request_api/models/FOIRequestRecords.py b/request-management-api/request_api/models/FOIRequestRecords.py index 2e8d99e78..a0ad9eebb 100644 --- a/request-management-api/request_api/models/FOIRequestRecords.py +++ b/request-management-api/request_api/models/FOIRequestRecords.py @@ -126,7 +126,25 @@ def getrecordsbyid(cls, recordids): finally: db.session.close() return records - + + @classmethod + def get_all_records_by_divisionid(cls, divisionid): + records = [] + 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' 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"]}) + 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/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/models/ProgramAreaDivisions.py b/request-management-api/request_api/models/ProgramAreaDivisions.py index 2952d2a93..16c535135 100644 --- a/request-management-api/request_api/models/ProgramAreaDivisions.py +++ b/request-management-api/request_api/models/ProgramAreaDivisions.py @@ -3,7 +3,7 @@ from .default_method_result import DefaultMethodResult from sqlalchemy.orm import relationship,backref from datetime import datetime -from sqlalchemy import text +from sqlalchemy import text,or_ class ProgramAreaDivision(db.Model): __tablename__ = 'ProgramAreaDivisions' @@ -13,6 +13,9 @@ class ProgramAreaDivision(db.Model): name = db.Column(db.String(500), unique=False, nullable=False) isactive = db.Column(db.Boolean, unique=False, nullable=False) sortorder = db.Column(db.Integer, unique=False, nullable=True) + issection = db.Column(db.Boolean, unique=False, nullable=True) + parentid = db.Column(db.Integer, unique=False, nullable=True) + specifictopersonalrequests = db.Column(db.Boolean, unique=False, nullable=True) created_at = db.Column(db.DateTime, default=datetime.now) createdby = db.Column(db.String(120), unique=False, default='system') updated_at = db.Column(db.DateTime, nullable=True) @@ -20,20 +23,58 @@ class ProgramAreaDivision(db.Model): @classmethod 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): + 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): division_schema = ProgramAreaDivisionSchema(many=True) - query = db.session.query(ProgramAreaDivision).filter_by(programareaid=programareaid, isactive=True).order_by(ProgramAreaDivision.name.asc()) + query = db.session.query(ProgramAreaDivision).filter_by(programareaid=programareaid, isactive=True,issection=False,specifictopersonalrequests=True).order_by(ProgramAreaDivision.name.asc()) + return division_schema.dump(query) + + @classmethod + def getpersonalrequestsprogramareasections(cls,programareaid): + division_schema = ProgramAreaDivisionSchema(many=True) + query = db.session.query(ProgramAreaDivision).filter_by(programareaid=programareaid, isactive=True,issection=True,specifictopersonalrequests=True).order_by(ProgramAreaDivision.name.asc()) + return division_schema.dump(query) + + @classmethod + def getpersonalrequestsdivisionsandsections(cls,programareaid): + division_schema = ProgramAreaDivisionSchema(many=True) + query = db.session.query(ProgramAreaDivision).filter_by(programareaid=programareaid, isactive=True,specifictopersonalrequests=True).order_by(ProgramAreaDivision.name.asc()) return division_schema.dump(query) @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) @@ -53,10 +94,14 @@ 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.updatedby:userid, ProgramAreaDivision.updated_at:datetime2.now()}, synchronize_session = False) + ProgramAreaDivision.isactive:True, ProgramAreaDivision.sortorder:sortorder, + 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: @@ -69,8 +114,12 @@ def getdivisionbynameandprogramarea(cls, programareadivision): division = dbquery.filter_by(programareaid=programareadivision["programareaid"], isactive=True, name=programareadivision["name"]) return division_schema.dump(division) - + @classmethod + 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) class ProgramAreaDivisionSchema(ma.Schema): class Meta: - fields = ('divisionid','programareaid', 'name','isactive','sortorder') \ No newline at end of file + fields = ('divisionid','programareaid', 'name','isactive','sortorder','issection','parentid', 'specifictopersonalrequests') \ No newline at end of file diff --git a/request-management-api/request_api/resources/foiadmin.py b/request-management-api/request_api/resources/foiadmin.py index 864157ec4..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 """ @@ -39,7 +40,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,17 +49,17 @@ 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 - except BusinessException as exception: + except KeyError as error: + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @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,11 +71,9 @@ 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 + except KeyError as error: + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -82,7 +81,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,11 +93,9 @@ 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 + except KeyError as error: + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -106,7 +103,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 @@ -115,11 +112,11 @@ 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, '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': 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 e8dad2f91..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/') @@ -57,10 +58,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':verr.messages}, 400 - except KeyError as err: - logging.error(err) - return {'status': False, 'message': EXCEPTION_MESSAGE_BAD_REQUEST}, 400 + 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 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -87,10 +88,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':verr.messages}, 400 - except KeyError as err: - logging.error(err) - return {'status': False, 'message': EXCEPTION_MESSAGE_BAD_REQUEST}, 400 + 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 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -109,7 +110,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': 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 f68db9c50..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') @@ -54,8 +55,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -77,8 +78,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -108,8 +109,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -136,8 +137,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -166,8 +167,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -184,7 +185,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 +201,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 f095f9444..83e0d6ccd 100644 --- a/request-management-api/request_api/resources/foidocument.py +++ b/request-management-api/request_api/resources/foidocument.py @@ -23,14 +23,16 @@ 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') -TRACER = Tracer.get_instance() +TRACER = Tracer.get_instance() +CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " @cors_preflight('GET,OPTIONS') @@ -49,8 +51,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -73,9 +75,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':err.messages}, 400 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + return {'status': False, 'message': str(err)}, 400 + except KeyError as error: + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -97,12 +99,50 @@ 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 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + return {'status': False, 'message': str(err)}, 400 + except KeyError as error: + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(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': str(err)}, 400 + except KeyError as error: + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(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): @@ -120,9 +160,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':err.messages}, 400 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + return {'status': False, 'message': str(err)}, 400 + except KeyError as error: + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -141,7 +181,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': 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 0b2aa80b1..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//') @@ -50,9 +51,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':err.messages}, 500 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + return {'status': 500, 'message': str(err)}, 500 + except KeyError as error: + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -71,9 +72,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':err.messages}, 500 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + return {'status': 500, 'message': str(err)}, 500 + except KeyError as error: + 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 86c9ef0d6..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/') @@ -52,8 +53,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -71,8 +72,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -96,8 +97,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -121,8 +122,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -145,8 +146,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -167,7 +168,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': 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/foiflowmasterdata.py b/request-management-api/request_api/resources/foiflowmasterdata.py index 297632c9a..6525eef82 100644 --- a/request-management-api/request_api/resources/foiflowmasterdata.py +++ b/request-management-api/request_api/resources/foiflowmasterdata.py @@ -71,7 +71,6 @@ def get(): except BusinessException: return "Error happened while accessing applicant categories" , 500 - @cors_preflight('GET,OPTIONS') @API.route('/foiflow/programareas') class FOIFlowProgramAreas(Resource): @@ -167,6 +166,7 @@ def get(): @cors_preflight('GET,OPTIONS') @API.route('/foiflow/divisions/') +@API.route('/foiflow/divisions///') class FOIFlowDivisions(Resource): """Retrieves all active divisions for the passed in gov code . """ @@ -178,14 +178,47 @@ class FOIFlowDivisions(Resource): unless=cache_filter, response_filter=response_filter ) - def get(bcgovcode): + def get(bcgovcode,specifictopersonalrequests=None, fetchmode = None): try: - data = divisionstageservice().getdivisionandstages(bcgovcode) + data = None + if(specifictopersonalrequests is not None and specifictopersonalrequests.lower() == 'true'): + match fetchmode: + case 'divisions': + data = divisionstageservice().getpersonalspecificdivisionandstages(bcgovcode) + case 'sections': + data = divisionstageservice().getpersonalspecificprogramareasections(bcgovcode) + case 'divisionsandsections': + data = divisionstageservice().getpersonalspecificdivisionsandsections(bcgovcode) + case _: + data = divisionstageservice().getpersonalspecificdivisionandstages(bcgovcode) + else: + data = divisionstageservice().getdivisionandstages(bcgovcode) jsondata = json.dumps(data) return jsondata , 200 - except BusinessException: - return "Error happened while accessing divisions" , 500 + except Exception as exception: + return {'status': False, 'message': str(type(exception).__name__)}, 400 +@cors_preflight('GET,OPTIONS') +@API.route('/foiflow/divisions//all') +class FOIFlowDivisionTags(Resource): + """Retrieves all active divisions for the passed in gov code . + """ + @staticmethod + @TRACER.trace() + @cross_origin(origins=allowedorigins()) + @auth.require + @request_api.cache.cached( + unless=cache_filter, + response_filter=response_filter + ) + def get(bcgovcode): + try: + data = divisionstageservice().getalldivisionsandsections(bcgovcode) + jsondata = json.dumps(data) + return jsondata , 200 + except Exception as exception: + return {'status': False, 'message': str(type(exception).__name__)}, 400 + @cors_preflight('GET,OPTIONS') @API.route('/foiflow/closereasons') class FOIFlowCloseReasons(Resource): diff --git a/request-management-api/request_api/resources/foinotification.py b/request-management-api/request_api/resources/foinotification.py index a95fef873..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') @@ -47,8 +48,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -72,8 +73,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -92,8 +93,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': 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(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': 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 d5e7be7b5..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/') @@ -48,8 +49,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -69,8 +70,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -88,8 +89,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': 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 e53181c80..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/') @@ -46,10 +47,10 @@ 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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 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/') @@ -69,8 +70,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -91,8 +92,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -112,8 +113,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -134,8 +135,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -157,8 +158,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -177,8 +178,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -198,15 +199,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(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 except Exception as error: print("Exception error == ", error) - return {'status': False, 'message':error.message}, 500 + return {'status': False, 'message': str(error)}, 500 @cors_preflight('GET,OPTIONS') @API.route('/foirecord//ministryrequest///recrodschanged') @@ -224,12 +225,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(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 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': 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 4ccb5aff3..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') @@ -72,8 +73,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -117,9 +118,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':err.messages}, 400 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + return {'status': False, 'message': str(err)}, 400 + except KeyError as error: + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -146,9 +147,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':err.messages}, 400 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + return {'status': False, 'message': str(err)}, 400 + except KeyError as error: + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -179,16 +180,16 @@ 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)) + 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 else: 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 + return {'status': False, 'message': str(err)}, 400 + except KeyError as error: + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -224,7 +225,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': str(err)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -246,8 +247,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -271,7 +272,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 +288,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 70a16e0f3..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/') @@ -48,9 +49,9 @@ def get(requestid): result = watcherservice().getrawrequestwatchers(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 + return {'status': 500, 'message': str(err)}, 500 + except KeyError as error: + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -72,8 +73,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -91,8 +92,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -110,8 +111,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -133,8 +134,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': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 @@ -152,7 +153,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': 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 ccb2a0fa7..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') @@ -46,9 +47,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':err.messages}, 500 - except KeyError as err: - return {'status': False, 'message':err.messages}, 400 + return {'status': 500, 'message': str(err)}, 500 + except KeyError as error: + 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/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'): diff --git a/request-management-api/request_api/schemas/foidocument.py b/request-management-api/request_api/schemas/foidocument.py index 622b12f8a..16400d16b 100644 --- a/request-management-api/request_api/schemas/foidocument.py +++ b/request-management-api/request_api/schemas/foidocument.py @@ -16,6 +16,13 @@ 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) + class ReplaceDocumentSchema(Schema): class Meta: # pylint: disable=too-few-public-methods """Exclude unknown fields in the deserialized output.""" diff --git a/request-management-api/request_api/schemas/foiprogramareadivision.py b/request-management-api/request_api/schemas/foiprogramareadivision.py index 4137ec13c..659d3417f 100644 --- a/request-management-api/request_api/schemas/foiprogramareadivision.py +++ b/request-management-api/request_api/schemas/foiprogramareadivision.py @@ -1,4 +1,4 @@ -from marshmallow import EXCLUDE, Schema, fields +from marshmallow import EXCLUDE, Schema, fields, validates_schema, ValidationError class FOIProgramAreaDivisionSchema(Schema): class Meta: # pylint: disable=too-few-public-methods @@ -9,3 +9,6 @@ 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) \ No newline at end of file diff --git a/request-management-api/request_api/schemas/foirequestwrapper.py b/request-management-api/request_api/schemas/foirequestwrapper.py index bf2990df6..efdb027ac 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 @@ -144,6 +147,15 @@ class Meta: # pylint: disable=too-few-public-methods eApproval = fields.Str(data_key="eApproval",allow_none=True, validate=[validate.Length(max=12, error=MAX_EXCEPTION_MESSAGE)]) divisionReceivedDate = fields.Str(data_key="divisionReceivedDate",allow_none=True) +class CreateMinistrySignOffApprovalSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + approvername = fields.Str(data_key="approverName", allow_none=False) + approvertitle = fields.Str(data_key="approverTitle", allow_none=False) + approveddate = fields.Str(data_key="approvedDate", allow_none=False) + class FOIRequestMinistrySchema(Schema): @@ -163,4 +175,5 @@ class Meta: # pylint: disable=too-few-public-methods assignedToLastName = fields.Str(data_key="assignedToLastName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) assignedministrypersonFirstName = fields.Str(data_key="assignedministrypersonFirstName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) assignedministrypersonMiddleName = fields.Str(data_key="assignedministrypersonMiddleName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) - assignedministrypersonLastName = fields.Str(data_key="assignedministrypersonLastName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) \ No newline at end of file + assignedministrypersonLastName = fields.Str(data_key="assignedministrypersonLastName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) + ministrysignoffapproval = fields.Nested(CreateMinistrySignOffApprovalSchema, data_key="ministrysignoffapproval", allow_none=True) \ No newline at end of file diff --git a/request-management-api/request_api/services/divisionstageservice.py b/request-management-api/request_api/services/divisionstageservice.py index 8ddfa5dec..c62a8637c 100644 --- a/request-management-api/request_api/services/divisionstageservice.py +++ b/request-management-api/request_api/services/divisionstageservice.py @@ -9,10 +9,52 @@ 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"] 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'])}) + divisionstages.append({"divisionid": division['divisionid'], "name": self.escapestr(division['name']),"sortorder": division['sortorder'],"issection":division['issection']}) return {"divisions": divisionstages, "stages": self.getstages()} + + def getalldivisionsandsections(self, bcgovcode): + divisionstages = [] + programarea = ProgramArea.getprogramarea(bcgovcode) + divisions = ProgramAreaDivision.getallprogramareatags(programarea['programareaid']) + 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()} + + def getpersonalspecificdivisionandstages(self, bcgovcode): + divisionstages = [] + programarea = ProgramArea.getprogramarea(bcgovcode) + divisions = ProgramAreaDivision.getpersonalspecificprogramareadivisions(programarea['programareaid']) + 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()} + + def getpersonalspecificprogramareasections(self, bcgovcode): + programareasections = [] + programarea = ProgramArea.getprogramarea(bcgovcode) + _sections = ProgramAreaDivision.getpersonalrequestsprogramareasections(programarea['programareaid']) + _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} + + def getpersonalspecificdivisionsandsections(self, bcgovcode): + programareasections = [] + programarea = ProgramArea.getprogramarea(bcgovcode) + divisions = ProgramAreaDivision.getpersonalspecificprogramareadivisions(programarea['programareaid']) + sections = ProgramAreaDivision.getpersonalrequestsdivisionsandsections(programarea['programareaid']) + 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 = [] + for _section in sections: + if(_section['parentid'] == divisionid): + _sections.append(_section) + programareasections.append({"divisionid": divisionid, "name": self.escapestr(_division['name']),"sortorder":_division['sortorder'],"issection":_division['issection'],"sections":_sections}) + return {"divisions": programareasections} def getstages(self): activestages = [] diff --git a/request-management-api/request_api/services/documentservice.py b/request-management-api/request_api/services/documentservice.py index 54b2dbf69..3fd2c72d1 100644 --- a/request-management-api/request_api/services/documentservice.py +++ b/request-management-api/request_api/services/documentservice.py @@ -1,5 +1,6 @@ from os import stat +import os from re import VERBOSE from request_api.models.FOIMinistryRequestDocuments import FOIMinistryRequestDocument from request_api.models.FOIRawRequestDocuments import FOIRawRequestDocument @@ -9,6 +10,7 @@ from request_api.services.external.storageservice import storageservice from request_api.models.FOIApplicantCorrespondenceAttachments import FOIApplicantCorrespondenceAttachment from request_api.utils.enums import RequestType +import logging import json import base64 @@ -32,11 +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"] = "" - if metaobj["requesttype"].lower() == RequestType.GENERAL.value.lower() and document["category"] == 'applicant': - documents.remove(document) + if document["category"] != "applicant": + filtereddocuments.append(document) + return filtereddocuments return documents def createrequestdocument(self, requestid, documentschema, userid, requesttype): @@ -51,6 +55,27 @@ 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 = 'https://' + os.getenv("OSS_S3_HOST") + 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 = baseurl + '/' + bucket + '/' + filename + try: + moveresponse = storageservice().copy_file(source, bucket, filename) + if moveresponse['ResponseMetadata']['HTTPStatusCode'] == 200: + return {"status": "success", 'documentpath': newdocumentpath} + else: + raise Exception(moveresponse) + except Exception as ex: + logging.exception(ex) + return {"status": "error"} + def deleterequestdocument(self, requestid, documentid, userid, requesttype): documentschema = {'isactive':False} return self.createrequestdocumentversion(requestid, documentid, documentschema, userid, requesttype) @@ -76,8 +101,8 @@ def createministrydocumentversion(self, ministryrequestid, documentid, documents version = self.__getversionforrequest(ministryrequestid, "ministryrequest") document = FOIMinistryRequestDocument.getdocument(documentid) if document: - FOIMinistryRequestDocument.deActivateministrydocumentsversion(documentid, document['version']+1, userid) - return FOIMinistryRequestDocument.createdocumentversion(ministryrequestid, version, self.__copydocumentproperties(document,documentschema,document['version']), userid) + FOIMinistryRequestDocument.deActivateministrydocumentsversion(documentid, document['version'], userid) + return FOIMinistryRequestDocument.createdocumentversion(ministryrequestid, version, self.__copydocumentproperties(document,documentschema,document['version']), userid) elif isinstance(documentschema, list): return FOIMinistryRequestDocument.createdocuments(ministryrequestid, version, documentschema, userid) else: @@ -87,7 +112,7 @@ def createministrydocumentversion(self, ministryrequestid, documentid, documents def createrawdocumentversion(self, requestid, documentid, documentschema, userid): version = self.__getversionforrequest(requestid, "rawrequest") document = FOIRawRequestDocument.getdocument(documentid) - FOIRawRequestDocument.deActivaterawdocumentsversion(documentid, document['version']+1, userid) + FOIRawRequestDocument.deActivaterawdocumentsversion(documentid, document['version'], userid) return FOIRawRequestDocument.createdocumentversion(requestid, version, self.__copydocumentproperties(document,documentschema,document['version']), userid) def createrawrequestdocumentversion(self, requestid): 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..ef9d64125 100644 --- a/request-management-api/request_api/services/email/templates/templateservice.py +++ b/request-management-api/request_api/services/email/templates/templateservice.py @@ -83,6 +83,7 @@ def __generatetemplate(self, dynamictemplatevalues, emailtemplatehtml, title): if dynamictemplatevalues["assignedTo"] == None: dynamictemplatevalues["assignedToFirstName"] = "" dynamictemplatevalues["assignedToLastName"] = "" + dynamictemplatevalues["assignedGroupEmail"] = "" contenttemplate = Template(emailtemplatehtml) content = contenttemplate.render(dynamictemplatevalues) 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 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..3a76ad2b7 --- /dev/null +++ b/request-management-api/request_api/services/events/section5pending.py @@ -0,0 +1,60 @@ +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()) + section5pendings = FOIRawRequest.getlatestsection5pendings() + for entry in section5pendings: + duedate = self.formatduedate(entry['duedate']) + message = None + if _today == duedate: + 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/events/state.py b/request-management-api/request_api/services/events/state.py index f12a4e5e2..f317f6e7c 100644 --- a/request-management-api/request_api/services/events/state.py +++ b/request-management-api/request_api/services/events/state.py @@ -17,7 +17,6 @@ class stateevent: def createstatetransitionevent(self, requestid, requesttype, userid, username): state = self.__haschanged(requestid, requesttype) if state is not None: - #_commentresponse = self.__createcomment(requestid, state, requesttype, userid, username) _commentresponse = self.__createcommentwrapper(requestid, state, requesttype, userid, username) _notificationresponse = self.__createnotification(requestid, state, requesttype, userid) _cfrresponse = self.__createcfrentry(state, requestid, userid) @@ -63,6 +62,9 @@ def __createnotification(self, requestid, state, requesttype, userid): foirequest = notificationservice().getrequest(requestid, requesttype) _notificationtype = "Group Members" if foirequest['assignedministryperson'] is None else "State" 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': @@ -72,7 +74,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,15 +84,16 @@ def __createnotification(self, requestid, state, requesttype, userid): return DefaultMethodResult(True,'Notification added',requestid) return DefaultMethodResult(True,'No change',requestid) - def __preparenotification(self, state): 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): - comment = {"comment": self.__commentmessage(state, username)} + comment = {"comment": self.__commentmessage(state, username, requesttype, requestid)} if requesttype == "ministryrequest": comment['ministryrequestid']= requestid else: @@ -100,12 +103,20 @@ def __preparecomment(self, requestid, state,requesttype, username): def __formatstate(self, state): return "Open" if state == "Archived" else state - def __commentmessage(self, state, username): - 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): 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, '')): @@ -114,5 +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 - + 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 f6763c474..9610cb5a6 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 @@ -59,8 +60,10 @@ 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() + section5pendingresponse = section5pendingevent().createdueevent() + if cfreventresponse.success == False or legislativeeventresponse.success == False or divisioneventresponse.success == False or paymentremindereventresponse.success == False or section5pendingresponse == False: + current_app.logger.error("FOI Notification failed for reminder event response=%s ; legislative response=%s ; division response=%s ; payment response=%s ; section5pending response=%s" % (cfreventresponse.message, legislativeeventresponse.message, divisioneventresponse.message, paymentremindereventresponse.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/external/keycloakadminservice.py b/request-management-api/request_api/services/external/keycloakadminservice.py index 31e8b1e20..5ec6af21e 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 @@ -81,6 +83,50 @@ def getgroupsandmembers(self, allowedgroups = None): 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 + + # 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/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'] 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/requestservicecreate.py b/request-management-api/request_api/services/foirequest/requestservicecreate.py index daa9f1333..6d4b2142f 100644 --- a/request-management-api/request_api/services/foirequest/requestservicecreate.py +++ b/request-management-api/request_api/services/foirequest/requestservicecreate.py @@ -66,7 +66,7 @@ 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"]) diff --git a/request-management-api/request_api/services/foirequest/requestservicegetter.py b/request-management-api/request_api/services/foirequest/requestservicegetter.py index 5d48d28c8..e5ae2c249 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. @@ -54,8 +55,8 @@ def getrequest(self,foirequestid,foiministryrequestid): additionalpersonalinfo.update(additionalpersonalinfodetails) baserequestinfo['additionalPersonalInfo'] = additionalpersonalinfo - originalduedate = FOIMinistryRequest.getrequestoriginalduedate(foiministryrequestid) - baserequestinfo['originalDueDate'] = originalduedate.strftime(self.__genericdateformat()) + 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 @@ -142,6 +143,7 @@ def __preparebaseinfo(self,request,foiministryrequestid,requestministry,requestm 'receivedmodeid':request['receivedmode.receivedmodeid'], 'receivedMode':request['receivedmode.name'], 'assignedGroup': requestministry["assignedgroup"], + 'assignedGroupEmail': KeycloakAdminService().processgroupEmail(requestministry["assignedgroup"]), 'assignedTo': requestministry["assignedto"], 'idNumber':requestministry["filenumber"], 'axisRequestId': requestministry["axisrequestid"], @@ -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 parse(requestministry['duedate']).strftime(self.__genericdateformat()), '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..f35af8e74 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'] @@ -73,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 = requestdict["ministrysignoffapproval"] foiministryrequest.requeststatusid = requestdict['requeststatusid'] foiministryrequest.programareaid = requestdict['programareaid'] foiministryrequest.createdby = userid @@ -112,7 +115,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): 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): 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 diff --git a/request-management-api/request_api/services/programareadivisionservice.py b/request-management-api/request_api/services/programareadivisionservice.py index 57dc2fd10..c3b07c505 100644 --- a/request-management-api/request_api/services/programareadivisionservice.py +++ b/request-management-api/request_api/services/programareadivisionservice.py @@ -1,5 +1,7 @@ from request_api.models.ProgramAreaDivisions import ProgramAreaDivision from request_api.services.programareaservice import programareaservice +from request_api.services.recordservice import recordservice +from request_api.models.default_method_result import DefaultMethodResult class programareadivisionservice: @@ -10,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 """ @@ -23,8 +31,18 @@ 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_by_divisionid(divisionid) + 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 getchilddivisions(self, divisionid): + """ Returns all child divisions/sections for a given divisionid + """ + return ProgramAreaDivision.getchilddivisions(divisionid) + def __prepareprogramareas(self, data): """ Join program area name with division on programareaid """ diff --git a/request-management-api/request_api/services/rawrequestservice.py b/request-management-api/request_api/services/rawrequestservice.py index 594932595..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", ex) + logging.error(ex) asyncio.ensure_future(redispubservice.publishrequest(json_data)) return result diff --git a/request-management-api/request_api/services/records/recordservicegetter.py b/request-management-api/request_api/services/records/recordservicegetter.py index 9f335796f..90e15403b 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,173 @@ 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"]) + divisions = ProgramAreaDivision.getallprogramareatags(_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 +191,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 +207,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 diff --git a/request-management-api/request_api/services/recordservice.py b/request-management-api/request_api/services/recordservice.py index 96db2ccf6..eda12999a 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_by_divisionid(self, divisionid): + return FOIRequestRecord.get_all_records_by_divisionid(divisionid) def update(self, requestid, ministryrequestid, requestdata, userid): newrecords = [] diff --git a/request-management-api/request_api/services/workflowservice.py b/request-management-api/request_api/services/workflowservice.py index 14bd0f35a..f4b6f81a0 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 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) diff --git a/request-management-api/request_api/utils/constants.py b/request-management-api/request_api/utils/constants.py index 872e114c7..456e97c90 100644 --- a/request-management-api/request_api/utils/constants.py +++ b/request-management-api/request_api/utils/constants.py @@ -21,6 +21,9 @@ FORMAT_CONTACT_ADDRESS='JSON' BLANK_EXCEPTION_MESSAGE = 'Field cannot be blank' MAX_EXCEPTION_MESSAGE = 'Field exceeds the size limit' +FILE_CONVERSION_FILE_TYPES = '' +DEDUPE_FILE_TYPES = '' +NONREDACTABLE_FILE_TYPES = '' try: response = requests.request( method='GET', diff --git a/request-management-api/request_api/utils/enums.py b/request-management-api/request_api/utils/enums.py index b61187175..f21d807ab 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" @@ -125,6 +125,7 @@ class PaymentEventType(Enum): expired = "EXPIRED" outstandingpaid = "OUTSTANDINGPAID" depositpaid = "DEPOSITPAID" + reminder = "REMINDER" class CommentType(Enum): """Authorization header types.""" 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): 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