From 273068c83e9a556984910c6b35751d3155d16093 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 23 May 2024 15:12:10 -0700 Subject: [PATCH 01/33] initial commit for historical search api --- docker-compose.yml | 123 ++ historical-search-api/LICENSE | 13 + historical-search-api/README.md | 3 + historical-search-api/dockerfile | 25 + historical-search-api/dockerfile.local | 21 + historical-search-api/entrypoint.sh | 3 + historical-search-api/gunicorn_config.py | 25 + historical-search-api/logging.conf | 40 + historical-search-api/manage.py | 59 + historical-search-api/pre-hook-update-db.sh | 4 + .../pre_hook_create_database.py | 30 + historical-search-api/request_api/__init__.py | 128 ++ historical-search-api/request_api/auth.py | 290 ++++ historical-search-api/request_api/config.py | 201 +++ .../request_api/exceptions/__init__.py | 49 + .../request_api/exceptions/errors.py | 41 + .../request_api/models/ApplicantCategories.py | 28 + .../ApplicationCorrespondenceTemplates.py | 63 + .../request_api/models/CFRFeeStatus.py | 41 + .../request_api/models/CFRFormReason.py | 41 + .../request_api/models/CloseReasons.py | 32 + .../request_api/models/CommentTypes.py | 24 + .../request_api/models/ContactTypes.py | 22 + .../request_api/models/DeliveryModes.py | 27 + .../request_api/models/DocumentPathMapper.py | 41 + .../request_api/models/DocumentTemplate.py | 43 + .../request_api/models/DocumentType.py | 41 + .../request_api/models/ExtensionReasons.py | 28 + .../request_api/models/ExtensionStatuses.py | 27 + .../FOIApplicantCorrespondenceAttachments.py | 50 + .../models/FOIApplicantCorrespondences.py | 93 + .../request_api/models/FOIAssignees.py | 44 + .../models/FOIMinistryRequestDivisions.py | 58 + .../models/FOIMinistryRequestDocuments.py | 159 ++ .../models/FOIMinistryRequestSubjectCodes.py | 54 + .../request_api/models/FOIMinistryRequests.py | 1535 +++++++++++++++++ .../models/FOIRawRequestComments.py | 165 ++ .../models/FOIRawRequestDocuments.py | 88 + .../models/FOIRawRequestNotificationUsers.py | 270 +++ .../models/FOIRawRequestNotifications.py | 119 ++ .../models/FOIRawRequestWatchers.py | 106 ++ .../request_api/models/FOIRawRequests.py | 1161 +++++++++++++ .../models/FOIRequestApplicantMappings.py | 48 + .../models/FOIRequestApplicants.py | 67 + .../request_api/models/FOIRequestCFRFees.py | 110 ++ .../request_api/models/FOIRequestComments.py | 169 ++ .../models/FOIRequestContactInformation.py | 51 + .../FOIRequestExtensionDocumentMappings.py | 60 + .../models/FOIRequestExtensions.py | 189 ++ .../models/FOIRequestNotificationDashboard.py | 41 + .../models/FOIRequestNotificationUsers.py | 338 ++++ .../models/FOIRequestNotifications.py | 189 ++ .../request_api/models/FOIRequestOIPC.py | 63 + .../request_api/models/FOIRequestPayments.py | 85 + .../models/FOIRequestPersonalAttributes.py | 50 + .../request_api/models/FOIRequestRecords.py | 183 ++ .../request_api/models/FOIRequestStatus.py | 34 + .../request_api/models/FOIRequestTeams.py | 98 ++ .../request_api/models/FOIRequestWatchers.py | 138 ++ .../request_api/models/FOIRequests.py | 126 ++ .../models/FOIRestrictedMinistryRequests.py | 56 + .../request_api/models/FOIUsers.py | 57 + .../request_api/models/FeeCode.py | 44 + .../request_api/models/NotificationTypes.py | 33 + .../models/NotificationUserTypes.py | 42 + .../request_api/models/OIPCInquiryOutcomes.py | 19 + .../request_api/models/OIPCOutcomes.py | 19 + .../request_api/models/OIPCReasons.py | 20 + .../request_api/models/OIPCReviewTypes.py | 19 + .../models/OIPCReviewTypesReasons.py | 43 + .../request_api/models/OIPCStatuses.py | 19 + .../request_api/models/OperatingTeams.py | 47 + .../request_api/models/Payment.py | 58 + .../models/PersonalInformationAttributes.py | 27 + .../models/ProgramAreaDivisionStages.py | 23 + .../models/ProgramAreaDivisions.py | 125 ++ .../request_api/models/ProgramAreas.py | 45 + .../request_api/models/ReceivedModes.py | 27 + .../request_api/models/RequestorType.py | 27 + .../request_api/models/RevenueAccount.py | 23 + .../request_api/models/SubjectCodes.py | 32 + .../request_api/models/UnopenedReport.py | 23 + .../request_api/models/__init__.py | 63 + .../request_api/models/db.py | 28 + .../models/default_method_result.py | 10 + .../request_api/models/factRequestDetails.py | 84 + .../masterdataimport/applicantcategories.sql | 9 + .../models/masterdataimport/deliverymodes.sql | 2 + .../models/masterdataimport/programareas.sql | 37 + .../models/masterdataimport/receivedmodes.sql | 4 + .../models/samplequeries/businessteamdata.sql | 38 + .../models/samplequeries/findcolumnvalue.sql | 21 + .../models/samplequeries/wfmigration.sql | 406 +++++ .../models/views/FOINotifications.py | 21 + .../models/views/FOIRawRequests.py | 23 + .../request_api/models/views/FOIRequests.py | 24 + .../request_api/models/views/__init__.py | 16 + .../request_api/models/views/db.py | 28 + .../request_api/resources/__init__.py | 50 + .../request_api/resources/apihelper.py | 29 + .../request_api/resources/meta.py | 35 + .../request_api/resources/ops.py | 43 + .../request_api/resources/request.py | 111 ++ .../request_api/schemas/__init__.py | 15 + .../request_api/schemas/base_schema.py | 47 + .../request_api/schemas/basecode_type.py | 30 + .../request_api/schemas/external/bpmschema.py | 35 + .../schemas/foiapplicantcorrespondencelog.py | 28 + .../request_api/schemas/foiassignee.py | 20 + .../request_api/schemas/foiaxissync.py | 18 + .../request_api/schemas/foicfrfee.py | 69 + .../request_api/schemas/foicomment.py | 50 + .../request_api/schemas/foidocument.py | 50 + .../request_api/schemas/foiemail.py | 13 + .../request_api/schemas/foiextension.py | 40 + .../request_api/schemas/foipayment.py | 19 + .../schemas/foiprogramareadivision.py | 14 + .../request_api/schemas/foirecord.py | 166 ++ .../request_api/schemas/foirequest.py | 87 + .../schemas/foirequestsformslist.py | 15 + .../request_api/schemas/foirequestwrapper.py | 215 +++ .../request_api/schemas/foiwatcher.py | 35 + .../request_api/schemas/schemas/error.json | 28 + .../schemas/schemas/exception.json | 32 + .../schemas/schemas/rawrequest.json | 220 +++ .../request_api/schemas/schemas/sample.json | 28 + .../request_api/schemas/utils.py | 118 ++ .../request_api/services/__init__.py | 16 + .../services/applicantcategoryservice.py | 8 + .../applicantcorrespondencelog.py | 98 ++ .../request_api/services/assigneeservice.py | 42 + .../request_api/services/auditservice.py | 79 + .../request_api/services/cacheservice.py | 62 + .../request_api/services/cdogs_api_service.py | 127 ++ .../request_api/services/cfrfeeservice.py | 114 ++ .../services/cfrfeestatusservice.py | 13 + .../services/cfrformreasonservice.py | 13 + .../services/closereasonservice.py | 8 + .../request_api/services/commentservice.py | 185 ++ .../services/commons/duecalculator.py | 109 ++ .../services/dashboardeventservice.py | 69 + .../request_api/services/dashboardservice.py | 226 +++ .../services/deliverymodeservice.py | 8 + .../services/divisionstageservice.py | 68 + .../services/document_generation_service.py | 70 + .../request_api/services/documentservice.py | 211 +++ .../services/email/inboxservice.py | 59 + .../services/email/senderservice.py | 85 + .../email/templates/templateconfig.py | 93 + .../email/templates/templatefilters.py | 11 + .../email/templates/templateservice.py | 97 ++ .../email/templatevaluebuilderservice.py | 33 + .../request_api/services/emailservice.py | 102 ++ .../request_api/services/events/__init__.py | 15 + .../request_api/services/events/assignment.py | 98 ++ .../request_api/services/events/cfrdate.py | 76 + .../request_api/services/events/cfrfeeform.py | 100 ++ .../request_api/services/events/comment.py | 105 ++ .../request_api/services/events/division.py | 136 ++ .../services/events/divisiondate.py | 84 + .../request_api/services/events/email.py | 60 + .../request_api/services/events/extension.py | 275 +++ .../services/events/legislativedate.py | 78 + .../request_api/services/events/oipc.py | 166 ++ .../services/events/oipcduedate.py | 72 + .../request_api/services/events/payment.py | 130 ++ .../services/events/section5pending.py | 68 + .../request_api/services/events/state.py | 135 ++ .../request_api/services/events/watcher.py | 72 + .../request_api/services/eventservice.py | 144 ++ .../services/extensionreasonservice.py | 13 + .../request_api/services/extensionservice.py | 416 +++++ .../services/external/axissyncservice.py | 76 + .../services/external/bpmservice.py | 196 +++ .../services/external/camundaservice.py | 38 + .../services/external/eventqueueservice.py | 24 + .../services/external/keycloakadminservice.py | 204 +++ .../services/external/storageservice.py | 255 +++ .../request_api/services/fee_service.py | 239 +++ .../foirequest/requestservicebuilder.py | 197 +++ .../foirequest/requestserviceconfigurator.py | 84 + .../foirequest/requestservicecreate.py | 183 ++ .../foirequest/requestservicegetter.py | 338 ++++ .../requestserviceministrybuilder.py | 354 ++++ .../foirequest/requestserviceupdate.py | 26 + .../request_api/services/hash_service.py | 49 + .../services/historicalrequestservice.py | 14 + .../notifications/notificationconfig.py | 69 + .../notifications/notificationuser.py | 133 ++ .../services/notificationservice.py | 352 ++++ .../request_api/services/oipcservice.py | 27 + .../request_api/services/paymentservice.py | 156 ++ .../services/programareadivisionservice.py | 58 + .../services/programareaservice.py | 16 + .../rawrequest/rawrequestservicegetter.py | 230 +++ .../request_api/services/rawrequestservice.py | 153 ++ .../services/receivedmodeservice.py | 8 + .../services/records/recordservicebase.py | 45 + .../services/records/recordservicegetter.py | 272 +++ .../request_api/services/recordservice.py | 344 ++++ .../request_api/services/requestservice.py | 333 ++++ .../request_api/services/reset.py | 32 + .../request_api/services/rest_service.py | 183 ++ .../services/subjectcodeservice.py | 50 + .../services/unopenedreportservice.py | 165 ++ .../request_api/services/userservice.py | 43 + .../services/validators/__init__.py | 14 + .../services/validators/validator_response.py | 36 + .../request_api/services/watcherservice.py | 91 + .../request_api/services/workflowservice.py | 322 ++++ historical-search-api/request_api/status.py | 97 ++ historical-search-api/request_api/tracer.py | 40 + .../request_api/utils/__init__.py | 14 + .../request_api/utils/cache.py | 69 + .../utils/commons/datetimehandler.py | 43 + .../request_api/utils/constants.py | 42 + .../request_api/utils/custom_sql.py | 26 + .../request_api/utils/enums.py | 175 ++ .../request_api/utils/handlers.py | 26 + .../request_api/utils/passcode.py | 32 + .../request_api/utils/redispublisher.py | 36 + .../request_api/utils/redissubscriber.py | 31 + .../request_api/utils/roles.py | 58 + .../request_api/utils/run_version.py | 29 + .../request_api/utils/util.py | 142 ++ .../request_api/utils/util_logging.py | 49 + historical-search-api/request_api/version.py | 25 + historical-search-api/requirements.txt | 81 + historical-search-api/sample.env | 101 ++ historical-search-api/tests/__init__.py | 0 historical-search-api/tests/conftest.py | 112 ++ .../tests/restapi/__init__.py | 0 .../tests/restapi/test_fee_api.py | 125 ++ .../tests/restapi/test_foiassignees_api.py | 55 + .../tests/restapi/test_foiaudit_api.py | 66 + .../tests/restapi/test_foicfrfee_api.py | 139 ++ .../tests/restapi/test_foicomment_api.py | 168 ++ .../tests/restapi/test_foidocument_api.py | 360 ++++ .../tests/restapi/test_foiextension_api.py | 159 ++ .../tests/restapi/test_foimasterdata_api.py | 58 + .../tests/restapi/test_foinotification_api.py | 159 ++ .../tests/restapi/test_foirecord_api.py | 82 + .../tests/restapi/test_foirequest_api.py | 425 +++++ .../restapi/test_foisystemcomment_api.py | 94 + .../tests/restapi/test_foiwatcher_api.py | 78 + .../tests/restapi/test_rawrequest_api.py | 109 ++ .../foirequest-extension-oipc.json | 6 + .../foirequest-general-CFR.json | 62 + .../foirequest-general-update.json | 48 + .../samplerequestjson/foirequest-general.json | 51 + .../foirequest-ministry-general-update.json | 42 + .../foirequest-personal.json | 82 + .../tests/samplerequestjson/rawrequest.json | 63 + .../samplerequestjson/s3storagerequest.json | 13 + .../tests/services/__init__.py | 0 .../tests/services/test_dashboard_services.py | 14 + .../services/test_masterdata_services.py | 35 + .../services/test_rawrequest_services.py | 25 + .../test_requestextension_services.py | 29 + historical-search-api/wsgi.py | 76 + 260 files changed, 24265 insertions(+) create mode 100644 historical-search-api/LICENSE create mode 100644 historical-search-api/README.md create mode 100644 historical-search-api/dockerfile create mode 100644 historical-search-api/dockerfile.local create mode 100644 historical-search-api/entrypoint.sh create mode 100644 historical-search-api/gunicorn_config.py create mode 100644 historical-search-api/logging.conf create mode 100644 historical-search-api/manage.py create mode 100644 historical-search-api/pre-hook-update-db.sh create mode 100644 historical-search-api/pre_hook_create_database.py create mode 100644 historical-search-api/request_api/__init__.py create mode 100644 historical-search-api/request_api/auth.py create mode 100644 historical-search-api/request_api/config.py create mode 100644 historical-search-api/request_api/exceptions/__init__.py create mode 100644 historical-search-api/request_api/exceptions/errors.py create mode 100644 historical-search-api/request_api/models/ApplicantCategories.py create mode 100644 historical-search-api/request_api/models/ApplicationCorrespondenceTemplates.py create mode 100644 historical-search-api/request_api/models/CFRFeeStatus.py create mode 100644 historical-search-api/request_api/models/CFRFormReason.py create mode 100644 historical-search-api/request_api/models/CloseReasons.py create mode 100644 historical-search-api/request_api/models/CommentTypes.py create mode 100644 historical-search-api/request_api/models/ContactTypes.py create mode 100644 historical-search-api/request_api/models/DeliveryModes.py create mode 100644 historical-search-api/request_api/models/DocumentPathMapper.py create mode 100644 historical-search-api/request_api/models/DocumentTemplate.py create mode 100644 historical-search-api/request_api/models/DocumentType.py create mode 100644 historical-search-api/request_api/models/ExtensionReasons.py create mode 100644 historical-search-api/request_api/models/ExtensionStatuses.py create mode 100644 historical-search-api/request_api/models/FOIApplicantCorrespondenceAttachments.py create mode 100644 historical-search-api/request_api/models/FOIApplicantCorrespondences.py create mode 100644 historical-search-api/request_api/models/FOIAssignees.py create mode 100644 historical-search-api/request_api/models/FOIMinistryRequestDivisions.py create mode 100644 historical-search-api/request_api/models/FOIMinistryRequestDocuments.py create mode 100644 historical-search-api/request_api/models/FOIMinistryRequestSubjectCodes.py create mode 100644 historical-search-api/request_api/models/FOIMinistryRequests.py create mode 100644 historical-search-api/request_api/models/FOIRawRequestComments.py create mode 100644 historical-search-api/request_api/models/FOIRawRequestDocuments.py create mode 100644 historical-search-api/request_api/models/FOIRawRequestNotificationUsers.py create mode 100644 historical-search-api/request_api/models/FOIRawRequestNotifications.py create mode 100644 historical-search-api/request_api/models/FOIRawRequestWatchers.py create mode 100644 historical-search-api/request_api/models/FOIRawRequests.py create mode 100644 historical-search-api/request_api/models/FOIRequestApplicantMappings.py create mode 100644 historical-search-api/request_api/models/FOIRequestApplicants.py create mode 100644 historical-search-api/request_api/models/FOIRequestCFRFees.py create mode 100644 historical-search-api/request_api/models/FOIRequestComments.py create mode 100644 historical-search-api/request_api/models/FOIRequestContactInformation.py create mode 100644 historical-search-api/request_api/models/FOIRequestExtensionDocumentMappings.py create mode 100644 historical-search-api/request_api/models/FOIRequestExtensions.py create mode 100644 historical-search-api/request_api/models/FOIRequestNotificationDashboard.py create mode 100644 historical-search-api/request_api/models/FOIRequestNotificationUsers.py create mode 100644 historical-search-api/request_api/models/FOIRequestNotifications.py create mode 100644 historical-search-api/request_api/models/FOIRequestOIPC.py create mode 100644 historical-search-api/request_api/models/FOIRequestPayments.py create mode 100644 historical-search-api/request_api/models/FOIRequestPersonalAttributes.py create mode 100644 historical-search-api/request_api/models/FOIRequestRecords.py create mode 100644 historical-search-api/request_api/models/FOIRequestStatus.py create mode 100644 historical-search-api/request_api/models/FOIRequestTeams.py create mode 100644 historical-search-api/request_api/models/FOIRequestWatchers.py create mode 100644 historical-search-api/request_api/models/FOIRequests.py create mode 100644 historical-search-api/request_api/models/FOIRestrictedMinistryRequests.py create mode 100644 historical-search-api/request_api/models/FOIUsers.py create mode 100644 historical-search-api/request_api/models/FeeCode.py create mode 100644 historical-search-api/request_api/models/NotificationTypes.py create mode 100644 historical-search-api/request_api/models/NotificationUserTypes.py create mode 100644 historical-search-api/request_api/models/OIPCInquiryOutcomes.py create mode 100644 historical-search-api/request_api/models/OIPCOutcomes.py create mode 100644 historical-search-api/request_api/models/OIPCReasons.py create mode 100644 historical-search-api/request_api/models/OIPCReviewTypes.py create mode 100644 historical-search-api/request_api/models/OIPCReviewTypesReasons.py create mode 100644 historical-search-api/request_api/models/OIPCStatuses.py create mode 100644 historical-search-api/request_api/models/OperatingTeams.py create mode 100644 historical-search-api/request_api/models/Payment.py create mode 100644 historical-search-api/request_api/models/PersonalInformationAttributes.py create mode 100644 historical-search-api/request_api/models/ProgramAreaDivisionStages.py create mode 100644 historical-search-api/request_api/models/ProgramAreaDivisions.py create mode 100644 historical-search-api/request_api/models/ProgramAreas.py create mode 100644 historical-search-api/request_api/models/ReceivedModes.py create mode 100644 historical-search-api/request_api/models/RequestorType.py create mode 100644 historical-search-api/request_api/models/RevenueAccount.py create mode 100644 historical-search-api/request_api/models/SubjectCodes.py create mode 100644 historical-search-api/request_api/models/UnopenedReport.py create mode 100644 historical-search-api/request_api/models/__init__.py create mode 100644 historical-search-api/request_api/models/db.py create mode 100644 historical-search-api/request_api/models/default_method_result.py create mode 100644 historical-search-api/request_api/models/factRequestDetails.py create mode 100644 historical-search-api/request_api/models/masterdataimport/applicantcategories.sql create mode 100644 historical-search-api/request_api/models/masterdataimport/deliverymodes.sql create mode 100644 historical-search-api/request_api/models/masterdataimport/programareas.sql create mode 100644 historical-search-api/request_api/models/masterdataimport/receivedmodes.sql create mode 100644 historical-search-api/request_api/models/samplequeries/businessteamdata.sql create mode 100644 historical-search-api/request_api/models/samplequeries/findcolumnvalue.sql create mode 100644 historical-search-api/request_api/models/samplequeries/wfmigration.sql create mode 100644 historical-search-api/request_api/models/views/FOINotifications.py create mode 100644 historical-search-api/request_api/models/views/FOIRawRequests.py create mode 100644 historical-search-api/request_api/models/views/FOIRequests.py create mode 100644 historical-search-api/request_api/models/views/__init__.py create mode 100644 historical-search-api/request_api/models/views/db.py create mode 100644 historical-search-api/request_api/resources/__init__.py create mode 100644 historical-search-api/request_api/resources/apihelper.py create mode 100644 historical-search-api/request_api/resources/meta.py create mode 100644 historical-search-api/request_api/resources/ops.py create mode 100644 historical-search-api/request_api/resources/request.py create mode 100644 historical-search-api/request_api/schemas/__init__.py create mode 100644 historical-search-api/request_api/schemas/base_schema.py create mode 100644 historical-search-api/request_api/schemas/basecode_type.py create mode 100644 historical-search-api/request_api/schemas/external/bpmschema.py create mode 100644 historical-search-api/request_api/schemas/foiapplicantcorrespondencelog.py create mode 100644 historical-search-api/request_api/schemas/foiassignee.py create mode 100644 historical-search-api/request_api/schemas/foiaxissync.py create mode 100644 historical-search-api/request_api/schemas/foicfrfee.py create mode 100644 historical-search-api/request_api/schemas/foicomment.py create mode 100644 historical-search-api/request_api/schemas/foidocument.py create mode 100644 historical-search-api/request_api/schemas/foiemail.py create mode 100644 historical-search-api/request_api/schemas/foiextension.py create mode 100644 historical-search-api/request_api/schemas/foipayment.py create mode 100644 historical-search-api/request_api/schemas/foiprogramareadivision.py create mode 100644 historical-search-api/request_api/schemas/foirecord.py create mode 100644 historical-search-api/request_api/schemas/foirequest.py create mode 100644 historical-search-api/request_api/schemas/foirequestsformslist.py create mode 100644 historical-search-api/request_api/schemas/foirequestwrapper.py create mode 100644 historical-search-api/request_api/schemas/foiwatcher.py create mode 100644 historical-search-api/request_api/schemas/schemas/error.json create mode 100644 historical-search-api/request_api/schemas/schemas/exception.json create mode 100644 historical-search-api/request_api/schemas/schemas/rawrequest.json create mode 100644 historical-search-api/request_api/schemas/schemas/sample.json create mode 100644 historical-search-api/request_api/schemas/utils.py create mode 100644 historical-search-api/request_api/services/__init__.py create mode 100644 historical-search-api/request_api/services/applicantcategoryservice.py create mode 100644 historical-search-api/request_api/services/applicantcorrespondence/applicantcorrespondencelog.py create mode 100644 historical-search-api/request_api/services/assigneeservice.py create mode 100644 historical-search-api/request_api/services/auditservice.py create mode 100644 historical-search-api/request_api/services/cacheservice.py create mode 100644 historical-search-api/request_api/services/cdogs_api_service.py create mode 100644 historical-search-api/request_api/services/cfrfeeservice.py create mode 100644 historical-search-api/request_api/services/cfrfeestatusservice.py create mode 100644 historical-search-api/request_api/services/cfrformreasonservice.py create mode 100644 historical-search-api/request_api/services/closereasonservice.py create mode 100644 historical-search-api/request_api/services/commentservice.py create mode 100644 historical-search-api/request_api/services/commons/duecalculator.py create mode 100644 historical-search-api/request_api/services/dashboardeventservice.py create mode 100644 historical-search-api/request_api/services/dashboardservice.py create mode 100644 historical-search-api/request_api/services/deliverymodeservice.py create mode 100644 historical-search-api/request_api/services/divisionstageservice.py create mode 100644 historical-search-api/request_api/services/document_generation_service.py create mode 100644 historical-search-api/request_api/services/documentservice.py create mode 100644 historical-search-api/request_api/services/email/inboxservice.py create mode 100644 historical-search-api/request_api/services/email/senderservice.py create mode 100644 historical-search-api/request_api/services/email/templates/templateconfig.py create mode 100644 historical-search-api/request_api/services/email/templates/templatefilters.py create mode 100644 historical-search-api/request_api/services/email/templates/templateservice.py create mode 100644 historical-search-api/request_api/services/email/templatevaluebuilderservice.py create mode 100644 historical-search-api/request_api/services/emailservice.py create mode 100644 historical-search-api/request_api/services/events/__init__.py create mode 100644 historical-search-api/request_api/services/events/assignment.py create mode 100644 historical-search-api/request_api/services/events/cfrdate.py create mode 100644 historical-search-api/request_api/services/events/cfrfeeform.py create mode 100644 historical-search-api/request_api/services/events/comment.py create mode 100644 historical-search-api/request_api/services/events/division.py create mode 100644 historical-search-api/request_api/services/events/divisiondate.py create mode 100644 historical-search-api/request_api/services/events/email.py create mode 100644 historical-search-api/request_api/services/events/extension.py create mode 100644 historical-search-api/request_api/services/events/legislativedate.py create mode 100644 historical-search-api/request_api/services/events/oipc.py create mode 100644 historical-search-api/request_api/services/events/oipcduedate.py create mode 100644 historical-search-api/request_api/services/events/payment.py create mode 100644 historical-search-api/request_api/services/events/section5pending.py create mode 100644 historical-search-api/request_api/services/events/state.py create mode 100644 historical-search-api/request_api/services/events/watcher.py create mode 100644 historical-search-api/request_api/services/eventservice.py create mode 100644 historical-search-api/request_api/services/extensionreasonservice.py create mode 100644 historical-search-api/request_api/services/extensionservice.py create mode 100644 historical-search-api/request_api/services/external/axissyncservice.py create mode 100644 historical-search-api/request_api/services/external/bpmservice.py create mode 100644 historical-search-api/request_api/services/external/camundaservice.py create mode 100644 historical-search-api/request_api/services/external/eventqueueservice.py create mode 100644 historical-search-api/request_api/services/external/keycloakadminservice.py create mode 100644 historical-search-api/request_api/services/external/storageservice.py create mode 100644 historical-search-api/request_api/services/fee_service.py create mode 100644 historical-search-api/request_api/services/foirequest/requestservicebuilder.py create mode 100644 historical-search-api/request_api/services/foirequest/requestserviceconfigurator.py create mode 100644 historical-search-api/request_api/services/foirequest/requestservicecreate.py create mode 100644 historical-search-api/request_api/services/foirequest/requestservicegetter.py create mode 100644 historical-search-api/request_api/services/foirequest/requestserviceministrybuilder.py create mode 100644 historical-search-api/request_api/services/foirequest/requestserviceupdate.py create mode 100644 historical-search-api/request_api/services/hash_service.py create mode 100644 historical-search-api/request_api/services/historicalrequestservice.py create mode 100644 historical-search-api/request_api/services/notifications/notificationconfig.py create mode 100644 historical-search-api/request_api/services/notifications/notificationuser.py create mode 100644 historical-search-api/request_api/services/notificationservice.py create mode 100644 historical-search-api/request_api/services/oipcservice.py create mode 100644 historical-search-api/request_api/services/paymentservice.py create mode 100644 historical-search-api/request_api/services/programareadivisionservice.py create mode 100644 historical-search-api/request_api/services/programareaservice.py create mode 100644 historical-search-api/request_api/services/rawrequest/rawrequestservicegetter.py create mode 100644 historical-search-api/request_api/services/rawrequestservice.py create mode 100644 historical-search-api/request_api/services/receivedmodeservice.py create mode 100644 historical-search-api/request_api/services/records/recordservicebase.py create mode 100644 historical-search-api/request_api/services/records/recordservicegetter.py create mode 100644 historical-search-api/request_api/services/recordservice.py create mode 100644 historical-search-api/request_api/services/requestservice.py create mode 100644 historical-search-api/request_api/services/reset.py create mode 100644 historical-search-api/request_api/services/rest_service.py create mode 100644 historical-search-api/request_api/services/subjectcodeservice.py create mode 100644 historical-search-api/request_api/services/unopenedreportservice.py create mode 100644 historical-search-api/request_api/services/userservice.py create mode 100644 historical-search-api/request_api/services/validators/__init__.py create mode 100644 historical-search-api/request_api/services/validators/validator_response.py create mode 100644 historical-search-api/request_api/services/watcherservice.py create mode 100644 historical-search-api/request_api/services/workflowservice.py create mode 100644 historical-search-api/request_api/status.py create mode 100644 historical-search-api/request_api/tracer.py create mode 100644 historical-search-api/request_api/utils/__init__.py create mode 100644 historical-search-api/request_api/utils/cache.py create mode 100644 historical-search-api/request_api/utils/commons/datetimehandler.py create mode 100644 historical-search-api/request_api/utils/constants.py create mode 100644 historical-search-api/request_api/utils/custom_sql.py create mode 100644 historical-search-api/request_api/utils/enums.py create mode 100644 historical-search-api/request_api/utils/handlers.py create mode 100644 historical-search-api/request_api/utils/passcode.py create mode 100644 historical-search-api/request_api/utils/redispublisher.py create mode 100644 historical-search-api/request_api/utils/redissubscriber.py create mode 100644 historical-search-api/request_api/utils/roles.py create mode 100644 historical-search-api/request_api/utils/run_version.py create mode 100644 historical-search-api/request_api/utils/util.py create mode 100644 historical-search-api/request_api/utils/util_logging.py create mode 100644 historical-search-api/request_api/version.py create mode 100644 historical-search-api/requirements.txt create mode 100644 historical-search-api/sample.env create mode 100644 historical-search-api/tests/__init__.py create mode 100644 historical-search-api/tests/conftest.py create mode 100644 historical-search-api/tests/restapi/__init__.py create mode 100644 historical-search-api/tests/restapi/test_fee_api.py create mode 100644 historical-search-api/tests/restapi/test_foiassignees_api.py create mode 100644 historical-search-api/tests/restapi/test_foiaudit_api.py create mode 100644 historical-search-api/tests/restapi/test_foicfrfee_api.py create mode 100644 historical-search-api/tests/restapi/test_foicomment_api.py create mode 100644 historical-search-api/tests/restapi/test_foidocument_api.py create mode 100644 historical-search-api/tests/restapi/test_foiextension_api.py create mode 100644 historical-search-api/tests/restapi/test_foimasterdata_api.py create mode 100644 historical-search-api/tests/restapi/test_foinotification_api.py create mode 100644 historical-search-api/tests/restapi/test_foirecord_api.py create mode 100644 historical-search-api/tests/restapi/test_foirequest_api.py create mode 100644 historical-search-api/tests/restapi/test_foisystemcomment_api.py create mode 100644 historical-search-api/tests/restapi/test_foiwatcher_api.py create mode 100644 historical-search-api/tests/restapi/test_rawrequest_api.py create mode 100644 historical-search-api/tests/samplerequestjson/foirequest-extension-oipc.json create mode 100644 historical-search-api/tests/samplerequestjson/foirequest-general-CFR.json create mode 100644 historical-search-api/tests/samplerequestjson/foirequest-general-update.json create mode 100644 historical-search-api/tests/samplerequestjson/foirequest-general.json create mode 100644 historical-search-api/tests/samplerequestjson/foirequest-ministry-general-update.json create mode 100644 historical-search-api/tests/samplerequestjson/foirequest-personal.json create mode 100644 historical-search-api/tests/samplerequestjson/rawrequest.json create mode 100644 historical-search-api/tests/samplerequestjson/s3storagerequest.json create mode 100644 historical-search-api/tests/services/__init__.py create mode 100644 historical-search-api/tests/services/test_dashboard_services.py create mode 100644 historical-search-api/tests/services/test_masterdata_services.py create mode 100644 historical-search-api/tests/services/test_rawrequest_services.py create mode 100644 historical-search-api/tests/services/test_requestextension_services.py create mode 100644 historical-search-api/wsgi.py diff --git a/docker-compose.yml b/docker-compose.yml index 7110f214c..20591e134 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -127,6 +127,129 @@ services: #- SQLALCHEMY_CONNECT_TIMEOUT=${SQLALCHEMY_CONNECT_TIMEOUT} #- SQLALCHEMY_POOL_PRE_PING=${SQLALCHEMY_POOL_PRE_PING} #- SQLALCHEMY_ECHO=${SQLALCHEMY_ECHO} + historical-search-api: + build: + context: ./historical-search-api + dockerfile: dockerfile.local + image: historicalsearchapi + container_name: historical_search_api + depends_on: + - foi-requests-DB + networks: + services-network: + aliases: + - backendnw + ports: + - 15001:5000 + environment: + - DATABASE_USERNAME=${FOI_DATABASE_USERNAME} + - DATABASE_PASSWORD=${FOI_DATABASE_PASSWORD} + - DATABASE_NAME=${FOI_DATABASE_NAME} + - DATABASE_HOST=${FOI_DATABASE_HOST} + - DATABASE_PORT=${FOI_DATABASE_PORT} + - FLASK_ENV:production + - FOI_REQUESTQUEUE_REDISHOST=${WEBSOCKET_BROKER_HOST} + - FOI_REQUESTQUEUE_REDISPORT=${WEBSOCKET_BROKER_PORT} + - FOI_REQUESTQUEUE_REDISPASSWORD=${WEBSOCKET_BROKER_PASSCODE} + - FOI_REQUESTQUEUE_REDISCHANNEL=${WEBSOCKET_FOI_RAWREQUEST_TOPIC} + - KEYCLOAK_ADMIN_HOST=${KEYCLOAK_URL} + - KEYCLOAK_ADMIN_REALM=${KEYCLOAK_URL_REALM} + - KEYCLOAK_ADMIN_CLIENT_ID=foi-lob-api + - KEYCLOAK_ADMIN_CLIENT_SECRET=${KEYCLOAK_ADMIN_CLIENT_SECRET} + - KEYCLOAK_ADMIN_SRVACCOUNT=foisrcaccount + - KEYCLOAK_ADMIN_SRVPASSWORD=${KEYCLOAK_ADMIN_SRVPASSWORD} + - KEYCLOAK_ADMIN_INTAKE_GROUPID=${KEYCLOAK_ADMIN_INTAKE_GROUPID} + - BPM_ENGINE_REST_URL=${FORMSFLOW_WF_URL} + - BPM_TOKEN_URL=${KEYCLOAK_URL}/auth/realms/${KEYCLOAK_URL_REALM}/protocol/openid-connect/token + - BPM_CLIENT_ID=forms-flow-bpm + - BPM_CLIENT_SECRET=${KEYCLOAK_BPM_CLIENT_SECRET} + - JWT_OIDC_AUDIENCE=${JWT_OIDC_AUDIENCE} + - CORS_ORIGIN=${CORS_ORIGIN} + - JWT_OIDC_WELL_KNOWN_CONFIG=${KEYCLOAK_URL}/auth/realms/${KEYCLOAK_URL_REALM}/.well-known/openid-configuration + - JWT_OIDC_ALGORITHMS=RS256 + - JWT_OIDC_JWKS_URI=${KEYCLOAK_URL}/auth/realms/${KEYCLOAK_URL_REALM}/protocol/openid-connect/certs + - JWT_OIDC_ISSUER=${KEYCLOAK_URL}/auth/realms/${KEYCLOAK_URL_REALM} + - JWT_OIDC_CACHING_ENABLED=True + - JWT_OIDC_JWKS_CACHE_TIMEOUT=300 + - CACHE_TIMEOUT=${CACHE_TIMEOUT} + - KC_SRC_ACC_TOKEN_EXPIRY=${KC_SRC_ACC_TOKEN_EXPIRY} + - FOI_REQUESTQUEUE_REDISURL=${FOI_REQUESTQUEUE_REDISURL} + - CACHE_ENABLED=${CACHE_ENABLED} + - CACHE_TYPE=${CACHE_TYPE} + - OSS_S3_FORMS_BUCKET=${OSS_S3_FORMS_BUCKET} + - OSS_S3_FORMS_ACCESS_KEY_ID=${OSS_S3_FORMS_ACCESS_KEY_ID} + - OSS_S3_FORMS_SECRET_ACCESS_KEY=${OSS_S3_FORMS_SECRET_ACCESS_KEY} + - OSS_S3_HOST=${OSS_S3_HOST} + - OSS_S3_REGION=${OSS_S3_REGION} + - OSS_S3_SERVICE=${OSS_S3_SERVICE} + - OSS_S3_ENVIRONMENT=${OSS_S3_ENVIRONMENT} + - OSS_S3_RECORDS_ACCESS_KEY_ID=${OSS_S3_RECORDS_ACCESS_KEY_ID} + - OSS_S3_RECORDS_SECRET_ACCESS_KEY=${OSS_S3_RECORDS_SECRET_ACCESS_KEY} + - OSS_S3_CHUNK_SIZE=${OSS_S3_CHUNK_SIZE} + - TOTAL_RECORDS_UPLOAD_LIMIT=${TOTAL_RECORDS_UPLOAD_LIMIT} + - EVENT_QUEUE_HOST=${EVENT_QUEUE_HOST} + - EVENT_QUEUE_PORT=${EVENT_QUEUE_PORT} + - EVENT_QUEUE_PASSWORD=${EVENT_QUEUE_PASSWORD} + - FOI_NOTIFICATION_DAYS=${FOI_NOTIFICATION_DAYS} + - FOI_ADDITIONAL_HOLIDAYS=${FOI_ADDITIONAL_HOLIDAYS} + - SOCKETIO_PING_INTERVAL=${SOCKETIO_PING_INTERVAL} + - SOCKETIO_PING_TIMEOUT=${SOCKETIO_PING_TIMEOUT} + - SOCKETIO_LOG_ENABLED=${SOCKETIO_LOG_ENABLED} + - SOCKETIO_MESSAGE_QTYPE=${SOCKETIO_MESSAGE_QTYPE} + - SOCKETIO_REDISURL=${SOCKETIO_REDISURL} + - SOCKETIO_REDIS_COMMENT_CHANNEL=${SOCKETIO_REDIS_COMMENT_CHANNEL} + - SOCKETIO_REDIS_HEALTHCHECK_INTERVAL=${SOCKETIO_REDIS_HEALTHCHECK_INTERVAL} + - SOCKETIO_REDIS_CONNECT_TIMEOUT=${SOCKETIO_REDIS_CONNECT_TIMEOUT} + - SOCKETIO_REDIS_SLEEP_TIME=${SOCKETIO_REDIS_SLEEP_TIME} + - SOCKETIO_CONNECT_NONCE=${SOCKETIO_CONNECT_NONCE} + - CACHE_REDISURL=${CACHE_REDISURL} + - PAYBC_REF_NUMBER=${PAYBC_REF_NUMBER} + - PAYBC_PORTAL_URL=${PAYBC_PORTAL_URL} + - PAYBC_TXN_PREFIX=${PAYBC_TXN_PREFIX} + - PAYBC_API_KEY=${PAYBC_API_KEY} + - PAYBC_API_BASE_URL=${PAYBC_API_BASE_URL} + - PAYBC_API_CLIENT=${PAYBC_API_CLIENT} + - PAYBC_API_SECRET=${PAYBC_API_SECRET} + - FOI_FFA_URL=${FOI_FFA_URL} + - EMAIL_SERVER_IMAP=${EMAIL_SERVER_IMAP} + - EMAIL_SERVER_SMTP=${EMAIL_SERVER_SMTP} + - EMAIL_SERVER_SMTP_PORT=${EMAIL_SERVER_SMTP_PORT} + - EMAIL_SRUSERID=${EMAIL_SRUSERID} + - EMAIL_SRPWD=${EMAIL_SRPWD} + - EMAIL_SENDER_ADDRESS=${EMAIL_SENDER_ADDRESS} + - EMAIL_FOLDER_OUTBOX=${EMAIL_FOLDER_OUTBOX} + - EMAIL_FOLDER_INBOX=${EMAIL_FOLDER_INBOX} + - EVENT_QUEUE_CONVERSION_STREAMKEY=${EVENT_QUEUE_CONVERSION_STREAMKEY} + - EVENT_QUEUE_CONVERSION_LARGE_FILE_STREAM_KEY=${EVENT_QUEUE_CONVERSION_LARGE_FILE_STREAM_KEY} + - EVENT_QUEUE_DEDUPE_STREAMKEY=${EVENT_QUEUE_DEDUPE_STREAMKEY} + - EVENT_QUEUE_DEDUPE_LARGE_FILE_STREAMKEY=${EVENT_QUEUE_DEDUPE_LARGE_FILE_STREAMKEY} + - FOI_DOCREVIEWER_BASE_API_URL=${FOI_DOCREVIEWER_BASE_API_URL} + - FOI_DOCREVIEWER_BASE_API_TIMEOUT=${FOI_DOCREVIEWER_BASE_API_TIMEOUT} + - PAYMENT_CONFIG=${PAYMENT_CONFIG} + - FOI_REQ_MANAGEMENT_API_URL=${FOI_REQ_MANAGEMENT_API_URL} + - FOI_RECORD_FORMATS=${FOI_RECORD_FORMATS} + - EVENT_QUEUE_PDFSTITCH_STREAMKEY=${EVENT_QUEUE_PDFSTITCH_STREAMKEY} + - EVENT_QUEUE_PDFSTITCH_LARGE_FILE_STREAMKEY=${EVENT_QUEUE_PDFSTITCH_LARGE_FILE_STREAMKEY} + - STREAM_SEPARATION_FILE_SIZE_LIMIT=${STREAM_SEPARATION_FILE_SIZE_LIMIT} + - MUTE_NOTIFICATION=${MUTE_NOTIFICATION} + - EVENT_QUEUE_PAGECALCULATOR_STREAM_KEY=${EVENT_QUEUE_PAGECALCULATOR_STREAM_KEY} + - UNOPENED_REPORT_CUTOFF_DAYS=${UNOPENED_REPORT_CUTOFF_DAYS} + - UNOPENED_REPORT_WAIT_DAYS=${UNOPENED_REPORT_WAIT_DAYS} + - UNOPENED_REPORT_JARO_CUTOFF=${UNOPENED_REPORT_JARO_CUTOFF} + - UNOPENED_REPORT_EMAIL_RECIPIENT=${UNOPENED_REPORT_EMAIL_RECIPIENT} + - AXIS_API_URL=${AXIS_API_URL} + - AXIS_SYNC_BATCHSIZE=${AXIS_SYNC_BATCHSIZE} + #- LOG_ROOT=${LOG_ROOT} + #- LOG_BASIC=${LOG_BASIC} + #- LOG_TRACING=${LOG_TRACING} + #To tune connection settings (Optional) + #- SQLALCHEMY_POOL_SIZE=${SQLALCHEMY_POOL_SIZE} + #- SQLALCHEMY_MAX_OVERFLOW=${SQLALCHEMY_MAX_OVERFLOW} + #- SQLALCHEMY_POOL_TIMEOUT=${SQLALCHEMY_POOL_TIMEOUT} + #- SQLALCHEMY_CONNECT_TIMEOUT=${SQLALCHEMY_CONNECT_TIMEOUT} + #- SQLALCHEMY_POOL_PRE_PING=${SQLALCHEMY_POOL_PRE_PING} + #- SQLALCHEMY_ECHO=${SQLALCHEMY_ECHO} + foi-requests-DB: image: postgres container_name: aot_foi_requests_db_pg diff --git a/historical-search-api/LICENSE b/historical-search-api/LICENSE new file mode 100644 index 000000000..f05f09625 --- /dev/null +++ b/historical-search-api/LICENSE @@ -0,0 +1,13 @@ +Copyright 2019 Province of British Columbia + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/historical-search-api/README.md b/historical-search-api/README.md new file mode 100644 index 000000000..1cd0ab188 --- /dev/null +++ b/historical-search-api/README.md @@ -0,0 +1,3 @@ +# Request Management API + +README todo \ No newline at end of file diff --git a/historical-search-api/dockerfile b/historical-search-api/dockerfile new file mode 100644 index 000000000..f043ae942 --- /dev/null +++ b/historical-search-api/dockerfile @@ -0,0 +1,25 @@ +# For more information, please refer to https://aka.ms/vscode-docker-python +# 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.10.8-buster +EXPOSE 6402 +RUN set -eux; apt-get update; apt-get install -y --no-install-recommends ca-certificates curl netbase wget ; rm -rf /var/lib/apt/lists/* + +# Keeps Python from generating .pyc files in the container +ENV PYTHONDONTWRITEBYTECODE=1 + +# Turns off buffering for easier container logging +ENV PYTHONUNBUFFERED=1 + +COPY ./ /app +WORKDIR /app +RUN pip3 install --no-cache-dir -r requirements.txt + +# Creates a non-root user and adds permission to access the /app folder +# For more info, please refer to https://aka.ms/vscode-docker-python-configure-containers +#RUN useradd appuser && chown -R appuser /app +#USER appuser + +RUN chmod u+x /app/entrypoint.sh +ENTRYPOINT ["/bin/sh", "/app/entrypoint.sh"] \ No newline at end of file diff --git a/historical-search-api/dockerfile.local b/historical-search-api/dockerfile.local new file mode 100644 index 000000000..a3f3052a8 --- /dev/null +++ b/historical-search-api/dockerfile.local @@ -0,0 +1,21 @@ +# For more information, please refer to https://aka.ms/vscode-docker-python +FROM python:3.10.8 +EXPOSE 6402 + +# Keeps Python from generating .pyc files in the container +ENV PYTHONDONTWRITEBYTECODE=1 + +# Turns off buffering for easier container logging +ENV PYTHONUNBUFFERED=1 + +COPY ./ /app +WORKDIR /app +RUN pip3 install --no-cache-dir -r requirements.txt + +# Creates a non-root user and adds permission to access the /app folder +# For more info, please refer to https://aka.ms/vscode-docker-python-configure-containers +#RUN useradd appuser && chown -R appuser /app +#USER appuser + +RUN chmod u+x /app/entrypoint.sh +ENTRYPOINT ["/bin/sh", "/app/entrypoint.sh"] \ No newline at end of file diff --git a/historical-search-api/entrypoint.sh b/historical-search-api/entrypoint.sh new file mode 100644 index 000000000..8e5e5d61d --- /dev/null +++ b/historical-search-api/entrypoint.sh @@ -0,0 +1,3 @@ +#!/bin/bash +# python manage.py db upgrade && python wsgi.py +python wsgi.py \ No newline at end of file diff --git a/historical-search-api/gunicorn_config.py b/historical-search-api/gunicorn_config.py new file mode 100644 index 000000000..75ce2ac1a --- /dev/null +++ b/historical-search-api/gunicorn_config.py @@ -0,0 +1,25 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""The configuration for gunicorn, which picks up the + runtime options from environment variables +""" + +import os + + +workers = int(os.environ.get('GUNICORN_PROCESSES', '1')) # pylint: disable=invalid-name +threads = int(os.environ.get('GUNICORN_THREADS', '1')) # pylint: disable=invalid-name +worker_class = 'eventlet' +forwarded_allow_ips = '*' # pylint: disable=invalid-name +secure_scheme_headers = {'X-Forwarded-Proto': 'https'} # pylint: disable=invalid-name \ No newline at end of file diff --git a/historical-search-api/logging.conf b/historical-search-api/logging.conf new file mode 100644 index 000000000..37d53a495 --- /dev/null +++ b/historical-search-api/logging.conf @@ -0,0 +1,40 @@ +[loggers] +keys=root,api,tracing + +[handlers] +keys=console,file + +[formatters] +keys=simple + +[logger_root] +level=DEBUG +handlers=console + +[logger_api] +level=DEBUG +handlers=console +qualname=api +propagate=0 + +[logger_tracing] +level=ERROR +handlers=console +qualname=jaeger_tracing +propagate=0 + +[handler_console] +class=StreamHandler +level=DEBUG +formatter=simple +args=(sys.stdout,) + +[handler_file] +class=FileHandler +level=DEBUG +formatter=simple +args=('request_api/logs/logfile.log', 'a') + +[formatter_simple] +format=%(asctime)s - %(name)s - %(levelname)s in %(module)s:%(filename)s:%(lineno)d - %(funcName)s: %(message)s +datefmt= \ No newline at end of file diff --git a/historical-search-api/manage.py b/historical-search-api/manage.py new file mode 100644 index 000000000..696f556bb --- /dev/null +++ b/historical-search-api/manage.py @@ -0,0 +1,59 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Manage the database and some other items required to run the API +""" +import logging + +from flask import url_for +from flask_migrate import Migrate, MigrateCommand +from flask_script import Manager # class for handling a set of commands + +# models included so that migrate can build the database migrations +from request_api import models # pylint: disable=unused-import +from request_api import create_app +from request_api.models import db + +## python manage.py db migrate +## python manage.py db upgrade + + +APP = create_app() +MIGRATE = Migrate(APP, db) +MANAGER = Manager(APP) + +MANAGER.add_command('db', MigrateCommand) + + +@MANAGER.command +def list_routes(): + output = [] + for rule in APP.url_map.iter_rules(): + + options = {} + for arg in rule.arguments: + options[arg] = "[{0}]".format(arg) + + methods = ','.join(rule.methods) + url = url_for(rule.endpoint, **options) + line = ("{:50s} {:20s} {}".format(rule.endpoint, methods, url)) + output.append(line) + + for line in sorted(output): + print(line) + + +if __name__ == '__main__': + logging.log(logging.INFO, 'Running the Manager') + MANAGER.run() diff --git a/historical-search-api/pre-hook-update-db.sh b/historical-search-api/pre-hook-update-db.sh new file mode 100644 index 000000000..5624bf62f --- /dev/null +++ b/historical-search-api/pre-hook-update-db.sh @@ -0,0 +1,4 @@ +#! /bin/sh +cd /opt/app-root +echo 'starting upgrade' +python3 manage.py db upgrade diff --git a/historical-search-api/pre_hook_create_database.py b/historical-search-api/pre_hook_create_database.py new file mode 100644 index 000000000..ce7b3899a --- /dev/null +++ b/historical-search-api/pre_hook_create_database.py @@ -0,0 +1,30 @@ +import contextlib +import os +import sys + +import sqlalchemy +import sqlalchemy.exc + +from request_api.config import ProdConfig + + +DB_ADMIN_PASSWORD = os.getenv('DB_ADMIN_PASSWORD', None) + +if not hasattr(ProdConfig, 'DB_NAME') or not DB_ADMIN_PASSWORD: + print("Unable to create database.", sys.stdout) + sys.exit(-1) + +DATABASE_URI = 'postgresql://postgres:{password}@{host}:{port}/{name}'.format( + password=DB_ADMIN_PASSWORD, + host=ProdConfig.DB_HOST, + port=int(ProdConfig.DB_PORT), + name='postgres', +) + +with contextlib.suppress(sqlalchemy.exc.ProgrammingError): + with sqlalchemy.create_engine( + DATABASE_URI, + isolation_level='AUTOCOMMIT' + ).connect() as connection: + database = ProdConfig.DB_NAME + connection.execute(f'CREATE DATABASE {database}') diff --git a/historical-search-api/request_api/__init__.py b/historical-search-api/request_api/__init__.py new file mode 100644 index 000000000..44bbbc73a --- /dev/null +++ b/historical-search-api/request_api/__init__.py @@ -0,0 +1,128 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""The Authroization API service. + +This module is the API for the Authroization system. +""" + +import json +import os +import logging +from flask import Flask + +import request_api.config as config +from request_api.config import _Config + +from request_api.models import db, ma +from request_api.utils.util_logging import configure_logging +from request_api.auth import jwt +from flask_cors import CORS +import re +from flask_caching import Cache +from flask_socketio import SocketIO +import secure + +app = Flask(__name__) +#Cache Initialization +app.config.from_object('request_api.utils.cache.Config') +cache = Cache(app) + +SOCKETIO_PING_TIMEOUT = int(os.getenv('SOCKETIO_PING_TIMEOUT', 5)) +SOCKETIO_PING_INTERVAL = int(os.getenv('SOCKETIO_PING_INTERVAL', 25)) +SOCKETIO_LOG_ENABLED = True if os.getenv('SOCKETIO_LOG_ENABLED').lower() == "true" else False +SOCKETIO_CORS_ORIGIN= os.getenv('CORS_ORIGIN').split(",") + +socketio = SocketIO(logger=SOCKETIO_LOG_ENABLED, engineio_logger=SOCKETIO_LOG_ENABLED,ping_timeout=SOCKETIO_PING_TIMEOUT,ping_interval=SOCKETIO_PING_INTERVAL,cors_allowed_origins=SOCKETIO_CORS_ORIGIN) + +#Setup log +configure_logging() + +# Security Response headers +csp = ( + secure.ContentSecurityPolicy() + .default_src("'self'") + .script_src("'self'","'unsafe-inline'") + .style_src("'self'","'unsafe-inline'") + .object_src('self') + .connect_src("'self'","'unsafe-inline'") +) +hsts = secure.StrictTransportSecurity().include_subdomains().preload().max_age(31536000) +referrer = secure.ReferrerPolicy().no_referrer() +cache_value = secure.CacheControl().no_store().max_age(0) +xfo_value = secure.XFrameOptions().deny() +secure_headers = secure.Secure( + csp=csp, + hsts=hsts, + referrer=referrer, + cache=cache_value, + xfo=xfo_value +) + +@app.after_request +def set_secure_headers(response): + secure_headers.framework.flask(response) + response.headers.add('Cross-Origin-Resource-Policy','same-origin') + response.headers['Cross-Origin-Opener-Policy'] = 'same-origin' + response.headers['Cross-Origin-Embedder-Policy'] = 'unsafe-none' + return response + +def create_app(run_mode=os.getenv('FLASK_ENV', 'development')): + """Return a configured Flask App using the Factory method.""" + app.config.from_object(config.CONFIGURATION[run_mode]) + + from request_api.resources import API_BLUEPRINT #, DEFAULT_API_BLUEPRINT #, OPS_BLUEPRINT # pylint: disable=import-outside-toplevel + + print("environment :" + run_mode) + + CORS(app, supports_credentials=True) + db.init_app(app) + ma.init_app(app) + + app.register_blueprint(API_BLUEPRINT) + + if os.getenv('FLASK_ENV', 'production') != 'testing': + print("JWTSET DONE!!!!!!!!!!!!!!!!") + setup_jwt_manager(app, jwt) + + #ExceptionHandler(app) + + + register_shellcontext(app) + + return app + + +def setup_jwt_manager(app, jwt_manager): + """Use flask app to configure the JWTManager to work for a particular Realm.""" + + def get_roles(a_dict): + return a_dict['groups'] # pragma: no cover + + app.config['JWT_ROLE_CALLBACK'] = get_roles + + jwt_manager.init_app(app) + + + +def register_shellcontext(app): + """Register shell context objects.""" + + def shell_context(): + """Shell context objects.""" + return {'app': app, 'jwt': jwt, 'db': db, 'models': models} # pragma: no cover + + app.shell_context_processor(shell_context) + + + diff --git a/historical-search-api/request_api/auth.py b/historical-search-api/request_api/auth.py new file mode 100644 index 000000000..38bd2a9f0 --- /dev/null +++ b/historical-search-api/request_api/auth.py @@ -0,0 +1,290 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Bring in the common JWT Manager.""" +from functools import wraps +from http import HTTPStatus + +from flask import g, request +from flask_jwt_oidc import JwtManager +# from jose import jwt as josejwt +from jose import JWTError, jwt as josejwt +from request_api.utils.enums import MinistryTeamWithKeycloackGroup, ProcessingTeamWithKeycloackGroup, IAOTeamWithKeycloackGroup +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +jwt = ( + JwtManager() +) # pylint: disable=invalid-name; lower case name as used by convention in most Flask apps + + +class Auth: + """Extending JwtManager to include additional functionalities.""" + + @classmethod + def require(cls, f): + """Validate the Bearer Token.""" + + # @jwt.requires_auth + @wraps(f) + def decorated(*args, **kwargs): + # g.authorization_header = request.headers.get("Authorization", None) + # g.token_info = g.jwt_oidc_token_info + return f(*args, **kwargs) + + return decorated + + @classmethod + def hasusertype(cls,usertype): + def decorated(f): + @wraps(f) + def wrapper(*args, **kwargs): + if(usertype == AuthHelper.getusertype()): + return f(*args, **kwargs) + return "Unauthorized" , 401 + return wrapper + return decorated + + @classmethod + def belongstosameministry(cls,func): + @wraps(func) + def decorated(type, id, field,*args, **kwargs): + usertype = AuthHelper.getusertype() + if(usertype == "iao"): + return func(type, id, field,*args, **kwargs) + elif(usertype == "ministry"): + requestministry = FOIMinistryRequest.getrequestbyministryrequestid(id) + ministrygroups = AuthHelper.getministrygroups() + expectedministrygroup = MinistryTeamWithKeycloackGroup[requestministry['programarea.bcgovcode']].value + retval = "Unauthorized" , 401 + if(expectedministrygroup not in ministrygroups): + return retval + else: + return func(type, id, field,*args, **kwargs) + return decorated + + @classmethod + def documentbelongstosameministry(cls,func): + @wraps(func) + def decorated( ministryrequestid, *args, **kwargs): + usertype = AuthHelper.getusertype() + if(usertype == "iao"): + return func( ministryrequestid,*args, **kwargs) + elif(usertype == "ministry"): + requestministry = FOIMinistryRequest.getrequestbyministryrequestid(ministryrequestid) + ministrygroups = AuthHelper.getministrygroups() + expectedministrygroup = MinistryTeamWithKeycloackGroup[requestministry['programarea.bcgovcode']].value + retval = "Unauthorized" , 401 + if(expectedministrygroup not in ministrygroups): + return retval + else: + return func(id, *args, **kwargs) + return decorated + + + @classmethod + def ismemberofgroups(cls, groups): + """Check that at least one of the realm groups are in the token. + Args: + groups [str,]: Comma separated list of valid roles + """ + + def decorated(f): + # Token verification is commented here with an expectation to use this decorator in conjuction with require. + #@Auth.require + @wraps(f) + def wrapper(*args, **kwargs): + # _groups = groups.split(',') + # token = jwt.get_token_auth_header() + # unverified_claims = josejwt.get_unverified_claims(token) + # usergroups = unverified_claims['groups'] + # usergroups = [usergroup.replace('/','',1) if usergroup.startswith('/') else usergroup for usergroup in usergroups] + # exists = False + # for group in _groups: + # if group in usergroups: + # exists = True + # retval = "Unauthorized" , 401 + # if exists == True: + # return f(*args, **kwargs) + # return retval + return f(*args, **kwargs) + + return wrapper + + return decorated + + + @classmethod + def isfoiadmin(cls): + """Check if token group contains admin group. + """ + def decorated(f): + # Token verification is commented here with an expectation to use this decorator in conjuction with require. + #@Auth.require + @wraps(f) + def wrapper(*args, **kwargs): + token = jwt.get_token_auth_header() + unverified_claims = josejwt.get_unverified_claims(token) + usergroups = unverified_claims['groups'] + usergroups = [usergroup.replace('/','',1) if usergroup.startswith('/') else usergroup for usergroup in usergroups] + exists = False + if 'FOI Admin' in usergroups: + exists = True + retval = "Unauthorized" , 401 + if exists == True: + return f(*args, **kwargs) + return retval + return wrapper + return decorated + +auth = ( + Auth() +) + + +class AuthHelper: + + + @classmethod + def getuserid(cls, token=None): + try: + # if token is None: + # token = request.headers.get("Authorization", None) + # unverified_claims = josejwt.get_unverified_claims(token.partition("Bearer")[2].strip()) + # if 'identity_provider' in unverified_claims and unverified_claims['identity_provider'] == "idir": + # claim_name = 'foi_preferred_username' if "foi_preferred_username" in unverified_claims else 'preferred_username' + # claim_value = unverified_claims[claim_name].lower() + # return claim_value+'@idir' if claim_value.endswith("@idir") == False else claim_value + # return unverified_claims['preferred_username'] + return 'RICHAQI@idir' + except JWTError as exception: + print("JWTError >>> ", str(exception)) + except Exception as ex: + print("Exception >>> ", str(ex)) + + @classmethod + def getusername(cls): + # token = request.headers.get("Authorization", None) + # unverified_claims = josejwt.get_unverified_claims(token.partition("Bearer")[2].strip()) + # return unverified_claims['name'] + return 'Richard Qi' + + @classmethod + def isministrymember(cls): + # usergroups = cls.getusergroups() + # ministrygroups = list(set(usergroups).intersection(MinistryTeamWithKeycloackGroup.list())) + # if len(ministrygroups) > 0: + # return True + return False + + @classmethod + def isprocesingteammember(cls): + # usergroups = cls.getusergroups() + # ministrygroups = list(set(usergroups).intersection(MinistryTeamWithKeycloackGroup.list())) + # if len(ministrygroups) > 0: + # return False + # else: + # processinggroups = list(set(usergroups).intersection(ProcessingTeamWithKeycloackGroup.list())) + # if len(processinggroups) > 0: + # return True + return False + + @classmethod + def isiaorestrictedfilemanager(cls): + #roles is an array of strings + # roles = cls.getuserroles() + # try: + # if 'IAORestrictedFilesManager' in roles: + # return True + # else: + # return False + # except ValueError: + # return False + return False + + @classmethod + def isministryrestrictedfilemanager(cls): + #roles is an array of strings + # roles = cls.getuserroles() + # try: + # if 'MinistryRestrictedFilesManager' in roles: + # return True + # else: + # return False + # except ValueError: + # return False + return False + + + @classmethod + def getusergroups(cls): + # token = request.headers.get("Authorization", None) + # unverified_claims = josejwt.get_unverified_claims(token.partition("Bearer")[2].strip()) + # usergroups = unverified_claims['groups'] + # usergroups = [usergroup.replace('/','',1) if usergroup.startswith('/') else usergroup for usergroup in usergroups] + # return usergroups + return ['Flex Team', 'Intake Team'] + # return ['EDU Ministry Team'] + + @classmethod + def getuserroles(cls): + # token = request.headers.get("Authorization", None) + # unverified_claims = josejwt.get_unverified_claims(token.partition("Bearer")[2].strip()) + # roles = unverified_claims['role'] + # roles = [role.replace('/','',1) if role.startswith('/') else role for role in roles] + # return roles + return [] + + @classmethod + def getusertype(cls): + # usergroups = cls.getusergroups() + # ministrygroups = list(set(usergroups).intersection(MinistryTeamWithKeycloackGroup.list())) + # if len(ministrygroups) > 0: + # return "ministry" + # else: + # iaogroups = list(set(usergroups).intersection(IAOTeamWithKeycloackGroup.list())) + # if len(iaogroups) > 0: + # return "iao" + # return None + return "iao" + + @classmethod + def getiaotype(cls): + # usergroups = cls.getusergroups() + # _groups = set(usergroups) + # if cls.isministrymember() == False: + # processinggroups = list(_groups.intersection(ProcessingTeamWithKeycloackGroup.list())) + # if len(processinggroups) > 0: + # return "processing" + # else: + # if 'Flex Team' in _groups: + # return "flex" + # elif 'Intake Team' in _groups: + # return "intake" + # else: + # return None + # else: + # return None + return "intake" + + @classmethod + def getministrygroups(cls): + # usergroups = cls.getusergroups() + # return list(set(usergroups).intersection(MinistryTeamWithKeycloackGroup.list())) + return ['EDU Ministry Team'] + # return [] + + + @classmethod + def getauthtoken(cls): + return request.headers.get("Authorization", None) + + diff --git a/historical-search-api/request_api/config.py b/historical-search-api/request_api/config.py new file mode 100644 index 000000000..c8555df53 --- /dev/null +++ b/historical-search-api/request_api/config.py @@ -0,0 +1,201 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""All of the configuration for the service is captured here. + +All items are loaded, +or have Constants defined here that are loaded into the Flask configuration. +All modules and lookups get their configuration from the Flask config, +rather than reading environment variables directly or by accessing this configuration directly. +""" + +import os +import sys + +from dotenv import find_dotenv, load_dotenv + + +# this will load all the envars from a .env file located in the project root (api) +load_dotenv(find_dotenv()) + +CONFIGURATION = { + 'development': 'request_api.config.DevConfig', + 'testing': 'request_api.config.TestConfig', + 'production': 'request_api.config.ProdConfig', + 'default': 'request_api.config.ProdConfig' +} + + +def get_named_config(config_name: str = 'production'): + """Return the configuration object based on the name. + + :raise: KeyError: if an unknown configuration is requested + """ + if config_name in ['production', 'staging', 'default']: + config = ProdConfig() + elif config_name == 'testing': + config = TestConfig() + elif config_name == 'development': + config = DevConfig() + else: + raise KeyError("Unknown configuration '{config_name}'") + return config + + +class _Config(): # pylint: disable=too-few-public-methods + """Base class configuration that should set reasonable defaults for all the other configurations.""" + + PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) + + SECRET_KEY = 'a secret' + + TESTING = False + DEBUG = False + + ALEMBIC_INI = 'migrations/alembic.ini' + # Config to skip migrations when alembic migrate is used + SKIPPED_MIGRATIONS = ['authorizations_view'] + + # POSTGRESQL + DB_USER = os.getenv('DATABASE_USERNAME', '') + DB_PASSWORD = os.getenv('DATABASE_PASSWORD', '') + DB_NAME = os.getenv('DATABASE_NAME', '') + DB_HOST = os.getenv('DATABASE_HOST', '') + DB_PORT = os.getenv('DATABASE_PORT', '5432') + SQLALCHEMY_DATABASE_URI = 'postgresql://{user}:{password}@{host}:{port}/{name}'.format( + user=DB_USER, + password=DB_PASSWORD, + host=DB_HOST, + port=int(DB_PORT), + name=DB_NAME, + ) + #Engine configurations + pool_size = int(os.getenv('SQLALCHEMY_POOL_SIZE', '9')) + max_overflow = int(os.getenv('SQLALCHEMY_MAX_OVERFLOW', '18')) + + pool_timeout_string = os.getenv('SQLALCHEMY_POOL_TIMEOUT', '') + connect_timeout_string = os.getenv('SQLALCHEMY_CONNECT_TIMEOUT', '') + pool_pre_ping = (os.getenv('SQLALCHEMY_POOL_PRE_PING', 'False')).upper() == "TRUE" + db_sql_echo = (os.getenv('SQLALCHEMY_ECHO', 'False')).upper() == "TRUE" + # Try to set some options to avoid long delays. + SQLALCHEMY_ENGINE_OPTIONS = { + 'pool_size': pool_size, + 'max_overflow': max_overflow, + 'pool_pre_ping': pool_pre_ping + } + + if pool_timeout_string != "": + pool_timeout = int(pool_timeout_string) + SQLALCHEMY_ENGINE_OPTIONS['pool_timeout'] = pool_timeout + + if connect_timeout_string != "": + connect_timeout_int = int(connect_timeout_string) + SQLALCHEMY_ENGINE_OPTIONS['connect_args'] = {'connect_timeout': connect_timeout_int} + + #Logging echo settings + SQLALCHEMY_ECHO = db_sql_echo + SQLALCHEMY_TRACK_MODIFICATIONS = False + + + # JWT_OIDC Settings + JWT_OIDC_WELL_KNOWN_CONFIG = os.getenv('JWT_OIDC_WELL_KNOWN_CONFIG') + JWT_OIDC_ALGORITHMS = os.getenv('JWT_OIDC_ALGORITHMS') + JWT_OIDC_JWKS_URI = os.getenv('JWT_OIDC_JWKS_URI') + JWT_OIDC_ISSUER = os.getenv('JWT_OIDC_ISSUER') + JWT_OIDC_AUDIENCE = os.getenv('JWT_OIDC_AUDIENCE') + JWT_OIDC_CLIENT_SECRET = os.getenv('JWT_OIDC_CLIENT_SECRET') + JWT_OIDC_CACHING_ENABLED = os.getenv('JWT_OIDC_CACHING_ENABLED') + try: + JWT_OIDC_JWKS_CACHE_TIMEOUT = int(os.getenv('JWT_OIDC_JWKS_CACHE_TIMEOUT', '300')) + except ValueError: # pylint:disable=bare-except # noqa: B901, E722 + JWT_OIDC_JWKS_CACHE_TIMEOUT = 300 + + + # email + MAIL_FROM_ID = os.getenv('MAIL_FROM_ID') + + # Fees + LEGISLATIVE_TIMEZONE = 'America/Vancouver' + FOI_WEB_PAY_URL = os.getenv('FOI_WEB_PAY_URL') + FOI_FFA_URL = os.getenv('FOI_FFA_URL') + PAYBC_REF_NUMBER = os.getenv('PAYBC_REF_NUMBER') + PAYBC_PORTAL_URL = os.getenv('PAYBC_PORTAL_URL') + PAYBC_TXN_PREFIX = os.getenv('PAYBC_TXN_PREFIX', 'FOI') + PAYBC_API_KEY = os.getenv('PAYBC_API_KEY') + + PAYBC_API_BASE_URL = os.getenv('PAYBC_API_BASE_URL') + PAYBC_API_CLIENT = os.getenv('PAYBC_API_CLIENT') + PAYBC_API_SECRET = os.getenv('PAYBC_API_SECRET') + CONNECT_TIMEOUT = os.getenv('CONNECT_TIMEOUT', 60) + + # CDOGS + CDOGS_ACCESS_TOKEN = os.getenv('CDOGS_ACCESS_TOKEN') + CDOGS_BASE_URL = os.getenv('CDOGS_BASE_URL') + CDOGS_SERVICE_CLIENT = os.getenv('CDOGS_SERVICE_CLIENT') + CDOGS_SERVICE_CLIENT_SECRET = os.getenv('CDOGS_SERVICE_CLIENT_SECRET') + CDOGS_TOKEN_URL = os.getenv('CDOGS_TOKEN_URL') + + + + +class DevConfig(_Config): # pylint: disable=too-few-public-methods + """Dev Config.""" + + TESTING = False + DEBUG = True + + +class TestConfig(_Config): # pylint: disable=too-few-public-methods + """In support of testing only.used by the py.test suite.""" + + DEBUG = True + TESTING = True + # POSTGRESQL + DB_USER = os.getenv('DATABASE_TEST_USERNAME', 'postgres') + DB_PASSWORD = os.getenv('DATABASE_TEST_PASSWORD', 'postgres') + DB_NAME = os.getenv('DATABASE_TEST_NAME', 'postgres') + DB_HOST = os.getenv('DATABASE_TEST_HOST', 'localhost') + DB_PORT = os.getenv('DATABASE_TEST_PORT', '5432') + SQLALCHEMY_DATABASE_URI = os.getenv('DATABASE_TEST_URL', + 'postgresql://{user}:{password}@{host}:{port}/{name}'.format( + user=DB_USER, + password=DB_PASSWORD, + host=DB_HOST, + port=int(DB_PORT), + name=DB_NAME, + )) + + # JWT_OIDC Settings + JWT_OIDC_WELL_KNOWN_CONFIG = os.getenv('JWT_OIDC_WELL_KNOWN_CONFIG') + JWT_OIDC_ALGORITHMS = os.getenv('JWT_OIDC_ALGORITHMS') + JWT_OIDC_JWKS_URI = os.getenv('JWT_OIDC_JWKS_URI') + JWT_OIDC_ISSUER = os.getenv('JWT_OIDC_ISSUER') + JWT_OIDC_AUDIENCE = os.getenv('JWT_OIDC_AUDIENCE') + JWT_OIDC_CLIENT_SECRET = os.getenv('JWT_OIDC_CLIENT_SECRET') + JWT_OIDC_CACHING_ENABLED = os.getenv('JWT_OIDC_CACHING_ENABLED') + try: + JWT_OIDC_JWKS_CACHE_TIMEOUT = int(os.getenv('JWT_OIDC_JWKS_CACHE_TIMEOUT', '300')) + except ValueError: # pylint:disable=bare-except # noqa: B901, E722 + JWT_OIDC_JWKS_CACHE_TIMEOUT = 300 + +class ProdConfig(_Config): # pylint: disable=too-few-public-methods + """Production environment configuration.""" + + SECRET_KEY = os.getenv('SECRET_KEY', None) + + if not SECRET_KEY: + SECRET_KEY = os.urandom(24) + print('WARNING: SECRET_KEY being set as a one-shot', file=sys.stderr) + + TESTING = False + DEBUG = False diff --git a/historical-search-api/request_api/exceptions/__init__.py b/historical-search-api/request_api/exceptions/__init__.py new file mode 100644 index 000000000..c61e1fa59 --- /dev/null +++ b/historical-search-api/request_api/exceptions/__init__.py @@ -0,0 +1,49 @@ +"""Application Specific Exceptions, to manage the user errors. + +@log_error - a decorator to automatically log the exception to the logger provided + +UserException - error, status_code - user rules error +error - a description of the error {code / description: classname / full text} +status_code - where possible use HTTP Error Codes +""" +import traceback + + +from request_api.exceptions.errors import Error # noqa: I001, I003 + + +class BusinessException(Exception): + """Exception that adds error code and error name, that can be used for i18n support.""" + + def __init__(self, error, exception=None, *args, **kwargs): + """Return a valid BusinessException.""" + super().__init__(*args, **kwargs) + + self.message = error.message + self.error = error.message + self.code = error.name + self.status_code = error.status_code + self.detail = exception + + # log/tracing exception + #ExceptionTracing.trace(self, traceback.format_exc()) + + +class ServiceUnavailableException(Exception): + """Exception to be raised if third party service is unavailable.""" + + def __init__(self, error, *args, **kwargs): + """Return a valid BusinessException.""" + super().__init__(*args, **kwargs) + self.error = error + self.status_code = Error.SERVICE_UNAVAILABLE.name + + +class CustomException: + """A custom exception object to be used propagate errors.""" + + def __init__(self, message, status_code, name=None): + """Return a Custom exception when enum cant be used.""" + self.message = message + self.status_code = status_code + self.name = name diff --git a/historical-search-api/request_api/exceptions/errors.py b/historical-search-api/request_api/exceptions/errors.py new file mode 100644 index 000000000..2db106987 --- /dev/null +++ b/historical-search-api/request_api/exceptions/errors.py @@ -0,0 +1,41 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Descriptive error codes and descriptions for readability. + +Standardize error message to display user friendly messages. +""" + +from enum import Enum + +from request_api import status as http_status + + +class Error(Enum): + """Error Codes.""" + + INVALID_INPUT = 'Invalid input, please check.', http_status.HTTP_400_BAD_REQUEST + DATA_NOT_FOUND = 'No matching record found.', http_status.HTTP_404_NOT_FOUND + DATA_ALREADY_EXISTS = 'The data you want to insert already exists.', http_status.HTTP_400_BAD_REQUEST + INVALID_USER_CREDENTIALS = 'Invalid user credentials.', http_status.HTTP_401_UNAUTHORIZED + INVALID_REFRESH_TOKEN = 'Invalid refresh token.', http_status.HTTP_400_BAD_REQUEST + UNDEFINED_ERROR = 'Undefined error.', http_status.HTTP_400_BAD_REQUEST + SERVICE_UNAVAILABLE = 'Service Unavailable', http_status.HTTP_500_INTERNAL_SERVER_ERROR + MISSING_ACCESS_KEY = 'S3 Access Key missing in Document Path Mapper attributes', http_status.HTTP_500_INTERNAL_SERVER_ERROR + + def __new__(cls, message, status_code): + """Attributes for the enum.""" + obj = object.__new__(cls) + obj.message = message + obj.status_code = status_code + return obj diff --git a/historical-search-api/request_api/models/ApplicantCategories.py b/historical-search-api/request_api/models/ApplicantCategories.py new file mode 100644 index 000000000..08f90109a --- /dev/null +++ b/historical-search-api/request_api/models/ApplicantCategories.py @@ -0,0 +1,28 @@ +from .db import db, ma +from .default_method_result import DefaultMethodResult + + +class ApplicantCategory(db.Model): + __tablename__ = 'ApplicantCategories' + # Defining the columns + applicantcategoryid = db.Column(db.Integer, primary_key=True,autoincrement=True) + name = db.Column(db.String(100), unique=False, nullable=False) + description = db.Column(db.String(255), unique=False, nullable=True) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + + @classmethod + def getapplicantcategories(cls): + applicantcategory_schema = ApplicantCategorySchema(many=True) + query = db.session.query(ApplicantCategory).filter_by(isactive=True).all() + return applicantcategory_schema.dump(query) + + @classmethod + def getapplicantcategory(cls,appltcategory): + applicantcategory_schema = ApplicantCategorySchema() + query = db.session.query(ApplicantCategory).filter_by(name=appltcategory).first() + return applicantcategory_schema.dump(query) + + +class ApplicantCategorySchema(ma.Schema): + class Meta: + fields = ('applicantcategoryid', 'name', 'description','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/ApplicationCorrespondenceTemplates.py b/historical-search-api/request_api/models/ApplicationCorrespondenceTemplates.py new file mode 100644 index 000000000..8aae86aa6 --- /dev/null +++ b/historical-search-api/request_api/models/ApplicationCorrespondenceTemplates.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +from datetime import date, datetime +import string + +from sqlalchemy import ForeignKey + +from .db import db, ma + + +class ApplicationCorrespondenceTemplate(db.Model): + __tablename__ = 'ApplicantCorrespondenceTemplates' + # Defining the columns + + templateid = db.Column(db.Integer, primary_key=True, autoincrement=True) + documenturipath = db.Column(db.Text, nullable=False) + description = db.Column(db.String(1000), nullable=True) + name = db.Column(db.String(500), nullable=False) + active = db.Column(db.Boolean, nullable=False) + display = db.Column(db.Boolean, nullable=False) + version = db.Column(db.Integer, nullable=False) + created_at = db.Column(db.DateTime, default=datetime.now) + createdby = db.Column(db.String(120), unique=False, nullable=True) + + @classmethod + def get_template_by_id(cls, templateid: int): + """Given a type and optionally an extension, return the template.""" + + query = cls.query.filter_by(templateid = templateid). \ + filter(ApplicationCorrespondenceTemplate.templateid == templateid) + + return query.one_or_none() + + @classmethod + def get_template_by_name(cls, name: string): + """Given a type and optionally an extension, return the template.""" + + query = cls.query.filter_by(name = name). \ + filter(ApplicationCorrespondenceTemplate.name == name) + + return query.one_or_none() + + @classmethod + def getapplicantcorrespondencetemplates(cls): + correspondencetemplate_schema = ApplicationCorrespondenceTemplateSchema(many=True) + query = db.session.query(ApplicationCorrespondenceTemplate).filter_by(active=True, display=True).all() + return correspondencetemplate_schema.dump(query) + + @staticmethod + def commit(): + """Commit the session.""" + db.session.commit() + + def flush(self): + """Save and flush.""" + db.session.add(self) + db.session.flush() + return self + + +class ApplicationCorrespondenceTemplateSchema(ma.Schema): + class Meta: + fields = ('templateid','documenturipath', 'description','name','active', 'display','version','created_at','createdby') diff --git a/historical-search-api/request_api/models/CFRFeeStatus.py b/historical-search-api/request_api/models/CFRFeeStatus.py new file mode 100644 index 000000000..31ef05cfc --- /dev/null +++ b/historical-search-api/request_api/models/CFRFeeStatus.py @@ -0,0 +1,41 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey +from .db import db, ma +from datetime import datetime as datetime2 +from sqlalchemy.orm import relationship,backref +from .default_method_result import DefaultMethodResult +from sqlalchemy.dialects.postgresql import JSON, UUID +from sqlalchemy.sql.expression import distinct +from sqlalchemy import text, insert +import logging +from sqlalchemy import func + +class CFRFeeStatus(db.Model): + __tablename__ = 'CFRFeeStatuses' + # Defining the columns + cfrfeestatusid = db.Column(db.Integer, primary_key=True,autoincrement=True) + name = db.Column(db.String(25), unique=False, nullable=False) + description = db.Column(db.String(100), unique=False, nullable=False) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + + @classmethod + def getallcfrfeestatuses(cls): + cfrfeestatus_schema = CFRFeeStatusSchema(many=True) + query = db.session.query(CFRFeeStatus).filter_by(isactive=True).order_by(CFRFeeStatus.cfrfeestatusid.asc()).all() + return cfrfeestatus_schema.dump(query) + + @classmethod + def getcfrfeestatus(cls,cfrfeestatusid): + cfrfeestatus_schema = CFRFeeStatusSchema(many=False) + query = db.session.query(CFRFeeStatus).filter_by(cfrfeestatusid=cfrfeestatusid).first() + return cfrfeestatus_schema.dump(query) + + @classmethod + def getcfrfeestatusid(cls,cfrfeestatus): + cfrfeestatus_schema = CFRFeeStatusSchema(many=False) + query = db.session.query(CFRFeeStatus).filter(func.lower(CFRFeeStatus.name) == func.lower(cfrfeestatus)).first() + return cfrfeestatus_schema.dump(query) + +class CFRFeeStatusSchema(ma.Schema): + class Meta: + fields = ('cfrfeestatusid','name','description','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/CFRFormReason.py b/historical-search-api/request_api/models/CFRFormReason.py new file mode 100644 index 000000000..cc2b4e16b --- /dev/null +++ b/historical-search-api/request_api/models/CFRFormReason.py @@ -0,0 +1,41 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey +from .db import db, ma +from datetime import datetime as datetime2 +from sqlalchemy.orm import relationship,backref +from .default_method_result import DefaultMethodResult +from sqlalchemy.dialects.postgresql import JSON, UUID +from sqlalchemy.sql.expression import distinct +from sqlalchemy import text, insert +import logging +from sqlalchemy import func + +class CFRFormReason(db.Model): + __tablename__ = 'CFRFormReasons' + # Defining the columns + cfrformreasonid = db.Column(db.Integer, primary_key=True,autoincrement=True) + name = db.Column(db.String(25), unique=False, nullable=False) + description = db.Column(db.String(100), unique=False, nullable=False) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + + @classmethod + def getallcfrformreasons(cls): + cfrformreason_schema = CFRFormReasonSchema(many=True) + query = db.session.query(CFRFormReason).filter_by(isactive=True).order_by(CFRFormReason.cfrformreasonid.asc()).all() + return cfrformreason_schema.dump(query) + + @classmethod + def getcfrformreason(cls,cfrformreasonid): + cfrformreason_schema = CFRFormReasonSchema(many=False) + query = db.session.query(CFRFormReason).filter_by(cfrformreasonid=cfrformreasonid).first() + return cfrformreason_schema.dump(query) + + @classmethod + def getcfrformreasonid(cls,cfrformreason): + cfrformreason_schema = CFRFormReasonSchema(many=False) + query = db.session.query(CFRFormReason).filter(func.lower(CFRFormReason.name) == func.lower(cfrformreason)).first() + return cfrformreason_schema.dump(query) + +class CFRFormReasonSchema(ma.Schema): + class Meta: + fields = ('cfrformreasonid','name','description','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/CloseReasons.py b/historical-search-api/request_api/models/CloseReasons.py new file mode 100644 index 000000000..729bc779a --- /dev/null +++ b/historical-search-api/request_api/models/CloseReasons.py @@ -0,0 +1,32 @@ +from .db import db, ma +from .default_method_result import DefaultMethodResult +from sqlalchemy.orm import relationship,backref +from datetime import datetime +from sqlalchemy import text + +class CloseReason(db.Model): + __tablename__ = 'CloseReasons' + # Defining the columns + closereasonid = db.Column(db.Integer, primary_key=True,autoincrement=True) + name = db.Column(db.String(500), unique=False, nullable=False) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + created_at = db.Column(db.DateTime, default=datetime.now) + createdby = db.Column(db.String(120), unique=False, default='System') + + @classmethod + def getallclosereasons(cls): + closereason_schema = CloseReasonSchema(many=True) + query = db.session.query(CloseReason).filter_by(isactive=True).order_by(CloseReason.closereasonid.asc()).all() + return closereason_schema.dump(query) + + @classmethod + def getclosereason(cls,closereasonid): + closereason_schema = CloseReasonSchema(many=True) + query = db.session.query(CloseReason).filter_by(closereasonid=closereasonid).first() + return closereason_schema.dump(query) + + + +class CloseReasonSchema(ma.Schema): + class Meta: + fields = ('closereasonid','name','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/CommentTypes.py b/historical-search-api/request_api/models/CommentTypes.py new file mode 100644 index 000000000..f85d6e22b --- /dev/null +++ b/historical-search-api/request_api/models/CommentTypes.py @@ -0,0 +1,24 @@ +from .db import db, ma +from .default_method_result import DefaultMethodResult +from sqlalchemy.orm import relationship,backref +from datetime import datetime +from sqlalchemy import text + +class CommentType(db.Model): + __tablename__ = 'CommentTypes' + # Defining the columns + commenttypeid = db.Column(db.Integer, primary_key=True,autoincrement=True) + name = db.Column(db.String(100), unique=False, nullable=False) + description = db.Column(db.String(255), unique=False, nullable=False) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + + @classmethod + def getcommenttypes(cls): + commenttype_schema = CommentTypeSchema(many=True) + query = db.session.query(CommentType).filter_by(isactive=True).all() + return commenttype_schema.dump(query) + + +class CommentTypeSchema(ma.Schema): + class Meta: + fields = ('commenttypeid', 'name', 'description','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/ContactTypes.py b/historical-search-api/request_api/models/ContactTypes.py new file mode 100644 index 000000000..4e4301820 --- /dev/null +++ b/historical-search-api/request_api/models/ContactTypes.py @@ -0,0 +1,22 @@ +from .db import db, ma +from .default_method_result import DefaultMethodResult + + +class ContactType(db.Model): + __tablename__ = 'ContactTypes' + # Defining the columns + contacttypeid = db.Column(db.Integer, primary_key=True,autoincrement=True) + name = db.Column(db.String(100), unique=False, nullable=False) + description = db.Column(db.String(255), unique=False, nullable=True) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + + @classmethod + def getcontacttypes(cls): + contacttype_schema = ContactTypeSchema(many=True) + query = db.session.query(ContactType).filter_by(isactive=True).all() + return contacttype_schema.dump(query) + + +class ContactTypeSchema(ma.Schema): + class Meta: + fields = ('contacttypeid', 'name', 'description','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/DeliveryModes.py b/historical-search-api/request_api/models/DeliveryModes.py new file mode 100644 index 000000000..dbe51af6c --- /dev/null +++ b/historical-search-api/request_api/models/DeliveryModes.py @@ -0,0 +1,27 @@ +from .db import db, ma +from .default_method_result import DefaultMethodResult + + +class DeliveryMode(db.Model): + __tablename__ = 'DeliveryModes' + # Defining the columns + deliverymodeid = db.Column(db.Integer, primary_key=True,autoincrement=True) + name = db.Column(db.String(100), unique=False, nullable=False) + description = db.Column(db.String(255), unique=False, nullable=True) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + + @classmethod + def getdeliverymodes(cls): + deliverymode_schema = DeliveryModeSchema(many=True) + query = db.session.query(DeliveryMode).filter_by(isactive=True).all() + return deliverymode_schema.dump(query) + + @classmethod + def getdeliverymode(cls,deliverymode): + deliverymode_schema = DeliveryModeSchema() + query = db.session.query(DeliveryMode).filter_by(name=deliverymode).first() + return deliverymode_schema.dump(query) + +class DeliveryModeSchema(ma.Schema): + class Meta: + fields = ('deliverymodeid', 'name', 'description','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/DocumentPathMapper.py b/historical-search-api/request_api/models/DocumentPathMapper.py new file mode 100644 index 000000000..1e168c5a8 --- /dev/null +++ b/historical-search-api/request_api/models/DocumentPathMapper.py @@ -0,0 +1,41 @@ +from .db import db, ma +from .default_method_result import DefaultMethodResult +from datetime import datetime +from request_api.utils.enums import DocumentPathMapperCategory +from request_api.exceptions import BusinessException +import json +from sqlalchemy import func +from request_api import status as http_status +from request_api.exceptions.errors import Error + +class DocumentPathMapper(db.Model): + __tablename__ = 'DocumentPathMapper' + # Defining the columns + documentpathid = db.Column(db.Integer, primary_key=True,autoincrement=True) + category = db.Column(db.Text, unique=False, nullable=False) + bucket = db.Column(db.Text, unique=False, nullable=False) + attributes = db.Column(db.Text, unique=False, nullable=False) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + 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) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + + @classmethod + def getdocumentpath(cls, category, programarea=None): + documentpath_schema = DocumentPathMapperSchema() + query = db.session.query(DocumentPathMapper).filter(DocumentPathMapper.isactive == True, func.lower(DocumentPathMapper.category) == category.lower()) + if category.lower() == DocumentPathMapperCategory.Records.value.lower(): + query = query.filter(DocumentPathMapper.bucket.ilike(programarea.lower() + '%')) + pathmap = documentpath_schema.dump(query.first()) + try: + pathmap['attributes'] = json.loads(pathmap['attributes']) + except TypeError: + raise BusinessException(Error.MISSING_ACCESS_KEY) + + return pathmap + + +class DocumentPathMapperSchema(ma.Schema): + class Meta: + fields = ('documentpathid', 'category', 'bucket','attributes') \ No newline at end of file diff --git a/historical-search-api/request_api/models/DocumentTemplate.py b/historical-search-api/request_api/models/DocumentTemplate.py new file mode 100644 index 000000000..5b26e99ce --- /dev/null +++ b/historical-search-api/request_api/models/DocumentTemplate.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +from datetime import date, datetime + +from sqlalchemy import ForeignKey + +from .db import db, ma + + +class DocumentTemplate(db.Model): + __tablename__ = 'DocumentTemplates' + # Defining the columns + + template_id = db.Column(db.Integer, primary_key=True, autoincrement=True) + document_type_id = db.Column(db.Integer, ForeignKey('DocumentTypes.document_type_id'), nullable=False) + cdogs_hash_code = db.Column(db.String(64), nullable=True, unique=True) + extension = db.Column(db.String(10), nullable=False) + + @classmethod + def get_template_by_type(cls, document_type_id: int, extension: str = "docx"): + """Given a type and optionally an extension, return the template.""" + + query = cls.query.filter_by(document_type_id = document_type_id). \ + filter(DocumentTemplate.extension == extension) + + return query.one_or_none() + + @staticmethod + def commit(): + """Commit the session.""" + db.session.commit() + + def flush(self): + """Save and flush.""" + db.session.add(self) + db.session.flush() + return self + + +class DocumentTemplateSchema(ma.Schema): + class Meta: + model = DocumentTemplate + exclude = [] diff --git a/historical-search-api/request_api/models/DocumentType.py b/historical-search-api/request_api/models/DocumentType.py new file mode 100644 index 000000000..d5a7d9db5 --- /dev/null +++ b/historical-search-api/request_api/models/DocumentType.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +from datetime import date, datetime + +from sqlalchemy import ForeignKey + +from .db import db, ma + + +class DocumentType(db.Model): + __tablename__ = 'DocumentTypes' + # Defining the columns + + document_type_id = db.Column(db.Integer, primary_key=True, autoincrement=True) + document_type_name = db.Column(db.String(30), nullable=False, unique=False) + description = db.Column(db.String(100), unique=False, nullable=True) + + @classmethod + def get_document_type_by_name(cls, document_type_name: str): + """Given a type and optionally an extension, return the template.""" + + query = cls.query.filter_by(document_type_name = document_type_name) + + return query.one_or_none() + + @staticmethod + def commit(): + """Commit the session.""" + db.session.commit() + + def flush(self): + """Save and flush.""" + db.session.add(self) + db.session.flush() + return self + + +class DocumentTypeSchema(ma.Schema): + class Meta: + model = DocumentType + exclude = [] diff --git a/historical-search-api/request_api/models/ExtensionReasons.py b/historical-search-api/request_api/models/ExtensionReasons.py new file mode 100644 index 000000000..d7f88b33e --- /dev/null +++ b/historical-search-api/request_api/models/ExtensionReasons.py @@ -0,0 +1,28 @@ +from .db import db, ma + +class ExtensionReason(db.Model): + __tablename__ = 'ExtensionReasons' + # Defining the columns + extensionreasonid = db.Column(db.Integer, primary_key=True,autoincrement=True) + reason = db.Column(db.String(100), unique=False, nullable=False) + extensiontype = db.Column(db.String(25), unique=False, nullable=False) + defaultextendedduedays = db.Column(db.Integer, unique=False, nullable=True) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + + @classmethod + def getallextensionreasons(cls): + extensionreason_schema = ExtensionReasonSchema(many=True) + query = db.session.query(ExtensionReason).filter_by(isactive=True).order_by(ExtensionReason.extensionreasonid.asc()).all() + return extensionreason_schema.dump(query) + + @classmethod + def getextensionreasonbyid(cls,extensionreasonid): + extensionreason_schema = ExtensionReasonSchema() + query = db.session.query(ExtensionReason).filter_by(extensionreasonid=extensionreasonid).first() + return extensionreason_schema.dump(query) + + + +class ExtensionReasonSchema(ma.Schema): + class Meta: + fields = ('extensionreasonid','reason','extensiontype','isactive', 'defaultextendedduedays') \ No newline at end of file diff --git a/historical-search-api/request_api/models/ExtensionStatuses.py b/historical-search-api/request_api/models/ExtensionStatuses.py new file mode 100644 index 000000000..2a179860d --- /dev/null +++ b/historical-search-api/request_api/models/ExtensionStatuses.py @@ -0,0 +1,27 @@ +from .db import db, ma + +class ExtensionStatus(db.Model): + __tablename__ = 'ExtensionStatuses' + # Defining the columns + extensionstatusid = db.Column(db.Integer, primary_key=True,autoincrement=True) + name = db.Column(db.String(25), unique=False, nullable=False) + description = db.Column(db.String(100), unique=False, nullable=False) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + + @classmethod + def getallextensionstatuses(cls): + extensionstatus_schema = ExtensionStatusSchema(many=True) + query = db.session.query(ExtensionStatus).filter_by(isactive=True).order_by(ExtensionStatus.extensionstatusid.asc()).all() + return extensionstatus_schema.dump(query) + + @classmethod + def getextensionstatus(cls,extensionstatusid): + extensionstatus_schema = ExtensionStatusSchema(many=True) + query = db.session.query(ExtensionStatus).filter_by(extensionstatusid=extensionstatusid).first() + return extensionstatus_schema.dump(query) + + + +class ExtensionStatusSchema(ma.Schema): + class Meta: + fields = ('extensionstatusid','name','description','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIApplicantCorrespondenceAttachments.py b/historical-search-api/request_api/models/FOIApplicantCorrespondenceAttachments.py new file mode 100644 index 000000000..fd267214f --- /dev/null +++ b/historical-search-api/request_api/models/FOIApplicantCorrespondenceAttachments.py @@ -0,0 +1,50 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint +from .db import db, ma +from datetime import datetime +from sqlalchemy.orm import relationship,backref +from .default_method_result import DefaultMethodResult +from sqlalchemy.sql.expression import distinct +from sqlalchemy import or_,and_,text + +class FOIApplicantCorrespondenceAttachment(db.Model): + # Name of the table in our database + __tablename__ = 'FOIApplicantCorrespondenceAttachments' + __table_args__ = ( + ForeignKeyConstraint( + ["applicantcorrespondenceid"], ["FOIApplicantCorrespondences.applicantcorrespondenceid"] + ), + ) + + # Defining the columns + applicantcorrespondenceattachmentid = db.Column(db.Integer, primary_key=True,autoincrement=True) + attachmentdocumenturipath = db.Column(db.Text, unique=False, nullable=False) + attachmentfilename = db.Column(db.String(500), unique=False, nullable=False) + + created_at = db.Column(db.DateTime, default=datetime.now) + updated_at = db.Column(db.DateTime, nullable=True) + createdby = db.Column(db.String(120), unique=False, nullable=False) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + + applicantcorrespondenceid =db.Column(db.Integer, db.ForeignKey('FOIApplicantCorrespondences.applicantcorrespondenceid')) + + + + + @classmethod + def saveapplicantcorrespondenceattachment(cls, newapplicantcorrepondenceattachment)->DefaultMethodResult: + + db.session.add(newapplicantcorrepondenceattachment) + db.session.commit() + return DefaultMethodResult(True,'applicant correpondence attachment added',newapplicantcorrepondenceattachment.applicantcorrespondenceattachmentid) + + @classmethod + def getapplicantcorrespondenceattachmentsbyapplicantcorrespondenceid(cls,applicantcorrespondenceid): + correspondenceattachment_schema = FOIApplicantCorrespondenceAttachmentSchema(many=True) + query = db.session.query(FOIApplicantCorrespondenceAttachment).filter(FOIApplicantCorrespondenceAttachment.applicantcorrespondenceid == applicantcorrespondenceid).order_by(FOIApplicantCorrespondenceAttachment.applicantcorrespondenceattachmentid.asc()).all() + return correspondenceattachment_schema.dump(query) + +class FOIApplicantCorrespondenceAttachmentSchema(ma.Schema): + class Meta: + fields = ('applicantcorrespondenceattachmentid','applicantcorrespondenceid', 'attachmentdocumenturipath','attachmentfilename','created_at','createdby') + \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIApplicantCorrespondences.py b/historical-search-api/request_api/models/FOIApplicantCorrespondences.py new file mode 100644 index 000000000..2469b8d3d --- /dev/null +++ b/historical-search-api/request_api/models/FOIApplicantCorrespondences.py @@ -0,0 +1,93 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint +from .db import db, ma +from datetime import datetime +from sqlalchemy.orm import relationship,backref +from .default_method_result import DefaultMethodResult +from sqlalchemy.sql.expression import distinct +from sqlalchemy import or_,and_,text +from .FOIApplicantCorrespondenceAttachments import FOIApplicantCorrespondenceAttachment +from sqlalchemy.dialects.postgresql import JSON, UUID + +class FOIApplicantCorrespondence(db.Model): + # Name of the table in our database + __tablename__ = 'FOIApplicantCorrespondences' + __table_args__ = ( + ForeignKeyConstraint( + ["foiministryrequest_id", "foiministryrequestversion_id"], ["FOIMinistryRequests.foiministryrequestid", "FOIMinistryRequests.version"] + ), + ) + + # Defining the columns + applicantcorrespondenceid = db.Column(db.Integer, primary_key=True,autoincrement=True) + parentapplicantcorrespondenceid = db.Column(db.Integer) + templateid = db.Column(db.Integer, nullable=True) + correspondencemessagejson = db.Column(db.Text, unique=False, nullable=True) + + sentcorrespondencemessage = db.Column(JSON, unique=False, nullable=True) + sent_at = db.Column(db.DateTime, nullable=True) + sentby = db.Column(db.String(120), unique=False, nullable=True) + + created_at = db.Column(db.DateTime, default=datetime.now) + updated_at = db.Column(db.DateTime, nullable=True) + createdby = db.Column(db.String(120), unique=False, nullable=False) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + + #ForeignKey References + foiministryrequest_id =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.foiministryrequestid')) + foiministryrequestversion_id=db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.version')) + + attachments = relationship('FOIApplicantCorrespondenceAttachment', backref=backref("FOIApplicantCorrespondenceAttachments")) + + @classmethod + def getapplicantcorrespondences(cls,ministryrequestid): + comment_schema = FOIApplicantCorrespondenceSchema(many=True) + query = db.session.query(FOIApplicantCorrespondence).filter(FOIApplicantCorrespondence.foiministryrequest_id == ministryrequestid).order_by(FOIApplicantCorrespondence.applicantcorrespondenceid.desc()).all() + return comment_schema.dump(query) + + @classmethod + def getapplicantcorrespondencebyid(cls,applicantcorrespondenceid): + correspondence_schema = FOIApplicantCorrespondenceSchema() + query = db.session.query(FOIApplicantCorrespondence).filter(FOIApplicantCorrespondence.applicantcorrespondenceid == applicantcorrespondenceid).first() + return correspondence_schema.dump(query) + + @classmethod + def getlatestapplicantcorrespondence(cls,ministryrequestid): + correspondence_schema = FOIApplicantCorrespondenceSchema() + query = db.session.query(FOIApplicantCorrespondence).filter(FOIApplicantCorrespondence.foiministryrequest_id == ministryrequestid, FOIApplicantCorrespondence.createdby != 'System Generated Email').order_by(FOIApplicantCorrespondence.applicantcorrespondenceid.desc()).first() + return correspondence_schema.dump(query) + + @classmethod + def saveapplicantcorrespondence(cls, newapplicantcorrepondencelog,attachments)->DefaultMethodResult: + db.session.add(newapplicantcorrepondencelog) + db.session.commit() + try: + if(attachments is not None and len(attachments) > 0): + for _attachment in attachments: + attachment = FOIApplicantCorrespondenceAttachment() + attachment.applicantcorrespondenceid = newapplicantcorrepondencelog.applicantcorrespondenceid + attachment.attachmentdocumenturipath = _attachment['url'] + attachment.attachmentfilename = _attachment['filename'] + attachment.createdby = newapplicantcorrepondencelog.createdby + FOIApplicantCorrespondenceAttachment().saveapplicantcorrespondenceattachment(attachment) + except Exception: + return DefaultMethodResult(False,'applicantcorrepondence log exception while adding attachments',newapplicantcorrepondencelog.applicantcorrespondenceid) + + return DefaultMethodResult(True,'applicantcorrepondence log added',newapplicantcorrepondencelog.applicantcorrespondenceid) + + + @classmethod + def updatesentcorrespondence(cls, applicantcorrespondenceid, content)->DefaultMethodResult: + dbquery = db.session.query(FOIApplicantCorrespondence) + _correspondence = dbquery.filter_by(applicantcorrespondenceid=applicantcorrespondenceid) + if(_correspondence.count() > 0) : + _correspondence.update({FOIApplicantCorrespondence.sentcorrespondencemessage:content, FOIApplicantCorrespondence.sent_at:datetime.now(), FOIApplicantCorrespondence.sentby:"System Generated Email"}, synchronize_session = False) + db.session.commit() + return DefaultMethodResult(True,'Applicant correspondence updated for Id',applicantcorrespondenceid) + else: + return DefaultMethodResult(False,'Applicant correspondence not exists',-1) + +class FOIApplicantCorrespondenceSchema(ma.Schema): + class Meta: + fields = ('applicantcorrespondenceid','parentapplicantcorrespondenceid', 'templateid','correspondencemessagejson','foiministryrequest_id','foiministryrequestversion_id','created_at','createdby','attachments','sentcorrespondencemessage','sent_at','sentby') + \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIAssignees.py b/historical-search-api/request_api/models/FOIAssignees.py new file mode 100644 index 000000000..f5ebf9ab4 --- /dev/null +++ b/historical-search-api/request_api/models/FOIAssignees.py @@ -0,0 +1,44 @@ +from .db import db, ma +from .default_method_result import DefaultMethodResult +from sqlalchemy.dialects.postgresql import insert + +class FOIAssignee(db.Model): + # Name of the table in our database + __tablename__ = 'FOIAssignees' + # Defining the columns + foiassigneeid = db.Column(db.Integer, primary_key=True, autoincrement=True) + username = db.Column(db.String(120), unique=True, nullable=False) + firstname = db.Column(db.String(100), unique=False, nullable=False) + middlename = db.Column(db.String(100), unique=False, nullable=True) + lastname = db.Column(db.String(100), unique=False, nullable=False) + isactive = db.Column(db.Boolean, unique=False, nullable=False, default=True) + + @classmethod + def saveassignee(cls, username, firstname, middlename, lastname)->DefaultMethodResult: + + insertstmt = insert(FOIAssignee).values( + username=username, + firstname=firstname, + middlename=middlename, + lastname=lastname + ) + updatestmt = insertstmt.on_conflict_do_update(index_elements=[FOIAssignee.username], set_={"firstname": firstname,"middlename":middlename,"lastname":lastname}) + db.session.execute(updatestmt) + db.session.commit() + return DefaultMethodResult(True, 'Assignee added', username) + + @classmethod + def getassignees(cls): + assignee_schema = FOIAssigneeSchema(many=True) + query = db.session.query(FOIAssignee).filter_by(isactive=True).all() + return assignee_schema.dump(query) + + @classmethod + def getassignee(cls,username): + assignee_schema = FOIAssigneeSchema() + query = db.session.query(FOIAssignee).filter_by(username=username).first() + return assignee_schema.dump(query) + +class FOIAssigneeSchema(ma.Schema): + class Meta: + fields = ('foiassigneeid','username','firstname','middlename','lastname','isactive') diff --git a/historical-search-api/request_api/models/FOIMinistryRequestDivisions.py b/historical-search-api/request_api/models/FOIMinistryRequestDivisions.py new file mode 100644 index 000000000..33af2aeb8 --- /dev/null +++ b/historical-search-api/request_api/models/FOIMinistryRequestDivisions.py @@ -0,0 +1,58 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint +from .db import db, ma +from datetime import datetime +from sqlalchemy.orm import relationship,backref +from .default_method_result import DefaultMethodResult +from sqlalchemy.sql.expression import distinct +from sqlalchemy import or_, and_, text + + +class FOIMinistryRequestDivision(db.Model): + # Name of the table in our database + __tablename__ = 'FOIMinistryRequestDivisions' + __table_args__ = ( + ForeignKeyConstraint( + ["foiministryrequest_id", "foiministryrequestversion_id"], ["FOIMinistryRequests.foiministryrequestid", "FOIMinistryRequests.version"] + ), + ) + + + # Defining the columns + foiministrydivisionid = db.Column(db.Integer, primary_key=True,autoincrement=True) + + divisionid = db.Column(db.Integer,ForeignKey('ProgramAreaDivisions.divisionid')) + division = relationship("ProgramAreaDivision",backref=backref("ProgramAreaDivisions"),uselist=False) + + stageid = db.Column(db.Integer,ForeignKey('ProgramAreaDivisionStages.stageid')) + stage = relationship("ProgramAreaDivisionStage",backref=backref("ProgramAreaDivisionStages"),uselist=False) + + divisionduedate = db.Column(db.DateTime, nullable=True) + eapproval = db.Column(db.String(12), nullable=True) + + divisionreceiveddate = db.Column(db.DateTime, nullable=True) + + created_at = db.Column(db.DateTime, default=datetime.now) + updated_at = db.Column(db.DateTime, nullable=True) + createdby = db.Column(db.String(120), unique=False, nullable=True) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + + #ForeignKey References + foiministryrequest_id =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.foiministryrequestid')) + foiministryrequestversion_id = db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.version')) + foiministryrequest = relationship("FOIMinistryRequest",foreign_keys="[FOIMinistryRequestDivision.foiministryrequest_id]") + foiministryrequestversion = relationship("FOIMinistryRequest",foreign_keys="[FOIMinistryRequestDivision.foiministryrequestversion_id]") + + @classmethod + def getdivisions(cls,ministryrequestid,ministryrequestversion): + division_schema = FOIMinistryRequestDivisionSchema(many=True) + _divisions = db.session.query(FOIMinistryRequestDivision).filter(FOIMinistryRequestDivision.foiministryrequest_id == ministryrequestid , FOIMinistryRequestDivision.foiministryrequestversion_id == ministryrequestversion).order_by(FOIMinistryRequestDivision.foiministrydivisionid.asc()).all() + divisioninfos = division_schema.dump(_divisions) + return divisioninfos + + + +class FOIMinistryRequestDivisionSchema(ma.Schema): + class Meta: + fields = ('foiministrydivisionid','division.divisionid','division.name','stage.stageid','stage.name','foiministryrequest_id','foiministryrequestversion_id', 'divisionduedate', 'eapproval', 'divisionreceiveddate') + \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIMinistryRequestDocuments.py b/historical-search-api/request_api/models/FOIMinistryRequestDocuments.py new file mode 100644 index 000000000..3acdb80f2 --- /dev/null +++ b/historical-search-api/request_api/models/FOIMinistryRequestDocuments.py @@ -0,0 +1,159 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint +from .db import db, ma +from datetime import datetime +from sqlalchemy.orm import relationship,backref +from .default_method_result import DefaultMethodResult +from sqlalchemy.sql.expression import distinct +from sqlalchemy import or_,and_,text + +class FOIMinistryRequestDocument(db.Model): + # Name of the table in our database + __tablename__ = 'FOIMinistryRequestDocuments' + __table_args__ = ( + ForeignKeyConstraint( + ["foiministryrequest_id", "foiministryrequestversion_id"], ["FOIMinistryRequests.foiministryrequestid", "FOIMinistryRequests.version"] + ), + ) + + # Defining the columns + foiministrydocumentid = db.Column(db.Integer, primary_key=True,autoincrement=True) + documentpath = db.Column(db.String(1000), unique=False, nullable=False) + filename = db.Column(db.String(120), unique=False, nullable=True) + category = db.Column(db.String(120), unique=False, nullable=True) + version =db.Column(db.Integer, nullable=True) + isactive = db.Column(db.Boolean, unique=False, nullable=False,default=True) + + created_at = db.Column(db.DateTime, default=datetime.now) + updated_at = db.Column(db.DateTime, nullable=True) + createdby = db.Column(db.String(120), unique=False, nullable=True) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + + #ForeignKey References + foiministryrequest_id =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.foiministryrequestid')) + foiministryrequestversion_id = db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.version')) + foiministryrequest = relationship("FOIMinistryRequest",foreign_keys="[FOIMinistryRequestDocument.foiministryrequest_id]") + foiministryrequestversion = relationship("FOIMinistryRequest",foreign_keys="[FOIMinistryRequestDocument.foiministryrequestversion_id]") + + @classmethod + def getdocuments(cls,ministryrequestid,ministryrequestversion): + sql = 'SELECT * FROM (SELECT DISTINCT ON (foiministrydocumentid) foiministrydocumentid, filename, documentpath, category, isactive, created_at , createdby, version FROM "FOIMinistryRequestDocuments" where foiministryrequest_id =:ministryrequestid and foiministryrequestversion_id = :ministryrequestversion ORDER BY foiministrydocumentid, version DESC) AS list ORDER BY created_at DESC' + rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid, 'ministryrequestversion':ministryrequestversion}) + documents = [] + for row in rs: + if row["isactive"] == True: + documents.append({"foiministrydocumentid": row["foiministrydocumentid"], "filename": row["filename"], "documentpath": row["documentpath"], "category": row["category"], "created_at": row["created_at"].strftime('%Y-%m-%d %H:%M:%S.%f'), "createdby": row["createdby"], "version": row["version"]}) + return documents + + @classmethod + def getactivedocuments(cls,ministryrequestid): + 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: + if row["isactive"] == True: + documents.append({"foiministrydocumentid": row["foiministrydocumentid"], "filename": row["filename"], "documentpath": row["documentpath"], "category": row["category"], "created_at": row["created_at"].strftime('%Y-%m-%d %H:%M:%S.%f'), "createdby": row["createdby"], "version": row["version"]}) + return documents + + @classmethod + def getdocumentsbycategory(cls, ministryrequestid, ministryrequestversion, category): + sql = 'SELECT * FROM (SELECT DISTINCT ON (foiministrydocumentid) foiministrydocumentid, filename, documentpath, category, isactive, created_at , createdby, version FROM "FOIMinistryRequestDocuments" where foiministryrequest_id =:ministryrequestid and foiministryrequestversion_id = :ministryrequestversion and category = :category ORDER BY foiministrydocumentid, version DESC) AS list ORDER BY created_at DESC' + rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid, 'ministryrequestversion':ministryrequestversion, 'category': category}) + documents = [] + for row in rs: + if row["isactive"] == True: + documents.append({"foiministrydocumentid": row["foiministrydocumentid"], "filename": row["filename"], "documentpath": row["documentpath"], "category": row["category"], "created_at": row["created_at"].strftime('%Y-%m-%d %H:%M:%S.%f'), "createdby": row["createdby"]}) + return documents + @classmethod + def getdocument(cls,foiministrydocumentid): + document_schema = FOIMinistryRequestDocumentSchema() + request = db.session.query(FOIMinistryRequestDocument).filter_by(foiministrydocumentid=foiministrydocumentid).order_by(FOIMinistryRequestDocument.version.desc()).first() + return document_schema.dump(request) + + @classmethod + def createdocuments(cls,ministryrequestid,ministryrequestversion, documents, userid): + newdocuments = [] + for document in documents: + createuserid = document['createdby'] if 'createdby' in document and document['createdby'] is not None else userid + createdat = document['created_at'] if 'created_at' in document and document['created_at'] is not None else datetime.now() + newdocuments.append(FOIMinistryRequestDocument(documentpath=document["documentpath"], version='1', filename=document["filename"], category=document["category"], isactive=True, foiministryrequest_id=ministryrequestid, foiministryrequestversion_id=ministryrequestversion, created_at=createdat, createdby=createuserid)) + db.session.add_all(newdocuments) + db.session.commit() + return DefaultMethodResult(True,'Documents created') + + @classmethod + def createdocument(cls,ministryrequestid,ministryrequestversion, document, userid): + createuserid = document['createdby'] if 'createdby' in document and document['createdby'] is not None else userid + createdat = document['created_at'] if 'created_at' in document and document['created_at'] is not None else datetime.now() + newdocument = FOIMinistryRequestDocument(documentpath=document["documentpath"], version='1', filename=document["filename"], category=document["category"], isactive=True, foiministryrequest_id=ministryrequestid, foiministryrequestversion_id=ministryrequestversion, created_at=createdat, createdby=createuserid) + db.session.add(newdocument) + db.session.commit() + return DefaultMethodResult(True,'New Document version created', newdocument.foiministrydocumentid) + + @classmethod + def createdocumentversion(cls,ministryrequestid,ministryrequestversion, document, userid): + newdocument = FOIMinistryRequestDocument(documentpath=document["documentpath"], foiministrydocumentid=document["foiministrydocumentid"], version=document["version"], filename=document["filename"], category=document["category"], isactive=document["isactive"], foiministryrequest_id=ministryrequestid, foiministryrequestversion_id=ministryrequestversion, created_at=datetime.now(), createdby=userid) + db.session.add(newdocument) + db.session.commit() + return DefaultMethodResult(True,'New Document version created', newdocument.foiministrydocumentid) + + @classmethod + + def getlatestdocumentsforemail(cls, ministryrequestid, ministryrequestversion, category): + sql = 'SELECT DISTINCT ON (foiministrydocumentid) foiministrydocumentid, filename, documentpath, category, isactive, created_at , createdby, version FROM "FOIMinistryRequestDocuments" where foiministryrequest_id =:ministryrequestid and foiministryrequestversion_id = :ministryrequestversion and lower(category) = lower(:category) ORDER BY foiministrydocumentid DESC' + + rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid, 'ministryrequestversion':ministryrequestversion, 'category': category}) + documents = [] + for row in rs: + if row["isactive"] == True: + documents.append({"foiministrydocumentid": row["foiministrydocumentid"], "filename": row["filename"], "documentpath": row["documentpath"], "category": row["category"], "created_at": row["created_at"].strftime('%Y-%m-%d %H:%M:%S.%f'), "createdby": row["createdby"]}) + return documents + + def getlatestreceiptdocumentforemail(cls, ministryrequestid, category): + sql = 'SELECT DISTINCT ON (foiministrydocumentid) foiministrydocumentid, filename, documentpath, category, isactive, created_at , createdby, version FROM "FOIMinistryRequestDocuments" where foiministryrequest_id =:ministryrequestid and lower(category) = lower(:category) ORDER BY foiministrydocumentid DESC limit 1' + + rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid, 'category': category}) + documents = [] + for row in rs: + if row["isactive"] == True: + documents.append({"foiministrydocumentid": row["foiministrydocumentid"], "filename": row["filename"], "documentpath": row["documentpath"], "category": row["category"], "created_at": row["created_at"].strftime('%Y-%m-%d %H:%M:%S.%f'), "createdby": row["createdby"]}) + return documents + + @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.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.commit() + return DefaultMethodResult(True,'Documents Updated for the ministry',ministryid) + +class FOIMinistryRequestDocumentSchema(ma.Schema): + class Meta: + fields = ('foiministrydocumentid','documentpath', 'filename','category','version','isactive','foiministryrequest_id','foiministryrequestversion_id','created_at','createdby') + diff --git a/historical-search-api/request_api/models/FOIMinistryRequestSubjectCodes.py b/historical-search-api/request_api/models/FOIMinistryRequestSubjectCodes.py new file mode 100644 index 000000000..d1f363122 --- /dev/null +++ b/historical-search-api/request_api/models/FOIMinistryRequestSubjectCodes.py @@ -0,0 +1,54 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint +from .db import db, ma +from datetime import datetime +from sqlalchemy.orm import relationship,backref +from .default_method_result import DefaultMethodResult +from sqlalchemy.sql.expression import distinct +from sqlalchemy import or_, and_, text + + +class FOIMinistryRequestSubjectCode(db.Model): + # Name of the table in our database + __tablename__ = 'FOIMinistryRequestSubjectCodes' + __table_args__ = ( + ForeignKeyConstraint( + ["foiministryrequestid", "foiministryrequestversion"], ["FOIMinistryRequests.foiministryrequestid", "FOIMinistryRequests.version"] + ), + ) + + + # Defining the columns + foiministrysubjectcodeid = db.Column(db.Integer, primary_key=True,autoincrement=True) + subjectcodeid = db.Column(db.Integer,ForeignKey('SubjectCodes.subjectcodeid')) + subjectcode = relationship("SubjectCode",backref=backref("SubjectCodes"),uselist=False) + + created_at = db.Column(db.DateTime, default=datetime.now) + updated_at = db.Column(db.DateTime, nullable=True) + createdby = db.Column(db.String(120), unique=False, nullable=True) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + + #ForeignKey References + foiministryrequestid =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.foiministryrequestid')) + foiministryrequestversion = db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.version')) + ministryrequest = relationship("FOIMinistryRequest",foreign_keys="[FOIMinistryRequestSubjectCode.foiministryrequestid]") + ministryrequestversion = relationship("FOIMinistryRequest",foreign_keys="[FOIMinistryRequestSubjectCode.foiministryrequestversion]") + + @classmethod + def getministrysubjectcode(cls, ministryrequestid, ministryrequestversion): + ministrysubjectcode_schema = FOIMinistryRequestSubjectCodeSchema() + ministrysubjectcode = db.session.query(FOIMinistryRequestSubjectCode).filter(FOIMinistryRequestSubjectCode.foiministryrequestid == ministryrequestid , FOIMinistryRequestSubjectCode.foiministryrequestversion == ministryrequestversion).first() + subjectcodeinfos = ministrysubjectcode_schema.dump(ministrysubjectcode) + return subjectcodeinfos + + @classmethod + def savesubjectcode(cls, ministryrequestid, ministryrequestversion, subjectcodeid, userid): + newsubjectcode = FOIMinistryRequestSubjectCode(subjectcodeid=subjectcodeid, foiministryrequestid=ministryrequestid, foiministryrequestversion=ministryrequestversion, created_at=datetime.now(), createdby=userid) + db.session.add(newsubjectcode) + db.session.commit() + return DefaultMethodResult(True,'New subject code added', newsubjectcode.foiministrysubjectcodeid) + +class FOIMinistryRequestSubjectCodeSchema(ma.Schema): + class Meta: + fields = ('foiministrysubjectcodeid','subjectcodeid','subjectcode.name','foiministryrequestid','foiministryrequestversion') + \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIMinistryRequests.py b/historical-search-api/request_api/models/FOIMinistryRequests.py new file mode 100644 index 000000000..782a3b78b --- /dev/null +++ b/historical-search-api/request_api/models/FOIMinistryRequests.py @@ -0,0 +1,1535 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint +from .db import db, ma +from datetime import datetime +from sqlalchemy.orm import relationship, backref, aliased +from .default_method_result import DefaultMethodResult +from .FOIRequests import FOIRequest, FOIRequestsSchema +from sqlalchemy.sql.expression import distinct +from sqlalchemy import or_, and_, text, func, literal, cast, case, nullslast, nullsfirst, desc, asc +from sqlalchemy.sql.sqltypes import String +from sqlalchemy.dialects.postgresql import JSON +from .FOIRequestApplicantMappings import FOIRequestApplicantMapping +from .FOIRequestApplicants import FOIRequestApplicant +from .FOIRequestStatus import FOIRequestStatus +from .ApplicantCategories import ApplicantCategory +from .FOIRequestWatchers import FOIRequestWatcher +from .FOIRestrictedMinistryRequests import FOIRestrictedMinistryRequest +from .ProgramAreas import ProgramArea +from request_api.utils.enums import ProcessingTeamWithKeycloackGroup, IAOTeamWithKeycloackGroup +from .FOIAssignees import FOIAssignee +from .FOIRequestExtensions import FOIRequestExtension +from request_api.utils.enums import RequestorType +import logging +from sqlalchemy.sql.sqltypes import Date, Integer +from dateutil import parser +from request_api.utils.enums import StateName +from .FOIMinistryRequestSubjectCodes import FOIMinistryRequestSubjectCode +from .SubjectCodes import SubjectCode +from request_api.utils.enums import StateName +from .FOIRequestOIPC import FOIRequestOIPC + +class FOIMinistryRequest(db.Model): + # Name of the table in our database + __tablename__ = 'FOIMinistryRequests' + __table_args__ = ( + ForeignKeyConstraint( + ["foirequest_id", "foirequestversion_id"], ["FOIRequests.foirequestid", "FOIRequests.version"] + ), + ) + + # Defining the columns + foiministryrequestid = db.Column(db.Integer, primary_key=True,autoincrement=True) + version = db.Column(db.Integer, primary_key=True,nullable=False) + isactive = db.Column(db.Boolean, unique=False, nullable=False,default=True) + + filenumber = db.Column(db.String(50), unique=False, nullable=False) + description = db.Column(db.Text, unique=False, nullable=False) + recordsearchfromdate = db.Column(db.DateTime, nullable=True) + recordsearchtodate = db.Column(db.DateTime, nullable=True) + + 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) + + created_at = db.Column(db.DateTime, default=datetime.now) + updated_at = db.Column(db.DateTime, nullable=True) + createdby = db.Column(db.String(120), unique=False, nullable=True) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + assignedministryperson = db.Column(db.String(120), ForeignKey('FOIAssignees.username'), unique=False, nullable=True) + assignedministrygroup = db.Column(db.String(120), unique=False, nullable=True) + closedate = db.Column(db.DateTime, nullable=True) + + axissyncdate = db.Column(db.DateTime, nullable=True) + axisrequestid = db.Column(db.String(120), nullable=True) + axispagecount = db.Column(db.String(20), nullable=True) + axislanpagecount = db.Column(db.String(20), nullable=True) + recordspagecount = db.Column(db.String(20), nullable=True) + estimatedpagecount = db.Column(db.Integer, nullable=True) + estimatedtaggedpagecount = db.Column(db.Integer, 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) + requeststatuslabel = db.Column(db.String(50), nullable=False) + + #ForeignKey References + + closereasonid = db.Column(db.Integer,ForeignKey('CloseReasons.closereasonid')) + closereason = relationship("CloseReason",uselist=False) + + programareaid = db.Column(db.Integer,ForeignKey('ProgramAreas.programareaid')) + programarea = relationship("ProgramArea",backref=backref("ProgramAreas"),uselist=False) + + requeststatusid = db.Column(db.Integer,ForeignKey('FOIRequestStatuses.requeststatusid')) + requeststatus = relationship("FOIRequestStatus",backref=backref("FOIRequestStatuses"),uselist=False) + + foirequest_id =db.Column(db.Integer, db.ForeignKey('FOIRequests.foirequestid')) + foirequestversion_id = db.Column(db.Integer, db.ForeignKey('FOIRequests.version')) + foirequestkey = relationship("FOIRequest",foreign_keys="[FOIMinistryRequest.foirequest_id]") + foirequestversion = relationship("FOIRequest",foreign_keys="[FOIMinistryRequest.foirequestversion_id]") + + divisions = relationship('FOIMinistryRequestDivision', primaryjoin="and_(FOIMinistryRequest.foiministryrequestid==FOIMinistryRequestDivision.foiministryrequest_id, " + "FOIMinistryRequest.version==FOIMinistryRequestDivision.foiministryrequestversion_id)") + documents = relationship('FOIMinistryRequestDocument', primaryjoin="and_(FOIMinistryRequest.foiministryrequestid==FOIMinistryRequestDocument.foiministryrequest_id, " + "FOIMinistryRequest.version==FOIMinistryRequestDocument.foiministryrequestversion_id)") + extensions = relationship('FOIRequestExtension', primaryjoin="and_(FOIMinistryRequest.foiministryrequestid==FOIRequestExtension.foiministryrequest_id, " + "FOIMinistryRequest.version==FOIRequestExtension.foiministryrequestversion_id)") + + oipcreviews = relationship('FOIRequestOIPC', primaryjoin="and_(FOIMinistryRequest.foiministryrequestid==FOIRequestOIPC.foiministryrequest_id, " + "FOIMinistryRequest.version==FOIRequestOIPC.foiministryrequestversion_id)") + + assignee = relationship('FOIAssignee', foreign_keys="[FOIMinistryRequest.assignedto]") + ministryassignee = relationship('FOIAssignee', foreign_keys="[FOIMinistryRequest.assignedministryperson]") + + subjectcode = relationship('FOIMinistryRequestSubjectCode', primaryjoin="and_(FOIMinistryRequest.foiministryrequestid==FOIMinistryRequestSubjectCode.foiministryrequestid, " + "FOIMinistryRequest.version==FOIMinistryRequestSubjectCode.foiministryrequestversion)") + isofflinepayment = db.Column(db.Boolean, unique=False, nullable=True,default=False) + + isoipcreview = db.Column(db.Boolean, unique=False, nullable=True,default=False) + + @classmethod + def getrequest(cls,ministryrequestid): + request_schema = FOIMinistryRequestSchema(many=False) + query = db.session.query(FOIMinistryRequest).filter_by(foiministryrequestid=ministryrequestid).order_by(FOIMinistryRequest.version.desc()).first() + return request_schema.dump(query) + + @classmethod + def getLastStatusUpdateDate(cls,foiministryrequestid,requeststatuslabel): + statusdate = None + try: + sql = """select created_at from "FOIMinistryRequests" + where foiministryrequestid = :foiministryrequestid and requeststatuslabel = :requeststatuslabel + order by version desc limit 1;""" + rs = db.session.execute(text(sql), {'foiministryrequestid': foiministryrequestid, 'requeststatuslabel': requeststatuslabel}) + statusdate = [row[0] for row in rs][0] + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return statusdate + + + + @classmethod + def getassignmenttransition(cls,requestid): + assignments = [] + try: + sql = """select version, assignedto, assignedministryperson from "FOIMinistryRequests" + where foiministryrequestid = :requestid + order by version desc limit 2;""" + rs = db.session.execute(text(sql), {'requestid': requestid}) + for row in rs: + assignments.append({"assignedto": row["assignedto"], "assignedministryperson": row["assignedministryperson"], "version": row["version"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return assignments + + @classmethod + def deActivateFileNumberVersion(cls, ministryid, idnumber, userid)->DefaultMethodResult: + try: + sql = """update "FOIMinistryRequests" set isactive = false, updatedby = :userid, updated_at = now() + where foiministryrequestid = :ministryid and isactive = true and filenumber = :idnumber + and version != (select version from "FOIMinistryRequests" fr where foiministryrequestid = :ministryid order by "version" desc limit 1)""" + db.session.execute(text(sql), {'ministryid': ministryid, 'userid':userid, 'idnumber': idnumber}) + db.session.commit() + return DefaultMethodResult(True,'Request Updated',idnumber) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + + @classmethod + def getrequests(cls, group = None): + _session = db.session + _ministryrequestids = [] + + 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.requeststatuslabel.in_([StateName.open.name,StateName.callforrecords.name,StateName.closed.name,StateName.recordsreview.name,StateName.feeestimate.name,StateName.consult.name,StateName.ministrysignoff.value,StateName.onhold.name,StateName.deduplication.name,StateName.harmsassessment.name,StateName.response.name,StateName.peerreview.name,StateName.tagging.name,StateName.readytoscan.name])))).all() + elif (group in ProcessingTeamWithKeycloackGroup.list()): + _ministryrequestids = _session.query(distinct(FOIMinistryRequest.foiministryrequestid)).filter(and_(FOIMinistryRequest.isactive == True), and_(and_(FOIMinistryRequest.assignedgroup == group),and_(FOIMinistryRequest.requeststatuslabel.in_([StateName.open.name,StateName.callforrecords.name,StateName.closed.name,StateName.recordsreview.name,StateName.feeestimate.name,StateName.consult.name,StateName.ministrysignoff.value,StateName.onhold.name,StateName.response.name,StateName.peerreview.name,StateName.tagging.name,StateName.readytoscan.name])))).all() + else: + _ministryrequestids = _session.query(distinct(FOIMinistryRequest.foiministryrequestid)).filter(and_(FOIMinistryRequest.isactive == True), or_(and_(FOIMinistryRequest.assignedgroup == group),and_(FOIMinistryRequest.assignedministrygroup == group,or_(FOIMinistryRequest.requeststatuslabel.in_([StateName.callforrecords.name,StateName.recordsreview.name,StateName.feeestimate.name,StateName.consult.name,StateName.ministrysignoff.name,StateName.onhold.name,StateName.deduplication.name,StateName.harmsassessment.name,StateName.response.name,StateName.peerreview.name,StateName.tagging.name,StateName.readytoscan.name]))))).all() + + _requests = [] + ministryrequest_schema = FOIMinistryRequestSchema() + for _requestid in _ministryrequestids: + _request ={} + + ministryrequest =ministryrequest_schema.dump(_session.query(FOIMinistryRequest).filter(FOIMinistryRequest.foiministryrequestid == _requestid).order_by(FOIMinistryRequest.version.desc()).first()) + parentrequest = _session.query(FOIRequest).filter(FOIRequest.foirequestid == ministryrequest['foirequest_id'] and FOIRequest.version == ministryrequest['foirequestversion_id']).order_by(FOIRequest.version.desc()).first() + requestapplicants = FOIRequestApplicantMapping.getrequestapplicants(ministryrequest['foirequest_id'],ministryrequest['foirequestversion_id']) + _receiveddate = parentrequest.receiveddate + _request["firstName"] = requestapplicants[0]['foirequestapplicant.firstname'] + _request["lastName"] = requestapplicants[0]['foirequestapplicant.lastname'] + _request["requestType"] = parentrequest.requesttype + _request["idNumber"] = ministryrequest['filenumber'] + _request["axisRequestId"] = ministryrequest['axisrequestid'] + _request["linkedrequests"] = ministryrequest['linkedrequests'] + _request["currentState"] = ministryrequest["requeststatus.name"] + _request["dueDate"] = ministryrequest["duedate"] + _request["cfrDueDate"] = ministryrequest["cfrduedate"] + _request["originalDueDate"] = ministryrequest["originalldd"] + _request["receivedDate"] = _receiveddate.strftime('%Y %b, %d') + _request["receivedDateUF"] =str(_receiveddate) + _request["assignedGroup"]=ministryrequest["assignedgroup"] + _request["assignedTo"]=ministryrequest["assignedto"] + _request["assignedministrygroup"]=ministryrequest["assignedministrygroup"] + _request["assignedministryperson"]=ministryrequest["assignedministryperson"] + _request["xgov"]='No' + _request["version"] = ministryrequest['version'] + _request["id"] = parentrequest.foirequestid + _request["ministryrequestid"] = ministryrequest['foiministryrequestid'] + _request["applicantcategory"]=parentrequest.applicantcategory.name + _request["identityverified"] = ministryrequest['identityverified'] + _requests.append(_request) + + return _requests + + @classmethod + def getrequestbyministryrequestid(cls,ministryrequestid): + request_schema = FOIMinistryRequestSchema() + query = db.session.query(FOIMinistryRequest).filter_by(foiministryrequestid=ministryrequestid).order_by(FOIMinistryRequest.version.desc()).first() + return request_schema.dump(query) + + @classmethod + def getrequestbyfilenumberandversion(cls,filenumber, version): + request_schema = FOIMinistryRequestSchema() + query = db.session.query(FOIMinistryRequest).filter_by(filenumber=filenumber, version = version).order_by(FOIMinistryRequest.version.desc()).first() + return request_schema.dump(query) + + @classmethod + def getrequestById(cls,ministryrequestid): + request_schema = FOIMinistryRequestSchema(many=True) + query = db.session.query(FOIMinistryRequest).filter_by(foiministryrequestid=ministryrequestid).order_by(FOIMinistryRequest.version.asc()) + return request_schema.dump(query) + + @classmethod + def getrequeststatusById(cls,ministryrequestid): + summary = [] + try: + sql = 'select foirequest_id, version, requeststatusid, requeststatuslabel, created_at from "FOIMinistryRequests" fr where foiministryrequestid = :ministryrequestid and requeststatuslabel != :requeststatuslabel order by version desc;' + + rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid, 'requeststatuslabel': StateName.closed.name}) + for row in rs: + summary.append({"requeststatusid": row["requeststatusid"], "requeststatuslabel": row["requeststatuslabel"], "created_at": row["created_at"], "foirequest_id": row["foirequest_id"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return summary + + + @classmethod + def getversionforrequest(cls,ministryrequestid): + return db.session.query(FOIMinistryRequest.version).filter_by(foiministryrequestid=ministryrequestid).order_by(FOIMinistryRequest.version.desc()).first() + + @classmethod + def getstatesummary(cls, ministryrequestid): + transitions = [] + try: + """ + sql =select status, version from (select distinct name as status, version from "FOIMinistryRequests" fm inner join "FOIRequestStatuses" fs2 on fm.requeststatusid = fs2.requeststatusid + where foiministryrequestid=:ministryrequestid order by version asc) as fs3 order by version desc; + """ + sql = """select fm2.version, fs2."name" as status, fm2.created_at from "FOIMinistryRequests" fm2 inner join "FOIRequestStatuses" fs2 on fm2.requeststatusid = fs2.requeststatusid + where fm2.foiministryrequestid=:ministryrequestid order by version desc""" + rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) + _tmp_state = None + for row in rs: + if row["status"] != _tmp_state: + transitions.append({"status": row["status"], "version": row["version"], "created_at": row["created_at"].strftime('%Y-%m-%d %H:%M:%S.%f')}) + _tmp_state = row["status"] + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return transitions + + @classmethod + def getlastoffholddate(cls, ministryrequestid): + transitions = [] + try: + sql = """select fm2.version, fs2."name" as status, fm2.created_at from "FOIMinistryRequests" fm2 inner join "FOIRequestStatuses" fs2 on fm2.requeststatusid = fs2.requeststatusid + where fm2.foiministryrequestid=:ministryrequestid order by version asc""" + rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) + _tmp_state = None + for row in rs: + if row["status"] != _tmp_state: + transitions.append({"status": row["status"], "version": row["version"], "created_at": row["created_at"]}) + _tmp_state = row["status"] + desc_transitions = transitions[::-1] + index = 0 + onhold_occurance = 0 + recent_offhold_index = None + offhold_indicator = False + for entry in desc_transitions: + if entry["status"] == StateName.onhold.value: + onhold_occurance = onhold_occurance + 1 + if onhold_occurance > 1: + recent_offhold_index = index + offhold_indicator = True + index = index + 1 + return None if offhold_indicator == False or recent_offhold_index == 0 else desc_transitions[recent_offhold_index-1]["created_at"] + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + + @classmethod + def getstatenavigation(cls, ministryrequestid): + requeststates = [] + try: + sql = """select fs2."name" as status, version from "FOIMinistryRequests" fm inner join "FOIRequestStatuses" fs2 on fm.requeststatusid = fs2.requeststatusid + where foiministryrequestid=:ministryrequestid order by version desc limit 2""" + rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) + for row in rs: + requeststates.append(row["status"]) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return requeststates + + @classmethod + def getallstatenavigation(cls, ministryrequestid): + requeststates = [] + try: + sql = """select fs2."name" as status, version from "FOIMinistryRequests" fm inner join "FOIRequestStatuses" fs2 on fm.requeststatusid = fs2.requeststatusid + where foiministryrequestid=:ministryrequestid order by version desc""" + rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) + for row in rs: + requeststates.append(row["status"]) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return requeststates + + @classmethod + def getrequestssubquery(cls, groups, filterfields, keyword, additionalfilter, userid, iaoassignee, ministryassignee, requestby='IAO', isiaorestrictedfilemanager=False, isministryrestrictedfilemanager=False): + #for queue/dashboard + _session = db.session + + #ministry filter for group/team + ministryfilter = FOIMinistryRequest.getgroupfilters(groups) + + #subquery for getting latest version & proper group/team for FOIMinistryRequest + subquery_ministry_maxversion = _session.query(FOIMinistryRequest.foiministryrequestid, func.max(FOIMinistryRequest.version).label('max_version')).group_by(FOIMinistryRequest.foiministryrequestid).subquery() + joincondition_ministry = [ + subquery_ministry_maxversion.c.foiministryrequestid == FOIMinistryRequest.foiministryrequestid, + subquery_ministry_maxversion.c.max_version == FOIMinistryRequest.version, + ] + + #subquery for getting extension count + subquery_extension_count = _session.query(FOIRequestExtension.foiministryrequest_id, func.count(distinct(FOIRequestExtension.foirequestextensionid)).filter(FOIRequestExtension.isactive == True).label('extensions')).group_by(FOIRequestExtension.foiministryrequest_id).subquery() + + #subquery for getting all, distinct oipcs for foiministry request + subquery_with_oipc_sql = """ + SELECT distinct on (foiministryrequest_id) foiministryrequest_id, foiministryrequestversion_id, outcomeid + FROM "FOIRequestOIPC" fo + order by foiministryrequest_id, foiministryrequestversion_id desc + """ + subquery_with_oipc = text(subquery_with_oipc_sql).columns(FOIRequestOIPC.foiministryrequest_id, FOIRequestOIPC.foiministryrequestversion_id, FOIRequestOIPC.outcomeid).alias("oipcnoneoutcomes") + joincondition_oipc = [ + subquery_with_oipc.c.foiministryrequest_id == FOIMinistryRequest.foiministryrequestid, + subquery_with_oipc.c.foiministryrequestversion_id == FOIMinistryRequest.version, + ] + + #aliase for onbehalf of applicant info + onbehalf_applicantmapping = aliased(FOIRequestApplicantMapping) + onbehalf_applicant = aliased(FOIRequestApplicant) + + #aliase for getting ministry restricted flag from FOIRestrictedMinistryRequest + ministry_restricted_requests = aliased(FOIRestrictedMinistryRequest) + + #filter/search + if(len(filterfields) > 0 and keyword is not None): + + filtercondition = [] + + if(keyword != "restricted"): + for field in filterfields: + filtercondition.append(FOIMinistryRequest.findfield(field, iaoassignee, ministryassignee).ilike('%'+keyword+'%')) + else: + if(requestby == 'IAO'): + filtercondition.append(FOIRestrictedMinistryRequest.isrestricted == True) + else: + filtercondition.append(ministry_restricted_requests.isrestricted == True) + if (keyword.lower() == "oipc"): + filtercondition.append(FOIMinistryRequest.isoipcreview == True) + + intakesorting = case([ + (and_(FOIMinistryRequest.assignedto == None, FOIMinistryRequest.assignedgroup == 'Intake Team'), # Unassigned requests first + literal(None)), + ], + else_ = FOIRequest.receiveddate).label('intakeSorting') + + defaultsorting = case([ + (FOIMinistryRequest.assignedto == None, # Unassigned requests first + literal(None)), + ], + else_ = FOIMinistryRequest.duedate).label('defaultSorting') + + ministrysorting = case([ + (FOIMinistryRequest.assignedministryperson == None, # Unassigned requests first + literal(None)), + ], + else_ = FOIMinistryRequest.duedate).label('ministrySorting') + + assignedtoformatted = case([ + (and_(iaoassignee.lastname.isnot(None), iaoassignee.firstname.isnot(None)), + func.concat(iaoassignee.lastname, ', ', iaoassignee.firstname)), + (and_(iaoassignee.lastname.isnot(None), iaoassignee.firstname.is_(None)), + iaoassignee.lastname), + (and_(iaoassignee.lastname.is_(None), iaoassignee.firstname.isnot(None)), + iaoassignee.firstname), + ], + else_ = FOIMinistryRequest.assignedgroup).label('assignedToFormatted') + + ministryassignedtoformatted = case([ + (and_(ministryassignee.lastname.isnot(None), ministryassignee.firstname.isnot(None)), + func.concat(ministryassignee.lastname, ', ', ministryassignee.firstname)), + (and_(ministryassignee.lastname.isnot(None), ministryassignee.firstname.is_(None)), + ministryassignee.lastname), + (and_(ministryassignee.lastname.is_(None), ministryassignee.firstname.isnot(None)), + ministryassignee.firstname), + ], + else_ = FOIMinistryRequest.assignedministrygroup).label('ministryAssignedToFormatted') + + duedate = case([ + (FOIMinistryRequest.requeststatuslabel == StateName.onhold.name, # On Hold + literal(None)), + ], + else_ = cast(FOIMinistryRequest.duedate, String)).label('duedate') + + cfrduedate = case([ + (FOIMinistryRequest.requeststatuslabel == StateName.onhold.name, # On Hold + literal(None)), + ], + else_ = cast(FOIMinistryRequest.cfrduedate, String)).label('cfrduedate') + + axispagecount = case ([ + (FOIMinistryRequest.axispagecount.isnot(None), FOIMinistryRequest.axispagecount) + ], + else_= literal("0").label("axispagecount") + ) + axislanpagecount = case ([ + (FOIMinistryRequest.axislanpagecount.isnot(None), FOIMinistryRequest.axislanpagecount) + ], + else_= literal("0").label("axislanpagecount") + ) + recordspagecount = case ([ + (FOIMinistryRequest.recordspagecount.isnot(None), FOIMinistryRequest.recordspagecount) + ], + else_= literal("0").label("recordspagecount") + ) + + requestpagecount = case([ + (and_(FOIMinistryRequest.axispagecount.isnot(None), FOIMinistryRequest.recordspagecount.isnot(None), cast(axispagecount, Integer) > cast(recordspagecount, Integer)), + FOIMinistryRequest.axispagecount), + (and_(FOIMinistryRequest.recordspagecount.isnot(None)), + FOIMinistryRequest.recordspagecount), + (and_(FOIMinistryRequest.axispagecount.isnot(None)), + FOIMinistryRequest.axispagecount), + ], + else_= literal("0")) + + onbehalfformatted = case([ + (and_(onbehalf_applicant.lastname.isnot(None), onbehalf_applicant.firstname.isnot(None)), + func.concat(onbehalf_applicant.lastname, ', ', onbehalf_applicant.firstname)), + (and_(onbehalf_applicant.lastname.isnot(None), onbehalf_applicant.firstname.is_(None)), + onbehalf_applicant.lastname), + (and_(onbehalf_applicant.lastname.is_(None), onbehalf_applicant.firstname.isnot(None)), + onbehalf_applicant.firstname), + ], + else_ = 'N/A').label('onBehalfFormatted') + + extensions = case([ + (subquery_extension_count.c.extensions.is_(None), + 0), + ], + else_ = subquery_extension_count.c.extensions).label('extensions') + + + selectedcolumns = [ + FOIRequest.foirequestid.label('id'), + FOIMinistryRequest.version, + literal(None).label('sourceofsubmission'), + FOIRequestApplicant.firstname.label('firstName'), + FOIRequestApplicant.lastname.label('lastName'), + FOIRequest.requesttype.label('requestType'), + cast(FOIRequest.receiveddate, String).label('receivedDate'), + cast(FOIRequest.receiveddate, String).label('receivedDateUF'), + FOIRequestStatus.name.label('currentState'), + FOIMinistryRequest.assignedgroup.label('assignedGroup'), + FOIMinistryRequest.assignedto.label('assignedTo'), + cast(FOIMinistryRequest.filenumber, String).label('idNumber'), + cast(FOIMinistryRequest.axisrequestid, String).label('axisRequestId'), + cast(requestpagecount, Integer).label('requestpagecount'), + axispagecount, + axislanpagecount, + recordspagecount, + FOIMinistryRequest.foiministryrequestid.label('ministryrequestid'), + FOIMinistryRequest.assignedministrygroup.label('assignedministrygroup'), + FOIMinistryRequest.assignedministryperson.label('assignedministryperson'), + cfrduedate, + duedate, + ApplicantCategory.name.label('applicantcategory'), + FOIRequest.created_at.label('created_at'), + func.lower(ProgramArea.bcgovcode).label('bcgovcode'), + iaoassignee.firstname.label('assignedToFirstName'), + iaoassignee.lastname.label('assignedToLastName'), + ministryassignee.firstname.label('assignedministrypersonFirstName'), + ministryassignee.lastname.label('assignedministrypersonLastName'), + FOIMinistryRequest.description, + cast(FOIMinistryRequest.recordsearchfromdate, String).label('recordsearchfromdate'), + cast(FOIMinistryRequest.recordsearchtodate, String).label('recordsearchtodate'), + onbehalf_applicant.firstname.label('onBehalfFirstName'), + onbehalf_applicant.lastname.label('onBehalfLastName'), + defaultsorting, + intakesorting, + ministrysorting, + assignedtoformatted, + ministryassignedtoformatted, + FOIMinistryRequest.closedate, + onbehalfformatted, + extensions, + FOIRestrictedMinistryRequest.isrestricted.label('isiaorestricted'), + ministry_restricted_requests.isrestricted.label('isministryrestricted'), + SubjectCode.name.label('subjectcode'), + FOIMinistryRequest.isoipcreview.label('isoipcreview'), + literal(None).label('oipc_number'), + ] + + basequery = _session.query( + *selectedcolumns + ).join( + subquery_ministry_maxversion, + and_(*joincondition_ministry) + ).join( + subquery_extension_count, + subquery_extension_count.c.foiministryrequest_id == FOIMinistryRequest.foiministryrequestid, + isouter=True + ).join( + FOIRequest, + and_(FOIRequest.foirequestid == FOIMinistryRequest.foirequest_id, FOIRequest.version == FOIMinistryRequest.foirequestversion_id) + ).join( + FOIRequestStatus, + FOIRequestStatus.requeststatusid == FOIMinistryRequest.requeststatusid + ).join( + FOIRequestApplicantMapping, + and_(FOIRequestApplicantMapping.foirequest_id == FOIMinistryRequest.foirequest_id, FOIRequestApplicantMapping.foirequestversion_id == FOIMinistryRequest.foirequestversion_id, FOIRequestApplicantMapping.requestortypeid == RequestorType['applicant'].value) + ).join( + FOIRequestApplicant, + FOIRequestApplicant.foirequestapplicantid == FOIRequestApplicantMapping.foirequestapplicantid + ).join( + onbehalf_applicantmapping, + and_( + onbehalf_applicantmapping.foirequest_id == FOIMinistryRequest.foirequest_id, + onbehalf_applicantmapping.foirequestversion_id == FOIMinistryRequest.foirequestversion_id, + onbehalf_applicantmapping.requestortypeid == RequestorType['onbehalfof'].value), + isouter=True + ).join( + onbehalf_applicant, + onbehalf_applicant.foirequestapplicantid == onbehalf_applicantmapping.foirequestapplicantid, + isouter=True + ).join( + ApplicantCategory, + and_(ApplicantCategory.applicantcategoryid == FOIRequest.applicantcategoryid, ApplicantCategory.isactive == True) + ).join( + ProgramArea, + FOIMinistryRequest.programareaid == ProgramArea.programareaid + ).join( + iaoassignee, + iaoassignee.username == FOIMinistryRequest.assignedto, + isouter=True + ).join( + ministryassignee, + ministryassignee.username == FOIMinistryRequest.assignedministryperson, + isouter=True + ).join( + FOIRestrictedMinistryRequest, + and_( + FOIRestrictedMinistryRequest.ministryrequestid == FOIMinistryRequest.foiministryrequestid, + FOIRestrictedMinistryRequest.type == 'iao', + FOIRestrictedMinistryRequest.isactive == True), + isouter=True + ).join( + ministry_restricted_requests, + and_( + ministry_restricted_requests.ministryrequestid == FOIMinistryRequest.foiministryrequestid, + ministry_restricted_requests.type == 'ministry', + ministry_restricted_requests.isactive == True), + isouter=True + ).join( + FOIMinistryRequestSubjectCode, + and_(FOIMinistryRequestSubjectCode.foiministryrequestid == FOIMinistryRequest.foiministryrequestid, FOIMinistryRequestSubjectCode.foiministryrequestversion == FOIMinistryRequest.version), + isouter=True + ).join( + SubjectCode, + SubjectCode.subjectcodeid == FOIMinistryRequestSubjectCode.subjectcodeid, + isouter=True + ).join( + subquery_with_oipc, + and_( + *joincondition_oipc + ), + isouter=True + ).filter(or_(FOIMinistryRequest.requeststatuslabel != StateName.closed.name, + and_(FOIMinistryRequest.isoipcreview == True, FOIMinistryRequest.requeststatusid == 3, subquery_with_oipc.c.outcomeid == None))) + + + + if(additionalfilter == 'watchingRequests'): + #watchby + activefilter = and_(FOIMinistryRequest.isactive == True, FOIRequestStatus.isactive == True) + + subquery_watchby = FOIRequestWatcher.getrequestidsbyuserid(userid) + if(requestby == 'IAO'): + dbquery = basequery.join(subquery_watchby, subquery_watchby.c.ministryrequestid == FOIMinistryRequest.foiministryrequestid).filter(activefilter).filter(or_(or_(FOIRestrictedMinistryRequest.isrestricted == False, FOIRestrictedMinistryRequest.isrestricted == None), and_(FOIRestrictedMinistryRequest.isrestricted == True, FOIMinistryRequest.assignedto == userid))) + else: + dbquery = basequery.join(subquery_watchby, subquery_watchby.c.ministryrequestid == FOIMinistryRequest.foiministryrequestid).filter(activefilter).filter(or_(or_(ministry_restricted_requests.isrestricted == isministryrestrictedfilemanager, ministry_restricted_requests.isrestricted == None), and_(ministry_restricted_requests.isrestricted == True, FOIMinistryRequest.assignedministryperson == userid))) + + elif(additionalfilter == 'myRequests'): + #myrequest + if(requestby == 'IAO'): + dbquery = basequery.filter(FOIMinistryRequest.assignedto == userid).filter(ministryfilter) + else: + dbquery = basequery.filter(FOIMinistryRequest.assignedministryperson == userid).filter(ministryfilter) + elif(additionalfilter == 'unassignedRequests'): + if(requestby == 'IAO'): + dbquery = basequery.filter(FOIMinistryRequest.assignedto == None).filter(ministryfilter) + elif(additionalfilter.lower() == 'all'): + if(requestby == 'IAO'): + dbquery = basequery.filter(ministryfilter).filter(FOIMinistryRequest.assignedto.isnot(None)).filter(or_(FOIRestrictedMinistryRequest.isrestricted == isiaorestrictedfilemanager, or_(FOIRestrictedMinistryRequest.isrestricted.is_(None), FOIRestrictedMinistryRequest.isrestricted == False))) + else: + dbquery = basequery.filter(ministryfilter).filter(or_(ministry_restricted_requests.isrestricted == isministryrestrictedfilemanager, or_(ministry_restricted_requests.isrestricted.is_(None), ministry_restricted_requests.isrestricted == False))) + else: + if(isiaorestrictedfilemanager == True or isministryrestrictedfilemanager == True): + dbquery = basequery.filter(ministryfilter) + else: + if(requestby == 'IAO'): + dbquery = basequery.filter(or_(or_(FOIRestrictedMinistryRequest.isrestricted == False, FOIRestrictedMinistryRequest.isrestricted == None), and_(FOIRestrictedMinistryRequest.isrestricted == True, FOIMinistryRequest.assignedto == userid))).filter(ministryfilter) + else: + dbquery = basequery.filter(or_(or_(ministry_restricted_requests.isrestricted == False, ministry_restricted_requests.isrestricted == None), and_(ministry_restricted_requests.isrestricted == True, FOIMinistryRequest.assignedministryperson == userid))).filter(ministryfilter) + + + if(keyword is None): + return dbquery + else: + return dbquery.filter(or_(*filtercondition)) + + @classmethod + def getrequestspagination(cls, group, page, size, sortingitems, sortingorders, filterfields, keyword, additionalfilter, userid, isiaorestrictedfilemanager, isministryrestrictedfilemanager): + iaoassignee = aliased(FOIAssignee) + ministryassignee = aliased(FOIAssignee) + requestby = 'Ministry' + + subquery = FOIMinistryRequest.getrequestssubquery(group, filterfields, keyword, additionalfilter, userid, iaoassignee, ministryassignee, requestby, isiaorestrictedfilemanager, isministryrestrictedfilemanager) + + #sorting + sortingcondition = FOIMinistryRequest.getsorting(sortingitems, sortingorders, iaoassignee, ministryassignee) + + return subquery.order_by(*sortingcondition).paginate(page=page, per_page=size) + + @classmethod + def getsorting(cls, sortingitems, sortingorders, iaoassignee, ministryassignee): + #sorting + sortingcondition = [] + if(len(sortingitems) > 0 and len(sortingorders) > 0 and len(sortingitems) == len(sortingorders)): + for field in sortingitems: + order = sortingorders.pop(0) + sortingcondition.append(FOIMinistryRequest.getfieldforsorting(field, order, iaoassignee, ministryassignee)) + + #default sorting + if(len(sortingcondition) == 0): + sortingcondition.append(FOIMinistryRequest.findfield('currentState', iaoassignee, ministryassignee).asc()) + + #always sort by created_at last to prevent pagination collisions + sortingcondition.append(asc('created_at')) + + return sortingcondition + + @classmethod + def getfieldforsorting(cls, field, order, iaoassignee, ministryassignee): + #get one field + customizedfields = ['assignedToFormatted', 'ministryAssignedToFormatted', 'duedate', 'cfrduedate', 'ministrySorting', 'onBehalfFormatted', 'extensions', 'isministryrestricted'] + if(field in customizedfields): + if(order == 'desc'): + return nullslast(desc(field)) + else: + return nullsfirst(asc(field)) + else: + if(order == 'desc'): + return nullslast(FOIMinistryRequest.findfield(field, iaoassignee, ministryassignee).desc()) + else: + return nullsfirst(FOIMinistryRequest.findfield(field, iaoassignee, ministryassignee).asc()) + + @classmethod + def findfield(cls, x, iaoassignee, ministryassignee): + #add more fields here if need sort/filter/search more columns + axispagecount = case ([ + (FOIMinistryRequest.axispagecount.isnot(None), FOIMinistryRequest.axispagecount) + ], + else_= literal("0").label("axispagecount") + ) + recordspagecount = case ([ + (FOIMinistryRequest.recordspagecount.isnot(None), FOIMinistryRequest.recordspagecount) + ], + else_= literal("0").label("recordspagecount") + ) + requestpagecount = case([ + (and_(FOIMinistryRequest.axispagecount.isnot(None), FOIMinistryRequest.recordspagecount.isnot(None), cast(axispagecount, Integer) > cast(recordspagecount, Integer)), + FOIMinistryRequest.axispagecount), + (and_(FOIMinistryRequest.recordspagecount.isnot(None)), + FOIMinistryRequest.recordspagecount), + (and_(FOIMinistryRequest.axispagecount.isnot(None)), + FOIMinistryRequest.axispagecount), + ], + else_= literal("0")).label('requestpagecount') + + return { + 'firstName': FOIRequestApplicant.firstname, + 'lastName': FOIRequestApplicant.lastname, + 'requestType': FOIRequest.requesttype, + 'idNumber': FOIMinistryRequest.filenumber, + 'axisRequestId': FOIMinistryRequest.axisrequestid, + 'axisrequest_number': FOIMinistryRequest.axisrequestid, + 'rawRequestNumber': FOIMinistryRequest.filenumber, + 'currentState': FOIRequestStatus.name, + 'assignedTo': FOIMinistryRequest.assignedto, + 'receivedDate': FOIRequest.receiveddate, + 'receivedDateUF': FOIRequest.receiveddate, + 'applicantcategory': ApplicantCategory.name, + 'assignedministryperson': FOIMinistryRequest.assignedministryperson, + 'assignedToFirstName': iaoassignee.firstname, + 'assignedToLastName': iaoassignee.lastname, + 'assignedministrypersonFirstName': ministryassignee.firstname, + 'assignedministrypersonLastName': ministryassignee.lastname, + 'description': FOIMinistryRequest.description, + 'requestdescription': FOIMinistryRequest.description, + 'duedate': FOIMinistryRequest.duedate, + 'cfrduedate': FOIMinistryRequest.cfrduedate, + 'DueDateValue': FOIMinistryRequest.duedate, + 'DaysLeftValue': FOIMinistryRequest.duedate, + 'ministry': func.upper(ProgramArea.bcgovcode), + 'requestpagecount': requestpagecount, + 'axispagecount': axispagecount, + 'recordspagecount': recordspagecount, + 'closedate': FOIMinistryRequest.closedate, + 'subjectcode': SubjectCode.name, + 'isoipcreview': FOIMinistryRequest.isoipcreview + }.get(x, FOIMinistryRequest.axisrequestid) + + @classmethod + def getgroupfilters(cls, groups): + #ministry filter for group/team + if groups is None: + ministryfilter = FOIMinistryRequest.isactive == True + else: + groupfilter = [] + statusfilter = None + processinggroups = list(set(groups).intersection(ProcessingTeamWithKeycloackGroup.list())) + if IAOTeamWithKeycloackGroup.intake.value in groups or len(processinggroups) > 0: + groupfilter.append( + and_( + FOIMinistryRequest.assignedgroup.in_(tuple(groups)) + ) + ) + statusfilter = FOIMinistryRequest.requeststatuslabel != StateName.closed.name + else: + groupfilter.append( + or_( + FOIMinistryRequest.assignedgroup.in_(tuple(groups)), + and_( + FOIMinistryRequest.assignedministrygroup.in_(tuple(groups)) + ) + ) + ) + statusfilter = FOIMinistryRequest.requeststatuslabel.in_([StateName.callforrecords.name,StateName.recordsreview.name,StateName.feeestimate.name,StateName.consult.name,StateName.ministrysignoff.name,StateName.onhold.name,StateName.deduplication.name,StateName.harmsassessment.name,StateName.response.name,StateName.peerreview.name,StateName.tagging.name,StateName.readytoscan.name, StateName.recordsreadyforreview.name]) + ministryfilter = and_( + FOIMinistryRequest.isactive == True, + FOIRequestStatus.isactive == True, + or_(*groupfilter) + ) + ministryfilterwithclosedoipc = and_(ministryfilter, + or_(statusfilter, + and_(FOIMinistryRequest.isoipcreview == True, FOIMinistryRequest.requeststatuslabel == StateName.closed.name) + ) + ) + return ministryfilterwithclosedoipc + + @classmethod + def getrequestoriginalduedate(cls,ministryrequestid): + return db.session.query(FOIMinistryRequest.duedate).filter(FOIMinistryRequest.foiministryrequestid == ministryrequestid, FOIMinistryRequest.requeststatuslabel == StateName.open.name).order_by(FOIMinistryRequest.version).first()[0] + + @classmethod + def getduedate(cls,ministryrequestid): + return db.session.query(FOIMinistryRequest.duedate).filter(FOIMinistryRequest.foiministryrequestid == ministryrequestid).order_by(FOIMinistryRequest.version.desc()).first()[0] + + + @classmethod + def getupcomingcfrduerecords(cls): + upcomingduerecords = [] + try: + sql = """select distinct on (filenumber) filenumber, to_char(cfrduedate, 'YYYY-MM-DD') as cfrduedate, foiministryrequestid, version, foirequest_id, created_at, createdby from "FOIMinistryRequests" fpa + where isactive = true and cfrduedate is not null and requeststatuslabel = :requeststatuslabel + and cfrduedate between NOW() - INTERVAL '7 DAY' AND NOW() + INTERVAL '7 DAY' + order by filenumber , version desc;""" + rs = db.session.execute(text(sql), {'requeststatuslabel': StateName.callforrecords.name}) + for row in rs: + upcomingduerecords.append({"filenumber": row["filenumber"], "cfrduedate": row["cfrduedate"],"foiministryrequestid": row["foiministryrequestid"], "version": row["version"], "foirequest_id": row["foirequest_id"], "created_at": row["created_at"], "createdby": row["createdby"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return upcomingduerecords + + @classmethod + def getupcominglegislativeduerecords(cls): + upcomingduerecords = [] + try: + sql = """select distinct on (filenumber) filenumber, to_char(duedate, 'YYYY-MM-DD') as duedate, foiministryrequestid, version, foirequest_id, created_at, createdby from "FOIMinistryRequests" fpa + where isactive = true and duedate is not null and requeststatuslabel not in :requeststatuslabel + and duedate between NOW() - INTERVAL '7 DAY' AND NOW() + INTERVAL '7 DAY' + order by filenumber , version desc;""" + requeststatuslabel = tuple([StateName.closed.name,StateName.redirect.name,StateName.unopened.name,StateName.intakeinprogress.name,StateName.onhold.name,StateName.archived.name]) + rs = db.session.execute(text(sql), {'requeststatuslabel': requeststatuslabel}) + for row in rs: + upcomingduerecords.append({"filenumber": row["filenumber"], "duedate": row["duedate"],"foiministryrequestid": row["foiministryrequestid"], "version": row["version"], "foirequest_id": row["foirequest_id"], "created_at": row["created_at"], "createdby": row["createdby"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return upcomingduerecords + + @classmethod + def getupcomingdivisionduerecords(cls): + upcomingduerecords = [] + try: + sql = """select axisrequestid, filenumber, fma.foiministryrequestid , fma.foiministryrequestversion, fma.foirequest_id, + frd.divisionid, frd.stageid, pad2."name" divisionname, pads."name" stagename, + to_char(divisionduedate, 'YYYY-MM-DD') as duedate, frd.created_at, frd.createdby + from "FOIMinistryRequestDivisions" frd + inner join (select distinct on (fpa.foiministryrequestid) foiministryrequestid, version as foiministryrequestversion, axisrequestid, filenumber, foirequest_id, requeststatusid, requeststatuslabel + from "FOIMinistryRequests" fpa + order by fpa.foiministryrequestid , fpa.version desc) fma on frd.foiministryrequest_id = fma.foiministryrequestid and frd.foiministryrequestversion_id = fma.foiministryrequestversion and fma.requeststatuslabel not in :requeststatuslabel + inner join "ProgramAreaDivisions" pad2 on frd.divisionid = pad2.divisionid + inner join "ProgramAreaDivisionStages" pads on frd.stageid = pads.stageid and frd.stageid in (5, 7, 9) + and frd.divisionduedate between NOW() - INTERVAL '7 DAY' AND NOW() + INTERVAL '7 DAY' + order by frd.foiministryrequest_id , frd.foiministryrequestversion_id desc;""" + requeststatuslabel = tuple([StateName.closed.name,StateName.redirect.name,StateName.unopened.name,StateName.intakeinprogress.name,StateName.onhold.name,StateName.archived.name]) + rs = db.session.execute(text(sql), {'requeststatuslabel': requeststatuslabel}) + + for row in rs: + upcomingduerecords.append({"axisrequestid": row["axisrequestid"], "filenumber": row["filenumber"], + "foiministryrequestid": row["foiministryrequestid"], "version": row["foiministryrequestversion"], + "foirequest_id": row["foirequest_id"], "created_at": row["created_at"], "createdby": row["createdby"], + "divisionid": row["divisionid"],"divisionname": row["divisionname"], + "stageid": row["stageid"], "stagename": row["stagename"], + "duedate": row["duedate"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return upcomingduerecords + + @classmethod + def getupcomingoipcduerecords(cls): + upcomingduerecords = [] + try: + sql = """select axisrequestid, filenumber, fma.foiministryrequestid , fma.foiministryrequestversion, fma.foirequest_id, + frd.oipcid , frd.inquiryattributes ->> 'orderno'as orderno, + frd.inquiryattributes ->> 'inquirydate' as duedate, frd.created_at, frd.createdby + from "FOIRequestOIPC" frd + inner join (select distinct on (fpa.foiministryrequestid) foiministryrequestid, version as foiministryrequestversion, axisrequestid, filenumber, foirequest_id, requeststatusid + from "FOIMinistryRequests" fpa + order by fpa.foiministryrequestid , fpa.version desc) fma on frd.foiministryrequest_id = fma.foiministryrequestid + and frd.foiministryrequestversion_id = fma.foiministryrequestversion and fma.requeststatusid not in (5,6,4,11,3,15) + and inquiryattributes is not null + and frd.inquiryattributes ->> 'inquirydate' not in ('','null') + and (frd.inquiryattributes ->> 'inquirydate')::date between NOW() - INTERVAL '7 DAY' AND NOW() + INTERVAL '7 DAY' + and frd.outcomeid is null + order by frd.foiministryrequest_id , frd.foiministryrequestversion_id desc;""" + rs = db.session.execute(text(sql)) + for row in rs: + upcomingduerecords.append({"axisrequestid": row["axisrequestid"], "filenumber": row["filenumber"], + "foiministryrequestid": row["foiministryrequestid"], "version": row["foiministryrequestversion"], + "foirequest_id": row["foirequest_id"], "created_at": row["created_at"], "createdby": row["createdby"], + "orderno": row["orderno"],"duedate": row["duedate"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return upcomingduerecords + + @classmethod + def updateduedate(cls, ministryrequestid, duedate, userid)->DefaultMethodResult: + currequest = db.session.query(FOIMinistryRequest).filter_by(foiministryrequestid=ministryrequestid).order_by(FOIMinistryRequest.version.desc()).first() + setattr(currequest,'duedate',duedate) + setattr(currequest,'updated_at',datetime.now().isoformat()) + setattr(currequest,'updatedby',userid) + db.session.commit() + return DefaultMethodResult(True,'Request updated',ministryrequestid) + + @classmethod + def getministriesopenedbyuid(cls, rawrequestid): + ministries = [] + try: + """ + sql = select distinct filenumber, axisrequestid, foiministryrequestid, foirequest_id, pa."name" from "FOIMinistryRequests" fpa + inner join "FOIRequests" frt on fpa.foirequest_id = frt.foirequestid and fpa.foirequestversion_id = frt."version" + inner join "ProgramAreas" pa on fpa.programareaid = pa.programareaid + where fpa.isactive = true and frt.isactive =true and frt.foirawrequestid=:rawrequestid; + """ + sql = """select distinct filenumber, axisrequestid, foiministryrequestid, foirequest_id, pa."name", + assignedministrygroup, assignedministryperson, assignedgroup, assignedto, fs2."name" as status + from "FOIMinistryRequests" fpa + inner join "FOIRequests" frt on fpa.foirequest_id = frt.foirequestid and frt."version" = fpa.foirequestversion_id and frt."version" = 1 + inner join "ProgramAreas" pa on fpa.programareaid = pa.programareaid + inner join "FOIRequestStatuses" fs2 on fpa.requeststatusid = fs2.requeststatusid + where frt.foirawrequestid=:rawrequestid; """ + rs = db.session.execute(text(sql), {'rawrequestid': rawrequestid}) + for row in rs: + ministries.append({"filenumber": row["filenumber"], "axisrequestid": row["axisrequestid"], "name": row["name"], "requestid": row["foirequest_id"],"ministryrequestid": row["foiministryrequestid"], + "assignedministrygroup": row["assignedministrygroup"], "assignedministryperson": row["assignedministryperson"], "assignedgroup": row["assignedgroup"], "assignedto": row["assignedto"], + "id": row["foiministryrequestid"], "foirequestid": row["foirequest_id"], "status": row["status"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return ministries + + + @classmethod + def getactivitybyid(cls, ministryrequestid): + ministries = [] + try: + sql = """select fm2.filenumber, fm2.axisrequestid, fm2.foiministryrequestid, fm2.foirequest_id, fm2.version, fs2."name" as status, + fm2.assignedministrygroup, fm2.assignedministryperson, fm2.assignedgroup, fm2.assignedto + from "FOIMinistryRequests" fm2 inner join "FOIRequestStatuses" fs2 on fm2.requeststatusid = fs2.requeststatusid + where fm2.foiministryrequestid=:ministryrequestid order by version desc""" + rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) + _tmp_state = None + for row in rs: + if row["status"] != _tmp_state: + ministries.append({"id": row["foiministryrequestid"], "foirequestid": row["foirequest_id"], "axisrequestid": row["axisrequestid"], "filenumber": row["filenumber"], "status": row["status"], + "assignedministrygroup": row["assignedministrygroup"], "assignedministryperson": row["assignedministryperson"], + "assignedgroup": row["assignedgroup"], "assignedto": row["assignedto"], "version": row["version"] + }) + _tmp_state = row["status"] + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return ministries + + @classmethod + def getclosedaxisids(cls): + axisids = [] + try: + sql = """ select distinct on (foiministryrequestid) foiministryrequestid, version, axisrequestid + from "FOIMinistryRequests" fr + where requeststatuslabel = :requeststatuslabel + order by foiministryrequestid , version desc, axisrequestid""" + rs = db.session.execute(text(sql), {'requeststatuslabel': StateName.closed.name}) + for row in rs: + axisids.append(row["axisrequestid"]) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return axisids + + @classmethod + def getbasequery(cls, iaoassignee, ministryassignee, userid=None, requestby='IAO', isiaorestrictedfilemanager=False, isministryrestrictedfilemanager=False): + #for advanced search + + _session = db.session + + #ministry filter for group/team + ministryfilter = and_(FOIMinistryRequest.isactive == True, FOIRequestStatus.isactive == True) + + #subquery for getting latest version & proper group/team for FOIMinistryRequest + subquery_ministry_maxversion = _session.query(FOIMinistryRequest.foiministryrequestid, func.max(FOIMinistryRequest.version).label('max_version')).group_by(FOIMinistryRequest.foiministryrequestid).subquery() + joincondition_ministry = [ + subquery_ministry_maxversion.c.foiministryrequestid == FOIMinistryRequest.foiministryrequestid, + subquery_ministry_maxversion.c.max_version == FOIMinistryRequest.version, + ] + + #subquery for getting extension count + subquery_extension_count = _session.query(FOIRequestExtension.foiministryrequest_id , func.count(distinct(FOIRequestExtension.foirequestextensionid)).filter(FOIRequestExtension.isactive == True).label('extensions')).group_by(FOIRequestExtension.foiministryrequest_id).subquery() + + #aliase for onbehalf of applicant info + onbehalf_applicantmapping = aliased(FOIRequestApplicantMapping) + onbehalf_applicant = aliased(FOIRequestApplicant) + + #aliase for getting ministry restricted flag from FOIRestrictedMinistryRequest + ministry_restricted_requests = aliased(FOIRestrictedMinistryRequest) + + intakesorting = case([ + (FOIMinistryRequest.assignedto == None, # Unassigned requests first + literal(None)), + ], + else_ = FOIRequest.receiveddate).label('intakeSorting') + + defaultsorting = case([ + (FOIMinistryRequest.assignedto == None, # Unassigned requests first + literal(None)), + ], + else_ = FOIMinistryRequest.duedate).label('defaultSorting') + + ministrysorting = case([ + (FOIMinistryRequest.assignedministryperson == None, # Unassigned requests first + literal(None)), + ], + else_ = FOIMinistryRequest.duedate).label('ministrySorting') + + assignedtoformatted = case([ + (and_(iaoassignee.lastname.isnot(None), iaoassignee.firstname.isnot(None)), + func.concat(iaoassignee.lastname, ', ', iaoassignee.firstname)), + (and_(iaoassignee.lastname.isnot(None), iaoassignee.firstname.is_(None)), + iaoassignee.lastname), + (and_(iaoassignee.lastname.is_(None), iaoassignee.firstname.isnot(None)), + iaoassignee.firstname), + ], + else_ = FOIMinistryRequest.assignedgroup).label('assignedToFormatted') + + ministryassignedtoformatted = case([ + (and_(ministryassignee.lastname.isnot(None), ministryassignee.firstname.isnot(None)), + func.concat(ministryassignee.lastname, ', ', ministryassignee.firstname)), + (and_(ministryassignee.lastname.isnot(None), ministryassignee.firstname.is_(None)), + ministryassignee.lastname), + (and_(ministryassignee.lastname.is_(None), ministryassignee.firstname.isnot(None)), + ministryassignee.firstname), + ], + else_ = FOIMinistryRequest.assignedministrygroup).label('ministryAssignedToFormatted') + + duedate = case([ + (FOIMinistryRequest.requeststatuslabel == StateName.onhold.name, # On Hold + literal(None)), + ], + else_ = cast(FOIMinistryRequest.duedate, String)).label('duedate') + + cfrduedate = case([ + (FOIMinistryRequest.requeststatuslabel == StateName.onhold.name, # On Hold + literal(None)), + ], + else_ = cast(FOIMinistryRequest.cfrduedate, String)).label('cfrduedate') + + axispagecount = case ([ + (FOIMinistryRequest.axispagecount.isnot(None), FOIMinistryRequest.axispagecount) + ], + else_= literal("0").label("axispagecount") + ) + axislanpagecount = case ([ + (FOIMinistryRequest.axislanpagecount.isnot(None), FOIMinistryRequest.axislanpagecount) + ], + else_= literal("0").label("axislanpagecount") + ) + recordspagecount = case ([ + (FOIMinistryRequest.recordspagecount.isnot(None), FOIMinistryRequest.recordspagecount) + ], + else_= literal("0").label("recordspagecount") + ) + requestpagecount = case([ + (and_(FOIMinistryRequest.axispagecount.isnot(None), FOIMinistryRequest.recordspagecount.isnot(None), cast(axispagecount, Integer) > cast(recordspagecount, Integer)), + FOIMinistryRequest.axispagecount), + (and_(FOIMinistryRequest.recordspagecount.isnot(None)), + FOIMinistryRequest.recordspagecount), + (and_(FOIMinistryRequest.axispagecount.isnot(None)), + FOIMinistryRequest.axispagecount), + ], + else_= literal("0")) + + onbehalfformatted = case([ + (and_(onbehalf_applicant.lastname.isnot(None), onbehalf_applicant.firstname.isnot(None)), + func.concat(onbehalf_applicant.lastname, ', ', onbehalf_applicant.firstname)), + (and_(onbehalf_applicant.lastname.isnot(None), onbehalf_applicant.firstname.is_(None)), + onbehalf_applicant.lastname), + (and_(onbehalf_applicant.lastname.is_(None), onbehalf_applicant.firstname.isnot(None)), + onbehalf_applicant.firstname), + ], + else_ = 'N/A').label('onBehalfFormatted') + + extensions = case([ + (subquery_extension_count.c.extensions.is_(None), + 0), + ], + else_ = subquery_extension_count.c.extensions).label('extensions') + + selectedcolumns = [ + FOIRequest.foirequestid.label('id'), + FOIMinistryRequest.version, + literal(None).label('sourceofsubmission'), + FOIRequestApplicant.firstname.label('firstName'), + FOIRequestApplicant.lastname.label('lastName'), + FOIRequest.requesttype.label('requestType'), + cast(FOIRequest.receiveddate, String).label('receivedDate'), + cast(FOIRequest.receiveddate, String).label('receivedDateUF'), + FOIRequestStatus.name.label('currentState'), + FOIMinistryRequest.assignedgroup.label('assignedGroup'), + FOIMinistryRequest.assignedto.label('assignedTo'), + cast(FOIMinistryRequest.filenumber, String).label('idNumber'), + cast(FOIMinistryRequest.axisrequestid, String).label('axisRequestId'), + cast(requestpagecount, Integer).label('requestpagecount'), + axispagecount, + axislanpagecount, + recordspagecount, + FOIMinistryRequest.foiministryrequestid.label('ministryrequestid'), + FOIMinistryRequest.assignedministrygroup.label('assignedministrygroup'), + FOIMinistryRequest.assignedministryperson.label('assignedministryperson'), + cfrduedate, + duedate, + ApplicantCategory.name.label('applicantcategory'), + FOIRequest.created_at.label('created_at'), + func.lower(ProgramArea.bcgovcode).label('bcgovcode'), + iaoassignee.firstname.label('assignedToFirstName'), + iaoassignee.lastname.label('assignedToLastName'), + ministryassignee.firstname.label('assignedministrypersonFirstName'), + ministryassignee.lastname.label('assignedministrypersonLastName'), + FOIMinistryRequest.description, + cast(FOIMinistryRequest.recordsearchfromdate, String).label('recordsearchfromdate'), + cast(FOIMinistryRequest.recordsearchtodate, String).label('recordsearchtodate'), + onbehalf_applicant.firstname.label('onBehalfFirstName'), + onbehalf_applicant.lastname.label('onBehalfLastName'), + defaultsorting, + intakesorting, + ministrysorting, + assignedtoformatted, + ministryassignedtoformatted, + FOIMinistryRequest.closedate, + onbehalfformatted, + extensions, + FOIRestrictedMinistryRequest.isrestricted.label('isiaorestricted'), + ministry_restricted_requests.isrestricted.label('isministryrestricted'), + SubjectCode.name.label('subjectcode'), + FOIMinistryRequest.isoipcreview.label('isoipcreview'), + literal(None).label('oipc_number') + ] + + basequery = _session.query( + *selectedcolumns + ).join( + subquery_ministry_maxversion, + and_(*joincondition_ministry) + ).join( + subquery_extension_count, + subquery_extension_count.c.foiministryrequest_id == FOIMinistryRequest.foiministryrequestid, + isouter=True + ).join( + FOIRequest, + and_(FOIRequest.foirequestid == FOIMinistryRequest.foirequest_id, FOIRequest.version == FOIMinistryRequest.foirequestversion_id) + ).join( + FOIRequestStatus, + FOIRequestStatus.requeststatusid == FOIMinistryRequest.requeststatusid + ).join( + FOIRequestApplicantMapping, + and_(FOIRequestApplicantMapping.foirequest_id == FOIMinistryRequest.foirequest_id, FOIRequestApplicantMapping.foirequestversion_id == FOIMinistryRequest.foirequestversion_id, FOIRequestApplicantMapping.requestortypeid == RequestorType['applicant'].value) + ).join( + FOIRequestApplicant, + FOIRequestApplicant.foirequestapplicantid == FOIRequestApplicantMapping.foirequestapplicantid + ).join( + onbehalf_applicantmapping, + and_( + onbehalf_applicantmapping.foirequest_id == FOIMinistryRequest.foirequest_id, + onbehalf_applicantmapping.foirequestversion_id == FOIMinistryRequest.foirequestversion_id, + onbehalf_applicantmapping.requestortypeid == RequestorType['onbehalfof'].value), + isouter=True + ).join( + onbehalf_applicant, + onbehalf_applicant.foirequestapplicantid == onbehalf_applicantmapping.foirequestapplicantid, + isouter=True + ).join( + ApplicantCategory, + and_(ApplicantCategory.applicantcategoryid == FOIRequest.applicantcategoryid, ApplicantCategory.isactive == True) + ).join( + ProgramArea, + FOIMinistryRequest.programareaid == ProgramArea.programareaid + ).join( + iaoassignee, + iaoassignee.username == FOIMinistryRequest.assignedto, + isouter=True + ).join( + ministryassignee, + ministryassignee.username == FOIMinistryRequest.assignedministryperson, + isouter=True + ).join( + FOIRestrictedMinistryRequest, + and_( + FOIRestrictedMinistryRequest.ministryrequestid == FOIMinistryRequest.foiministryrequestid, + FOIRestrictedMinistryRequest.type == 'iao', + FOIRestrictedMinistryRequest.isactive == True), + isouter=True + ).join( + ministry_restricted_requests, + and_( + ministry_restricted_requests.ministryrequestid == FOIMinistryRequest.foiministryrequestid, + ministry_restricted_requests.type == 'ministry', + ministry_restricted_requests.isactive == True), + isouter=True + ).join( + FOIMinistryRequestSubjectCode, + and_(FOIMinistryRequestSubjectCode.foiministryrequestid == FOIMinistryRequest.foiministryrequestid, FOIMinistryRequestSubjectCode.foiministryrequestversion == FOIMinistryRequest.version), + isouter=True + ).join( + SubjectCode, + SubjectCode.subjectcodeid == FOIMinistryRequestSubjectCode.subjectcodeid, + isouter=True + ) + + + if(isiaorestrictedfilemanager == True or isministryrestrictedfilemanager == True): + dbquery = basequery.filter(ministryfilter) + else: + #watchby + activefilter = and_(FOIMinistryRequest.isactive == True, FOIRequestStatus.isactive == True) + + subquery_watchby = FOIRequestWatcher.getrequestidsbyuserid(userid) + newbasequery = basequery.join( + subquery_watchby, + subquery_watchby.c.ministryrequestid == FOIMinistryRequest.foiministryrequestid, + isouter=True).filter(activefilter) + + if(requestby == 'IAO'): + dbquery = newbasequery.filter( + or_( + or_(FOIRestrictedMinistryRequest.isrestricted == False, FOIRestrictedMinistryRequest.isrestricted.is_(None)), + and_(FOIRestrictedMinistryRequest.isrestricted == True, FOIMinistryRequest.assignedto == userid), + and_(FOIRestrictedMinistryRequest.isrestricted == True, subquery_watchby.c.watchedby == userid), + ) + ).filter(ministryfilter) + else: + dbquery = newbasequery.filter( + or_( + or_(ministry_restricted_requests.isrestricted == False, ministry_restricted_requests.isrestricted.is_(None)), + and_(ministry_restricted_requests.isrestricted == True, FOIMinistryRequest.assignedministryperson == userid), + and_(ministry_restricted_requests.isrestricted == True, subquery_watchby.c.watchedby == userid), + ) + ).filter(ministryfilter) + + return dbquery + + @classmethod + def advancedsearch(cls, params, userid, isministryrestrictedfilemanager = False): + #ministry requests + iaoassignee = aliased(FOIAssignee) + ministryassignee = aliased(FOIAssignee) + + groupfilter = [] + for group in params['groups']: + groupfilter.append(FOIMinistryRequest.assignedministrygroup == group) + + #ministry advanced search show cfr onwards + statefilter = FOIMinistryRequest.requeststatuslabel.in_([StateName.callforrecords.name,StateName.closed.name,StateName.recordsreview.name,StateName.feeestimate.name,StateName.consult.name,StateName.ministrysignoff.name,StateName.onhold.name,StateName.deduplication.name,StateName.harmsassessment.name,StateName.response.name,StateName.peerreview.name,StateName.tagging.name,StateName.readytoscan.name,StateName.recordsreadyforreview.name]) + + ministry_queue = FOIMinistryRequest.advancedsearchsubquery(params, iaoassignee, ministryassignee, userid, 'Ministry', False, isministryrestrictedfilemanager).filter(and_(or_(*groupfilter), statefilter)) + + #sorting + sortingcondition = FOIMinistryRequest.getsorting(params['sortingitems'], params['sortingorders'], iaoassignee, ministryassignee) + + return ministry_queue.order_by(*sortingcondition).paginate(page=params['page'], per_page=params['size']) + + @classmethod + def advancedsearchsubquery(cls, params, iaoassignee, ministryassignee, userid, requestby, isiaorestrictedfilemanager, isministryrestrictedfilemanager=False): + basequery = FOIMinistryRequest.getbasequery(iaoassignee, ministryassignee, userid, requestby, isiaorestrictedfilemanager, isministryrestrictedfilemanager) + + #filter/search + filtercondition = FOIMinistryRequest.getfilterforadvancedsearch(params, iaoassignee, ministryassignee) + return basequery.filter(and_(*filtercondition)) + + @classmethod + def getfilterforadvancedsearch(cls, params, iaoassignee, ministryassignee): + + #filter/search + filtercondition = [] + includeclosed = False + + #request state: unopened, call for records, etc. + if(len(params['requeststate']) > 0): + requeststatecondition = FOIMinistryRequest.getfilterforrequeststate(params, includeclosed) + filtercondition.append(requeststatecondition['condition']) + includeclosed = requeststatecondition['includeclosed'] + + #request status: overdue || on time + if(len(params['requeststatus']) == 1): + requeststatuscondition = FOIMinistryRequest.getfilterforrequeststatus(params, iaoassignee, ministryassignee) + filtercondition.append(requeststatuscondition) + + # return all except closed + if(includeclosed == False): + filtercondition.append(FOIMinistryRequest.requeststatuslabel != StateName.closed.name) + elif(len(params['requeststatus']) > 1 and includeclosed == False): + # return all except closed + filtercondition.append(FOIMinistryRequest.requeststatuslabel != StateName.closed.name) + + #request type: personal, general + if(len(params['requesttype']) > 0): + requesttypecondition = FOIMinistryRequest.getfilterforrequesttype(params, iaoassignee, ministryassignee) + filtercondition.append(requesttypecondition) + + #request flags: restricted, oipc, phased + if(len(params['requestflags']) > 0): + requestflagscondition = FOIMinistryRequest.getfilterforrequestflags(params, iaoassignee, ministryassignee) + filtercondition.append(requestflagscondition) + + #public body: EDUC, etc. + if(len(params['publicbody']) > 0): + publicbodycondition = FOIMinistryRequest.getfilterforpublicbody(params, iaoassignee, ministryassignee) + filtercondition.append(publicbodycondition) + + #axis request #, raw request #, applicant name, assignee name, request description, subject code + if(len(params['keywords']) > 0 and params['search'] is not None): + searchcondition = FOIMinistryRequest.getfilterforsearch(params, iaoassignee, ministryassignee) + filtercondition.append(searchcondition) + + if(params['fromdate'] is not None and params['daterangetype'] is not None): + filtercondition.append(FOIMinistryRequest.findfield(params['daterangetype'], iaoassignee, ministryassignee).cast(Date) >= parser.parse(params['fromdate'])) + + if(params['todate'] is not None and params['daterangetype'] is not None): + filtercondition.append(FOIMinistryRequest.findfield(params['daterangetype'], iaoassignee, ministryassignee).cast(Date) <= parser.parse(params['todate'])) + + return filtercondition + + @classmethod + def getfilterforrequeststate(cls, params, includeclosed): + #request state: unopened, call for records, etc. + requeststatecondition = [] + for statelabel in params['requeststate']: + requeststatecondition.append(FOIMinistryRequest.requeststatuslabel == statelabel) + if(statelabel == StateName.closed.name): + includeclosed = True + return {'condition': or_(*requeststatecondition), 'includeclosed': includeclosed} + + @classmethod + def getfilterforrequeststatus(cls, params, iaoassignee, ministryassignee): + #request status: overdue || on time + if(params['requeststatus'][0] == 'overdue'): + #exclude "on hold" for overdue + statelabel = StateName.onhold.name + return and_(FOIMinistryRequest.findfield('duedate', iaoassignee, ministryassignee) < datetime.now().date(), FOIMinistryRequest.requeststatuslabel != statelabel) + else: + return FOIMinistryRequest.findfield('duedate', iaoassignee, ministryassignee) >= datetime.now().date() + + @classmethod + def getfilterforrequesttype(cls, params, iaoassignee, ministryassignee): + #request type: personal, general + requesttypecondition = [] + for type in params['requesttype']: + requesttypecondition.append(FOIMinistryRequest.findfield('requestType', iaoassignee, ministryassignee) == type) + return or_(*requesttypecondition) + + @classmethod + def getfilterforrequestflags(cls, params, iaoassignee, ministryassignee): + #request flags: restricted, oipc, phased + requestflagscondition = [] + #alias for getting ministry restricted flag from FOIRestrictedMinistryRequest + ministry_restricted_requests = aliased(FOIRestrictedMinistryRequest) + + for flag in params['requestflags']: + if (flag.lower() == 'restricted'): + if(iaoassignee): + requestflagscondition.append(FOIRestrictedMinistryRequest.isrestricted == True) + elif (ministryassignee): + requestflagscondition.append(ministry_restricted_requests.isrestricted == True) + if (flag.lower() == 'oipc'): + requestflagscondition.append(FOIMinistryRequest.findfield('isoipcreview', iaoassignee, ministryassignee) == True) + if (flag.lower() == 'phased'): + # requestflagscondition.append(FOIMinistryRequest.findfield('isphasedrelease', iaoassignee, ministryassignee) == True) + continue + return or_(*requestflagscondition) + + @classmethod + def getfilterforpublicbody(cls, params, iaoassignee, ministryassignee): + #public body: EDUC, etc. + publicbodycondition = [] + for ministry in params['publicbody']: + publicbodycondition.append(FOIMinistryRequest.findfield('ministry', iaoassignee, ministryassignee) == ministry) + return or_(*publicbodycondition) + + @classmethod + def getfilterforsearch(cls, params, iaoassignee, ministryassignee): + #axis request #, raw request #, applicant name, assignee name, request description, subject code + if(len(params['keywords']) > 0 and params['search'] is not None): + if(params['search'] == 'applicantname'): + searchcondition1 = [] + searchcondition2 = [] + for keyword in params['keywords']: + searchcondition1.append(FOIMinistryRequest.findfield('firstName', iaoassignee, ministryassignee).ilike('%'+keyword+'%')) + searchcondition2.append(FOIMinistryRequest.findfield('lastName', iaoassignee, ministryassignee).ilike('%'+keyword+'%')) + return or_(and_(*searchcondition1), and_(*searchcondition2)) + elif(params['search'] == 'assigneename'): + searchcondition1 = [] + searchcondition2 = [] + searchcondition3 = [] + for keyword in params['keywords']: + searchcondition1.append(FOIMinistryRequest.findfield('assignedToFirstName', iaoassignee, ministryassignee).ilike('%'+keyword+'%')) + searchcondition2.append(FOIMinistryRequest.findfield('assignedToLastName', iaoassignee, ministryassignee).ilike('%'+keyword+'%')) + searchcondition3.append(FOIMinistryRequest.assignedgroup.ilike('%'+keyword+'%')) + return or_(and_(*searchcondition1), and_(*searchcondition2), and_(*searchcondition3)) + elif(params['search'] == 'ministryassigneename'): + searchcondition1 = [] + searchcondition2 = [] + for keyword in params['keywords']: + searchcondition1.append(FOIMinistryRequest.findfield('assignedministrypersonFirstName', iaoassignee, ministryassignee).ilike('%'+keyword+'%')) + searchcondition2.append(FOIMinistryRequest.findfield('assignedministrypersonLastName', iaoassignee, ministryassignee).ilike('%'+keyword+'%')) + return or_(and_(*searchcondition1), and_(*searchcondition2)) + elif(params['search'] == 'oipc_number'): + searchcondition1 = [] + searchcondition2 = [] + for keyword in params['keywords']: + oipccondition = FOIRequestOIPC.getrequestidsbyoipcno(keyword) + searchcondition1.append(oipccondition.c.foiministryrequest_id == FOIMinistryRequest.foiministryrequestid) + searchcondition2.append(oipccondition.c.foiministryrequestversion_id == FOIMinistryRequest.version) + return and_(and_(*searchcondition1), and_(*searchcondition2)) + else: + searchcondition = [] + for keyword in params['keywords']: + searchcondition.append(FOIMinistryRequest.findfield(params['search'], iaoassignee, ministryassignee).ilike('%'+keyword+'%')) + return and_(*searchcondition) + @classmethod + def getfilenumberforrequest(cls,requestid, ministryrequestid): + return db.session.query(FOIMinistryRequest.filenumber).filter_by(foiministryrequestid=ministryrequestid, foirequest_id=requestid).first()[0] + + @classmethod + def getaxisrequestidforrequest(cls,requestid, ministryrequestid): + return db.session.query(FOIMinistryRequest.axisrequestid).filter_by(foiministryrequestid=ministryrequestid, foirequest_id=requestid).first()[0] + + @classmethod + def getrequest_by_pgmarea_type(cls,programarea, requesttype): + requestdetails = [] + try: + sql = """select fr.axisrequestid, fr.foiministryrequestid, fr."version", fr.axispagecount, fr.axislanpagecount + from "FOIMinistryRequests" fr join "FOIRequests" f + ON fr.foirequest_id = f.foirequestid and fr.foirequestversion_id = f."version" + where programareaid = :programarea and f.requesttype = :requesttype and fr.isactive = true + order by fr.created_at ;""" + rs = db.session.execute(text(sql), {'programarea': programarea, 'requesttype': requesttype}) + for row in rs: + requestdetails.append({"axisrequestid":row["axisrequestid"], "axispagecount": row["axispagecount"], "axislanpagecount": row["axislanpagecount"], "foiministryrequestid":row["foiministryrequestid"], "version":row["version"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return requestdetails + + @classmethod + def bulk_update_axispagecount(cls, requestdetails): + try: + db.session.bulk_update_mappings(FOIMinistryRequest, requestdetails) + db.session.commit() + return DefaultMethodResult(True,'Request updated', len(requestdetails)) + except Exception as ex: + logging.error(ex) + return DefaultMethodResult(False,'Request update failed', len(requestdetails)) + finally: + db.session.close() + + + @classmethod + def getmetadata(cls,ministryrequestid): + requestdetails = {} + try: + sql = """select fmr.version, assignedto, fa.firstname, fa.lastname, pa.bcgovcode, fmr.programareaid, f.requesttype, fmr.isoipcreview + from "FOIMinistryRequests" fmr join "FOIRequests" f on fmr.foirequest_id = f.foirequestid and fmr.foirequestversion_id = f."version" + FULL OUTER JOIN "FOIAssignees" fa ON fa.username = fmr.assignedto + INNER JOIN "ProgramAreas" pa ON pa.programareaid = fmr.programareaid + where foiministryrequestid = :ministryrequestid + order by fmr.version desc limit 1;""" + rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) + for row in rs: + requestdetails["assignedTo"] = row["assignedto"] + requestdetails["assignedToFirstName"] = row["firstname"] + requestdetails["assignedToLastName"] = row["lastname"] + requestdetails["bcgovcode"] = row["bcgovcode"] + requestdetails["version"] = row["version"] + requestdetails["programareaid"] = row["programareaid"] + requestdetails["requesttype"] = row["requesttype"] + requestdetails["isoipcreview"] = row["isoipcreview"] + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return requestdetails + + @classmethod + def getofflinepaymentflag(cls,ministryrequestid): + try: + sql = """select isofflinepayment from "FOIMinistryRequests" fci where + foiministryrequestid = :ministryrequestid and isofflinepayment = true limit 1;""" + rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) + for row in rs: + if row["isofflinepayment"] == True: + return True + return False + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + + @classmethod + def updaterecordspagecount(cls, ministryrequestid, pagecount, userid): + currequest = db.session.query(FOIMinistryRequest).filter_by(foiministryrequestid=ministryrequestid).order_by(FOIMinistryRequest.version.desc()).first() + setattr(currequest,'recordspagecount',pagecount) + setattr(currequest,'updated_at',datetime.now().isoformat()) + setattr(currequest,'updatedby',userid) + db.session.commit() + return DefaultMethodResult(True,'Request updated',ministryrequestid) + +class FOIMinistryRequestSchema(ma.Schema): + class Meta: + fields = ('foiministryrequestid','version','filenumber','description','recordsearchfromdate','recordsearchtodate', + 'startdate','duedate','assignedgroup','assignedto','programarea.programareaid', + 'foirequest.foirequestid','foirequest.requesttype','foirequest.receiveddate','foirequest.deliverymodeid', + 'foirequest.receivedmodeid','requeststatus.requeststatusid','requeststatuslabel','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', 'axispagecount', 'axislanpagecount', 'linkedrequests', 'ministrysignoffapproval', 'identityverified','originalldd', + 'isoipcreview', 'recordspagecount', 'estimatedpagecount', 'estimatedtaggedpagecount') + diff --git a/historical-search-api/request_api/models/FOIRawRequestComments.py b/historical-search-api/request_api/models/FOIRawRequestComments.py new file mode 100644 index 000000000..3d55b4d16 --- /dev/null +++ b/historical-search-api/request_api/models/FOIRawRequestComments.py @@ -0,0 +1,165 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey +from .db import db, ma +from datetime import datetime +from sqlalchemy.orm import relationship, backref +from .default_method_result import DefaultMethodResult +from sqlalchemy.dialects.postgresql import JSON, UUID, insert +from sqlalchemy.sql.expression import distinct +from sqlalchemy import text +import logging +import json + + +class FOIRawRequestComment(db.Model): + # Name of the table in our database + __tablename__ = 'FOIRawRequestComments' + # Defining the columns + commentid = db.Column(db.Integer, primary_key=True, autoincrement=True) + requestid = db.Column( + db.Integer, db.ForeignKey('FOIRawRequests.requestid')) + version = db.Column(db.Integer, db.ForeignKey('FOIRawRequests.version')) + comment = db.Column(db.Text, unique=False, nullable=True) + taggedusers = db.Column(JSON, unique=False, nullable=True) + parentcommentid = db.Column(db.Integer, nullable=True) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + created_at = db.Column(db.DateTime, default=datetime.now) + createdby = db.Column(db.String(120), unique=False, nullable=True) + updated_at = db.Column(db.DateTime, nullable=True) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + + commenttypeid = db.Column(db.Integer, unique=False, nullable=False) + commentsversion = db.Column(db.Integer, nullable=False) + + @classmethod + def savecomment(cls, commenttypeid, foirequestcomment, version, userid) -> DefaultMethodResult: + commentsversion = 1 + parentcommentid = foirequestcomment["parentcommentid"] if 'parentcommentid' in foirequestcomment else None + taggedusers = foirequestcomment["taggedusers"] if 'taggedusers' in foirequestcomment else None + newcomment = FOIRawRequestComment(commenttypeid=commenttypeid, requestid=foirequestcomment["requestid"], version=version, comment=foirequestcomment["comment"], parentcommentid=parentcommentid, isactive=True, createdby=userid,taggedusers=taggedusers, commentsversion=commentsversion) + db.session.add(newcomment) + db.session.commit() + return DefaultMethodResult(True, 'Comment added', newcomment.commentid) + + @classmethod + def disablecomment(cls, commentid, userid): + dbquery = db.session.query(FOIRawRequestComment) + comment = dbquery.filter_by(commentid=commentid) + if(comment.count() > 0): + childcomments = dbquery.filter_by(parentcommentid=commentid, isactive=True) + if (childcomments.count() > 0) : + return DefaultMethodResult(False,'Cannot delete parent comment with replies',commentid) + comment.update({FOIRawRequestComment.isactive: False, FOIRawRequestComment.updatedby: userid, + FOIRawRequestComment.updated_at: datetime.now()}, synchronize_session=False) + db.session.commit() + return DefaultMethodResult(True, 'Comment disabled', commentid) + else: + return DefaultMethodResult(True, 'No Comment found', commentid) + + @classmethod + def deactivatecomment(cls, commentid, userid, commentsversion): + dbquery = db.session.query(FOIRawRequestComment) + comment = dbquery.filter_by(commentid=commentid, commentsversion=commentsversion) + if(comment.count() > 0) : + comment.update({FOIRawRequestComment.isactive: False, FOIRawRequestComment.updatedby: userid, + FOIRawRequestComment.updated_at: datetime.now()}, synchronize_session=False) + db.session.commit() + return DefaultMethodResult(True,'Comment deactivated',commentid) + else: + return DefaultMethodResult(True,'No Comment found',commentid) + + @classmethod + def updatecomment(cls, commentid, foirequestcomment, userid): + dbquery = db.session.query(FOIRawRequestComment) + comment = dbquery.filter_by(commentid=commentid).order_by(FOIRawRequestComment.commentsversion.desc()).first() + _commentsversion = 0 + _existingtaggedusers = [] + if comment is not None : + _existingtaggedusers = comment.taggedusers + _taggedusers = foirequestcomment["taggedusers"] if 'taggedusers' in foirequestcomment else _existingtaggedusers + _commentsversion = int(comment.commentsversion) + insertstmt = ( + insert(FOIRawRequestComment). + values( + commentid= comment.commentid, + requestid=comment.requestid, + version=comment.version, + comment=foirequestcomment["comment"], + taggedusers=_taggedusers, + parentcommentid=comment.parentcommentid, + isactive=True, + created_at=datetime.now(), + createdby=userid, + updated_at=datetime.now(), + updatedby=userid, + commenttypeid=comment.commenttypeid, + commentsversion=_commentsversion + 1 + ) + ) + updatestmt = insertstmt.on_conflict_do_update(index_elements=[FOIRawRequestComment.commentid, FOIRawRequestComment.commentsversion], + set_={"requestid": comment.requestid, "version":comment.version, "comment": foirequestcomment["comment"], + "taggedusers":_taggedusers, "parentcommentid":comment.parentcommentid, "isactive":True, + "created_at":datetime.now(), "createdby": userid, "updated_at": datetime.now(), "updatedby": userid, + "commenttypeid": comment.commenttypeid + } + ) + db.session.execute(updatestmt) + db.session.commit() + return DefaultMethodResult(True, 'Updated Comment added', commentid, _existingtaggedusers, _commentsversion) + else: + return DefaultMethodResult(True, 'No Comment found', commentid, _existingtaggedusers, _commentsversion) + + @classmethod + def getcomments(cls, requestid) -> DefaultMethodResult: + comment_schema = FOIRawRequestCommentSchema(many=True) + query = db.session.query(FOIRawRequestComment).filter_by( + requestid=requestid, isactive=True).order_by(FOIRawRequestComment.commentid.asc()).all() + return comment_schema.dump(query) + + # comments = [] + # try: + # sql = """SELECT distinct on (commentid) commentid, parentcommentid, requestid, version, comment, + # created_at, createdby, updated_at, updatedby, isactive, commenttypeid, taggedusers, commentsversion + # FROM public."FOIRawRequestComments" where requestid = :requestid and isactive = true order by commentid, commentsversion desc;""" + # rs = db.session.execute(text(sql), {'requestid': requestid}) + # for row in rs: + # comments.append(dict(row)) + # except Exception as ex: + # logging.error(ex) + # raise ex + # finally: + # db.session.close() + # return comments + + + @classmethod + def getcommentbyid(cls, commentid) -> DefaultMethodResult: + comment_schema = FOIRawRequestCommentSchema() + query = db.session.query(FOIRawRequestComment).filter_by( + commentid=commentid, isactive=True).first() + return comment_schema.dump(query) + + + @classmethod + def getcommentusers(cls, commentid): + users = [] + try: + sql = """select commentid, createdby, taggedusers from ( + select commentid, commenttypeid, createdby, taggedusers from "FOIRawRequestComments" frc where commentid = (select parentcommentid from "FOIRawRequestComments" frc where commentid=:commentid) and isactive = true + union all + select commentid, commenttypeid, createdby, taggedusers from "FOIRawRequestComments" frc where commentid <> :commentid and parentcommentid = (select parentcommentid from "FOIRawRequestComments" frc where commentid=:commentid) and isactive = true + ) cmt where commenttypeid =1""" + rs = db.session.execute(text(sql), {'commentid': commentid}) + for row in rs: + users.append({"commentid": row["commentid"], "createdby": row["createdby"], "taggedusers": row["taggedusers"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return users + +class FOIRawRequestCommentSchema(ma.Schema): + class Meta: + fields = ('commentid', 'requestid', 'parentcommentid', 'comment', 'commenttypeid', + 'commenttype', 'isactive', 'created_at', 'createdby', 'updated_at', 'updatedby','taggedusers', 'commentsversion') diff --git a/historical-search-api/request_api/models/FOIRawRequestDocuments.py b/historical-search-api/request_api/models/FOIRawRequestDocuments.py new file mode 100644 index 000000000..465e3a2cf --- /dev/null +++ b/historical-search-api/request_api/models/FOIRawRequestDocuments.py @@ -0,0 +1,88 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint +from .db import db, ma +from datetime import datetime +from sqlalchemy.orm import relationship,backref +from .default_method_result import DefaultMethodResult +from sqlalchemy.sql.expression import distinct +from sqlalchemy import or_,and_,text +import logging +class FOIRawRequestDocument(db.Model): + # Name of the table in our database + __tablename__ = 'FOIRawRequestDocuments' + __table_args__ = ( + ForeignKeyConstraint( + ["foirequest_id", "foirequestversion_id"], ["FOIRawRequests.requestid", "FOIRawRequests.version"] + ), + ) + + # Defining the columns + foidocumentid = db.Column(db.Integer, primary_key=True,autoincrement=True) + documentpath = db.Column(db.String(1000), unique=False, nullable=False) + filename = db.Column(db.String(120), unique=False, nullable=True) + category = db.Column(db.String(120), unique=False, nullable=True) + version =db.Column(db.Integer, nullable=True) + isactive = db.Column(db.Boolean, unique=False, nullable=False,default=True) + + created_at = db.Column(db.DateTime, default=datetime.now) + updated_at = db.Column(db.DateTime, nullable=True) + createdby = db.Column(db.String(120), unique=False, nullable=True) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + + #ForeignKey References + foirequest_id =db.Column(db.Integer, unique=False, nullable=False) + foirequestversion_id = db.Column(db.Integer, unique=False, nullable=False) + + @classmethod + def getdocuments(cls,requestid, requestversion): + documents = [] + try: + 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: + if row["isactive"] == True: + documents.append({"foidocumentid": row["foidocumentid"], "filename": row["filename"], "documentpath": row["documentpath"], "category": row["category"], "created_at": row["created_at"].strftime('%Y-%m-%d %H:%M:%S.%f'), "createdby": row["createdby"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return documents + + @classmethod + def getdocument(cls,foidocumentid): + document_schema = FOIRawRequestDocumentSchema() + request = db.session.query(FOIRawRequestDocument).filter_by(foidocumentid=foidocumentid).order_by(FOIRawRequestDocument.version.desc()).first() + return document_schema.dump(request) + + @classmethod + def createdocuments(cls,requestid,requestversion, documents, userid): + newdocuments = [] + for document in documents: + createuserid = document['createdby'] if 'createdby' in document and document['createdby'] is not None else userid + createdat = document['created_at'] if 'created_at' in document and document['created_at'] is not None else datetime.now() + newdocuments.append(FOIRawRequestDocument(documentpath=document["documentpath"], version='1', filename=document["filename"], category=document["category"], isactive=True, foirequest_id=requestid, foirequestversion_id=requestversion, created_at=createdat, createdby=createuserid)) + db.session.add_all(newdocuments) + db.session.commit() + return DefaultMethodResult(True,'Documents created') + + + @classmethod + def createdocumentversion(cls,requestid,requestversion, document, userid): + newdocument = FOIRawRequestDocument(documentpath=document["documentpath"], foidocumentid=document["foidocumentid"], version=document["version"], filename=document["filename"], category=document["category"], isactive=document["isactive"], foirequest_id=requestid, foirequestversion_id=requestversion, created_at=datetime.now(), createdby=userid) + db.session.add(newdocument) + db.session.commit() + return DefaultMethodResult(True,'New Document version created', newdocument.foidocumentid) + + @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.commit() + return DefaultMethodResult(True,'Raw Request Document Updated',documentid) + + +class FOIRawRequestDocumentSchema(ma.Schema): + class Meta: + fields = ('foidocumentid','documentpath', 'filename','category','version','isactive','foirequest_id','foirequestversion_id','created_at','createdby') + \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRawRequestNotificationUsers.py b/historical-search-api/request_api/models/FOIRawRequestNotificationUsers.py new file mode 100644 index 000000000..9d3b15523 --- /dev/null +++ b/historical-search-api/request_api/models/FOIRawRequestNotificationUsers.py @@ -0,0 +1,270 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey +from .db import db, ma +from datetime import datetime as datetime2, timezone +from sqlalchemy.orm import relationship,backref +from .default_method_result import DefaultMethodResult +from sqlalchemy.dialects.postgresql import JSON, UUID +from sqlalchemy.sql.expression import distinct +from sqlalchemy import text +import logging +import json +from sqlalchemy.sql.sqltypes import DateTime, String, Date, Integer +from sqlalchemy.orm import relationship, backref, aliased +from sqlalchemy import insert, and_, or_, text, func, literal, cast, asc, desc, case, nullsfirst, nullslast, TIMESTAMP, extract +from .FOIRawRequestNotifications import FOIRawRequestNotification +from .FOIRawRequestWatchers import FOIRawRequestWatcher +from request_api.utils.enums import ProcessingTeamWithKeycloackGroup, IAOTeamWithKeycloackGroup +from request_api.models.views.FOINotifications import FOINotifications +from request_api.models.views.FOIRawRequests import FOIRawRequests +f = open('common/notificationusertypes.json', encoding="utf8") +notificationusertypes_cache = json.load(f) + +file = open('common/notificationtypes.json', encoding="utf8") +notificationtypes_cache = json.load(file) + +class FOIRawRequestNotificationUser(db.Model): + # Name of the table in our database + __tablename__ = 'FOIRawRequestNotificationUsers' + # Defining the columns + notificationuserid = db.Column(db.Integer, primary_key=True,autoincrement=True) + notificationid = db.Column(db.Integer,ForeignKey('FOIRawRequestNotifications.notificationid')) + userid = db.Column(db.String(100), unique=False, nullable=True) + isdeleted = db.Column(db.Boolean, unique=False, nullable=True, default=False) + created_at = db.Column(db.DateTime, default=datetime2.now) + createdby = db.Column(db.String(120), unique=False, nullable=True) + updated_at = db.Column(db.DateTime, nullable=True) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + notificationusertypeid = db.Column(db.Integer,nullable=False) + notificationusertypelabel = db.Column(db.String(120),nullable=False) + + + @classmethod + def dismissnotification(cls, notificationuserid, userid='system'): + exists = bool(db.session.query(FOIRawRequestNotificationUser.notificationuserid).filter_by(notificationuserid=notificationuserid).first()) + if exists == False: + return DefaultMethodResult(False,'Invalid ID',notificationuserid) + db.session.query(FOIRawRequestNotificationUser).filter(FOIRawRequestNotificationUser.notificationuserid == notificationuserid).update({FOIRawRequestNotificationUser.isdeleted: True, FOIRawRequestNotificationUser.updatedby: userid, + FOIRawRequestNotificationUser.updated_at: datetime2.now()}) + db.session.commit() + return DefaultMethodResult(True,'Notification deleted',notificationuserid) + + @classmethod + def dismissnotificationbyuser(cls, userid): + db.session.query(FOIRawRequestNotificationUser).filter(FOIRawRequestNotificationUser.userid == userid).update({FOIRawRequestNotificationUser.isdeleted: True, FOIRawRequestNotificationUser.updatedby: userid, + FOIRawRequestNotificationUser.updated_at: datetime2.now()}) + db.session.commit() + return DefaultMethodResult(True,'Notifications deleted for user',userid) + + @classmethod + def dismissnotificationbyuserandtype(cls, userid, notificationusertypelabel): + db.session.query(FOIRawRequestNotificationUser).filter(FOIRawRequestNotificationUser.userid == userid, FOIRawRequestNotificationUser.notificationusertypelabel == notificationusertypelabel).update({FOIRawRequestNotificationUser.isdeleted: True, FOIRawRequestNotificationUser.updatedby: userid, + FOIRawRequestNotificationUser.updated_at: datetime2.now()}) + db.session.commit() + return DefaultMethodResult(True,'Notifications deleted for user',userid) + + @classmethod + def getnotificationsbyid(cls, notificationuserid): + notifications = [] + try: + sql = """select notificationid, count(1) as relcount from "FOIRawRequestNotificationUsers" frnu + where notificationid in (select notificationid from "FOIRawRequestNotificationUsers" frnu where notificationuserid = :notificationuserid) group by notificationid """ + rs = db.session.execute(text(sql), {'notificationuserid': notificationuserid}) + for row in rs: + notifications.append({"notificationid": row["notificationid"], "count" : row["relcount"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return notifications + + @classmethod + def getnotificationsbyuser(cls, userid): + notifications = [] + try: + sql = """select notificationid, count(1) as relcount from "FOIRawRequestNotificationUsers" frnu + where notificationid in (select notificationid from "FOIRawRequestNotificationUsers" frnu where userid = :userid) group by notificationid """ + rs = db.session.execute(text(sql), {'userid': userid}) + for row in rs: + notifications.append({"notificationid": row["notificationid"], "count" : row["relcount"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return notifications + + @classmethod + def getnotificationsbyuserandtype(cls, userid, notificationusertypelabel): + notifications = [] + try: + sql = """select notificationid, count(1) as relcount from "FOIRawRequestNotificationUsers" frnu + where notificationid in (select notificationid from "FOIRawRequestNotificationUsers" frnu where userid = :userid and notificationusertypelabel = :notificationusertypelabel) group by notificationid """ + rs = db.session.execute(text(sql), {'userid': userid, 'notificationusertypelabel': notificationusertypelabel}) + for row in rs: + notifications.append({"notificationid": row["notificationid"], "count" : row["relcount"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return notifications + + @classmethod + def dismissbynotificationid(cls, notificationids, userid='system'): + db.session.query(FOIRawRequestNotificationUser).filter(FOIRawRequestNotificationUser.notificationid.in_(notificationids), FOIRawRequestNotificationUser.isdeleted == False).update({FOIRawRequestNotificationUser.isdeleted: True, FOIRawRequestNotificationUser.updatedby: userid, + FOIRawRequestNotificationUser.updated_at: datetime2.now()}, synchronize_session=False) + db.session.commit() + return DefaultMethodResult(True,'Notifications deleted for id',notificationids) + + @classmethod + def getnotificationidsbyuserandid(cls, userid, notificationids): + ids = [] + try: + sql = """select notificationid from "FOIRawRequestNotificationUsers" where userid = :userid and notificationid = ANY(:notificationids) """ + rs = db.session.execute(text(sql), {'userid': userid, 'notificationids': notificationids}) + for row in rs: + ids.append(row["notificationid"]) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return ids + + # Begin of Dashboard functions + + @classmethod + def getbasequery(cls, groups, additionalfilter=None, userid=None, isiaorestrictedfilemanager=False): + _session = db.session + + selectedcolumns = [ + FOIRawRequests.crtid.label('crtid'), + FOIRawRequests.axisrequestid.label('axisRequestId'), + FOIRawRequests.rawrequestid.label('rawrequestid'), + FOIRawRequests.foirequest_id.label('requestid'), + FOIRawRequests.ministryrequestid.label('ministryrequestid'), + FOIRawRequests.status.label('status'), + FOIRawRequests.assignedtoformatted.label('assignedToFormatted'), + FOIRawRequests.ministryassignedtoformatted.label('ministryAssignedToFormatted'), + FOIRawRequests.description.label('description'), + FOINotifications.notificationtype.label('notificationtype'), + FOINotifications.notification.label('notification'), + FOINotifications.created_at.label('createdat'), + FOINotifications.createdatformatted.label('createdatformatted'), + FOINotifications.userformatted.label('userFormatted'), + FOINotifications.creatorformatted.label('creatorFormatted'), + FOINotifications.id.label('id') + ] + + basequery = _session.query( + *selectedcolumns + ).join( + FOIRawRequests, + and_(FOIRawRequests.axisrequestid == FOINotifications.axisnumber), + ) + + if additionalfilter is not None: + if(additionalfilter == 'watchingRequests' and userid is not None): + #watchby + subquery_watchby = FOIRawRequestWatcher.getrequestidsbyuserid(userid) + return basequery.join(subquery_watchby, subquery_watchby.c.requestid == cast(FOIRawRequests.rawrequestid, Integer)) + elif(additionalfilter == 'myRequests'): + #myrequest + return basequery.filter(or_(FOIRawRequests.assignedto == userid, and_(FOINotifications.userid == userid, FOINotifications.notificationtypelabel == notificationtypes_cache['taggedusercomments']['notificationtypelabel']))) + else: + if(isiaorestrictedfilemanager == True): + return basequery.filter(FOIRawRequests.assignedgroup.in_(groups)) + else: + return basequery.filter( + and_( + or_(FOIRawRequests.isiaorestricted.is_(None), FOIRawRequests.isiaorestricted == False, and_(FOIRawRequests.isiaorestricted == True, FOIRawRequests.assignedto == userid)))).filter(FOIRawRequests.assignedgroup.in_(groups)) + + + @classmethod + def getrequestssubquery(cls, groups, filterfields, keyword, additionalfilter, userid, isiaorestrictedfilemanager): + basequery = FOIRawRequestNotificationUser.getbasequery(groups, additionalfilter, userid, isiaorestrictedfilemanager) + #filter/search + if(len(filterfields) > 0 and keyword is not None): + filtercondition = FOIRawRequestNotificationUser.getfilterforrequestssubquery(filterfields, keyword) + return basequery.filter(filtercondition) + else: + return basequery + + @classmethod + def getfilterforrequestssubquery(cls, filterfields, keyword): + keyword = keyword.lower() + #filter/search + filtercondition = [] + if(keyword != 'restricted'): + for field in filterfields: + _keyword = FOIRawRequestNotificationUser.getfilterkeyword(keyword, field) + filtercondition.append(FOIRawRequestNotificationUser.findfield(field).ilike('%'+_keyword+'%')) + filtercondition.append(FOIRawRequests.isiaorestricted == True) + + return or_(*filtercondition) + + + + @classmethod + def getfilterkeyword(cls, keyword, field): + _newkeyword = keyword + if(field == 'idNumber'): + _newkeyword = _newkeyword.replace('u-00', '') + return _newkeyword + + @classmethod + def findfield(cls, x): + return { + 'axisRequestId' : FOIRawRequests.axisrequestid, + 'createdat': FOINotifications.created_at, + 'createdatformatted': FOINotifications.createdatformatted, + 'notification': FOINotifications.notification, + 'assignedToFormatted': FOIRawRequests.assignedtoformatted, + 'ministryAssignedToFormatted': FOIRawRequests.ministryassignedtoformatted, + 'userFormatted': FOINotifications.userformatted, + 'creatorFormatted': FOINotifications.creatorformatted + }.get(x, cast(FOINotifications.axisnumber, String)) + + + @classmethod + def getsorting(cls, sortingitems, sortingorders): + sortingcondition = [] + if(len(sortingitems) > 0 and len(sortingorders) > 0 and len(sortingitems) == len(sortingorders)): + for field in sortingitems: + if(FOIRawRequestNotificationUser.validatefield(field)): + order = sortingorders.pop(0) + if(order == 'desc'): + sortingcondition.append(nullslast(desc(field))) + else: + sortingcondition.append(nullsfirst(asc(field))) + #default sorting + if(len(sortingcondition) == 0): + sortingcondition.append(desc('createdat')) + + #always sort by created_at last to prevent pagination collisions + sortingcondition.append(desc('createdat')) + + return sortingcondition + + @classmethod + def validatefield(cls, x): + validfields = [ + 'notification', + 'axisRequestId', + 'createdat', + 'assignedToFormatted', + 'ministryAssignedToFormatted', + 'userFormatted', + 'creatorFormatted' + ] + if x in validfields: + return True + else: + return False + # End of Dashboard functions + +class FOIRawRequestNotificationUserSchema(ma.Schema): + class Meta: + fields = ('notificationid', 'userid', 'notificationusertypeid' , 'notificationusertypelabel','created_at','createdby','updated_at','updatedby') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRawRequestNotifications.py b/historical-search-api/request_api/models/FOIRawRequestNotifications.py new file mode 100644 index 000000000..cb21f0769 --- /dev/null +++ b/historical-search-api/request_api/models/FOIRawRequestNotifications.py @@ -0,0 +1,119 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint +from sqlalchemy.sql.schema import ForeignKey +from .db import db, ma +from sqlalchemy.dialects.postgresql import JSON +from datetime import datetime as datetime2, timedelta +from sqlalchemy.orm import relationship,backref +from .default_method_result import DefaultMethodResult +from sqlalchemy.dialects.postgresql import JSON, UUID +from sqlalchemy.sql.expression import distinct +from sqlalchemy import text +import logging +import json +class FOIRawRequestNotification(db.Model): + # Name of the table in our database + __tablename__ = 'FOIRawRequestNotifications' + # Defining the columns + notificationid = db.Column(db.Integer, primary_key=True,autoincrement=True) + requestid =db.Column(db.Integer, db.ForeignKey('FOIRawRequests.requestid')) + version =db.Column(db.Integer, db.ForeignKey('FOIRawRequests.version')) + idnumber = db.Column(db.String(50), unique=False, nullable=True) + axisnumber = db.Column(db.String(50), unique=False, nullable=True) + notification = db.Column(JSON, unique=False, nullable=True) + isdeleted = db.Column(db.Boolean, unique=False, nullable=True, default=False) + created_at = db.Column(db.DateTime, default=datetime2.now) + createdby = db.Column(db.String(120), unique=False, nullable=True) + updated_at = db.Column(db.DateTime, nullable=True) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + + notificationtypeid = db.Column(db.Integer, nullable=False) + notificationtypelabel = db.Column(db.Integer, nullable=False) + + notificationusers = db.relationship('FOIRawRequestNotificationUser', backref='FOIRawRequestNotifications', lazy='dynamic') + + + @classmethod + def savenotification(cls,foinotification)->DefaultMethodResult: + try: + db.session.add(foinotification) + db.session.commit() + return DefaultMethodResult(True,'Notification added',foinotification.requestid) + except: + db.session.rollback() + raise + + @classmethod + def dismissnotification(cls, notificationids, userid='system'): + try: + db.session.query(FOIRawRequestNotification).filter(FOIRawRequestNotification.notificationid.in_(notificationids), FOIRawRequestNotification.isdeleted == False).update({FOIRawRequestNotification.isdeleted: True, FOIRawRequestNotification.updatedby: userid, + FOIRawRequestNotification.updated_at: datetime2.now()}, synchronize_session=False) + db.session.commit() + return DefaultMethodResult(True,'Notifications deleted ', notificationids) + except: + db.session.rollback() + raise + + @classmethod + def updatenotification(cls, foinotification, userid): + dbquery = db.session.query(FOIRawRequestNotification) + _notification = dbquery.filter_by(notificationid=foinotification['notificationid']) + if(_notification.count() > 0) : + _notification.update({ + FOIRawRequestNotification.notification:foinotification['notification'], + FOIRawRequestNotification.updatedby:userid, + FOIRawRequestNotification.updated_at:datetime2.now() + }, synchronize_session = False) + db.session.commit() + return DefaultMethodResult(True,'notification updated',foinotification['notificationid']) + else: + return DefaultMethodResult(True,'No notification found',foinotification['notificationid']) + + @classmethod + def getnotificationidsbynumberandtype(cls, idnumber, notificationtypelabel): + notificationids = [] + try: + sql = """select notificationid from "FOIRawRequestNotifications" where idnumber = :idnumber and notificationtypelabel= :notificationtypelabel """ + rs = db.session.execute(text(sql), {'idnumber': idnumber, 'notificationtypelabel': notificationtypelabel}) + for row in rs: + notificationids.append(row["notificationid"]) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return notificationids + + @classmethod + def getnotificationidsbynumber(cls, idnumber): + notificationids = [] + try: + sql = """select notificationid from "FOIRawRequestNotifications" where idnumber = :idnumber """ + rs = db.session.execute(text(sql), {'idnumber': idnumber}) + for row in rs: + notificationids.append(row["notificationid"]) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return notificationids + + @classmethod + def getnotificationidsbytype(cls, notificationtypelabel): + notificationids = [] + try: + sql = """select notificationid from "FOIRawRequestNotifications" where notificationtypelabel= :notificationtypelabel and isdeleted = false """ + rs = db.session.execute(text(sql), {'notificationtypelabel': notificationtypelabel}) + for row in rs: + notificationids.append(row["notificationid"]) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return notificationids + +class FOIRawRequestNotificationSchema(ma.Schema): + class Meta: + fields = ('notificationid', 'requestid', 'idnumber','notification', 'notificationtypeid', 'notificationtypelabel','created_at','createdby','updated_at','updatedby','notificationusers') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRawRequestWatchers.py b/historical-search-api/request_api/models/FOIRawRequestWatchers.py new file mode 100644 index 000000000..3080d6c58 --- /dev/null +++ b/historical-search-api/request_api/models/FOIRawRequestWatchers.py @@ -0,0 +1,106 @@ +from enum import unique + + +from sqlalchemy.sql.expression import distinct +from .db import db, ma +from sqlalchemy.dialects.postgresql import JSON, UUID +from .default_method_result import DefaultMethodResult +from datetime import datetime +from sqlalchemy import insert, and_, text, func +from flask import jsonify +import logging +class FOIRawRequestWatcher(db.Model): + # Name of the table in our database + __tablename__ = 'FOIRawRequestWatchers' + # Defining the columns + watcherid = db.Column(db.Integer, primary_key=True,autoincrement=True) + requestid =db.Column(db.Integer, db.ForeignKey('FOIRawRequests.requestid')) + version =db.Column(db.Integer, db.ForeignKey('FOIRawRequests.version')) + watchedbygroup = db.Column(db.String(250), unique=False, nullable=True) + watchedby = db.Column(db.String(120), unique=False, nullable=True) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + created_at = db.Column(db.DateTime, default=datetime.now) + createdby = db.Column(db.String(120), unique=False, nullable=True) + updated_at = db.Column(db.DateTime, nullable=True) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + + @classmethod + def savewatcher(cls, foirawrequestwatcher, version, userid)->DefaultMethodResult: + newwatcher = FOIRawRequestWatcher(requestid=foirawrequestwatcher["requestid"], version=version, watchedbygroup=foirawrequestwatcher["watchedbygroup"], watchedby=foirawrequestwatcher["watchedby"], isactive=foirawrequestwatcher["isactive"], createdby=userid) + db.session.add(newwatcher) + db.session.commit() + return DefaultMethodResult(True,'Request added') + + @classmethod + def savewatcherbygroups(cls, foirawrequestwatcher, version, userid, watchergroups)->DefaultMethodResult: + for group in watchergroups: + foirawrequestwatcher["watchedbygroup"] = group + newwatcher = FOIRawRequestWatcher(requestid=foirawrequestwatcher["requestid"], version=version, watchedbygroup=foirawrequestwatcher["watchedbygroup"], watchedby=foirawrequestwatcher["watchedby"], isactive=foirawrequestwatcher["isactive"], createdby=userid) + db.session.add(newwatcher) + db.session.commit() + return DefaultMethodResult(True,'Request added') + + @classmethod + def getwatchers(cls, requestid): + watchers = [] + try: + sql = 'select distinct on (watchedby, watchedbygroup) watchedby, watchedbygroup, isactive from "FOIRawRequestWatchers" where requestid=:requestid order by watchedby, watchedbygroup, created_at desc' + rs = db.session.execute(text(sql), {'requestid': requestid}) + for row in rs: + if row["isactive"] == True: + watchers.append({"watchedby": row["watchedby"], "watchedbygroup": row["watchedbygroup"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return watchers + + @classmethod + def isawatcher(cls, requestid,userid): + _iswatcher = False + try: + sql = 'select distinct on (watchedby, watchedbygroup) watchedby, watchedbygroup, isactive from "FOIRawRequestWatchers" where requestid=:requestid and watchedby=:watchedby order by watchedby, watchedbygroup, created_at desc' + rs = db.session.execute(text(sql), {'requestid': requestid,'watchedby':userid}) + for row in rs: + if row["isactive"] == True: + _iswatcher = True + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return _iswatcher + + @classmethod + def getrequestidsbyuserid(cls, userid): + #subquery for getting latest watching status + subquery_max = db.session.query(FOIRawRequestWatcher.requestid, FOIRawRequestWatcher.watchedby ,func.max(FOIRawRequestWatcher.watcherid).label('max_watcherid')).group_by(FOIRawRequestWatcher.requestid, FOIRawRequestWatcher.watchedby).subquery() + joincondition = [ + subquery_max.c.requestid == FOIRawRequestWatcher.requestid, + subquery_max.c.watchedby == FOIRawRequestWatcher.watchedby, + subquery_max.c.max_watcherid == FOIRawRequestWatcher.watcherid, + ] + + return db.session.query( + FOIRawRequestWatcher.requestid, + FOIRawRequestWatcher.watchedby + ).join( + subquery_max, + and_(*joincondition) + ).filter(and_(FOIRawRequestWatcher.watchedby == userid, FOIRawRequestWatcher.isactive == True)).subquery() + + @classmethod + def disablewatchers(cls, requestid, userid): + dbquery = db.session.query(FOIRawRequestWatcher) + requestraqw = dbquery.filter_by(requestid=requestid) + if(requestraqw.count() > 0) : + requestraqw.update({FOIRawRequestWatcher.isactive:False, FOIRawRequestWatcher.updatedby:userid, FOIRawRequestWatcher.updated_at:datetime.now()}, synchronize_session = False) + db.session.commit() + return DefaultMethodResult(True,'Watchers disabled',requestid) + else: + return DefaultMethodResult(True,'No Watchers found',requestid) + +class FOIRawRequestWatcherSchema(ma.Schema): + class Meta: + fields = ('watcherid', 'requestid', 'watchedbygroup','watchedby','isactive','created_at','createdby','updated_at','updatedby') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRawRequests.py b/historical-search-api/request_api/models/FOIRawRequests.py new file mode 100644 index 000000000..b31e90cc6 --- /dev/null +++ b/historical-search-api/request_api/models/FOIRawRequests.py @@ -0,0 +1,1161 @@ +from enum import unique + +from sqlalchemy.sql.sqltypes import DateTime, String, Date, Integer + +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint +from sqlalchemy.sql.expression import distinct +from .db import db, ma +from sqlalchemy.dialects.postgresql import JSON, UUID +from .default_method_result import DefaultMethodResult +from datetime import datetime +from sqlalchemy.orm import relationship, backref, aliased +from sqlalchemy import insert, and_, or_, text, func, literal, cast, asc, desc, case, nullsfirst, nullslast, TIMESTAMP + +from .FOIMinistryRequests import FOIMinistryRequest +from .FOIRawRequestWatchers import FOIRawRequestWatcher +from .FOIAssignees import FOIAssignee +import logging +from dateutil import parser +import json +from request_api.utils.enums import StateName +from request_api.utils.enums import ProcessingTeamWithKeycloackGroup, IAOTeamWithKeycloackGroup + +class FOIRawRequest(db.Model): + # Name of the table in our database + __tablename__ = 'FOIRawRequests' + # Defining the columns + requestid = db.Column(db.Integer, primary_key=True,autoincrement=True) + version = db.Column(db.Integer, primary_key=True,nullable=False) + requestrawdata = db.Column(JSON, unique=False, nullable=True) + status = db.Column(db.String(25), unique=False, nullable=True) + requeststatuslabel = db.Column(db.String(50), unique=False, nullable=False) + notes = db.Column(db.String(120), unique=False, nullable=True) + wfinstanceid = db.Column(UUID(as_uuid=True), unique=False, 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) + created_at = db.Column(db.DateTime, default=datetime.now) + updated_at = db.Column(db.DateTime, nullable=True) + createdby = db.Column(db.String(120), unique=False, nullable=True) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + sourceofsubmission = db.Column(db.String(120), nullable=True) + ispiiredacted = db.Column(db.Boolean, unique=False, nullable=False,default=False) + closedate = db.Column(db.DateTime, nullable=True) + requirespayment = db.Column(db.Boolean, unique=False, nullable=True, default=False) + + axissyncdate = db.Column(db.DateTime, nullable=True) + axisrequestid = db.Column(db.String(120), nullable=True) + linkedrequests = db.Column(JSON, unique=False, nullable=True) + + + isiaorestricted = db.Column(db.Boolean, unique=False, nullable=False,default=False) + + closereasonid = db.Column(db.Integer,ForeignKey('CloseReasons.closereasonid')) + closereason = relationship("CloseReason", uselist=False) + + assignee = relationship('FOIAssignee', foreign_keys="[FOIRawRequest.assignedto]") + + @classmethod + def saverawrequest(cls, _requestrawdata, sourceofsubmission, ispiiredacted, userid, notes, requirespayment, axisrequestid, axissyncdate, linkedrequests, assigneegroup=None, assignee=None, assigneefirstname=None, assigneemiddlename=None, assigneelastname=None)->DefaultMethodResult: + version = 1 + newrawrequest = FOIRawRequest(requestrawdata=_requestrawdata, status = StateName.unopened.value if sourceofsubmission != "intake" else StateName.intakeinprogress.value, requeststatuslabel = StateName.unopened.name if sourceofsubmission != "intake" else StateName.intakeinprogress.name, createdby=userid, version=version, sourceofsubmission=sourceofsubmission, assignedgroup=assigneegroup, assignedto=assignee, ispiiredacted=ispiiredacted, notes=notes, requirespayment=requirespayment, axisrequestid=axisrequestid, axissyncdate=axissyncdate, linkedrequests=linkedrequests) + if assignee is not None: + FOIAssignee.saveassignee(assignee, assigneefirstname, assigneemiddlename, assigneelastname) + + db.session.add(newrawrequest) + db.session.commit() + return DefaultMethodResult(True,'Request added',newrawrequest.requestid) + + @classmethod + def saverawrequest_foipayment(cls,_requestrawdata,notes, requirespayment, ispiiredacted)->DefaultMethodResult: + version = 1 + newrawrequest = FOIRawRequest(requestrawdata=_requestrawdata, status=StateName.unopened.value, requeststatuslabel = StateName.unopened.name ,createdby=None,version=version,sourceofsubmission="onlineform",assignedgroup=None,assignedto=None,ispiiredacted=ispiiredacted,notes=notes, requirespayment= requirespayment) + db.session.add(newrawrequest) + db.session.commit() + return DefaultMethodResult(True,'Request added',newrawrequest.requestid) + + @classmethod + def saverawrequestversion(cls,_requestrawdata,requestid,assigneegroup,assignee,status,ispiiredacted,userid,statuslabel,assigneefirstname=None,assigneemiddlename=None,assigneelastname=None)->DefaultMethodResult: + request = db.session.query(FOIRawRequest).filter_by(requestid=requestid).order_by(FOIRawRequest.version.desc()).first() + if request is not None: + _assginee = assignee if assignee not in (None,'') else None + if _assginee not in (None,''): + FOIAssignee.saveassignee(_assginee, assigneefirstname, assigneemiddlename, assigneelastname) + + closedate = _requestrawdata["closedate"] if 'closedate' in _requestrawdata else None + closereasonid = _requestrawdata["closereasonid"] if 'closereasonid' in _requestrawdata else None + axisrequestid = _requestrawdata["axisRequestId"] if 'axisRequestId' in _requestrawdata else None + axissyncdate = _requestrawdata["axisSyncDate"] if 'axisSyncDate' in _requestrawdata else None + linkedrequests = _requestrawdata["linkedRequests"] if 'linkedRequests' in _requestrawdata else None + _version = request.version+1 + insertstmt =( + insert(FOIRawRequest). + values( + requestid=request.requestid, + requestrawdata=_requestrawdata, + version=_version, + updatedby=None, + updated_at=datetime.now(), + status=status, + requeststatuslabel=statuslabel, + assignedgroup=assigneegroup, + assignedto=_assginee, + wfinstanceid=request.wfinstanceid, + sourceofsubmission=request.sourceofsubmission, + ispiiredacted=ispiiredacted, + createdby=userid, + closedate=closedate, + closereasonid=closereasonid, + axisrequestid= axisrequestid, + axissyncdate=axissyncdate, + linkedrequests=linkedrequests, + isiaorestricted = request.isiaorestricted + + ) + ) + db.session.execute(insertstmt) + db.session.commit() + return DefaultMethodResult(True,'Request versioned - {0}'.format(str(_version)),requestid,request.wfinstanceid,assignee) + else: + return DefaultMethodResult(True,'No request foound') + + + @classmethod + def saveiaorestrictedrawrequest(cls,requestid,_isiaorestricted=False, _updatedby=None)->DefaultMethodResult: + currentrequest = db.session.query(FOIRawRequest).filter_by(requestid=requestid).order_by(FOIRawRequest.version.desc()).first() + request = currentrequest + _version = currentrequest.version+1 + insertstmt = ( + insert(FOIRawRequest). + values( + requestid=request.requestid, + requestrawdata=request.requestrawdata, + version=_version, + updatedby=_updatedby, + updated_at=datetime.now(), + status=request.status, + assignedgroup=request.assignedgroup, + assignedto=request.assignedto, + wfinstanceid=request.wfinstanceid, + sourceofsubmission=request.sourceofsubmission, + ispiiredacted=request.ispiiredacted, + createdby=request.createdby, + closedate=request.closedate, + closereasonid=request.closereasonid, + axisrequestid= request.axisrequestid, + axissyncdate=request.axissyncdate, + linkedrequests=request.linkedrequests, + created_at=request.created_at, + requirespayment = request.requirespayment, + isiaorestricted =_isiaorestricted, + notes = request.notes, + requeststatuslabel = request.requeststatuslabel, + + ) + ) + db.session.execute(insertstmt) + db.session.commit() + return DefaultMethodResult(True,'Request Updated for iaorestricted - {0}'.format(str(request.version)),requestid,request.wfinstanceid,_isiaorestricted) + + @classmethod + def saverawrequestassigneeversion(cls,requestid,assigneegroup,assignee,userid,assigneefirstname=None,assigneemiddlename=None,assigneelastname=None)->DefaultMethodResult: + request = db.session.query(FOIRawRequest).filter_by(requestid=requestid).order_by(FOIRawRequest.version.desc()).first() + if request is not None: + _assginee = assignee if assignee not in (None,'') else None + if _assginee not in (None,''): + FOIAssignee.saveassignee(_assginee, assigneefirstname, assigneemiddlename, assigneelastname) + + closedate = request.closedate + closereasonid = request.closereasonid + axisrequestid = request.axisrequestid + axissyncdate = request.axissyncdate + linkedrequests=request.linkedrequests + _version = request.version+1 + rawrequest = request.requestrawdata + rawrequest["assignedGroup"] = assigneegroup + rawrequest["assignedTo"] = _assginee + rawrequest["assignedToFirstName"] = assigneefirstname + rawrequest["assignedToLastName"] = assigneelastname + insertstmt =( + insert(FOIRawRequest). + values( + requestid=request.requestid, + requestrawdata=rawrequest, + version=_version, + updatedby=None, + updated_at=datetime.now(), + status=request.status, + assignedgroup=assigneegroup, + assignedto=_assginee, + wfinstanceid=request.wfinstanceid, + sourceofsubmission=request.sourceofsubmission, + ispiiredacted=request.ispiiredacted, + createdby=userid, + closedate=closedate, + closereasonid=closereasonid, + axisrequestid= axisrequestid, + axissyncdate=axissyncdate, + linkedrequests=linkedrequests, + isiaorestricted = request.isiaorestricted, + requeststatuslabel = request.requeststatuslabel + ) + ) + db.session.execute(insertstmt) + db.session.commit() + return DefaultMethodResult(True,'Request versioned - {0}'.format(str(_version)),requestid,request.wfinstanceid,assignee) + else: + return DefaultMethodResult(True,'No request foound') + + @classmethod + def getworkflowinstancebyraw(cls,requestid)->DefaultMethodResult: + request_schema = FOIRawRequestSchema() + try: + sql = """select wfinstanceid, assignedto, assignedgroup, requestid from "FOIRawRequests" fr where requestid = :requestid order by "version" desc limit 1;""" + rs = db.session.execute(text(sql), {'requestid': requestid}) + for row in rs: + request_schema.__dict__.update({"requestid": row["requestid"],"assignedto": row["assignedto"], "assignedgroup": row["assignedgroup"], "wfinstanceid": row["wfinstanceid"]}) + except Exception as ex: + logging.error(ex) + finally: + db.session.close() + return request_schema + + + @classmethod + def getworkflowinstancebyministry(cls,requestid)->DefaultMethodResult: + request_schema = FOIRawRequestSchema() + try: + sql = """select fr.wfinstanceid, fr.assignedto, fr.assignedgroup, fr.requestid + from "FOIMinistryRequests" fr2, "FOIRequests" fr3, "FOIRawRequests" fr + where fr2.foirequest_id = fr3.foirequestid and fr3.foirawrequestid = fr.requestid + and fr2.foiministryrequestid= :requestid order by fr."version" desc limit 1""" + rs = db.session.execute(text(sql), {'requestid': requestid}) + for row in rs: + request_schema.__dict__.update({"requestid": row["requestid"], "assignedto": row["assignedto"], "assignedgroup": row["assignedgroup"], "wfinstanceid": row["wfinstanceid"]}) + except Exception as ex: + logging.error(ex) + finally: + db.session.close() + return request_schema + + @classmethod + def updateworkflowinstance(cls,wfinstanceid,requestid, userid)->DefaultMethodResult: + updatedat = datetime.now() + dbquery = db.session.query(FOIRawRequest) + requestraqw = dbquery.filter_by(requestid=requestid,version = 1) + if(requestraqw.count() > 0) : + existingrequestswithwfid = dbquery.filter_by(wfinstanceid=wfinstanceid) + if(existingrequestswithwfid.count() == 0) : + requestraqw.update({FOIRawRequest.wfinstanceid:wfinstanceid, FOIRawRequest.updated_at:updatedat,FOIRawRequest.updatedby:userid, FOIRawRequest.notes:"WF Instance created"}, synchronize_session = False) + db.session.commit() + return DefaultMethodResult(True,'Request updated with WF Instance Id',requestid) + else: + return DefaultMethodResult(False,'WF instance already exists',requestid) + else: + return DefaultMethodResult(False,'Requestid not exists',-1) + + @classmethod + def updateworkflowinstance_n(cls,wfinstanceid,requestid, userid)->DefaultMethodResult: + updatedat = datetime.now() + dbquery = db.session.query(FOIRawRequest) + _requestraqw = dbquery.filter_by(requestid=requestid).order_by(FOIRawRequest.version.desc()).first() + requestraqw = dbquery.filter_by(requestid=requestid,version = _requestraqw.version) + if(requestraqw.count() > 0) : + requestraqw.update({FOIRawRequest.wfinstanceid:wfinstanceid, FOIRawRequest.updated_at:updatedat,FOIRawRequest.updatedby:userid}, synchronize_session = False) + db.session.commit() + return DefaultMethodResult(True,'Request updated',requestid) + else: + return DefaultMethodResult(False,'Requestid not exists',-1) + + @classmethod + def updateworkflowinstancewithstatus(cls,wfinstanceid,requestid,notes,userid)-> DefaultMethodResult: + updatedat = datetime.now() + dbquery = db.session.query(FOIRawRequest) + _requestraqw = dbquery.filter_by(requestid=requestid).order_by(FOIRawRequest.version.desc()).first() + requestraqw = dbquery.filter_by(requestid=requestid,version = _requestraqw.version) + if(requestraqw.count() > 0) : + request_schema = FOIRawRequestSchema() + request = request_schema.dump(_requestraqw) + status = request["status"] + + requestraqw.update({FOIRawRequest.wfinstanceid:wfinstanceid, FOIRawRequest.updated_at:updatedat,FOIRawRequest.notes:notes,FOIRawRequest.status:status,FOIRawRequest.updatedby:userid}, synchronize_session = False) + db.session.commit() + return DefaultMethodResult(True,'Request updated',requestid) + else: + return DefaultMethodResult(False,'Requestid not exists',-1) + + @classmethod + def getrequests(cls): + _session = db.session + _archivedrequestids = _session.query(distinct(FOIRawRequest.requestid)).filter(FOIRawRequest.status.in_(['Archived'])).all() + _requestids = _session.query(distinct(FOIRawRequest.requestid)).filter(FOIRawRequest.requestid.notin_(_archivedrequestids)).all() + requests = [] + for _requestid in _requestids: + request = _session.query(FOIRawRequest).filter(FOIRawRequest.requestid == _requestid).order_by(FOIRawRequest.version.desc()).first() + requests.append(request) + + return requests + + + @classmethod + def getDescriptionSummaryById(cls, requestid): + requests = [] + try: + sql = """select * , + CASE WHEN description = (select requestrawdata -> 'descriptionTimeframe' ->> 'description' from "FOIRawRequests" where requestid = :requestid and status = :requeststatus and version = 1) + then 'Online Form' + else savedby END as createdby + from (select CASE WHEN lower(status) <> 'unopened' + then requestrawdata ->> 'description' + ELSE requestrawdata -> 'descriptionTimeframe' ->> 'description' END as description , + CASE WHEN lower(status) <> 'unopened' + then requestrawdata ->> 'fromDate' + ELSE requestrawdata -> 'descriptionTimeframe' ->> 'fromDate' END as fromdate, + CASE WHEN lower(status) <> 'unopened' + then requestrawdata ->> 'toDate' + ELSE requestrawdata -> 'descriptionTimeframe' ->> 'toDate' END as todate, + to_char(created_at, 'YYYY-MM-DD HH24:MI:SS') as createdat, status, ispiiredacted, + createdby as savedby from "FOIRawRequests" fr + where requestid = :requestid order by version ) as sq;""" + rs = db.session.execute(text(sql), {'requestid': requestid, 'requeststatus': StateName.unopened.value}) + for row in rs: + requests.append(dict(row)) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return requests + + @classmethod + def get_request(cls,requestid): + request_schema = FOIRawRequestSchema() + request = db.session.query(FOIRawRequest).filter_by(requestid=requestid).order_by(FOIRawRequest.version.desc()).first() + return request_schema.dump(request) + + @classmethod + def getLastStatusUpdateDate(cls,requestid,status): + lastupdatedate = None + try: + sql = """select created_at from "FOIRawRequests" + where requestid = :requestid and status = :status + order by version desc limit 1;""" + rs = db.session.execute(text(sql), {'requestid': requestid, 'status': status}) + lastupdatedate = [row[0] for row in rs][0] + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return lastupdatedate + + @classmethod + def getassignmenttransition(cls,requestid): + assignments = [] + try: + sql = """select version, assignedto, status from "FOIRawRequests" + where requestid = :requestid + order by version desc limit 2;""" + rs = db.session.execute(text(sql), {'requestid': requestid}) + for row in rs: + assignments.append({"assignedto": row["assignedto"], "status": row["status"], "version": row["version"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return assignments + + @classmethod + def getappfeeowingrequests(cls): # with the reminder date + appfeeowingrequests = [] + try: + sql = ''' + SELECT * FROM (SELECT DISTINCT ON (requestid) requestid, updated_at, status FROM public."FOIRawRequests" + ORDER BY requestid ASC, version DESC) r + WHERE r.status = :requeststaus + order by r.updated_at asc + ''' + rs = db.session.execute(text(sql), {'requeststaus': StateName.appfeeowing.value}) + appfeeowingrequests = rs + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return appfeeowingrequests + + @classmethod + def getversionforrequest(cls,requestid): + return db.session.query(FOIRawRequest.version).filter_by(requestid=requestid).order_by(FOIRawRequest.version.desc()).first() + + # @classmethod + # def getstatesummary(cls, requestid): + # transitions = [] + # try: + # sql = """select status, version from (select distinct on (status) status, version from "FOIRawRequests" + # where requestid=:requestid order by status, version asc) as fs3 order by version desc""" + # rs = db.session.execute(text(sql), {'requestid': requestid}) + + # for row in rs: + # transitions.append({"status": row["status"], "version": row["version"]}) + # except Exception as ex: + # logging.error(ex) + # raise ex + # finally: + # db.session.close() + # return transitions + + @classmethod + def getstatesummary(cls, requestid): + transitions = [] + try: + sql = """select status, version from "FOIRawRequests" where requestid=:requestid order by version desc""" + rs = db.session.execute(text(sql), {'requestid': requestid}) + _tmp_state = None + for row in rs: + if row["status"] != _tmp_state: + transitions.append({"status": row["status"], "version": row["version"]}) + _tmp_state = row["status"] + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return transitions + + @classmethod + def getstatenavigation(cls, requestid): + _session = db.session + _requeststates = _session.query(FOIRawRequest.status).filter(FOIRawRequest.requestid == requestid).order_by(FOIRawRequest.version.desc()).limit(2) + requeststates = [] + for _requeststate in _requeststates: + requeststates.append(_requeststate[0]) + return requeststates + + @classmethod + def getbasequery(cls, additionalfilter=None, userid=None, isiaorestrictedfilemanager=False, groups=[]): + _session = db.session + + #rawrequests + #subquery for getting the latest version + subquery_maxversion = _session.query(FOIRawRequest.requestid, func.max(FOIRawRequest.version).label('max_version')).group_by(FOIRawRequest.requestid).subquery() + joincondition = [ + subquery_maxversion.c.requestid == FOIRawRequest.requestid, + subquery_maxversion.c.max_version == FOIRawRequest.version, + ] + + requesttype = case([ + (FOIRawRequest.status == StateName.unopened.value, + FOIRawRequest.requestrawdata['requestType']['requestType'].astext), + ], + else_ = FOIRawRequest.requestrawdata['requestType'].astext).label('requestType') + firstname = case([ + (FOIRawRequest.status == StateName.unopened.value, + FOIRawRequest.requestrawdata['contactInfo']['firstName'].astext), + ], + else_ = FOIRawRequest.requestrawdata['firstName'].astext).label('firstName') + lastname = case([ + (FOIRawRequest.status == StateName.unopened.value, + FOIRawRequest.requestrawdata['contactInfo']['lastName'].astext), + ], + else_ = FOIRawRequest.requestrawdata['lastName'].astext).label('lastName') + description = case([ + (FOIRawRequest.status == StateName.unopened.value, + FOIRawRequest.requestrawdata['descriptionTimeframe']['description'].astext), + ], + else_ = FOIRawRequest.requestrawdata['description'].astext).label('description') + recordsearchfromdate = case([ + (FOIRawRequest.status == StateName.unopened.value, + FOIRawRequest.requestrawdata['descriptionTimeframe']['fromDate'].astext), + ], + else_ = FOIRawRequest.requestrawdata['fromDate'].astext).label('recordsearchfromdate') + recordsearchtodate = case([ + (FOIRawRequest.status == StateName.unopened.value, + FOIRawRequest.requestrawdata['descriptionTimeframe']['toDate'].astext), + ], + else_ = FOIRawRequest.requestrawdata['toDate'].astext).label('recordsearchtodate') + duedate = case([ + (FOIRawRequest.status == StateName.unopened.value, + literal(None)), + ], + else_ = FOIRawRequest.requestrawdata['dueDate'].astext).label('duedate') + receiveddate = case([ + (and_(FOIRawRequest.status == StateName.unopened.value, FOIRawRequest.requestrawdata['receivedDate'].is_(None)), + func.to_char(FOIRawRequest.created_at, 'YYYY-mm-DD')), + ], + else_ = FOIRawRequest.requestrawdata['receivedDate'].astext).label('receivedDate') + receiveddateuf = case([ + (and_(FOIRawRequest.status == StateName.unopened.value, FOIRawRequest.requestrawdata['receivedDateUF'].is_(None)), + func.to_char(FOIRawRequest.created_at, 'YYYY-mm-DD HH:MM:SS')), + ], + else_ = FOIRawRequest.requestrawdata['receivedDateUF'].astext).label('receivedDateUF') + + assignedtoformatted = case([ + (and_(FOIAssignee.lastname.isnot(None), FOIAssignee.firstname.isnot(None)), + func.concat(FOIAssignee.lastname, ', ', FOIAssignee.firstname)), + (and_(FOIAssignee.lastname.isnot(None), FOIAssignee.firstname.is_(None)), + FOIAssignee.lastname), + (and_(FOIAssignee.lastname.is_(None), FOIAssignee.firstname.isnot(None)), + FOIAssignee.firstname), + (and_(FOIAssignee.lastname.is_(None), FOIAssignee.firstname.is_(None), FOIRawRequest.assignedgroup.is_(None)), + 'Unassigned'), + ], + else_ = FOIRawRequest.assignedgroup).label('assignedToFormatted') + + axisrequestid = case([ + (FOIRawRequest.axisrequestid.is_(None), + 'U-00' + cast(FOIRawRequest.requestid, String)), + ], + else_ = cast(FOIRawRequest.axisrequestid, String)).label('axisRequestId') + + requestpagecount = case([ + (FOIRawRequest.requestrawdata['axispagecount'].is_(None), + '0'), + ], + else_ = cast(FOIRawRequest.requestrawdata['axispagecount'], String)) + + intakesorting = case([ + (FOIRawRequest.assignedto == None, # Unassigned requests first + literal(None)), + ], + else_ = cast(FOIRawRequest.requestrawdata['receivedDateUF'].astext, TIMESTAMP)).label('intakeSorting') + + isiaorestricted = case([ + (FOIRawRequest.isiaorestricted.is_(None), + False), + ], + else_ = FOIRawRequest.isiaorestricted).label('isiaorestricted') + + subjectcode = case([ + (FOIRawRequest.requestrawdata['subjectCode'].is_(None), + literal(None)), + ], + else_ = cast(FOIRawRequest.requestrawdata['subjectCode'], String)).label('subjectcode') + + selectedcolumns = [ + FOIRawRequest.requestid.label('id'), + FOIRawRequest.version, + FOIRawRequest.sourceofsubmission, + firstname, + lastname, + requesttype, + receiveddate, + receiveddateuf, + FOIRawRequest.status.label('currentState'), + FOIRawRequest.assignedgroup.label('assignedGroup'), + FOIRawRequest.assignedto.label('assignedTo'), + cast(FOIRawRequest.requestid, String).label('idNumber'), + axisrequestid, + cast(requestpagecount, Integer).label('requestpagecount'), + requestpagecount.label('axispagecount'), + literal(None).label('axislanpagecount'), + literal(None).label('recordspagecount'), + literal(None).label('ministryrequestid'), + literal(None).label('assignedministrygroup'), + literal(None).label('assignedministryperson'), + literal(None).label('cfrduedate'), + duedate, + FOIRawRequest.requestrawdata['category'].astext.label('applicantcategory'), + FOIRawRequest.created_at.label('created_at'), + literal(None).label('bcgovcode'), + FOIAssignee.firstname.label('assignedToFirstName'), + FOIAssignee.lastname.label('assignedToLastName'), + literal(None).label('assignedministrypersonFirstName'), + literal(None).label('assignedministrypersonLastName'), + description, + recordsearchfromdate, + recordsearchtodate, + literal(None).label('onBehalfFirstName'), + literal(None).label('onBehalfLastName'), + literal(None).label('defaultSorting'), + intakesorting, + literal(None).label('ministrySorting'), + assignedtoformatted, + literal(None).label('ministryAssignedToFormatted'), + literal(None).label('closedate'), + literal(None).label('onBehalfFormatted'), + literal(None).label('extensions'), + isiaorestricted, + literal(None).label('isministryrestricted'), + subjectcode, + literal(None).label('isoipcreview'), + literal(None).label('oipc_number') + ] + + basequery = _session.query(*selectedcolumns).join(subquery_maxversion, and_(*joincondition)).join(FOIAssignee, FOIAssignee.username == FOIRawRequest.assignedto, isouter=True) + + return FOIRawRequest.handleadditionalfilter(basequery, additionalfilter, userid, isiaorestrictedfilemanager, groups) + + @classmethod + def handleadditionalfilter(cls, basequery, additionalfilter=None, userid=None, isiaorestrictedfilemanager=False, groups=[]): + if additionalfilter: + return FOIRawRequest.addadditionalfilter(basequery, additionalfilter, userid, isiaorestrictedfilemanager, groups) + return FOIRawRequest.noadditionalfilter(basequery, userid, isiaorestrictedfilemanager) + + @classmethod + def noadditionalfilter(cls, basequery, userid=None, isiaorestrictedfilemanager=False): + if(isiaorestrictedfilemanager == True): + return basequery.filter(FOIRawRequest.status.notin_(['Archived'])) + else: + subquery_watchby = FOIRawRequestWatcher.getrequestidsbyuserid(userid) + + return basequery.join( + subquery_watchby, + subquery_watchby.c.requestid == FOIRawRequest.requestid, + isouter=True + ).filter( + and_( + FOIRawRequest.status.notin_(['Archived']), + or_( + FOIRawRequest.isiaorestricted == False, + and_(FOIRawRequest.isiaorestricted == True, FOIRawRequest.assignedto == userid), + and_(FOIRawRequest.isiaorestricted == True, subquery_watchby.c.watchedby == userid)))) + + @classmethod + def addadditionalfilter(cls, basequery, additionalfilter=None, userid=None, isiaorestrictedfilemanager=False, groups=[]): + isprocessingteam = False + + if groups: + isprocessingteam = any(item in ProcessingTeamWithKeycloackGroup.list() for item in groups) + + if(additionalfilter == 'watchingRequests' and userid is not None): + #watchby + subquery_watchby = FOIRawRequestWatcher.getrequestidsbyuserid(userid) + return basequery.join(subquery_watchby, subquery_watchby.c.requestid == FOIRawRequest.requestid).filter(FOIRawRequest.status.notin_(['Archived'])) + elif(additionalfilter == 'myRequests'): + #myrequest + return basequery.filter(and_(FOIRawRequest.status.notin_(['Archived']), FOIRawRequest.assignedto == userid)) + elif(additionalfilter == 'unassignedRequests'): + return basequery.filter(and_(FOIRawRequest.status.notin_(['Archived']), FOIRawRequest.assignedto == None, FOIRawRequest.assignedgroup.in_(tuple(groups)))) + elif (additionalfilter.lower() == 'all'): + if(isiaorestrictedfilemanager == True): + basequery = basequery.filter(FOIRawRequest.status.notin_(['Archived'])) + else: + if isprocessingteam: + basequery = basequery.filter( + and_( + FOIRawRequest.status.notin_(['Archived']), + or_(and_(FOIRawRequest.isiaorestricted == False, FOIRawRequest.assignedgroup.in_(ProcessingTeamWithKeycloackGroup.list()), FOIRawRequest.assignedgroup.in_(tuple(groups))), and_(FOIRawRequest.isiaorestricted == True, FOIRawRequest.assignedto == userid)))) + basequery = basequery.filter( + and_( + FOIRawRequest.status.notin_(['Archived']), + or_(and_(FOIRawRequest.isiaorestricted == False, FOIRawRequest.assignedgroup == "Intake Team"), and_(FOIRawRequest.isiaorestricted == True, FOIRawRequest.assignedto == userid)))) + return basequery.filter(FOIRawRequest.assignedto != None) + else: + if(isiaorestrictedfilemanager == True): + return basequery.filter(FOIRawRequest.status.notin_(['Archived'])) + else: + return basequery.filter( + and_( + FOIRawRequest.status.notin_(['Archived']), + or_(FOIRawRequest.isiaorestricted == False, and_(FOIRawRequest.isiaorestricted == True, FOIRawRequest.assignedto == userid)))) + + @classmethod + def getrequestssubquery(cls, filterfields, keyword, additionalfilter, userid, isiaorestrictedfilemanager, groups): + basequery = FOIRawRequest.getbasequery(additionalfilter, userid, isiaorestrictedfilemanager, groups) + basequery = basequery.filter(FOIRawRequest.status != 'Unopened').filter(FOIRawRequest.status != 'Closed') + #filter/search + if(len(filterfields) > 0 and keyword is not None): + filtercondition = FOIRawRequest.getfilterforrequestssubquery(filterfields, keyword) + return basequery.filter(filtercondition) + else: + return basequery + + @classmethod + def getfilterforrequestssubquery(cls, filterfields, keyword): + keyword = keyword.lower() + + #filter/search + filtercondition = [] + if(keyword != 'restricted'): + for field in filterfields: + if(field == 'idNumber'): + keyword = keyword.replace('u-00', '') + + filtercondition.append(FOIRawRequest.findfield(field).ilike('%'+keyword+'%')) + if(field == 'firstName'): + filtercondition.append(FOIRawRequest.findfield('contactFirstName').ilike('%'+keyword+'%')) + if(field == 'lastName'): + filtercondition.append(FOIRawRequest.findfield('contactLastName').ilike('%'+keyword+'%')) + if(field == 'requestType'): + filtercondition.append(FOIRawRequest.findfield('requestTypeRequestType').ilike('%'+keyword+'%')) + else: + filtercondition.append(FOIRawRequest.isiaorestricted == True) + + return or_(*filtercondition) + + + @classmethod + def getrequestspagination(cls, groups, page, size, sortingitems, sortingorders, filterfields, keyword, additionalfilter, userid, isiaorestrictedfilemanager, usertype, isministryrestrictedfilemanager=False): + #ministry requests + iaoassignee = aliased(FOIAssignee) + ministryassignee = aliased(FOIAssignee) + subquery_ministry_queue = FOIMinistryRequest.getrequestssubquery(groups, filterfields, keyword, additionalfilter, userid, iaoassignee, ministryassignee, 'IAO', isiaorestrictedfilemanager, isministryrestrictedfilemanager) + + #sorting + sortingcondition = FOIRawRequest.getsorting(sortingitems, sortingorders) + #rawrequests + if usertype == "iao" or groups is None: + subquery_rawrequest_queue = FOIRawRequest.getrequestssubquery(filterfields, keyword, additionalfilter, userid, isiaorestrictedfilemanager, groups) + query_full_queue = subquery_rawrequest_queue.union(subquery_ministry_queue) + return query_full_queue.order_by(*sortingcondition).paginate(page=page, per_page=size) + else: + return subquery_ministry_queue.order_by(*sortingcondition).paginate(page=page, per_page=size) + + @classmethod + def findfield(cls, x): + return { + 'firstName': FOIRawRequest.requestrawdata['firstName'].astext, + 'lastName': FOIRawRequest.requestrawdata['lastName'].astext, + 'contactFirstName': FOIRawRequest.requestrawdata['contactInfo']['firstName'].astext, + 'contactLastName': FOIRawRequest.requestrawdata['contactInfo']['lastName'].astext, + 'requestType': FOIRawRequest.requestrawdata['requestType'].astext, + 'requestTypeRequestType': FOIRawRequest.requestrawdata['requestType']['requestType'].astext, + 'idNumber': cast(FOIRawRequest.requestid, String), + 'axisRequestId': cast(FOIRawRequest.axisrequestid, String), + 'axisrequest_number': cast(FOIRawRequest.axisrequestid, String), + 'currentState': FOIRawRequest.status, + 'assignedTo': FOIRawRequest.assignedto, + 'assignedToFirstName': FOIAssignee.firstname, + 'assignedToLastName': FOIAssignee.lastname, + 'receivedDate': FOIRawRequest.requestrawdata['receivedDate'].astext, + 'description': FOIRawRequest.requestrawdata['description'].astext, + 'descriptionDescription': FOIRawRequest.requestrawdata['descriptionTimeframe']['description'].astext, + 'ministry': FOIRawRequest.requestrawdata['selectedMinistries'].astext, + 'ministryMinistry': FOIRawRequest.requestrawdata['ministry']['selectedMinistry'].astext, + 'duedate': FOIRawRequest.requestrawdata['dueDate'].astext, + 'DueDateValue': FOIRawRequest.requestrawdata['dueDate'].astext, + 'DaysLeftValue': FOIRawRequest.requestrawdata['dueDate'].astext, + 'subjectcode': FOIRawRequest.requestrawdata['subjectCode'].astext + }.get(x, cast(FOIRawRequest.requestid, String)) + + @classmethod + def validatefield(cls, x): + validfields = [ + 'firstName', + 'lastName', + 'requestType', + 'idNumber', + 'axisRequestId', + 'requestpagecount', + 'currentState', + 'assignedTo', + 'receivedDate', + 'receivedDateUF', + 'assignedToFirstName', + 'assignedToLastName', + 'duedate', + 'defaultSorting', + 'intakeSorting', + 'ministrySorting', + 'assignedToFormatted', + 'ministryAssignedToFormatted', + 'cfrduedate', + 'applicantcategory', + 'onBehalfFormatted', + 'extensions', + 'isiaorestricted' + ] + if x in validfields: + return True + else: + return False + + @classmethod + def getsorting(cls, sortingitems, sortingorders): + sortingcondition = [] + if(len(sortingitems) > 0 and len(sortingorders) > 0 and len(sortingitems) == len(sortingorders)): + for field in sortingitems: + if(FOIRawRequest.validatefield(field)): + order = sortingorders.pop(0) + if(order == 'desc'): + sortingcondition.append(nullslast(desc(field))) + else: + sortingcondition.append(nullsfirst(asc(field))) + #default sorting + if(len(sortingcondition) == 0): + sortingcondition.append(asc('currentState')) + + #always sort by created_at last to prevent pagination collisions + sortingcondition.append(asc('created_at')) + + return sortingcondition + + + @classmethod + def advancedsearch(cls, params, userid, isiaorestrictedfilemanager=False): + basequery = FOIRawRequest.getbasequery(None, userid, isiaorestrictedfilemanager) + + #filter/search + filtercondition = FOIRawRequest.getfilterforadvancedsearch(params) + searchquery = basequery.filter(and_(*filtercondition)) + + #ministry requests + iaoassignee = aliased(FOIAssignee) + ministryassignee = aliased(FOIAssignee) + subquery_ministry_queue = FOIMinistryRequest.advancedsearchsubquery(params, iaoassignee, ministryassignee, userid, 'IAO', isiaorestrictedfilemanager) + + #sorting + sortingcondition = FOIRawRequest.getsorting(params['sortingitems'], params['sortingorders']) + + #rawrequests + query_full_queue = searchquery.union(subquery_ministry_queue) + return query_full_queue.order_by(*sortingcondition).paginate(page=params['page'], per_page=params['size']) + + @classmethod + def getfilterforadvancedsearch(cls, params): + + #filter/search + filtercondition = [] + includeclosed = False + + #request state: unopened, call for records, etc. + if(len(params['requeststate']) > 0): + requeststatecondition = FOIRawRequest.getfilterforrequeststate(params, includeclosed) + filtercondition.append(requeststatecondition['condition']) + includeclosed = requeststatecondition['includeclosed'] + else: + filtercondition.append(FOIRawRequest.status != StateName.unopened.value) #not return Unopened by default + + #request status: overdue, on time - no due date for unopen & intake in progress, so return all except closed + if(len(params['requeststatus']) > 0 and includeclosed == False): + if(len(params['requeststatus']) == 1 and params['requeststatus'][0] == 'overdue'): + #no rawrequest returned for this case + filtercondition.append(FOIRawRequest.status == 'ReturnNothing') + else: + filtercondition.append(FOIRawRequest.status != StateName.closed.value) + + #request type: personal, general + if(len(params['requesttype']) > 0): + requesttypecondition = FOIRawRequest.getfilterforrequesttype(params) + filtercondition.append(or_(*requesttypecondition)) + + #request flags: restricted, oipc, phased + if(len(params['requestflags']) > 0): + requestflagscondition = FOIRawRequest.getfilterforrequestflags(params) + filtercondition.append(or_(*requestflagscondition)) + + #public body: EDUC, etc. + if(len(params['publicbody']) > 0): + ministrycondition = FOIRawRequest.getfilterforpublicbody(params) + filtercondition.append(ministrycondition) + + #axis request #, raw request #, applicant name, assignee name, request description, subject code + if(len(params['keywords']) > 0 and params['search'] is not None): + searchcondition = FOIRawRequest.getfilterforsearch(params) + filtercondition.append(searchcondition) + + if(params['daterangetype'] is not None): + filterconditionfordate = FOIRawRequest.getfilterfordate(params) + filtercondition += filterconditionfordate + + return filtercondition + + @classmethod + def getfilterfordate(cls, params): + filterconditionfordate = [] + if(params['daterangetype'] == 'closedate'): + #no rawrequest returned for this case + filterconditionfordate.append(FOIRawRequest.requestid < 0) + else: + if(params['fromdate'] is not None): + if(params['daterangetype'] == 'receivedDate'): + #online form submission has no receivedDate in json - using created_at + filterconditionfordate.append( + or_( + and_(FOIRawRequest.requestrawdata['receivedDate'].is_(None), FOIRawRequest.created_at.cast(Date) >= parser.parse(params['fromdate'])), + and_(FOIRawRequest.requestrawdata['receivedDate'].isnot(None), FOIRawRequest.findfield(params['daterangetype']).cast(Date) >= parser.parse(params['fromdate'])), + ) + ) + else: + filterconditionfordate.append(FOIRawRequest.findfield(params['daterangetype']).cast(Date) >= parser.parse(params['fromdate'])) + + if(params['todate'] is not None): + if(params['daterangetype'] == 'receivedDate'): + #online form submission has no receivedDate in json - using created_at + filterconditionfordate.append( + or_( + and_(FOIRawRequest.requestrawdata['receivedDate'].is_(None), FOIRawRequest.created_at.cast(Date) <= parser.parse(params['todate'])), + and_(FOIRawRequest.requestrawdata['receivedDate'].isnot(None), FOIRawRequest.findfield(params['daterangetype']).cast(Date) <= parser.parse(params['todate'])), + ) + ) + else: + filterconditionfordate.append(FOIRawRequest.findfield(params['daterangetype']).cast(Date) <= parser.parse(params['todate'])) + + return filterconditionfordate + + @classmethod + def getfilterforrequeststate(cls, params, includeclosed): + #request state: unopened, call for records, etc. + requeststatecondition = [] + for state in params['requeststate']: + if(state == StateName.closed.name): + requeststatecondition.append(FOIRawRequest.status == StateName.closed.value) + includeclosed = True + elif(state == StateName.redirect.name): + requeststatecondition.append(FOIRawRequest.status == StateName.redirect.value) + elif(state == StateName.unopened.name): + requeststatecondition.append(FOIRawRequest.status == StateName.unopened.value) + elif(state == StateName.intakeinprogress.name): + requeststatecondition.append(FOIRawRequest.status == StateName.intakeinprogress.value) + + if(len(requeststatecondition) == 0): + requeststatecondition.append(FOIRawRequest.status == 'Not Applicable') #searched state does not apply to rawrequests + + return {'condition': or_(*requeststatecondition), 'includeclosed': includeclosed} + + @classmethod + def getfilterforrequesttype(cls, params): + #request type: personal, general + requesttypecondition = [] + for type in params['requesttype']: + requesttypecondition.append(FOIRawRequest.findfield('requestType') == type) + requesttypecondition.append(FOIRawRequest.findfield('requestTypeRequestType') == type) + + return or_(*requesttypecondition) + + @classmethod + def getfilterforrequestflags(cls, params): + # this search will be done by the ministry union, so returns filter with no results + requestflagscondition = [] + for flag in params['requestflags']: + if (flag.lower() == 'restricted'): # no results for raw restricted + requestflagscondition.append(FOIRawRequest.findfield('axisRequestId') == 'thisismeanttoreturnafilterconditionwith0results') + if (flag.lower() == 'oipc'): # no results for raw oipc + requestflagscondition.append(FOIRawRequest.findfield('axisRequestId') == 'thisismeanttoreturnafilterconditionwith0results') + if (flag.lower() == 'phased'): + # requestflagscondition.append(FOIMinistryRequest.findfield('isphasedrelease', iaoassignee, ministryassignee) == True) + continue + return requestflagscondition + + @classmethod + def getfilterforpublicbody(cls, params): + #public body: EDUC, etc. + ministrycondition = [] + for ministry in params['publicbody']: + ministrycondition.append(FOIRawRequest.findfield('ministry').ilike('%"'+ministry+'"%')) + ministrycondition.append(FOIRawRequest.findfield('ministryMinistry').ilike('%"'+ministry+'"%')) + + return or_(*ministrycondition) + + @classmethod + def getfilterforsearch(cls, params): + #axis request #, raw request #, applicant name, assignee name, request description, subject code + if(params['search'] == 'requestdescription'): + return FOIRawRequest.__getfilterfordescription(params) + elif(params['search'] == 'applicantname'): + return FOIRawRequest.__getfilterforapplicantname(params) + elif(params['search'] == 'assigneename'): + return FOIRawRequest.__getfilterforassigneename(params) + elif(params['search'] == 'idnumber'): + return FOIRawRequest.__getfilterforidnumber(params) + elif(params['search'] == 'axisrequest_number'): + return FOIRawRequest.__getfilterforaxisnumber(params) + else: + searchcondition = [] + for keyword in params['keywords']: + searchcondition.append(FOIRawRequest.findfield(params['search']).ilike('%'+keyword+'%')) + return and_(*searchcondition) + + @classmethod + def __getfilterfordescription(cls,params): + searchcondition1 = [] + searchcondition2 = [] + for keyword in params['keywords']: + searchcondition1.append(FOIRawRequest.findfield('description').ilike('%'+keyword+'%')) + searchcondition2.append(FOIRawRequest.findfield('descriptionDescription').ilike('%'+keyword+'%')) + return or_(and_(*searchcondition1), and_(*searchcondition2)) + + @classmethod + def __getfilterforapplicantname(cls,params): + searchcondition1 = [] + searchcondition2 = [] + searchcondition3 = [] + searchcondition4 = [] + for keyword in params['keywords']: + searchcondition1.append(FOIRawRequest.findfield('firstName').ilike('%'+keyword+'%')) + searchcondition2.append(FOIRawRequest.findfield('lastName').ilike('%'+keyword+'%')) + searchcondition3.append(FOIRawRequest.findfield('contactFirstName').ilike('%'+keyword+'%')) + searchcondition4.append(FOIRawRequest.findfield('contactLastName').ilike('%'+keyword+'%')) + return or_(and_(*searchcondition1), and_(*searchcondition2), and_(*searchcondition3), and_(*searchcondition4)) + + @classmethod + def __getfilterforassigneename(cls,params): + searchcondition1 = [] + searchcondition2 = [] + searchcondition3 = [] + for keyword in params['keywords']: + searchcondition1.append(FOIRawRequest.findfield('assignedToFirstName').ilike('%'+keyword+'%')) + searchcondition2.append(FOIRawRequest.findfield('assignedToLastName').ilike('%'+keyword+'%')) + searchcondition3.append(FOIRawRequest.assignedgroup.ilike('%'+keyword+'%')) + return or_(and_(*searchcondition1), and_(*searchcondition2), and_(*searchcondition3)) + + @classmethod + def __getfilterforidnumber(cls,params): + searchcondition = [] + for keyword in params['keywords']: + keyword = keyword.lower() + keyword = keyword.replace('u-00', '') + searchcondition.append(FOIRawRequest.findfield('idNumber').ilike('%'+keyword+'%')) + return and_(*searchcondition) + + @classmethod + def __getfilterforaxisnumber(cls,params): + searchcondition1 = [] + searchcondition2 = [] + for keyword in params['keywords']: + keyword = keyword.lower() + keyword = keyword.replace('u-00', '') + searchcondition1.append(FOIRawRequest.findfield('idNumber').ilike('%'+keyword+'%')) + searchcondition2.append(FOIRawRequest.findfield('axisrequest_number').ilike('%'+keyword+'%')) + return or_(and_(*searchcondition1), and_(*searchcondition2)) + + + @classmethod + def getDistinctAXISRequestIds(cls): + axisrequestids = [] + try: + sql = """select distinct axisrequestid from "FOIRawRequests" where axisrequestid is not null;""" + axisids = db.session.execute(text(sql)) + for axisid in axisids: + axisrequestids.append(axisid[0]) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return axisrequestids + + @classmethod + def getCountOfAXISRequestIdbyAXISRequestId(cls, axisrequestid): + try: + query = db.session.query(func.count(FOIRawRequest.axisrequestid)).filter_by(axisrequestid=axisrequestid) + return query.scalar() + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + + @classmethod + def getmetadata(cls,requestid): + requestdetails = {} + try: + sql = """select requestrawdata ->> 'assignedTo' as assignedTo, + requestrawdata ->> 'assignedToFirstName' as assignedToFirstName, + requestrawdata ->> 'assignedToLastName' as assignedToLastName, + requestrawdata -> 'selectedMinistries'-> 0 ->> 'code' as bcgovcode from "FOIRawRequests" + where requestid = :requestid + order by version desc limit 1;""" + rs = db.session.execute(text(sql), {'requestid': requestid}) + for row in rs: + requestdetails["assignedTo"] = row['assignedto'] + requestdetails["assignedToFirstName"] = row["assignedtofirstname"] + requestdetails["assignedToLastName"] = row["assignedtolastname"] + requestdetails["bcgovcode"] = row["bcgovcode"] + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return requestdetails + + @classmethod + def getlatestsection5pendings(cls): + section5pendings = [] + try: + sql = """SELECT * FROM + (SELECT DISTINCT ON (requestid) requestid, created_at, version, status, axisrequestid + FROM public."FOIRawRequests" + ORDER BY requestid ASC, version DESC) foireqs + WHERE foireqs.status = :requeststatus;""" + rs = db.session.execute(text(sql), {'requeststatus': StateName.section5pending.value}) + for row in rs: + section5pendings.append({"requestid": row["requestid"], "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 + + @classmethod + def getunopenedunactionedrequests(cls, startdate, enddate): + try: + requests = [] + sql = '''select rr.created_at, rr.requestrawdata, rr.requestid, coalesce(p.status, '') as status, + coalesce(p.transaction_number, '') as txnno, + coalesce(p.total::text, '') as fee + from public."FOIRawRequests" rr + join ( + select max(version) as version, requestid from public."FOIRawRequests" + group by requestid + ) mv on mv.requestid = rr.requestid and mv.version = rr.version + left join ( + select request_id, max(payment_id) from public."Payments" + where fee_code_id = 1 + group by request_id + order by request_id + ) mp on mp.request_id = rr.requestid + left join public."Payments" p on p.payment_id = mp.max + where rr.status = 'Unopened' and rr.version = 1 and created_at > :startdate and created_at < :enddate + order by rr.requestid ''' + rs = db.session.execute(text(sql), {'startdate': startdate, 'enddate': enddate}) + for row in rs: + requests.append({ + "requestid": row["requestid"], + "created_at": row["created_at"], + "requestrawdata": row["requestrawdata"], + "paymentstatus": row["status"], + "fee": row["fee"], + "txnno": row["txnno"] + }) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return requests + + @classmethod + def getpotentialactionedmatches(cls, request): + try: + requests = [] + sql = '''select rr.created_at, rr.* from public."FOIRawRequests" rr + join ( + select max(version) as version, requestid from public."FOIRawRequests" + group by requestid + ) mv on mv.requestid = rr.requestid and mv.version = rr.version + where ( + requestrawdata->>'lastName' ilike :lastName + or requestrawdata->>'firstName' ilike :firstName + or requestrawdata->>'email' ilike :email + or requestrawdata->>'address' ilike :address + or requestrawdata->>'phonePrimary' ilike :phonePrimary + or requestrawdata->>'postal' ilike :postal + ) and substring(rr.requestrawdata->>'receivedDateUF', 1, 10) = :receiveddate + order by requestid desc, version desc ''' + rs = db.session.execute(text(sql), { + 'lastName': request['requestrawdata']['contactInfo']['lastName'], + 'firstName': request['requestrawdata']['contactInfo']['firstName'], + 'email': request['requestrawdata']['contactInfoOptions']['email'], + 'address': request['requestrawdata']['contactInfoOptions']['address'], + 'phonePrimary': request['requestrawdata']['contactInfoOptions']['phonePrimary'], + 'postal': request['requestrawdata']['contactInfoOptions']['postal'], + 'receiveddate': request['requestrawdata']['receivedDateUF'][0:10], + }) + for row in rs: + requests.append({"requestid": row["requestid"], "created_at": row["created_at"], "requestrawdata": row["requestrawdata"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return requests + + + +class FOIRawRequestSchema(ma.Schema): + class Meta: + fields = ('requestid', 'requestrawdata', 'status', 'requeststatuslabel', 'notes','created_at','wfinstanceid','version','updated_at','assignedgroup','assignedto','updatedby','createdby','sourceofsubmission','ispiiredacted','assignee.firstname','assignee.lastname', 'axisrequestid', 'axissyncdate', 'linkedrequests', 'closedate','isiaorestricted') diff --git a/historical-search-api/request_api/models/FOIRequestApplicantMappings.py b/historical-search-api/request_api/models/FOIRequestApplicantMappings.py new file mode 100644 index 000000000..877646a01 --- /dev/null +++ b/historical-search-api/request_api/models/FOIRequestApplicantMappings.py @@ -0,0 +1,48 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint +from .db import db, ma +from datetime import datetime +from sqlalchemy.orm import relationship,backref +from .default_method_result import DefaultMethodResult +from .FOIRequests import FOIRequest + +class FOIRequestApplicantMapping(db.Model): + # Name of the table in our database + __tablename__ = 'FOIRequestApplicantMappings' + __table_args__ = ( + ForeignKeyConstraint( + ["foirequest_id", "foirequestversion_id"], ["FOIRequests.foirequestid", "FOIRequests.version"] + ), + ) + # Defining the columns + + foirequestapplicantmappingid = db.Column(db.Integer, primary_key=True,autoincrement=True) + created_at = db.Column(db.DateTime, default=datetime.now) + updated_at = db.Column(db.DateTime, nullable=True) + createdby = db.Column(db.String(120), unique=False, nullable=True) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + + #ForeignKey References + + requestortypeid = db.Column(db.Integer,ForeignKey('RequestorTypes.requestortypeid')) + requestortype = relationship("RequestorType",backref=backref("RequestorTypes"),uselist=False) + + foirequestapplicantid = db.Column(db.Integer,ForeignKey('FOIRequestApplicants.foirequestapplicantid')) + foirequestapplicant = relationship("FOIRequestApplicant",backref=backref("FOIRequestApplicants"),uselist=False) + + foirequest_id =db.Column(db.Integer, db.ForeignKey('FOIRequests.foirequestid')) + foirequestversion_id = db.Column(db.Integer, db.ForeignKey('FOIRequests.version')) + foirequestkey = relationship("FOIRequest",foreign_keys="[FOIRequestApplicantMapping.foirequest_id]") + foirequestversion = relationship("FOIRequest",foreign_keys="[FOIRequestApplicantMapping.foirequestversion_id]") + + @classmethod + def getrequestapplicants(cls,foirequest_id,foirequestversion): + requestapplicant_schema = FOIRequestApplicantMappingSchema(many=True) + _applicantinfos = db.session.query(FOIRequestApplicantMapping).filter(FOIRequestApplicantMapping.foirequest_id == foirequest_id , FOIRequestApplicantMapping.foirequestversion_id == foirequestversion).order_by(FOIRequestApplicantMapping.foirequestapplicantmappingid.asc()).all() + applicantinfos = requestapplicant_schema.dump(_applicantinfos) + return applicantinfos + +class FOIRequestApplicantMappingSchema(ma.Schema): + class Meta: + fields = ('foirequestapplicantmappingid','foirequest.foirequestid','foirequest.version','requestortype.requestortypeid','requestortype.name','foirequestapplicant.foirequestapplicantid','foirequestapplicant.firstname','foirequestapplicant.lastname','foirequestapplicant.middlename','foirequestapplicant.alsoknownas','foirequestapplicant.dob','foirequestapplicant.businessname') + \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestApplicants.py b/historical-search-api/request_api/models/FOIRequestApplicants.py new file mode 100644 index 000000000..7f4f8f1ad --- /dev/null +++ b/historical-search-api/request_api/models/FOIRequestApplicants.py @@ -0,0 +1,67 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint +from .db import db, ma +from datetime import datetime +from sqlalchemy.orm import relationship,backref +from .default_method_result import DefaultMethodResult +from .FOIRequests import FOIRequest + +class FOIRequestApplicant(db.Model): + # Name of the table in our database + __tablename__ = 'FOIRequestApplicants' + # Defining the columns + foirequestapplicantid = db.Column(db.Integer, primary_key=True,autoincrement=True) + + + firstname = db.Column(db.String(50), unique=False, nullable=True) + middlename = db.Column(db.String(50), unique=False, nullable=True) + lastname = db.Column(db.String(50), unique=False, nullable=True) + + alsoknownas = db.Column(db.String(50), unique=False, nullable=True) + dob = db.Column(db.DateTime, unique=False, nullable=True) + businessname = db.Column(db.String(255), unique=False, nullable=True) + + created_at = db.Column(db.DateTime, default=datetime.now) + updated_at = db.Column(db.DateTime, nullable=True) + createdby = db.Column(db.String(120), unique=False, nullable=True) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + + @classmethod + def saveapplicant(cls,firstname, lastname, middlename, businessname, alsoknownas, dob, userid): + dbquery = db.session.query(FOIRequestApplicant) + dbquery = dbquery.filter_by(firstname=firstname) + applicant = dbquery.filter_by(lastname=lastname) + if (applicant.count() > 0): + _applicant = { + FOIRequestApplicant.updatedby: userid, + FOIRequestApplicant.updated_at: datetime.now(), + FOIRequestApplicant.middlename: middlename, + FOIRequestApplicant.businessname: businessname, + FOIRequestApplicant.alsoknownas: alsoknownas + } + if dob is not None and dob != "": + _applicant[FOIRequestApplicant.dob] = dob + else: + _applicant[FOIRequestApplicant.dob] = None + applicant.update(_applicant) + return DefaultMethodResult(True,'Applicant updated',applicant.first().foirequestapplicantid) + else: + applicant = FOIRequestApplicant() + applicant.createdby = userid + applicant.firstname = firstname + applicant.lastname = lastname + applicant.middlename = middlename + applicant.businessname = businessname + applicant.alsoknownas = alsoknownas + if dob is not None and dob != "": + applicant.dob = dob + else: + applicant.dob = None + db.session.add(applicant) + db.session.commit() + return DefaultMethodResult(True,'Applicant added',applicant.foirequestapplicantid) + +class FOIRequestApplicantSchema(ma.Schema): + class Meta: + fields = ('foirequestapplicantid','firstname','middlename','lastname','alsoknownas','dob','businessname') + \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestCFRFees.py b/historical-search-api/request_api/models/FOIRequestCFRFees.py new file mode 100644 index 000000000..471860a78 --- /dev/null +++ b/historical-search-api/request_api/models/FOIRequestCFRFees.py @@ -0,0 +1,110 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey +from .db import db, ma +from marshmallow import pre_dump, post_dump +from datetime import datetime as datetime2 +from sqlalchemy.orm import relationship,backref +from .default_method_result import DefaultMethodResult +from sqlalchemy.dialects.postgresql import JSON, UUID +from sqlalchemy.sql.expression import distinct +from sqlalchemy import null, text, insert +from .CFRFeeStatus import CFRFeeStatus +import logging + +class FOIRequestCFRFee(db.Model): + # Name of the table in our database + __tablename__ = 'FOIRequestCFRFees' + # Defining the columns + cfrfeeid = db.Column(db.Integer, primary_key=True,autoincrement=True) + ministryrequestid =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.foiministryrequestid')) + ministryrequestversion=db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.version')) + version =db.Column(db.Integer,primary_key=True,nullable=False) + feedata = db.Column(JSON, unique=False, nullable=True) + overallsuggestions = db.Column(db.Text, unique=False, nullable=True) + cfrfeestatusid =db.Column(db.Integer, db.ForeignKey('CFRFeeStatuses.cfrfeestatusid')) + cfrfeestatus = relationship("CFRFeeStatus",backref=backref("CFRFeeStatus"),uselist=False) + created_at = db.Column(db.DateTime, default=datetime2.now) + createdby = db.Column(db.String(120), unique=False, nullable=True) + updated_at = db.Column(db.DateTime, nullable=True) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + cfrformreasonid =db.Column(db.Integer, db.ForeignKey('CFRFormReasons.cfrformreasonid'), nullable=True) + cfrformreason = relationship("CFRFormReason",backref=backref("CFRFormReason"),uselist=False) + + + @classmethod + def createcfrfee(cls, cfrfee, userid)->DefaultMethodResult: + cfrfee.created_at=datetime2.now().isoformat(), + cfrfee.createdby=userid + db.session.add(cfrfee) + db.session.commit() + return DefaultMethodResult(True,'CFR Fee added for ministry request : '+ str(cfrfee.ministryrequestid), cfrfee.cfrfeeid) + + + @classmethod + def getcfrfee(cls, ministryrequestid)->DefaultMethodResult: + comment_schema = FOIRequestCFRFormSchema(many=False) + query = db.session.query(FOIRequestCFRFee).filter_by(ministryrequestid=ministryrequestid).order_by(FOIRequestCFRFee.cfrfeeid.desc(), FOIRequestCFRFee.version.desc()).first() + return comment_schema.dump(query) + + @classmethod + def getapprovedcfrfee(cls, ministryrequestid)->DefaultMethodResult: + comment_schema = FOIRequestCFRFormSchema(many=False) + query = db.session.query(FOIRequestCFRFee).filter_by(ministryrequestid=ministryrequestid, cfrfeestatusid=2).order_by(FOIRequestCFRFee.cfrfeeid.desc(), FOIRequestCFRFee.version.desc()).first() + return comment_schema.dump(query) + + @classmethod + def getcfrfeehistory(cls, ministryrequestid)->DefaultMethodResult: + comment_schema = FOIRequestCFRFormSchema(many=False) + subquery1 = db.session.query(FOIRequestCFRFee.cfrfeeid, db.func.max(FOIRequestCFRFee.version).label('version')).group_by(FOIRequestCFRFee.cfrfeeid).subquery() + subquery2 = db.session.query(FOIRequestCFRFee.cfrfeeid, FOIRequestCFRFee.created_at.label('version_created_at'), FOIRequestCFRFee.createdby.label('version_createdby')).filter_by(version=1).subquery() + query = db.session.query(FOIRequestCFRFee, subquery2.c.version_created_at, subquery2.c.version_createdby).filter_by( + ministryrequestid=ministryrequestid, cfrfeestatusid=2).join( + subquery1, (FOIRequestCFRFee.cfrfeeid == subquery1.c.cfrfeeid) & (FOIRequestCFRFee.version == subquery1.c.version) + ).join(subquery2, (FOIRequestCFRFee.cfrfeeid == subquery2.c.cfrfeeid)).order_by(FOIRequestCFRFee.cfrfeeid.desc()).all() + history = [] + for row in query: + cfrfee = comment_schema.dump(row[0]) + cfrfee['version_created_at'] = row[1] + cfrfee['version_createdby'] = row[2] + history.append(cfrfee) + return history + + @classmethod + def getcfrfeebyid(cls, cfrfeeid) -> DefaultMethodResult: + comment_schema = FOIRequestCFRFormSchema() + query = db.session.query(FOIRequestCFRFee).filter_by(cfrfeeid=cfrfeeid, isactive=True).first() + return comment_schema.dump(query) + + @classmethod + def getstatenavigation(cls, ministryrequestid, cfrfeeid): + _session = db.session + _entries = _session.query(FOIRequestCFRFee).filter_by(ministryrequestid = ministryrequestid, cfrfeeid = cfrfeeid).order_by(FOIRequestCFRFee.version.desc()).limit(2) + requeststates = [] + for _entry in _entries: + if _entry.cfrfeestatusid: + requeststates.append(_entry.cfrfeestatus.description) + return requeststates + + @classmethod + def getfeedataforamountcomparison(cls, ministryrequestid): + _session = db.session + _entries = _session.query(FOIRequestCFRFee).filter(FOIRequestCFRFee.ministryrequestid == ministryrequestid and FOIRequestCFRFee.feedata is not null).order_by(FOIRequestCFRFee.cfrfeeid.desc(), FOIRequestCFRFee.version.desc()).limit(2) + feedata = [] + for _entry in _entries: + feedata.append(_entry.feedata) + return feedata + + @classmethod + def updatecfrfeedatabyid(cls, ministryrequestid, feedata)->DefaultMethodResult: + sq = db.session.query(FOIRequestCFRFee.cfrfeeid, FOIRequestCFRFee.version).filter_by(ministryrequestid=ministryrequestid).order_by(FOIRequestCFRFee.cfrfeeid.desc(), FOIRequestCFRFee.version.desc()).limit(1).subquery() + cfrfee = db.session.query(FOIRequestCFRFee).filter_by(cfrfeeid=sq.c.cfrfeeid, version=sq.c.version) + cfrfee.update({FOIRequestCFRFee.feedata: feedata, FOIRequestCFRFee.updatedby: 'Online Payment', + FOIRequestCFRFee.updated_at: datetime2.now().isoformat()}, synchronize_session=False) + db.session.commit() + return DefaultMethodResult(True,'CFR Fee Data updated for ministry request : '+ str(ministryrequestid), sq.c.cfrfeeid) + +class FOIRequestCFRFormSchema(ma.Schema): + class Meta: + fields = ('cfrfeeid', 'ministryrequestid', 'feedata', 'overallsuggestions', 'created_at','createdby','updated_at','updatedby','cfrfeestatusid', 'cfrfeestatus.name','cfrfeestatus.description','version','cfrformreasonid','cfrformreason.name','cfrformreason.description') + + diff --git a/historical-search-api/request_api/models/FOIRequestComments.py b/historical-search-api/request_api/models/FOIRequestComments.py new file mode 100644 index 000000000..1bb0bba11 --- /dev/null +++ b/historical-search-api/request_api/models/FOIRequestComments.py @@ -0,0 +1,169 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey +from .db import db, ma +from datetime import datetime as datetime2 +from sqlalchemy.orm import relationship,backref +from .default_method_result import DefaultMethodResult +from sqlalchemy.dialects.postgresql import JSON, UUID, insert +from sqlalchemy.sql.expression import distinct +from sqlalchemy import text +import logging +import json +class FOIRequestComment(db.Model): + # Name of the table in our database + __tablename__ = 'FOIRequestComments' + # Defining the columns + commentid = db.Column(db.Integer, primary_key=True,autoincrement=True) + ministryrequestid =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.foiministryrequestid')) + version =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.version')) + comment = db.Column(db.Text, unique=False, nullable=True) + taggedusers = db.Column(JSON, unique=False, nullable=True) + parentcommentid = db.Column(db.Integer, db.ForeignKey('FOIRequestComments.commentid'), nullable=True) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + created_at = db.Column(db.DateTime, default=datetime2.now) + createdby = db.Column(db.String(120), unique=False, nullable=True) + updated_at = db.Column(db.DateTime, nullable=True) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + parentcomment = relationship("FOIRequestComment", backref=backref("FOIRequestComments"), remote_side=[commentid], uselist=False) + + commenttypeid = db.Column(db.Integer, unique=False, nullable=False) + commentsversion = db.Column(db.Integer, nullable=False) + + + @classmethod + def savecomment(cls, commenttypeid, foirequestcomment, version, userid,commentcreatedate=None)->DefaultMethodResult: + commentsversion = 1 + parentcommentid = foirequestcomment["parentcommentid"] if 'parentcommentid' in foirequestcomment else None + taggedusers = foirequestcomment["taggedusers"] if 'taggedusers' in foirequestcomment else None + _createddate = datetime2.now().isoformat() if commentcreatedate is None else commentcreatedate + newcomment = FOIRequestComment(commenttypeid=commenttypeid, ministryrequestid=foirequestcomment["ministryrequestid"], version=version, comment=foirequestcomment["comment"], parentcommentid=parentcommentid, isactive=True, created_at=_createddate, createdby=userid,taggedusers=taggedusers, commentsversion=commentsversion) + db.session.add(newcomment) + db.session.commit() + return DefaultMethodResult(True,'Comment added',newcomment.commentid) + + @classmethod + def deleteextensioncommentsbyministry(cls, ministryid): + db.session.query(FOIRequestComment).filter(FOIRequestComment.ministryrequestid == ministryid, FOIRequestComment.commenttypeid == 2).delete(synchronize_session=False) + db.session.commit() + return DefaultMethodResult(True,'Extensions comments deleted for the ministry ', ministryid) + + @classmethod + def disablecomment(cls, commentid, userid): + dbquery = db.session.query(FOIRequestComment) + comment = dbquery.filter_by(commentid=commentid) + if(comment.count() > 0) : + childcomments = dbquery.filter_by(parentcommentid=commentid, isactive=True) + if (childcomments.count() > 0) : + return DefaultMethodResult(False,'Cannot delete parent comment with replies',commentid) + comment.update({FOIRequestComment.isactive:False, FOIRequestComment.updatedby:userid, FOIRequestComment.updated_at:datetime2.now()}, synchronize_session = False) + db.session.commit() + return DefaultMethodResult(True,'Comment disabled',commentid) + else: + return DefaultMethodResult(True,'No Comment found',commentid) + + @classmethod + def deactivatecomment(cls, commentid, userid, commentsversion): + dbquery = db.session.query(FOIRequestComment) + comment = dbquery.filter_by(commentid=commentid, commentsversion=commentsversion) + if(comment.count() > 0) : + comment.update({FOIRequestComment.isactive:False, FOIRequestComment.updatedby:userid, FOIRequestComment.updated_at:datetime2.now()}, synchronize_session = False) + db.session.commit() + return DefaultMethodResult(True,'Comment deactivated',commentid) + else: + return DefaultMethodResult(True,'No Comment found',commentid) + + @classmethod + def updatecomment(cls, commentid, foirequestcomment, userid): + dbquery = db.session.query(FOIRequestComment) + comment = dbquery.filter_by(commentid=commentid).order_by(FOIRequestComment.commentsversion.desc()).first() + _existingtaggedusers = [] + _commentsversion = 0 + if comment is not None : + _existingtaggedusers = comment.taggedusers + _taggedusers = foirequestcomment["taggedusers"] if 'taggedusers' in foirequestcomment else _existingtaggedusers + _commentsversion = int(comment.commentsversion) + insertstmt = ( + insert(FOIRequestComment). + values( + commentid=commentid, + ministryrequestid=comment.ministryrequestid, + version=comment.version, + comment=foirequestcomment["comment"], + taggedusers=_taggedusers, + parentcommentid=comment.parentcommentid, + isactive=True, + created_at=datetime2.now(), + createdby=userid, + updated_at=datetime2.now(), + updatedby=userid, + commenttypeid=comment.commenttypeid, + commentsversion=_commentsversion + 1 + ) + ) + updatestmt = insertstmt.on_conflict_do_update(index_elements=[FOIRequestComment.commentid, FOIRequestComment.commentsversion], + set_={"ministryrequestid": comment.ministryrequestid,"version":comment.version, "comment": foirequestcomment["comment"], + "taggedusers":_taggedusers, "parentcommentid":comment.parentcommentid, "isactive":True, + "created_at":datetime2.now(), "createdby": userid, "updated_at": datetime2.now(), "updatedby": userid, + "commenttypeid": comment.commenttypeid + } + ) + db.session.execute(updatestmt) + db.session.commit() + return DefaultMethodResult(True,'Updated Comment added',commentid, _existingtaggedusers, _commentsversion) + else: + return DefaultMethodResult(True,'No Comment found',commentid, _existingtaggedusers, _commentsversion) + + @classmethod + def getcomments(cls, ministryrequestid)->DefaultMethodResult: + comment_schema = FOIRequestCommentSchema(many=True) + query = db.session.query(FOIRequestComment).filter_by(ministryrequestid=ministryrequestid, isactive = True).order_by(FOIRequestComment.commentid.desc()).all() + return comment_schema.dump(query) + # comments = [] + # try: + # sql = """SELECT distinct on (commentid) commentid, parentcommentid, commentsversion, ministryrequestid, version, comment, created_at, createdby, + # updated_at, updatedby, isactive, commenttypeid, taggedusers + # FROM public."FOIRequestComments" where ministryrequestid = :ministryrequestid and isactive = true order by commentid, commentsversion desc;""" + # rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) + # for row in rs: + # # comments.append( + # # {"commentid": row["commentid"], "parentcommentid": row["parentcommentid"], "commentsversion": row["commentsversion"], + # # "ministryrequestid": row["ministryrequestid"], "version": row["version"], "comment": row["comment"], + # # "created_at": row["created_at"], "createdby": row["createdby"], "updated_at": row["updated_at"], "updatedby": row["updatedby"], + # # "isactive": row["isactive"], "commenttypeid": row["commenttypeid"], "taggedusers": row["taggedusers"] + # # } + # # ) + # comments.append(dict(row)) + # except Exception as ex: + # logging.error(ex) + # raise ex + # finally: + # db.session.close() + # return comments + + @classmethod + def getcommentbyid(cls, commentid) -> DefaultMethodResult: + comment_schema = FOIRequestCommentSchema() + query = db.session.query(FOIRequestComment).filter_by(commentid=commentid, isactive=True).first() + return comment_schema.dump(query) + + @classmethod + def getcommentusers(cls, commentid): + users = [] + try: + sql = """select commentid, createdby, taggedusers from ( + select commentid, commenttypeid, createdby, taggedusers from "FOIRequestComments" frc where commentid = (select parentcommentid from "FOIRequestComments" frc where commentid=:commentid) and isactive = true + union all + select commentid, commenttypeid, createdby, taggedusers from "FOIRequestComments" frc where commentid <> :commentid and parentcommentid = (select parentcommentid from "FOIRequestComments" frc where commentid=:commentid) and isactive = true + ) cmt where commenttypeid =1""" + rs = db.session.execute(text(sql), {'commentid': commentid}) + for row in rs: + users.append({"commentid": row["commentid"], "createdby": row["createdby"], "taggedusers": row["taggedusers"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return users +class FOIRequestCommentSchema(ma.Schema): + class Meta: + fields = ('commentid', 'ministryrequestid', 'parentcommentid','comment', 'commenttypeid','commenttype','isactive','created_at','createdby','updated_at','updatedby','taggedusers', 'parentcomment.taggedusers', 'commentsversion') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestContactInformation.py b/historical-search-api/request_api/models/FOIRequestContactInformation.py new file mode 100644 index 000000000..be1b7d6f3 --- /dev/null +++ b/historical-search-api/request_api/models/FOIRequestContactInformation.py @@ -0,0 +1,51 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint +from .db import db, ma +from datetime import datetime +from sqlalchemy.orm import relationship,backref +from .default_method_result import DefaultMethodResult +from .FOIRequests import FOIRequest + +class FOIRequestContactInformation(db.Model): + # Name of the table in our database + __tablename__ = 'FOIRequestContactInformation' + __table_args__ = ( + ForeignKeyConstraint( + ["foirequest_id", "foirequestversion_id"], ["FOIRequests.foirequestid", "FOIRequests.version"] + ), + ) + # Defining the columns + foirequestcontactid = db.Column(db.Integer, primary_key=True,autoincrement=True) + + contactinformation = db.Column(db.String(500), unique=False, nullable=False) + dataformat = db.Column(db.String(40), unique=False, nullable=True) + + + created_at = db.Column(db.DateTime, default=datetime.now) + updated_at = db.Column(db.DateTime, nullable=True) + createdby = db.Column(db.String(120), unique=False, nullable=True) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + + #ForeignKey References + + contacttypeid = db.Column(db.Integer,ForeignKey('ContactTypes.contacttypeid')) + contacttype = relationship("ContactType",backref=backref("ContactTypes"),uselist=False) + + foirequest_id =db.Column(db.Integer, db.ForeignKey('FOIRequests.foirequestid')) + foirequestversion_id = db.Column(db.Integer, db.ForeignKey('FOIRequests.version')) + foirequestkey = relationship("FOIRequest",foreign_keys="[FOIRequestContactInformation.foirequest_id]") + foirequestversion = relationship("FOIRequest",foreign_keys="[FOIRequestContactInformation.foirequestversion_id]") + + + @classmethod + def getrequestcontactinformation(cls,foirequest_id,foirequestversion): + requestcontact_schema = FOIRequestContactInformationSchema(many=True) + _contactinfos = db.session.query(FOIRequestContactInformation).filter(FOIRequestContactInformation.foirequest_id == foirequest_id , FOIRequestContactInformation.foirequestversion_id == foirequestversion).order_by(FOIRequestContactInformation.foirequestcontactid.asc()).all() + contactinfos = requestcontact_schema.dump(_contactinfos) + return contactinfos + + +class FOIRequestContactInformationSchema(ma.Schema): + class Meta: + fields = ('foirequestcontactid','contactinformation','dataformat','contacttype.contacttypeid','contacttype.name','foirequest.foirequestid','foirequest.version') + \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestExtensionDocumentMappings.py b/historical-search-api/request_api/models/FOIRequestExtensionDocumentMappings.py new file mode 100644 index 000000000..3c0965ef3 --- /dev/null +++ b/historical-search-api/request_api/models/FOIRequestExtensionDocumentMappings.py @@ -0,0 +1,60 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint +from .db import db, ma +from datetime import datetime +from sqlalchemy.orm import relationship,backref +from .default_method_result import DefaultMethodResult +from sqlalchemy.sql.expression import distinct +from sqlalchemy import or_,and_,text + +class FOIRequestExtensionDocumentMapping(db.Model): + # Name of the table in our database + __tablename__ = 'FOIRequestExtensionDocumentMapping' + + # Defining the columns + foirequestextensiondocumentid = db.Column(db.Integer, primary_key=True,autoincrement=True) + + created_at = db.Column(db.DateTime, default=datetime.now) + updated_at = db.Column(db.DateTime, nullable=True) + createdby = db.Column(db.String(120), unique=False, nullable=False) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + + #ForeignKey References + foirequestextensionid =db.Column(db.Integer, ForeignKey('FOIRequestExtensions.foirequestextensionid')) + extensionversion = db.Column(db.Integer, ForeignKey('FOIRequestExtensions.version')) + foiministrydocumentid =db.Column(db.Integer, ForeignKey('FOIMinistryRequestDocuments.foiministrydocumentid')) + + @classmethod + def getextensiondocument(cls,foirequestextensiondocumentid): + document_schema = FOIRequestExtensionDocumentMappingSchema() + request = db.session.query(FOIRequestExtensionDocumentMapping).filter_by(foirequestextensiondocumentid=foirequestextensiondocumentid) + return document_schema.dump(request) + + @classmethod + def getextensiondocuments(cls,foirequestextensionid, extensionversion): + document_schema = FOIRequestExtensionDocumentMappingSchema(many=True) + request = db.session.query(FOIRequestExtensionDocumentMapping).filter(FOIRequestExtensionDocumentMapping.foirequestextensionid == foirequestextensionid, FOIRequestExtensionDocumentMapping.extensionversion == extensionversion).all() + return document_schema.dump(request) + + @classmethod + def saveextensiondocument(cls, extensionid, documents, version, userid): + newdocuments = [] + for document in documents: + createuserid = document['createdby'] if 'createdby' in document and document['createdby'] is not None else userid + createdat = document['created_at'] if 'created_at' in document and document['created_at'] is not None else datetime.now() + newextensiondocument = FOIRequestExtensionDocumentMapping( + foirequestextensionid=extensionid, + extensionversion=version, + foiministrydocumentid=document["foiministrydocumentid"], + created_at=createdat, + createdby=createuserid + ) + newdocuments.append(newextensiondocument) + db.session.add_all(newdocuments) + db.session.commit() + return DefaultMethodResult(True,'Extension Document Mapping created') + +class FOIRequestExtensionDocumentMappingSchema(ma.Schema): + class Meta: + fields = ('foirequestextensiondocumentid', 'foirequestextensionid', 'foiministrydocumentid', 'extensionversion','created_at','createdby','updated_at','updatedby') + \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestExtensions.py b/historical-search-api/request_api/models/FOIRequestExtensions.py new file mode 100644 index 000000000..fea3ee86b --- /dev/null +++ b/historical-search-api/request_api/models/FOIRequestExtensions.py @@ -0,0 +1,189 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint +from .db import db, ma +from datetime import datetime +from sqlalchemy.orm import relationship,backref +from .default_method_result import DefaultMethodResult +from sqlalchemy.sql.expression import distinct +from sqlalchemy import or_,and_,text +import logging +class FOIRequestExtension(db.Model): + # Name of the table in our database + __tablename__ = 'FOIRequestExtensions' + + # Defining the columns + foirequestextensionid = db.Column(db.Integer, primary_key=True,autoincrement=True) + extendedduedays =db.Column(db.Integer, nullable=True) + extendedduedate = db.Column(db.DateTime, nullable=True) + decisiondate = db.Column(db.DateTime, nullable=True) + approvednoofdays =db.Column(db.Integer, nullable=True) + version =db.Column(db.Integer, nullable=True) + isactive = db.Column(db.Boolean, unique=False, nullable=False,default=True) + + created_at = db.Column(db.DateTime, default=datetime.now) + updated_at = db.Column(db.DateTime, nullable=True) + createdby = db.Column(db.String(120), unique=False, nullable=False) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + + #ForeignKey References + foiministryrequest_id =db.Column(db.Integer, ForeignKey('FOIMinistryRequests.foiministryrequestid')) + foiministryrequestversion_id = db.Column(db.Integer, ForeignKey('FOIMinistryRequests.version')) + + extensionstatusid =db.Column(db.Integer, unique=False, nullable=False) + extensionreasonid =db.Column(db.Integer, unique=False, nullable=False) + + extensiondocuments = relationship('FOIRequestExtensionDocumentMapping', primaryjoin="and_(FOIRequestExtension.foirequestextensionid==FOIRequestExtensionDocumentMapping.foirequestextensionid, " + "FOIRequestExtension.version==FOIRequestExtensionDocumentMapping.extensionversion)") + + @classmethod + def getextension(cls,foirequestextensionid): + document_schema = FOIRequestExtensionSchema() + request = db.session.query(FOIRequestExtension).filter_by(foirequestextensionid=foirequestextensionid).order_by(FOIRequestExtension.version.desc()).first() + return document_schema.dump(request) + + @classmethod + def saveextension(cls,ministryrequestid,ministryrequestversion, extension, extensionreason, userid, newduedate=None): + + createuserid = extension['createdby'] if 'createdby' in extension and extension['createdby'] is not None else userid + createdat = extension['created_at'] if 'created_at' in extension and extension['created_at'] is not None else datetime.now() + approveddate = extension['approveddate'] if 'approveddate' in extension else None + denieddate = extension['denieddate'] if 'denieddate' in extension else None + decisiondate = approveddate if approveddate else denieddate + approvednoofdays = extension['approvednoofdays'] if 'approvednoofdays' in extension else None + + if 'extensiontype' in extensionreason and extensionreason['extensiontype'] == 'Public Body': + extensionstatusid = 2 + elif 'extensionstatusid' in extension: + extensionstatusid = extension['extensionstatusid'] + else: + extensionstatusid = 1 + + newextension = FOIRequestExtension( + extensionreasonid=extension['extensionreasonid'], + extendedduedays=extension['extendedduedays'], + extendedduedate=extension['extendedduedate'], + decisiondate=decisiondate, + approvednoofdays=approvednoofdays, + extensionstatusid=extensionstatusid, + version=1, + isactive=True, + foiministryrequest_id=ministryrequestid, + foiministryrequestversion_id=ministryrequestversion, + created_at=createdat, + createdby=createuserid) + db.session.add(newextension) + db.session.commit() + return DefaultMethodResult(True,'Extension created', newextension.foirequestextensionid, newduedate) + + @classmethod + def saveextensions(cls, newextensions): + if(len(newextensions) > 0): + db.session.add_all(newextensions) + db.session.commit() + extensionids = [] + for extension in newextensions: + extensionids.append(extension.foirequestextensionid) + return DefaultMethodResult(True,'Extensions created',-1,extensionids) + else: + return DefaultMethodResult(True,'No Extensions to add ') + + @classmethod + def createextensionversion(cls,ministryrequestid,ministryrequestversion, extension, userid): + # if 'document' in extension: + # newextensiondocument = + newextesion = FOIRequestExtension( foirequestextensionid=extension["foirequestextensionid"], extensionreasonid=extension['extensionreasonid'], extensionstatusid=extension['extensionstatusid'], extendedduedays=extension["extendedduedays"], extendedduedate=extension["extendedduedate"], decisiondate=extension["decisiondate"], approvednoofdays=extension["approvednoofdays"], version=extension["version"], isactive=extension["isactive"], foiministryrequest_id=ministryrequestid, foiministryrequestversion_id=ministryrequestversion, createdby=userid) + db.session.add(newextesion) + db.session.commit() + return DefaultMethodResult(True,'New Extension version created', newextesion.foirequestextensionid) + + @classmethod + def getextensions(cls,ministryrequestid,ministryrequestversion): + extensions = [] + try: + sql = 'SELECT * FROM (SELECT DISTINCT ON (foirequestextensionid) foirequestextensionid, fre.extensionreasonid, er.reason, er.extensiontype, fre.extensionstatusid, es.name, extendedduedays, extendedduedate, decisiondate, approvednoofdays, fre.isactive, created_at , createdby, fre.version FROM "FOIRequestExtensions" fre INNER JOIN "ExtensionReasons" er ON fre.extensionreasonid = er.extensionreasonid INNER JOIN "ExtensionStatuses" es ON fre.extensionstatusid = es.extensionstatusid where foiministryrequest_id =:ministryrequestid and foiministryrequestversion_id = :ministryrequestversion ORDER BY foirequestextensionid, version DESC) AS list ORDER BY extensionstatusid ASC, created_at DESC' + rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid, 'ministryrequestversion':ministryrequestversion}) + for row in rs: + if row["isactive"] == True: + extensions.append(dict(row)) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return extensions + + @classmethod + def getextensionscount(cls,ministryrequestid): + extensioncount = 0 + try: + sql = """SELECT + count(*) as extensions_count + FROM + ( + SELECT + DISTINCT ON (foirequestextensionid) foirequestextensionid, + fre.extensionstatusid + FROM + "FOIRequestExtensions" fre + INNER JOIN "ExtensionStatuses" es ON fre.extensionstatusid = es.extensionstatusid + WHERE foiministryrequest_id = :ministryrequestid + AND es.extensionstatusid = 2 + AND fre.isactive is True + ) AS list """ + rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) + for row in rs: + extensioncount = row["extensions_count"] + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return extensioncount + + @classmethod + def getlatestapprovedextension(cls, extensionid, ministryrequestid, ministryrequestversion): + extension_schema = FOIRequestExtensionSchema() + request = db.session.query(FOIRequestExtension).filter(FOIRequestExtension.foirequestextensionid != extensionid, FOIRequestExtension.foiministryrequest_id == ministryrequestid, FOIRequestExtension.foiministryrequestversion_id == ministryrequestversion, FOIRequestExtension.extensionstatusid == 2).order_by(FOIRequestExtension.created_at.desc()).first() + return extension_schema.dump(request) + + @classmethod + def getversionforextension(cls,extensionid): + return db.session.query(FOIRequestExtension.version).filter_by(foirequestextensionid=extensionid).order_by(FOIRequestExtension.version.desc()).first() + + @classmethod + def getextensionforversion(cls, foirequestextensionid, version): + extension_schema = FOIRequestExtensionSchema() + extension = db.session.query(FOIRequestExtension).filter(FOIRequestExtension.foirequestextensionid == foirequestextensionid, FOIRequestExtension.version == version).order_by(FOIRequestExtension.version.desc()).first() + return extension_schema.dump(extension) + + @classmethod + def deleteextensionbyministryid(cls, ministryrequestid, userid): + db.session.query(FOIRequestExtension).filter(FOIRequestExtension.foiministryrequest_id == ministryrequestid).update({"isactive": False, "updated_at": datetime.now(),"updatedby": userid}, synchronize_session=False) + db.session.commit() + return DefaultMethodResult(True,'Extensions disabled for the ministry',ministryrequestid) + + @classmethod + def disableextension(cls, foirequestextensionid, userid): + db.session.query(FOIRequestExtension).filter(FOIRequestExtension.foirequestextensionid == foirequestextensionid).update({"isactive": False, "updated_at": datetime.now(),"updatedby": userid}, synchronize_session=False) + db.session.commit() + return DefaultMethodResult(True,'Extensions disabled for extension ',foirequestextensionid) + + @classmethod + def disableoldversions(cls, version, ministryrequestid, userid): + db.session.query(FOIRequestExtension).filter(FOIRequestExtension.foiministryrequest_id == ministryrequestid, FOIRequestExtension.foiministryrequestversion_id != version, FOIRequestExtension.isactive == True).update({"isactive": False, "updated_at": datetime.now(),"updatedby": userid}, synchronize_session=False) + db.session.commit() + return DefaultMethodResult(True,'Previous extensions disabled for ministry request ',ministryrequestid) + + @classmethod + def disableextensions(cls, foirequestextensionids, userid): + if(len(foirequestextensionids) > 0): + db.session.query(FOIRequestExtension).filter(FOIRequestExtension.foirequestextensionid.in_(foirequestextensionids)).update({"isactive": False, "updated_at": datetime.now(),"updatedby": userid}, synchronize_session=False) + db.session.commit() + return DefaultMethodResult(True,'Extensions disabled for extension ids ','',foirequestextensionids) + else: + return DefaultMethodResult(True,'No Extensions to disable ') + +class FOIRequestExtensionSchema(ma.Schema): + class Meta: + fields = ('foirequestextensionid', 'extensionreasonid', 'extensionstatusid', 'foiministryrequest_id', 'foiministryrequestversion_id', 'extendedduedays', 'extendedduedate', 'decisiondate', 'approvednoofdays', 'version', 'isactive') + \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestNotificationDashboard.py b/historical-search-api/request_api/models/FOIRequestNotificationDashboard.py new file mode 100644 index 000000000..952492eba --- /dev/null +++ b/historical-search-api/request_api/models/FOIRequestNotificationDashboard.py @@ -0,0 +1,41 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey +from .db import db, ma +from datetime import datetime as datetime2, timezone +from sqlalchemy.orm import relationship,backref +from .default_method_result import DefaultMethodResult +from sqlalchemy.dialects.postgresql import JSON, UUID +from sqlalchemy.sql.expression import distinct +from sqlalchemy import text +from sqlalchemy.sql.sqltypes import DateTime, String, Date +from sqlalchemy.orm import relationship, backref, aliased +from sqlalchemy import insert, and_, or_, text, func, literal, cast, asc, desc, case, nullsfirst, nullslast, TIMESTAMP, extract +from .FOIRequestNotificationUsers import FOIRequestNotificationUser +from .FOIRawRequestNotificationUsers import FOIRawRequestNotificationUser + + +class FOIRequestNotificationDashboard: + + @classmethod + def getiaoeventpagination(cls, groups, page, size, sortingitems, sortingorders, filterfields, keyword, additionalfilter, userid, isiaorestrictedfilemanager, isministryrestrictedfilemanager=False): + #ministry requests + subquery_ministry_queue = FOIRequestNotificationUser.geteventsubquery(groups, filterfields, keyword, additionalfilter, userid, 'IAO', isiaorestrictedfilemanager, isministryrestrictedfilemanager) + + #sorting + sortingcondition = FOIRawRequestNotificationUser.getsorting(sortingitems, sortingorders) + #if "Intake Team" in groups or groups is None: + subquery_rawrequest_queue = FOIRawRequestNotificationUser.getrequestssubquery(groups, filterfields, keyword, additionalfilter, userid, isiaorestrictedfilemanager) + query_full_queue = subquery_rawrequest_queue.union_all(subquery_ministry_queue) + return query_full_queue.order_by(*sortingcondition).paginate(page=page, per_page=size) + #else: + # return subquery_ministry_queue.order_by(*sortingcondition).paginate(page=page, per_page=size) + + @classmethod + def getministryeventpagination(cls, group, page, size, sortingitems, sortingorders, filterfields, keyword, additionalfilter, userid,isiaorestrictedfilemanager, isministryrestrictedfilemanager): + requestby = 'Ministry' + + subquery = FOIRequestNotificationUser.geteventsubquery(group, filterfields, keyword, additionalfilter, userid, requestby, isiaorestrictedfilemanager, isministryrestrictedfilemanager) + sortingcondition = FOIRequestNotificationUser.getsorting(sortingitems, sortingorders) + + return subquery.order_by(*sortingcondition).paginate(page=page, per_page=size) + \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestNotificationUsers.py b/historical-search-api/request_api/models/FOIRequestNotificationUsers.py new file mode 100644 index 000000000..9e80691ca --- /dev/null +++ b/historical-search-api/request_api/models/FOIRequestNotificationUsers.py @@ -0,0 +1,338 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey +from .db import db, ma +from datetime import datetime as datetime2 +from sqlalchemy.orm import relationship,backref, aliased +from .default_method_result import DefaultMethodResult +from sqlalchemy.dialects.postgresql import JSON, UUID +from sqlalchemy.sql.functions import coalesce +from sqlalchemy import or_, and_, text, func, literal, cast, case, nullslast, nullsfirst, desc, asc, extract +from sqlalchemy.sql.sqltypes import String +from sqlalchemy.sql.expression import distinct +from sqlalchemy import text +import logging +import json +f = open('common/notificationtypes.json', encoding="utf8") +notificationtypes_cache = json.load(f) + + +from .FOIRequestApplicantMappings import FOIRequestApplicantMapping +from .FOIRequestApplicants import FOIRequestApplicant +from .FOIRequestStatus import FOIRequestStatus +from .ApplicantCategories import ApplicantCategory +from .FOIRequestWatchers import FOIRequestWatcher +from .FOIRequests import FOIRequest +from .FOIRestrictedMinistryRequests import FOIRestrictedMinistryRequest +from .ProgramAreas import ProgramArea +from request_api.utils.enums import ProcessingTeamWithKeycloackGroup, IAOTeamWithKeycloackGroup +from .FOIAssignees import FOIAssignee +from .FOIRequestExtensions import FOIRequestExtension +from request_api.utils.enums import RequestorType +from request_api.utils.enums import StateName +from .FOIMinistryRequestSubjectCodes import FOIMinistryRequestSubjectCode +from .SubjectCodes import SubjectCode +from .FOIRequestStatus import FOIRequestStatus + +from .FOIRawRequestNotifications import FOIRawRequestNotification +from .FOIRawRequestNotificationUsers import FOIRawRequestNotificationUser +from request_api.models.views.FOINotifications import FOINotifications +from request_api.models.views.FOIRequests import FOIRequests + + +class FOIRequestNotificationUser(db.Model): + # Name of the table in our database + __tablename__ = 'FOIRequestNotificationUsers' + # Defining the columns + notificationuserid = db.Column(db.Integer, primary_key=True,autoincrement=True) + notificationid = db.Column(db.Integer,ForeignKey('FOIRequestNotifications.notificationid')) + userid = db.Column(db.String(100), unique=False, nullable=True) + isdeleted = db.Column(db.Boolean, unique=False, nullable=True, default=False) + created_at = db.Column(db.DateTime, default=datetime2.now) + createdby = db.Column(db.String(120), unique=False, nullable=True) + updated_at = db.Column(db.DateTime, nullable=True) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + notificationusertypelabel = db.Column(db.String(100),nullable=False) + notificationusertypeid = db.Column(db.Integer,nullable=False) + + + @classmethod + def dismissnotification(cls, notificationuserid, userid='system'): + exists = bool(db.session.query(FOIRequestNotificationUser.notificationuserid).filter_by(notificationuserid=notificationuserid).first()) + if exists == False: + return DefaultMethodResult(False,'Invalid ID',notificationuserid) + db.session.query(FOIRequestNotificationUser).filter(FOIRequestNotificationUser.notificationuserid == notificationuserid).update({FOIRequestNotificationUser.isdeleted: True, FOIRequestNotificationUser.updatedby: userid, + FOIRequestNotificationUser.updated_at: datetime2.now()}) + db.session.commit() + return DefaultMethodResult(True,'Notification deleted',notificationuserid) + + @classmethod + def dismissnotificationbyuser(cls, userid): + db.session.query(FOIRequestNotificationUser).filter(FOIRequestNotificationUser.userid == userid).update({FOIRequestNotificationUser.isdeleted: True, FOIRequestNotificationUser.updatedby: userid, + FOIRequestNotificationUser.updated_at: datetime2.now()}) + db.session.commit() + return DefaultMethodResult(True,'Notifications deleted for user',userid) + + @classmethod + def dismissnotificationbyuserandtype(cls, userid, notificationusertypelabel): + db.session.query(FOIRequestNotificationUser).filter(FOIRequestNotificationUser.userid == userid, FOIRequestNotificationUser.notificationusertypelabel == notificationusertypelabel).update({FOIRequestNotificationUser.isdeleted: True, FOIRequestNotificationUser.updatedby: userid, + FOIRequestNotificationUser.updated_at: datetime2.now()}) + db.session.commit() + return DefaultMethodResult(True,'Notifications deleted for user',userid) + + @classmethod + def dismissbynotificationid(cls, notificationids, userid='system'): + db.session.query(FOIRequestNotificationUser).filter(FOIRequestNotificationUser.notificationid.in_(notificationids)).update({FOIRequestNotificationUser.isdeleted: True, FOIRequestNotificationUser.updatedby: userid, + FOIRequestNotificationUser.updated_at: datetime2.now()}, synchronize_session=False) + db.session.commit() + return DefaultMethodResult(True,'Notifications deleted for id',notificationids) + + @classmethod + def getnotificationsbyid(cls, notificationuserid): + notifications = [] + try: + sql = """select notificationid, count(1) as relcount from "FOIRequestNotificationUsers" frnu + where notificationid in (select notificationid from "FOIRequestNotificationUsers" frnu where notificationuserid = :notificationuserid) group by notificationid """ + rs = db.session.execute(text(sql), {'notificationuserid': notificationuserid}) + + for row in rs: + notifications.append({"notificationid": row["notificationid"], "count" : row["relcount"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return notifications + + @classmethod + def getnotificationsbyuser(cls, userid): + notifications = [] + try: + sql = """select notificationid, count(1) as relcount from "FOIRequestNotificationUsers" frnu + where notificationid in (select notificationid from "FOIRequestNotificationUsers" frnu where userid = :userid) group by notificationid """ + rs = db.session.execute(text(sql), {'userid': userid}) + for row in rs: + notifications.append({"notificationid": row["notificationid"], "count" : row["relcount"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return notifications + + @classmethod + def getnotificationsbyuserandtype(cls, userid, notificationusertypelabel): + notifications = [] + try: + sql = """select notificationid, count(1) as relcount from "FOIRequestNotificationUsers" frnu + where notificationid in (select notificationid from "FOIRequestNotificationUsers" frnu where userid = :userid and notificationusertypelabel = :notificationusertypelabel) group by notificationid """ + rs = db.session.execute(text(sql), {'userid': userid, 'notificationusertypelabel':notificationusertypelabel}) + for row in rs: + notifications.append({"notificationid": row["notificationid"], "count" : row["relcount"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return notifications + + @classmethod + def getnotificationidsbyuserandid(cls, userid, notificationids): + ids = [] + try: + sql = """select notificationid from "FOIRequestNotificationUsers" where userid = :userid and notificationid = ANY(:notificationids) """ + rs = db.session.execute(text(sql), {'userid': userid, 'notificationids': notificationids}) + for row in rs: + ids.append(row["notificationid"]) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return ids + + + # Begin of Dashboard functions + @classmethod + def geteventsubquery(cls, groups, filterfields, keyword, additionalfilter, userid, requestby='IAO', isiaorestrictedfilemanager=False, isministryrestrictedfilemanager=False): + #for queue/dashboard + _session = db.session + + #aliase for getting ministry restricted flag from FOIRestrictedMinistryRequest + ministry_restricted_requests = aliased(FOIRestrictedMinistryRequest) + + #ministry filter for group/team + ministryfilter = FOIRequestNotificationUser.getgroupfilters(groups) + + #filter/search + if(len(filterfields) > 0 and keyword is not None): + filtercondition = [] + if(keyword != "restricted"): + for field in filterfields: + _keyword = FOIRequestNotificationUser.getfilterkeyword(keyword, field) + filtercondition.append(FOIRequestNotificationUser.findfield(field).ilike('%'+_keyword+'%')) + else: + if(requestby == 'IAO'): + filtercondition.append(FOIRestrictedMinistryRequest.isrestricted == True) + else: + filtercondition.append(ministry_restricted_requests.isrestricted == True) + + selectedcolumns = [ + FOIRequests.crtid.label('crtid'), + FOIRequests.axisrequestid.label('axisRequestId'), + FOIRequests.rawrequestid.label('rawrequestid'), + FOIRequests.foirequest_id.label('requestid'), + FOIRequests.foiministryrequestid.label('ministryrequestid'), + FOIRequests.status.label('status'), + FOIRequests.assignedtoformatted.label('assignedToFormatted'), + FOIRequests.ministryassignedtoformatted.label('ministryAssignedToFormatted'), + FOIRequests.description, + FOINotifications.notificationtype.label('notificationtype'), + FOINotifications.notification.label('notification'), + FOINotifications.created_at.label('createdat'), + FOINotifications.createdatformatted.label('createdatformatted'), + FOINotifications.userformatted.label('userFormatted'), + FOINotifications.creatorformatted.label('creatorFormatted'), + FOINotifications.id.label('id') + ] + + basequery = _session.query( + *selectedcolumns + ).join( + FOIRestrictedMinistryRequest, + and_( + FOIRestrictedMinistryRequest.ministryrequestid == FOIRequests.foiministryrequestid, + FOIRestrictedMinistryRequest.type == 'iao', + FOIRestrictedMinistryRequest.isactive == True), + isouter=True + ).join( + ministry_restricted_requests, + and_( + ministry_restricted_requests.ministryrequestid == FOIRequests.foiministryrequestid, + ministry_restricted_requests.type == 'ministry', + ministry_restricted_requests.isactive == True), + isouter=True + ).join( + FOINotifications, + and_(FOINotifications.axisnumber == FOIRequests.axisrequestid), + ).filter(FOIRequests.requeststatuslabel != StateName.closed.name) + + if(additionalfilter == 'watchingRequests'): + #watchby + #activefilter = and_(FOIMinistryRequest.isactive == True, FOIRequestStatus.isactive == True) + + subquery_watchby = FOIRequestWatcher.getrequestidsbyuserid(userid) + dbquery = basequery.join(subquery_watchby, subquery_watchby.c.ministryrequestid == FOIRequests.foiministryrequestid)#.filter(activefilter) + elif(additionalfilter == 'myRequests'): + #myrequest + if(requestby == 'IAO'): + dbquery = basequery.filter(or_(and_(FOIRequests.assignedto == userid, ministryfilter),and_(FOINotifications.userid == userid, FOINotifications.notificationtypelabel == notificationtypes_cache['taggedusercomments']['notificationtypelabel']))) + else: + dbquery = basequery.filter(or_(and_(FOIRequests.assignedministryperson == userid, ministryfilter),and_(FOINotifications.userid == userid, FOINotifications.notificationtypelabel == notificationtypes_cache['taggedusercomments']['notificationtypelabel']))) + else: + if(isiaorestrictedfilemanager == True or isministryrestrictedfilemanager == True): + dbquery = basequery + else: + if(requestby == 'IAO'): + dbquery = basequery.filter(or_(or_(FOIRestrictedMinistryRequest.isrestricted == False, FOIRestrictedMinistryRequest.isrestricted == None), and_(FOIRestrictedMinistryRequest.isrestricted == True, FOIRequests.assignedto == userid))).filter(ministryfilter) + else: + dbquery = basequery.filter(or_(or_(ministry_restricted_requests.isrestricted == False, ministry_restricted_requests.isrestricted == None), and_(ministry_restricted_requests.isrestricted == True, FOIRequests.assignedministryperson == userid))).filter(ministryfilter) + + if(keyword is None): + return dbquery + else: + return dbquery.filter(or_(*filtercondition)) + + + @classmethod + def getgroupfilters(cls, groups): + #ministry filter for group/team + if groups is None: + #ministryfilter = FOIMinistryRequest.isactive == True + ministryfilter = None + else: + groupfilter = [] + for group in groups: + if (group == IAOTeamWithKeycloackGroup.flex.value or group in ProcessingTeamWithKeycloackGroup.list()): + groupfilter.append( + and_( + FOIRequests.assignedgroup == group + ) + ) + elif (group == IAOTeamWithKeycloackGroup.intake.value): + groupfilter.append( + or_( + FOIRequests.assignedgroup == group, + and_( + FOIRequests.assignedgroup == IAOTeamWithKeycloackGroup.flex.value, + FOIRequests.requeststatuslabel.in_([StateName.open.name]) + ) + ) + ) + else: + groupfilter.append( + or_( + FOIRequests.assignedgroup == group, + and_( + FOIRequests.assignedministrygroup == group, + FOIRequests.requeststatuslabel.in_([StateName.callforrecords.name,StateName.recordsreview.name,StateName.feeestimate.name,StateName.consult.name,StateName.ministrysignoff.name,StateName.onhold.name,StateName.deduplication.name,StateName.harmsassessment.name,StateName.response.name,StateName.tagging.name,StateName.readytoscan.name]) + ) + ) + ) + + ministryfilter = and_( + or_(*groupfilter) + ) + + return ministryfilter + + @classmethod + def getfilterkeyword(cls, keyword, field): + _newkeyword = keyword + if(field == 'idNumber'): + _newkeyword = _newkeyword.replace('u-00', '') + return _newkeyword + + @classmethod + def getsorting(cls, sortingitems, sortingorders): + #sorting + sortingcondition = [] + if(len(sortingitems) > 0 and len(sortingorders) > 0 and len(sortingitems) == len(sortingorders)): + for field in sortingitems: + order = sortingorders.pop(0) + sortingcondition.append(FOIRequestNotificationUser.getfieldforsorting(field, order)) + + #default sorting + if(len(sortingcondition) == 0): + sortingcondition.append(FOIRequestNotificationUser.findfield('createdat').desc()) + + #always sort by created_at last to prevent pagination collisions + sortingcondition.append(asc('created_at')) + + return sortingcondition + + @classmethod + def getfieldforsorting(cls, field, order): + if(order == 'desc'): + return nullslast(FOIRequestNotificationUser.findfield(field).desc()) + else: + return nullsfirst(FOIRequestNotificationUser.findfield(field).asc()) + + @classmethod + def findfield(cls, x): + #add more fields here if need sort/filter/search more columns + return { + 'axisRequestId' : FOIRequests.axisrequestid, + 'createdat': FOINotifications.created_at, + 'createdatformatted': FOINotifications.createdatformatted, + 'notification': FOINotifications.notification, + 'assignedToFormatted': FOIRequests.assignedtoformatted, + 'ministryAssignedToFormatted': FOIRequests.ministryassignedtoformatted, + 'userFormatted': FOINotifications.userformatted, + 'creatorFormatted': FOINotifications.creatorformatted + }.get(x, cast(FOIRequests.axisrequestid, String)) + + # End of Dashboard functions + +class FOIRequestNotificationUserSchema(ma.Schema): + class Meta: + fields = ('notificationid', 'userid','notificationusertypeid', 'notificationusertypelabel','created_at','createdby','updated_at','updatedby') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestNotifications.py b/historical-search-api/request_api/models/FOIRequestNotifications.py new file mode 100644 index 000000000..fc8728878 --- /dev/null +++ b/historical-search-api/request_api/models/FOIRequestNotifications.py @@ -0,0 +1,189 @@ +import logging +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint +from sqlalchemy.sql.schema import ForeignKey +from .db import db, ma +from sqlalchemy.dialects.postgresql import JSON +from datetime import datetime as datetime2 +from sqlalchemy.orm import relationship,backref +from .default_method_result import DefaultMethodResult +from sqlalchemy.dialects.postgresql import JSON, UUID +from sqlalchemy.sql.expression import distinct +from sqlalchemy import text +import maya +import json +f = open('common/notificationtypes.json', encoding="utf8") +notificationtypes_cache = json.load(f) + +class FOIRequestNotification(db.Model): + # Name of the table in our database + __tablename__ = 'FOIRequestNotifications' + # Defining the columns + notificationid = db.Column(db.Integer, primary_key=True,autoincrement=True) + foirequestid =db.Column(db.Integer, nullable=False) + requestid =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.foiministryrequestid')) + version =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.version')) + idnumber = db.Column(db.String(50), unique=False, nullable=True) + axisnumber = db.Column(db.String(50), unique=False, nullable=True) + notification = db.Column(JSON, unique=False, nullable=True) + isdeleted = db.Column(db.Boolean, unique=False, nullable=True, default=False) + created_at = db.Column(db.DateTime, default=datetime2.now) + createdby = db.Column(db.String(120), unique=False, nullable=True) + updated_at = db.Column(db.DateTime, nullable=True) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + notificationtypeid = db.Column(db.Integer, nullable=False) + notificationtypelabel = db.Column(db.String(50), nullable=False) + + notificationusers = db.relationship('FOIRequestNotificationUser', backref='FOIRequestNotifications', lazy='dynamic') + + + @classmethod + def savenotification(cls,foinotification)->DefaultMethodResult: + try: + db.session.add(foinotification) + db.session.commit() + return DefaultMethodResult(True,'Notification added',foinotification.requestid) + except: + db.session.rollback() + raise + + @classmethod + def updatenotification(cls, foinotification, userid): + dbquery = db.session.query(FOIRequestNotification) + _notification = dbquery.filter_by(notificationid=foinotification['notificationid']) + if(_notification.count() > 0) : + _notification.update({ + FOIRequestNotification.notification:foinotification['notification'], + FOIRequestNotification.updatedby:userid, + FOIRequestNotification.updated_at:datetime2.now() + }, synchronize_session = False) + db.session.commit() + return DefaultMethodResult(True,'notification updated',foinotification['notificationid']) + else: + return DefaultMethodResult(True,'No notification found',foinotification['notificationid']) + + + @classmethod + def getconsolidatednotifications(cls, userid, days): + notifications = [] + try: + sql = """select idnumber, axisnumber, notificationid, notification , notificationtype, userid, notificationusertype, created_at, createdby, requesttype, requestid, foirequestid from ( + select frn.idnumber, frn.axisnumber, frn.requestid, frns.notificationuserid as notificationid, frn.notification -> 'message' as notification , nty.name as notificationtype, frn.created_at , frns.createdby, frns.userid, ntu.name as notificationusertype, 'ministryrequest' requesttype, frn.foirequestid from "FOIRequestNotifications" frn inner join "FOIRequestNotificationUsers" frns on frn.notificationid = frns.notificationid and frns.isdeleted = false inner join "NotificationTypes" nty on frn.notificationtypelabel = nty.notificationtypelabel inner join "NotificationUserTypes" ntu on frns.notificationusertypelabel = ntu.notificationusertypelabel where frns.userid=:userid and frn.created_at >= current_date - interval :days day + union all + select frn.idnumber, frn.axisnumber, frn.requestid, frns.notificationuserid as notificationid, frn.notification -> 'message' as notification, nty.name as notificationtype, frn.created_at , frns.createdby, frns.userid, ntu.name as notificationusertype, 'rawrequest' requesttype, 0 foirequestid from "FOIRawRequestNotifications" frn inner join "FOIRawRequestNotificationUsers" frns on frn.notificationid = frns.notificationid and frns.isdeleted = false inner join "NotificationTypes" nty on frn.notificationtypelabel = nty.notificationtypelabel inner join "NotificationUserTypes" ntu on frns.notificationusertypelabel = ntu.notificationusertypelabel where frns.userid=:userid and frn.created_at >= current_date - interval :days day + ) as notf order by created_at desc""" + rs = db.session.execute(text(sql), {'userid': userid, 'days': days}) + for row in rs: + dt = maya.parse(row["created_at"]).datetime(to_timezone='America/Vancouver', naive=False) + _createddate = dt + notifications.append({"idnumber": row["idnumber"], "axisnumber": row["axisnumber"], "notificationid": row["notificationid"], "notification": row["notification"], "notificationtype": row["notificationtype"], "notificationusertype": row["notificationusertype"], "created_at": _createddate.strftime('%Y %b %d | %I:%M %p').upper(), "createdby": row["createdby"], "requesttype":row["requesttype"], "requestid":row["requestid"],"foirequestid":row["foirequestid"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return notifications + + @classmethod + def getcommentnotifications(cls, commentid): + notifications = [] + try: + sql = """select idnumber, axisnumber, notificationid, notificationuserid, notification , notificationtype, userid, notificationusertype, created_at, createdby, requesttype, requestid, foirequestid from ( + select frn.idnumber, frn.axisnumber, frn.requestid, frn.notificationid, frns.notificationuserid, frn.notification -> 'message' as notification , nty.name as notificationtype, frn.created_at , frns.createdby, frns.userid, ntu.name as notificationusertype, 'ministryrequest' requesttype, frn.foirequestid from "FOIRequestNotifications" frn inner join "FOIRequestNotificationUsers" frns on frn.notificationid = frns.notificationid and frns.isdeleted = false inner join "NotificationTypes" nty on frn.notificationtypelabel = nty.notificationtypelabel inner join "NotificationUserTypes" ntu on frns.notificationusertypelabel = ntu.notificationusertypelabel where frn.notificationtypelabel in :notificationtypelabel and (frn.notification ->> 'commentid')::int = :commentid + union all + select frn.idnumber, frn.axisnumber, frn.requestid, frn.notificationid, frns.notificationuserid, frn.notification -> 'message' as notification, nty.name as notificationtype, frn.created_at , frns.createdby, frns.userid, ntu.name as notificationusertype, 'rawrequest' requesttype, 0 foirequestid from "FOIRawRequestNotifications" frn inner join "FOIRawRequestNotificationUsers" frns on frn.notificationid = frns.notificationid and frns.isdeleted = false inner join "NotificationTypes" nty on frn.notificationtypelabel = nty.notificationtypelabel inner join "NotificationUserTypes" ntu on frns.notificationusertypelabel = ntu.notificationusertypelabel where frn.notificationtypelabel in :notificationtypelabel and (frn.notification ->> 'commentid')::int = :commentid + ) as notf order by created_at desc""" + notificationtypelabel = tuple([notificationtypes_cache['newusercomments']['notificationtypelabel'], + notificationtypes_cache['replyusercomments']['notificationtypelabel'], + notificationtypes_cache['taggedusercomments']['notificationtypelabel'], + ]) # 3,9,10 + # notificationtypelabel = ','.join(str(e) for e in notificationtypelabel) + rs = db.session.execute(text(sql), {'commentid': commentid, 'notificationtypelabel': notificationtypelabel}) + for row in rs: + dt = maya.parse(row["created_at"]).datetime(to_timezone='America/Vancouver', naive=False) + _createddate = dt + notifications.append({"userid": row["userid"],"idnumber": row["idnumber"], "axisnumber": row["axisnumber"], "notificationid": row["notificationid"], "notificationuserid": row["notificationuserid"], "notification": row["notification"], "notificationtype": row["notificationtype"], "notificationusertype": row["notificationusertype"], "created_at": _createddate.strftime('%Y %b %d | %I:%M %p').upper(), "createdby": row["createdby"], "requesttype":row["requesttype"], "requestid":row["requestid"],"foirequestid":row["foirequestid"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return notifications + + @classmethod + def getextensionnotifications(cls, extensionid): + notifications = [] + try: + sql = sql = """select idnumber, axisnumber, notificationid, notification , notificationtypelabel from "FOIRequestNotifications" where isdeleted = false and notification->>'extensionid' = :extensionid """ + rs = db.session.execute(text(sql), {'extensionid': str(extensionid)}) + for row in rs: + notifications.append({"idnumber": row["idnumber"], "axisnumber": row["axisnumber"], "notificationid": row["notificationid"], "notification": row["notification"], "notificationtypelabel": row["notificationtypelabel"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return notifications + + @classmethod + def dismissnotification(cls, notificationids, userid='system'): + try: + db.session.query(FOIRequestNotification).filter(FOIRequestNotification.notificationid.in_(notificationids)).update({FOIRequestNotification.isdeleted: True, FOIRequestNotification.updatedby: userid, + FOIRequestNotification.updated_at: datetime2.now()}, synchronize_session=False) + db.session.commit() + return DefaultMethodResult(True,'Notifications deleted ', notificationids) + except: + db.session.rollback() + raise + + @classmethod + def getnotificationidsbynumberandtype(cls, idnumber, notificationtypelabels): + notificationids = [] + try: + sql = """select notificationid from "FOIRequestNotifications" where idnumber = :idnumber and notificationtypelabel = ANY(:notificationtypelabels) and isdeleted = false """ + rs = db.session.execute(text(sql), {'idnumber': idnumber, 'notificationtypelabels': notificationtypelabels}) + for row in rs: + notificationids.append(row["notificationid"]) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return notificationids + + @classmethod + def getnotificationidsbynumber(cls, idnumber): + notificationids = [] + try: + sql = """select notificationid from "FOIRequestNotifications" where idnumber = :idnumber and isdeleted = false """ + rs = db.session.execute(text(sql), {'idnumber': idnumber}) + for row in rs: + notificationids.append(row["notificationid"]) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return notificationids + + @classmethod + def getnotificationidsbytype(cls, notificationtypelabel): + sql = """select notificationid from "FOIRequestNotifications" where notificationtypelabel= :notificationtypelabel and isdeleted = false """ + rs = db.session.execute(text(sql), {'notificationtypelabel': notificationtypelabel}) + notificationids = [] + for row in rs: + notificationids.append(row["notificationid"]) + return notificationids + + @classmethod + def getextensionnotificationidsbyministry(cls, ministryid): + sql = """select notificationid from "FOIRequestNotifications" where requestid = :requestid and notificationtypelabel = 4 and isdeleted = false """ + rs = db.session.execute(text(sql), {'requestid': ministryid}) + notificationids = [] + for row in rs: + notificationids.append(row["notificationid"]) + return notificationids + +class FOIRequestNotificationSchema(ma.Schema): + class Meta: + fields = ('notificationid', 'ministryrequestid', 'notification', 'notificationtypeid', 'notificationtypelabel','created_at','createdby','updated_at','updatedby') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestOIPC.py b/historical-search-api/request_api/models/FOIRequestOIPC.py new file mode 100644 index 000000000..2d65c413d --- /dev/null +++ b/historical-search-api/request_api/models/FOIRequestOIPC.py @@ -0,0 +1,63 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey +from .db import db, ma +from datetime import datetime +from sqlalchemy.orm import relationship,backref +from .default_method_result import DefaultMethodResult +from sqlalchemy.dialects.postgresql import JSON, UUID +from sqlalchemy.sql.expression import distinct +from sqlalchemy import text, and_, func +import logging +import json +from sqlalchemy.dialects.postgresql import JSON, insert + +class FOIRequestOIPC(db.Model): + # Name of the table in our database + __tablename__ = 'FOIRequestOIPC' + # Defining the columns + oipcid = db.Column(db.Integer, primary_key=True,autoincrement=True) + foiministryrequest_id =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.foiministryrequestid')) + foiministryrequestversion_id =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.version')) + oipcno = db.Column(db.String(120), unique=False, nullable=True) + reviewtypeid = db.Column(db.Integer,ForeignKey('OIPCReviewTypes.reviewtypeid')) + reviewtype = relationship("OIPCReviewTypes",backref=backref("OIPCReviewTypes"),uselist=False) + reasonid = db.Column(db.Integer,ForeignKey('OIPCReasons.reasonid')) + reason = relationship("OIPCReasons",backref=backref("OIPCReasons"),uselist=False) + statusid = db.Column(db.Integer,ForeignKey('OIPCStatuses.statusid')) + status = relationship("OIPCStatuses",backref=backref("OIPCStatuses"),uselist=False) + outcomeid = db.Column(db.Integer,ForeignKey('OIPCOutcomes.outcomeid')) + outcome = relationship("OIPCOutcomes",backref=backref("OIPCOutcomes"),uselist=False) + isinquiry = db.Column(db.Boolean, unique=False, nullable=True) + inquiryattributes = db.Column(JSON, unique=False, nullable=True) + isjudicialreview = db.Column(db.Boolean, unique=False, nullable=True) + issubsequentappeal = db.Column(db.Boolean, unique=False, nullable=True) + investigator = db.Column(db.String(500), unique=False, nullable=True) + receiveddate = db.Column(db.Date, nullable=True) + closeddate = db.Column(db.Date, nullable=True) + created_at = db.Column(db.DateTime, default=datetime.now) + createdby = db.Column(db.String(120), unique=False, nullable=False) + updated_at = db.Column(db.DateTime, nullable=True) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + + + @classmethod + def getoipc(cls,ministryrequestid,ministryrequestversion): + oipc_schema = FOIRequestOIPCSchema(many=True) + _oipclist = db.session.query(FOIRequestOIPC).filter(FOIRequestOIPC.foiministryrequest_id == ministryrequestid , FOIRequestOIPC.foiministryrequestversion_id == ministryrequestversion).order_by(FOIRequestOIPC.oipcid.asc()).all() + divisioninfos = oipc_schema.dump(_oipclist) + return divisioninfos + + + @classmethod + def getrequestidsbyoipcno(cls, oipcno): + return db.session.query( + FOIRequestOIPC.foiministryrequest_id, + FOIRequestOIPC.foiministryrequestversion_id + ).filter(FOIRequestOIPC.oipcno.ilike('%'+oipcno+'%')).group_by(FOIRequestOIPC.foiministryrequest_id, FOIRequestOIPC.foiministryrequestversion_id).subquery() + + +class FOIRequestOIPCSchema(ma.Schema): + class Meta: + fields = ('oipcid', 'version', 'foiministryrequest_id', 'investigator', 'foiministryrequestversion_id','oipcno','reviewtypeid','reasonid','statusid','outcomeid','isinquiry','inquiryattributes','isjudicialreview', + 'issubsequentappeal','receiveddate','closeddate','created_at','createdby','updated_at','updatedby', + 'reviewtype.name', 'reason.name', 'status.name', 'outcome.name') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestPayments.py b/historical-search-api/request_api/models/FOIRequestPayments.py new file mode 100644 index 000000000..b0c64b5d7 --- /dev/null +++ b/historical-search-api/request_api/models/FOIRequestPayments.py @@ -0,0 +1,85 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey +from .db import db, ma +from datetime import datetime +from sqlalchemy.orm import relationship,backref +from .default_method_result import DefaultMethodResult +from sqlalchemy.dialects.postgresql import JSON, UUID +from sqlalchemy.sql.expression import distinct +from sqlalchemy import text, and_, func +import logging +import maya +import os +from dateutil.parser import parse +from pytz import timezone +import json + +class FOIRequestPayment(db.Model): + # Name of the table in our database + __tablename__ = 'FOIRequestPayments' + # Defining the columns + paymentid = db.Column(db.Integer, primary_key=True,autoincrement=True) + version =db.Column(db.Integer,primary_key=True,nullable=False) + foirequestid =db.Column(db.Integer, nullable=False) + ministryrequestid =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.foiministryrequestid')) + ministryrequestversion=db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.version')) + paymenturl = db.Column(db.Text, unique=False, nullable=True) + paymentexpirydate = db.Column(db.DateTime, nullable=True) + paidamount = db.Column(db.Numeric(10,2), nullable=True) + created_at = db.Column(db.DateTime, default=datetime.now) + createdby = db.Column(db.String(120), unique=False, nullable=True) + updated_at = db.Column(db.DateTime, nullable=True) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + + @classmethod + def savepayment(cls, newpayment)->DefaultMethodResult: + db.session.add(newpayment) + db.session.commit() + return DefaultMethodResult(True,'Payment added') + + @classmethod + def updatepayment(cls, paymentid, paymenturl, userid)->DefaultMethodResult: + currequest = db.session.query(FOIRequestPayment).filter_by(paymentid=paymentid).order_by(FOIRequestPayment.version.desc()).first() + setattr(currequest,'paymenturl',paymenturl) + setattr(currequest,'updated_at',datetime.now().isoformat()) + setattr(currequest,'updatedby',userid) + db.session.commit() + return DefaultMethodResult(True,'Payment updated',paymentid) + + @classmethod + def getpayment(cls, foirequestid, ministryrequestid)->DefaultMethodResult: + payment_schema = FOIRequestPaymentSchema() + payment = db.session.query(FOIRequestPayment).filter(FOIRequestPayment.foirequestid == foirequestid, FOIRequestPayment.ministryrequestid == ministryrequestid).order_by(FOIRequestPayment.paymentid.desc(), FOIRequestPayment.version.desc()).first() + return payment_schema.dump(payment) + + @classmethod + def getactivepayment(cls, foirequestid, ministryrequestid) -> DefaultMethodResult: + now_pst = maya.parse(maya.now()).datetime(to_timezone='America/Vancouver', naive=False) + _psttoday = now_pst.strftime('%Y-%m-%d') + try: + """ + sql = select distinct on (paymentid) paymentid, paymenturl from "FOIRequestPayments" fp where foirequestid = :foirequestid and ministryrequestid = :ministryrequestid + and TO_DATE(paymentexpirydate::TEXT,'YYYY-MM-DD') >= TO_DATE(:today,'YYYY-MM-DD') + order by paymentid, version desc + """ + sql = """select fp1.paymentid , fp1.paymenturl, fp1.version from "FOIRequestPayments" fp1, ( + select distinct on (paymentid) paymentid, paymenturl, createdby, version from "FOIRequestPayments" fp where foirequestid = :foirequestid and ministryrequestid = :ministryrequestid + order by paymentid, version desc) as fp2 + where fp1.paymentid = fp2.paymentid and fp1.version = fp2.version + and fp1.createdby <> 'System_Cancel' and fp1.paymenturl is not null and fp1.paidamount is null + """ + + rs = db.session.execute(text(sql), {'foirequestid': foirequestid, 'ministryrequestid' : ministryrequestid, 'today' : _psttoday}) + for row in rs: + return ({"paymentid": row["paymentid"], "paymenturl": row["paymenturl"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return None + + +class FOIRequestPaymentSchema(ma.Schema): + class Meta: + fields = ('paymentid', 'version', 'foirequestid', 'ministryrequestid', 'paymenturl','created_at','createdby','updated_at','updatedby', 'paymentexpirydate', 'paidamount') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestPersonalAttributes.py b/historical-search-api/request_api/models/FOIRequestPersonalAttributes.py new file mode 100644 index 000000000..11dbe8a6b --- /dev/null +++ b/historical-search-api/request_api/models/FOIRequestPersonalAttributes.py @@ -0,0 +1,50 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint +from .db import db, ma +from datetime import datetime +from sqlalchemy.orm import relationship,backref +from .default_method_result import DefaultMethodResult +from .FOIRequests import FOIRequest + +class FOIRequestPersonalAttribute(db.Model): + # Name of the table in our database + __tablename__ = 'FOIRequestPersonalAttributes' + __table_args__ = ( + ForeignKeyConstraint( + ["foirequest_id", "foirequestversion_id"], ["FOIRequests.foirequestid", "FOIRequests.version"] + ), + ) + # Defining the columns + foirequestpersonalattributeid = db.Column(db.Integer, primary_key=True,autoincrement=True) + + + attributevalue = db.Column(db.String(256), unique=False, nullable=False) + + + created_at = db.Column(db.DateTime, default=datetime.now) + updated_at = db.Column(db.DateTime, nullable=True) + createdby = db.Column(db.String(120), unique=False, nullable=True) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + + #ForeignKey References + + personalattributeid = db.Column(db.Integer,ForeignKey('PersonalInformationAttributes.attributeid')) + personalattribute = relationship("PersonalInformationAttribute",backref=backref("PersonalInformationAttributes"),uselist=False) + + foirequest_id =db.Column(db.Integer, db.ForeignKey('FOIRequests.foirequestid')) + foirequestversion_id = db.Column(db.Integer, db.ForeignKey('FOIRequests.version')) + foirequestkey = relationship("FOIRequest",foreign_keys="[FOIRequestPersonalAttribute.foirequest_id]") + foirequestversion = relationship("FOIRequest",foreign_keys="[FOIRequestPersonalAttribute.foirequestversion_id]") + + @classmethod + def getrequestpersonalattributes(cls,foirequest_id,foirequestversion): + requestpersonalattribute_schema = FOIRequestPersonalAttributeSchema(many=True) + _personalattributes = db.session.query(FOIRequestPersonalAttribute).filter(FOIRequestPersonalAttribute.foirequest_id == foirequest_id , FOIRequestPersonalAttribute.foirequestversion_id == foirequestversion).order_by(FOIRequestPersonalAttribute.foirequestpersonalattributeid.asc()).all() + personalattributes = requestpersonalattribute_schema.dump(_personalattributes) + return personalattributes + + +class FOIRequestPersonalAttributeSchema(ma.Schema): + class Meta: + fields = ('foirequestpersonalattributeid','attributevalue','foirequest.foirequestid','personalattribute.name','personalattributeid') + \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestRecords.py b/historical-search-api/request_api/models/FOIRequestRecords.py new file mode 100644 index 000000000..a0ad9eebb --- /dev/null +++ b/historical-search-api/request_api/models/FOIRequestRecords.py @@ -0,0 +1,183 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey +from .db import db, ma +from datetime import datetime +from sqlalchemy.orm import relationship,backref +from .default_method_result import DefaultMethodResult +from sqlalchemy.dialects.postgresql import JSON, UUID +from sqlalchemy.sql.expression import distinct +from sqlalchemy import text +import logging +import json +class FOIRequestRecord(db.Model): + # Name of the table in our database + __tablename__ = 'FOIRequestRecords' + # Defining the columns + recordid = db.Column(db.Integer, primary_key=True,autoincrement=True) + version =db.Column(db.Integer,primary_key=True,nullable=False) + foirequestid =db.Column(db.Integer, nullable=False) + ministryrequestid =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.foiministryrequestid')) + ministryrequestversion=db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.version')) + filename = db.Column(db.Text, unique=False, nullable=True) + s3uripath = db.Column(db.Text, unique=False, nullable=True) + attributes = db.Column(JSON, unique=False, nullable=True) + created_at = db.Column(db.DateTime, default=datetime.now) + createdby = db.Column(db.String(120), unique=False, nullable=True) + updated_at = db.Column(db.DateTime, nullable=True) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + isactive = db.Column(db.Boolean, unique=False, nullable=False,default=True) + replacementof = db.Column(db.Integer, unique=False, nullable=False) + + @classmethod + def create(cls, records): + db.session.add_all(records) + db.session.commit() + _recordids = {} + for record in records: + _recordids[record.s3uripath] = {"filename": record.filename, "recordid": record.recordid} + return DefaultMethodResult(True,'Records created', -1, _recordids) + + @classmethod + def fetch(cls, foirequestid, ministryrequestid): + records = [] + try: + sql = """select distinct on (fr1.recordid) recordid, fr1.isactive, fr1.filename, + fr1.s3uripath, fr1."attributes" attributes, json_extract_path_text("attributes" ::json,'batch') as batchid, + fr1.createdby createdby, fr1.created_at,fr1.replacementof + from public."FOIRequestRecords" fr1 + where fr1.foirequestid = :foirequestid and fr1.ministryrequestid = :ministryrequestid + order by recordid desc, version desc + """ + + rs = db.session.execute(text(sql), {'foirequestid': foirequestid, 'ministryrequestid' : ministryrequestid}) + + for row in rs: + if row["isactive"] == True: + _originalfile ='' + _originalfilename ='' + if row["replacementof"] is not None: + originalrecord = FOIRequestRecord.getrecordbyid(row["replacementof"]) + _originalfile = originalrecord["s3uripath"] + _originalfilename = originalrecord["filename"] + records.append({ + "recordid": row["recordid"], + "filename": row["filename"], + "s3uripath": row["s3uripath"], + "attributes": row["attributes"], + "batchid": row["batchid"], + "createdby": row["createdby"], + "created_at": row["created_at"], + "replacementof":row["replacementof"], + "originalfile" : _originalfile, + "originalfilename":_originalfilename + }) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return records + + @classmethod + def getrecordbyid(cls, recordid)->DefaultMethodResult: + comment_schema = FOIRequestRecordSchema(many=False) + query = db.session.query(FOIRequestRecord).filter_by(recordid=recordid).order_by(FOIRequestRecord.version.desc()).first() + return comment_schema.dump(query) + + @classmethod + def getrecordsbyid(cls, recordids): + records = [] + try: + sql = """select + fr1.recordid, fr1.version, fr1.foirequestid, fr1.ministryrequestid, + fr1.ministryrequestversion, fr1.attributes, fr1.filename, fr1.s3uripath, + fr1.created_at, fr1.createdby, fr1.updated_at, fr1.updatedby, fr1.replacementof + from public."FOIRequestRecords" fr1 + inner join ( + select fr2.recordid, max(fr2.version) as maxversion + from public."FOIRequestRecords" fr2 + where fr2.recordid in ("""+ ','.join([str(id) for id in recordids]) +""") + group by fr2.recordid + ) fr3 on fr3.recordid = fr1.recordid and fr3.maxversion = fr1.version + order by fr1.recordid desc + """ + rs = db.session.execute(text(sql)) + + for row in rs: + records.append({ + "recordid": row["recordid"], + "version": row["version"], + "foirequestid": row["foirequestid"], + "ministryrequestid": row["ministryrequestid"], + "ministryrequestversion": row["ministryrequestversion"], + "attributes": row["attributes"], + "filename": row["filename"], + "s3uripath": row["s3uripath"], + "created_at": row["created_at"], + "createdby": row["createdby"], + "updated_at":row["updated_at"], + "updatedby":row["updatedby"], + "updated_at":row["updated_at"], + "replacementof":row["replacementof"] + }) + except Exception as ex: + logging.error(ex) + raise ex + 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() + replacingrecord.isactive=False + db.session.commit() + db.session.add_all(records) + db.session.commit() + _recordids = {} + for record in records: + _recordids[record.s3uripath] = {"filename": record.filename, "recordid": record.recordid} + return DefaultMethodResult(True,'Records replaced', -1, _recordids) + + @classmethod + def getbatchcount(cls, ministryrequestid): + batchcount = 0 + try: + sql = """select count(distinct + json_extract_path_text("attributes" ::json,'batch')) AS batch_count + FROM "FOIRequestRecords" r + join (select max(version), recordid + from public."FOIRequestRecords" + group by recordid) r2 on r2.max = r.version and r2.recordid = r.recordid + where ministryrequestid = :ministryrequestid and isactive = true """ + rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) + for row in rs: + batchcount = row["batch_count"] + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return batchcount + +class FOIRequestRecordSchema(ma.Schema): + class Meta: + fields = ('recordid','version','foirequestid','ministryrequestid','ministryrequestversion','attributes','filename','s3uripath','created_at','createdby','updated_at','updatedby','replacementof') diff --git a/historical-search-api/request_api/models/FOIRequestStatus.py b/historical-search-api/request_api/models/FOIRequestStatus.py new file mode 100644 index 000000000..439c9f6ed --- /dev/null +++ b/historical-search-api/request_api/models/FOIRequestStatus.py @@ -0,0 +1,34 @@ +from .db import db, ma +from .default_method_result import DefaultMethodResult + + +class FOIRequestStatus(db.Model): + __tablename__ = 'FOIRequestStatuses' + # Defining the columns + requeststatusid = db.Column(db.Integer, primary_key=True,autoincrement=True) + name = db.Column(db.String(100), unique=False, nullable=False) + description = db.Column(db.String(255), unique=False, nullable=True) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + statuslabel = db.Column(db.String(50), unique=True, nullable=False) + + @classmethod + def getrequeststatuses(cls): + requeststatus_schema = RequestStatusSchema(many=True) + query = db.session.query(FOIRequestStatus).filter_by(isactive=True).all() + return requeststatus_schema.dump(query) + + @classmethod + def getrequeststatus(cls,status): + requeststatus_schema = RequestStatusSchema() + query = db.session.query(FOIRequestStatus).filter_by(name=status).first() + return requeststatus_schema.dump(query) + + @classmethod + def getrequeststatusbylabel(cls,statuslabel): + requeststatus_schema = RequestStatusSchema() + query = db.session.query(FOIRequestStatus).filter_by(statuslabel=statuslabel, isactive=True).first() + return requeststatus_schema.dump(query) + +class RequestStatusSchema(ma.Schema): + class Meta: + fields = ('requeststatusid', 'name', 'description','isactive','statuslabel') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestTeams.py b/historical-search-api/request_api/models/FOIRequestTeams.py new file mode 100644 index 000000000..e6ddbe50f --- /dev/null +++ b/historical-search-api/request_api/models/FOIRequestTeams.py @@ -0,0 +1,98 @@ +from .db import db, ma +from .default_method_result import DefaultMethodResult +from sqlalchemy.orm import relationship,backref +from sqlalchemy.sql.schema import ForeignKey +from sqlalchemy import text +import logging +from request_api.utils.enums import StateName + +class FOIRequestTeam(db.Model): + __tablename__ = 'FOIRequestTeams' + # Defining the columns + requestteamid = db.Column(db.Integer, primary_key=True,autoincrement=True) + requesttype = db.Column(db.String(100), unique=False, nullable=True) + requeststatusid = db.Column(db.Integer,ForeignKey('FOIRequestStatuses.requeststatusid')) + requeststatuslabel = db.Column(db.String(50), unique=False, nullable=False) + teamid = db.Column(db.Integer,ForeignKey('OperatingTeams.teamid')) + programareaid = db.Column(db.Integer,ForeignKey('ProgramAreas.programareaid')) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + + @classmethod + def getrequestteams(cls): + programarea_schema = FOIRequestTeamSchema(many=True) + query = db.session.query(FOIRequestTeam).filter_by(isactive=True).all() + return programarea_schema.dump(query) + + @classmethod + def getteamsbystatusandprogramarea(cls, requesttype, status, bcgovcode): + teams = [] + try: + # and replace(lower(fs2."name"),' ','') = :status + sql = """ + with mappedteams as ( + select ot."name" as name, ot."type" as type, ft.requestteamid as orderby from "FOIRequestTeams" ft inner join "FOIRequestStatuses" fs2 on ft.requeststatusid = fs2.requeststatusid + inner join "OperatingTeams" ot on ft.teamid = ot.teamid + left join "ProgramAreas" pa on ft.programareaid = pa.programareaid + where ft.isactive = true and lower(ft.requesttype) = :requesttype + and ft.requeststatuslabel = :status + and (lower(pa.bcgovcode) = :bcgovcode or ft.programareaid is null) + ) + -- remove the with statement and below query go back to mapped teams only in assignee drop down + select * from mappedteams union + (select name, type, 1 as orderby from "OperatingTeams" where name not in (select name from mappedteams) and type = 'iao' and isactive = true) + order by orderby desc""" + rs = db.session.execute(text(sql), {'requesttype': requesttype, 'status': status,'bcgovcode':bcgovcode}) + + for row in rs: + teams.append({"name":row["name"], "type":row["type"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return teams + + @classmethod + def getprocessingteamsbytype(cls, requesttype): + teams = [] + try: + sql = """select ot."name" as team, pa."name" as ministry, pa.bcgovcode, pa.iaocode from "FOIRequestTeams" ft + inner join "OperatingTeams" ot on ft.teamid = ot.teamid + inner join "ProgramAreas" pa on ft.programareaid = pa.programareaid + where lower(ft.requesttype) = :requesttype and ft.programareaid is not null + and ot."type" = 'iao' + and ft.requeststatuslabel = :requeststatuslabel""" + rs = db.session.execute(text(sql), {'requesttype': requesttype, 'requeststatuslabel': StateName.feeestimate.name}) + for row in rs: + teams.append({"team":row["team"], "ministry":row["ministry"], "bcgovcode":row["bcgovcode"], "iaocode":row["iaocode"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return teams + + @classmethod + def getdefaultprocessingteamforpersonal(cls, bcgovcode): + defaultteam = None + try: + sql = """select ot."name" as name from "FOIRequestTeams" ft inner join "FOIRequestStatuses" fs2 on ft.requeststatusid = fs2.requeststatusid + inner join "OperatingTeams" ot on ft.teamid = ot.teamid + left join "ProgramAreas" pa on ft.programareaid = pa.programareaid + where ft.isactive = true and lower(ft.requesttype) = 'personal' + and replace(lower(fs2."name"),' ','') = 'open' + and ot."name" not in ('Intake Team','Flex Team') + and (lower(pa.bcgovcode) = :bcgovcode or ft.programareaid is null) order by requestteamid desc limit 1""" + rs = db.session.execute(text(sql), {'bcgovcode':bcgovcode.lower()}) + for row in rs: + defaultteam = row["name"] + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return defaultteam + +class FOIRequestTeamSchema(ma.Schema): + class Meta: + fields = ('requestteamid', 'requesttype', 'requeststatusid','teamid','programareaid','isactive', 'requeststatuslabel') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestWatchers.py b/historical-search-api/request_api/models/FOIRequestWatchers.py new file mode 100644 index 000000000..a95f4913b --- /dev/null +++ b/historical-search-api/request_api/models/FOIRequestWatchers.py @@ -0,0 +1,138 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey +from .db import db, ma +from datetime import datetime +from sqlalchemy.orm import relationship,backref +from .default_method_result import DefaultMethodResult +from sqlalchemy.dialects.postgresql import JSON, UUID +from sqlalchemy.sql.expression import distinct +from sqlalchemy import text, and_, func +import logging +import json +class FOIRequestWatcher(db.Model): + # Name of the table in our database + __tablename__ = 'FOIRequestWatchers' + # Defining the columns + watcherid = db.Column(db.Integer, primary_key=True,autoincrement=True) + ministryrequestid =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.foiministryrequestid')) + version =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.version')) + watchedbygroup = db.Column(db.String(250), unique=False, nullable=True) + watchedby = db.Column(db.String(120), unique=False, nullable=True) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + created_at = db.Column(db.DateTime, default=datetime.now) + createdby = db.Column(db.String(120), unique=False, nullable=True) + updated_at = db.Column(db.DateTime, nullable=True) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + + @classmethod + def savewatcher(cls, foirequestwatcher, version, userid)->DefaultMethodResult: + newwatcher = FOIRequestWatcher(ministryrequestid=foirequestwatcher["ministryrequestid"], version=version, watchedbygroup=foirequestwatcher["watchedbygroup"], watchedby=foirequestwatcher["watchedby"], isactive=foirequestwatcher["isactive"], createdby=userid) + db.session.add(newwatcher) + db.session.commit() + return DefaultMethodResult(True,'Request added') + + @classmethod + def savewatcherbygroups(cls, foirequestwatcher, version, userid, watchergroups)->DefaultMethodResult: + for group in watchergroups: + foirequestwatcher["watchedbygroup"] = group + newwatcher = FOIRequestWatcher(ministryrequestid=foirequestwatcher["ministryrequestid"], version=version, watchedbygroup=foirequestwatcher["watchedbygroup"], watchedby=foirequestwatcher["watchedby"], isactive=foirequestwatcher["isactive"], createdby=userid) + db.session.add(newwatcher) + db.session.commit() + return DefaultMethodResult(True,'Request added') + + @classmethod + def getMinistrywatchers(cls, ministryrequestid): + watchers = [] + try: + sql = 'select distinct on (watchedby, watchedbygroup) watchedby, watchedbygroup, isactive from "FOIRequestWatchers" where ministryrequestid=:ministryrequestid and watchedbygroup like \'%Ministry Team\' order by watchedby, watchedbygroup, created_at desc' + rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) + for row in rs: + if row["isactive"] == True: + watchers.append({"watchedby": row["watchedby"], "watchedbygroup": row["watchedbygroup"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return watchers + + @classmethod + def getNonMinistrywatchers(cls, ministryrequestid): + watchers = [] + try: + sql = 'select distinct on (watchedby, watchedbygroup) watchedby, watchedbygroup, isactive from "FOIRequestWatchers" where ministryrequestid=:ministryrequestid and watchedbygroup not like \'%Ministry Team\' order by watchedby, watchedbygroup, created_at desc' + rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) + for row in rs: + if row["isactive"] == True: + watchers.append({"watchedby": row["watchedby"], "watchedbygroup": row["watchedbygroup"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return watchers + + @classmethod + def isaiaoministryrequestwatcher(cls, ministryrequestid,userid): + _iswatcher = False + try: + sql = 'select distinct on (watchedby, watchedbygroup) watchedby, watchedbygroup, isactive from "FOIRequestWatchers" where ministryrequestid=:ministryrequestid and watchedby=:watchedby and watchedbygroup not like \'%Ministry Team\' order by watchedby, watchedbygroup, created_at desc' + rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid,'watchedby':userid}) + for row in rs: + if row["isactive"] == True: + _iswatcher = True + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return _iswatcher + + @classmethod + def isaministryministryrequestwatcher(cls, ministryrequestid,userid): + _iswatcher = False + try: + sql = 'select distinct on (watchedby, watchedbygroup) watchedby, watchedbygroup, isactive from "FOIRequestWatchers" where ministryrequestid=:ministryrequestid and watchedby=:watchedby and watchedbygroup like \'%Ministry Team\' order by watchedby, watchedbygroup, created_at desc' + rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid,'watchedby':userid}) + for row in rs: + if row["isactive"] == True: + _iswatcher = True + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return _iswatcher + + @classmethod + def getrequestidsbyuserid(cls, userid): + #subquery for getting latest watching status + subquery_max = db.session.query(FOIRequestWatcher.ministryrequestid, FOIRequestWatcher.watchedby ,func.max(FOIRequestWatcher.watcherid).label('max_watcherid')).group_by(FOIRequestWatcher.ministryrequestid, FOIRequestWatcher.watchedby).subquery() + joincondition = [ + subquery_max.c.ministryrequestid == FOIRequestWatcher.ministryrequestid, + subquery_max.c.watchedby == FOIRequestWatcher.watchedby, + subquery_max.c.max_watcherid == FOIRequestWatcher.watcherid, + ] + + return db.session.query( + FOIRequestWatcher.ministryrequestid, + FOIRequestWatcher.watchedby + ).join( + subquery_max, + and_(*joincondition) + ).filter(and_(FOIRequestWatcher.watchedby == userid, FOIRequestWatcher.isactive == True)).subquery() + + @classmethod + def disablewatchers(cls, ministryrequestid, userid): + dbquery = db.session.query(FOIRequestWatcher) + requestraqw = dbquery.filter_by(ministryrequestid=ministryrequestid) + if(requestraqw.count() > 0) : + requestraqw.update({FOIRequestWatcher.isactive:False, FOIRequestWatcher.updatedby:userid, FOIRequestWatcher.updated_at:datetime.now()}, synchronize_session = False) + db.session.commit() + return DefaultMethodResult(True,'Watchers disabled',ministryrequestid) + else: + return DefaultMethodResult(True,'No Watchers found',ministryrequestid) + +class FOIRequestWatcherSchema(ma.Schema): + class Meta: + fields = ('watcherid', 'foirequestid', 'ministryrequestid', 'watchedbygroup','watchedby','isactive','created_at','createdby','updated_at','updatedby') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequests.py b/historical-search-api/request_api/models/FOIRequests.py new file mode 100644 index 000000000..fcd9ea81a --- /dev/null +++ b/historical-search-api/request_api/models/FOIRequests.py @@ -0,0 +1,126 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey +from .db import db, ma +from datetime import datetime +from sqlalchemy.orm import relationship,backref +from .default_method_result import DefaultMethodResult +from sqlalchemy.dialects.postgresql import JSON, UUID +from sqlalchemy.sql.expression import distinct +from sqlalchemy import text +import logging + +import json +class FOIRequest(db.Model): + # Name of the table in our database + __tablename__ = 'FOIRequests' + # Defining the columns + foirequestid = db.Column(db.Integer, primary_key=True,autoincrement=True) + version = db.Column(db.Integer, primary_key=True,nullable=False) + requesttype = db.Column(db.String(15), unique=False, nullable=False) + receiveddate = db.Column(db.DateTime, default=datetime.now) + isactive = db.Column(db.Boolean, unique=False, nullable=False,default=True) + + initialdescription = db.Column(db.String(500), unique=False, nullable=True) + initialrecordsearchfromdate = db.Column(db.DateTime, nullable=True) + initialrecordsearchtodate = db.Column(db.DateTime, nullable=True) + + created_at = db.Column(db.DateTime, default=datetime.now) + updated_at = db.Column(db.DateTime, nullable=True) + createdby = db.Column(db.String(120), unique=False, nullable=True) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + wfinstanceid = db.Column(UUID(as_uuid=True), unique=False, nullable=True) + + #ForeignKey References + + applicantcategoryid = db.Column(db.Integer,ForeignKey('ApplicantCategories.applicantcategoryid')) + applicantcategory = relationship("ApplicantCategory",backref=backref("ApplicantCategories"),uselist=False) + + deliverymodeid = db.Column(db.Integer,ForeignKey('DeliveryModes.deliverymodeid')) + deliverymode = relationship("DeliveryMode",backref=backref("DeliveryModes"),uselist=False) + + receivedmodeid = db.Column(db.Integer,ForeignKey('ReceivedModes.receivedmodeid')) + receivedmode = relationship("ReceivedMode",backref=backref("ReceivedModes"),uselist=False) + + foirawrequestid = db.Column(db.Integer,unique=False, nullable=True) + + ministryRequests = relationship('FOIMinistryRequest', primaryjoin="and_(FOIRequest.foirequestid==FOIMinistryRequest.foirequest_id, " + "FOIRequest.version==FOIMinistryRequest.foirequestversion_id)") + + contactInformations = relationship('FOIRequestContactInformation', primaryjoin="and_(FOIRequest.foirequestid==FOIRequestContactInformation.foirequest_id, " + "FOIRequest.version==FOIRequestContactInformation.foirequestversion_id)") + + personalAttributes = relationship('FOIRequestPersonalAttribute', primaryjoin="and_(FOIRequest.foirequestid==FOIRequestPersonalAttribute.foirequest_id, " + "FOIRequest.version==FOIRequestPersonalAttribute.foirequestversion_id)") + + requestApplicants = relationship('FOIRequestApplicantMapping', primaryjoin="and_(FOIRequest.foirequestid==FOIRequestApplicantMapping.foirequest_id, " + "FOIRequest.version==FOIRequestApplicantMapping.foirequestversion_id)") + + + + + + @classmethod + def getrequest(cls,foirequestid): + request_schema = FOIRequestsSchema() + query = db.session.query(FOIRequest).filter_by(foirequestid=foirequestid).order_by(FOIRequest.version.desc()).first() + return request_schema.dump(query) + + @classmethod + def saverequest(cls,foirequest)->DefaultMethodResult: + db.session.add(foirequest) + db.session.commit() + ministryarr = [] + for ministry in foirequest.ministryRequests: + assignedministrygroup = ministry.assignedministrygroup if ministry.assignedministrygroup is not None else "" + assignedgroup = ministry.assignedgroup if ministry.assignedgroup is not None else "" + ministryarr.append({"id": ministry.foiministryrequestid, "foirequestid": ministry.foirequest_id, "axisrequestid": ministry.axisrequestid, "filenumber": ministry.filenumber, "status": ministry.requeststatus.name, "assignedministrygroup": assignedministrygroup, "assignedgroup": assignedgroup, "version":ministry.version}) + return DefaultMethodResult(True,'Request added',foirequest.foirequestid,ministryarr,foirequest.wfinstanceid) + + @classmethod + def updateWFInstance(cls, foirequestid, wfinstanceid, userid)->DefaultMethodResult: + if wfinstanceid not in (None, ""): + currequest = db.session.query(FOIRequest).filter_by(foirequestid=foirequestid).order_by(FOIRequest.version.desc()).first() + setattr(currequest,'wfinstanceid',wfinstanceid) + setattr(currequest,'updated_at',datetime.now().isoformat()) + setattr(currequest,'updatedby',userid) + db.session.commit() + return DefaultMethodResult(True,'Request updated',foirequestid) + return DefaultMethodResult(True,'wfinstanceid is None',foirequestid) + + @classmethod + def updateStatus(cls, foirequestid, updatedministries, userid)->DefaultMethodResult: + currequest = db.session.query(FOIRequest).filter_by(foirequestid=foirequestid).order_by(FOIRequest.version.desc()).first() + for ministry in currequest.ministryRequests: + for data in updatedministries: + if ministry.filenumber == data["filenumber"]: + ministry.requeststatusid = data["requeststatusid"] + ministry.updated_at = datetime.now().isoformat() + ministry.updatedby = userid + currequest.updated_at = datetime.now().isoformat() + currequest.updatedby = userid + db.session.commit() + return DefaultMethodResult(True,'Request updated',foirequestid) + + @classmethod + def getworkflowinstance(cls,requestid)->DefaultMethodResult: + request_schema = FOIRequestsSchema() + try: + sql = """select fr3.wfinstanceid, fr3.foirequestid from "FOIMinistryRequests" fr2, "FOIRequests" fr3 + where fr2.foirequest_id = fr3.foirequestid and fr2.foiministryrequestid=:requestid + order by fr3."version" desc limit 1""" + rs = db.session.execute(text(sql), {'requestid': requestid}) + for row in rs: + request_schema.__dict__.update({"wfinstanceid":row["wfinstanceid"] , "foirequestid": row["foirequestid"]}) + except Exception as ex: + logging.error(ex) + finally: + db.session.close() + return request_schema + +class FOIRequestsSchema(ma.Schema): + class Meta: + fields = ('foirequestid','version','foirawrequestid','requesttype','receiveddate','initialdescription', + 'initialrecordSearchFromDate','initialrecordsearchtodate','receivedmode.receivedmodeid', + 'deliverymode.deliverymodeid','receivedmode.name','deliverymode.name', + 'applicantcategory.applicantcategoryid','applicantcategory.name','wfinstanceid','ministryRequests') + \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRestrictedMinistryRequests.py b/historical-search-api/request_api/models/FOIRestrictedMinistryRequests.py new file mode 100644 index 000000000..2935b13b4 --- /dev/null +++ b/historical-search-api/request_api/models/FOIRestrictedMinistryRequests.py @@ -0,0 +1,56 @@ +from flask.app import Flask +from sqlalchemy.sql.schema import ForeignKey +from .db import db, ma +from datetime import datetime +from sqlalchemy.orm import relationship,backref +from .default_method_result import DefaultMethodResult +from sqlalchemy.dialects.postgresql import JSON, UUID +from sqlalchemy.sql.expression import distinct +from sqlalchemy import text, and_, func +import logging +import json + + +class FOIRestrictedMinistryRequest(db.Model): + # Name of the table in our database + __tablename__ = 'FOIRestrictedMinistryRequests' + # Defining the columns + restrictionid = db.Column(db.Integer, primary_key=True,autoincrement=True) + ministryrequestid =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.foiministryrequestid')) + version =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.version')) + type = db.Column(db.String(50), unique=False, nullable=False) + isrestricted = db.Column(db.Boolean, unique=False, nullable=False) + isactive = db.Column(db.Boolean, unique=False, nullable=False, default=True) + created_at = db.Column(db.DateTime, default=datetime.now) + createdby = db.Column(db.String(120), unique=False, nullable=False) + + @classmethod + def saverestrictedrequest(cls, ministryrequestid ,type,isrestricted,version, userid)->DefaultMethodResult: + restrictedrequest = FOIRestrictedMinistryRequest(ministryrequestid=ministryrequestid , version=version, type=type, isrestricted=isrestricted, isactive=True, createdby=userid) + db.session.add(restrictedrequest) + db.session.commit() + return DefaultMethodResult(True,'Restricted Request added') + + + @classmethod + def getrestricteddetails(cls,ministryrequestid ,type): + data_schema = FOIRestrictedMinistryRequestSchema() + request = db.session.query(FOIRestrictedMinistryRequest).filter_by(ministryrequestid=ministryrequestid,type=type,isactive=True).order_by(FOIRestrictedMinistryRequest.created_at.desc()).first() + return data_schema.dump(request) + + + @classmethod + def disablerestrictedrequests(cls, ministryrequestid, type, userid): + dbquery = db.session.query(FOIRestrictedMinistryRequest) + prevrestrictedrequestsbytype = dbquery.filter_by(ministryrequestid=ministryrequestid,type=type) + if(prevrestrictedrequestsbytype.count() > 0) : + prevrestrictedrequestsbytype.update({FOIRestrictedMinistryRequest.isactive:False}, synchronize_session = False) + db.session.commit() + return DefaultMethodResult(True,'Restricted Requests disabled',ministryrequestid) + else: + return DefaultMethodResult(True,'No Restricted Requests found',ministryrequestid) + + +class FOIRestrictedMinistryRequestSchema(ma.Schema): + class Meta: + fields = ('restrictionid', 'ministryrequestid', 'version', 'type','isrestricted','isactive','created_at','createdby') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIUsers.py b/historical-search-api/request_api/models/FOIUsers.py new file mode 100644 index 000000000..5faa2dfad --- /dev/null +++ b/historical-search-api/request_api/models/FOIUsers.py @@ -0,0 +1,57 @@ +from .db import db, ma +from .default_method_result import DefaultMethodResult +from sqlalchemy.orm import relationship,backref +from datetime import datetime +from sqlalchemy import text + +class FOIUser(db.Model): + __tablename__ = 'FOIUsers' + # Defining the columns + foiuserid = db.Column(db.Integer, primary_key=True,autoincrement=True) + username = db.Column(db.Text, unique=False, nullable=False) + preferred_username = db.Column(db.Text, unique=False, nullable=False) + firstname = db.Column(db.Text, unique=False, nullable=False) + lastname = db.Column(db.Text, unique=False, nullable=False) + email = db.Column(db.Text, unique=False, nullable=False) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + created_at = db.Column(db.DateTime, default=datetime.now) + updated_at = db.Column(db.DateTime, nullable=True) + createdby = db.Column(db.String(120), unique=False, default='System') + updatedby = db.Column(db.String(120), unique=False, nullable=True) + + @classmethod + def createuser(cls, foiuser): + db.session.add(foiuser) + db.session.commit() + return DefaultMethodResult(True,'User added',foiuser.preferred_username) + + + @classmethod + def saveuser(cls, foiuser): + try: + dbquery = db.session.query(FOIUser) + _user = dbquery.filter_by(username=foiuser.username) + if(_user.count() > 0): + _user.update({FOIUser.firstname: foiuser.firstname, + FOIUser.lastname: foiuser.lastname, + FOIUser.email: foiuser.email, + FOIUser.preferred_username: foiuser.preferred_username, + FOIUser.isactive:foiuser.isactive, + FOIUser.updated_at:datetime.now(), FOIUser.updatedby:"System"}, synchronize_session = False) + db.session.commit() + return DefaultMethodResult(True,'User updated for Id',FOIUser.preferred_username) + else: + cls.createuser(foiuser) + except: + db.session.rollback() + raise + + @classmethod + def getall(cls): + user_schema = UserSchema(many=True) + query = db.session.query(FOIUser).order_by(FOIUser.foiuserid.asc()).all() + return user_schema.dump(query) + +class UserSchema(ma.Schema): + class Meta: + fields = ('foiuserid','username','preferred_username','firstname','lastname','email','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FeeCode.py b/historical-search-api/request_api/models/FeeCode.py new file mode 100644 index 000000000..8a5dfaec1 --- /dev/null +++ b/historical-search-api/request_api/models/FeeCode.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +from datetime import date, datetime + +from sqlalchemy import ForeignKey + +from .db import db, ma + + +class FeeCode(db.Model): + __tablename__ = 'FeeCodes' + # Defining the columns + fee_code_id = db.Column(db.Integer, primary_key=True, autoincrement=True) + code = db.Column(db.String(10), nullable=False) + description = db.Column(db.String(100), unique=False, nullable=True) + start_date = db.Column(db.Date, default=date.today(), nullable=False) + end_date = db.Column(db.Date, default=None, nullable=True) + fee = db.Column(db.Float, nullable=False) + revenue_account_id = db.Column(db.Integer, ForeignKey('RevenueAccounts.revenue_account_id'), nullable=False) + + @classmethod + def get_fee(cls, code: str, + valid_date: date + ) -> FeeCode: + """Given a code and date, return the active fee code.""" + if not valid_date: + valid_date = date.today() + + query = cls.query.filter_by(code=code). \ + filter(FeeCode.start_date <= valid_date). \ + filter((FeeCode.end_date.is_(None)) | (FeeCode.end_date >= valid_date)) + + return query.one_or_none() + + @classmethod + def find_by_id(cls, identifier: int) -> FeeCode: + """Return by id.""" + return cls.query.get(identifier) + + +class FeeCodeSchema(ma.Schema): + class Meta: + model = FeeCode + exclude = [] diff --git a/historical-search-api/request_api/models/NotificationTypes.py b/historical-search-api/request_api/models/NotificationTypes.py new file mode 100644 index 000000000..71be7a7d3 --- /dev/null +++ b/historical-search-api/request_api/models/NotificationTypes.py @@ -0,0 +1,33 @@ +from .db import db, ma +from .default_method_result import DefaultMethodResult +from sqlalchemy.orm import relationship,backref +from datetime import datetime +from sqlalchemy import text + +class NotificationType(db.Model): + __tablename__ = 'NotificationTypes' + # Defining the columns + notificationtypeid = db.Column(db.Integer, primary_key=True,autoincrement=True) + name = db.Column(db.String(100), unique=False, nullable=False) + description = db.Column(db.String(255), unique=False, nullable=False) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + notificationtypelabel = db.Column(db.String(100), unique=True, nullable=False) + + @classmethod + def getnotificationtypes(cls): + type_schema = NotificationTypeSchema(many=True) + query = db.session.query(NotificationType).filter_by(isactive=True).all() + return type_schema.dump(query) + + # create a class method that returns the notification type id + @classmethod + def getnotificationtypeid(cls, notificationtype): + type_schema = NotificationTypeSchema(many=False) + query = db.session.query(NotificationType).filter_by(name=notificationtype, isactive=True).first() + return type_schema.dump(query) if query is not None else None + + +class NotificationTypeSchema(ma.Schema): + class Meta: + fields = ('notificationtypeid', 'name', 'description','isactive', 'notificationtypelabel') + diff --git a/historical-search-api/request_api/models/NotificationUserTypes.py b/historical-search-api/request_api/models/NotificationUserTypes.py new file mode 100644 index 000000000..c358ffbea --- /dev/null +++ b/historical-search-api/request_api/models/NotificationUserTypes.py @@ -0,0 +1,42 @@ +from .db import db, ma +from .default_method_result import DefaultMethodResult +from sqlalchemy.orm import relationship,backref +from datetime import datetime +from sqlalchemy import text +import json +f = open('common/notificationusertypes.json', encoding="utf8") +notificationusertypes_cache = json.load(f) + + +class NotificationUserType(db.Model): + __tablename__ = 'NotificationUserTypes' + # Defining the columns + notificationusertypeid = db.Column(db.Integer, primary_key=True,autoincrement=True) + name = db.Column(db.String(100), unique=False, nullable=False) + description = db.Column(db.String(255), unique=False, nullable=False) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + notificationusertypelabel = db.Column(db.String(100), unique=True, nullable=False) + + @classmethod + def getnotificationusertypes(cls): + usertype_schema = NotificationUserTypeSchema(many=True) + query = db.session.query(NotificationUserType).filter_by(isactive=True).all() + return usertype_schema.dump(query) + + # create a class method that returns the notification type id + @classmethod + def getnotificationusertypesid(cls, notificationusertype): + notificationusertypelabel = None + for usertype in notificationusertypes_cache: + if (notificationusertypes_cache[usertype]['name'] == notificationusertype) or (notificationusertypes_cache[usertype]['notificationusertypelabel'] == notificationusertype): + notificationusertypelabel = notificationusertypes_cache[usertype]['notificationusertypelabel'] + if notificationusertypelabel is None: + return None + type_schema = NotificationUserTypeSchema(many=False) + query = db.session.query(NotificationUserType).filter_by(notificationusertypelabel=notificationusertypelabel, isactive=True).first() + return type_schema.dump(query) if query is not None else None + + +class NotificationUserTypeSchema(ma.Schema): + class Meta: + fields = ('notificationusertypeid', 'notificationusertypelabel', 'name', 'description','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/OIPCInquiryOutcomes.py b/historical-search-api/request_api/models/OIPCInquiryOutcomes.py new file mode 100644 index 000000000..a613f0a7f --- /dev/null +++ b/historical-search-api/request_api/models/OIPCInquiryOutcomes.py @@ -0,0 +1,19 @@ +from .db import db, ma + +class OIPCInquiryOutcomes(db.Model): + __tablename__ = 'OIPCInquiryOutcomes' + # Defining the columns + inquiryoutcomeid = db.Column(db.Integer, primary_key=True,autoincrement=True) + name = db.Column(db.String(100), unique=False, nullable=False) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + + @classmethod + def getinquiryoutcomes(cls): + type_schema = InquiryOutcomeSchema(many=True) + query = db.session.query(OIPCInquiryOutcomes).filter_by(isactive=True).all() + return type_schema.dump(query) + + +class InquiryOutcomeSchema(ma.Schema): + class Meta: + fields = ('inquiryoutcomeid', 'name','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/OIPCOutcomes.py b/historical-search-api/request_api/models/OIPCOutcomes.py new file mode 100644 index 000000000..c891a88a8 --- /dev/null +++ b/historical-search-api/request_api/models/OIPCOutcomes.py @@ -0,0 +1,19 @@ +from .db import db, ma + +class OIPCOutcomes(db.Model): + __tablename__ = 'OIPCOutcomes' + # Defining the columns + outcomeid = db.Column(db.Integer, primary_key=True,autoincrement=True) + name = db.Column(db.String(100), unique=False, nullable=False) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + + @classmethod + def getoutcomes(cls): + type_schema = OutcomeSchema(many=True) + query = db.session.query(OIPCOutcomes).filter_by(isactive=True).all() + return type_schema.dump(query) + + +class OutcomeSchema(ma.Schema): + class Meta: + fields = ('outcomeid', 'name','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/OIPCReasons.py b/historical-search-api/request_api/models/OIPCReasons.py new file mode 100644 index 000000000..6eadc03b6 --- /dev/null +++ b/historical-search-api/request_api/models/OIPCReasons.py @@ -0,0 +1,20 @@ + +from .db import db, ma + +class OIPCReasons(db.Model): + __tablename__ = 'OIPCReasons' + # Defining the columns + reasonid = db.Column(db.Integer, primary_key=True,autoincrement=True) + name = db.Column(db.String(100), unique=False, nullable=False) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + + @classmethod + def getreasons(cls): + type_schema = ReasonSchema(many=True) + query = db.session.query(OIPCReasons).filter_by(isactive=True).all() + return type_schema.dump(query) + + +class ReasonSchema(ma.Schema): + class Meta: + fields = ('reasonid', 'name','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/OIPCReviewTypes.py b/historical-search-api/request_api/models/OIPCReviewTypes.py new file mode 100644 index 000000000..bb5317de8 --- /dev/null +++ b/historical-search-api/request_api/models/OIPCReviewTypes.py @@ -0,0 +1,19 @@ +from .db import db, ma + +class OIPCReviewTypes(db.Model): + __tablename__ = 'OIPCReviewTypes' + # Defining the columns + reviewtypeid = db.Column(db.Integer, primary_key=True,autoincrement=True) + name = db.Column(db.String(100), unique=False, nullable=False) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + + @classmethod + def getreviewtypes(cls): + type_schema = ReviewTypeSchema(many=True) + query = db.session.query(OIPCReviewTypes).filter_by(isactive=True).all() + return type_schema.dump(query) + + +class ReviewTypeSchema(ma.Schema): + class Meta: + fields = ('reviewtypeid', 'name','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/OIPCReviewTypesReasons.py b/historical-search-api/request_api/models/OIPCReviewTypesReasons.py new file mode 100644 index 000000000..32fe9334b --- /dev/null +++ b/historical-search-api/request_api/models/OIPCReviewTypesReasons.py @@ -0,0 +1,43 @@ +from .db import db, ma +from sqlalchemy.orm import relationship, backref +from sqlalchemy.sql.schema import ForeignKey +from sqlalchemy import text + +class OIPCReviewTypesReasons(db.Model): + __tablename__ = 'OIPCReviewTypesReasons' + # Defining the columns + reviewtypereasonid = db.Column(db.Integer, primary_key=True,autoincrement=True) + reviewtypeid = db.Column(db.Integer, ForeignKey('OIPCReviewTypes')) + relationship("OIPCReviewTypes", backref=backref("OIPCReviewTypes"), uselist=False) + reasonid = db.Column(db.Integer, ForeignKey('OIPCReasons')) + relationship("OIPCReasons", backref=backref("OIPCReasons"), uselist=False) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + + @classmethod + def getreviewtypeswithreasons(cls): + type_schema = ReviewTypeReasonSchema(many=True) + sql = ''' + SELECT types.reviewtypeid, reasons.reasonid, reasons.name as reason_name, types.name as type_name, typereason.isactive as reviewtypereason_isactive, reasons.isactive as reason_isactive, types.isactive as reviewtype_isactive FROM public."OIPCReviewTypesReasons" typereason + JOIN public."OIPCReasons" reasons + ON reasons.reasonid = typereason.reasonid + JOIN public."OIPCReviewTypes" types + ON types.reviewtypeid = typereason.reviewtypeid + ORDER BY reviewtypereasonid ASC + ''' + rs = db.session.execute(text(sql)) + reviewtypereasons = [] + for row in rs: + reviewtypereasons.append({ + "reviewtypeid": row["reviewtypeid"], + "reasonid": row["reasonid"], + "reason_name": row["reason_name"], + "type_name": row["type_name"], + "reviewtypereason_isactive": row["reviewtypereason_isactive"], + "reviewtype_isactive": row["reviewtype_isactive"], + "reason_isactive": row["reason_isactive"], + }) + return reviewtypereasons + +class ReviewTypeReasonSchema(ma.Schema): + class Meta: + fields = ('reviewtypereasonid', 'reviewtypeid', 'reasonid', 'isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/OIPCStatuses.py b/historical-search-api/request_api/models/OIPCStatuses.py new file mode 100644 index 000000000..dce07aac2 --- /dev/null +++ b/historical-search-api/request_api/models/OIPCStatuses.py @@ -0,0 +1,19 @@ +from .db import db, ma + +class OIPCStatuses(db.Model): + __tablename__ = 'OIPCStatuses' + # Defining the columns + statusid = db.Column(db.Integer, primary_key=True,autoincrement=True) + name = db.Column(db.String(100), unique=False, nullable=False) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + + @classmethod + def getstatuses(cls): + type_schema = StatusSchema(many=True) + query = db.session.query(OIPCStatuses).filter_by(isactive=True).all() + return type_schema.dump(query) + + +class StatusSchema(ma.Schema): + class Meta: + fields = ('statusid', 'name','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/OperatingTeams.py b/historical-search-api/request_api/models/OperatingTeams.py new file mode 100644 index 000000000..dbf1778c2 --- /dev/null +++ b/historical-search-api/request_api/models/OperatingTeams.py @@ -0,0 +1,47 @@ +from .db import db, ma +from .default_method_result import DefaultMethodResult +from sqlalchemy import text +import logging +class OperatingTeam(db.Model): + __tablename__ = 'OperatingTeams' + # Defining the columns + teamid = db.Column(db.Integer, primary_key=True,autoincrement=True) + name = db.Column(db.String(255), unique=False, nullable=False) + description = db.Column(db.String(500), unique=False, nullable=True) + type = db.Column(db.String(100), unique=False, nullable=True) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + + @classmethod + def getalloperatingteams(cls): + teams = [] + try: + sql = """select name, type from "OperatingTeams" ot where ot.isactive = true""" + rs = db.session.execute(text(sql)) + for row in rs: + teams.append({"name":row["name"], "type":row["type"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return teams + + @classmethod + def getteam(cls, team): + try: + sql = """select type, name from "OperatingTeams" ot + 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']} + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return None + + +class OperatingTeamSchema(ma.Schema): + class Meta: + fields = ('teamid', 'name', 'description','type','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/Payment.py b/historical-search-api/request_api/models/Payment.py new file mode 100644 index 000000000..e0b2610a2 --- /dev/null +++ b/historical-search-api/request_api/models/Payment.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +from datetime import date, datetime + +from sqlalchemy import ForeignKey + +from .db import db, ma + + +class Payment(db.Model): + __tablename__ = 'Payments' + # Defining the columns + payment_id = db.Column(db.Integer, primary_key=True, autoincrement=True) + fee_code_id = db.Column(db.Integer, ForeignKey('FeeCodes.fee_code_id'), nullable=False) + quantity = db.Column(db.Integer, nullable=False) + total = db.Column(db.Float, nullable=False) + status = db.Column(db.String, nullable=False) + request_id = db.Column(db.Integer, nullable=False) + created_on = db.Column(db.DateTime, default=datetime.now, nullable=False) + completed_on = db.Column(db.DateTime, default=None, nullable=True) + paybc_url = db.Column(db.String, nullable=True) + response_url = db.Column(db.String, nullable=True) + order_id = db.Column(db.String(50), nullable=True) + transaction_number = db.Column(db.String(50), nullable=True) + + @classmethod + def find_by_id(cls, identifier: int) -> Payment: + """Return by id.""" + return cls.query.get(identifier) + + @classmethod + def find_paid_transaction(cls, transaction_number: str) -> Payment: + """Return by transaction_number.""" + a = cls.query.filter_by(transaction_number=transaction_number, status='PAID').one_or_none() + return a + + @classmethod + def find_failed_transaction(cls, transaction_number: str) -> Payment: + """Return by transaction_number.""" + a = cls.query.filter(Payment.transaction_number == transaction_number, Payment.status != 'PAID').one_or_none() + return a + + @staticmethod + def commit(): + """Commit the session.""" + db.session.commit() + + def flush(self): + """Save and flush.""" + db.session.add(self) + db.session.flush() + return self + + +class PaymentSchema(ma.Schema): + class Meta: + model = Payment + exclude = [] diff --git a/historical-search-api/request_api/models/PersonalInformationAttributes.py b/historical-search-api/request_api/models/PersonalInformationAttributes.py new file mode 100644 index 000000000..0e6ccd936 --- /dev/null +++ b/historical-search-api/request_api/models/PersonalInformationAttributes.py @@ -0,0 +1,27 @@ +from .db import db, ma +from .default_method_result import DefaultMethodResult + + +class PersonalInformationAttribute(db.Model): + __tablename__ = 'PersonalInformationAttributes' + # Defining the columns + attributeid = db.Column(db.Integer, primary_key=True,autoincrement=True) + name = db.Column(db.String(100), unique=False, nullable=False) + description = db.Column(db.String(255), unique=False, nullable=True) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + + @classmethod + def getpersonalattributes(cls): + requestortype_schema = PersonalInformationAttributesSchema(many=True) + query = db.session.query(PersonalInformationAttribute).filter_by(isactive=True).all() + return requestortype_schema.dump(query) + + @classmethod + def getpersonalattribute(cls,attribname): + deliverymode_schema = PersonalInformationAttributesSchema() + query = db.session.query(PersonalInformationAttribute).filter_by(name=attribname).first() + return deliverymode_schema.dump(query) + +class PersonalInformationAttributesSchema(ma.Schema): + class Meta: + fields = ('attributeid', 'name', 'description','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/ProgramAreaDivisionStages.py b/historical-search-api/request_api/models/ProgramAreaDivisionStages.py new file mode 100644 index 000000000..d467edbda --- /dev/null +++ b/historical-search-api/request_api/models/ProgramAreaDivisionStages.py @@ -0,0 +1,23 @@ +from .db import db, ma +from .default_method_result import DefaultMethodResult +from datetime import datetime + +class ProgramAreaDivisionStage(db.Model): + __tablename__ = 'ProgramAreaDivisionStages' + # Defining the columns + stageid = db.Column(db.Integer, primary_key=True,autoincrement=True) + name = db.Column(db.String(500), unique=False, nullable=False) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + created_at = db.Column(db.DateTime, default=datetime.now) + createdby = db.Column(db.String(120), unique=False, default='System') + + @classmethod + def getprogramareadivisionstages(cls): + divisionstage_schema = DivisionStageSchema(many=True) + query = db.session.query(ProgramAreaDivisionStage).filter_by(isactive=True).all() + return divisionstage_schema.dump(query) + + +class DivisionStageSchema(ma.Schema): + class Meta: + fields = ('stageid', 'name','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/ProgramAreaDivisions.py b/historical-search-api/request_api/models/ProgramAreaDivisions.py new file mode 100644 index 000000000..16c535135 --- /dev/null +++ b/historical-search-api/request_api/models/ProgramAreaDivisions.py @@ -0,0 +1,125 @@ +from .db import db, ma +from datetime import datetime as datetime2 +from .default_method_result import DefaultMethodResult +from sqlalchemy.orm import relationship,backref +from datetime import datetime +from sqlalchemy import text,or_ + +class ProgramAreaDivision(db.Model): + __tablename__ = 'ProgramAreaDivisions' + # Defining the columns + divisionid = db.Column(db.Integer, primary_key=True,autoincrement=True) + programareaid = db.Column(db.Integer, db.ForeignKey('ProgramAreas.programareaid')) + 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) + updatedby = db.Column(db.String(120), unique=False, nullable=True) + + @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,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, + issection=programareadivision["issection"], + parentid=programareadivision["parentid"], + specifictopersonalrequests=programareadivision["specifictopersonalrequests"] + ) + db.session.add(newprogramareadivision) + db.session.commit() + return DefaultMethodResult(True,'Division added successfully',newprogramareadivision.divisionid) + + @classmethod + def disableprogramareadivision(cls, divisionid, userid): + dbquery = db.session.query(ProgramAreaDivision) + division = dbquery.filter_by(divisionid=divisionid) + if(division.count() > 0) : + division.update({ProgramAreaDivision.isactive:False,ProgramAreaDivision.updatedby:userid, ProgramAreaDivision.updated_at:datetime2.now()}, synchronize_session = False) + db.session.commit() + return DefaultMethodResult(True,'Division disabled successfully',divisionid) + else: + return DefaultMethodResult(True,'No Division found',divisionid) + + @classmethod + 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: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: + return DefaultMethodResult(True,'No Division found',divisionid) + + @classmethod + def getdivisionbynameandprogramarea(cls, programareadivision): + division_schema = ProgramAreaDivisionSchema(many=True) + dbquery = db.session.query(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','issection','parentid', 'specifictopersonalrequests') \ No newline at end of file diff --git a/historical-search-api/request_api/models/ProgramAreas.py b/historical-search-api/request_api/models/ProgramAreas.py new file mode 100644 index 000000000..be1179f42 --- /dev/null +++ b/historical-search-api/request_api/models/ProgramAreas.py @@ -0,0 +1,45 @@ +from .db import db, ma +from .default_method_result import DefaultMethodResult +from sqlalchemy import or_ + +class ProgramArea(db.Model): + __tablename__ = 'ProgramAreas' + # Defining the columns + programareaid = db.Column(db.Integer, primary_key=True,autoincrement=True) + name = db.Column(db.String(255), unique=False, nullable=False) + iaocode = db.Column(db.String(30), unique=False, nullable=True) + bcgovcode = db.Column(db.String(30), unique=False, nullable=True) + type = db.Column(db.String(100), unique=False, nullable=True) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + + @classmethod + def getprogramareas(cls): + programarea_schema = ProgramAreaSchema(many=True) + query = db.session.query(ProgramArea).filter_by(isactive=True).order_by(ProgramArea.iaocode.asc()).all() + return programarea_schema.dump(query) + + @classmethod + def getprogramareasforministryuser(cls, groups): + bcgovcodefilter = [] + for group in groups: + bcgovcodefilter.append(ProgramArea.bcgovcode == group.replace(' Ministry Team', '')) + + programarea_schema = ProgramAreaSchema(many=True) + query = db.session.query(ProgramArea).filter_by(isactive=True).filter(or_(*bcgovcodefilter)).order_by(ProgramArea.bcgovcode.asc()).all() + return programarea_schema.dump(query) + + @classmethod + def getprogramarea(cls,pgbcgovcode): + programarea_schema = ProgramAreaSchema() + query = db.session.query(ProgramArea).filter_by(bcgovcode=pgbcgovcode.upper()).first() + return programarea_schema.dump(query) + + @classmethod + def getprogramareabyiaocode(cls,iaocode): + programarea_schema = ProgramAreaSchema() + query = db.session.query(ProgramArea).filter_by(iaocode=iaocode.upper()).first() + return programarea_schema.dump(query) + +class ProgramAreaSchema(ma.Schema): + class Meta: + fields = ('programareaid', 'name', 'iaocode','bcgovcode','type','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/ReceivedModes.py b/historical-search-api/request_api/models/ReceivedModes.py new file mode 100644 index 000000000..ebf81304a --- /dev/null +++ b/historical-search-api/request_api/models/ReceivedModes.py @@ -0,0 +1,27 @@ +from .db import db, ma +from .default_method_result import DefaultMethodResult + + +class ReceivedMode(db.Model): + __tablename__ = 'ReceivedModes' + # Defining the columns + receivedmodeid = db.Column(db.Integer, primary_key=True,autoincrement=True) + name = db.Column(db.String(100), unique=False, nullable=False) + description = db.Column(db.String(255), unique=False, nullable=True) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + + @classmethod + def getreceivedmodes(cls): + receivedmode_schema = ReceivedModeSchema(many=True) + query = db.session.query(ReceivedMode).filter_by(isactive=True).all() + return receivedmode_schema.dump(query) + + @classmethod + def getreceivedmode(cls,receivedmode): + deliverymode_schema = ReceivedModeSchema() + query = db.session.query(ReceivedMode).filter_by(name=receivedmode).first() + return deliverymode_schema.dump(query) + +class ReceivedModeSchema(ma.Schema): + class Meta: + fields = ('receivedmodeid', 'name', 'description','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/RequestorType.py b/historical-search-api/request_api/models/RequestorType.py new file mode 100644 index 000000000..a891cd6b4 --- /dev/null +++ b/historical-search-api/request_api/models/RequestorType.py @@ -0,0 +1,27 @@ +from .db import db, ma +from .default_method_result import DefaultMethodResult + + +class RequestorType(db.Model): + __tablename__ = 'RequestorTypes' + # Defining the columns + requestortypeid = db.Column(db.Integer, primary_key=True,autoincrement=True) + name = db.Column(db.String(100), unique=False, nullable=False) + description = db.Column(db.String(255), unique=False, nullable=True) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + + @classmethod + def getrequestortypes(cls): + requestortype_schema = RequestorTypeSchema(many=True) + query = db.session.query(RequestorType).filter_by(isactive=True).all() + return requestortype_schema.dump(query) + + @classmethod + def getrequestortype(cls,type): + programarea_schema = RequestorTypeSchema() + query = db.session.query(RequestorType).filter_by(name=type).first() + return programarea_schema.dump(query) + +class RequestorTypeSchema(ma.Schema): + class Meta: + fields = ('requestortypeid', 'name', 'description','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/RevenueAccount.py b/historical-search-api/request_api/models/RevenueAccount.py new file mode 100644 index 000000000..e8cd4fa78 --- /dev/null +++ b/historical-search-api/request_api/models/RevenueAccount.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +from .db import db, ma + + +class RevenueAccount(db.Model): + __tablename__ = 'RevenueAccounts' + # Defining the columns + revenue_account_id = db.Column(db.Integer, primary_key=True, autoincrement=True) + client = db.Column(db.String(3), nullable=True) + responsibility_centre = db.Column(db.String(5), nullable=True) + service_line = db.Column(db.String(5), nullable=True) + stob = db.Column(db.String(4), nullable=True) + project_code = db.Column(db.String(7), nullable=True) + + @classmethod + def find_by_id(cls, identifier: int): + """Return by id.""" + return cls.query.get(identifier) + +class RevenueAccountSchema(ma.Schema): + class Meta: + model = RevenueAccount diff --git a/historical-search-api/request_api/models/SubjectCodes.py b/historical-search-api/request_api/models/SubjectCodes.py new file mode 100644 index 000000000..1549c44c1 --- /dev/null +++ b/historical-search-api/request_api/models/SubjectCodes.py @@ -0,0 +1,32 @@ +from .db import db, ma + +class SubjectCode(db.Model): + __tablename__ = 'SubjectCodes' + # Defining the columns + subjectcodeid = db.Column(db.Integer, primary_key=True,autoincrement=True) + name = db.Column(db.String(120), unique=False, nullable=False) + description = db.Column(db.String(255), unique=False, nullable=True) + isaxissubjectcode = db.Column(db.Boolean, unique=False, nullable=False) + isactive = db.Column(db.Boolean, unique=False, nullable=False) + + @classmethod + def getsubjectcodes(cls): + subjectcode_schema = SubjectCodeSchema(many=True) + query = db.session.query(SubjectCode).filter_by(isactive=True).order_by(SubjectCode.name.asc()).all() + return subjectcode_schema.dump(query) + + @classmethod + def getsubjectcodebyname(cls,subjectcode): + subjectcode_schema = SubjectCodeSchema() + query = db.session.query(SubjectCode).filter_by(name=subjectcode).first() + return subjectcode_schema.dump(query) + + @classmethod + def getsubjectcodebyid(cls,subjectcodeid): + subjectcode_schema = SubjectCodeSchema() + query = db.session.query(SubjectCode).filter_by(subjectcodeid=subjectcodeid).first() + return subjectcode_schema.dump(query) + +class SubjectCodeSchema(ma.Schema): + class Meta: + fields = ('subjectcodeid', 'name', 'description','isaxissubjectcode', 'isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/UnopenedReport.py b/historical-search-api/request_api/models/UnopenedReport.py new file mode 100644 index 000000000..72a08b8ee --- /dev/null +++ b/historical-search-api/request_api/models/UnopenedReport.py @@ -0,0 +1,23 @@ +from .db import db, ma +from .default_method_result import DefaultMethodResult +from sqlalchemy.orm import relationship,backref +from datetime import datetime +from sqlalchemy import text +from sqlalchemy.dialects.postgresql import JSON +import json + +class UnopenedReport(db.Model): + __tablename__ = 'UnopenedReport' + # Defining the columns + id = db.Column(db.Integer, primary_key=True,autoincrement=True) + rawrequestid = db.Column(db.Text, unique=False, nullable=False) + date = db.Column(db.Text, unique=False, nullable=False) + rank = db.Column(db.Text, unique=False, nullable=False) + potentialmatches = db.Column(JSON, unique=False, nullable=False) + + @classmethod + def bulkinsert(cls, rows): + for row in rows: + db.session.add(row) + db.session.commit() + return DefaultMethodResult(True,'Report Rows added',map(lambda row: row.rawrequestid, rows)) \ No newline at end of file diff --git a/historical-search-api/request_api/models/__init__.py b/historical-search-api/request_api/models/__init__.py new file mode 100644 index 000000000..0966b3ced --- /dev/null +++ b/historical-search-api/request_api/models/__init__.py @@ -0,0 +1,63 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This exports all of the models and schemas used by the application.""" +# noqa: I004 +# noqa: I001, I003, I004 +#from sbc_common_components.tracing.db_tracing import DBTracing # noqa: I001, I004 +#from sqlalchemy import event # noqa: I001 +#from sqlalchemy.engine import Engine # noqa: I001, I003, I004 + + +from .db import db, ma + +# from .FOIRawRequests import FOIRawRequest +# from .ProgramAreas import ProgramArea +# from .ApplicantCategories import ApplicantCategory +# from .DeliveryModes import DeliveryMode +# from .ReceivedModes import ReceivedMode + +# from .FOIRequests import FOIRequest +# from .FOIRequestStatus import FOIRequestStatus +# from .FOIMinistryRequests import FOIMinistryRequest + +# from .ContactTypes import ContactType +# from .FOIRequestContactInformation import FOIRequestContactInformation + +# from .RequestorType import RequestorType +# from .FOIRequestApplicants import FOIRequestApplicant +# from .FOIRequestApplicantMappings import FOIRequestApplicantMapping + +# from .PersonalInformationAttributes import PersonalInformationAttribute + +# from .FOIRequestPersonalAttributes import FOIRequestPersonalAttribute + +# from .RevenueAccount import RevenueAccount +# from .FeeCode import FeeCode +# from .Payment import Payment +# from .DocumentType import DocumentType +# from .DocumentTemplate import DocumentTemplate + +# from .FOIRequestExtensions import FOIRequestExtension +# from .FOIAssignees import FOIAssignee + +# from .ApplicationCorrespondenceTemplates import ApplicationCorrespondenceTemplate +# from .FOIApplicantCorrespondenceAttachments import FOIApplicantCorrespondenceAttachment + +# from .FOIRestrictedMinistryRequests import FOIRestrictedMinistryRequest + + +from .factRequestDetails import factRequestDetails + +#event.listen(Engine, 'before_cursor_execute', DBTracing.query_tracing) diff --git a/historical-search-api/request_api/models/db.py b/historical-search-api/request_api/models/db.py new file mode 100644 index 000000000..2f5d7cf30 --- /dev/null +++ b/historical-search-api/request_api/models/db.py @@ -0,0 +1,28 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Create SQLAlchenmy and Schema managers. + +These will get initialized by the application using the models +""" +from flask_marshmallow import Marshmallow +from flask_sqlalchemy import SQLAlchemy + + +# by convention in the Flask community these are lower case, +# whereas pylint wants them upper case +ma = Marshmallow() # pylint: disable=invalid-name +db = SQLAlchemy() # pylint: disable=invalid-name + + +#make_versioned(user_cls=None, plugins=[activity_plugin]) diff --git a/historical-search-api/request_api/models/default_method_result.py b/historical-search-api/request_api/models/default_method_result.py new file mode 100644 index 000000000..3f4caaad9 --- /dev/null +++ b/historical-search-api/request_api/models/default_method_result.py @@ -0,0 +1,10 @@ +# models\defaultMethodResult.py +class DefaultMethodResult(): + success: False + message: '' + identifier:'' + def __init__(self, success, message,identifier=None,*args): + self.success = success + self.message = message + self.identifier=identifier + self.args=args \ No newline at end of file diff --git a/historical-search-api/request_api/models/factRequestDetails.py b/historical-search-api/request_api/models/factRequestDetails.py new file mode 100644 index 000000000..7df15f2d1 --- /dev/null +++ b/historical-search-api/request_api/models/factRequestDetails.py @@ -0,0 +1,84 @@ +from .db import db, ma +from .default_method_result import DefaultMethodResult +from sqlalchemy import text +import logging + + +class factRequestDetails(db.Model): + __tablename__ = 'ApplicantCategories' + # Defining the columns + foirequestid = db.Column(db.Integer, primary_key=True,autoincrement=True) + + @classmethod + def getrequestbyid(cls, requestid): + request = {} + try: + sql = """select + rt.requesttypename, + rm.receivedmodename, + dm.deliverymodename, + rqt.requestertypename, + r.firstname, r.middlename, r.lastname, r.company, r.email, r.workphone1, r.workphone2, r.mobile, r.home, + r2.firstname as behalffirstname, r2.middlename as behalfmiddlename, r2.lastname as behalflastname, + rs.requeststatusname, + a.address1, a.address2, a.city, a.state, a.country, a.zipcode, + rd.description, rd.startdate, rd.closeddate, rd.receiveddate, rd.targetdate AS duedate, rd.originaltargetdate AS originalduedate, + rd.subject + --, rd.* + from public."factRequestDetails" rd + join public."dimRequestStatuses" rs on rs.requeststatusid = rd.requeststatusid + join public."factRequestRequesters" rr1 on rr1.requesterid = rd.requesterid and rr1.foirequestid = rd.foirequestid and rr1.activeflag = 'Y' + + join public."dimRequesters" r on rr1.requesterid = r.requesterid + left join public."factRequestRequesters" rr2 on rr2.requesterid = rd.onbehalfofrequesterid and rr2.foirequestid = rd.foirequestid and rr2.activeflag = 'Y' + left join public."dimRequesters" r2 on rr2.requesterid = r2.requesterid + LEFT JOIN "dimRequesterTypes" rqt ON rd.applicantcategoryid = rqt.requestertypeid + join public."dimReceivedModes" rm on rm.receivedmodeid = rd.receivedmodeid + join public."dimAddress" a on a.addressid = rd.shipaddressid + join public."dimRequestTypes" rt on rt.requesttypeid = rd.requesttypeid + join public."dimDeliveryModes" dm on dm.deliverymodeid = rd.deliverymodeid + where rd.visualrequestfilenumber = 'CFD-2023-30109' and rd.activeflag = 'Y'""" + rs = db.session.execute(text(sql), {'requestid': requestid}) + for row in rs: + request["firstName"] = row['firstname'] + request["lastName"] = row['lastname'] + request["middleName"] = row['middlename'] + request["businessName"] = row['company'] + request["category"] = row['requestertypename'] + request['additionalPersonalInfo'] = {} + request['additionalPersonalInfo']["anotherFirstName"] = row['behalffirstname'] + request['additionalPersonalInfo']["anotherLastName"] = row['behalflastname'] + request['additionalPersonalInfo']["anotherMiddleName"] = row['behalfmiddlename'] + request["email"] = row['email'] + request["phonePrimary"] = row['home'] + request["phoneSecondary"] = row['mobile'] + request["workPhonePrimary"] = row['workphone1'] + request["workPhoneSecondary"] = row['workphone2'] + request["address"] = row['address'] + request["addressSecondary"] = row['address2'] + request["city"] = row['city'] + request["country"] = row['country'] + request["postal"] = row['zipcode'] + request["province"] = row['state'] + request["description"] = row['description'] + request["subjectCode"] = row['subject'] + request["receivedDate"] = row['receiveddate'] + request["requestProcessStart"] = row['startdate'] + request["originalDueDate"] = row['originalduedate'] + request["dueDate"] = row['duedate'] + request["closedate"] = row['closeddate'] + request["requestType"] = row['requesttypename'] + request["receivedMode"] = row['receivedmodename'] + request["deliveryMode"] = row['deliverymodename'] + request["subjectCode"] = row['subject'] + # requestdetails["assignedToFirstName"] = row["assignedtofirstname"] + # requestdetails["assignedToLastName"] = row["assignedtolastname"] + # requestdetails["bcgovcode"] = row["bcgovcode"] + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return requestdetails + + \ No newline at end of file diff --git a/historical-search-api/request_api/models/masterdataimport/applicantcategories.sql b/historical-search-api/request_api/models/masterdataimport/applicantcategories.sql new file mode 100644 index 000000000..eee677d52 --- /dev/null +++ b/historical-search-api/request_api/models/masterdataimport/applicantcategories.sql @@ -0,0 +1,9 @@ +INSERT INTO public."ApplicantCategories"(name, description, isactive) VALUES ('Business', 'Business', True);commit; +INSERT INTO public."ApplicantCategories"(name, description, isactive) VALUES ('Individual', 'Individual', True);commit; +INSERT INTO public."ApplicantCategories"(name, description, isactive) VALUES ('Interest Group', 'Interest Group', True);commit; +INSERT INTO public."ApplicantCategories"(name, description, isactive) VALUES ('Law Firm', 'Law Firm', True);commit; +INSERT INTO public."ApplicantCategories"(name, description, isactive) VALUES ('Media', 'Media', True);commit; +INSERT INTO public."ApplicantCategories"(name, description, isactive) VALUES ('Political Party', 'Political Party', True);commit; +INSERT INTO public."ApplicantCategories"(name, description, isactive) VALUES ('Researcher', 'Researcher', True);commit; +INSERT INTO public."ApplicantCategories"(name, description, isactive) VALUES ('Other Governments', 'Other Governments', True);commit; +INSERT INTO public."ApplicantCategories"(name, description, isactive) VALUES ('Other Public Body', 'Other Public Body', True);commit; \ No newline at end of file diff --git a/historical-search-api/request_api/models/masterdataimport/deliverymodes.sql b/historical-search-api/request_api/models/masterdataimport/deliverymodes.sql new file mode 100644 index 000000000..c5b28c61a --- /dev/null +++ b/historical-search-api/request_api/models/masterdataimport/deliverymodes.sql @@ -0,0 +1,2 @@ +INSERT INTO public."DeliveryModes"(name, description, isactive) VALUES ('Secure File Transfer', 'Secure File Transfer', True);commit; +INSERT INTO public."DeliveryModes"(name, description, isactive) VALUES ('In Person Pick up', 'In Person Pick up', True);commit; \ No newline at end of file diff --git a/historical-search-api/request_api/models/masterdataimport/programareas.sql b/historical-search-api/request_api/models/masterdataimport/programareas.sql new file mode 100644 index 000000000..38c264910 --- /dev/null +++ b/historical-search-api/request_api/models/masterdataimport/programareas.sql @@ -0,0 +1,37 @@ + +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Advanced Education and Skills Training','BC GOV Ministry', True,'AEST','AED'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Agriculture and Food','BC GOV Ministry', True,'AGR','AGR'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Attorney General','BC GOV Ministry', True,'AG','MAG'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Children and Family Development','BC GOV Ministry', True,'MCF','CFD'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Citizens’ Services','BC GOV Ministry', True,'CITZ','CTZ'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Education and Childcare','BC GOV Ministry', True,'EDU','EDU'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Energy, Mines and Low Carbon Innovation','BC GOV Ministry', True,'EMLI','EML'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Environment and Climate Change Strategy','BC GOV Ministry', True,'ENV','MOE'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Finance','BC GOV Ministry', True,'FIN','FIN'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Forests','BC GOV Ministry', True,'FOR','FOR'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Health','BC GOV Ministry', True,'HLTH','HTH'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Indigenous Relations and Reconciliation','BC GOV Ministry', True,'IRR','IRR'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Declaration Act Secretariat','BC GOV Ministry', True,'DAS','DAS'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Jobs, Economic Recovery and Innovation','BC GOV Ministry', True,'JERI','JER'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Labour','BC GOV Ministry', True,'LBR','LBR'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Lands, Water and Resource Stewardship','BC GOV Ministry', True,'LWR','LWR'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Mental Health and Addictions','BC GOV Ministry', True,'MMHA','MHA'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Municipal Affairs','BC GOV Ministry', True,'MUNI','MMA'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Public Safety and Solicitor General','BC GOV Ministry', True,'PSSG','PSS'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Social Development and Poverty Reduction','BC GOV Ministry', True,'SDPR','MSD'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Tourism, Arts, Culture and Sport','BC GOV Ministry', True,'TACS','TAC'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Transportation and Infrastructure','BC GOV Ministry', True,'TRAN','TRA'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('BC Coroners Service','Other', True,'OCC','OCC'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('BC Public Service Agency','Other', True,'PSA','PSA'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Board Resourcing and Development Office','Other', True,'BRD','BRD'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Community Living BC','Other', True,'CLB','CLB'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Crown Agencies Secretariat','Other', True,'CAS','CAS'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Emergency Management BC','Other', True,'EMBC','EMB'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Environmental Assessment Office','Other', True,'EAO','EAO'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Government Communications and Public Engagement','Other', True,'GCPE','GCP'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Independent Investigations Office','Other', True,'IIO','IIO'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Office of the Premier','Other', True,'PREM','OOP'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Liquor Distribution Branch','Other', True,'LDB','LDB'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Transportation Investment Corporation','Other', True,'TIC','TIC'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Order of British Columbia Advisory Council','Other', True,'OBC','OBC'); +INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Medal of Good Citizenship Selection Committee','Other', True,'MGC','MGC'); diff --git a/historical-search-api/request_api/models/masterdataimport/receivedmodes.sql b/historical-search-api/request_api/models/masterdataimport/receivedmodes.sql new file mode 100644 index 000000000..59b4d2c48 --- /dev/null +++ b/historical-search-api/request_api/models/masterdataimport/receivedmodes.sql @@ -0,0 +1,4 @@ +INSERT INTO public."ReceivedModes"(name, description, isactive) VALUES ('Email', 'Email', True);commit; +INSERT INTO public."ReceivedModes"(name, description, isactive) VALUES ('Fax', 'Fax', True);commit; +INSERT INTO public."ReceivedModes"(name, description, isactive) VALUES ('Mail', 'Mail', True);commit; +INSERT INTO public."ReceivedModes"(name, description, isactive) VALUES ('Online Form', 'Online Form', True);commit; \ No newline at end of file diff --git a/historical-search-api/request_api/models/samplequeries/businessteamdata.sql b/historical-search-api/request_api/models/samplequeries/businessteamdata.sql new file mode 100644 index 000000000..29a754a25 --- /dev/null +++ b/historical-search-api/request_api/models/samplequeries/businessteamdata.sql @@ -0,0 +1,38 @@ +--get latest version of all ministry ids where assignedgroup = 'Business Team' +SELECT foiministryrequestid, version FROM +public."FOIMinistryRequests" JOIN ( + SELECT foiministryrequestid, MAX(version) as version + FROM public."FOIMinistryRequests" where assignedgroup = 'Business Team' + GROUP BY foiministryrequestid + ) max_version USING (foiministryrequestid, version); + + +select * from public."FOIMinistryRequests" where assignedgroup = 'Business Team' order by foiministryrequestid, version desc +select assignedgroup,* from public."FOIMinistryRequests" where foiministryrequestid in (5214,5215,5216,5255,5257,5258,5260) order by foiministryrequestid, version desc + + +select * from public."FOIRawRequestWatchers" where watchedbygroup = 'Business Team'; +select * from public."FOIRequestWatchers" where watchedbygroup = 'Business Team'; + + + +-- only the latest version will get updated. +UPDATE public."FOIMinistryRequests" ministryrequests SET assignedgroup = 'Central Team' FROM ( + SELECT foiministryrequestid, MAX(version) as version + FROM public."FOIMinistryRequests" where assignedgroup = 'Business Team' + GROUP BY foiministryrequestid + ) max_version WHERE ministryrequests.foiministryrequestid = max_version.foiministryrequestid and ministryrequests.version = max_version.version; + +UPDATE public."FOIRequestWatchers" SET watchedbygroup = 'Central Team' WHERE watchedbygroup = 'Business Team'; +UPDATE public."FOIRawRequestWatchers" SET watchedbygroup = 'Central Team' WHERE watchedbygroup = 'Business Team'; + + +--CAMUNDA DB --- +select * from act_ru_variable where text_ like '%Business%'; +select * from act_ru_authorization where group_id_ like '%Business%'; +select * from act_ru_identitylink where group_id_ like '%Business%'; + + +UPDATE act_ru_variable SET text_ = replace(text_,'Business','Central') where text_ like '%Business%'; +UPDATE act_ru_authorization SET group_id_ = 'Central Team' where group_id_ like '%Business%'; +UPDATE act_ru_identitylink SET group_id_ = 'Central Team' where group_id_ like '%Business%'; \ No newline at end of file diff --git a/historical-search-api/request_api/models/samplequeries/findcolumnvalue.sql b/historical-search-api/request_api/models/samplequeries/findcolumnvalue.sql new file mode 100644 index 000000000..f0fc29264 --- /dev/null +++ b/historical-search-api/request_api/models/samplequeries/findcolumnvalue.sql @@ -0,0 +1,21 @@ +CREATE OR REPLACE FUNCTION search_whole_db(_like_pattern text) + RETURNS TABLE(_tbl regclass, _ctid tid) AS +$func$ +BEGIN + FOR _tbl IN + SELECT c.oid::regclass + FROM pg_class c + JOIN pg_namespace n ON n.oid = relnamespace + WHERE c.relkind = 'r' -- only tables +-- AND n.nspname !~ '^(pg_|information_schema)' -- exclude system schemas + ORDER BY n.nspname, c.relname + LOOP + RETURN QUERY EXECUTE format( + 'SELECT $1, ctid FROM %s t WHERE t::text ~~ %L' + , _tbl, '%' || _like_pattern || '%') + USING _tbl; + END LOOP; +END +$func$ LANGUAGE plpgsql; + +SELECT distinct _tbl FROM search_whole_db('Business Team'); \ No newline at end of file diff --git a/historical-search-api/request_api/models/samplequeries/wfmigration.sql b/historical-search-api/request_api/models/samplequeries/wfmigration.sql new file mode 100644 index 000000000..299e60d92 --- /dev/null +++ b/historical-search-api/request_api/models/samplequeries/wfmigration.sql @@ -0,0 +1,406 @@ +/* +********************************************************************************************************************* +The purpose of this document is to capture the instructions to do the migration of workflow instances for Fee. + +Change Details: +New FOI Request Processes (related to Fee) +1. FOI Fee Processing +2. FOI Email Processing +Modified FOI Request Processes (related to Fee) +1. FOI Request Processing (Key: foi-request-processing) + +********************************************************************************************************************** +Below given instructions are for migrating foi-request-processing to latest deployed definition version +Note#1: The approach that would be taken for migration is incremental (semi-automation) with manual verification between the steps. +Note#2: The below given instructions to be followed after deploying the WF +********************************************************************************************************************** +*/ + +/* +Step-0 (Login & Navigate to Processes): Login to Camunda application as admin - > Navigate to Cockpit -> Navigate to Processes +*/ + +/* +Step-1 (Identify the latest deployed version - FOI Raw Request Processing) : Click on Process "FOI Raw Request Processing" and get the latest version. +*/ + +/* +Step-2 Migrate Variables from version X to Y +*/ + +do $$ +declare + process_def_key varchar(25) := 'foi-request'; + ru_variable_counter integer := 0; + X_process_definition_id varchar(64) := ''; + Y_process_definition_id varchar(64) := ''; + X integer := 0; + Y integer := 0; + +begin + select count(proc_def_id_) into ru_variable_counter from act_ru_variable where proc_def_id_ = (select id_ from act_re_procdef where key_ = process_def_key and version_ = X); + raise notice'act_ru_variable has % proc_def_id_s found', ru_variable_counter; + + select id_ into X_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = X; + select id_ into Y_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = Y; + raise notice'The X_process_definition_id is %, The Y_process_definition_id is %', X_process_definition_id, Y_process_definition_id; + + if X_process_definition_id is NOT NULL and Y_process_definition_id is NOT NULL and ru_variable_counter > 0 then + + raise notice'inside if: act_ru_variable has % proc_def_id_s found', ru_variable_counter; + + update act_ru_variable set proc_def_id_ = Y_process_definition_id where proc_def_id_ = X_process_definition_id; + + end if; +end $$; +/* +Step-3 Migrate Execution instances from version X to Y +*/ +do $$ +declare + process_def_key varchar(25) := 'foi-request'; + ru_execution_counter integer := 0; + X_process_definition_id varchar(64) := ''; + Y_process_definition_id varchar(64) := ''; + X integer := 0; + Y integer := 0; + +begin + select count(proc_def_id_) into ru_execution_counter from act_ru_execution where proc_def_id_ = (select id_ from act_re_procdef where key_ = process_def_key and version_ = X); + raise notice'act_ru_execution has % proc_def_id_s found', ru_execution_counter; + + select id_ into X_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = X; + select id_ into Y_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = Y; + raise notice'The X_process_definition_id is %, The Y_process_definition_id is %', X_process_definition_id, Y_process_definition_id; + + if X_process_definition_id is NOT NULL and Y_process_definition_id is NOT NULL and ru_execution_counter > 0 then + + raise notice'inside if: act_ru_execution has % proc_def_id_s found', ru_execution_counter; + + update act_ru_execution set proc_def_id_ = Y_process_definition_id where proc_def_id_ = X_process_definition_id; + + end if; +end $$; + +/* +Step-4 Migrate Jobs from version X to Y +*/ + +do $$ +declare + process_def_key varchar(25) := 'foi-request'; + ru_job_counter integer := 0; + X_process_definition_id varchar(64) := ''; + Y_process_definition_id varchar(64) := ''; + X integer := 0; + Y integer := 0; + +begin + select count(process_def_id_) into ru_job_counter from act_ru_job where process_def_id_ = (select id_ from act_re_procdef where key_ = process_def_key and version_ = X); + raise notice'act_ru_job has % proc_def_id_s found', ru_job_counter; + + select id_ into X_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = X; + select id_ into Y_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = Y; + raise notice'The X_process_definition_id is %, The Y_process_definition_id is %', X_process_definition_id, Y_process_definition_id; + + if X_process_definition_id is NOT NULL and Y_process_definition_id is NOT NULL and ru_job_counter > 0 then + + raise notice'inside if: act_ru_job has % proc_def_id_s found', ru_job_counter; + + update act_ru_job set process_def_id_ = Y_process_definition_id where process_def_id_ = X_process_definition_id; + + end if; +end $$; + +/* +Step-5 Migrate Tasks from version X to Y +*/ + +do $$ +declare + process_def_key varchar(25) := 'foi-request'; + ru_task_counter integer := 0; + X_process_definition_id varchar(64) := ''; + Y_process_definition_id varchar(64) := ''; + X integer := 0; + Y integer := 0; + +begin + select count(proc_def_id_) into ru_task_counter from act_ru_task where proc_def_id_ = (select id_ from act_re_procdef where key_ = process_def_key and version_ = X); + raise notice'act_ru_task has % proc_def_id_s found', ru_task_counter; + + select id_ into X_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = X; + select id_ into Y_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = Y; + raise notice'The X_process_definition_id is %, The Y_process_definition_id is %', X_process_definition_id, Y_process_definition_id; + + if X_process_definition_id is NOT NULL and Y_process_definition_id is NOT NULL and ru_task_counter > 0 then + + raise notice'inside if: act_ru_task has % proc_def_id_s found', ru_task_counter; + + update act_ru_task set proc_def_id_ = Y_process_definition_id where proc_def_id_ = X_process_definition_id; + + end if; +end $$; + +/* +Step-1 (Identify the latest deployed version - FOI Request Processing) : Click on Process "FOI Request Processing" and get the latest version. +*/ + +/* +Step-2 Migrate Variables from version X to Y +*/ + +do $$ +declare + process_def_key varchar(25) := 'foi-request-processing'; + ru_variable_counter integer := 0; + X_process_definition_id varchar(64) := ''; + Y_process_definition_id varchar(64) := ''; + X integer := 0; + Y integer := 0; + +begin + select count(proc_def_id_) into ru_variable_counter from act_ru_variable where proc_def_id_ = (select id_ from act_re_procdef where key_ = process_def_key and version_ = X); + raise notice'act_ru_variable has % proc_def_id_s found', ru_variable_counter; + + select id_ into X_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = X; + select id_ into Y_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = Y; + raise notice'The X_process_definition_id is %, The Y_process_definition_id is %', X_process_definition_id, Y_process_definition_id; + + if X_process_definition_id is NOT NULL and Y_process_definition_id is NOT NULL and ru_variable_counter > 0 then + + raise notice'inside if: act_ru_variable has % proc_def_id_s found', ru_variable_counter; + + update act_ru_variable set proc_def_id_ = Y_process_definition_id where proc_def_id_ = X_process_definition_id; + + end if; +end $$; +/* +Step-3 Migrate Execution instances from version X to Y +*/ +do $$ +declare + process_def_key varchar(25) := 'foi-request-processing'; + ru_execution_counter integer := 0; + X_process_definition_id varchar(64) := ''; + Y_process_definition_id varchar(64) := ''; + X integer := 0; + Y integer := 0; + +begin + select count(proc_def_id_) into ru_execution_counter from act_ru_execution where proc_def_id_ = (select id_ from act_re_procdef where key_ = process_def_key and version_ = X); + raise notice'act_ru_execution has % proc_def_id_s found', ru_execution_counter; + + select id_ into X_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = X; + select id_ into Y_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = Y; + raise notice'The X_process_definition_id is %, The Y_process_definition_id is %', X_process_definition_id, Y_process_definition_id; + + if X_process_definition_id is NOT NULL and Y_process_definition_id is NOT NULL and ru_execution_counter > 0 then + + raise notice'inside if: act_ru_execution has % proc_def_id_s found', ru_execution_counter; + + update act_ru_execution set proc_def_id_ = Y_process_definition_id where proc_def_id_ = X_process_definition_id; + + end if; +end $$; + +/* +Step-4 Migrate Jobs from version X to Y +*/ + +do $$ +declare + process_def_key varchar(25) := 'foi-request-processing'; + ru_job_counter integer := 0; + X_process_definition_id varchar(64) := ''; + Y_process_definition_id varchar(64) := ''; + X integer := 0; + Y integer := 0; + +begin + select count(process_def_id_) into ru_job_counter from act_ru_job where process_def_id_ = (select id_ from act_re_procdef where key_ = process_def_key and version_ = X); + raise notice'act_ru_job has % proc_def_id_s found', ru_job_counter; + + select id_ into X_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = X; + select id_ into Y_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = Y; + raise notice'The X_process_definition_id is %, The Y_process_definition_id is %', X_process_definition_id, Y_process_definition_id; + + if X_process_definition_id is NOT NULL and Y_process_definition_id is NOT NULL and ru_job_counter > 0 then + + raise notice'inside if: act_ru_job has % proc_def_id_s found', ru_job_counter; + + update act_ru_job set process_def_id_ = Y_process_definition_id where process_def_id_ = X_process_definition_id; + + end if; +end $$; + +/* +Step-5 Migrate Tasks from version X to Y +*/ + +do $$ +declare + process_def_key varchar(25) := 'foi-request-processing'; + ru_task_counter integer := 0; + X_process_definition_id varchar(64) := ''; + Y_process_definition_id varchar(64) := ''; + X integer := 0; + Y integer := 0; + +begin + select count(proc_def_id_) into ru_task_counter from act_ru_task where proc_def_id_ = (select id_ from act_re_procdef where key_ = process_def_key and version_ = X); + raise notice'act_ru_task has % proc_def_id_s found', ru_task_counter; + + select id_ into X_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = X; + select id_ into Y_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = Y; + raise notice'The X_process_definition_id is %, The Y_process_definition_id is %', X_process_definition_id, Y_process_definition_id; + + if X_process_definition_id is NOT NULL and Y_process_definition_id is NOT NULL and ru_task_counter > 0 then + + raise notice'inside if: act_ru_task has % proc_def_id_s found', ru_task_counter; + + update act_ru_task set proc_def_id_ = Y_process_definition_id where proc_def_id_ = X_process_definition_id; + + end if; +end $$; + +/* +Step-6[SPECIAL-INSTRUCTION] PURPOSE: Create entries for correspondence (Specific to Fee) +Note: This to be ran after completion of Step 2 to 5 i.e. migration of all instances +*/ +INSERT INTO public.act_ru_event_subscr (id_, rev_, event_type_, event_name_, execution_id_, +proc_inst_id_, activity_id_, configuration_, created_, tenant_id_) +select tmp1.proc_inst_id_, 1, 'message', 'foi-iao-correnspodence', ext.execution_id_, tmp1.proc_inst_id_, +'correnspodance', NULL, now(), null from +(select distinct proc_inst_id_ from act_ru_variable arv where name_='status' and text_ <> 'Open' +and proc_inst_id_ not in (select proc_inst_id_ from act_ru_event_subscr arv where event_name_='foi-iao-correnspodence')) as tmp1, +act_ru_task ext where tmp1.proc_inst_id_ = ext.proc_inst_id_ +and name_ like '%IAO'; +commit; + +/* +This needs to be run only in the marshals. + */ +update public.act_ru_event_subscr set activity_id_ = 'Event_1tvpamu' where activity_id_ = 'complete'; + +/* +Step-1 (Identify the latest deployed version - FOI Fee Processing) : Click on Process "FOI Fee Processing" and get the latest version. +*/ + +/* +Step-2 Migrate Variables from version X to Y +*/ + +do $$ +declare + process_def_key varchar(25) := 'foi-fee-processing'; + ru_variable_counter integer := 0; + X_process_definition_id varchar(64) := ''; + Y_process_definition_id varchar(64) := ''; + X integer := 0; + Y integer := 0; + +begin + select count(proc_def_id_) into ru_variable_counter from act_ru_variable where proc_def_id_ = (select id_ from act_re_procdef where key_ = process_def_key and version_ = X); + raise notice'act_ru_variable has % proc_def_id_s found', ru_variable_counter; + + select id_ into X_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = X; + select id_ into Y_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = Y; + raise notice'The X_process_definition_id is %, The Y_process_definition_id is %', X_process_definition_id, Y_process_definition_id; + + if X_process_definition_id is NOT NULL and Y_process_definition_id is NOT NULL and ru_variable_counter > 0 then + + raise notice'inside if: act_ru_variable has % proc_def_id_s found', ru_variable_counter; + + update act_ru_variable set proc_def_id_ = Y_process_definition_id where proc_def_id_ = X_process_definition_id; + + end if; +end $$; +/* +Step-3 Migrate Execution instances from version X to Y +*/ +do $$ +declare + process_def_key varchar(25) := 'foi-fee-processing'; + ru_execution_counter integer := 0; + X_process_definition_id varchar(64) := ''; + Y_process_definition_id varchar(64) := ''; + X integer := 0; + Y integer := 0; + +begin + select count(proc_def_id_) into ru_execution_counter from act_ru_execution where proc_def_id_ = (select id_ from act_re_procdef where key_ = process_def_key and version_ = X); + raise notice'act_ru_execution has % proc_def_id_s found', ru_execution_counter; + + select id_ into X_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = X; + select id_ into Y_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = Y; + raise notice'The X_process_definition_id is %, The Y_process_definition_id is %', X_process_definition_id, Y_process_definition_id; + + if X_process_definition_id is NOT NULL and Y_process_definition_id is NOT NULL and ru_execution_counter > 0 then + + raise notice'inside if: act_ru_execution has % proc_def_id_s found', ru_execution_counter; + + update act_ru_execution set proc_def_id_ = Y_process_definition_id where proc_def_id_ = X_process_definition_id; + + end if; +end $$; + +/* +Step-4 Migrate Jobs from version X to Y +*/ + +do $$ +declare + process_def_key varchar(25) := 'foi-fee-processing'; + ru_job_counter integer := 0; + X_process_definition_id varchar(64) := ''; + Y_process_definition_id varchar(64) := ''; + X integer := 0; + Y integer := 0; + +begin + select count(process_def_id_) into ru_job_counter from act_ru_job where process_def_id_ = (select id_ from act_re_procdef where key_ = process_def_key and version_ = X); + raise notice'act_ru_job has % proc_def_id_s found', ru_job_counter; + + select id_ into X_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = X; + select id_ into Y_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = Y; + raise notice'The X_process_definition_id is %, The Y_process_definition_id is %', X_process_definition_id, Y_process_definition_id; + + if X_process_definition_id is NOT NULL and Y_process_definition_id is NOT NULL and ru_job_counter > 0 then + + raise notice'inside if: act_ru_job has % proc_def_id_s found', ru_job_counter; + + update act_ru_job set process_def_id_ = Y_process_definition_id where process_def_id_ = X_process_definition_id; + + end if; +end $$; + +/* +Step-5 Migrate Tasks from version X to Y +*/ + +do $$ +declare + process_def_key varchar(25) := 'foi-fee-processing'; + ru_task_counter integer := 0; + X_process_definition_id varchar(64) := ''; + Y_process_definition_id varchar(64) := ''; + X integer := 0; + Y integer := 0; + +begin + select count(proc_def_id_) into ru_task_counter from act_ru_task where proc_def_id_ = (select id_ from act_re_procdef where key_ = process_def_key and version_ = X); + raise notice'act_ru_task has % proc_def_id_s found', ru_task_counter; + + select id_ into X_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = X; + select id_ into Y_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = Y; + raise notice'The X_process_definition_id is %, The Y_process_definition_id is %', X_process_definition_id, Y_process_definition_id; + + if X_process_definition_id is NOT NULL and Y_process_definition_id is NOT NULL and ru_task_counter > 0 then + + raise notice'inside if: act_ru_task has % proc_def_id_s found', ru_task_counter; + + update act_ru_task set proc_def_id_ = Y_process_definition_id where proc_def_id_ = X_process_definition_id; + + end if; +end $$; \ No newline at end of file diff --git a/historical-search-api/request_api/models/views/FOINotifications.py b/historical-search-api/request_api/models/views/FOINotifications.py new file mode 100644 index 000000000..dc14ffe7b --- /dev/null +++ b/historical-search-api/request_api/models/views/FOINotifications.py @@ -0,0 +1,21 @@ +from .db import db, ma +from sqlalchemy.dialects.postgresql import insert, base + +class FOINotifications(db.Model): + # Name of the table in our database + __tablename__ = 'v_FOINotifications' + # Defining the columns + id = db.Column(db.String(100), primary_key=True) + idnumber = db.Column(db.String(100)) + axisnumber = db.Column(db.String(100)) + notification = db.Column(db.Text) + notificationtypeid = db.Column(db.Integer) + userid = db.Column(db.String(500)) + createdby = db.Column(db.String(500)) + created_at = db.Column(db.DateTime) + createdatformatted = db.Column(db.DateTime) + userformatted = db.Column(db.Text) + creatorformatted = db.Column(db.Text) + notificationtype = db.Column(db.String(500)) + notificationtypelabel = db.Column(db.String(500)) + diff --git a/historical-search-api/request_api/models/views/FOIRawRequests.py b/historical-search-api/request_api/models/views/FOIRawRequests.py new file mode 100644 index 000000000..ac17d19c0 --- /dev/null +++ b/historical-search-api/request_api/models/views/FOIRawRequests.py @@ -0,0 +1,23 @@ +from .db import db, ma +from sqlalchemy.dialects.postgresql import insert, base + +class FOIRawRequests(db.Model): + # Name of the table in our database + __tablename__ = 'v_FOIRawRequests' + # Defining the columns + rawrequestid = db.Column(db.Integer, primary_key=True) + version = db.Column(db.Integer) + axisrequestid = db.Column(db.String(500)) + foirequest_id = db.Column(db.Integer) + ministryrequestid = db.Column(db.Integer) + status = db.Column(db.String(100)) + assignedto = db.Column(db.String(500)) + assignedgroup = db.Column(db.String(500)) + assignedministryperson = db.Column(db.String(500)) + assignedministrygroup = db.Column(db.String(500)) + assignedtoformatted = db.Column(db.Text) + ministryassignedtoformatted = db.Column(db.String(500)) + description = db.Column(db.Text) + isiaorestricted = db.Column(db.Boolean) + crtid = db.Column(db.Text) + diff --git a/historical-search-api/request_api/models/views/FOIRequests.py b/historical-search-api/request_api/models/views/FOIRequests.py new file mode 100644 index 000000000..baa3395f3 --- /dev/null +++ b/historical-search-api/request_api/models/views/FOIRequests.py @@ -0,0 +1,24 @@ +from .db import db, ma +from sqlalchemy.dialects.postgresql import insert, base + +class FOIRequests(db.Model): + # Name of the table in our database + __tablename__ = 'v_FOIRequests' + # Defining the columns + foiministryrequestid = db.Column(db.Integer, primary_key=True) + version = db.Column(db.Integer) + foirequest_id = db.Column(db.Integer) + rawrequestid = db.Column(db.Integer) + axisrequestid = db.Column(db.String(500)) + requeststatusid = db.Column(db.Integer) + status = db.Column(db.String(100)) + assignedto = db.Column(db.String(500)) + assignedgroup = db.Column(db.String(500)) + assignedministryperson = db.Column(db.String(500)) + assignedministrygroup = db.Column(db.String(500)) + assignedtoformatted = db.Column(db.Text) + ministryassignedtoformatted = db.Column(db.String(500)) + description = db.Column(db.Text) + crtid = db.Column(db.Text) + requeststatuslabel = db.Column(db.String(50)) + diff --git a/historical-search-api/request_api/models/views/__init__.py b/historical-search-api/request_api/models/views/__init__.py new file mode 100644 index 000000000..aa1477907 --- /dev/null +++ b/historical-search-api/request_api/models/views/__init__.py @@ -0,0 +1,16 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This exports all of the views used by the application.""" + diff --git a/historical-search-api/request_api/models/views/db.py b/historical-search-api/request_api/models/views/db.py new file mode 100644 index 000000000..2f5d7cf30 --- /dev/null +++ b/historical-search-api/request_api/models/views/db.py @@ -0,0 +1,28 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Create SQLAlchenmy and Schema managers. + +These will get initialized by the application using the models +""" +from flask_marshmallow import Marshmallow +from flask_sqlalchemy import SQLAlchemy + + +# by convention in the Flask community these are lower case, +# whereas pylint wants them upper case +ma = Marshmallow() # pylint: disable=invalid-name +db = SQLAlchemy() # pylint: disable=invalid-name + + +#make_versioned(user_cls=None, plugins=[activity_plugin]) diff --git a/historical-search-api/request_api/resources/__init__.py b/historical-search-api/request_api/resources/__init__.py new file mode 100644 index 000000000..f262ef261 --- /dev/null +++ b/historical-search-api/request_api/resources/__init__.py @@ -0,0 +1,50 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Exposes all of the resource endpoints mounted in Flask-Blueprint style. + +Uses restplus namespaces to mount individual api endpoints into the service. + +All services have 2 defaults sets of endpoints: + - ops + - meta +That are used to expose operational health information about the service, and meta information. +""" + +from flask import Blueprint + +from .apihelper import Api + +from .meta import API as META_API +from .ops import API as OPS_API +from .request import API as REQUEST_API + +__all__ = ('API_BLUEPRINT') + +# This will add the Authorize button to the swagger docs +#AUTHORIZATIONS = {'apikey': {'type': 'apiKey', 'in': 'header', 'name': 'Authorization'}} + + +API_BLUEPRINT = Blueprint('API', __name__ ) + + +API = Api( + API_BLUEPRINT, + title='FOI Request API', + version='1.0', + description='The Core API for the FOI Request System', +) + + + +API.add_namespace(REQUEST_API ,path="/api") \ No newline at end of file diff --git a/historical-search-api/request_api/resources/apihelper.py b/historical-search-api/request_api/resources/apihelper.py new file mode 100644 index 000000000..73f4188e7 --- /dev/null +++ b/historical-search-api/request_api/resources/apihelper.py @@ -0,0 +1,29 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Meta information about the service. + +to support swagger on http +""" +from flask import url_for +from flask_restx import Api as BaseApi + + +class Api(BaseApi): + """Monkey patch Swagger API to return HTTPS URLs.""" + + @property + def specs_url(self): + """Return URL for endpoint.""" + scheme = 'http' if '5000' in self.base_url else 'https' + return url_for(self.endpoint('specs'), _external=True, _scheme=scheme) diff --git a/historical-search-api/request_api/resources/meta.py b/historical-search-api/request_api/resources/meta.py new file mode 100644 index 000000000..badc63cc8 --- /dev/null +++ b/historical-search-api/request_api/resources/meta.py @@ -0,0 +1,35 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Meta information about the service. + +Currently this only provides API versioning information +""" +from flask import jsonify +from flask_restx import Namespace, Resource + +from request_api.utils.run_version import get_run_version + + +API = Namespace('Meta', description='Metadata') + + +@API.route('/info') +class Info(Resource): + """Meta information about the overall service.""" + + @staticmethod + def get(): + """Return a JSON object with meta information about the Service.""" + version = get_run_version() + return jsonify(API=f'request_api/{version}') diff --git a/historical-search-api/request_api/resources/ops.py b/historical-search-api/request_api/resources/ops.py new file mode 100644 index 000000000..20ba02997 --- /dev/null +++ b/historical-search-api/request_api/resources/ops.py @@ -0,0 +1,43 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Endpoints to check and manage the health of the service.""" + +from flask_restx import Namespace, Resource +from flask import current_app + +API = Namespace('OPS', description='Service - OPS checks') + +@API.route('/healthz') +class Healthz(Resource): + """Determines if the service and required dependencies are still working. + This could be thought of as a heartbeat for the service. + """ + + @staticmethod + def get(): + """Made it here..so its all fine.""" + current_app.logger.info('Info Logging') + current_app.logger.error('Error Logging') + current_app.logger.debug('Debug Logging') + return {'message': 'api is healthy'}, 200 + + +@API.route('/readyz') +class Readyz(Resource): + """Determines if the service is ready to respond.""" + + @staticmethod + def get(): + """Return a JSON object that identifies if the service is setupAnd ready to work.""" + return {'message': 'api is ready'}, 200 \ No newline at end of file diff --git a/historical-search-api/request_api/resources/request.py b/historical-search-api/request_api/resources/request.py new file mode 100644 index 000000000..88fdc36da --- /dev/null +++ b/historical-search-api/request_api/resources/request.py @@ -0,0 +1,111 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""API endpoints for managing a FOI Requests resource.""" + + +from flask import g, request +from flask_restx import Namespace, Resource +from flask_expects_json import expects_json +from flask_cors import cross_origin +from request_api.auth import auth, AuthHelper +from request_api.tracer import Tracer +from request_api.utils.util import cors_preflight, allowedorigins,str_to_bool,canrestictdata +from request_api.exceptions import BusinessException +# from request_api.services.rawrequestservice import rawrequestservice +# from request_api.services.documentservice import documentservice +# from request_api.services.eventservice import eventservice +# from request_api.services.unopenedreportservice import unopenedreportservice +from request_api.services.historicalrequestservice import historicalrequestservice +# from request_api.utils.enums import StateName +import json +import asyncio +from jose import jwt as josejwt +import holidays +from datetime import datetime, timedelta +import os +import pytz + + + +API = Namespace('FOIRawRequests', description='Endpoints for FOI request management') +TRACER = Tracer.get_instance() +with open('request_api/schemas/schemas/rawrequest.json') as f: + schema = json.load(f) + +INVALID_REQUEST_ID = 'Invalid Request Id' + +SHORT_DATE_FORMAT = '%Y-%m-%d' +LONG_DATE_FORMAT = '%Y-%m-%d %H:%M:%S' + +@cors_preflight('GET,POST,OPTIONS') +@API.route('/foihistoricalrequest/') +class FOIRawRequest(Resource): + """Retrieve historical request details from EDW""" + + @staticmethod + @TRACER.trace() + @cross_origin(origins=allowedorigins()) + # @auth.require + def get(requestid): + try : + jsondata = {} + statuscode = 200 + # requestidisinteger = int(requestid) + # if requestidisinteger : + # baserequestinfo = rawrequestservice().getrawrequest(requestid) + + # assignee = baserequestinfo['assignedTo'] + # isiaorestricted = baserequestinfo['isiaorestricted'] + # # print('Request # {0} Assigned to {1} and is restricted {2} '.format(requestid,assignee,isiaorestricted)) + # if(isiaorestricted and canrestictdata(requestid,assignee,isiaorestricted,True)): + # jsondata = {'status': 401, 'message':'Restricted Request'} + # statuscode = 401 + # else: + # jsondata = json.dumps(baserequestinfo) + # statuscode = 200 + +# select +# rt.requesttypename, +# rm.receivedmodename, +# dm.deliverymodename, +# rqt.requestertypename, +# r.firstname, r.lastname, r.company, r.email, r.workphone1, r.workphone2, r.mobile, r.home, +# r2.firstname, r2.lastname, +# rs.requeststatusname, +# a.address1, a.address2, a.city, a.state, a.country, a.zipcode, +# rd.description, rd.startdate, rd.closeddate, rd.receiveddate, rd.targetdate AS duedate, +# rd.subject +# --, rd.* +# from public."factRequestDetails" rd +# join public."dimRequestStatuses" rs on rs.requeststatusid = rd.requeststatusid +# join public."factRequestRequesters" rr1 on rr1.requesterid = rd.requesterid and rr1.foirequestid = rd.foirequestid and rr1.activeflag = 'Y' + +# join public."dimRequesters" r on rr1.requesterid = r.requesterid +# left join public."factRequestRequesters" rr2 on rr2.requesterid = rd.onbehalfofrequesterid and rr2.foirequestid = rd.foirequestid and rr2.activeflag = 'Y' +# left join public."dimRequesters" r2 on rr2.requesterid = r2.requesterid +# LEFT JOIN "dimRequesterTypes" rqt ON rd.applicantcategoryid = rqt.requestertypeid +# join public."dimReceivedModes" rm on rm.receivedmodeid = rd.receivedmodeid +# join public."dimAddress" a on a.addressid = rd.shipaddressid +# join public."dimRequestTypes" rt on rt.requesttypeid = rd.requesttypeid +# join public."dimDeliveryModes" dm on dm.deliverymodeid = rd.deliverymodeid +# where rd.visualrequestfilenumber = 'CFD-2023-30109' and rd.activeflag = 'Y' + + + return jsondata , statuscode + except ValueError: + return {'status': 500, 'message':INVALID_REQUEST_ID}, 500 + except BusinessException as exception: + return {'status': exception.status_code, 'message':exception.message}, 500 + + \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/__init__.py b/historical-search-api/request_api/schemas/__init__.py new file mode 100644 index 000000000..d5f8ef784 --- /dev/null +++ b/historical-search-api/request_api/schemas/__init__.py @@ -0,0 +1,15 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Schema package.""" + diff --git a/historical-search-api/request_api/schemas/base_schema.py b/historical-search-api/request_api/schemas/base_schema.py new file mode 100644 index 000000000..2cda5ed47 --- /dev/null +++ b/historical-search-api/request_api/schemas/base_schema.py @@ -0,0 +1,47 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Super class to handle all operations related to base schema.""" + +from marshmallow import fields, post_dump + +from request_api.models import ma + + +class BaseSchema(ma.ModelSchema): # pylint: disable=too-many-ancestors + """Base Schema.""" + + created_by = fields.Function(lambda obj: '{} {}'.format(obj.created_by.firstname, obj.created_by.lastname) + if obj.created_by else None) + + modified_by = fields.Function(lambda obj: '{} {}'.format(obj.modified_by.firstname, obj.modified_by.lastname) + if obj.modified_by else None) + + @post_dump(pass_many=True) + def _remove_empty(self, data, many): # pylint: disable=no-self-use + """Remove all empty values and versions from the dumped dict.""" + if not many: + for key in list(data): + if key == 'versions': + data.pop(key) + + return { + key: value for key, value in data.items() + if value is not None + } + for item in data: + for key in list(item): + if (item[key] is None) or (key == 'versions'): + item.pop(key) + + return data diff --git a/historical-search-api/request_api/schemas/basecode_type.py b/historical-search-api/request_api/schemas/basecode_type.py new file mode 100644 index 000000000..d59b8822f --- /dev/null +++ b/historical-search-api/request_api/schemas/basecode_type.py @@ -0,0 +1,30 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Manager for base type schema and export.""" + +from marshmallow import fields + +from .base_schema import BaseSchema + + +class BaseCodeSchema(BaseSchema): # pylint: disable=too-many-ancestors, too-few-public-methods + """This is the schema for the BaseCode model.""" + + class Meta: # pylint: disable=too-few-public-methods + """Maps fields to a default schema.""" + + fields = ('code', 'description', 'default') + + # front end expects desc still + description = fields.String(data_key='desc') diff --git a/historical-search-api/request_api/schemas/external/bpmschema.py b/historical-search-api/request_api/schemas/external/bpmschema.py new file mode 100644 index 000000000..0698d0228 --- /dev/null +++ b/historical-search-api/request_api/schemas/external/bpmschema.py @@ -0,0 +1,35 @@ +from marshmallow import EXCLUDE, Schema, fields + +""" +This class consolidates schemas of bpm operations. + +__author__ = "sumathi.thirumani@aot-technologies.com" + +""" +class VariableSchema(Schema): + type = fields.Str() + value = fields.Str() + + +class MessageSchema(Schema): + + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + + messageName = fields.Str(data_key="messageName") + processInstanceId = fields.Str(data_key="processInstanceId") + processVariables = fields.Dict(keys=fields.String(),values=fields.Nested(VariableSchema)) + localCorrelationKeys = fields.Dict(keys=fields.String(),values=fields.Nested(VariableSchema)) + correlationKeys = fields.Dict(keys=fields.String(),values=fields.Nested(VariableSchema)) + +class VariableMessageSchema(Schema): + + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + + variables = fields.Dict(keys=fields.String(),values=fields.Nested(VariableSchema)) + \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/foiapplicantcorrespondencelog.py b/historical-search-api/request_api/schemas/foiapplicantcorrespondencelog.py new file mode 100644 index 000000000..56f444e06 --- /dev/null +++ b/historical-search-api/request_api/schemas/foiapplicantcorrespondencelog.py @@ -0,0 +1,28 @@ +from marshmallow import EXCLUDE, Schema, fields, validate +from request_api.utils.constants import MAX_EXCEPTION_MESSAGE + + +class AttachmentSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + filename = fields.Str(data_key="filename",required=True,allow_none=False, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + url = fields.Str(data_key="url",required=True,allow_none=False, validate=[validate.Length(max=1000, error=MAX_EXCEPTION_MESSAGE)]) + + +class FOIApplicantCorrespondenceSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + templateid = fields.Int(data_key="templateid",allow_none=True) + correspondencemessagejson = fields.Str(data_key="correspondencemessagejson",allow_none=False) + attachments = fields.Nested(AttachmentSchema, many=True, required=False,allow_none=True) + attributes = fields.List( + fields.Dict(fields.Str(), fields.Str()), + data_key="attributes", + required=False, + ) + + \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/foiassignee.py b/historical-search-api/request_api/schemas/foiassignee.py new file mode 100644 index 000000000..d83ed2722 --- /dev/null +++ b/historical-search-api/request_api/schemas/foiassignee.py @@ -0,0 +1,20 @@ +from marshmallow import EXCLUDE, Schema, fields, validate +from request_api.utils.constants import MAX_EXCEPTION_MESSAGE + +class FOIRequestAssigneeSchema(Schema): + + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + assignedministrygroup = fields.Str(data_key="assignedministrygroup",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + assignedministryperson = fields.Str(data_key="assignedministryperson",allow_none=True, validate=[validate.Length(max=120, 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)]) + assignedgroup = fields.Str(data_key="assignedGroup",allow_none=True, validate=[validate.Length(max=250, error=MAX_EXCEPTION_MESSAGE)]) + assignedto = fields.Str(data_key="assignedTo",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + assignedToFirstName = fields.Str(data_key="assignedToFirstName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) + assignedToMiddleName = fields.Str(data_key="assignedToMiddleName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) + assignedToLastName = fields.Str(data_key="assignedToLastName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) + \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/foiaxissync.py b/historical-search-api/request_api/schemas/foiaxissync.py new file mode 100644 index 000000000..94b4906e1 --- /dev/null +++ b/historical-search-api/request_api/schemas/foiaxissync.py @@ -0,0 +1,18 @@ +from marshmallow import EXCLUDE, Schema, fields, validate +from request_api.utils.constants import MAX_EXCEPTION_MESSAGE + +class FOIAxisProgramDetailsSchema(Schema): + + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + bcgovcode = fields.Str(data_key="bcgovcode",allow_none=False) + requesttype = fields.Str(data_key="requesttype",allow_none=False) + +class FOIRequestAxisSyncSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + data = fields.Nested(FOIAxisProgramDetailsSchema, many=True, validate=validate.Length(min=1), required=True,allow_none=False) diff --git a/historical-search-api/request_api/schemas/foicfrfee.py b/historical-search-api/request_api/schemas/foicfrfee.py new file mode 100644 index 000000000..19d2e2ba8 --- /dev/null +++ b/historical-search-api/request_api/schemas/foicfrfee.py @@ -0,0 +1,69 @@ + +from marshmallow import EXCLUDE, Schema, fields, validate + +""" +This class consolidates schemas of CFR Fee Form operations. + +__author__ = "aparna.s@aot-technologies.com" + +""" + + +class FOIFeeDataSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + amountpaid = fields.Float(data_key="amountpaid") + estimatedtotaldue = fields.Float(data_key="estimatedtotaldue") + actualtotaldue = fields.Float(data_key="actualtotaldue") + estimatepaymentmethod = fields.Str(data_key="estimatepaymentmethod", validate=validate.OneOf(['moneyorder', 'creditcardphone', 'creditcardonline', 'cheque', 'cash']), required=False) + balancepaymentmethod = fields.Str(data_key="balancepaymentmethod", validate=validate.OneOf(['moneyorder', 'creditcardphone', 'creditcardonline', 'cheque', 'cash']), required=False) + balanceremaining = fields.Float(data_key="balanceremaining") + feewaiveramount = fields.Float(data_key="feewaiveramount") + refundamount = fields.Float(data_key="refundamount") + estimatedlocatinghrs = fields.Float(data_key="estimatedlocatinghrs") + actuallocatinghrs = fields.Float(data_key="actuallocatinghrs") + estimatedproducinghrs = fields.Float(data_key="estimatedproducinghrs") + actualproducinghrs = fields.Float(data_key="actualproducinghrs") + estimatediaopreparinghrs = fields.Float(data_key="estimatediaopreparinghrs") + estimatedministrypreparinghrs = fields.Float(data_key="estimatedministrypreparinghrs") + actualiaopreparinghrs = fields.Float(data_key="actualiaopreparinghrs") + actualministrypreparinghrs = fields.Float(data_key="actualministrypreparinghrs") + estimatedelectronicpages = fields.Int(data_key="estimatedelectronicpages") + actualelectronicpages = fields.Int(data_key="actualelectronicpages") + estimatedhardcopypages = fields.Int(data_key="estimatedhardcopypages") + actualhardcopypages = fields.Int(data_key="actualhardcopypages") + + +class FOICFRFeeSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + feedata = fields.Nested(FOIFeeDataSchema,allow_none=False) + overallsuggestions = fields.Str(data_key="overallsuggestions") + status = fields.Str(data_key="status") + cfrfeeid = fields.Int(data_key="cfrfeeid",required=False, allow_none=True) + reason = fields.Str(data_key="reason") + +class FOIFeeDataSanctionSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + amountpaid = fields.Float(data_key="amountpaid") + estimatepaymentmethod = fields.Str(data_key="estimatepaymentmethod", validate=validate.OneOf(['moneyorder', 'creditcardphone', 'creditcardonline', 'cheque', 'cash']), required=False) + balancepaymentmethod = fields.Str(data_key="balancepaymentmethod", validate=validate.OneOf(['moneyorder', 'creditcardphone', 'creditcardonline', 'cheque', 'cash']), required=False) + estimatediaopreparinghrs = fields.Float(data_key="estimatediaopreparinghrs") + actualiaopreparinghrs = fields.Float(data_key="actualiaopreparinghrs") + estimatedtotaldue = fields.Float(data_key="estimatedtotaldue") + actualtotaldue = fields.Float(data_key="actualtotaldue") + balanceremaining = fields.Float(data_key="balanceremaining") + feewaiveramount = fields.Float(data_key="feewaiveramount") + refundamount = fields.Float(data_key="refundamount") + +class FOICFRFeeSanctionSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + feedata = fields.Nested(FOIFeeDataSanctionSchema,allow_none=False) + status = fields.Str(data_key="status", required=True) + reason = fields.Str(data_key="reason") \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/foicomment.py b/historical-search-api/request_api/schemas/foicomment.py new file mode 100644 index 000000000..361a900b9 --- /dev/null +++ b/historical-search-api/request_api/schemas/foicomment.py @@ -0,0 +1,50 @@ + + +from marshmallow import EXCLUDE, Schema, fields + +""" +This class consolidates schemas of watcher operations. + +__author__ = "sumathi.thirumani@aot-technologies.com" + +""" +class FOIRawRequestCommentSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + requestid = fields.Int(data_key="requestid") + comment = fields.Str(data_key="comment") + parentcommentid = fields.Int(data_key="parentcommentid",allow_none=True) + isactive = fields.Bool(data_key="isactive",allow_none=True) + taggedusers = fields.Str(data_key="taggedusers") + +class FOIMinistryRequestCommentSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + ministryrequestid = fields.Int(data_key="ministryrequestid") + comment = fields.Str(data_key="comment") + parentcommentid = fields.Int(data_key="parentcommentid",allow_none=True) + isactive = fields.Bool(data_key="isactive",allow_none=True) + taggedusers = fields.Str(data_key="taggedusers") + +class EditFOIRawRequestCommentSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + comment = fields.Str(data_key="comment") + taggedusers = fields.Str(data_key="taggedusers") + + + +class EditFOIMinistryRequestCommentSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + comment = fields.Str(data_key="comment") + taggedusers = fields.Str(data_key="taggedusers") + \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/foidocument.py b/historical-search-api/request_api/schemas/foidocument.py new file mode 100644 index 000000000..16400d16b --- /dev/null +++ b/historical-search-api/request_api/schemas/foidocument.py @@ -0,0 +1,50 @@ + + +from marshmallow import EXCLUDE, Schema, fields, validate +from request_api.utils.constants import MAX_EXCEPTION_MESSAGE + +""" +This class consolidates schemas of document operations. + +__author__ = "sumathi.thirumani@aot-technologies.com" + +""" +class RenameDocumentSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + 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.""" + + unknown = EXCLUDE + filename = fields.Str(data_key="filename",required=True,allow_none=False, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + documentpath = fields.Str(data_key="documentpath",required=True,allow_none=False, validate=[validate.Length(max=1000, error=MAX_EXCEPTION_MESSAGE)]) + category = fields.Str(data_key="category",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + + +class DocumentSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + filename = fields.Str(data_key="filename",required=True,allow_none=False, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + documentpath = fields.Str(data_key="documentpath",required=True,allow_none=False, validate=[validate.Length(max=1000, error=MAX_EXCEPTION_MESSAGE)]) + category = fields.Str(data_key="category",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + +class CreateDocumentSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + documents = fields.Nested(DocumentSchema, many=True, validate=validate.Length(min=1), required=True,allow_none=False) \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/foiemail.py b/historical-search-api/request_api/schemas/foiemail.py new file mode 100644 index 000000000..d03d51fe9 --- /dev/null +++ b/historical-search-api/request_api/schemas/foiemail.py @@ -0,0 +1,13 @@ + +from marshmallow import EXCLUDE, Schema, fields + +""" +This class consolidates schemas of email operations. +""" +class FOIEmailSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + templatename = fields.Str(data_key="templateName", allow_none=True) + applicantcorrespondenceid = fields.Int(data_key="applicantCorrespondenceId",allow_none=True) \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/foiextension.py b/historical-search-api/request_api/schemas/foiextension.py new file mode 100644 index 000000000..623cbfb08 --- /dev/null +++ b/historical-search-api/request_api/schemas/foiextension.py @@ -0,0 +1,40 @@ + + +from marshmallow import EXCLUDE, Schema, fields, validate +from request_api.utils.constants import MAX_EXCEPTION_MESSAGE + +""" +This class consolidates schemas of extension operations. + +__author__ = "divya.v@aot-technologies.com" + +""" + +class FOIMinistryRequestDocumentSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + foiministrydocumentid = fields.Int(data_key="foiministrydocumentid",required=False, allow_none=True) + documentpath = fields.Str(data_key="documentpath",allow_none=False, validate=[validate.Length(max=1000, error=MAX_EXCEPTION_MESSAGE)]) + filename = fields.Str(data_key="filename",allow_none=False, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + category = fields.Str(data_key="category",allow_none=False, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + +class FOIRequestExtensionSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + extensionreasonid = fields.Int(data_key="extensionreasonid") + extendedduedays = fields.Int(data_key="extendedduedays") + extensionstatusid = fields.Int(data_key="extensionstatusid") + extendedduedate = fields.Str(data_key="extendedduedate") + decisiondate = fields.Str(data_key="decisiondate",required=False, allow_none=True) + denieddate = fields.Str(data_key="denieddate",required=False, allow_none=True) + approveddate = fields.Str(data_key="approveddate",required=False, allow_none=True) + approvednoofdays = fields.Int(data_key="approvednoofdays", required=False, allow_none=True) + version = fields.Int(data_key="version") + foiministryrequest_id = fields.Int(data_key="foiministryrequest_id") + foiministryrequestversion_id = fields.Int(data_key="foiministryrequestversion_id") + isactive = fields.Bool(data_key="isactive",allow_none=True) + documents = fields.Nested(FOIMinistryRequestDocumentSchema, required=False, many=True, allow_none=True) \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/foipayment.py b/historical-search-api/request_api/schemas/foipayment.py new file mode 100644 index 000000000..3cc36318f --- /dev/null +++ b/historical-search-api/request_api/schemas/foipayment.py @@ -0,0 +1,19 @@ + + +from marshmallow import EXCLUDE, Schema, fields + +""" +This class consolidates schemas of payment operations. + +__author__ = "sumathi.thirumani@aot-technologies.com" + +""" +class FOIRequestPaymentSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + paymenturl = fields.Str(data_key="paymenturl") + paymentexpirydate = fields.Date(data_key="paymentexpirydate") + + diff --git a/historical-search-api/request_api/schemas/foiprogramareadivision.py b/historical-search-api/request_api/schemas/foiprogramareadivision.py new file mode 100644 index 000000000..659d3417f --- /dev/null +++ b/historical-search-api/request_api/schemas/foiprogramareadivision.py @@ -0,0 +1,14 @@ +from marshmallow import EXCLUDE, Schema, fields, validates_schema, ValidationError + +class FOIProgramAreaDivisionSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + programareaid = fields.Int(data_key="programareaid") + 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/historical-search-api/request_api/schemas/foirecord.py b/historical-search-api/request_api/schemas/foirecord.py new file mode 100644 index 000000000..56a739a2f --- /dev/null +++ b/historical-search-api/request_api/schemas/foirecord.py @@ -0,0 +1,166 @@ + + +from marshmallow import EXCLUDE, Schema, fields, validate +from request_api.utils.constants import MAX_EXCEPTION_MESSAGE + +""" +This class consolidates schemas of record operations. + +__author__ = "sumathi.thirumani@aot-technologies.com" + +""" +class DivisionSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + divisionid = fields.Int(data_key="divisionid",allow_none=False) + +class CreateRecordAttributeSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + #divisions = fields.List(fields.Int(),data_key="divisions",required=True) + divisions = fields.Nested(DivisionSchema, many=True, validate=validate.Length(min=1), required=True,allow_none=False) + lastmodified = fields.Str(data_key="lastmodified",allow_none=False, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + filesize = fields.Int(data_key="filesize", allow_none=False) + parentpdfmasterid = fields.Int(required=False,allow_none=True) + +class FOIRequestCreateRecordSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + attributes = fields.Nested(CreateRecordAttributeSchema) + s3uripath = fields.Str(data_key="s3uripath",allow_none=False, validate=[validate.Length(max=1000, error=MAX_EXCEPTION_MESSAGE)]) + filename = fields.Str(data_key="filename",allow_none=False, validate=[validate.Length(max=500, error=MAX_EXCEPTION_MESSAGE)]) + + + +class FOIRequestBulkCreateRecordSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + records = fields.Nested(FOIRequestCreateRecordSchema, many=True, validate=validate.Length(min=1), required=True,allow_none=False) + +class RetryRecordAttributeSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + #divisions = fields.List(fields.Int(),data_key="divisions",required=True) + divisions = fields.Nested(DivisionSchema, many=True, validate=validate.Length(min=1), required=True,allow_none=False) + lastmodified = fields.Str(data_key="lastmodified",allow_none=False, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + filesize = fields.Int(data_key="filesize", allow_none=False) + convertedfilesize = fields.Int(data_key="convertedfilesize", allow_none=True) + trigger = fields.Str(data_key="trigger", allow_none=True) + batch = fields.Str(data_key="batch", allow_none=False, validate=validate.Length(min=1), required=True) + incompatible = fields.Boolean(required=True,allow_none=False) + extension = fields.Str(validate=validate.Length(min=1, max=10), required=True,allow_none=False) + isattachment = fields.Boolean(required=False,allow_none=False) + +class ReplaceRecordAttributeSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + divisions = fields.Nested(DivisionSchema, many=True, validate=validate.Length(min=1), required=True,allow_none=False) + lastmodified = fields.Str(data_key="lastmodified",allow_none=False, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + filesize = fields.Int(data_key="filesize", allow_none=False) + batch = fields.Str(data_key="batch", allow_none=False, validate=validate.Length(min=1), required=True) + incompatible = fields.Boolean(required=True,allow_none=False) + extension = fields.Str(validate=validate.Length(min=1, max=10), required=True,allow_none=False) + isattachment = fields.Boolean(required=False,allow_none=False) + hasfilereplaced = fields.Boolean(required=False,allow_none=False) + +class FOIRequestRetryRecordSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + attributes = fields.Nested(RetryRecordAttributeSchema) + s3uripath = fields.Str(data_key="s3uripath",allow_none=False, validate=[validate.Length(max=1000, error=MAX_EXCEPTION_MESSAGE)]) + filename = fields.Str(data_key="filename",allow_none=False, validate=[validate.Length(max=500, error=MAX_EXCEPTION_MESSAGE)]) + trigger = fields.Str(validate=validate.OneOf(['recordreplace', 'recordretry']), required=True,allow_none=False) + service = fields.Str(validate=validate.OneOf(['deduplication', 'conversion',"all"]), required=False,allow_none=False) + documentmasterid = fields.Integer(required=True,allow_none=True) + outputdocumentmasterid = fields.Integer(required=False,allow_none=True) + createdby = fields.Str(validate=validate.Length(min=1),required=True,allow_none=False) + +class FOIRequestReplaceRecordSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + attributes = fields.Nested(ReplaceRecordAttributeSchema) + s3uripath = fields.Str(data_key="s3uripath",allow_none=False, validate=[validate.Length(max=1000, error=MAX_EXCEPTION_MESSAGE)]) + filename = fields.Str(data_key="filename",allow_none=False, validate=[validate.Length(max=500, error=MAX_EXCEPTION_MESSAGE)]) + replacements3uripath = fields.Str(data_key="replacements3uripath",allow_none=False, validate=[validate.Length(max=1000, error=MAX_EXCEPTION_MESSAGE)]) + replacementfilename = fields.Str(data_key="replacementfilename",allow_none=False, validate=[validate.Length(max=500, error=MAX_EXCEPTION_MESSAGE)]) + trigger = fields.Str(data_key="trigger",validate=validate.OneOf(['recordreplace', 'recordretry']),allow_none=False) + service = fields.Str(data_key="service",validate=validate.OneOf(['deduplication', 'conversion']),allow_none=False) + replacementof = fields.Str(data_key="replacementof",allow_none=True) + + +class FOIRequestBulkRetryRecordSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + records = fields.Nested(FOIRequestRetryRecordSchema, many=True, validate=validate.Length(min=1), required=True,allow_none=False) + + +class FileSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + recordid = fields.Int(data_key="recordid",allow_none=True) + filename = fields.Str(data_key="filename",allow_none=False, validate=[validate.Length(max=500, error=MAX_EXCEPTION_MESSAGE)]) + s3uripath = fields.Str(data_key="s3uripath",allow_none=False, validate=[validate.Length(max=1000, error=MAX_EXCEPTION_MESSAGE)]) + lastmodified = fields.Str(data_key="lastmodified",allow_none=False, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + filesize = fields.Number(data_key="filesize") + +class DownloadRecordAttributeSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + files = fields.Nested(FileSchema, many=True, validate=validate.Length(min=1), required=True,allow_none=False) + divisionname = fields.Str(data_key="divisionname",allow_none=False, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + divisionid = fields.Int(data_key="divisionid", allow_none=False) + divisionfilesize = fields.Number(data_key="divisionfilesize") + +class FOIRequestRecordDownloadSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + requestnumber = fields.Str(data_key="requestnumber",allow_none=False, validate=[validate.Length(max=100, error=MAX_EXCEPTION_MESSAGE)]) + bcgovcode = fields.Str(data_key="bcgovcode",allow_none=False, validate=[validate.Length(max=20, error=MAX_EXCEPTION_MESSAGE)]) + category = fields.Str(data_key="category",allow_none=False, validate=[validate.Length(max=25, error=MAX_EXCEPTION_MESSAGE)]) + attributes = fields.Nested(DownloadRecordAttributeSchema, many=True, validate=validate.Length(min=1), required=True,allow_none=False) + totalfilesize = fields.Number(data_key="totalfilesize") + +class RecordSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + recordid = fields.Int(data_key="recordid",allow_none=True) + documentmasterid = fields.Int(data_key="documentmasterid",allow_none=False) + filepath = fields.String(data_key="filepath",allow_none=False,validate=[validate.Length(max=1000, error=MAX_EXCEPTION_MESSAGE)]) + +class FOIRequestRecordUpdateSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + records = fields.Nested(RecordSchema,many=True,data_key="records",required=True) + divisions = fields.Nested(DivisionSchema,many=True,validate=validate.Length(min=1),required=False,allow_none=True) + isdelete = fields.Boolean(required=True,allow_none=False) + + + diff --git a/historical-search-api/request_api/schemas/foirequest.py b/historical-search-api/request_api/schemas/foirequest.py new file mode 100644 index 000000000..239f320db --- /dev/null +++ b/historical-search-api/request_api/schemas/foirequest.py @@ -0,0 +1,87 @@ + + +from marshmallow import EXCLUDE, Schema, fields, validate +from request_api.utils.constants import BLANK_EXCEPTION_MESSAGE, MAX_EXCEPTION_MESSAGE + +""" +This class consolidates schemas of bpm operations. + +__author__ = "sumathi.thirumani@aot-technologies.com" + +""" +class FOIMinistryRequestSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + filenumber = fields.Str(data_key="fileNumber", validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) + description = fields.Str(data_key="description") + recordSearchFromDate = fields.Date(data_key="recordSearchFromDate") + recordSearchToDate = fields.Date(data_key="recordSearchToDate") + startdate = fields.Date(data_key="startDate") + duedate = fields.Date(data_key="dueDate") + cfrduedate = fields.Date(data_key="cfrDueDate", required=False,allow_none=True) + assignedto = fields.Str(data_key="assignedTo", validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + programareaid = fields.Int(data_key="programAreaId") + +class FOIContactInformationSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + contacttypeid = fields.Int(data_key="contactTypeId") + contactinformation = fields.Str(data_key="contactInformation") + dataformat = fields.Str(data_key="dataFormat") + +class FOIPersonalAttributeSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + personalattributeid = fields.Int(data_key="personalAttributeId") + attributevalue = fields.Str(data_key="attributeValue") + +class FOIApplicantSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + firstname = fields.Str(data_key="firstName") + middlename = fields.Str(data_key="middleName") + lastname = fields.Str(data_key="lastName") + alsoknownas = fields.Str(data_key="alsoKnownAs") + dob = fields.Date(data_key="dob") + businessname = fields.Str(data_key="businessName") + +class FOIRequestApplicantSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + applicant = fields.Nested(FOIApplicantSchema, many=False) + requestortypeid = fields.Int(data_key="requestorTypeId") + +class FOIRequestTypeSchema(Schema): + requestType = fields.Str(data_key="requestType", validate=[validate.Length(max=10, error=MAX_EXCEPTION_MESSAGE)]) + +class FOIRequestSchema(Schema): + + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + version = fields.Int(data_key="version") + requestType = fields.Nested(FOIRequestTypeSchema) + initialrecordsearchfromdate = fields.DateTime(data_key="recordSearchFromDate") + initialrecordsearchtodate = fields.DateTime(data_key="recordSearchToDate") + deliverymodeid = fields.Int(data_key="deliveryModeId") + receivedmodeid = fields.Int(data_key="receivedModeId") + foirawrequestid = fields.Int(data_key="foiRawRequestId") + description = fields.Str(data_key="description") + receiveddate = fields.DateTime(data_key="receiveddate") + isactive=fields.Bool(data_key="isactive") + ministryRequests = fields.Nested(FOIMinistryRequestSchema, many=True) + contactInformations = fields.Nested(FOIContactInformationSchema, many=True) + personalAttributes = fields.Nested(FOIPersonalAttributeSchema, many=True) + requestApplicants = fields.Nested(FOIRequestApplicantSchema, many=True) + diff --git a/historical-search-api/request_api/schemas/foirequestsformslist.py b/historical-search-api/request_api/schemas/foirequestsformslist.py new file mode 100644 index 000000000..381eb07c4 --- /dev/null +++ b/historical-search-api/request_api/schemas/foirequestsformslist.py @@ -0,0 +1,15 @@ +from marshmallow import EXCLUDE, Schema, fields + + +class FOIRequestsFormsList(Schema): + class Meta: + unknown = EXCLUDE + + ministrycode = fields.Str(data_key="ministrycode",allow_none=False) + requestnumber = fields.Str(data_key="requestnumber",allow_none=False) + filestatustransition = fields.Str(data_key="filestatustransition",allow_none=False) + filename = fields.Str(data_key="filename",allow_none=False) + filepath = fields.Str(data_key="filepath",allow_none=True) + authheader = fields.Str(data_key="authheader",allow_none=True) + amzdate = fields.Str(data_key="amzdate",allow_none=True) + s3sourceuri = fields.Str(data_key="s3sourceuri",allow_none=True) \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/foirequestwrapper.py b/historical-search-api/request_api/schemas/foirequestwrapper.py new file mode 100644 index 000000000..5242708b1 --- /dev/null +++ b/historical-search-api/request_api/schemas/foirequestwrapper.py @@ -0,0 +1,215 @@ +from marshmallow import EXCLUDE, Schema, fields, validate +from request_api.utils.constants import BLANK_EXCEPTION_MESSAGE, MAX_EXCEPTION_MESSAGE + +""" +This class consolidates schemas of bpm operations. + +__author__ = "sumathi.thirumani@aot-technologies.com" + +""" +class FOIMinistryRequestWrapperSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + code = fields.Str(data_key="code", validate=[validate.Length(max=30, error=MAX_EXCEPTION_MESSAGE)]) + name = fields.Str(data_key="name", validate=[validate.Length(max=255, error=MAX_EXCEPTION_MESSAGE)]) + isSelected = fields.Bool(data_key="isSelected") + +class FOIAdditionallPersonalInfoWrapperSchema(Schema): + + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + childFirstName = fields.Str(data_key="childFirstName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) + childMiddleName = fields.Str(data_key="childMiddleName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) + childLastName = fields.Str(data_key="childLastName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) + childAlsoKnownAs = fields.Str(data_key="childAlsoKnownAs",allow_none=True) + childBirthDate = fields.Str(data_key="childBirthDate",allow_none=True) + + anotherFirstName = fields.Str(data_key="anotherFirstName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) + anotherMiddleName = fields.Str(data_key="anotherMiddleName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) + anotherLastName = fields.Str(data_key="anotherLastName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) + anotherAlsoKnownAs = fields.Str(data_key="anotherAlsoKnownAs",allow_none=True) + anotherBirthDate = fields.Str(data_key="anotherBirthDate",allow_none=True) + + adoptiveMotherFirstName = fields.Str(data_key="adoptiveMotherFirstName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) + adoptiveMotherLastName = fields.Str(data_key="adoptiveMotherLastName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) + adoptiveFatherFirstName = fields.Str(data_key="adoptiveFatherFirstName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) + 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)]) + + birthDate = fields.Str(data_key="birthDate",allow_none=True) + alsoKnownAs = fields.Str(data_key="alsoKnownAs",allow_none=True) + +class FOIMinistryRequestDocumentSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + documentpath = fields.Str(data_key="documentpath",allow_none=False, validate=[validate.Length(max=1000, error=MAX_EXCEPTION_MESSAGE)]) + filename = fields.Str(data_key="filename",allow_none=False, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + category = fields.Str(data_key="category",allow_none=False, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + +class FOIOIPCInquirySchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + inquirydate = fields.Str(data_key="inquirydate",allow_none=True) + orderno = fields.Str(data_key="orderno",allow_none=True) + inquiryoutcome = fields.Int(data_key="inquiryoutcome",allow_none=True) + +class FOIMinistryRequestOIPCSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + oipcno = fields.Str(data_key="oipcno") + reviewtypeid = fields.Int(data_key="reviewtypeid") + reasonid = fields.Int(data_key="reasonid") + statusid = fields.Int(data_key="statusid") + outcomeid = fields.Int(data_key="outcomeid",allow_none=True) + investigator = fields.Str(data_key="investigator",allow_none=True, validate=[validate.Length(max=500, error=MAX_EXCEPTION_MESSAGE)]) + isinquiry = fields.Bool(data_key="isinquiry") + isjudicialreview = fields.Bool(data_key="isjudicialreview") + issubsequentappeal = fields.Bool(data_key="issubsequentappeal") + inquiryattributes = fields.Nested(FOIOIPCInquirySchema, data_key="inquiryattributes", allow_none=True) + receiveddate = fields.Str(data_key="receiveddate",allow_none=True) + closeddate = fields.Str(data_key="closeddate",allow_none=True) + +class FOIRequestWrapperSchema(Schema): + + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + foirawrequestid = fields.Int(data_key="id") + axisSyncDate = fields.Str(data_key="axisSyncDate",allow_none=True) + axisRequestId = fields.Str(data_key="axisRequestId",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + axispagecount = fields.Int(data_key="axispagecount",allow_none=True) + description = fields.Str(data_key="description", required=True,validate=[validate.Length(min=1, error=BLANK_EXCEPTION_MESSAGE)]) + category = fields.Str(data_key="category", required=True,validate=[validate.Length(min=1, error=BLANK_EXCEPTION_MESSAGE)]) + requestType = fields.Str(data_key="requestType", required=True,validate=[validate.Length(min=1, error=BLANK_EXCEPTION_MESSAGE)]) + firstName = fields.Str(data_key="firstName", required=True,validate=[validate.Length(min=1, error=BLANK_EXCEPTION_MESSAGE, max=50)]) + middleName = fields.Str(data_key="middleName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) + lastName = fields.Str(data_key="lastName", required=True,validate=[validate.Length(min=1, error=BLANK_EXCEPTION_MESSAGE, max=50)]) + email = fields.Str(data_key="email",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + businessName = fields.Str(data_key="businessName",allow_none=True, validate=[validate.Length(max=255, error=MAX_EXCEPTION_MESSAGE)]) + assignedGroup = fields.Str(data_key="assignedGroup",allow_none=True, validate=[validate.Length(max=250, error=MAX_EXCEPTION_MESSAGE)]) + assignedTo = fields.Str(data_key="assignedTo",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + fromDate = fields.Str(data_key="fromDate",allow_none=True) + toDate = fields.Str(data_key="toDate",allow_none=True) + 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)]) + startDate = fields.Str(data_key="requestProcessStart", required=True,validate=[validate.Length(min=1, error=BLANK_EXCEPTION_MESSAGE)]) + assignedministrygroup = fields.Str(data_key="assignedministrygroup",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + assignedministryperson = fields.Str(data_key="assignedministryperson",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + assignedToFirstName = fields.Str(data_key="assignedToFirstName",allow_none=True) + assignedToMiddleName = fields.Str(data_key="assignedToMiddleName",allow_none=True) + assignedToLastName = fields.Str(data_key="assignedToLastName",allow_none=True) + assignedministrypersonFirstName = fields.Str(data_key="assignedministrypersonFirstName",allow_none=True) + assignedministrypersonMiddleName = fields.Str(data_key="assignedministrypersonMiddleName",allow_none=True) + assignedministrypersonLastName = fields.Str(data_key="assignedministrypersonLastName",allow_none=True) + + reopen = fields.Bool(data_key="reopen",allow_none=True) + + phonePrimary = fields.Str(data_key="phonePrimary",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) + workPhonePrimary = fields.Str(data_key="workPhonePrimary",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) + phoneSecondary = fields.Str(data_key="phoneSecondary",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) + workPhoneSecondary = fields.Str(data_key="workPhoneSecondary",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) + address = fields.Str(data_key="address",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + addressSecondary = fields.Str(data_key="addressSecondary",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + city = fields.Str(data_key="city",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + province = fields.Str(data_key="province",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + postal = fields.Str(data_key="postal",allow_none=True, validate=[validate.Length(max=10, error=MAX_EXCEPTION_MESSAGE)]) + country = fields.Str(data_key="country",allow_none=True) + requeststatusid = fields.Int(data_key="requeststatusid",allow_none=True) + requeststatuslabel = fields.Str(data_key="requeststatuslabel",allow_none=False) + closedate = fields.Date(data_key="closedate", required=False,allow_none=True) + closereasonid = fields.Int(data_key="closereasonid",allow_none=True) + correctionalServiceNumber = fields.Str(data_key="correctionalServiceNumber",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) + publicServiceEmployeeNumber = fields.Str(data_key="publicServiceEmployeeNumber",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) + isiaorestricted = fields.Bool(data_key="isiaorestricted") + isoipcreview = fields.Bool(data_key="isoipcreview") + + selectedMinistries = fields.Nested(FOIMinistryRequestWrapperSchema, many=True) + additionalPersonalInfo = fields.Nested(FOIAdditionallPersonalInfoWrapperSchema,required=False,allow_none=True) + documents = fields.Nested(FOIMinistryRequestDocumentSchema, many=True,allow_none=True) + idNumber = fields.Str(data_key="idNumber",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + 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) + + oipcdetails = fields.Nested(FOIMinistryRequestOIPCSchema, many=True,allow_none=True) + + estimatedpagecount = fields.Int(data_key="estimatedpagecount",allow_none=True) + estimatedtaggedpagecount = fields.Int(data_key="estimatedtaggedpagecount",allow_none=True) + + +class EditableFOIMinistryRequestWrapperSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + filenumber = fields.Str(data_key="filenumber", validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) + status = fields.Str(data_key="status", validate=[validate.Length(max=100, error=MAX_EXCEPTION_MESSAGE)]) + +class EditableFOIRequestWrapperSchema(Schema): + wfinstanceid = fields.Str(data_key="wfinstanceId",allow_none=True) + selectedMinistries = fields.Nested(EditableFOIMinistryRequestWrapperSchema, many=True) + +class FOIRequestStatusSchema(Schema): + nextstatename = fields.Str(data_key="nextStateName",allow_none=True) + +class FOIMinistryRequestDivisionSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + divisionid = fields.Int(data_key="divisionid") + stageid = fields.Int(data_key="stageid") + divisionDueDate = fields.Str(data_key="divisionDueDate",allow_none=True) + 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): + + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + assignedministrygroup = fields.Str(data_key="assignedministrygroup",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + assignedministryperson = fields.Str(data_key="assignedministryperson",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + assignedgroup = fields.Str(data_key="assignedGroup",allow_none=True, validate=[validate.Length(max=250, error=MAX_EXCEPTION_MESSAGE)]) + assignedto = fields.Str(data_key="assignedTo",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + requeststatusid = fields.Int(data_key="requeststatusid",allow_none=True) + requeststatuslabel = fields.Str(data_key="requeststatuslabel",allow_none=True) + divisions = fields.Nested(FOIMinistryRequestDivisionSchema, many=True,allow_none=True) + documents = fields.Nested(FOIMinistryRequestDocumentSchema, many=True,allow_none=True) + assignedToFirstName = fields.Str(data_key="assignedToFirstName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) + assignedToMiddleName = fields.Str(data_key="assignedToMiddleName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) + 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)]) + ministrysignoffapproval = fields.Nested(CreateMinistrySignOffApprovalSchema, data_key="ministrysignoffapproval", allow_none=True) \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/foiwatcher.py b/historical-search-api/request_api/schemas/foiwatcher.py new file mode 100644 index 000000000..1243e2c77 --- /dev/null +++ b/historical-search-api/request_api/schemas/foiwatcher.py @@ -0,0 +1,35 @@ + + +from marshmallow import EXCLUDE, Schema, fields, validate +from request_api.utils.constants import MAX_EXCEPTION_MESSAGE + +""" +This class consolidates schemas of watcher operations. + +__author__ = "sumathi.thirumani@aot-technologies.com" + +""" +class FOIRawRequestWatcherSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + requestid = fields.Int(data_key="requestid") + watchedbygroup = fields.Str(data_key="watchedbygroup",allow_none=True, validate=[validate.Length(max=250, error=MAX_EXCEPTION_MESSAGE)]) + watchedby = fields.Str(data_key="watchedby", validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + isactive = fields.Bool(data_key="isactive") + fullname = fields.Str(data_key="fullname",allow_none=True ,validate=[validate.Length(max=250, error=MAX_EXCEPTION_MESSAGE)]) + isrestricted = fields.Bool(data_key="isrestricted") + + +class FOIMinistryRequestWatcherSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + ministryrequestid = fields.Int(data_key="ministryrequestid") + watchedbygroup = fields.Str(data_key="watchedbygroup",allow_none=True, validate=[validate.Length(max=250, error=MAX_EXCEPTION_MESSAGE)]) + watchedby = fields.Str(data_key="watchedby", validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) + isactive = fields.Bool(data_key="isactive") + fullname = fields.Str(data_key="fullname",allow_none=True ,validate=[validate.Length(max=250, error=MAX_EXCEPTION_MESSAGE)]) + isrestricted = fields.Bool(data_key="isrestricted") \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/schemas/error.json b/historical-search-api/request_api/schemas/schemas/error.json new file mode 100644 index 000000000..347d0533b --- /dev/null +++ b/historical-search-api/request_api/schemas/schemas/error.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "https://foi.gov.bc.ca/.well_known/schemas/error", + "type": "object", + "title": "The Error response schema", + "description": "The error schema for unsucessful response status.", + "default": {}, + "examples": [ + { + "message": "The requested invitation could not be found." + } + ], + "required": [ + "message" + ], + "properties": { + "message": { + "$id": "#/properties/message", + "type": "string", + "title": "Message", + "default": "", + "examples": [ + "The requested invitation could not be found." + ] + } + }, + "additionalProperties": true +} \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/schemas/exception.json b/historical-search-api/request_api/schemas/schemas/exception.json new file mode 100644 index 000000000..d8aa23c0d --- /dev/null +++ b/historical-search-api/request_api/schemas/schemas/exception.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://foi.gov.bc.ca/.well_known/schemas/exception", + "type": "object", + "title": "Error Message", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "$id": "#/properties/code", + "type": "string", + "title": "Error Code", + "examples": [ + "INVALID_CORP_OR_FILING_TYPE", + "INVALID_CORP_OR_FILING_TYPE" + ], + "pattern": "^(.*)$" + }, + "message": { + "$id": "#/properties/message", + "type": "string", + "title": "Error Message", + "examples": [ + "No matching record found for Corp Type and Filing Type" + ], + "pattern": "^(.*)$" + } + } + } + \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/schemas/rawrequest.json b/historical-search-api/request_api/schemas/schemas/rawrequest.json new file mode 100644 index 000000000..b249451dc --- /dev/null +++ b/historical-search-api/request_api/schemas/schemas/rawrequest.json @@ -0,0 +1,220 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "requestData": { + "type": "object", + "properties": { + "requestType": { + "type": "object", + "properties": { + "requestType": { + "type": "string" + } + }, + "required": [ + "requestType" + ] + }, + "ministry": { + "type": "object", + "properties": { + "selectedMinistry": { + "type": "array", + "items": [ + { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "name": { + "type": "string" + }, + "selected": { + "type": "boolean" + } + } + } + ] + }, + "ministryPage": { + "type": "string" + }, + "defaultMinistry": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "name": { + "type": "string" + }, + "defaulted": { + "type": "boolean" + }, + "selected": { + "type": "boolean" + } + } + } + }, + "required": [ + "selectedMinistry", + "ministryPage" + ] + }, + "descriptionTimeframe": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "fromDate": { + "type": "string" + }, + "toDate": { + "type": "string" + }, + "correctionalServiceNumber": { + "type": ["string","null"] + }, + "publicServiceEmployeeNumber": { + "type": ["string","null"] + }, + "topic": { + "type": "string" + } + }, + "required": [ + "description", + "fromDate", + "toDate" + ] + }, + "contactInfo": { + "type": "object", + "properties": { + "firstName": { + "type": "string" + }, + "middleName": { + "type": ["string","null"] + }, + "lastName": { + "type": "string" + }, + "businessName": { + "type": ["string","null"] + }, + "alsoKnownAs": { + "type":["string","null"] + }, + "birthDate": { + "type": ["string","null"] + } + }, + "required": [ + "firstName", + "lastName" + ] + }, + "contactInfoOptions": { + "type": "object", + "properties": { + "email": { + "type": ["string","null"] + }, + "phonePrimary": { + "type": ["string","null"] + }, + "phoneSecondary": { + "type": ["string","null"] + }, + "address": { + "type": ["string","null"] + }, + "city": { + "type": ["string","null"] + }, + "postal": { + "type": ["string","null"] + }, + "province": { + "type": ["string","null"] + }, + "country": { + "type": ["string","null"] + } + } + }, + "choose-idenity": { + "type": "object", + "properties": { + "answerYes": { + "type": ["string","null"] + } + } + }, + "selectAbout": { + "type": "object", + "properties": { + "yourself": { + "type": ["boolean","null"] + }, + "child": { + "type": ["boolean","null"] + }, + "another": { + "type": ["boolean","null"] + } + } + }, + "requestTopic": { + "type": "object", + "properties": { + "value": { + "type": ["string","null"] + }, + "text": { + "type": ["string","null"] + }, + "ministryCode": { + "type": ["string","null"] + } + } + }, + "adoptiveParents": { + "type": "object", + "properties": { + "motherFirstName": { + "type": ["string","null"] + }, + "motherLastName": { + "type": ["string","null"] + }, + "fatherFirstName": { + "type": ["string","null"] + }, + "fatherLastName": { + "type": ["string","null"] + } + } + }, + "Attachments": { + "type": "array", + "items": {} + } + }, + "required": [ + "requestType", + "ministry", + "descriptionTimeframe", + "contactInfo" + ] + } + }, + "required": [ + "requestData" + ] +} \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/schemas/sample.json b/historical-search-api/request_api/schemas/schemas/sample.json new file mode 100644 index 000000000..b4c5aa433 --- /dev/null +++ b/historical-search-api/request_api/schemas/schemas/sample.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "requestdata": { + "type": "object", + "properties": { + "Name": { + "type": "string" + } + }, + "required": [ + "Name" + ] + }, + "name": { + "type": "string" + }, + "email": { + "type": "string" + } + }, + "required": [ + "requestdata", + "name", + "email" + ] +} \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/utils.py b/historical-search-api/request_api/schemas/utils.py new file mode 100644 index 000000000..a64722414 --- /dev/null +++ b/historical-search-api/request_api/schemas/utils.py @@ -0,0 +1,118 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Utilities to load and validate against JSONSchemas. + +Test helper functions to load and assert that a JSON payload validates against a defined schema. +""" +import json +import logging +from os import listdir, path +from typing import Tuple + +from jsonschema import Draft7Validator, RefResolver, SchemaError, draft7_format_checker + + +BASE_URI = 'https://bcrs.gov.bc.ca/.well_known/schemas' + + +def get_schema(filename: str) -> dict: + """Return the given schema file identified by filename.""" + return _load_json_schema(filename) + + +def _load_json_schema(filename: str): + """Return the given schema file identified by filename.""" + relative_path = path.join('schemas', filename) + absolute_path = path.join(path.dirname(__file__), relative_path) + + with open(absolute_path, 'r') as schema_file: + schema = json.loads(schema_file.read()) + + return schema + + +def get_schema_store(validate_schema: bool = False, schema_search_path: str = None) -> dict: + """Return a schema_store as a dict. + + The default returns schema_store of the default schemas found in this package. + """ + try: + if not schema_search_path: + schema_search_path = path.join(path.dirname(__file__), 'schemas') + schemastore = {} + fnames = listdir(schema_search_path) + for fname in fnames: + fpath = path.join(schema_search_path, fname) + if fpath[-5:] == '.json': + with open(fpath, 'r') as schema_fd: + schema = json.load(schema_fd) + if '$id' in schema: + schemastore[schema['$id']] = schema + + if validate_schema: + for _, schema in schemastore.items(): + Draft7Validator.check_schema(schema) + + return schemastore + except (SchemaError, json.JSONDecodeError) as error: + logging.error(error) + raise error + + +def validate(json_data: json, + schema_id: str, + schema_store: dict = None, + validate_schema: bool = False, + schema_search_path: str = None + ) -> Tuple[bool, iter]: + """Load the json file and validate against loaded schema.""" + try: + if not schema_search_path: + schema_search_path = path.join(path.dirname(__file__), 'schemas') + + if not schema_store: + schema_store = get_schema_store(validate_schema, schema_search_path) + + schema = schema_store.get(f'{BASE_URI}/{schema_id}') + if validate_schema: + Draft7Validator.check_schema(schema) + + schema_file_path = path.join(schema_search_path, schema_id) + resolver = RefResolver(f'file://{schema_file_path}.json', schema, schema_store) + + if Draft7Validator(schema, + format_checker=draft7_format_checker, + resolver=resolver + ) \ + .is_valid(json_data): + return True, None + + errors = Draft7Validator(schema, + format_checker=draft7_format_checker, + resolver=resolver + ) \ + .iter_errors(json_data) + return False, errors + + except SchemaError as error: + # handle schema error + return False, error + + +def serialize(errors): + """Serialize errors.""" + error_message = [] + for error in errors: + error_message.append(error.message) + return error_message diff --git a/historical-search-api/request_api/services/__init__.py b/historical-search-api/request_api/services/__init__.py new file mode 100644 index 000000000..4121aa102 --- /dev/null +++ b/historical-search-api/request_api/services/__init__.py @@ -0,0 +1,16 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Exposes all of the Services used in the API.""" + +# from .fee_service import FeeService diff --git a/historical-search-api/request_api/services/applicantcategoryservice.py b/historical-search-api/request_api/services/applicantcategoryservice.py new file mode 100644 index 000000000..fecd9cebc --- /dev/null +++ b/historical-search-api/request_api/services/applicantcategoryservice.py @@ -0,0 +1,8 @@ +from request_api.models.ApplicantCategories import ApplicantCategory + +class applicantcategoryservice: + + def getapplicantcategories(self): + """ Returns the active records + """ + return ApplicantCategory.getapplicantcategories() \ No newline at end of file diff --git a/historical-search-api/request_api/services/applicantcorrespondence/applicantcorrespondencelog.py b/historical-search-api/request_api/services/applicantcorrespondence/applicantcorrespondencelog.py new file mode 100644 index 000000000..cc265139b --- /dev/null +++ b/historical-search-api/request_api/services/applicantcorrespondence/applicantcorrespondencelog.py @@ -0,0 +1,98 @@ +from request_api.models.ApplicationCorrespondenceTemplates import ApplicationCorrespondenceTemplate +from request_api.models.FOIApplicantCorrespondences import FOIApplicantCorrespondence +from request_api.models.FOIMinistryRequests import FOIMinistryRequest + +import maya +import json +import html +from datetime import datetime +class applicantcorrespondenceservice: + + def getapplicantcorrespondencetemplates(self): + """ Returns the active applicant correspondence templates + """ + return ApplicationCorrespondenceTemplate.getapplicantcorrespondencetemplates() + + def gettemplatebyid(self, templateid): + """ Returns the active applicant correspondence templates + """ + return ApplicationCorrespondenceTemplate.get_template_by_id(templateid) + + def getapplicantcorrespondencelogs(self,ministryrequestid): + """ Returns the active applicant correspondence logs + """ + _correspondencelogs = FOIApplicantCorrespondence.getapplicantcorrespondences(ministryrequestid) + correspondencelogs =[] + for _correpondencelog in _correspondencelogs: + attachments = [] + for _attachment in _correpondencelog['attachments']: + attachment = { + "applicantcorrespondenceattachmentid" : _attachment.applicantcorrespondenceattachmentid, + "documenturipath" : _attachment.attachmentdocumenturipath, + "filename" : _attachment.attachmentfilename, + } + attachments.append(attachment) + correpondencelog = self.__createcorrespondencelog(_correpondencelog, attachments) + correspondencelogs.append(correpondencelog) + return correspondencelogs + + def saveapplicantcorrespondencelog(self, requestid, ministryrequestid, data, userid): + applicantcorrespondencelog = FOIApplicantCorrespondence() + applicantcorrespondencelog.templateid = data['templateid'] + applicantcorrespondencelog.foiministryrequest_id = ministryrequestid + applicantcorrespondencelog.foiministryrequestversion_id =FOIMinistryRequest.getversionforrequest(ministryrequestid=ministryrequestid) + if userid == 'system': + applicantcorrespondencelog.sentcorrespondencemessage = data['correspondencemessagejson'] + applicantcorrespondencelog.sentby = 'System Generated Email' + applicantcorrespondencelog.sent_at = datetime.now() + else: + applicantcorrespondencelog.correspondencemessagejson = data['correspondencemessagejson'] + applicantcorrespondencelog.createdby = userid + return FOIApplicantCorrespondence.saveapplicantcorrespondence(applicantcorrespondencelog,data['attachments']) + + + def updateapplicantcorrespondencelog(self, correspondenceid, content): + return FOIApplicantCorrespondence.updatesentcorrespondence(correspondenceid, content) + + def getapplicantcorrespondencelogbyid(self, applicantcorrespondenceid): + applicantcorrespondence = FOIApplicantCorrespondence.getapplicantcorrespondencebyid(applicantcorrespondenceid) + (_correspondencemessagejson, _isjson) = self.__getjsonobject(applicantcorrespondence["correspondencemessagejson"]) + emailhtml_decoded_string = html.unescape(self.__getvaluefromjson(_correspondencemessagejson, 'emailhtml')) + return emailhtml_decoded_string if _isjson else _correspondencemessagejson + + def getlatestapplicantcorrespondence(self, ministryid): + return FOIApplicantCorrespondence().getlatestapplicantcorrespondence(ministryid) + + def __createcorrespondencelog(self, _correpondencelog, attachments): + (_correspondencemessagejson, _isjson) = self.__getjsonobject(_correpondencelog['correspondencemessagejson']) if _correpondencelog['correspondencemessagejson'] is not None else (None, None) + _sentcorrespondencemessagejson = json.loads(_correpondencelog["sentcorrespondencemessage"]) if _correpondencelog['sentcorrespondencemessage'] not in [None,''] else None + correpondencelog ={ + "applicantcorrespondenceid":_correpondencelog['applicantcorrespondenceid'], + "parentapplicantcorrespondenceid":_correpondencelog['parentapplicantcorrespondenceid'], + "templateid":_correpondencelog['templateid'], + "text": self.__getvaluefromschema(_sentcorrespondencemessagejson, 'message') if _sentcorrespondencemessagejson is not None else self.__getvaluefromjson(_correspondencemessagejson, 'emailhtml') if _isjson else None, + "id": self.__getvaluefromjson(_correspondencemessagejson, 'id') if _isjson else None, + "type": self.__getvaluefromjson(_correspondencemessagejson, 'type') if _isjson else None, + "created_at":_correpondencelog['sent_at'] if _sentcorrespondencemessagejson is not None else _correpondencelog['created_at'], + "createdby":_correpondencelog['createdby'] if _correpondencelog['createdby'] is not None else _correpondencelog['sentby'], + "date": self.__pstformat(_correpondencelog['sent_at']) if _sentcorrespondencemessagejson is not None else self.__pstformat(_correpondencelog['created_at']), + "userId": _correpondencelog['createdby'] if _correpondencelog['createdby'] is not None else _correpondencelog['sentby'], + "attachments" : attachments + } + return correpondencelog + + def __getjsonobject(self, correspondencemessagejson): + try: + data = json.loads(correspondencemessagejson) + except ValueError: + return correspondencemessagejson, False + return data, True + + def __getvaluefromjson(self, jsonobject, property): + return jsonobject[property] if jsonobject is not None else None + + def __pstformat(self, _date): + return maya.parse(_date).datetime(to_timezone='America/Vancouver', naive=False).strftime('%Y %b %d | %I:%M %p') + + def __getvaluefromschema(self, schema, property): + return schema.get(property) if property in schema else None \ No newline at end of file diff --git a/historical-search-api/request_api/services/assigneeservice.py b/historical-search-api/request_api/services/assigneeservice.py new file mode 100644 index 000000000..5ccace8b9 --- /dev/null +++ b/historical-search-api/request_api/services/assigneeservice.py @@ -0,0 +1,42 @@ + +from os import stat +from request_api.services.external.keycloakadminservice import KeycloakAdminService +from request_api.utils.enums import UserGroup +from request_api.models.FOIAssignees import FOIAssignee +from request_api.models.OperatingTeams import OperatingTeam +from request_api.models.FOIRequestTeams import FOIRequestTeam + +class assigneeservice: + """ FOI Assignee management service + + This service class interacts with Keycloak and returns eligible groups and members based on the state of application. + + """ + + def getgroupsandmembersbytypeandstatus(self, requesttype, status, bcgovcode=None): + if requesttype is None and status is None: + return KeycloakAdminService().getgroupsandmembers(OperatingTeam.getalloperatingteams()) + else: + filteredgroups = self.__getgroups(requesttype, status, bcgovcode) + if filteredgroups is not None: + return KeycloakAdminService().getgroupsandmembers(filteredgroups) + return None + + def getmembersbygroupname(self, groupname): + return KeycloakAdminService().getmembersbygroupname(groupname) + + def getprocessingteamsbyrequesttype(self,requesttype): + return FOIRequestTeam.getprocessingteamsbytype(requesttype) + + def saveassignee(self, username, firstname, middlename, lastname): + # FOIAssignee + newassignee = FOIAssignee() + newassignee.username = username + newassignee.firstname = firstname + newassignee.middlename = middlename + newassignee.lastname = lastname + return FOIAssignee.saveassignee(newassignee) + + def __getgroups(self,requesttype, status=None, bcgovcode=None): + return FOIRequestTeam.getteamsbystatusandprogramarea(requesttype,status,bcgovcode) + diff --git a/historical-search-api/request_api/services/auditservice.py b/historical-search-api/request_api/services/auditservice.py new file mode 100644 index 000000000..d9289b544 --- /dev/null +++ b/historical-search-api/request_api/services/auditservice.py @@ -0,0 +1,79 @@ + +from os import stat +from request_api.auth import AuthHelper +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from request_api.models.FOIRequests import FOIRequest +from request_api.models.FOIRawRequests import FOIRawRequest +from datetime import datetime +from request_api.utils.enums import RequestType +import dateutil.parser +import maya +class auditservice: + """ FOI audit management service + + This service class interacts with datastore to retrive the audit of changes. + + """ + + def getauditforfield(self, type, id, field, isall=False): + if field == "description": + return self.__getauditfordescription(type, id, isall) + else: + return None + + + def __getauditfordescription(self, type, id, isall): + _alldescriptions = [] + if type == "ministryrequest": + ministryrsp = self.__getauditfromministryrequest(id) + _alldescriptions = self.__getauditfromrawrequest(type, ministryrsp['foirequestid']) + ministryrsp['audit'] + else: + _alldescriptions = self.__getauditfromrawrequest(type, id) + #Filter summary of changes + datasummary=[] + _data, _startdate, _enddate = None, None, None + if len(_alldescriptions) > 0: + for entry in _alldescriptions: + if isall == True or (isall == False and _data != entry['description'] or _startdate != entry['fromdate'] or _enddate != entry['todate']): + datasummary.append({"description": entry['description'], "fromDate": entry['fromdate'], "toDate": entry['todate'], "createdAt": entry['createdat'], "createdBy": entry['createdby'], "status": entry['status']}) + _data = entry['description'] + _startdate = entry['fromdate'] + _enddate = entry['todate'] + return datasummary[::-1] + + + def __getauditfromministryrequest(self, id): + _ministrydescriptions = [] + ministryrecords = FOIMinistryRequest().getrequestById(id) + foirequestid = 0 + for entry in ministryrecords: + foirequestid = entry['foirequest_id'] + createdat = maya.parse(entry['created_at']).datetime(to_timezone='America/Vancouver', naive=False).strftime('%Y-%m-%d %H:%M:%S') + fromdate = datetime.fromisoformat(entry['recordsearchfromdate']).strftime("%Y-%m-%d") if entry['recordsearchfromdate'] is not None else None + todate = datetime.fromisoformat(entry['recordsearchtodate']).strftime("%Y-%m-%d") if entry['recordsearchtodate'] is not None else None + _ministrydescriptions.append({"description": entry['description'], "fromdate": fromdate, "todate": todate, "createdat": createdat, "createdby": entry['createdby'], "status": entry['requeststatus.name']}) + return {"foirequestid" :foirequestid , "audit":_ministrydescriptions} + + def __getauditfromrawrequest(self, type, id): + _rawdescriptions = [] + if type == "ministryrequest": + requestrecord = FOIRequest().getrequest(id) + rawrequestid= requestrecord['foirawrequestid'] + else: + rawrequestid= id + rawrecords = FOIRawRequest().getDescriptionSummaryById(rawrequestid) + + + for entry in rawrecords: + createdat = maya.parse(entry['createdat']).datetime(to_timezone='America/Vancouver', naive=False).strftime('%Y-%m-%d %H:%M:%S') + fromdate =dateutil.parser.parse(entry['fromdate']).strftime('%Y-%m-%d') if entry['fromdate'] is not None else None + todate = dateutil.parser.parse(entry['todate']).strftime('%Y-%m-%d') if entry['todate'] is not None else None + if AuthHelper.getusertype() == "iao": + _rawdescriptions.append({"description": entry['description'], "fromdate": fromdate, "todate": todate, "createdat": createdat , "createdby": entry['createdby'], "status": entry['status']}) + else: + if requestrecord['requesttype'] == 'personal' or entry['ispiiredacted'] == True: + _rawdescriptions.append({"description": entry['description'], "fromdate": fromdate, "todate": todate, "createdat": createdat , "createdby": entry['createdby'], "status": entry['status']}) + return _rawdescriptions + + + \ No newline at end of file diff --git a/historical-search-api/request_api/services/cacheservice.py b/historical-search-api/request_api/services/cacheservice.py new file mode 100644 index 000000000..b0516eb53 --- /dev/null +++ b/historical-search-api/request_api/services/cacheservice.py @@ -0,0 +1,62 @@ +import logging +import os +import requests +from request_api.utils.cache import clear_cache, clear_cache_key +from request_api.services.external.keycloakadminservice import KeycloakAdminService +from request_api.utils.enums import CacheUrls +from request_api.exceptions import BusinessException + +class cacheservice: + + request_url = os.getenv("FOI_REQ_MANAGEMENT_API_URL") + + def refreshcache(self, request_json): + try: + result= False + if(request_json is not None and 'key' in request_json): + result= self.__refreshcachebykey(request_json['key']) + else: + resp_flag = clear_cache() + if resp_flag: + result=self.__invokeresources(self.__getapilistbykey()) + return result + except Exception as ex: + logging.error(ex) + return False + + def __refreshcachebykey(self, key): + try: + result= False + resp_flag = clear_cache_key(key) + if resp_flag: + result= self.__invokeresources(self.__getapilistbykey(key)) + return result + except BusinessException as ex: + logging.error(ex) + return {'status': ex.status_code, 'message':ex.message}, 500 + return False + + def __getapilistbykey(self, key=None): + apiurls=[] + if key is not None: + apiurls.append(self.request_url+CacheUrls[key].value) + else: + apiurls.append(self.request_url+CacheUrls.keycloakusers.value) + apiurls.append(self.request_url+CacheUrls.programareas.value) + apiurls.append(self.request_url+CacheUrls.deliverymodes.value) + apiurls.append(self.request_url+CacheUrls.receivedmodes.value) + apiurls.append(self.request_url+CacheUrls.closereasons.value) + apiurls.append(self.request_url+CacheUrls.extensionreasons.value) + apiurls.append(self.request_url+CacheUrls.applicantcategories.value) + apiurls.append(self.request_url+CacheUrls.subjectcodes.value) + return apiurls + + def __invokeresources(self, apiurls): + try: + headers= {"Authorization": "Bearer " + KeycloakAdminService().get_token()} + for url in apiurls: + requests.get(url, headers=headers) + return True + except BusinessException as ex: + logging.error(ex) + return {'status': ex.status_code, 'message':ex.message}, 500 diff --git a/historical-search-api/request_api/services/cdogs_api_service.py b/historical-search-api/request_api/services/cdogs_api_service.py new file mode 100644 index 000000000..4ec2ddf66 --- /dev/null +++ b/historical-search-api/request_api/services/cdogs_api_service.py @@ -0,0 +1,127 @@ +# Copyright © 2019 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Service for receipt generation.""" +import base64 +import json +import os +import re + +from flask import current_app +import requests +from request_api.exceptions import BusinessException, Error + + +class CdogsApiService: + """cdogs api Service class.""" + + def __init__(self): + self.access_token = self._get_access_token(); + + + + def generate_receipt(self, template_hash_code: str, data): + request_body = { + "options": { + "cachereport": False, + "convertTo": "pdf", + "overwrite": True, + "reportName": "Receipt" + }, + "data": data + } + json_request_body = json.dumps(request_body) + + headers = { + 'Content-Type': 'application/json', + 'Authorization': f'Bearer {self.access_token}' + } + url = f"{current_app.config['CDOGS_BASE_URL']}/api/v2/template/{template_hash_code}/render" + return self._post_generate_receipt(json_request_body, headers, url) + + def _post_generate_receipt(self, json_request_body, headers, url): + return requests.post(url, data= json_request_body, headers= headers) + + def upload_template(self, receipt_template_path: str): + + file_dir = os.path.dirname(os.path.realpath('__file__')) + template_file_path = os.path.join(file_dir, receipt_template_path) + + headers = { + "Authorization": f'Bearer {self.access_token}' + } + + url = f"{current_app.config['CDOGS_BASE_URL']}/api/v2/template" + template = {'template':('template', open(template_file_path, 'rb'), "multipart/form-data")} + + current_app.logger.info('Uploading template %s', template_file_path) + print('Uploading template %s', template_file_path) + response = self._post_upload_template(headers, url, template) + + if response.status_code == 200: + if response.headers.get("X-Template-Hash") is None: + raise BusinessException(Error.DATA_NOT_FOUND) + + current_app.logger.info('Returning new hash %s', response.headers['X-Template-Hash']) + print('Returning new hash %s', response.headers['X-Template-Hash']) + return response.headers['X-Template-Hash']; + + response_json = json.loads(response.content) + + if response.status_code == 405 and response_json['detail'] is not None: + match = re.findall(r"Hash '(.*?)'", response_json['detail']); + if match: + current_app.logger.info('Template already hashed with code %s', match[0]) + print('Template already hashed with code %s', match[0]) + return match[0] + + raise BusinessException(Error.DATA_NOT_FOUND) + + def _post_upload_template(self, headers, url, template): + response = requests.post(url, headers= headers, files= template) + return response + + def check_template_cached(self, template_hash_code: str): + + headers = { + "Authorization": f'Bearer {self.access_token}' + } + + url = f"{current_app.config['CDOGS_BASE_URL']}/api/v2/template/{template_hash_code}" + + response = requests.get(url, headers= headers) + return response.status_code == 200 + + + @staticmethod + def _get_access_token(): + token_url = current_app.config['CDOGS_TOKEN_URL'] + service_client = current_app.config['CDOGS_SERVICE_CLIENT'] + service_client_secret = current_app.config['CDOGS_SERVICE_CLIENT_SECRET'] + + basic_auth_encoded = base64.b64encode( + bytes(service_client + ':' + service_client_secret, 'utf-8')).decode('utf-8') + data = 'grant_type=client_credentials' + response = requests.post( + token_url, + data=data, + headers={ + 'Authorization': f'Basic {basic_auth_encoded}', + 'Content-Type': 'application/x-www-form-urlencoded' + } + ) + + response_json = response.json() + return response_json['access_token'] \ No newline at end of file diff --git a/historical-search-api/request_api/services/cfrfeeservice.py b/historical-search-api/request_api/services/cfrfeeservice.py new file mode 100644 index 000000000..3dd4cc5fc --- /dev/null +++ b/historical-search-api/request_api/services/cfrfeeservice.py @@ -0,0 +1,114 @@ + +from os import stat +from re import VERBOSE +from request_api.models.FOIRequestCFRFees import FOIRequestCFRFee +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from request_api.models.FOIRequestPayments import FOIRequestPayment +from request_api.services.cfrfeestatusservice import cfrfeestatusservice +from request_api.services.cfrformreasonservice import cfrformreasonservice +from request_api.utils.enums import PaymentEventType +from dateutil.parser import parse + +from dateutil import parser +from dateutil import tz +import pytz +import maya +from datetime import date +from datetime import datetime +import requests +from flask import current_app + +class cfrfeeservice: + """ FOI CFR Fee Form management service + Supports creation, update and delete of CFR fee form + """ + + def createcfrfee(self, ministryrequestid, data, userid): + cfrfee = self.__preparecfrfee(ministryrequestid, data, data.get('cfrfeeid') is not None) + cfrfee.__dict__.update(data) + return FOIRequestCFRFee.createcfrfee(cfrfee, userid) + + def sanctioncfrfee(self, ministryrequestid, data, userid): + cfrfee = self.__preparecfrfee(ministryrequestid, data) + cfrfee.feedata.update(data.get('feedata', {})) + return FOIRequestCFRFee.createcfrfee(cfrfee, userid) + + def getactivepayment(self, foirequestid, ministryrequestid): + return FOIRequestPayment.getactivepayment(foirequestid, ministryrequestid) + + def paycfrfee(self, ministryrequestid, amountpaid): + cfrfee = self.__preparecfrfee(ministryrequestid) + _amountpaid = float(cfrfee.feedata['amountpaid']) + amountpaid + _balanceremaining = cfrfee.feedata['balanceremaining'] - amountpaid + cfrfee.feedata['balanceremaining'] = _balanceremaining + cfrfee.feedata['amountpaid'] = '{:.2f}'.format(_amountpaid) + cfrfee.feedata['paymentdate'] = datetime.now().astimezone(pytz.timezone(current_app.config['LEGISLATIVE_TIMEZONE'])).strftime('%Y-%m-%d') + return FOIRequestCFRFee.createcfrfee(cfrfee, 'Online Payment') + + def updatepaymentmethod(self, ministryrequestid, paymenttype): + cfrfee = FOIRequestCFRFee.getcfrfee(ministryrequestid) + feedata = cfrfee['feedata'] + if (paymenttype == PaymentEventType.outstandingpaid.value): + feedata['balancepaymentmethod'] = 'creditcardonline' + else: + feedata['estimatepaymentmethod'] = 'creditcardonline' + return FOIRequestCFRFee.updatecfrfeedatabyid(ministryrequestid, feedata) + + def __preparecfrfee(self, ministryrequestid, data={}, getprevious=True): + cfrfee = FOIRequestCFRFee() + lkupcfrfee = self.getcfrfee(ministryrequestid) if getprevious else None + _version = 1 + if lkupcfrfee: + cfrfee.__dict__.update(lkupcfrfee) + _version = lkupcfrfee['version'] + 1 + cfrfee.updated_at = None + cfrfee.updatedby = None + cfrfee.version = _version + cfrfee.ministryrequestid = ministryrequestid + cfrfee.ministryrequestversion = FOIMinistryRequest.getversionforrequest(ministryrequestid) + if "status" in data and data['status'] not in (None,''): + cfrfee.cfrfeestatusid = cfrfeestatusservice().getcfrfeestatusidbyname(data['status']) + if "reason" in data and data['reason'] not in (None,''): + cfrfee.cfrformreasonid = cfrformreasonservice().getcfrformreasonidbyname(data['reason']) + return cfrfee + + + def getcfrfee(self, ministryrequestid): + cfrfee = FOIRequestCFRFee.getcfrfee(ministryrequestid) + return self.__formatcfrfee(cfrfee) + + def getapprovedcfrfee(self, ministryrequestid): + cfrfee = FOIRequestCFRFee.getapprovedcfrfee(ministryrequestid) + return self.__formatcfrfee(cfrfee) + + + def getcfrfeehistory(self, ministryrequestid): + cfrfees = [] + _cfrfees = FOIRequestCFRFee.getcfrfeehistory(ministryrequestid) + for cfrfee in _cfrfees: + cfrfees.append(self.__formatcfrfee(cfrfee)) + return cfrfees + + def __formatcfrfee(self,cfrfee): + if cfrfee is not None and cfrfee != {}: + cfrfee['created_at'] = self.__pstformat(cfrfee['created_at']) + if cfrfee.get('version_created_at') is not None: + cfrfee['version_created_at'] = self.__pstformat(cfrfee['version_created_at']) + if cfrfee['cfrfeestatusid'] is not None: + cfrfee['status'] = cfrfee['cfrfeestatus.name'] + cfrfee.pop('cfrfeestatus.name') + else: + cfrfee['status'] = None + if cfrfee['cfrformreasonid'] is not None: + cfrfee['reason'] = cfrfee['cfrformreason.name'] + cfrfee.pop('cfrformreason.name') + else: + cfrfee['reason'] = None + return cfrfee + else: + return {} + + + def __pstformat(self, inpdate): + formateddate = maya.parse(inpdate).datetime(to_timezone='America/Vancouver', naive=False) + return formateddate.strftime('%Y %b %d | %I:%M %p') diff --git a/historical-search-api/request_api/services/cfrfeestatusservice.py b/historical-search-api/request_api/services/cfrfeestatusservice.py new file mode 100644 index 000000000..c547da573 --- /dev/null +++ b/historical-search-api/request_api/services/cfrfeestatusservice.py @@ -0,0 +1,13 @@ +from request_api.models.CFRFeeStatus import CFRFeeStatus + +class cfrfeestatusservice: + + def getcfrfeestatuses(self): + """ Returns the active records + """ + return CFRFeeStatus().getallcfrfeestatuses() + + def getcfrfeestatusidbyname(self, status): + """ Returns the active records + """ + return CFRFeeStatus().getcfrfeestatusid(status)['cfrfeestatusid'] \ No newline at end of file diff --git a/historical-search-api/request_api/services/cfrformreasonservice.py b/historical-search-api/request_api/services/cfrformreasonservice.py new file mode 100644 index 000000000..11cadb44c --- /dev/null +++ b/historical-search-api/request_api/services/cfrformreasonservice.py @@ -0,0 +1,13 @@ +from request_api.models.CFRFormReason import CFRFormReason + +class cfrformreasonservice: + + def getcfrformreasons(self): + """ Returns the active records + """ + return CFRFormReason().getallcfrformreasons() + + def getcfrformreasonidbyname(self, reason): + """ Returns the active records + """ + return CFRFormReason().getcfrformreasonid(reason)['cfrformreasonid'] \ No newline at end of file diff --git a/historical-search-api/request_api/services/closereasonservice.py b/historical-search-api/request_api/services/closereasonservice.py new file mode 100644 index 000000000..053aca842 --- /dev/null +++ b/historical-search-api/request_api/services/closereasonservice.py @@ -0,0 +1,8 @@ +from request_api.models.CloseReasons import CloseReason + +class closereasonservice: + + def getclosereasons(self): + """ Returns the active records + """ + return CloseReason.getallclosereasons() \ No newline at end of file diff --git a/historical-search-api/request_api/services/commentservice.py b/historical-search-api/request_api/services/commentservice.py new file mode 100644 index 000000000..7528793e9 --- /dev/null +++ b/historical-search-api/request_api/services/commentservice.py @@ -0,0 +1,185 @@ + +from os import stat +from re import VERBOSE +from operator import itemgetter +from request_api.models.FOIRequestComments import FOIRequestComment +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from request_api.models.FOIRawRequestComments import FOIRawRequestComment +from request_api.models.FOIRawRequests import FOIRawRequest +from request_api.services.assigneeservice import assigneeservice +from request_api.services.watcherservice import watcherservice +from request_api.models.default_method_result import DefaultMethodResult +import json +from dateutil.parser import parse +import datetime + +from dateutil import parser +from dateutil import tz +from pytz import timezone +import pytz +import maya + + + +class commentservice: + """ FOI comment management service + Supports creation, update and delete of comments for both unopened(raw) and opened(ministry) request + """ + + def createministryrequestcomment(self, data, userid, type=1): + version = FOIMinistryRequest.getversionforrequest(data["ministryrequestid"]) + return FOIRequestComment.savecomment(type, data, version, userid) + + def createrawrequestcomment(self, data, userid, type=1): + version = FOIRawRequest.getversionforrequest(data["requestid"]) + return FOIRawRequestComment.savecomment(type, data, version, userid) + + def createcomments(self, data, userid, type=2): + return FOIRequestComment.savecomment(type, data, data['version'], userid) + + def disableministryrequestcomment(self, commentid, userid): + return FOIRequestComment.disablecomment(commentid, userid) + + def disablerawrequestcomment(self, commentid, userid): + return FOIRawRequestComment.disablecomment(commentid, userid) + + def updateministryrequestcomment(self, commentid, data, userid): + result = FOIRequestComment.updatecomment(commentid, data, userid) + deactivateresult = None + if result.success == True: + commentsversion = result.args[1] + if commentsversion and commentsversion > 0: + deactivateresult = FOIRequestComment.deactivatecomment(commentid, userid, commentsversion) + if result and deactivateresult: + return result + return DefaultMethodResult(False,'Error in editing comment',commentid) + + def updaterawrequestcomment(self, commentid, data, userid): + result = FOIRawRequestComment.updatecomment(commentid, data, userid) + deactivateresult = None + if result.success == True: + commentsversion = result.args[1] + if commentsversion and commentsversion > 0: + deactivateresult = FOIRawRequestComment.deactivatecomment(commentid, userid, commentsversion) + if result and deactivateresult: + return result + return DefaultMethodResult(False,'Error in editing raw request comment',commentid) + + def getministryrequestcomments(self, ministryrequestid): + data = FOIRequestComment.getcomments(ministryrequestid) + result = self.__preparecomments(data) + return result + + def getrawrequestcomments(self, requestid): + data = FOIRawRequestComment.getcomments(requestid) + return self.__preparecomments(data) + + def copyrequestcomment(self, ministryrequestid, comments, userid): + _comments = [] + for comment in comments: + commentresponse=FOIRequestComment.savecomment(comment['commentTypeId'], self.__copyparentcomment(ministryrequestid, comment), 1, userid,comment['dateUF']) + _comments.append({"ministrycommentid":commentresponse.identifier,"rawcommentid":comment['commentId']}) + if comment['replies']: + for reply in comment['replies']: + response=FOIRequestComment.savecomment(reply['commentTypeId'], self.__copyreplycomment(ministryrequestid, reply, commentresponse.identifier), 1, userid,reply['dateUF']) + _comments.append({"ministrycommentid":response.identifier,"rawcommentid":comment['commentId']}) + return _comments + + def __copyparentcomment(self, ministryrequestid, entry): + return { + "ministryrequestid": ministryrequestid, + "comment": entry['text'], + "taggedusers": entry['taggedusers'] + } + + def __copyreplycomment(self, ministryrequestid, entry, parentcommentid): + return { + "ministryrequestid": ministryrequestid, + "comment": entry['text'], + "taggedusers": entry['taggedusers'], + "parentcommentid":parentcommentid + } + + def __preparecomments(self, data): + comments=[] + comments = self.__parentcomments(data) + for entry in data: + if entry['parentcommentid'] is not None: + for _comment in comments: + if entry['parentcommentid'] == _comment['commentId']: + _comment['replies'].append(self.__comment(entry)) + return comments + + def __parentcomments(self, data): + parentcomments = [] + for entry in data: + if entry['parentcommentid'] is None: + _comment = self.__comment(entry) + _comment['replies'] = [] + parentcomments.append(_comment) + return parentcomments + + + def __comment(self, comment): + commentcreateddate = maya.parse(comment["created_at"]).datetime(to_timezone='America/Vancouver', naive=False) + return { + "userId": comment['createdby'], + "commentId": comment['commentid'], + "text": comment['comment'], + "dateUF":maya.parse(comment['created_at']).iso8601(), + "date": commentcreateddate.strftime('%Y %b %d | %I:%M %p'), + "parentCommentId":comment['parentcommentid'], + "commentTypeId":comment['commenttypeid'], + "taggedusers" : comment['taggedusers'], + "edited": comment["commentsversion"] > 1 # edited: True/False + } + + def createcommenttagginguserlist(self,type,requestid): + if type == "ministryrequest": + watchers = watcherservice().getallministryrequestwatchers(requestid) + baserequestinfo = FOIMinistryRequest.getmetadata(requestid) + else: + watchers = watcherservice().getrawrequestwatchers(requestid) + baserequestinfo = FOIRawRequest.getmetadata(requestid) + userlist = [] + watcherteams= [] + if baserequestinfo is not None: + user= self.__formatuserlist(baserequestinfo['assignedTo'], baserequestinfo['assignedToFirstName'], baserequestinfo['assignedToLastName']) + userlist.append(user) + if 'bcgovcode' in baserequestinfo: + teamname = baserequestinfo['bcgovcode'].lower()+"ministryteam" + else: + teamname = baserequestinfo['selectedMinistries'][0]['code'].lower()+"ministryteam" + ministryteam = assigneeservice().getmembersbygroupname(teamname) + for ministry in ministryteam: + for member in ministry['members']: + user= self.__formatuserlist(member['username'], member['firstname'], member['lastname']) + userlist.append(user) + if watchers is not None: + self.__getwatchernames(watchers,watcherteams, userlist) + return userlist + + def __getwatchernames(self, watchers, watcherteams, userlist): + watchergrouplist= list(map(itemgetter('watchedbygroup'), watchers)) + watchergroups = set(watchergrouplist) + for group in watchergroups: + watcherteams.append(assigneeservice().getmembersbygroupname(group)) + for watcher in watchers: + #check if user already in list + existingusernames = list(map(itemgetter('username'), userlist)) + if watcher['watchedby'] not in existingusernames: + for team in watcherteams: + member= list(filter(lambda x: x['username'] == watcher['watchedby'], team[0]['members'])) + if len(member) > 0: + user= self.__formatuserlist(watcher['watchedby'], member[0]['firstname'], member[0]['lastname']) + userlist.append(user) + break + + def __formatuserlist(self, username, firstname, lastname): + user={} + user['username'] = username + user['firstname'] = firstname + user['lastname'] = lastname + user['fullname'] = lastname+", "+firstname + user['name'] = lastname+", "+firstname + return user \ No newline at end of file diff --git a/historical-search-api/request_api/services/commons/duecalculator.py b/historical-search-api/request_api/services/commons/duecalculator.py new file mode 100644 index 000000000..c6ecd061e --- /dev/null +++ b/historical-search-api/request_api/services/commons/duecalculator.py @@ -0,0 +1,109 @@ + +from os import stat +from re import VERBOSE +from datetime import datetime as datetime2 +from datetime import datetime, timedelta +import holidays +import os +from dateutil.parser import parse +from pytz import timezone +from request_api.utils.commons.datetimehandler import datetimehandler + +class duecalculator: + """ Due date calculator helper service + + """ + + def getpreviousbusinessday(self, cfrduedate,ca_holidays): + _prevbusinessday = self.__getpreviousweekday(cfrduedate) + if self.__isholiday(_prevbusinessday,ca_holidays) == False: + return _prevbusinessday + else: + return self.getpreviousbusinessday(_prevbusinessday,ca_holidays) + + def getpreviousbusinessday_by_n(self, duedate, ca_holidays, n): + _prevbusinessday = duedate + for i in range(n): + _prevbusinessday = self.getpreviousbusinessday(_prevbusinessday, ca_holidays) + return _prevbusinessday + + def formatduedate(self,input): + return datetimehandler().formatdate(input) + + def addbusinessdays(self, inpdate, days): + _holidays = self.getholidays() + businessdays = 0 + __calcdate = datetimehandler().getdate(inpdate) + while businessdays < days: + __calcdate = __calcdate + timedelta(days=1) + if self.isbusinessday(__calcdate, _holidays) == True: + businessdays += 1 + return __calcdate + + def getbusinessdaysbetween(self, date1, date2=None): + _holidays = self.getholidays() + businessdays = 0 + date2 = date2 if date2 not in (None, '') else self.gettoday() + _date1_date_fmt = datetimehandler().getdate(date1) + _date2_date_fmt = datetimehandler().getdate(date2) + __fromdate = _date1_date_fmt if _date1_date_fmt <= _date2_date_fmt else _date2_date_fmt + __todate = _date2_date_fmt if _date2_date_fmt >= _date1_date_fmt else _date1_date_fmt + __fromcalcdate =__fromdate + while datetimehandler().getdate(__fromcalcdate).date() < datetimehandler().getdate(__todate).date(): + if self.isbusinessday(__fromcalcdate, _holidays) == True: + businessdays += 1 + __fromcalcdate = __fromcalcdate + timedelta(days=1) + return businessdays + + def getholidays(self): + ca_holidays = [] + currentyear = datetime.today().year + years = [currentyear - 1, currentyear, currentyear + 1] + for year in years: + ca_holidays.extend(self.__getholidaysbyyear(year)) + return ca_holidays + + def gettoday(self): + return datetimehandler().gettoday() + + def now(self): + return datetimehandler().now() + + def __getholidaysbyyear(self, year): + ca_holidays = [] + for date, name in sorted(holidays.CA(prov='BC', years=year).items()): + ca_holidays.append(date.strftime(datetimehandler().getdefaultdateformat())) + if 'FOI_ADDITIONAL_HOLIDAYS' in os.environ and os.getenv('FOI_ADDITIONAL_HOLIDAYS') != '': + _addldays = os.getenv('FOI_ADDITIONAL_HOLIDAYS') + for _addlday in _addldays.split(","): + ca_holidays.append(_addlday.strip().replace('XXXX',str(year))) + return ca_holidays + + def isbusinessday(self, inpdate, holidays=None): + _holidays = self.getholidays() if holidays is None else holidays + if datetimehandler().formatdate(inpdate) not in _holidays and self.__isweekday(inpdate) == True: + return True + return False + + def __isweekday(self, inpdate): + _inpdate = datetimehandler().getdate(inpdate) + if _inpdate.weekday() < 5: + return True + else: + return False + + def __getpreviousweekday(self, cfrduedate): + diff = 1 + _cfrduedate = datetimehandler().getdate(cfrduedate) + if _cfrduedate.weekday() == 0: + diff = 3 + elif _cfrduedate.weekday() == 6: + diff = 2 + else : + diff = 1 + res = _cfrduedate - timedelta(days=diff) + return res.strftime(datetimehandler().getdefaultdateformat()) + + def __isholiday(self, input, ca_holidays): + return input in ca_holidays + diff --git a/historical-search-api/request_api/services/dashboardeventservice.py b/historical-search-api/request_api/services/dashboardeventservice.py new file mode 100644 index 000000000..017b29135 --- /dev/null +++ b/historical-search-api/request_api/services/dashboardeventservice.py @@ -0,0 +1,69 @@ + +from os import stat +from re import VERBOSE +from request_api.models.FOIRequestNotificationUsers import FOIRequestNotificationUser +from request_api.models.FOIRawRequestNotificationUsers import FOIRawRequestNotificationUser +from request_api.models.FOIRequestNotificationDashboard import FOIRequestNotificationDashboard +from request_api.auth import AuthHelper +from dateutil import tz, parser +from flask import jsonify +from datetime import datetime as datetime2 +from request_api.utils.commons.datetimehandler import datetimehandler +import re + +class dashboardeventservice: + """ FOI Event Dashboard + """ + + def geteventqueuepagination(self, queuetype, groups=None, page=1, size=10, sortingitems=[], sortingorders=[], filterfields=[], keyword=None, additionalfilter='All', userid=None): + _filterfields = self.__validateandtransform(filterfields) + notifications = None + if AuthHelper.getusertype() == "iao" and (queuetype is None or queuetype == "all"): + notifications = FOIRequestNotificationDashboard.getiaoeventpagination(groups, page, size, sortingitems, sortingorders, _filterfields, keyword, additionalfilter, userid, AuthHelper.isiaorestrictedfilemanager()) + elif AuthHelper.getusertype() == "ministry" and (queuetype is not None and queuetype == "ministry"): + notifications = FOIRequestNotificationDashboard.getministryeventpagination(groups, page, size, sortingitems, sortingorders, _filterfields, keyword, additionalfilter, userid, AuthHelper.isiaorestrictedfilemanager(), AuthHelper.isministryrestrictedfilemanager()) + if notifications is not None: + eventqueue = [] + for notification in notifications.items: + eventqueue.append(self.__prepareevent(notification)) + + meta = { + 'page': notifications.page, + 'pages': notifications.pages, + 'total': notifications.total, + 'prev_num': notifications.prev_num, + 'next_num': notifications.next_num, + 'has_next': notifications.has_next, + 'has_prev': notifications.has_prev, + } + return jsonify({'data': eventqueue, 'meta': meta}) + return jsonify({'data': [], 'meta': None}) + + def __validateandtransform(self, filterfields): + return self.__transformfilteringfields(filterfields) + + def __transformfilteringfields(self, filterfields): + return list(map(lambda x: x.replace('createdat', 'createdatformatted'), filterfields)) + + + + def __prepareevent(self, notification): + return { + 'id': notification.id+notification.crtid, + 'status': notification.status, + 'rawrequestid': notification.rawrequestid, + 'requestid': notification.requestid, + 'ministryrequestid': notification.ministryrequestid, + 'createdat' : notification.createdatformatted, + #'createdat' : self.__formatedate(notification.createdat), + 'axisRequestId': notification.axisRequestId, + 'notification': notification.notification, + 'assignedToFormatted': notification.assignedToFormatted, + 'ministryAssignedToFormatted': notification.ministryAssignedToFormatted, + 'userFormatted': notification.userFormatted, + 'creatorFormatted': notification.creatorFormatted, + 'notificationType': notification.notificationtype, + 'description':notification.description + } + + \ No newline at end of file diff --git a/historical-search-api/request_api/services/dashboardservice.py b/historical-search-api/request_api/services/dashboardservice.py new file mode 100644 index 000000000..000cac1ce --- /dev/null +++ b/historical-search-api/request_api/services/dashboardservice.py @@ -0,0 +1,226 @@ +from request_api.models.FOIRawRequests import FOIRawRequest +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from request_api.models.FOIRestrictedMinistryRequests import FOIRestrictedMinistryRequest +from request_api.models.FOIRawRequestWatchers import FOIRawRequestWatcher +from request_api.models.FOIRequestWatchers import FOIRequestWatcher +from dateutil import tz, parser +import datetime as dt +from pytz import timezone +import pytz +import maya +from request_api.auth import AuthHelper + +from flask import jsonify + +SHORT_DATEFORMAT = '%Y %b, %d' +LONG_DATEFORMAT = '%Y-%m-%d %H:%M:%S.%f' + +class dashboardservice: + """ FOI dashboard management service + + This service class manages dashboard retrival for both unopened and opened request with consideration of user types. + + """ + + def __preparefoirequestinfo(self, request, receiveddate, receiveddateuf, idnumberprefix = ''): + idnumber = self.__getidnumber(idnumberprefix, request.axisRequestId, request.idNumber) + baserequestinfo = self.__preparebaserequestinfo( + request.id, + request.requestType, + request.currentState, + receiveddate, + receiveddateuf, + request.assignedGroup, + request.assignedTo, + idnumberprefix + request.idNumber, + idnumber, + request.version, + request.description, + request.recordsearchfromdate, + request.recordsearchtodate, + ) + baserequestinfo.update({'firstName': request.firstName}) + baserequestinfo.update({'lastName': request.lastName}) + baserequestinfo.update({'xgov': 'No'}) + baserequestinfo.update({'assignedToFirstName': request.assignedToFirstName}) + baserequestinfo.update({'duedate': request.duedate}) + baserequestinfo.update({'cfrduedate': request.cfrduedate}) + baserequestinfo.update({'applicantcategory': request.applicantcategory}) + baserequestinfo.update({'assignedToLastName': request.assignedToLastName}) + baserequestinfo.update({'onBehalfFirstName': request.onBehalfFirstName}) + baserequestinfo.update({'onBehalfLastName': request.onBehalfLastName}) + baserequestinfo.update({'onBehalfFormatted': request.onBehalfFormatted}) + baserequestinfo.update({'requestpagecount': request.requestpagecount}) + baserequestinfo.update({'recordspagecount': request.recordspagecount}) + baserequestinfo.update({'axispagecount': request.axispagecount}) + baserequestinfo.update({'axislanpagecount': request.axislanpagecount}) + baserequestinfo.update({'bcgovcode': request.bcgovcode}) + isoipcreview = request.isoipcreview if request.isoipcreview == True else False + baserequestinfo.update({'isoipcreview': isoipcreview}) + return baserequestinfo + + def __preparebaserequestinfo(self, id, requesttype, status, receiveddate, receiveddateuf, assignedgroup, assignedto, idnumber, axisrequestid, version, description, fromdate, todate): + return {'id': id, + 'requestType': requesttype, + 'currentState': status, + 'receivedDate': receiveddate, + 'receivedDateUF': receiveddateuf, + 'assignedGroup': assignedgroup, + 'assignedTo': assignedto, + 'idNumber': idnumber, + 'axisRequestId': axisrequestid, + 'version':version, + 'description':description, + 'fromdate':fromdate, + 'todate':todate, + } + + def getrequestqueuepagination(self, groups=None, page=1, size=10, sortingitems=[], sortingorders=[], filterfields=[], keyword=None, additionalfilter='All', userid=None): + requests = FOIRawRequest.getrequestspagination(groups, page, size, sortingitems, sortingorders, filterfields, keyword, additionalfilter, userid, AuthHelper.isiaorestrictedfilemanager(), AuthHelper.getusertype()) + requestqueue = [] + for request in requests.items: + + if(request.receivedDateUF is None): #request from online form has no received date in json + _receiveddate = maya.parse(request.created_at).datetime(to_timezone='America/Vancouver', naive=False) + else: + _receiveddate = parser.parse(request.receivedDateUF) + + if(request.ministryrequestid == None): + unopenrequest = self.__preparefoirequestinfo(request, _receiveddate.strftime(SHORT_DATEFORMAT), _receiveddate.strftime(LONG_DATEFORMAT), idnumberprefix= 'U-00') + unopenrequest.update({'assignedToFormatted': request.assignedToFormatted}) + unopenrequest.update({'isiaorestricted': request.isiaorestricted}) + + # isawatcher = FOIRawRequestWatcher.isawatcher(request.id,userid) + if request.isiaorestricted == True: + unopenrequest.update({'lastName': 'Restricted'}) + unopenrequest.update({'firstName': 'Request'}) + + requestqueue.append(unopenrequest) + + else: + _openrequest = self.__preparefoirequestinfo(request, _receiveddate.strftime(SHORT_DATEFORMAT), _receiveddate.strftime(LONG_DATEFORMAT)) + _openrequest.update({'ministryrequestid': request.ministryrequestid}) + _openrequest.update({'extensions': request.extensions}) + _openrequest.update({'assignedToFormatted': request.assignedToFormatted}) + _openrequest.update({'ministryAssignedToFormatted': request.ministryAssignedToFormatted}) + + isiaorestricted = request.isiaorestricted if request.isiaorestricted == True else False + _openrequest.update({'isiaorestricted': isiaorestricted}) + + if isiaorestricted == True: + _openrequest.update({'lastName': 'Restricted'}) + _openrequest.update({'firstName': 'Request'}) + + requestqueue.append(_openrequest) + + + meta = { + 'page': requests.page, + 'pages': requests.pages, + 'total': requests.total, + 'prev_num': requests.prev_num, + 'next_num': requests.next_num, + 'has_next': requests.has_next, + 'has_prev': requests.has_prev, + } + + + + + return jsonify({'data': requestqueue, 'meta': meta}) + + def getministryrequestqueuepagination (self, groups=None, page=1, size=10, sortingitems=[], sortingorders=[], filterfields=[], keyword=None, additionalfilter='All', userid=None): + requests = FOIMinistryRequest.getrequestspagination(groups, page, size, sortingitems, sortingorders, filterfields, keyword, additionalfilter, userid, AuthHelper.isiaorestrictedfilemanager(), AuthHelper.isministryrestrictedfilemanager()) + + requestqueue = [] + for request in requests.items: + _openrequest = self.__preparebaserequestinfo(request.id, request.requestType, request.currentState, + request.receivedDate, request.receivedDateUF, request.assignedGroup, + request.assignedTo, request.idNumber, request.axisRequestId, request.version, + request.description, request.recordsearchfromdate, request.recordsearchtodate) + _openrequest.update({'assignedministrygroup': request.assignedministrygroup}) + _openrequest.update({'assignedministryperson': request.assignedministryperson}) + _openrequest.update({'cfrstatus':'Select Division'}) + _openrequest.update({'cfrduedate': request.cfrduedate}) + _openrequest.update({'duedate': request.duedate}) + _openrequest.update({'ministryrequestid': request.ministryrequestid}) + _openrequest.update({'applicantcategory': request.applicantcategory}) + _openrequest.update({'bcgovcode': request.bcgovcode}) + _openrequest.update({'assignedToFirstName': request.assignedToFirstName}) + _openrequest.update({'assignedToLastName': request.assignedToLastName}) + _openrequest.update({'assignedministrypersonFirstName': request.assignedministrypersonFirstName}) + _openrequest.update({'assignedministrypersonLastName': request.assignedministrypersonLastName}) + _openrequest.update({'assignedToFormatted': request.assignedToFormatted}) + _openrequest.update({'ministryAssignedToFormatted': request.ministryAssignedToFormatted}) + + isministryrestricted = request.isministryrestricted if request.isministryrestricted == True else False + _openrequest.update({'isministryrestricted': isministryrestricted}) + isoipcreview = request.isoipcreview if request.isoipcreview == True else False + _openrequest.update({'isoipcreview': isoipcreview}) + requestqueue.append(_openrequest) + + meta = { + 'page': requests.page, + 'pages': requests.pages, + 'total': requests.total, + 'prev_num': requests.prev_num, + 'next_num': requests.next_num, + 'has_next': requests.has_next, + 'has_prev': requests.has_prev, + } + + return jsonify({'data': requestqueue, 'meta': meta}) + + def advancedsearch(self, params={'usertype': 'iao', 'groups':None, 'page':1, 'size':10, 'sortingitems':[], 'sortingorders':[], 'requeststate':[], 'requeststatus':[], 'requesttype':[], 'requestflags':[], 'publicbody':[], 'daterangetype':None, 'fromdate':None, 'todate':None, 'search':None, 'keywords':[], 'userid':None}): + userid = AuthHelper.getuserid() + + if (params['usertype'] == "iao"): + requests = FOIRawRequest.advancedsearch(params, userid, AuthHelper.isiaorestrictedfilemanager()) + else: + requests = FOIMinistryRequest.advancedsearch(params, userid, AuthHelper.isministryrestrictedfilemanager()) + + requestqueue = [] + for request in requests.items: + if(request.receivedDateUF is None): #request from online form has no received date in json + _receiveddate = maya.parse(request.created_at).datetime(to_timezone='America/Vancouver', naive=False) + else: + _receiveddate = parser.parse(request.receivedDateUF) + + if(request.ministryrequestid == None): + unopenrequest = self.__preparefoirequestinfo(request, _receiveddate.strftime(SHORT_DATEFORMAT), _receiveddate.strftime(LONG_DATEFORMAT), idnumberprefix= 'U-00') + unopenrequest.update({'description':request.description}) + unopenrequest.update({'assignedToFormatted': request.assignedToFormatted}) + unopenrequest.update({'isiaorestricted': request.isiaorestricted}) + + requestqueue.append(unopenrequest) + else: + _openrequest = self.__preparefoirequestinfo(request, _receiveddate.strftime(SHORT_DATEFORMAT), _receiveddate.strftime(LONG_DATEFORMAT)) + _openrequest.update({'ministryrequestid':request.ministryrequestid}) + _openrequest.update({'extensions': request.extensions}) + _openrequest.update({'description':request.description}) + _openrequest.update({'assignedToFormatted': request.assignedToFormatted}) + _openrequest.update({'ministryAssignedToFormatted': request.ministryAssignedToFormatted}) + + isiaorestricted = request.isiaorestricted if request.isiaorestricted == True else False + _openrequest.update({'isiaorestricted': isiaorestricted}) + + requestqueue.append(_openrequest) + + meta = { + 'page': requests.page, + 'pages': requests.pages, + 'total': requests.total, + 'prev_num': requests.prev_num, + 'next_num': requests.next_num, + 'has_next': requests.has_next, + 'has_prev': requests.has_prev, + } + + return jsonify({'data': requestqueue, 'meta': meta}) + + def __getidnumber(self, idprefix, axisrequestid, filenumber): + if axisrequestid is not None: + return axisrequestid + elif idprefix: + return idprefix + filenumber + return "" \ No newline at end of file diff --git a/historical-search-api/request_api/services/deliverymodeservice.py b/historical-search-api/request_api/services/deliverymodeservice.py new file mode 100644 index 000000000..754824183 --- /dev/null +++ b/historical-search-api/request_api/services/deliverymodeservice.py @@ -0,0 +1,8 @@ +from request_api.models.DeliveryModes import DeliveryMode + +class deliverymodeservice: + + def getdeliverymodes(self): + """ Returns the active records + """ + return DeliveryMode.getdeliverymodes() \ No newline at end of file diff --git a/historical-search-api/request_api/services/divisionstageservice.py b/historical-search-api/request_api/services/divisionstageservice.py new file mode 100644 index 000000000..c62a8637c --- /dev/null +++ b/historical-search-api/request_api/services/divisionstageservice.py @@ -0,0 +1,68 @@ +from request_api.models.ProgramAreas import ProgramArea +from request_api.models.ProgramAreaDivisions import ProgramAreaDivision +from request_api.models.ProgramAreaDivisionStages import ProgramAreaDivisionStage +import json + +class divisionstageservice: + + def getdivisionandstages(self, bcgovcode): + divisionstages = [] + programarea = ProgramArea.getprogramarea(bcgovcode) + divisions = ProgramAreaDivision.getprogramareadivisions(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 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 = [] + division_stages = ProgramAreaDivisionStage.getprogramareadivisionstages() + for stage in division_stages: + if stage['isactive'] == True: + activestages.append({"stageid": stage['stageid'], "name": self.escapestr(stage['name'])}) + return activestages + + def escapestr(self,value): + return value.replace(u"’", u"'") \ No newline at end of file diff --git a/historical-search-api/request_api/services/document_generation_service.py b/historical-search-api/request_api/services/document_generation_service.py new file mode 100644 index 000000000..0cb8e9ed8 --- /dev/null +++ b/historical-search-api/request_api/services/document_generation_service.py @@ -0,0 +1,70 @@ +# Copyright © 2019 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Service for receipt generation.""" + +from flask import current_app +from request_api.exceptions import BusinessException, Error +from request_api.models import DocumentTemplate, DocumentType +from request_api.services.cdogs_api_service import CdogsApiService +from request_api.services.external.storageservice import storageservice +from request_api.services.email.templates.templateconfig import templateconfig +from request_api.services.documentservice import documentservice +import json +import logging + + +class DocumentGenerationService: + """document generation Service class.""" + + def __init__(self,documenttypename='receipt'): + self.cdgos_api_service = CdogsApiService() + self.documenttypename = documenttypename + receipt_document_type : DocumentType = DocumentType.get_document_type_by_name(self.documenttypename) + if receipt_document_type is None: + raise BusinessException(Error.DATA_NOT_FOUND) + + self.receipt_template : DocumentTemplate = DocumentTemplate \ + .get_template_by_type(document_type_id = receipt_document_type.document_type_id) + if self.receipt_template is None: + raise BusinessException(Error.DATA_NOT_FOUND) + + + def generate_receipt(self, data, receipt_template_path='request_api/receipt_templates/receipt_word.docx'): + template_cached = False + if self.receipt_template.cdogs_hash_code: + current_app.logger.info('Checking if template %s is cached', self.receipt_template.cdogs_hash_code) + template_cached = self.cdgos_api_service.check_template_cached(self.receipt_template.cdogs_hash_code) + + if self.receipt_template.cdogs_hash_code is None or not template_cached: + current_app.logger.info('Uploading new template') + self.receipt_template.cdogs_hash_code = self.cdgos_api_service.upload_template(receipt_template_path) + self.receipt_template.flush() + self.receipt_template.commit() + + current_app.logger.info('Generating receipt') + return self.cdgos_api_service.generate_receipt(template_hash_code= self.receipt_template.cdogs_hash_code, data= data) + + def upload_receipt(self, filename, filebytes, ministryrequestid, ministrycode, filenumber, attachmentcategory): + try: + logging.info("Upload receipt for ministry request id"+ str(ministryrequestid)) + _response = storageservice().uploadbytes(filename, filebytes, ministrycode, filenumber) + logging.info("Upload status for payload"+ json.dumps(_response)) + if _response["success"] == True: + _documentschema = {"documents": [{"filename": _response["filename"], "documentpath": _response["documentpath"], "category": templateconfig().getattachmentcategory(attachmentcategory)}]} + documentservice().createrequestdocument(ministryrequestid, _documentschema, "SYSTEM", "ministryrequest") + return _response + except Exception as ex: + logging.exception(ex) \ No newline at end of file diff --git a/historical-search-api/request_api/services/documentservice.py b/historical-search-api/request_api/services/documentservice.py new file mode 100644 index 000000000..3fd2c72d1 --- /dev/null +++ b/historical-search-api/request_api/services/documentservice.py @@ -0,0 +1,211 @@ + +from os import stat +import os +from re import VERBOSE +from request_api.models.FOIMinistryRequestDocuments import FOIMinistryRequestDocument +from request_api.models.FOIRawRequestDocuments import FOIRawRequestDocument +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from request_api.models.FOIRawRequests import FOIRawRequest +from request_api.schemas.foidocument import CreateDocumentSchema +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 +import maya + +class documentservice: + """ FOI Document management service + """ + + def getrequestdocuments(self, requestid, requesttype, version=None): + requestversion = self.__getversionforrequest(requestid,requesttype) if version is None else version + documents = FOIMinistryRequestDocument.getdocuments(requestid, requestversion) if requesttype == "ministryrequest" else FOIRawRequestDocument.getdocuments(requestid, requestversion) + return self.__formatcreateddate(documents) + + def getactiverequestdocuments(self, requestid, requesttype, version=None): + requestversion = self.__getversionforrequest(requestid,requesttype) if version is None else version + documents = FOIMinistryRequestDocument.getactivedocuments(requestid) if requesttype == "ministryrequest" else FOIRawRequestDocument.getdocuments(requestid, requestversion) + return self.__formatcreateddate(documents) + + 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 document["category"] != "applicant": + filtereddocuments.append(document) + return filtereddocuments + return documents + + def createrequestdocument(self, requestid, documentschema, userid, requesttype): + if requesttype == "ministryrequest": + return self.createministryrequestdocument(requestid, documentschema, userid) + else: + return self.createrawrequestdocument(requestid, documentschema, userid) + + def createrequestdocumentversion(self, requestid, documentid, documentschema, userid, requesttype): + if requesttype == "ministryrequest": + return self.createministrydocumentversion(requestid, documentid, documentschema, userid) + 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) + + def copyrequestdocuments(self, ministryrequestid, documents, userid): + """ Copies request documents upon updates + """ + _documents = [] + for document in documents: + _documents.append({"documentpath":document["documentpath"],"filename":document["filename"],"category":document["category"], "created_at":document["created_at"],"createdby": document["createdby"]}) + documentschema = {"documents": _documents} + return self.createministryrequestdocument(ministryrequestid, documentschema, userid) + + def createministryrequestdocument(self, ministryrequestid, documentschema, userid): + version = self.__getversionforrequest(ministryrequestid, "ministryrequest") + return FOIMinistryRequestDocument.createdocuments(ministryrequestid, version, documentschema['documents'], userid) + + def createrawrequestdocument(self, requestid, documentschema, userid): + version = self.__getversionforrequest(requestid, "rawrequest") + return FOIRawRequestDocument.createdocuments(requestid, version, documentschema['documents'], userid) + + def createministrydocumentversion(self, ministryrequestid, documentid, documentschema, userid): + version = self.__getversionforrequest(ministryrequestid, "ministryrequest") + document = FOIMinistryRequestDocument.getdocument(documentid) + if document: + 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: + return FOIMinistryRequestDocument.createdocument(ministryrequestid, version, documentschema, userid) + + + def createrawdocumentversion(self, requestid, documentid, documentschema, userid): + version = self.__getversionforrequest(requestid, "rawrequest") + document = FOIRawRequestDocument.getdocument(documentid) + FOIRawRequestDocument.deActivaterawdocumentsversion(documentid, document['version'], userid) + return FOIRawRequestDocument.createdocumentversion(requestid, version, self.__copydocumentproperties(document,documentschema,document['version']), userid) + + def createrawrequestdocumentversion(self, requestid): + newversion = self.__getversionforrequest(requestid,"rawrequest") + documents = self.getrequestdocuments(requestid, "rawrequest", newversion-1) + documentarr = [] + for document in documents: + documentarr.append({"documentpath": document["documentpath"], "filename": document["filename"], "category": document['category'], "version": 1, "foirequest_id": requestid, "foirequestversion_id": newversion - 1, "createdby": document['createdby'], "created_at": document['created_at'] }) + return self.createrawrequestdocument(requestid, {"documents": documentarr}, None) + + def uploadpersonaldocuments(self, requestid, attachments): + attachmentlist = [] + if attachments: + for attachment in attachments: + attachment['filestatustransition'] = 'personal' + attachment['ministrycode'] = 'Misc' + attachment['requestnumber'] = str(requestid) + attachment['file'] = base64.b64decode(attachment['base64data']) + attachment.pop('base64data') + attachmentresponse = storageservice().upload(attachment) + attachmentlist.append(attachmentresponse) + + documentschema = CreateDocumentSchema().load({'documents': attachmentlist}) + return self.createrequestdocument(requestid, documentschema, None, "rawrequest") + + def getattachments(self, requestid, requesttype, category): + documents = self.getlatestdocumentsforemail(requestid, requesttype, category) + return self.__getattachmentlist(documents) + + def getreceiptattachments(self, ministryrequestid, category): + _documents = FOIMinistryRequestDocument().getlatestreceiptdocumentforemail(ministryrequestid, category) + documents = self.__formatcreateddate(_documents) + return self.__getattachmentlist(documents) + + def getlatestdocumentsforemail(self, requestid, requesttype, category, version=None): + requestversion = self.__getversionforrequest(requestid,requesttype) if version is None else version + documents = FOIMinistryRequestDocument.getlatestdocumentsforemail(requestid, requestversion, category) if requesttype == "ministryrequest" else FOIRawRequestDocument.getdocuments(requestid, requestversion) + return self.__formatcreateddate(documents) + + def getapplicantcorrespondenceattachmentsbyapplicantcorrespondenceid(self, applicantcorrespondenceid): + documents = FOIApplicantCorrespondenceAttachment.getapplicantcorrespondenceattachmentsbyapplicantcorrespondenceid(applicantcorrespondenceid) + if(documents is None): + raise ValueError('No template found') + attachmentlist = [] + for document in documents: + filename = document.get('attachmentfilename') + s3uri = document.get('attachmentdocumenturipath') + attachment= storageservice().download(s3uri) + attachdocument = {"filename": filename, "file": attachment, "url": s3uri} + attachmentlist.append(attachdocument) + return attachmentlist + + def __getattachmentlist(self, documents): + if(documents is None): + raise ValueError('No template found') + attachmentlist = [] + for document in documents: + filename = document.get('filename') + s3uri = document.get('documentpath') + attachment= storageservice().download(s3uri) + attachdocument = {"filename": filename, "file": attachment, "url": s3uri} + attachmentlist.append(attachdocument) + return attachmentlist + + def __getversionforrequest(self, requestid, requesttype): + """ Returns the active version of the request id based on type. + """ + if requesttype == "ministryrequest": + document = FOIMinistryRequest.getversionforrequest(requestid) + else: + document = FOIRawRequest.getversionforrequest(requestid) + if document: + return document[0] + + def __copydocumentproperties(self, document, documentschema, version): + document['version'] = version +1 + document['filename'] = documentschema['filename'] if 'filename' in documentschema else document['filename'] + document['documentpath'] = documentschema['documentpath'] if 'documentpath' in documentschema else document['documentpath'] + document['category'] = documentschema['category'] if 'category' in documentschema else document['category'] + document['isactive'] = documentschema['isactive'] if 'isactive' in documentschema else True + document['created_at'] = documentschema['created_at'] if 'created_at' in documentschema else None + document['createdby'] = documentschema['createdby'] if 'createdby' in documentschema else None + return document + + def __formatcreateddate(self, documents): + for document in documents: + document = self.__pstformat(document) + return documents + + def __pstformat(self, document): + formatedcreateddate = maya.parse(document['created_at']).datetime(to_timezone='America/Vancouver', naive=False) + document['created_at'] = formatedcreateddate.strftime('%Y %b %d | %I:%M %p') + return document + + + diff --git a/historical-search-api/request_api/services/email/inboxservice.py b/historical-search-api/request_api/services/email/inboxservice.py new file mode 100644 index 000000000..43a1a8d2e --- /dev/null +++ b/historical-search-api/request_api/services/email/inboxservice.py @@ -0,0 +1,59 @@ + +from os import stat +import imaplib +import os +from imap_tools import MailBox, AND +import logging +import email + +MAIL_SERVER_IMAP = os.getenv('EMAIL_SERVER_IMAP') +MAIL_SRV_USERID = os.getenv('EMAIL_SRUSERID') +MAIL_SRV_PASSWORD = os.getenv('EMAIL_SRPWD') + +class inboxservice: + + """ FOI Email Inbox Service + + This service wraps up inbox operations. + + """ + + def get_failure_deliverystatus_as_eml(self, subject, email): + message = self._get_deliverystatus_by_text(subject, email) + if message is not None: + return {"success" : False, "message": "Unable to send", "content": message["content"].obj.__bytes__(), "reason": message["reason"]} + return {"success" : True, "message": "Sent successfully", "content": None, "reason": None} + + def _get_deliverystatus_by_text(self, msgkey, text): + try: + mailbox = MailBox(MAIL_SERVER_IMAP) + mailbox.login(MAIL_SRV_USERID, MAIL_SRV_PASSWORD) + messages = mailbox.fetch(criteria=AND(text=text), reverse = True) + for message in messages: + email_message = email.message_from_bytes(message.obj.__bytes__()).as_string() + if text in email_message and msgkey in email_message: + _reasoncode = 500 + for code in self.__getmanagederrorcodes(): + if code in message.text: + _reasoncode = code + return {"code": _reasoncode, "reason": self.__getreason(_reasoncode), "content": message} + return None + except Exception as ex: + logging.exception(ex) + logging.debug('Read email for '+text) + + + def __getmanagederrorcodes(self): + return ["541", "550", "550 5.1.1", "551", "552"] + + def __getreason(self, code): + if code == "541": + return "Message rejected by the recipient address" + elif str(code).startswith("550"): + return "The email account that you tried to reach does not exist. Please try double-checking the recipient's email address for typos or unnecessary spaces" + elif code == "551": + return "Intended recipient mailbox isn't available on the receiving server" + elif code == "552": + return "Message wasn't sent because the recipient mailbox doesn't have enough storage" + else: + return "Server could not complete the action" \ No newline at end of file diff --git a/historical-search-api/request_api/services/email/senderservice.py b/historical-search-api/request_api/services/email/senderservice.py new file mode 100644 index 000000000..4c5034e4a --- /dev/null +++ b/historical-search-api/request_api/services/email/senderservice.py @@ -0,0 +1,85 @@ + +from audioop import reverse +from os import stat +import os +import smtplib +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from email.mime.base import MIMEBase +from email import encoders +from request_api.services.email.templates.templateconfig import templateconfig +import imaplib +from imap_tools import MailBox, AND +import logging +import email +import json +from request_api.services.external.storageservice import storageservice + +MAIL_SERVER_SMTP = os.getenv('EMAIL_SERVER_SMTP') +MAIL_SERVER_SMTP_PORT = os.getenv('EMAIL_SERVER_SMTP_PORT') +MAIL_SERVER_IMAP = os.getenv('EMAIL_SERVER_IMAP') +MAIL_SRV_USERID = os.getenv('EMAIL_SRUSERID') +MAIL_SRV_PASSWORD = os.getenv('EMAIL_SRPWD') +MAIL_FROM_ADDRESS = os.getenv('EMAIL_SENDER_ADDRESS') +MAIL_FOLDER_OUTBOX = os.getenv('EMAIL_FOLDER_OUTBOX') +MAIL_FOLDER_INBOX = os.getenv('EMAIL_FOLDER_INBOX') +class senderservice: + """ Email Sender service + + This service wraps up send operations. + + """ + + def send(self, subject, content, _messageattachmentlist, requestjson): + logging.debug("Begin: Send email for request = "+json.dumps(requestjson)) + msg = MIMEMultipart() + msg['From'] = MAIL_FROM_ADDRESS + msg['To'] = requestjson["email"] + msg['Subject'] = subject + part = MIMEText(content, "html") + msg.attach(part) + # Add Attachment and Set mail headers + for attachment in _messageattachmentlist: + part = MIMEBase("application", "octet-stream") + part.set_payload(attachment.get('file').content) + encoders.encode_base64(part) + part.add_header( + "Content-Disposition", + "attachment", filename= attachment.get('filename') + ) + msg.attach(part) + try: + with smtplib.SMTP(MAIL_SERVER_SMTP, MAIL_SERVER_SMTP_PORT) as smtpobj: + smtpobj.ehlo() + smtpobj.starttls() + smtpobj.ehlo() + #smtpobj.login(MAIL_SRV_USERID, MAIL_SRV_PASSWORD) + smtpobj.sendmail(msg['From'], msg['To'], msg.as_string()) + smtpobj.quit() + logging.debug("End: Send email for request = "+json.dumps(requestjson)) + return {"success" : True, "message": "Sent successfully"} + except Exception as e: + logging.exception(e) + return {"success" : False, "message": "Unable to send"} + + + def read_outbox_as_bytes(self, servicekey, requestjson): + logging.debug("Begin: Read sent item for request = "+json.dumps(requestjson)) + _subject = templateconfig().getsubject(servicekey, requestjson) + try: + mailbox = MailBox(MAIL_SERVER_IMAP) + mailbox.login(MAIL_SRV_USERID, MAIL_SRV_PASSWORD) + #Navigate to sent Items + is_exists = mailbox.folder.exists(MAIL_FOLDER_OUTBOX) + if is_exists == True: + mailbox.folder.set(MAIL_FOLDER_OUTBOX) + messages = mailbox.fetch(criteria=AND(subject=_subject), reverse = True) + for message in messages: + logging.debug("End: Read sent item for request = "+json.dumps(requestjson)) + return message.obj.__bytes__() + except Exception as ex: + logging.exception(ex) + logging.debug("End: Read sent item for request = "+json.dumps(requestjson)) + return None + + diff --git a/historical-search-api/request_api/services/email/templates/templateconfig.py b/historical-search-api/request_api/services/email/templates/templateconfig.py new file mode 100644 index 000000000..b307a2cfe --- /dev/null +++ b/historical-search-api/request_api/services/email/templates/templateconfig.py @@ -0,0 +1,93 @@ +import json +import logging + +class templateconfig: + """This class is reserved for consolidating all templates + """ + + def gettemplatename(self, key): + if key == "PAYONLINE": + return "fee_estimate_notification.html" + elif key == "HALFPAYMENT": + return "fee_payment_confirmation_half.html" + elif key == "FULLPAYMENT": + return "fee_payment_confirmation_full.html" + elif key == "PAYOUTSTANDING": + return "fee_estimate_notification_outstanding.html" + elif key == "PAYOUTSTANDINGFULLPAYMENT": + return "fee_payment_confirmation_outstanding.html" + else: + logging.info("Unknown key") + return None + + def getsubject(self, key, requestjson): + if key == "PAYONLINE" or key == "PAYOUTSTANDING" or key == "FEEESTIMATENOTIFICATION" or key == "OUTSTANDINGFEEESTIMATENOTIFICATION": + return "Your FOI Request ["+requestjson["axisRequestId"]+"]" + elif key == "FEE-ESTIMATE-PAYMENT-RECEIPT" or key == "OUTSTANDING-PAYMENT-RECEIPT": + return "Your FOI Request ["+requestjson["axisRequestId"]+"] - Fee Payment Received" + return None + + def getstage(self, key): + if key == "PAYONLINE" or key == "PAYOUTSTANDING": + return "Fee Estimate" + elif key == "FEE-ESTIMATE-PAYMENT-RECEIPT": + return "Fee Estimate - Payment Receipt" + elif key == "OUTSTANDING-PAYMENT-RECEIPT": + return "Fees - Balance Outstanding - Payment Receipt" + return None + + def getattachmentname(self, key): + if key == "PAYONLINE": + return "Fees - Estimate Sent" + elif key == "PAYONLINE-SEND-FAILURE": + return "Fees - Estimate Correspondence Failed" + if key == "FEE-ESTIMATE-PAYMENT-RECEIPT": + return "Fee Estimate - Payment Receipt Sent" + elif key == "FEE-ESTIMATE-PAYMENT-RECEIPT-FAILURE": + return "Fee Estimate - Payment Receipt Correspondence Failed" + elif key == "PAYOUTSTANDING": + return "Fees - Balance Outstanding Sent" + elif key == "PAYOUTSTANDING-SEND-FAILURE": + return "Fees - Balance Outstanding Correspondence Failed" + if key == "OUTSTANDING-PAYMENT-RECEIPT": + return "Fees - Balance Outstanding - Payment Receipt Sent" + elif key == "OUTSTANDING-PAYMENT-RECEIPT-FAILURE": + return "Fees - Balance Outstanding - Payment Receipt Correspondence Failed" + return None + + def getattachmentcategory(self, key): + if key == "PAYONLINE" or key == "FEEESTIMATENOTIFICATION": + return "FEEASSESSED-ONHOLD" + elif key == "PAYONLINE-SUCCESSFUL" or key == "FEEESTIMATENOTIFICATION-SUCCESSFUL": + return "Fee Estimate - Letter" + elif key == "PAYONLINE-FAILED" or key == "FEEESTIMATENOTIFICATION-FAILED": + return "Fee Estimate - Correspondence Failed" + elif key == "FEE-ESTIMATE-PAYMENT-RECEIPT": + return "Fee Estimate - Payment Receipt" + elif key == "FEE-ESTIMATE-PAYMENT-RECEIPT-SUCCESSFUL": + return "Fee Estimate - Payment Success" + elif key == "FEE-ESTIMATE-PAYMENT-RECEIPT-FAILED": + return "Fee Estimate - Payment Success - Correspondence Failed" + elif key == "PAYOUTSTANDING": + return "RESPONSE-ONHOLD" + elif key == "PAYOUTSTANDING-SUCCESSFUL": + return "Fee Balance Outstanding - Letter" + elif key == "PAYOUTSTANDING-FAILED": + return "Fee Balance Outstanding - Correspondence Failed" + elif key == "OUTSTANDING-PAYMENT-RECEIPT": + return "Fee Balance Outstanding - Payment Receipt" + elif key == "OUTSTANDING-PAYMENT-RECEIPT-SUCCESSFUL": + return "Fee Balance Outstanding - Payment Success" + elif key == "OUTSTANDING-PAYMENT-RECEIPT-FAILED": + return "Fee Balance Outstanding - Payment Success - Correspondence Failed" + return None + + + def isnotreceipt(self, key): + if key == "FEE-ESTIMATE-PAYMENT-RECEIPT" or key == "OUTSTANDING-PAYMENT-RECEIPT": + return False + return True + + + + \ No newline at end of file diff --git a/historical-search-api/request_api/services/email/templates/templatefilters.py b/historical-search-api/request_api/services/email/templates/templatefilters.py new file mode 100644 index 000000000..1d6f64c37 --- /dev/null +++ b/historical-search-api/request_api/services/email/templates/templatefilters.py @@ -0,0 +1,11 @@ +import jinja2 +from request_api.utils.commons.datetimehandler import datetimehandler +from datetime import datetime + +def formatdate(value, format): + return datetimehandler().formatdate(value, format) + +def init_filters(): + jinja2.filters.FILTERS['formatdate'] = formatdate + + \ No newline at end of file diff --git a/historical-search-api/request_api/services/email/templates/templateservice.py b/historical-search-api/request_api/services/email/templates/templateservice.py new file mode 100644 index 000000000..ef9d64125 --- /dev/null +++ b/historical-search-api/request_api/services/email/templates/templateservice.py @@ -0,0 +1,97 @@ +from jinja2 import Template +from request_api.services.external.storageservice import storageservice +from request_api.services.email.templates.templateconfig import templateconfig +from request_api.services.requestservice import requestservice +from request_api.services.applicantcorrespondence.applicantcorrespondencelog import applicantcorrespondenceservice +from request_api.models.ApplicationCorrespondenceTemplates import ApplicationCorrespondenceTemplate +import json +import logging +from flask import current_app +from request_api.services.email.templates.templatefilters import init_filters + +init_filters() + +class templateservice: + """This class is reserved for jinja templating services integration. + """ + + + def generate_by_servicename_and_id(self, servicename, requestid, ministryrequestid): + try: + _request = requestservice().getrequestdetails(requestid,ministryrequestid) + requestjson = json.dumps(_request) + return self.generate_by_servicename_and_schema(servicename, requestjson, ministryrequestid) + except Exception as ex: + logging.exception(ex) + return None + + def generate_by_servicename_and_schema(self, servicename, requestjson, ministryrequestid, applicantcorrespondenceid = None): + try: + _template = self.__gettemplate(servicename) + if _template is None: + _templatename = self.__gettemplatenamewrapper(servicename, requestjson, ministryrequestid) + _template = self.__gettemplate(_templatename) + if (applicantcorrespondenceid and applicantcorrespondenceid != 0 and templateconfig().isnotreceipt(servicename)): + emailtemplatehtml = self.__generatecorrespondencetetemplate(applicantcorrespondenceid) + else: + emailtemplatehtml= storageservice().downloadtemplate(_template.documenturipath) + return self.__generatetemplate(requestjson, emailtemplatehtml, _template.description) + except Exception as ex: + logging.exception(ex) + return None + + def __gettemplatenamewrapper(self, servicename, requestjson, ministryrequestid): + _templatename = templateconfig().gettemplatename(servicename) + if _templatename is None: + _latesttemplatename = self.__getlatesttemplatename(ministryrequestid) + if requestjson is not None and requestjson != {}: + balancedue = float(requestjson['cfrfee']['feedata']["balanceDue"]) + prevstate = self.__getprevstate(requestjson) + if balancedue > 0: + return "HALFPAYMENT" + + elif balancedue == 0: + templatekey = "FULLPAYMENT" + if prevstate.lower() == "response" or (_latesttemplatename and _latesttemplatename == 'PAYOUTSTANDING'): + templatekey = "PAYOUTSTANDINGFULLPAYMENT" + return templatekey + + return _templatename + + def __getlatesttemplatename(self, ministryrequestid): + latestcorrespondence = applicantcorrespondenceservice().getlatestapplicantcorrespondence(ministryrequestid) + _latesttemplateid = latestcorrespondence['templateid'] if 'templateid' in latestcorrespondence else None + if _latesttemplateid: + return applicantcorrespondenceservice().gettemplatebyid(_latesttemplateid).name + return None + + def __getprevstate(self, requestjson): + return requestjson["stateTransition"][2]["status"] if "stateTransition" in requestjson and len(requestjson["stateTransition"]) > 3 else None + + def __gettemplate(self, templatename): + return ApplicationCorrespondenceTemplate.get_template_by_name(templatename) + + def __generatetemplate(self, dynamictemplatevalues, emailtemplatehtml, title): + dynamictemplatevalues["ffaurl"] = current_app.config['FOI_FFA_URL'] + headerfooterhtml = storageservice().downloadtemplate('/TEMPLATES/EMAILS/header_footer_template.html') + if(emailtemplatehtml is None): + raise ValueError('No template found') + + if(dynamictemplatevalues is None): + raise ValueError('Values not found') + + if dynamictemplatevalues["assignedTo"] == None: + dynamictemplatevalues["assignedToFirstName"] = "" + dynamictemplatevalues["assignedToLastName"] = "" + dynamictemplatevalues["assignedGroupEmail"] = "" + + contenttemplate = Template(emailtemplatehtml) + content = contenttemplate.render(dynamictemplatevalues) + dynamictemplatevalues["content"] = content + dynamictemplatevalues['title'] = title + finaltemplate = Template(headerfooterhtml) + finaltemplatedhtml = finaltemplate.render(dynamictemplatevalues) + return finaltemplatedhtml, content + + def __generatecorrespondencetetemplate(self, applicantcorrespondenceid): + return applicantcorrespondenceservice().getapplicantcorrespondencelogbyid(applicantcorrespondenceid) diff --git a/historical-search-api/request_api/services/email/templatevaluebuilderservice.py b/historical-search-api/request_api/services/email/templatevaluebuilderservice.py new file mode 100644 index 000000000..35d50fcbf --- /dev/null +++ b/historical-search-api/request_api/services/email/templatevaluebuilderservice.py @@ -0,0 +1,33 @@ +from jinja2 import Template +from request_api.services.external.storageservice import storageservice +import json + +class templatevaluebuilderservice: + """This class is reserved for value builder service + """ + + + def preparemetadata(self, data): + return { + "to": data[""], + "cc": data[""], + "bcc": data[""], + "subject": data[""], + "body": data[""] + } + + def preparevalue(self, emailtemplatename, dynamictemplatevalues): + + emailtemplatehtml= storageservice().download(emailtemplatename) + if(emailtemplatehtml is None): + raise ValueError('No template found') + + if(dynamictemplatevalues is None): + raise ValueError('Values not found') + + template = Template(emailtemplatehtml) + dynamicvalues = dict(dynamictemplatevalues) + templatedhtml = template.render(dynamicvalues) + return templatedhtml + + diff --git a/historical-search-api/request_api/services/emailservice.py b/historical-search-api/request_api/services/emailservice.py new file mode 100644 index 000000000..6df55855c --- /dev/null +++ b/historical-search-api/request_api/services/emailservice.py @@ -0,0 +1,102 @@ + +import os +from re import VERBOSE +import json +import email +import smtplib +import imaplib +from imap_tools import MailBox, AND +import logging +from request_api.services.documentservice import documentservice +from request_api.services.external.storageservice import storageservice +from request_api.services.email.templates.templateservice import templateservice +from request_api.services.email.templates.templateconfig import templateconfig +from request_api.services.email.senderservice import senderservice +from request_api.services.email.inboxservice import inboxservice +from request_api.services.eventservice import eventservice +from request_api.services.requestservice import requestservice +from request_api.services.applicantcorrespondence.applicantcorrespondencelog import applicantcorrespondenceservice +from request_api.utils.enums import ServiceName +import logging + +class emailservice: + """ FOI Email Service + """ + + def send(self, servicename, requestid, ministryrequestid, emailschema): + try: + requestjson = requestservice().getrequestdetails(requestid,ministryrequestid) + _templatename = self.__getvaluefromschema(emailschema, "templatename") + servicename = _templatename if servicename == ServiceName.correspondence.value.upper() else servicename + _applicantcorrespondenceid = self.__getvaluefromschema(emailschema, "applicantcorrespondenceid") + _messagepart, content = templateservice().generate_by_servicename_and_schema(servicename, requestjson, ministryrequestid, _applicantcorrespondenceid) + if (_applicantcorrespondenceid and templateconfig().isnotreceipt(servicename)): + servicename = _templatename.upper() if _templatename else "" + _messageattachmentlist = self.__get_attachments(ministryrequestid, emailschema, servicename) + self.__pre_send_correspondence_audit(requestid, ministryrequestid,emailschema, content, templateconfig().isnotreceipt(servicename), _messageattachmentlist) + subject = templateconfig().getsubject(servicename, requestjson) + return senderservice().send(subject, _messagepart, _messageattachmentlist, requestjson) + except Exception as ex: + logging.exception(ex) + + def acknowledge(self, servicename, requestid, ministryrequestid): + try: + requestjson = requestservice().getrequestdetails(requestid,ministryrequestid) + self.__upload_sent_email(servicename, ministryrequestid, requestjson) + ackresponse = inboxservice().get_failure_deliverystatus_as_eml(templateconfig().getsubject(servicename, requestjson), requestjson["email"]) + if ackresponse["success"] == False: + self.__upload(templateconfig().getattachmentname(servicename+"-SEND-FAILURE")+".eml", ackresponse["content"], ministryrequestid, requestjson, templateconfig().getattachmentcategory(servicename+"-FAILED")) + eventservice().posteventforemailfailure(ministryrequestid, "ministryrequest", templateconfig().getstage(servicename), ackresponse["reason"], requestjson["assignedTo"]) + + return {"success" : True, "message": "Acknowledgement successful"} + except Exception as ex: + logging.exception(ex) + return {"success" : False, "message": "Acknowledgement successful"} + + def __get_attachments(self, ministryrequestid, emailschema, servicename): + _messageattachmentlist = [] + _applicantcorrespondenceid = self.__getvaluefromschema(emailschema, "applicantcorrespondenceid") + if (_applicantcorrespondenceid and templateconfig().isnotreceipt(servicename)): + _messageattachmentlist = documentservice().getapplicantcorrespondenceattachmentsbyapplicantcorrespondenceid(_applicantcorrespondenceid) + elif templateconfig().isnotreceipt(servicename) is not True: + _messageattachmentlist = documentservice().getreceiptattachments(ministryrequestid, templateconfig().getattachmentcategory(servicename).lower()) + else: + _messageattachmentlist = documentservice().getattachments(ministryrequestid, 'ministryrequest', templateconfig().getattachmentcategory(servicename).lower()) + return _messageattachmentlist + + + def __pre_send_correspondence_audit(self, requestid, ministryrequestid, emailschema, content, isnotreceipt, attachmentlist=None): + _applicantcorrespondenceid = self.__getvaluefromschema(emailschema, "applicantcorrespondenceid") + if _applicantcorrespondenceid and isnotreceipt: + return applicantcorrespondenceservice().updateapplicantcorrespondencelog(_applicantcorrespondenceid, {"message": content}) + else: + data = { + "templateid": None, + "correspondencemessagejson": {"message": content}, + "attachments": attachmentlist + } + return applicantcorrespondenceservice().saveapplicantcorrespondencelog(requestid, ministryrequestid, data, 'system') + + + def __upload_sent_email(self, servicekey, ministryrequestid, requestjson): + try: + _originalmsg = senderservice().read_outbox_as_bytes(servicekey, requestjson) + if _originalmsg is not None: + return self.__upload(templateconfig().getattachmentname(servicekey)+".eml",_originalmsg, ministryrequestid, requestjson, templateconfig().getattachmentcategory(servicekey+"-SUCCESSFUL")) + except Exception as ex: + logging.exception(ex) + + def __upload(self, filename, filebytes, ministryrequestid, requestjson, category): + try: + logging.info("Upload file for payload"+ json.dumps(requestjson)) + _response = storageservice().uploadbytes(filename, filebytes, requestjson["bcgovcode"], requestjson["idNumber"]) + logging.info("Upload status for payload"+ json.dumps(_response)) + if _response["success"] == True: + _documentschema = {"documents": [{"filename": _response["filename"], "documentpath": _response["documentpath"], "category": category}]} + documentservice().createrequestdocument(ministryrequestid, _documentschema, "SYSTEM", "ministryrequest") + return _response + except Exception as ex: + logging.exception(ex) + + def __getvaluefromschema(self, emailschema, property): + return emailschema.get(property) if property in emailschema else None diff --git a/historical-search-api/request_api/services/events/__init__.py b/historical-search-api/request_api/services/events/__init__.py new file mode 100644 index 000000000..00409e2cb --- /dev/null +++ b/historical-search-api/request_api/services/events/__init__.py @@ -0,0 +1,15 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Exposes all of the Services used in the API.""" + diff --git a/historical-search-api/request_api/services/events/assignment.py b/historical-search-api/request_api/services/events/assignment.py new file mode 100644 index 000000000..538317aba --- /dev/null +++ b/historical-search-api/request_api/services/events/assignment.py @@ -0,0 +1,98 @@ + +from cmath import asin +from os import stat +from re import VERBOSE +from request_api.services.commentservice import commentservice +from request_api.services.notificationservice import notificationservice +from request_api.models.FOIRawRequests import FOIRawRequest +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from request_api.models.FOIRequestStatus import FOIRequestStatus +from request_api.models.NotificationTypes import NotificationType +import json +from request_api.models.default_method_result import DefaultMethodResult +from request_api.utils.enums import CommentType + +class assignmentevent: + """ FOI Event management service + + """ + def createassignmentevent(self, requestid, requesttype, userid, isministryuser,assigneename,username): + changeresp = self.__haschanged(requestid, requesttype) + ischanged = changeresp['ischanged'] + previousassignee = changeresp['previous'] + currentassignee = changeresp['current'] + commentrespose = self.__createcomment(assigneename,username,requestid,userid,requesttype) + if ischanged == True: + notificationservice().dismissnotifications_by_requestid_type_userid(requestid, requesttype, 'User Assignment Removal', currentassignee) + notificationresponse = self.__createnotification(requestid, requesttype, userid, isministryuser) + #Previous Assignee Notification + previousassigneenotification = None + if self.__isnoneorblank(previousassignee) == False and previousassignee != userid: + previousassigneenotification = self.__createnotificationforremoval(requestid, requesttype, userid, username, previousassignee) + if notificationresponse.success == True and commentrespose.success == True and \ + (previousassigneenotification is None or previousassigneenotification.success == True): + return DefaultMethodResult(True,'Assignment Notification, Comment has been created',requestid) + else: + return DefaultMethodResult(False,'unable to create notification and/or comment for assignment',requestid) + + return DefaultMethodResult(True,'No change',requestid) + + def __createnotification(self, requestid, requesttype, userid, isministryuser): + notification = self.__preparenotification() + notificationtype = NotificationType().getnotificationtypeid(self.__assignmenttype(isministryuser)) + return notificationservice().createnotification({"message" : notification}, requestid, requesttype, notificationtype, userid) + + def __createnotificationforremoval(self, requestid, requesttype, userid, username, previousassignee): + notification = self.__preparenotification(username, True) + return notificationservice().createusernotification({"message" : notification}, requestid, requesttype, 'User Assignment Removal', previousassignee, userid) + + def __preparenotification(self, username=None, isremoved=False): + return self.__notificationmessage(username,isremoved) + + def __haschanged(self, requestid, requesttype): + assignments = self.__getassignments(requestid, requesttype) + previousassignee = "" + currentassignee = "" + if len(assignments) == 1 and self.__isnoneorblank(assignments[0]) == False: + return {"ischanged": True, "previous": previousassignee, "current": currentassignee } + if len(assignments) == 2 and \ + ((assignments[0]['assignedto'] != assignments[1]['assignedto'] and self.__isnoneorblank(assignments[0]['assignedto']) == False) \ + or (requesttype == "ministryrequest" and \ + assignments[0]['assignedministryperson'] != assignments[1]['assignedministryperson'] \ + and self.__isnoneorblank(assignments[0]['assignedministryperson']) == False)): + previousassignee= assignments[1]['assignedto'] if assignments[0]['assignedto'] != assignments[1]['assignedto'] else assignments[1]['assignedministryperson'] + currentassignee= assignments[0]['assignedto'] if assignments[0]['assignedto'] != assignments[1]['assignedto'] else assignments[0]['assignedministryperson'] + return {"ischanged": True, "previous": previousassignee, "current": currentassignee } + return {"ischanged": False, "previous": previousassignee, "current": currentassignee } + + def __isnoneorblank(self, value): + if value is not None and value != '': + return False + return True + def __getassignments(self, requestid, requesttype): + if requesttype == "ministryrequest": + return FOIMinistryRequest.getassignmenttransition(requestid) + else: + return FOIRawRequest.getassignmenttransition(requestid) + + def __notificationmessage(self, username=None, isremoved=False): + if isremoved == True: + return username+' has removed your assignment to this request' + else: + return 'New Request Assigned to You.' + + def __assignmenttype(self, isministryuser): + return 'Ministry Assignment' if isministryuser == True else 'IAO Assignment' + + def __createcomment(self, assigneename, username,requestid,userid,requesttype): + if(assigneename !=''): + _commentmessage ='{0} assigned this request to {1}'.format(username,assigneename) + if requesttype == "ministryrequest": + comment = {"ministryrequestid": requestid, "comment": _commentmessage} + return commentservice().createministryrequestcomment(comment, userid, CommentType.SystemGenerated.value) + else: + comment = {"requestid": requestid, "comment": _commentmessage} + return commentservice().createrawrequestcomment(comment, userid, CommentType.SystemGenerated.value) + else: + return DefaultMethodResult(True,'No Assignee',requestid) + \ No newline at end of file diff --git a/historical-search-api/request_api/services/events/cfrdate.py b/historical-search-api/request_api/services/events/cfrdate.py new file mode 100644 index 000000000..6d742234a --- /dev/null +++ b/historical-search-api/request_api/services/events/cfrdate.py @@ -0,0 +1,76 @@ + +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.FOIMinistryRequests import FOIMinistryRequest +from request_api.models.FOIRequestComments import FOIRequestComment +from request_api.models.NotificationTypes import NotificationType +import json +from request_api.models.default_method_result import DefaultMethodResult +from enum import Enum +from request_api.exceptions import BusinessException +from datetime import datetime +import holidays +import maya +import os +from flask import current_app +from dateutil.parser import parse +import time as t + +class cfrdateevent(duecalculator): + """ FOI Event management service + + """ + def createdueevent(self): + try: + _today = self.gettoday() + notificationservice().dismissremindernotification("ministryrequest", self.__notificationtype()) + + ca_holidays = self.getholidays() + _upcomingdues = FOIMinistryRequest.getupcomingcfrduerecords() + notificationtype = NotificationType().getnotificationtypeid(self.__notificationtype()) + for entry in _upcomingdues: + _duedate = self.formatduedate(entry['cfrduedate']) + message = None + if _duedate == _today: + message = self.__todayduemessage() + elif self.getpreviousbusinessday(entry['cfrduedate'],ca_holidays) == _today: + message = self.__upcomingduemessage(_duedate) + self.__createnotification(message,entry['foiministryrequestid'], notificationtype) + self.__createcomment(entry, message) + return DefaultMethodResult(True,'CFR reminder notifications created',_today) + except BusinessException as exception: + current_app.logger.error("%s,%s" % ('CFR reminder Notification Error', exception.message)) + return DefaultMethodResult(False,'CFR reminder notifications failed',_today) + + def __createnotification(self, message, requestid, notificationtype): + if message is not None: + return notificationservice().createremindernotification({"message" : message}, requestid, "ministryrequest", notificationtype, self.__defaultuserid()) + + def __createcomment(self, entry, message): + if message is not None: + _comment = self.__preparecomment(entry, message) + return commentservice().createcomments(_comment, self.__defaultuserid(), 2) + + def __preparecomment(self, foirequest, message): + _comment = dict() + _comment['comment'] = message + _comment['ministryrequestid'] = foirequest["foiministryrequestid"] + _comment['version'] = foirequest["version"] + _comment['taggedusers'] = None + _comment['parentcommentid'] = None + return _comment + + def __upcomingduemessage(self, duedate): + return 'Call for Records due on ' + parse(str(duedate)).strftime("%Y %b %d").upper() + + def __todayduemessage(self): + return 'Call for Records due Today' + + def __notificationtype(self): + return "CFR Due Reminder" + + def __defaultuserid(self): + return "System" \ No newline at end of file diff --git a/historical-search-api/request_api/services/events/cfrfeeform.py b/historical-search-api/request_api/services/events/cfrfeeform.py new file mode 100644 index 000000000..e98711244 --- /dev/null +++ b/historical-search-api/request_api/services/events/cfrfeeform.py @@ -0,0 +1,100 @@ + +from os import stat +from re import VERBOSE +from request_api.models.FOIRequestCFRFees import FOIRequestCFRFee +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from request_api.services.commentservice import commentservice +from request_api.services.cfrfeeservice import cfrfeeservice +from request_api.services.notificationservice import notificationservice +from request_api.models.NotificationTypes import NotificationType +import json +from request_api.models.default_method_result import DefaultMethodResult +from enum import Enum +from request_api.exceptions import BusinessException +from datetime import datetime +import holidays +import maya +import os +from flask import current_app +from dateutil.parser import parse + +class cfrfeeformevent: + """ CFR Form Event management service + + """ + def createstatetransitionevent(self, requestid, userid, username): + cfrfee = cfrfeeservice().getcfrfee(requestid) + state = self.__haschanged(requestid, cfrfee['cfrfeeid']) + if state is not None: + _commentresponse = self.__createcomment(requestid, state, userid, username) + _notificationresponse = self.__createnotification(requestid, state, userid) + if _commentresponse.success == True and _notificationresponse.success == True : + return DefaultMethodResult(True,'Comment posted',requestid) + else: + return DefaultMethodResult(False,'unable to post comment',requestid) + return DefaultMethodResult(True,'No change',requestid) + + + def createeventforupdatedamounts(self, requestid, userid, username): + feedata = FOIRequestCFRFee.getfeedataforamountcomparison(requestid) + updatedamounts={} + _feewaivercommentresponse=DefaultMethodResult(True,'No change',requestid) + _refundcommentresponse=DefaultMethodResult(True,'No change',requestid) + if len(feedata) == 2: + newfeedata = feedata[0] + oldfeedata = feedata[1] + newfeewaiveramount = self.__getvaluefromschema(newfeedata, "feewaiveramount") + oldfeewaiveramount = self.__getvaluefromschema(oldfeedata, "feewaiveramount") + if newfeewaiveramount != oldfeewaiveramount: + updatedamounts = {'feewaiveramountchanged': newfeewaiveramount} + _feewaivercommentresponse = self.__createcomment(requestid, None, userid, username, updatedamounts) + newrefundamount = self.__getvaluefromschema(newfeedata, "refundamount") + oldrefundamount = self.__getvaluefromschema(oldfeedata, "refundamount") + if newrefundamount != oldrefundamount: + updatedamounts = {'refundamountchanged': newrefundamount} + _refundcommentresponse = self.__createcomment(requestid, None, userid, username, updatedamounts) + return _feewaivercommentresponse, _refundcommentresponse + + def __createcomment(self, requestid, state, userid, username, updatedamounts=None): + comment = self.__preparecomment(requestid, state, username, updatedamounts) + return commentservice().createministryrequestcomment(comment, userid, 2) + + def __createnotification(self, requestid, state, userid): + notification = self.__preparenotification(state) + notificationtype = NotificationType().getnotificationtypeid("CFR Fee Form") + return notificationservice().createnotification({"message" : notification}, requestid, "ministryrequest", notificationtype, userid) + + def __preparecomment(self, requestid, state, username, updatedamounts): + comment = {"comment": self.__commentmessage(state, username, updatedamounts)} + comment['ministryrequestid']= requestid + return comment + + def __preparenotification(self, state): + return self.__notificationmessage(state) + + def __haschanged(self, requestid, cfrfeeid): + states = FOIRequestCFRFee.getstatenavigation(requestid, cfrfeeid) + if len(states) == 2: + newstate = states[0] + oldstate = states[1] + if newstate != oldstate: + return newstate + elif len(states) == 1: + return states[0] + return None + + def __commentmessage(self, state, username, updatedamounts): + if state is not None: + return username+' updated Fee Estimate status to '+state + if updatedamounts is not None: + if 'feewaiveramountchanged' in updatedamounts and updatedamounts['feewaiveramountchanged'] is not None: + return username+ ' entered a fee waiver for the amount of $'+"{:.2f}".format(updatedamounts['feewaiveramountchanged']) + if 'refundamountchanged' in updatedamounts and updatedamounts['refundamountchanged'] is not None: + return username+ ' entered a refund for the amount of $'+"{:.2f}".format(updatedamounts['refundamountchanged']) + + + def __notificationmessage(self, state): + return 'Updated Fee Estimate Status to '+state + + def __getvaluefromschema(self,requestsschema, property): + return requestsschema.get(property) if property in requestsschema else None diff --git a/historical-search-api/request_api/services/events/comment.py b/historical-search-api/request_api/services/events/comment.py new file mode 100644 index 000000000..3e382d6ae --- /dev/null +++ b/historical-search-api/request_api/services/events/comment.py @@ -0,0 +1,105 @@ + +from os import stat +from re import VERBOSE +from request_api.services.notificationservice import notificationservice +from request_api.models.FOIRawRequestComments import FOIRawRequestComment +from request_api.models.FOIRequestComments import FOIRequestComment +import json +from request_api.models.default_method_result import DefaultMethodResult +from enum import Enum +from request_api.exceptions import BusinessException +from datetime import datetime +import holidays +import maya +import os +from flask import current_app +from dateutil.parser import parse +from request_api import socketio +import asyncio +from request_api.utils.redispublisher import RedisPublisherService +class commentevent: + """ FOI Event management service + """ + def createcommentevent(self, commentid, requesttype, userid, isdelete=False, existingtaggedusers=False): + try: + if isdelete == True: + return self.__deletecommentnotification(commentid, userid) + elif existingtaggedusers: + return self.__editcommentnotification(commentid, requesttype, existingtaggedusers, userid) + else: + return self.__createcommentnotification(commentid, requesttype, userid) + except BusinessException as exception: + current_app.logger.error("%s,%s" % ('Comment Notification Error', exception.message)) + return DefaultMethodResult(False,'Comment notifications failed',commentid) + + def __createcommentnotification(self, commentid, requesttype, userid): + _comment = self.__getcomment(commentid,requesttype) + notificationservice().createcommentnotification(self.getcommentmessage(commentid, _comment), _comment, self.__getcommenttype(_comment), requesttype, userid) + if _comment["taggedusers"] != '[]': + notificationservice().createcommentnotification(self.getcommentmessage(commentid, _comment), _comment, "Tagged User Comments", requesttype, userid) + self.__pushcommentnotification(commentid) + return DefaultMethodResult(True,'Comment notifications created',commentid) + + def __deletecommentnotification(self, commentid, userid): + _pushnotifications = notificationservice().getcommentnotifications(commentid) + for _pushnotification in _pushnotifications: + notificationservice().dismissnotification(userid, None, _pushnotification["idnumber"], _pushnotification["notificationuserid"]) + _pushnotification["action"] = "delete" + RedisPublisherService().publishcommment(json.dumps(_pushnotification)) + return DefaultMethodResult(True,'Comment notifications deleted',commentid) + + def __editcommentnotification(self, commentid, requesttype, existingtaggedusers, userid): + _comment = self.__getcomment(commentid,requesttype) + notificationservice().editcommentnotification(self.getcommentmessage(commentid, _comment), _comment, userid) + newtaggedusers = [user for user in json.loads(_comment["taggedusers"]) if user not in json.loads(existingtaggedusers)] + if newtaggedusers: + _comment["taggedusers"] = json.dumps(newtaggedusers) + notificationservice().createcommentnotification(self.getcommentmessage(commentid, _comment), _comment, "Tagged User Comments", requesttype, userid) + self.__pushcommentnotification(commentid) + return DefaultMethodResult(True,'Comment notifications created',commentid) + + def __pushcommentnotification(self,commentid, _pushnotifications): + try: + if os.getenv("SOCKETIO_MESSAGE_QTYPE") != "NONE": + for _pushnotification in _pushnotifications: + if os.getenv("SOCKETIO_MESSAGE_QTYPE") == "REDIS": + RedisPublisherService().publishcommment(json.dumps(_pushnotification)) + if os.getenv("SOCKETIO_MESSAGE_QTYPE") == "IN-MEMORY": + socketio.emit(_pushnotification["userid"], _pushnotification) + except BusinessException as exception: + current_app.logger.error("%s,%s" % ('Comment Notification publish Error', exception.message)) + return DefaultMethodResult(False,'Comment notifications publish failed',commentid) + + def __pushcommentnotification(self, commentid): + _pushnotifications = notificationservice().getcommentnotifications(commentid) + for _pushnotification in _pushnotifications: + RedisPublisherService().publishcommment(json.dumps(_pushnotification)) + + def getcommentmessage(self, commentid, comment): + return {"commentid":commentid, "message" :self.__formatmessage(comment)} + + def __formatmessage(self, comment): + _comment = json.loads(comment["comment"]) + msg = _comment["blocks"][0]["text"] + if comment["taggedusers"] != '[]': + msg = self.__formattaggedmessage(msg, json.loads(comment["taggedusers"])) + msg = msg.strip() + return msg + + def __formattaggedmessage(self, message, taggedusers): + for _taguser in taggedusers: + message = message.replace(_taguser["name"], "") + return message + + def __getcomment(self, commentid, requesttype): + if requesttype == "ministryrequest": + return FOIRequestComment.getcommentbyid(commentid) + else: + return FOIRawRequestComment.getcommentbyid(commentid) + + def __getcommenttype(self, comment): + if not comment["parentcommentid"]: + return "New User Comments" + else: + return "Reply User Comments" + \ No newline at end of file diff --git a/historical-search-api/request_api/services/events/division.py b/historical-search-api/request_api/services/events/division.py new file mode 100644 index 000000000..7c17eb7f4 --- /dev/null +++ b/historical-search-api/request_api/services/events/division.py @@ -0,0 +1,136 @@ + +from os import stat +from re import VERBOSE +from request_api.models.FOIMinistryRequestDivisions import FOIMinistryRequestDivision +from request_api.services.commentservice import commentservice +from request_api.models.FOIRawRequests import FOIRawRequest +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from request_api.models.FOIRequestStatus import FOIRequestStatus +import json +from request_api.models.default_method_result import DefaultMethodResult +from enum import Enum +from request_api.exceptions import BusinessException +from dateutil.parser import parse +from request_api.utils.enums import CommentType + +class divisionevent: + """ FOI Event management service + + """ + def createdivisionevent(self, requestid, requesttype, userid): + if requesttype != "ministryrequest": + return DefaultMethodResult(True,'No division required',requestid) + version = FOIMinistryRequest.getversionforrequest(requestid) + curdivisions = FOIMinistryRequestDivision.getdivisions(requestid, version) + prevdivisions = FOIMinistryRequestDivision.getdivisions(requestid, version[0]-1) + divisionsummary = self.__maintained(curdivisions, prevdivisions) + self.__deleted(curdivisions, prevdivisions) + if divisionsummary is None or (divisionsummary and len(divisionsummary) <1): + return DefaultMethodResult(True,'No change',requestid) + try: + for division in divisionsummary: + self.createcomment(requestid, division, userid) + return DefaultMethodResult(True,'Comment posted',requestid) + except BusinessException as exception: + return DefaultMethodResult(False,'unable to post comment - '+exception.message,requestid) + + + def createcomment(self, requestid, division, userid): + comment = {"ministryrequestid": requestid, "comment": self.__preparemessage(division)} + commentservice().createministryrequestcomment(comment, userid, CommentType.DivisionStages.value) + + + def __maintained(self,cdivisions, pdivisions): + divisions = [] + for cdivision in cdivisions: + if self.__isdivisionpresent(self.__getdivisioname(cdivision), pdivisions) == False: + divisions.append(self.__createdivisionsummary(cdivision, EventType.add.value)) + else: + if self.__isstagechanged(self.__getdivisioname(cdivision), self.__getstagename(cdivision), pdivisions) == True or self.__isdivisionduedatechanged(self.__getdivisioname(cdivision), self.__getdivisionduedate(cdivision), pdivisions) or self.__iseapprovalchanged(self.__getdivisioname(cdivision), self.__geteaproval(cdivision), pdivisions) or self.__isdivisionreceiveddatechanged(self.__getdivisioname(cdivision), self.__getdivisionreceiveddate(cdivision), pdivisions) : + divisions.append(self.__createdivisionsummary(cdivision, EventType.modify.value)) + return divisions + + def __deleted(self,cdivisions, pdivisions): + divisions = [] + for pdivision in pdivisions: + if self.__isdivisionpresent(self.__getdivisioname(pdivision), cdivisions) == False: + divisions.append(self.__createdivisionsummary(pdivision, EventType.delete.value)) + return divisions + + def __isdivisionpresent(self, divisionid, divisionlist): + for division in divisionlist: + if self.__getdivisioname(division) == divisionid: + return True + return False + + def __isstagechanged(self, divisionid, stageid, divisionlist): + for division in divisionlist: + if self.__getdivisioname(division) == divisionid and self.__getstagename(division) != stageid: + return True + return False + + def __isdivisionduedatechanged(self, divisionid, cdivisionduedate, divisionlist): + for division in divisionlist: + if self.__getdivisioname(division) == divisionid and self.__getdivisionduedate(division) != cdivisionduedate: + return True + return False + + def __iseapprovalchanged(self, divisionid, ceapproval, divisionlist): + for division in divisionlist: + if self.__getdivisioname(division) == divisionid and self.__geteaproval(division) != ceapproval: + return True + return False + + def __isdivisionreceiveddatechanged(self, divisionid, cdivisionreceiveddate, divisionlist): + for division in divisionlist: + if self.__getdivisioname(division) == divisionid and self.__getdivisionreceiveddate(division) != cdivisionreceiveddate: + return True + return False + + def __createdivisionsummary(self, division, event): + return {'division': self.__getdivisioname(division), + 'stage': self.__getstagename(division), + 'divisionduedate':self.__getdivisionduedate(division), + 'eapproval': self.__geteaproval(division), + 'divisionreceiveddate': self.__getdivisionreceiveddate(division), + 'event': event} + + def __getdivisioname(self, dataschema): + return dataschema['division.name'] + + def __getstagename(self, dataschema): + return dataschema['stage.name'] + + def __getdivisionduedate(self, dataschema): + return parse(dataschema['divisionduedate']).strftime(self.__genericdateformat()) if dataschema['divisionduedate'] is not None else None + + def __geteaproval(self, dataschema): + return dataschema['eapproval'] + + def __getdivisionreceiveddate(self, dataschema): + return parse(dataschema['divisionreceiveddate']).strftime(self.__genericdateformat()) if dataschema['divisionreceiveddate'] is not None else None + + def __preparemessage(self, division): + message = "" + if division['eapproval'] is not None: + message = ' E-App/Other reference Number: ' + division['eapproval'] + if division['divisionduedate'] is not None: + message += ' Due on ' + division['divisionduedate'] + if division['divisionreceiveddate'] is not None: + message += ' Received on ' + division['divisionreceiveddate'] + + if division['event'] == EventType.modify.value: + return self.__formatmessage(division['division'])+' division has been updated to stage '+ self.__formatmessage(division['stage']) + message + elif division['event'] == EventType.add.value: + return self.__formatmessage(division['division'])+' division has been added with stage '+ self.__formatmessage(division['stage']) + message + else: + return self.__formatmessage(division['division'])+' division with stage '+ self.__formatmessage(division['stage']) + message + ' has been removed' + + + def __formatmessage(self, data): + return ''+data+'' + def __genericdateformat(self): + return '%Y-%m-%d' +class EventType(Enum): + add = "add" + delete = "delete" + modify = "modify" diff --git a/historical-search-api/request_api/services/events/divisiondate.py b/historical-search-api/request_api/services/events/divisiondate.py new file mode 100644 index 000000000..c208581dd --- /dev/null +++ b/historical-search-api/request_api/services/events/divisiondate.py @@ -0,0 +1,84 @@ +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.FOIMinistryRequests import FOIMinistryRequest +from request_api.models.FOIRequestComments import FOIRequestComment +from request_api.models.NotificationTypes import NotificationType +import json +from request_api.models.default_method_result import DefaultMethodResult +from enum import Enum +from request_api.exceptions import BusinessException +from datetime import datetime +import holidays +import maya +import os +from flask import current_app +from dateutil.parser import parse + +class divisiondateevent(duecalculator): + """ FOI Event management service + """ + + def createdueevent(self): + try: + _today = self.gettoday() + notificationservice().dismissremindernotification("ministryrequest", self.__notificationtype()) + ca_holidays = self.getholidays() + _upcomingdues = FOIMinistryRequest.getupcomingdivisionduerecords() + notificationtype = NotificationType().getnotificationtypeid(self.__notificationtype()) + for entry in _upcomingdues: + _duedate = self.formatduedate(entry['duedate']) + message = None + if _duedate == _today: + message = self.__todayduemessage(entry) + elif self.getpreviousbusinessday(entry['duedate'],ca_holidays) == _today: + message = self.__upcomingduemessage(entry) + self.__createnotification(message,entry['foiministryrequestid'], notificationtype) + self.__createcomment(entry, message) + return DefaultMethodResult(True,'Division reminder notifications created',_today) + except BusinessException as exception: + current_app.logger.error("%s,%s" % ('Legislative reminder Notification Error', exception.message)) + return DefaultMethodResult(False,'Division reminder notifications failed',_today) + + def __createnotification(self, message, requestid, notificationtype): + if message is not None: + return notificationservice().createnotification({"message" : message}, requestid, "ministryrequest", notificationtype, self.__defaultuserid(), False) + + def __createcomment(self, entry, message): + if message is not None: + _comment = self.__preparecomment(entry, message) + return commentservice().createcomments(_comment, self.__defaultuserid(), 2) + + + def __preparecomment(self, foirequest, message): + _comment = dict() + _comment['comment'] = message + _comment['ministryrequestid'] = foirequest["foiministryrequestid"] + _comment['version'] = foirequest["version"] + _comment['taggedusers'] = None + _comment['parentcommentid'] = None + return _comment + + def __upcomingduemessage(self, data): + return self.__getformattedprefix(data)+ ' due on ' + parse(str(data['duedate'])).strftime("%Y %b %d").upper() + + def __todayduemessage(self, data): + return self.__getformattedprefix(data)+ ' due Today' + + def __getformattedprefix(self,data): + _message = data['divisionname'] + if data['stageid'] == 5: + _message += " records are" + elif data['stageid'] == 7: + _message += " harms are" + elif data['stageid'] == 9: + _message += " sign off is" + return _message + + def __notificationtype(self): + return "Division Due Reminder" + + def __defaultuserid(self): + return "System" \ No newline at end of file diff --git a/historical-search-api/request_api/services/events/email.py b/historical-search-api/request_api/services/events/email.py new file mode 100644 index 000000000..8011ad324 --- /dev/null +++ b/historical-search-api/request_api/services/events/email.py @@ -0,0 +1,60 @@ + +from os import stat +from re import VERBOSE +from request_api.services.commentservice import commentservice +from request_api.services.notificationservice import notificationservice +from request_api.exceptions import BusinessException +from request_api.models.FOIRawRequests import FOIRawRequest +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from request_api.models.FOIRequestStatus import FOIRequestStatus +from request_api.models.NotificationTypes import NotificationType +import json +from request_api.models.default_method_result import DefaultMethodResult +import logging + +class emailevent: + """ FOI Event management service + """ + def createemailevent(self, requestid, requesttype, stage, reason, userid): + try: + _commentresponse = self.__createcomment(requestid, requesttype, stage, reason, userid) + _notificationresponse = self.__createnotification(requestid, requesttype, stage) + if _commentresponse.success == True and _notificationresponse.success == True and _notificationresponse.success == True: + return DefaultMethodResult(True,'Email Failure Notification posted',requestid) + else: + return DefaultMethodResult(False,'Email Failure Notification - failed',requestid) + except BusinessException as ex: + logging.exception(ex) + return DefaultMethodResult(False,'Email Failure Notification - failed',requestid) + + def __createcomment(self, requestid, requesttype, stage, reason, userid): + comment = self.__preparecomment(requestid, requesttype, stage, reason) + if requesttype == "ministryrequest": + return commentservice().createministryrequestcomment(comment, userid, 2) + else: + logging.info("Unsupported requesttype") + + def __createnotification(self, requestid, requesttype, stage): + notification = self.__preparenotification(stage) + notificationtype = NotificationType().getnotificationtypeid("Email Failure") + return notificationservice().createnotification({"message" : notification}, requestid, requesttype, notificationtype, self.__defaultuserid()) + + def __preparenotification(self, stage): + return self.__notificationmessage(stage) + + def __preparecomment(self, requestid, requesttype, stage, reason): + comment = {"comment": self.__commentmessage(stage, reason)} + if requesttype == "ministryrequest": + comment['ministryrequestid']= requestid + else: + comment['requestid']=requestid + return comment + + def __commentmessage(self, stage, reason): + return stage+' correspondence failed to send to applicant due to reason: "'+reason+'". See attachment log for details.' + + def __notificationmessage(self, stage): + return stage+' correspondence failed to send to applicant. - see attachment log for details.' + + def __defaultuserid(self): + return "System" \ No newline at end of file diff --git a/historical-search-api/request_api/services/events/extension.py b/historical-search-api/request_api/services/events/extension.py new file mode 100644 index 000000000..dec4a8447 --- /dev/null +++ b/historical-search-api/request_api/services/events/extension.py @@ -0,0 +1,275 @@ + +from os import stat +from re import VERBOSE +from request_api.models.FOIRequestExtensions import FOIRequestExtension +from request_api.services.commentservice import commentservice +from request_api.services.extensionreasonservice import extensionreasonservice +from request_api.services.notificationservice import notificationservice +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from request_api.models.FOIRequestComments import FOIRequestComment +from request_api.models.FOIRequestNotifications import FOIRequestNotification +from request_api.models.NotificationTypes import NotificationType +import json +from request_api.models.default_method_result import DefaultMethodResult +from enum import Enum +from request_api.exceptions import BusinessException +import dateutil.parser + +MSG_NO_CHANGE='No change' + +class extensionevent: + """ FOI Event management service + + """ + def createextensionevent(self, ministryrequestid, extensionid, userid, username, event): + version = FOIRequestExtension.getversionforextension(extensionid) + curextension = FOIRequestExtension().getextensionforversion(extensionid, version) + prevextension = FOIRequestExtension().getextensionforversion(extensionid, version[0]-1) + extensionsummaryforcomment = self.__maintained(curextension, prevextension, event) + message = "" + try: + notificationresponse = self.createnotification(ministryrequestid, extensionid, curextension, prevextension, userid, event) + if extensionsummaryforcomment is None or (extensionsummaryforcomment and len(extensionsummaryforcomment) < 1): + return DefaultMethodResult(True, MSG_NO_CHANGE ,extensionid) + else: + commentresponse = self.createcomment(ministryrequestid, userid, username, extensionsummaryforcomment) + if commentresponse.success == True: + message += 'Comment posted' + if notificationresponse.success == True and event != EventType.delete.value: + message += 'Notification posted' + return DefaultMethodResult(True, message, extensionid) + except BusinessException as exception: + return DefaultMethodResult(False,'unable to post comment - '+exception.message,extensionid) + + def deleteexistingrelatedevents(self, ministryrequestid): + try: + notificationids = FOIRequestNotification().getextensionnotificationidsbyministry(ministryrequestid) + if notificationids: + self.__deleteaxisextensionnotifications(notificationids) + FOIRequestComment().deleteextensioncommentsbyministry(ministryrequestid) + except BusinessException as exception: + return DefaultMethodResult(False,'Issue in deleting previous event related to ministry id - '+exception.message,ministryrequestid) + + def createcomment(self, ministryrequestid, userid, username, extensionsummary): + _comment = self.__preparemessage(username, extensionsummary) + if _comment not in [None,'']: + return commentservice().createministryrequestcomment({"ministryrequestid": ministryrequestid, "comment": _comment}, userid, 2) + else: + return DefaultMethodResult(True, MSG_NO_CHANGE ,ministryrequestid) + + def createnotification(self, ministryrequestid, extensionid, curextension, prevextension, userid, event): + + nootificationrequired = self.__nonotificationrequired(curextension, prevextension, event) + onlycleanuprequired = self.__onlycleanuprequired(curextension, prevextension, event) + onlynotificationrequired = self.__onlynotificationrequired(curextension, prevextension, event) + notificationandcleanup = self.__bothnotificationandcleanup(curextension, prevextension, event) + if nootificationrequired == True: + return DefaultMethodResult(True, "No Notification", ministryrequestid) + elif onlycleanuprequired == True: + self.__deleteextensionnotification(extensionid) + return DefaultMethodResult(True, "Delete Extension", ministryrequestid) + elif onlynotificationrequired == True or notificationandcleanup == True: + extensionsummary = self.__createnotificationsummary(curextension, prevextension, event) + notification = self.__preparenotification(extensionsummary) + if notificationandcleanup == True: + self.__deleteextensionnotification(extensionid) + notificationtype = NotificationType().getnotificationtypeid("Extension") + return notificationservice().createnotification({"extensionid": extensionid, "message": notification}, ministryrequestid, "ministryrequest", notificationtype, userid, False) + + def __deleteaxisextensionnotifications(self, notificationids): + notificationservice().dismissnotificationbyid("ministryrequest", notificationids) + + def __deleteextensionnotification(self, extensionid): + _extensionnotifications = notificationservice().getextensionnotifications(extensionid) + noticiationids = [] + for _extensionnotification in _extensionnotifications: + noticiationids.append(_extensionnotification["notificationid"]) + notificationservice().dismissnotificationbyid("ministryrequest", noticiationids) + return DefaultMethodResult(True,'Extension notifications deleted', extensionid) + + def __preparenotification(self, extensionsummary): + ispublicbody = self.__getvalueof('ispublicbody', extensionsummary) + isdenied = self.__getvalueof('isdenied', extensionsummary) + isapproved = self.__getvalueof('isapproved', extensionsummary) + extension = self.__getvalueof('extension', extensionsummary) + prevextension = self.__getvalueof('prevextension', extensionsummary) + prevapprovedays = self.__getvalueof('approvednoofdays', prevextension) if prevextension else None + approveddays = self.__getvalueof('approvednoofdays', extension) + prevextendeddays = self.__getvalueof('extendedduedays', prevextension) if prevextension else None + extendedduedays = self.__getvalueof('extendedduedays', extension) + newduedate = self.__formatdate(self.__getvalueof('extendedduedate', extension), self.__genericdateformat()) + + approveddayschanged = True if prevapprovedays != approveddays else False + extendeddayschanged = True if prevextendeddays and prevextendeddays != extendedduedays else False + + if isdenied == True: + return "Extension request to OIPC has been denied." + elif ispublicbody == True and isapproved == True or (extendeddayschanged == True): + return "Extension taken for " + str(extendedduedays) + " days. The new legislated due date is "+ newduedate + "." + elif isapproved == True and not ispublicbody or (approveddayschanged == True): + return "Extension taken for " + str(approveddays) + " days. The new legislated due date is "+ newduedate + "." + else: + return "Extension has been edited." + + # modified attributes without changing state + def __isstatechanged(self, curextension, prevextension, event): + if event == EventType.modify.value and self.__getvalueof('extensionstatusid',curextension) != ExtensionStatus.pending.value and self.__getvalueof('extensionstatusid',curextension) == self.__getvalueof('extensionstatusid',prevextension): + return False + else: + return True + + def __getextensionreason(self, reasonid): + return extensionreasonservice().getextensionreasonbyid(reasonid) + + def __ispublicbody(self, curextension): + extnreson = extensionreasonservice().getextensionreasonbyid(curextension['extensionreasonid']) + extntype = self.__getextensiontype(extnreson) + if extntype == ExtensionType.publicbody.value: + return True + return False + + # add denied or modify to denied + def __isdenied(self, curextension, prevextension, event): + return self.__isvalidaction(curextension, prevextension, event, ExtensionStatus.denied.value) + + # add approved or modify to approved + def __isapproved(self, curextension, prevextension, event): + return self.__isvalidaction(curextension, prevextension, event, ExtensionStatus.approved.value) + + def __isvalidaction(self,curextension, prevextension, event, status): + curextensionstatusid = self.__getvalueof('extensionstatusid',curextension) + prevextensionstatusid = self.__getvalueof('extensionstatusid',prevextension) + if (event == EventType.modify.value and curextensionstatusid != prevextensionstatusid) or (event == EventType.add.value): + if curextensionstatusid in [ExtensionStatus.denied.value,ExtensionStatus.approved.value] and status == curextensionstatusid: + return True + return False + + def __nonotificationrequired(self, curextension, prevextension, event): + curextensionstatusid = self.__getvalueof('extensionstatusid',curextension) + prevextensionstatusid = self.__getvalueof('extensionstatusid',prevextension) + curapproveddays = self.__getvalueof('approvednoofdays',curextension) + prevapproveddays = self.__getvalueof('approvednoofdays',prevextension) + if (event == EventType.add.value and curextensionstatusid == 1) or (event == EventType.modify.value and curextensionstatusid == prevextensionstatusid and curapproveddays == prevapproveddays): + return True + return False + + def __onlycleanuprequired(self, curextension, prevextension, event): + curextensionstatusid = self.__getvalueof('extensionstatusid',curextension) + prevextensionstatusid = self.__getvalueof('extensionstatusid',prevextension) + if event == EventType.delete.value or (event == EventType.modify.value and str(prevextensionstatusid) in [str(ExtensionStatus.denied.value), str(ExtensionStatus.approved.value)] and curextensionstatusid == 1): + return True + return False + + def __onlynotificationrequired(self, curextension, prevextension, event): + if self.__isdenied(curextension, prevextension, event) == True or self.__isapproved(curextension, prevextension, event) == True or self.__ispublicbody(curextension) == True: + return True + return False + + def __bothnotificationandcleanup(self, curextension, prevextension, event): + curextensionstatusid = self.__getvalueof('extensionstatusid',curextension) + prevextensionstatusid = self.__getvalueof('extensionstatusid',prevextension) + curapproveddays = self.__getvalueof('approvednoofdays',curextension) + prevapproveddays = self.__getvalueof('approvednoofdays',prevextension) + if (event == EventType.modify.value and curextensionstatusid in [ExtensionStatus.approved.value, ExtensionStatus.denied.value] and prevextensionstatusid in [ExtensionStatus.approved.value, ExtensionStatus.denied.value]) or (event == EventType.modify.value and curextensionstatusid == ExtensionStatus.approved.value and curextensionstatusid == prevextensionstatusid and prevapproveddays != curapproveddays): + return True + return False + + def __maintained(self, curextension, prevextension, event): + return self.__createextensionsummary(curextension, prevextension, event) + + def __createextensionsummary(self, curextension, prevextension, event): + _extensionsummary = {'extension': curextension, 'prevextension':prevextension, 'reason': self.__getreasonfromid(self.__getvalueof("extensionreasonid", curextension)), 'action': event} + if event == EventType.delete.value: + _extensionsummary['isdelete'] = True + elif curextension["extensionstatusid"] != ExtensionStatus.pending.value and (event == EventType.modify.value or event == EventType.add.value): + _extensionsummary['isdelete'] = False + _extensionsummary['isdenied'] = self.__isdenied(curextension, prevextension, event) + _extensionsummary['ispublicbody'] = self.__ispublicbody(curextension) + _extensionsummary['isapproved'] = self.__isapproved(curextension, prevextension, event) + return _extensionsummary + + def __createnotificationsummary(self, curextension, prevextension, event): + isdenied = self.__isdenied(curextension, prevextension, event) + ispublicbody = self.__ispublicbody(curextension) + isapproved = self.__isapproved(curextension, prevextension, event) + + if event == EventType.modify.value: + return {'extension': curextension, 'prevextension': prevextension, 'ispublicbody': ispublicbody, 'isdenied': isdenied, 'isapproved': isapproved} + elif event == EventType.add.value and curextension["extensionstatusid"] != ExtensionStatus.pending.value: + return {'extension': curextension, 'ispublicbody': ispublicbody, 'isdenied': isdenied, 'isapproved': isapproved} + + def __preparemessage(self, username, extensionsummary): + action = self.__getvalueof('action', extensionsummary) + extension = self.__getvalueof('extension', extensionsummary) + prevextension = self.__getvalueof('prevextension', extensionsummary) + ispublicbody = self.__getvalueof('ispublicbody', extensionsummary) + isapproved = self.__getvalueof('isapproved', extensionsummary) + message = "" + if action == EventType.delete.value: + message = "Extension for " + self.__getvalueof('reason',extensionsummary) + " has been deleted." + elif action in [EventType.modify.value, EventType.add.value]: + if self.__isstatechanged(extension, prevextension, action) == False: + message = self.__preparemodifycomment(extensionsummary) + else: + if self.__getvalueof('isdenied', extensionsummary) == True: + message = "The OIPC has denied a "+ str(self.__getvalueof('extendedduedays',extension)) +" day extension." + elif isapproved == True: + if ispublicbody == True: + message = username + " has taken a "+ str(self.__getvalueof('extendedduedays',extension)) +" day Public Body extension." + else: + message = "The OIPC has granted a "+ str(self.__getvalueof('approvednoofdays',extension) ) +" day extension." + message += " The new legislated due date is "+ self.__formatdate(self.__getvalueof('extendedduedate',extension), self.__genericdateformat()) + else: + message = "Extension for " + self.__getvalueof('reason',extensionsummary) + " has been edited." + return message + + def __preparemodifycomment(self,extensionsummary): + extension = self.__getvalueof('extension', extensionsummary) + prevextension = self.__getvalueof('prevextension', extensionsummary) + isapproved = True if self.__getvalueof('extensionstatusid', extension) == ExtensionStatus.approved.value else False + comment = "" + if self.__getvalueof('extensionreasonid', extension) != self.__getvalueof('extensionreasonid', prevextension): + comment = "Extension reason has been updated to " + self.__getvalueof('reason', extensionsummary) + + if isapproved is True: + if self.__getvalueof('approvednoofdays', extension) != self.__getvalueof('approvednoofdays', prevextension): + comment += " AND " if comment not in [None,""] else "" + comment +="Approved number of days for extension has changed to " + str(self.__getvalueof('approvednoofdays', extension))+ "." + comment += " The new legislated due date is "+ self.__formatdate(self.__getvalueof('extendedduedate', extension), self.__genericdateformat()) + else: + if self.__getvalueof('extendedduedays', extension) != self.__getvalueof('extendedduedays', prevextension): + comment += " AND " if comment not in [None,""] else "" + comment +="Denied number of days for extension has changed to " + str(self.__getvalueof('extendedduedays', extension))+ "." + return comment + + def __getreasonfromid(self, curreasonid): + return self.__getextensionreasonvalue(self.__getextensionreason(curreasonid)) + + def __getextensionreasonvalue(self, extnreson): + return extnreson["reason"] + + def __getextensiontype(self, extnreson): + return extnreson["extensiontype"] + + def __getvalueof(self, key, extensionsummary): + return extensionsummary[key] if key in extensionsummary else None + + def __formatdate(self, datevalue, format): + return dateutil.parser.parse(datevalue).strftime(format) if datevalue is not None else None + + def __genericdateformat(self): + return '%b %d %Y' + +class EventType(Enum): + add = "add" + delete = "delete" + modify = "modify" + +class ExtensionStatus(Enum): + pending = 1 + approved = 2 + denied = 3 + +class ExtensionType(Enum): + publicbody = "Public Body" + oipc = "OIPC" \ No newline at end of file diff --git a/historical-search-api/request_api/services/events/legislativedate.py b/historical-search-api/request_api/services/events/legislativedate.py new file mode 100644 index 000000000..56885014c --- /dev/null +++ b/historical-search-api/request_api/services/events/legislativedate.py @@ -0,0 +1,78 @@ + +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.FOIMinistryRequests import FOIMinistryRequest +from request_api.models.FOIRequestComments import FOIRequestComment +from request_api.models.NotificationTypes import NotificationType +import json +from request_api.models.default_method_result import DefaultMethodResult +from enum import Enum +from request_api.exceptions import BusinessException +from datetime import datetime +import holidays +import maya +import os +from flask import current_app +from dateutil.parser import parse +import time as t + +class legislativedateevent(duecalculator): + """ FOI Event management service + + """ + + def createdueevent(self): + try: + _today = self.gettoday() + notificationservice().dismissremindernotification("ministryrequest", self.__notificationtype()) + ca_holidays = self.getholidays() + _upcomingdues = FOIMinistryRequest.getupcominglegislativeduerecords() + notificationtype = NotificationType().getnotificationtypeid(self.__notificationtype()) + for entry in _upcomingdues: + _duedate = self.formatduedate(entry['duedate']) + message = None + if _duedate == _today: + message = self.__todayduemessage() + elif self.getpreviousbusinessday(entry['duedate'],ca_holidays) == _today or self.getbusinessdaysbetween(entry['duedate'],_today) == 5: + message = self.__upcomingduemessage(_duedate) + + self.__createnotification(message,entry['foiministryrequestid'], notificationtype) + self.__createcomment(entry, message) + return DefaultMethodResult(True,'Legislative reminder notifications created',_today) + except BusinessException as exception: + current_app.logger.error("%s,%s" % ('Legislative reminder Notification Error', exception.message)) + return DefaultMethodResult(False,'Legislative reminder notifications failed',_today) + + def __createnotification(self, message, requestid, notificationtype): + if message is not None: + return notificationservice().createnotification({"message" : message}, requestid, "ministryrequest", notificationtype, self.__defaultuserid(), False) + + def __createcomment(self, entry, message): + if message is not None: + _comment = self.__preparecomment(entry, message) + return commentservice().createcomments(_comment, self.__defaultuserid(), 2) + + def __preparecomment(self, foirequest, message): + _comment = dict() + _comment['comment'] = message + _comment['ministryrequestid'] = foirequest["foiministryrequestid"] + _comment['version'] = foirequest["version"] + _comment['taggedusers'] = None + _comment['parentcommentid'] = None + return _comment + + def __upcomingduemessage(self, duedate): + return 'Legislative Due Date due on ' + parse(str(duedate)).strftime("%Y %b %d").upper() + + def __todayduemessage(self): + return 'Legislative Due Date is Today' + + def __notificationtype(self): + return "Legislative Due Reminder" + + def __defaultuserid(self): + return "System" + diff --git a/historical-search-api/request_api/services/events/oipc.py b/historical-search-api/request_api/services/events/oipc.py new file mode 100644 index 000000000..df3305dec --- /dev/null +++ b/historical-search-api/request_api/services/events/oipc.py @@ -0,0 +1,166 @@ + +from os import stat +from re import VERBOSE +from request_api.models.FOIRequestOIPC import FOIRequestOIPC +from request_api.services.commentservice import commentservice +from request_api.services.oipcservice import oipcservice +from request_api.services.notificationservice import notificationservice +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +import json +from request_api.models.default_method_result import DefaultMethodResult +from enum import Enum +from request_api.exceptions import BusinessException +from dateutil.parser import parse +from request_api.utils.enums import CommentType +from request_api.models.NotificationTypes import NotificationType + +class oipcevent: + """ FOI OIPC Event management service + """ + + def createoipcevent(self, requestid, requesttype, userid): + if requesttype != "ministryrequest": + return DefaultMethodResult(True,'No change',requestid) + ministryrequest = FOIMinistryRequest.getmetadata(requestid) + if ministryrequest["isoipcreview"] in (None, False): + notificationservice().dismissnotifications_by_requestid_type(requestid, "ministryrequest", self.__notificationtype()) + return DefaultMethodResult(True,'Dismiss OIPC events',requestid) + inquiryoutcomes = oipcservice().getinquiryoutcomes() + version = ministryrequest["version"] + curoipcs = FOIRequestOIPC.getoipc(requestid, version) + prevoipcs = FOIRequestOIPC.getoipc(requestid, version-1) + oipcsummary = self.__maintained(curoipcs, prevoipcs, inquiryoutcomes) + if oipcsummary is None or (oipcsummary and len(oipcsummary) <1): + return DefaultMethodResult(True,'No change',requestid) + try: + for oipc in oipcsummary: + self.__createcomment(requestid, oipc, userid) + self.__createnotification(requestid, oipc, userid) + return DefaultMethodResult(True,'Comment posted',requestid) + except BusinessException as exception: + return DefaultMethodResult(False,'unable to post comment - '+exception.message,requestid) + + + def __createcomment(self, requestid, oipc, userid): + comment = {"ministryrequestid": requestid, "comment": self.__preparemessage(oipc)} + commentservice().createministryrequestcomment(comment, userid, CommentType.SystemGenerated.value) + + def __createnotification(self, requestid, oipc, userid): + notificationtype = NotificationType().getnotificationtypeid(self.__notificationtype()) + return notificationservice().createnotification({"message" : self.__preparemessage(oipc)}, requestid, "ministryrequest", notificationtype, userid, False) + + + def __maintained(self,coipcs, poipcs, inquiryoutcomes): + oipcs = [] + for coipc in coipcs: + if self.__isoipcpresent(self.__getoipcnumber(coipc), poipcs) == False: + oipcs.append(self.__createoipcsummary(coipc, EventType.add.value, inquiryoutcomes)) + else: + if self.__isoutcomeclosed(coipc, poipcs) == True: + oipcs.append(self.__createoipcsummary(coipc, EventType.close.value, inquiryoutcomes)) + if self.__isinquirychanged(coipc, poipcs) == True: + oipcs.append(self.__createoipcsummary(coipc, EventType.inquirychange.value, inquiryoutcomes)) + elif self.__isinquiryoutcomechanged(coipc, poipcs, inquiryoutcomes) == True: + oipcs.append(self.__createoipcsummary(coipc, EventType.inquiryoutcome.value, inquiryoutcomes)) + return oipcs + + # def __deleted(self, coipcs, poipcs): + # oipcs = [] + # for poipc in poipcs: + # if self.__isoipcpresent(self.__getoipcnumber(poipc), coipcs) == False: + # oipcs.append(self.__createoipcsummary(poipc, EventType.delete.value)) + # return oipcs + + def __isoipcpresent(self, oipcno, poipcs): + for oipc in poipcs: + if self.__getoipcnumber(oipc) == oipcno: + return True + return False + + def __isoutcomeclosed(self, coipc, poipcs): + for oipc in poipcs: + if self.__getoipcnumber(oipc) == self.__getoipcnumber(coipc) and self.__getoutcome(oipc) != self.__getoutcome(coipc) and self.__getoutcome(coipc) == "Closed": + return True + return False + + def __isinquirychanged(self, coipc, poipcs): + for oipc in poipcs: + if self.__getoipcnumber(oipc) == self.__getoipcnumber(coipc) and self.__getinquiry(oipc) != self.__getinquiry(coipc): + if self.__getinquirycomplydate(coipc) not in (None, "") and self.__getinquiryorderno(coipc) not in (None, "") and (self.__getinquiryorderno(oipc) != self.__getinquiryorderno(coipc) or self.__getinquirycomplydate(oipc) != self.__getinquirycomplydate(coipc)): + return True + return False + + def __isinquiryoutcomechanged(self, coipc, poipcs, inquiryoutcomes): + for oipc in poipcs: + if self.__getoipcnumber(oipc) == self.__getoipcnumber(coipc) and self.__getinquiry(oipc) != self.__getinquiry(coipc): + if self.__getinquiryoutcome(coipc, inquiryoutcomes) not in (None, "") and self.__getinquiryoutcome(oipc, inquiryoutcomes) != self.__getinquiryoutcome(coipc, inquiryoutcomes): + return True + return False + + def __createoipcsummary(self, oipc, event, inquiryoutcomes): + return {'oipcno': self.__getoipcnumber(oipc), + 'reviewtype': self.__getoipcreviewtype(oipc), + 'reason':self.__getreason(oipc), + 'outcome': self.__getoutcome(oipc), + 'inquirycomplydate': self.__getinquirycomplydate(oipc), + 'inquiryorderno': self.__getinquirycomplydate(oipc), + 'inquiryoutcome': self.__getinquiryoutcome(oipc, inquiryoutcomes), + 'event': event} + + def __getoipcnumber(self, dataschema): + return dataschema['oipcno'] + + def __getoipcreviewtype(self, dataschema): + return dataschema['reviewtype.name'] + + def __getreason(self, dataschema): + return dataschema['reason.name'] + + def __getoutcome(self, dataschema): + return dataschema['outcome.name'] if dataschema['outcomeid'] not in (None,"") else None + + def __getinquirycomplydate(self, dataschema): + return self.__getinquiry(dataschema)['inquirydate'] if dataschema['inquiryattributes'] not in (None,"") else None + + def __getinquiryoutcome(self, dataschema, inquiryoutcomes): + if dataschema['inquiryattributes'] not in (None,""): + inquiryoutcomeid = self.__getinquiry(dataschema)['inquiryoutcome'] + if inquiryoutcomeid not in (None,""): + return self.__getinquiryoutcomename(inquiryoutcomeid, inquiryoutcomes) + return None + + def __getinquiryorderno(self, dataschema): + return self.__getinquiry(dataschema)['orderno'] if dataschema['inquiryattributes'] not in (None,"") else None + + def __getinquiry(self, dataschema): + return dataschema['inquiryattributes'] + + def __getinquiryoutcomename(self, inquiryoutcomeid, inquiryoutcomes): + for outcome in inquiryoutcomes: + if inquiryoutcomeid == outcome["inquiryoutcomeid"]: + return outcome["name"] + return None + + def __preparemessage(self, oipc): + if oipc['event'] == EventType.add.value: + return 'OIPC '+ oipc['reviewtype'] +' opened for '+ oipc['reason'] + elif oipc['event'] == EventType.close.value: + return 'OIPC '+ oipc['reviewtype'] +' closed for '+ oipc['reason'] + elif oipc['event'] == EventType.inquirychange.value: + _inquirychange_msg = 'OIPC Inquiry Order '+ oipc['inquiryorderno'] +' compliance date due '+oipc['inquirycomplydate'] + if oipc['inquiryoutcome'] not in (None, ""): + return _inquirychange_msg+' .Inquiry Decision:' + oipc['inquiryoutcome'] + else: + return _inquirychange_msg + elif oipc['event'] == EventType.inquiryoutcome.value: + return 'OIPC '+ oipc['reviewtype'] +' Inquiry Decision: '+ oipc['inquiryoutcome'] + + def __notificationtype(self): + return "OIPC" + +class EventType(Enum): + add = "add" + delete = "delete" + close = "close" + inquirychange = "inquirychange" + inquiryoutcome = "inquiryoutcome" diff --git a/historical-search-api/request_api/services/events/oipcduedate.py b/historical-search-api/request_api/services/events/oipcduedate.py new file mode 100644 index 000000000..a8db3021a --- /dev/null +++ b/historical-search-api/request_api/services/events/oipcduedate.py @@ -0,0 +1,72 @@ + +from os import stat +from re import VERBOSE +from request_api.services.commentservice import commentservice +from request_api.services.commons.duecalculator import duecalculator +from request_api.services.notificationservice import notificationservice +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from request_api.models.NotificationTypes import NotificationType +import json +from request_api.models.default_method_result import DefaultMethodResult +from request_api.exceptions import BusinessException +from dateutil.parser import parse +from flask import current_app + +class oipcduedateevent(duecalculator): + """ FOI OIPC Due Date Event management service + """ + + + def createdueevent(self): + try: + _today = self.gettoday() + notificationservice().dismissremindernotification("ministryrequest", self.__notificationtype()) + ca_holidays = self.getholidays() + _upcomingdues = FOIMinistryRequest.getupcomingoipcduerecords() + for entry in _upcomingdues: + _duedate = self.formatduedate(entry['duedate']) + message = None + if _duedate == _today: + message = self.__todayduemessage(entry) + elif self.getpreviousbusinessday(entry['duedate'],ca_holidays) == _today: + message = self.__upcomingduemessage(entry) + elif self.getpreviousbusinessday_by_n(entry['duedate'],ca_holidays, 2) == _today: + message = self.__upcomingduemessage(entry) + self.__createnotification(message,entry['foiministryrequestid']) + self.__createcomment(entry, message) + return DefaultMethodResult(True,'OIPC reminder notifications created',_today) + except BusinessException as exception: + current_app.logger.error("%s,%s" % ('OIPC reminder Notification Error', exception.message)) + return DefaultMethodResult(False,'OIPC reminder notifications failed',_today) + + def __createnotification(self, message, requestid): + if message is not None: + notificationtype = NotificationType().getnotificationtypeid(self.__notificationtype()) + return notificationservice().createnotification({"message" : message}, requestid, "ministryrequest", notificationtype, self.__defaultuserid(), False) + + def __createcomment(self, entry, message): + if message is not None: + _comment = self.__preparecomment(entry, message) + return commentservice().createcomments(_comment, self.__defaultuserid(), 2) + + + def __preparecomment(self, foirequest, message): + _comment = dict() + _comment['comment'] = message + _comment['ministryrequestid'] = foirequest["foiministryrequestid"] + _comment['version'] = foirequest["version"] + _comment['taggedusers'] = None + _comment['parentcommentid'] = None + return _comment + + def __upcomingduemessage(self, data): + return 'OIPC Inquiry Order '+data['orderno'] +' compliance date due on ' + parse(str(data['duedate'])).strftime("%Y %b %d").upper() + + def __todayduemessage(self, data): + return 'OIPC Inquiry Order '+data['orderno'] +' compliance due Today' + + def __notificationtype(self): + return "OIPC Due Reminder" + + def __defaultuserid(self): + return "System" \ No newline at end of file diff --git a/historical-search-api/request_api/services/events/payment.py b/historical-search-api/request_api/services/events/payment.py new file mode 100644 index 000000000..f22cbb83b --- /dev/null +++ b/historical-search-api/request_api/services/events/payment.py @@ -0,0 +1,130 @@ + +from os import stat +from re import VERBOSE +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 +from request_api.models.NotificationTypes import NotificationType +import json +from request_api.models.default_method_result import DefaultMethodResult +from enum import Enum +import maya +import os +from dateutil.parser import parse +from pytz import timezone +from request_api.utils.enums import PaymentEventType +from request_api.utils.commons.datetimehandler import datetimehandler +from request_api.services.commons.duecalculator import duecalculator +from request_api.exceptions import BusinessException +from flask import current_app + +class paymentevent: + """ FOI Event management service + + """ + def createpaymentevent(self, requestid, eventtype): + _commentresponse = self.__createcomment(requestid, eventtype) + _notificationresponse = self.__createnotification(requestid, eventtype) + if _commentresponse.success == True and _notificationresponse.success == True: + return DefaultMethodResult(True,'Payment notification posted',requestid) + else: + return DefaultMethodResult(False,'Unable to post Payment notification',requestid) + + def createpaymentexpiredevent(self, requestid): + _commentresponse = self.__createcomment(requestid, PaymentEventType.expired.value) + _notificationresponse = self.__createnotification(requestid, PaymentEventType.expired.value) + if _commentresponse.success == True and _notificationresponse.success == True: + return DefaultMethodResult(True,'Payment Expiry notification posted',requestid) + else: + return DefaultMethodResult(False,'Unable to post Payment Expiry notification',requestid) + + def createpaymentreminderevent(self): + try: + _today = datetimehandler().gettoday() + + notificationservice().dismissremindernotification("rawrequest", self.__notificationtype()) + eventtype = PaymentEventType.reminder.value + _onholdrequests = FOIRawRequest.getappfeeowingrequests() + notificationtype = NotificationType().getnotificationtypeid(self.__notificationtype()) + for entry in _onholdrequests: + _dateofstatechange = datetimehandler().formatdate(entry['updated_at']) + businessdayselapsed = duecalculator().getbusinessdaysbetween(_dateofstatechange) + if businessdayselapsed >= 20 and duecalculator().isbusinessday(_today): + commentexists = False + existingcomments = commentservice().getrawrequestcomments(entry['requestid']) + for comment in existingcomments: + if comment['text'] == self.__preparecomment(entry['requestid'], eventtype)['comment']: #checks if comment already exists + commentexists = True + if not commentexists: + self.__createcommentforrawrequest(entry['requestid'], eventtype) + self.__createnotificationforrawrequest(entry['requestid'], eventtype, notificationtype) + 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, notificationtype): + notification = self.__preparenotification(requestid, eventtype) + return notificationservice().createnotification({"message" : notification}, requestid, "rawrequest", notificationtype, "system") + + def __createcomment(self, requestid, eventtype): + comment = self.__preparecomment(requestid, eventtype) + return commentservice().createministryrequestcomment(comment, self.__defaultuserid(), 2) + + def __createnotification(self, requestid, eventtype): + notificationtype = NotificationType().getnotificationtypeid(self.__notificationtype()) + notification = self.__preparenotification(requestid, eventtype) + return notificationservice().createnotification({"message" : notification}, requestid, "ministryrequest", notificationtype, self.__defaultuserid()) + + def __preparenotification(self, requestid, eventtype): + return self.__notificationmessage(requestid, eventtype) + + def __preparecomment(self, requestid, eventtype): + if eventtype == PaymentEventType.paid.value: + comment = {"comment": "Applicant has paid required fee. New LDD is " + FOIMinistryRequest.getduedate(requestid).strftime("%m/%d/%Y")} + elif eventtype == PaymentEventType.expired.value: + comment = {"comment": "Fees were due to be paid by " + self.gettoday() + ", you may consider closing the request as abandoned."} + elif eventtype == PaymentEventType.outstandingpaid.value: + 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": "20 business days has passed awaiting payment, you can consider closing the request as abandoned"} + else: + comment = None + if comment is not None: + if eventtype == PaymentEventType.reminder.value: + comment['requestid'] = requestid + else: + comment['ministryrequestid']= requestid + return comment + + def __notificationmessage(self, requestid, eventtype): + if eventtype == PaymentEventType.paid.value: + return "Applicant has paid required fee, resume gathering. New LDD is " + FOIMinistryRequest.getduedate(requestid).strftime("%m/%d/%Y") + elif eventtype == PaymentEventType.expired.value: + return "Fees were due to be paid by " + self.gettoday() + ", you may consider closing the request as abandoned." + elif eventtype == PaymentEventType.outstandingpaid.value: + 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 + + def __defaultuserid(self): + return "Online Payment" + + def gettoday(self): + now_pst = maya.parse(maya.now()).datetime(to_timezone='America/Vancouver', naive=False) + return now_pst.strftime('%m/%d/%Y') + + def __notificationtype(self): + return "Payment" \ No newline at end of file diff --git a/historical-search-api/request_api/services/events/section5pending.py b/historical-search-api/request_api/services/events/section5pending.py new file mode 100644 index 000000000..84e6897f5 --- /dev/null +++ b/historical-search-api/request_api/services/events/section5pending.py @@ -0,0 +1,68 @@ +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.NotificationTypes import NotificationType +from request_api.models.default_method_result import DefaultMethodResult +from enum import Enum +from request_api.exceptions import BusinessException +from request_api.utils.commons.datetimehandler import datetimehandler +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() + notificationtype = NotificationType().getnotificationtypeid(self.__notificationtype()) + for entry in section5pendings: + _dateofstatechange = datetimehandler().formatdate(entry['created_at']) + businessdayselapsed = self.getbusinessdaysbetween(_dateofstatechange) + if businessdayselapsed >= 10 and self.isbusinessday(_today): + message = self.__passeddueremindermessage() + commentexists = False + existingcomments = commentservice().getrawrequestcomments(entry['requestid']) + for comment in existingcomments: + if comment['text'] == message: #checks if comment already exists + commentexists = True + if not commentexists: + self.__createcomment(entry, message) + self.__createnotification(message, entry['requestid'], notificationtype) + 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, notificationtype): + if message is not None: + return notificationservice().createremindernotification({"message" : message}, requestid, "rawrequest", 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/historical-search-api/request_api/services/events/state.py b/historical-search-api/request_api/services/events/state.py new file mode 100644 index 000000000..d5b015d43 --- /dev/null +++ b/historical-search-api/request_api/services/events/state.py @@ -0,0 +1,135 @@ + +from os import stat +from re import VERBOSE +from request_api.services.commentservice import commentservice +from request_api.services.notificationservice import notificationservice +from request_api.services.cfrfeeservice import cfrfeeservice +from request_api.models.FOIRawRequests import FOIRawRequest +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from request_api.models.FOIRequestStatus import FOIRequestStatus +from request_api.models.NotificationTypes import NotificationType +import json +from request_api.models.default_method_result import DefaultMethodResult +from request_api.utils.enums import StateName + +class stateevent: + """ FOI Event management service + + """ + def createstatetransitionevent(self, requestid, requesttype, userid, username): + state = self.__haschanged(requestid, requesttype) + if state is not None: + _commentresponse = self.__createcommentwrapper(requestid, state, requesttype, userid, username) + _notificationresponse = self.__createnotification(requestid, state, requesttype, userid) + _cfrresponse = self.__createcfrentry(state, requestid, userid) + if _commentresponse.success == True and _notificationresponse.success == True and _cfrresponse.success == True: + return DefaultMethodResult(True,'Comment posted',requestid) + else: + return DefaultMethodResult(False,'unable to post comment',requestid) + return DefaultMethodResult(True,'No change',requestid) + + def __haschanged(self, requestid, requesttype): + if requesttype == "rawrequest": + states = FOIRawRequest.getstatenavigation(requestid) + else: + states = FOIMinistryRequest.getstatenavigation(requestid) + if len(states) == 2: + newstate = states[0] + oldstate = states[1] + if newstate != oldstate and newstate != StateName.intakeinprogress.value: + return newstate + return None + + def __createcommentwrapper(self, requestid, state, requesttype, userid, username): + if state == StateName.archived.value: + _openedministries = FOIMinistryRequest.getministriesopenedbyuid(requestid) + for ministry in _openedministries: + response=self.__createcomment(ministry["ministryrequestid"], state, 'ministryrequest', userid, username) + else: + response=self.__createcomment(requestid, state, requesttype, userid, username) + return response + + + def __createcomment(self, requestid, state, requesttype, userid, username): + comment = self.__preparecomment(requestid, state, requesttype, username) + if requesttype == "ministryrequest": + return commentservice().createministryrequestcomment(comment, userid, 2) + else: + return commentservice().createrawrequestcomment(comment, userid,2) + + + def __createnotification(self, requestid, state, requesttype, userid): + _notificationtype = "State" + if state == StateName.callforrecords.value and requesttype == "ministryrequest": + foirequest = notificationservice().getrequest(requestid, requesttype) + _notificationtype = "Group Members" if foirequest['assignedministryperson'] is None else "State" + notification = self.__preparenotification(state) + if state == StateName.response.value and requesttype == "ministryrequest": + signgoffapproval = FOIMinistryRequest().getrequest(requestid)['ministrysignoffapproval'] + if signgoffapproval: + notification = notification + f". Approved by {signgoffapproval['approvername']}, {signgoffapproval['approvertitle']} on {signgoffapproval['approveddate']}" + if state == StateName.closed.value or state == StateName.archived.value: + notificationservice().dismissnotificationsbyrequestid(requestid, requesttype) + if state == StateName.archived.value: + _openedministries = FOIMinistryRequest.getministriesopenedbyuid(requestid) + for ministry in _openedministries: + notificationtype = NotificationType().getnotificationtypeid("State") + response = notificationservice().createnotification({"message" : notification}, ministry["ministryrequestid"], 'ministryrequest', notificationtype, userid) + else: + notificationtype = NotificationType().getnotificationtypeid("State") + response = notificationservice().createnotification({"message" : notification}, requestid, requesttype, notificationtype, userid) + if _notificationtype == "Group Members": + notificationtype = NotificationType().getnotificationtypeid(_notificationtype) + 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) + else: + return DefaultMethodResult(False,'Unable to add notification',requestid) + if response.success == True: + return DefaultMethodResult(True,'Notification added',requestid) + return DefaultMethodResult(True,'No change',requestid) + + def __preparenotification(self, state): + return self.__notificationmessage(state) + + def __preparegroupmembernotification(self, state, requestid): + if state == StateName.callforrecords.value: + return self.__notificationcfrmessage(requestid) + return self.__groupmembernotificationmessage(state) + + def __preparecomment(self, requestid, state,requesttype, username): + comment = {"comment": self.__commentmessage(state, username, requesttype, requestid)} + if requesttype == "ministryrequest": + comment['ministryrequestid']= requestid + else: + comment['requestid']=requestid + return comment + + def __formatstate(self, state): + return StateName.open.value if state == StateName.archived.value else state + + def __commentmessage(self, state, username, requesttype, requestid): + comment = username+' changed the state of the request to '+self.__formatstate(state) + if state == StateName.response.value and requesttype == "ministryrequest": + signgoffapproval = FOIMinistryRequest().getrequest(requestid)['ministrysignoffapproval'] + if signgoffapproval: + 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 == StateName.feeestimate.value and cfrfee['cfrfeestatusid'] in (None, '')): + return cfrfeeservice().sanctioncfrfee(ministryrequestid, {"status": "review"}, userid) + else: + return DefaultMethodResult(True,'No action needed',ministryrequestid) + + def __groupmembernotificationmessage(self, state): + return 'New request is in '+state \ No newline at end of file diff --git a/historical-search-api/request_api/services/events/watcher.py b/historical-search-api/request_api/services/events/watcher.py new file mode 100644 index 000000000..45043ca88 --- /dev/null +++ b/historical-search-api/request_api/services/events/watcher.py @@ -0,0 +1,72 @@ +from cmath import asin +from os import stat +from re import VERBOSE +from request_api.services.notificationservice import notificationservice +from request_api.services.commentservice import commentservice +import json +from request_api.models.default_method_result import DefaultMethodResult +from request_api.utils.enums import CommentType + +class watcherevent: + + def createwatcherevent(self, requestid, watcher, requesttype, userid, username): + #Create Notification - Watcher inactive + if watcher['isactive'] == False: + notificationresponse = self.__createnotification(requesttype, watcher, userid, username) + if notificationresponse.success == True: + return DefaultMethodResult(True,'Watcher Notification has been created',requestid) + else: + return DefaultMethodResult(False,'unable to create notification for removed watcher',requestid) + else: + #Dismiss Notification By ID & Userid - Watcher active + notificationservice().dismissnotifications_by_requestid_type_userid(requestid, requesttype, 'Watcher', watcher['watchedby']) + return DefaultMethodResult(True,'No change',requestid) + + def createwatchereventforrestricted(self, requestid, watcher, requesttype, userid, username): + #Create comment - add/remove Watcher + #Create Notification - Watcher inactive + if watcher['isactive'] == False: + notificationresponse = self.__createnotification(requesttype, watcher, userid, username) + commentrespose = self.__createcommentforremove(watcher['fullname'],username,requestid,userid,requesttype) + if notificationresponse.success == True and commentrespose.success == True: + return DefaultMethodResult(True,'Watcher Notification, Comment have been created',requestid) + else: + return DefaultMethodResult(False,'unable to create notification and/or comment for removed watcher',requestid) + else: + #Dismiss Notification By ID & Userid - Watcher active + notificationservice().dismissnotifications_by_requestid_type_userid(requestid, requesttype, 'Watcher', watcher['watchedby']) + commentrespose = self.__createcommentforadd(watcher['fullname'],username,requestid,userid,requesttype) + if commentrespose.success == True: + return DefaultMethodResult(True,'Watcher Comment has been created',requestid) + else: + return DefaultMethodResult(False,'unable to create comment for removed watcher',requestid) + # return DefaultMethodResult(True,'No change',requestid) + + + def __createnotification(self, requesttype, watcher, userid, username): + notification = self.__preparenotification(username) + return notificationservice().createwatchernotification({"message" : notification}, requesttype, watcher, userid) + + def __preparenotification(self,username): + return self.__notificationmessage(username) + + def __notificationmessage(self,username): + return username+' has removed you as a watcher to this request' + + def __createcommentforadd(self, assigneename, username, requestid, userid, requesttype): + _commentmessage ='{0} has made {1} a watcher'.format(username,assigneename) + if requesttype == "ministryrequest": + comment = {"ministryrequestid": requestid, "comment": _commentmessage} + return commentservice().createministryrequestcomment(comment, userid, CommentType.SystemGenerated.value) + else: + comment = {"requestid": requestid, "comment": _commentmessage} + return commentservice().createrawrequestcomment(comment, userid, CommentType.SystemGenerated.value) + + def __createcommentforremove(self, assigneename, username, requestid, userid, requesttype): + _commentmessage ='{0} has removed {1} as a watcher'.format(username,assigneename) + if requesttype == "ministryrequest": + comment = {"ministryrequestid": requestid, "comment": _commentmessage} + return commentservice().createministryrequestcomment(comment, userid, CommentType.SystemGenerated.value) + else: + comment = {"requestid": requestid, "comment": _commentmessage} + return commentservice().createrawrequestcomment(comment, userid, CommentType.SystemGenerated.value) diff --git a/historical-search-api/request_api/services/eventservice.py b/historical-search-api/request_api/services/eventservice.py new file mode 100644 index 000000000..96ba48dcc --- /dev/null +++ b/historical-search-api/request_api/services/eventservice.py @@ -0,0 +1,144 @@ + +from os import stat +from re import VERBOSE +from request_api.services.events.state import stateevent +from request_api.services.events.division import divisionevent +from request_api.services.events.oipc import oipcevent +from request_api.services.events.assignment import assignmentevent +from request_api.services.events.cfrdate import cfrdateevent +from request_api.services.events.comment import commentevent +from request_api.services.events.watcher import watcherevent +from request_api.services.events.legislativedate import legislativedateevent +from request_api.services.events.divisiondate import divisiondateevent +from request_api.services.events.oipcduedate import oipcduedateevent +from request_api.services.events.extension import extensionevent +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 +import time as timer + +import json +from flask import current_app +class eventservice: + """ FOI event management service + + """ + + async def postevent(self, requestid, requesttype, userid, username, isministryuser,assigneename=''): + self.posteventsync(requestid, requesttype, userid, username, isministryuser,assigneename) + + def posteventsync(self, requestid, requesttype, userid, username, isministryuser,assigneename=''): + try: + stateeventresponse = stateevent().createstatetransitionevent(requestid, requesttype, userid, username) + divisioneventresponse = divisionevent().createdivisionevent(requestid, requesttype, userid) + assignmentresponse = assignmentevent().createassignmentevent(requestid, requesttype, userid, isministryuser,assigneename,username) + oipcresponse = oipcevent().createoipcevent(requestid, requesttype, userid) + if stateeventresponse.success == False or divisioneventresponse.success == False or assignmentresponse.success == False or oipcresponse.success == False: + current_app.logger.error("FOI Notification failed for event for request= %s ; state response=%s ; division response=%s ; assignment response=%s ; oipc response=%s" % (requestid, stateeventresponse.message, divisioneventresponse.message, assignmentresponse.message, oipcresponse.message)) + except BusinessException as exception: + self.__logbusinessexception(exception) + + def posteventforextension(self, ministryrequestid, extensionid, userid, username, event): + try: + extensioneventresponse = extensionevent().createextensionevent(ministryrequestid, extensionid, userid, username, event) + if extensioneventresponse.success == False: + current_app.logger.error("FOI Notification failed for event for extension= %s" % (extensionid)) + except BusinessException as exception: + self.__logbusinessexception(exception) + + def posteventforaxisextension(self, ministryrequestid, extensionids, userid, username, event): + try: + for extensionid in extensionids: + extensioneventresponse = extensionevent().createextensionevent(ministryrequestid, extensionid, userid, username, event) + if extensioneventresponse.success == False: + current_app.logger.error("FOI Notification failed for event for extension= %s" % (extensionid)) + except BusinessException as exception: + self.__logbusinessexception(exception) + + def postreminderevent(self): + try: + cfreventresponse = cfrdateevent().createdueevent() + legislativeeventresponse = legislativedateevent().createdueevent() + divisioneventresponse = divisiondateevent().createdueevent() + oipceventresponse = oipcduedateevent().createdueevent() + 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 or oipceventresponse == 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 ; oipcduereminder response=%s" % (cfreventresponse.message, legislativeeventresponse.message, divisioneventresponse.message, paymentremindereventresponse.message, section5pendingresponse.message, oipceventresponse.message)) + return DefaultMethodResult(False,'Due reminder notifications failed',cfreventresponse.identifier) + return DefaultMethodResult(True,'Due reminder notifications created',cfreventresponse.identifier) + except BusinessException as exception: + self.__logbusinessexception(exception) + + async def postcommentevent(self, commentid, requesttype, userid, isdelete=False, existingtaggedusers=False): + try: + commentresponse = commentevent().createcommentevent(commentid, requesttype, userid, isdelete, existingtaggedusers) + if commentresponse.success == False : + current_app.logger.error("FOI Notification failed for comment event=%s" % (commentresponse.message)) + return DefaultMethodResult(False,'Comment notifications failed',commentresponse.identifier) + return DefaultMethodResult(True,'Comment notifications created',commentresponse.identifier) + except BusinessException as exception: + self.__logbusinessexception(exception) + + def posteventforwatcher(self, requestid, request, requesttype, userid, username): + try: + if request['isrestricted']: + watcherresponse = watcherevent().createwatchereventforrestricted(requestid, request, requesttype, userid, username) + else: + watcherresponse = watcherevent().createwatcherevent(requestid, request, requesttype, userid, username) + if watcherresponse.success == False: + current_app.logger.error("FOI Notification failed for event for request= %s ; watcher response=%s" % (requestid, watcherresponse.message)) + except BusinessException as exception: + self.__logbusinessexception(exception) + + + async def posteventforsanctioncfrfeeform(self, ministryrequestid, userid, username): + self.__posteventforsanctioncfrfeeform(ministryrequestid, userid, username) + + async def posteventforcfrfeeform(self, ministryrequestid, userid, username): + try: + feewaivercommentresponse, refundcommentresponse= cfrfeeformevent().createeventforupdatedamounts(ministryrequestid, userid, username) + if feewaivercommentresponse.success == False or refundcommentresponse.success == False: + current_app.logger.error("FOI Comment failed for amount update event for CFRFEEFORM= %s" % (ministryrequestid)) + except BusinessException as exception: + self.__logbusinessexception(exception) + + async def postpaymentevent(self, requestid, paymenteventtype = PaymentEventType.paid.value): + try: + paymeneteventresponse = paymentevent().createpaymentevent(requestid, paymenteventtype) + if paymeneteventresponse.success == False: + current_app.logger.error("FOI Notification failed for payment event for request= %s ; event response=%s" % (requestid, paymeneteventresponse.message)) + except BusinessException as exception: + self.__logbusinessexception(exception) + + def posteventforemailfailure(self, ministryrequestid, requesttype, stage, reason, userid): + try: + emaileventresponse = emailevent().createemailevent(ministryrequestid, requesttype, stage, reason, userid) + if emaileventresponse.success == False: + current_app.logger.error("FOI Notification failed for email event = %s" % (emaileventresponse)) + except BusinessException as exception: + self.__logbusinessexception(exception) + + def postpaymentexpiryevent(self, ministryrequestid): + try: + paymeneteventresponse = paymentevent().createpaymentexpiredevent(ministryrequestid) + if paymeneteventresponse.success == False: + current_app.logger.error("FOI Expiry Notification failed for payment event for request= %s ; event response=%s" % (ministryrequestid, paymeneteventresponse.message)) + return paymeneteventresponse + except BusinessException as exception: + self.__logbusinessexception(exception) + + def __logbusinessexception(self, exception): + current_app.logger.error("%s,%s" % ('FOI Comment Notification Error', exception.message)) + + def __posteventforsanctioncfrfeeform(self, ministryrequestid, userid, username): + try: + cfrfeeeventresponse = cfrfeeformevent().createstatetransitionevent(ministryrequestid, userid, username) + if cfrfeeeventresponse.success == False: + current_app.logger.error("FOI Notification failed for event for CFRFEEFORM= %s" % (ministryrequestid)) + except BusinessException as exception: + self.__logbusinessexception(exception) \ No newline at end of file diff --git a/historical-search-api/request_api/services/extensionreasonservice.py b/historical-search-api/request_api/services/extensionreasonservice.py new file mode 100644 index 000000000..a3022059a --- /dev/null +++ b/historical-search-api/request_api/services/extensionreasonservice.py @@ -0,0 +1,13 @@ +from request_api.models.ExtensionReasons import ExtensionReason + +class extensionreasonservice: + + def getextensionreasons(self): + """ Returns the active records + """ + return ExtensionReason().getallextensionreasons() + + def getextensionreasonbyid(self, extensionreasonid): + """ Returns the active records + """ + return ExtensionReason().getextensionreasonbyid(extensionreasonid) \ No newline at end of file diff --git a/historical-search-api/request_api/services/extensionservice.py b/historical-search-api/request_api/services/extensionservice.py new file mode 100644 index 000000000..7013cb81c --- /dev/null +++ b/historical-search-api/request_api/services/extensionservice.py @@ -0,0 +1,416 @@ +from os import stat +from re import VERBOSE +from request_api.models.FOIRequestExtensions import FOIRequestExtension +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from request_api.models.FOIMinistryRequestDocuments import FOIMinistryRequestDocument +from request_api.models.FOIRequestExtensionDocumentMappings import FOIRequestExtensionDocumentMapping +from request_api.services.requestservice import requestservice +from request_api.services.documentservice import documentservice +from request_api.services.extensionreasonservice import extensionreasonservice +from request_api.services.eventservice import eventservice +from request_api.services.events.extension import ExtensionType +from datetime import datetime +import asyncio +import json +import base64 +from request_api.exceptions import BusinessException, Error + +class extensionservice: + """ FOI Extension management service + """ + otherdateformat = '%Y-%m-%d' + + def getrequestextensions(self, requestid, version=None): + extensions = [] + created_atdateformat = '%Y-%m-%d %H:%M:%S.%f' + requestversion = self.__getversionforrequest(requestid) if version is None else version + extensionrecords = FOIRequestExtension.getextensions(requestid, requestversion) + for entry in extensionrecords: + extensions.append({"foirequestextensionid": entry["foirequestextensionid"], + "extensionreasonid": entry["extensionreasonid"], + "extensionreson": entry["reason"], + "extensiontype": entry["extensiontype"], + "extensionstatusid": entry["extensionstatusid"], + "extensionstatus": entry["name"], + "extendedduedays": entry["extendedduedays"], + "extendedduedate": self.__formatdate(entry["extendedduedate"], self.otherdateformat), + "decisiondate": self.__formatdate(entry["decisiondate"], self.otherdateformat), + "approvednoofdays": entry["approvednoofdays"], + "created_at": self.__formatdate(entry["created_at"], created_atdateformat), + "createdby": entry["createdby"]}) + return extensions + + def __ispublicbodyextension(self, reasonid): + extensionreason = extensionreasonservice().getextensionreasonbyid(reasonid) + return 'extensiontype' in extensionreason and extensionreason['extensiontype'] == ExtensionType.publicbody.value + + def getrequestextension(self, extensionid): + requestextension = FOIRequestExtension().getextension(extensionid) + extensiondocuments = self.__getextensiondocuments(requestextension["foirequestextensionid"], requestextension["version"]) + documents = self.__getextensiondocumentsinfo(extensiondocuments) + extensionreason = extensionreasonservice().getextensionreasonbyid(requestextension['extensionreasonid']) + requestextensionwithdocuments = self.__createextensionobject(requestextension, documents, extensionreason) + return requestextensionwithdocuments + + def createrequestextension(self, foirequestid, ministryrequestid, extensionschema, userid): + version = self.__getversionforrequest(ministryrequestid) + reasonid = extensionschema['extensionreasonid'] + extensionreason = extensionreasonservice().getextensionreasonbyid(reasonid) + ispublicbodyextension = self.__ispublicbodyextension(reasonid) + if ('extensionstatusid' in extensionschema and extensionschema['extensionstatusid'] == 2) or ispublicbodyextension == True: + self.validatecreateextension(ministryrequestid, extensionschema, ispublicbodyextension) + ministryrequestschema = { + "duedate": extensionschema['extendedduedate'] + } + result = requestservice().saveministryrequestversion(ministryrequestschema, foirequestid, ministryrequestid, userid) + + newduedate = \ + ministryrequestschema['duedate'] \ + if isinstance(ministryrequestschema['duedate'], str) \ + else self.__formatdate(ministryrequestschema['duedate'], self.otherdateformat) + + if result.success == True: + version = self.__getversionforrequest(ministryrequestid) + #Set isactive:false for extensions with previous ministry request version + FOIRequestExtension.disableoldversions(version,ministryrequestid, userid) + extnsionresult = FOIRequestExtension.saveextension(ministryrequestid, version, extensionschema, extensionreason, userid, newduedate= newduedate) + else: + extnsionresult = FOIRequestExtension.saveextension(ministryrequestid, version, extensionschema, extensionreason, userid) + if 'documents' in extensionschema and extensionschema['extensionstatusid'] != 1: + self.saveextensiondocument(extensionschema['documents'], ministryrequestid, userid, extnsionresult.identifier) + return extnsionresult + + + def validatecreateextension(self, ministryrequestid, extensionschema, ispublicbodyextension= None): + if ispublicbodyextension is None: + ispublicbodyextension = self.__ispublicbodyextension(extensionschema['extensionreasonid']) + + if not ispublicbodyextension: + return + + extensions = self.getrequestextensions(ministryrequestid) + publicbodyextensiondays = [extension['extendedduedays'] for extension in extensions if extension['extensiontype'] == ExtensionType.publicbody.value] + if sum(publicbodyextensiondays) + extensionschema['extendedduedays'] > 30: + raise BusinessException(Error.INVALID_INPUT) + + + def getextensiontobesaved(self, ministryrequestid, extensions,version): + extensionstoadd=[] + extensionstodelete=[] + extensionidstodelete=[] + existingextensions = FOIRequestExtension.getextensions(ministryrequestid, version) + if(len(existingextensions) > 0): + identifiersforexisting = [] + identifiersforaxis = [] + for existingextension in existingextensions: + datestring = str(existingextension["extendedduedate"]).split(" ",1)[0] + identifierforexisting= str(existingextension["extensionreasonid"])+datestring+str(existingextension["extensionstatusid"]) + identifiersforexisting.append(identifierforexisting) + for axisextension in extensions: + datestring = str(axisextension["extendedduedate"]).split(" ",1)[0] + identifierforaxis= str(axisextension["extensionreasonid"])+datestring+str(axisextension["extensionstatusid"]) + identifiersforaxis.append(identifierforaxis) + if(identifierforaxis not in identifiersforexisting): + extensionstoadd.append(axisextension) + for existingextension in existingextensions: + datestring = str(existingextension["extendedduedate"]).split(" ",1)[0] + identifierforexisting= str(existingextension["extensionreasonid"])+datestring+str(existingextension["extensionstatusid"]) + if(identifierforexisting not in identifiersforaxis): + extensionstodelete.append(existingextension) + extensionidstodelete.append(existingextension["foirequestextensionid"]) + else: + extensionstoadd = extensions + + return extensionstoadd, extensionstodelete, extensionidstodelete + + def saveaxisrequestextension(self, ministryrequestid, extensions, userid, username): + version = self.__getversionforrequest(ministryrequestid) + extensionstoadd, extensionstodelete, extensionidstodelete = self.getextensiontobesaved(ministryrequestid, extensions,version) + if(len(extensionstodelete) > 0): + for existingextension in extensionstodelete: + self.deletedocuments(existingextension['foirequestextensionid'], existingextension['version'], ministryrequestid, userid) + deletedextensionresult= FOIRequestExtension.disableextensions(extensionidstodelete, userid) + newextensions = [] + if(len(extensionstoadd) > 0): + for extension in extensionstoadd: + newextensions.append(self.__createextension(extension, ministryrequestid, version, userid)) + extnsionresult = FOIRequestExtension.saveextensions(newextensions) + if deletedextensionresult.success == True and len(deletedextensionresult.args) > 0: + # Post event for system generated comments & notifications for deleted extensions + eventservice().posteventforaxisextension(ministryrequestid, deletedextensionresult.args[0], userid, username, "delete") + return extnsionresult + + def __createextension(self, extension, ministryrequestid, ministryrequestversion, userid): + createuserid = extension['createdby'] if 'createdby' in extension and extension['createdby'] is not None else userid + createdat = extension['created_at'] if 'created_at' in extension and extension['created_at'] is not None else datetime.now() + approveddate = extension['approveddate'] if 'approveddate' in extension else None + denieddate = extension['denieddate'] if 'denieddate' in extension else None + decisiondate = approveddate if approveddate else denieddate + approvednoofdays = extension['approvednoofdays'] if 'approvednoofdays' in extension else None + + if 'extensionstatusid' in extension: + extensionstatusid = extension['extensionstatusid'] + else: + extensionstatusid = 1 + + return FOIRequestExtension( + extensionreasonid=extension['extensionreasonid'], + extendedduedays=extension['extendedduedays'], + extendedduedate=extension['extendedduedate'], + decisiondate=decisiondate, + approvednoofdays=approvednoofdays, + extensionstatusid=extensionstatusid, + version=1, + isactive=True, + foiministryrequest_id=ministryrequestid, + foiministryrequestversion_id=ministryrequestversion, + created_at=createdat, + createdby=createuserid) + + # This is used for edit/approve/deny extension + # Edit can be normal edit like Pending -> Pending, Approved -> Approved, Denied -> Denied + # or it can be complex edit like Pending -> Approved/Denied, Approved -> Pending/Denied, Denied -> Approved/Pending + # if Pending -> Approved then the due date needs to be updated (new ministry version will get created), documents need to be mapped (if any) + # if Approved -> Pending/Denied then, due date needs to be reverted back (new ministry version will get created), documents need to be deleted (if any) + # any new ministry version created will create a new entry in FOIRequestExtensions, FOIMinistryDocuments (if any), FOIRequestExtensionDocumentsMapping (if any) tables + def createrequestextensionversion(self, foirequestid, ministryrequestid, extensionid, extensionschema, userid, username): + updatedduedate = None + ministryversion = self.__getversionforrequest(ministryrequestid) + extension = FOIRequestExtension.getextension(extensionid) + extensionversion = extension['version'] + prevstatus = extension["extensionstatusid"] + currentstatus = extensionschema["extensionstatusid"] + isstatuschangedfromapproved = self.__isstatuschangedfromapproved(prevstatus, currentstatus) + if isstatuschangedfromapproved == True: + # gets the latest approved request + approvedextension = self.getlatestapprovedrequest(extensionid, ministryrequestid, ministryversion) + # gets the latest approved due date if any else gets the original due date + updatedduedate = self.getlatestapprovedduedate(prevstatus, ministryrequestid, approvedextension) + + isdeletedocument = self.__isdeletedocument(isstatuschangedfromapproved, extensionid, extensionversion) + if isdeletedocument == True: + self.deletedocuments(extensionid, extensionversion, ministryrequestid, userid) + + #copyextension has the updated extension with data passed from FE with the new version of extension + updatedextension = self.__copyextensionproperties(extension, extensionschema, extensionversion) + # if current state is approved then gets the current extended due date + extendedduedate = self.getextendedduedate(updatedextension) + + extensionresult = FOIRequestExtension.createextensionversion(ministryrequestid, ministryversion, updatedextension, userid) + # Post event for system generated comments + eventservice().posteventforextension(ministryrequestid, extensionid, userid, username, "modify") + # save documents if it is part of current extension (update to the ministrydocuments table and extensiondocumentmapping table) + if 'documents' in updatedextension and updatedextension['documents'] and updatedextension['extensionstatusid'] != 1: + self.saveextensiondocument(updatedextension['documents'], ministryrequestid, userid, extensionid) + + # updates the duedate to extendedduedate or updatedduedate + # new ministry, extension, extensionmapping and document version gets created + if extensionresult.success == True and (isstatuschangedfromapproved == True or updatedextension['extensionstatusid'] == 2): + ministryrequestschema = { + "duedate": extendedduedate if extendedduedate else updatedduedate + } + requestservice().saveministryrequestversion(ministryrequestschema, foirequestid, ministryrequestid, userid) + + version = self.__getversionforrequest(ministryrequestid) + FOIRequestExtension.disableoldversions(version,ministryrequestid, userid) + + newduedate = \ + ministryrequestschema['duedate'] \ + if isinstance(ministryrequestschema['duedate'], str) \ + else self.__formatdate(ministryrequestschema['duedate'], self.otherdateformat) + + extensionresult.args = (*extensionresult.args, newduedate) + return extensionresult + + def deleterequestextension(self, requestid, ministryrequestid, extensionid, userid): + return self.createrequestextensionversionfordelete(requestid, ministryrequestid, extensionid, userid) + + # This is used for delete extension + # soft delete of extension and related documents + # due date reverted back to the prev approved due date + def createrequestextensionversionfordelete(self, requestid, ministryrequestid, extensionid, userid): + ministryversion = self.__getversionforrequest(ministryrequestid) + extension = FOIRequestExtension.getextension(extensionid) + prevstatus = extension["extensionstatusid"] + extensionversion = extension['version'] + + # this will be true if any document is attched to the extension + isdeletedocument = self.__isdeletedocument(True, extensionid, extensionversion) + + # gets the latest approvedextension if any + approvedextension = self.getlatestapprovedrequest(extensionid, ministryrequestid, ministryversion) + # gets the latest approved due date if any else gets the original due date + updatedduedate = self.getlatestapprovedduedate(prevstatus, ministryrequestid, approvedextension) + + + #copyextension has the updated extension with soft delete(isactive: False) with the new version of extension + # updatedextension = self.__copyextensionproperties(extension, extensionschema, extensionversion) + # this will create a new version of extension with isactive = False + # extensionresult = FOIRequestExtension.createextensionversion(ministryrequestid, ministryversion, updatedextension, userid) + #LATEST UPDATE :- updates existing isactive field to false if delete performed & no new version will be created for delete. + extensionresult = FOIRequestExtension.disableextension(extension["foirequestextensionid"], userid) + # once soft deleted, revert back the due date to prev due date + # creates a new version of ministry request, extension, extensiondocuments(if any) and documents(if any) + if extensionresult.success == True and prevstatus == 2: + ministryrequestschema = { + "duedate": updatedduedate + } + requestservice().saveministryrequestversion(ministryrequestschema, requestid, ministryrequestid, userid) + version = self.__getversionforrequest(ministryrequestid) + #Set isactive:false for extensions with previous ministry request version + FOIRequestExtension.disableoldversions(version,ministryrequestid, userid) + ## return due date + + newduedate = \ + ministryrequestschema['duedate'] \ + if isinstance(ministryrequestschema['duedate'], str) \ + else self.__formatdate(ministryrequestschema['duedate'], self.otherdateformat) + + extensionresult.args = (*extensionresult.args, newduedate) + # soft delete the documents attached to the extension + if extensionresult.success == True and isdeletedocument == True: + self.deletedocuments(extensionid, extensionversion, ministryrequestid, userid) + + return extensionresult + + def __isstatuschangedfromapproved(self, prevstatus, currentstatus): + if prevstatus == 2 and currentstatus != prevstatus: + return True + + def getduedatetoupdate(self, extension, ministryrequestid, updatedextension, approvedextension): + # gets the latest approved due date if any else gets the original due date + updatedduedate = self.getlatestapprovedduedate(extension, ministryrequestid, approvedextension) + # if current state is approved then gets the current extended due date + extendedduedate = self.getextendedduedate(updatedextension) + return extendedduedate if extendedduedate else updatedduedate + + def saveextensiondocument(self, extensiondocuments, ministryrequestid, userid, extensionid): + documents = [] + documentids = self.__savedocumentversion(ministryrequestid, extensiondocuments, userid) + for documentid in documentids: + documents.append(FOIMinistryRequestDocument().getdocument(documentid)) + self.saveextensiondocumentversion(extensionid, documents, userid) + + def __isdeletedocument(self, isstatuschangedfromapproved, extensionid, extensionversion): + isdeletedocument = False + documents = self.__getextensiondocuments(extensionid, extensionversion) + # 1. if prev status is Approved and current status is Pending or Denied + # 2. Prev extension has documents + # if any of the above condition is true then deletedocuments + if isstatuschangedfromapproved == True or documents: + isdeletedocument = True + return isdeletedocument + + def deletedocuments(self,extensionid, extensionversion, ministryrequestid, userid): + documents = self.__getextensiondocuments(extensionid, extensionversion) + for document in documents: + documentservice().deleterequestdocument(ministryrequestid, document["foiministrydocumentid"], userid, "ministryrequest") + + def getextendedduedate(self, extensionschema): + extensionreason = extensionreasonservice().getextensionreasonbyid(extensionschema['extensionreasonid']) + # if status is Approved or reason is Public Body then directly take the extendedduedate + if ('extensionstatusid' in extensionschema and extensionschema['extensionstatusid'] == 2) or extensionreason['extensiontype'] == ExtensionType.publicbody.value: + return extensionschema['extendedduedate'] + + def getlatestapprovedrequest(self, extensionid, ministryrequestid, ministryversion): + return FOIRequestExtension().getlatestapprovedextension(extensionid, ministryrequestid, ministryversion) + + def getlatestapprovedduedate(self, prevstatus, ministryrequestid, approvedextension): + if approvedextension and len(approvedextension) != 0: + return approvedextension['extendedduedate'] + # if Prev extension status was Approved and no approved extension in FOIRequestExtension table then get the original DueDate from FOIMinisrtRequests table + elif prevstatus == 2 and not approvedextension: + duedate = FOIMinistryRequest.getrequestoriginalduedate(ministryrequestid) + return duedate + #if current and prev status is Pending or Denied + else: + return None + + def saveextensiondocumentversion(self, extensionid, documents, userid): + extensionversion = self.__getextensionversion(extensionid) + if documents: + return FOIRequestExtensionDocumentMapping.saveextensiondocument(extensionid, documents, extensionversion, userid) + + + def __getextensionversion(self, extensionid): + return FOIRequestExtension().getversionforextension(extensionid) + + def __createextensionobject(self, requestextension, documents, extensionreason): + + decisiondate =requestextension['decisiondate'] if 'decisiondate' in requestextension else None + approvednoofdays = requestextension['approvednoofdays'] if 'approvednoofdays' in requestextension else None + extension = { + "foirequestextensionid": requestextension["foirequestextensionid"], + "extensionreasonid": requestextension["extensionreasonid"], + "extensionstatusid": requestextension["extensionstatusid"], + "extendedduedays": requestextension["extendedduedays"], + "extendedduedate": requestextension["extendedduedate"], + "extensiontype": extensionreason["extensiontype"], + "approvednoofdays": approvednoofdays, + "documents": documents + } + if requestextension["extensionstatusid"] == 2: + extension["approveddate"] = decisiondate + elif requestextension["extensionstatusid"] == 3: + extension["denieddate"] = decisiondate + return extension + + def __getextensiondocuments(self, extensionid, extensionversion): + return FOIRequestExtensionDocumentMapping().getextensiondocuments(extensionid, extensionversion) + + def __getextensiondocumentsinfo(self, extensiondocuments): + reqdocuments = [] + for extensiondocument in extensiondocuments: + document = FOIMinistryRequestDocument().getdocument(extensiondocument["foiministrydocumentid"]) + if document["isactive"] == True: + reqdocuments.append({"foiministrydocumentid": document["foiministrydocumentid"], "filename": document["filename"], "documentpath": document["documentpath"], "category": document["category"]}) + return reqdocuments + + def __savedocumentversion(self, ministryrequestid, extensiondocumentschema, userid): + documentids = [] + for document in extensiondocumentschema: + if 'foiministrydocumentid' in document: + documentid = document['foiministrydocumentid'] + else: + documentid = 0 + documentresult = documentservice().createministrydocumentversion(ministryrequestid, documentid, document, userid) + documentids.append(documentresult.identifier) + return documentids + + def __copyextensionproperties(self, copyextension, extensionschema, version): + copyextension['version'] = version +1 + copyextension['extensionreasonid'] = extensionschema['extensionreasonid'] if 'extensionreasonid' in extensionschema else copyextension['extensionreasonid'] + + ispublicbodyextension = self.__ispublicbodyextension(copyextension['extensionreasonid']) + if ispublicbodyextension == True: + extensionstatusid = 2 + else: + extensionstatusid = extensionschema['extensionstatusid'] if 'extensionstatusid' in extensionschema else copyextension['extensionstatusid'] + copyextension['extensionstatusid'] = extensionstatusid + copyextension['extendedduedays'] = extensionschema['extendedduedays'] if 'extendedduedays' in extensionschema else copyextension['extendedduedays'] + copyextension['extendedduedate'] = extensionschema['extendedduedate'] if 'extendedduedate' in extensionschema else copyextension['extendedduedate'] + approveddate = extensionschema['approveddate'] if 'approveddate' in extensionschema else copyextension['decisiondate'] + denieddate = extensionschema['denieddate'] if 'denieddate' in extensionschema else copyextension['decisiondate'] + decisiondate = approveddate if extensionstatusid == 2 else denieddate + copyextension['decisiondate'] = decisiondate + copyextension['approvednoofdays'] = extensionschema['approvednoofdays'] if 'approvednoofdays' in extensionschema else copyextension['approvednoofdays'] + + copyextension['documents'] = extensionschema['documents'] if 'documents' in extensionschema else None + copyextension['isactive'] = extensionschema['isactive'] if 'isactive' in extensionschema else True + copyextension['created_at'] = extensionschema['created_at'] if 'created_at' in extensionschema else None + copyextension['createdby'] = extensionschema['createdby'] if 'createdby' in extensionschema else None + return copyextension + + def __getversionforrequest(self, requestid): + """ Returns the active version of the request id based on type. + """ + return FOIMinistryRequest.getversionforrequest(requestid)[0] + + def __formatdate(self, datevalue, format): + return datevalue.strftime(format) if datevalue is not None else None + + def getrequestextensionscount(self, requestid): + extensionrecordscount = FOIRequestExtension.getextensionscount(requestid) + return extensionrecordscount + + diff --git a/historical-search-api/request_api/services/external/axissyncservice.py b/historical-search-api/request_api/services/external/axissyncservice.py new file mode 100644 index 000000000..2d3e10308 --- /dev/null +++ b/historical-search-api/request_api/services/external/axissyncservice.py @@ -0,0 +1,76 @@ +import requests +import os +from enum import Enum +from request_api.services.programareaservice import programareaservice +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from request_api.models.default_method_result import DefaultMethodResult +import more_itertools +from datetime import datetime as datetime2 +from request_api.services.external.keycloakadminservice import KeycloakAdminService +from flask import current_app +import json +class axissyncservice: + + AXIS_BASE_URL = os.getenv('AXIS_API_URL', None) + DEFAULT_SYNC_BATCHSIZE = 250 + AXIS_SYNC_BATCHSIZE = int(os.getenv('AXIS_SYNC_BATCHSIZE')) if os.getenv('AXIS_SYNC_BATCHSIZE') not in (None,'') else DEFAULT_SYNC_BATCHSIZE + + def syncpagecounts(self, axispgmrequests): + for entry in axispgmrequests: + self.__syncpagecounts(entry["bcgovcode"], entry["requesttype"]) + return DefaultMethodResult(True,'Batch execution completed', axispgmrequests) + + def __syncpagecounts(self, bcgovcode, requesttype): + programeara = programareaservice().getprogramareabyiaocode(bcgovcode) + requests = FOIMinistryRequest.getrequest_by_pgmarea_type(programeara['programareaid'], requesttype) + for batch in list(more_itertools.batched(requests, self.AXIS_SYNC_BATCHSIZE)): + batchrequest = list(batch) + axisids = self.__getaxisids(batchrequest) + #Fetch pagecount from axis : Begin + axis_pageinfo = self.axis_getpageinfo(axisids) + #Fetch pagecount from axis : End + if axis_pageinfo != {}: + response = FOIMinistryRequest.bulk_update_axispagecount(self.updatepagecount(batchrequest, axis_pageinfo)) + if response.success == False: + print("batch update failed for ids=", axisids) + else: + print("axis page response is empty for ids=", axisids) + return DefaultMethodResult(True,'Batch execution completed for bcgovcode=%s | requesttype=%s', bcgovcode, requesttype) + + + def axis_getpageinfo(self, axis_payload): + try: + if self.AXIS_BASE_URL not in (None,''): + access_token = KeycloakAdminService().get_token() + axis_page_endpoint = f'{self.AXIS_BASE_URL}/api/requestspagecount' + response = requests.post( + axis_page_endpoint, + headers={ + 'Authorization': f'Bearer {access_token}', + 'Content-Type': 'application/json' + }, + timeout=current_app.config.get('CONNECT_TIMEOUT'), + data=json.dumps(axis_payload) + ) + response.raise_for_status() + if response.status_code == 200: + return response.json() + return {} + except Exception as ex: + print('Exception occured in fetching page details', ex) + return {} + + def updatepagecount(self, requests, axisresponse): + for entry in requests: + axisrequestid = entry["axisrequestid"] + entry["updatedby"] = 'System' + entry["updated_at"] = datetime2.now() + entry["axispagecount"] = axisresponse[axisrequestid] if axisrequestid in axisresponse else entry["axispagecount"] + entry["axislanpagecount"] = axisresponse[axisrequestid] if axisrequestid in axisresponse else entry["axislanpagecount"] + return requests + + + def __getaxisids(self, requests): + return [entry['axisrequestid'] for entry in requests] + + diff --git a/historical-search-api/request_api/services/external/bpmservice.py b/historical-search-api/request_api/services/external/bpmservice.py new file mode 100644 index 000000000..1ef074eb0 --- /dev/null +++ b/historical-search-api/request_api/services/external/bpmservice.py @@ -0,0 +1,196 @@ +import requests +import os +import json +from enum import Enum + +from request_api.schemas.external.bpmschema import MessageSchema, VariableSchema, VariableMessageSchema +from request_api.services.external.camundaservice import camundaservice, VariableType + +""" +This class is reserved for workflow services integration. +Supported operations: claim + +__author__ = "sumathi.thirumani@aot-technologies.com" + +""" +class bpmservice(camundaservice): + + def createinstance(self, messagequeue, message, token=None): + if self.bpmengineresturl is not None: + _variables = {"variables":{}} + for key in message: + _variabletype = VariableType.Integer.value if key in ["id"] else VariableType.String.value + _variables["variables"][key] = {"type" : _variabletype, "value": message[key]} + variableschema = VariableMessageSchema().dump(_variables) + createresponce = requests.post(self._getUrl_(None,self._geProcessDefinitionKey_(messagequeue)), data=json.dumps(variableschema), headers = self._getHeaders_(token)) + if createresponce.ok: + _createresponce = json.loads(createresponce.content) + return _createresponce["id"] + return None + + def getinstancevariables(self, instanceid, token=None): + if self.bpmengineresturl is not None: + response = requests.get(self.bpmengineresturl+"/process-instance/"+str(instanceid)+"/variables", headers = self._getHeaders_(token)) + return json.loads(response.content) if response.ok else None + return None + + def searchinstancebyvariable(self, definitionkey, searchby, token=None): + if self.bpmengineresturl is not None: + searchschema = {"processDefinitionKey": definitionkey, + "variables": searchby, + "sortBy":"definitionId","sortOrder":"desc", + "maxResults":1 + } + searchresponse = requests.post(self.bpmengineresturl+"/process-instance",data=json.dumps(searchschema), headers = self._getHeaders_(token)) + if searchresponse.ok: + _search_content = json.loads(searchresponse.content) + if _search_content not in ([], None) and len(_search_content) > 0: + return self.searchprocessinstance(str(_search_content[0]["id"])) + return None + return None + + def searchprocessinstance(self, pid, token=None): + if self.bpmengineresturl is not None: + if pid not in (None, ""): + idresponse = requests.get(self.bpmengineresturl+"/process-instance/"+pid, headers = self._getHeaders_(token)) + if idresponse.ok: + return pid + return None + return None + + def unopenedsave(self,processinstanceid, userid, messagetype, token=None): + if self.bpmengineresturl is not None: + messageschema = MessageSchema().dump({"processInstanceId": processinstanceid, + "messageName": messagetype, + "processVariables":{ + "assignedTo": VariableSchema().dump({"type" : VariableType.String.value, "value": userid}) + } + }) + return self.__post_message(messagetype, messageschema, token) + else: + return + + + def unopenedcomplete(self,processinstanceid, data, messagetype, token=None): + if self.bpmengineresturl is not None: + messageschema = MessageSchema().dump({"processInstanceId": processinstanceid, + "messageName": messagetype, + "processVariables":{ + "foiRequestMetaData": VariableSchema().dump({"data" : VariableType.String.value, "value": data}) + } + }) + return self.__post_message(messagetype, messageschema, token) + else: + return + + """" + def opened(self, filenumber, groupname, userid, messagetype, token=None): + if self.bpmengineresturl is not None: + messageschema = MessageSchema().dump({"messageName": messagetype, + "localCorrelationKeys":{ + "id": VariableSchema().dump({"type" : VariableType.String.value, "value": filenumber}) + }, + "processVariables":{ + "filenumber": VariableSchema().dump({"type" : VariableType.String.value, "value": filenumber}), + "assignedGroup": VariableSchema().dump({"type" : VariableType.String.value, "value": groupname}), + "assignedTo": VariableSchema().dump({"type" : VariableType.String.value, "value": userid}) + } + }) + return requests.post(self._getUrl_(messagetype), data=json.dumps(messageschema), headers = self._getHeaders_(token)) + else: + return + """ + + def openedcomplete(self, wfinstanceid, filenumber, data, messagetype, token=None): + if self.bpmengineresturl is not None: + messageschema = MessageSchema().dump({"messageName": messagetype, + "processInstanceId": wfinstanceid, + "localCorrelationKeys":{ + "id": VariableSchema().dump({"type" : VariableType.String.value, "value": filenumber}) + }, + "processVariables":{ + "foiRequestMetaData": VariableSchema().dump({"data" : VariableType.String.value, "value": data})} + }) + return self.__post_message(messagetype, messageschema, token) + else: + return + + def feeevent(self,axisrequestid, data, paymentstatus, token=None): + if self.bpmengineresturl is not None: + messageschema = MessageSchema().dump({"messageName": MessageType.managepayment.value, + "correlationKeys":{ + "axisRequestId": VariableSchema().dump({"type" : VariableType.String.value, "value": axisrequestid}) + }, + "processVariables":{ + "foiRequestMetaData": VariableSchema().dump({"data" : VariableType.String.value, "value": data}), + "paymentstatus": VariableSchema().dump({"type" : VariableType.String.value, "value": paymentstatus})} + }) + return self.__post_message(MessageType.managepayment.value, messageschema, token) + else: + return + + def correspondanceevent(self,wfinstanceid, filenumber, data, token=None): + if self.bpmengineresturl is not None: + messageschema = MessageSchema().dump({"messageName": MessageType.iaocorrenspodence.value, + "processInstanceId": wfinstanceid, + "localCorrelationKeys":{ + "id": VariableSchema().dump({"type" : VariableType.String.value, "value": filenumber}) + }, + "processVariables":{ + "foiRequestMetaData": VariableSchema().dump({"data" : VariableType.String.value, "value": data})} + }) + return self.__post_message(MessageType.iaocorrenspodence.value, messageschema, token) + else: + return + + def reopenevent(self,processinstanceid, data, messagetype, token=None): + return self.unopenedcomplete(processinstanceid, data, messagetype, token) + + def __post_message(self, messagetype, messageschema, token=None): + return requests.post(self._getUrl_(messagetype), data=json.dumps(messageschema), headers = self._getHeaders_(token)) + + def _getUrl_(self, messagetype, definitionkey=None): + if messagetype is not None: + return self.bpmengineresturl+"/message" + elif definitionkey is not None: + return self.bpmengineresturl+"/process-definition/key/"+definitionkey+"/start" + return self.bpmengineresturl + + def _geProcessDefinitionKey_(self, messagequeue): + if messagequeue == "foi-rawrequest": + return "foi-request" + return None + + def _getserviceaccounttoken_(self): + auth_response = requests.post(self.bpmtokenurl, auth=(self.bpmclientid, self.bpmclientsecret), headers={ + 'Content-Type': 'application/x-www-form-urlencoded'}, data='grant_type=client_credentials') + return auth_response.json().get('access_token') + + def _getHeaders_(self, token): + """Generate headers.""" + if token is None: + token = self._getserviceaccounttoken_(); + return { + "Authorization": "Bearer " + token, + "Content-Type": "application/json", + } + +class MessageType(Enum): + intakeclaim = "foi-intake-assignment" + intakecomplete = "foi-intake-complete" + intakereopen = "foi-intake-reopen" + iaoopenclaim = "foi-iao-open-assignment" + iaoopencomplete = "foi-iao-open-complete" + iaoclaim = "foi-iao-assignment" + iaocomplete = "foi-iao-complete" + iaoreopen = "foi-iao-reopen" + ministryclaim = "foi-ministry-assignment" + ministrycomplete = "foi-ministry-complete" + feepayment = "foi-fee-payment" + managepayment = "foi-manage-payment" + iaocorrenspodence = "foi-iao-correnspodence" + +class ProcessDefinitionKey(Enum): + rawrequest = "foi-request" + ministryrequest = "foi-request-processing" + \ No newline at end of file diff --git a/historical-search-api/request_api/services/external/camundaservice.py b/historical-search-api/request_api/services/external/camundaservice.py new file mode 100644 index 000000000..be25070ac --- /dev/null +++ b/historical-search-api/request_api/services/external/camundaservice.py @@ -0,0 +1,38 @@ +import requests +import os +from enum import Enum + +""" +This class is reserved for workflow services integration. +Supported operations: claim + +__author__ = "sumathi.thirumani@aot-technologies.com" + +""" +class camundaservice: + + bpmengineresturl = os.getenv('BPM_ENGINE_REST_URL') + bpmtokenurl = os.getenv("BPM_TOKEN_URL") + bpmclientid = os.getenv("BPM_CLIENT_ID") + bpmclientsecret = os.getenv("BPM_CLIENT_SECRET") + bpmgranttype = os.getenv("BPM_GRANT_TYPE") + + + def _getserviceaccounttoken_(self): + auth_response = requests.post(self.bpmtokenurl, auth=(self.bpmclientid, self.bpmclientsecret), headers={ + 'Content-Type': 'application/x-www-form-urlencoded'}, data='grant_type=client_credentials') + return auth_response.json().get('access_token') + + + def _getheaders_(self, token): + """Generate headers.""" + if token is None: + token = self._getserviceaccounttoken_(); + return { + "Authorization": "Bearer " + token, + "Content-Type": "application/json", + } + +class VariableType(Enum): + String = "String" + Integer = "Integer" \ No newline at end of file diff --git a/historical-search-api/request_api/services/external/eventqueueservice.py b/historical-search-api/request_api/services/external/eventqueueservice.py new file mode 100644 index 000000000..6b11cec88 --- /dev/null +++ b/historical-search-api/request_api/services/external/eventqueueservice.py @@ -0,0 +1,24 @@ +import os +from walrus import Database +from request_api.models.default_method_result import DefaultMethodResult +from request_api.exceptions import BusinessException +import logging + +class eventqueueservice: + """This class is reserved for integration with event queue (currently redis streams). + """ + host = os.getenv('EVENT_QUEUE_HOST') + port = os.getenv('EVENT_QUEUE_PORT') + password = os.getenv('EVENT_QUEUE_PASSWORD') + + db = Database(host=host, port=port, db=0,password=password) + + def add(self, streamkey, payload): + try: + stream = self.db.Stream(streamkey) + msgid = stream.add(payload, id="*") + return DefaultMethodResult(True,'Added to stream',msgid.decode('utf-8')) + except Exception as err: + logging.error("Error in contacting Redis Stream") + logging.error(err) + return DefaultMethodResult(False,err,-1) \ No newline at end of file diff --git a/historical-search-api/request_api/services/external/keycloakadminservice.py b/historical-search-api/request_api/services/external/keycloakadminservice.py new file mode 100644 index 000000000..5ec6af21e --- /dev/null +++ b/historical-search-api/request_api/services/external/keycloakadminservice.py @@ -0,0 +1,204 @@ +import requests +import os +import ast +import request_api +from request_api.models.OperatingTeams import OperatingTeam +from request_api.exceptions import BusinessException, Error +import redis + +class KeycloakAdminService: + + keycloakhost = os.getenv('KEYCLOAK_ADMIN_HOST') + keycloakrealm = os.getenv('KEYCLOAK_ADMIN_REALM') + keycloakclientid = os.getenv('KEYCLOAK_ADMIN_CLIENT_ID') + keycloakclientsecret = os.getenv('KEYCLOAK_ADMIN_CLIENT_SECRET') + keycloakadminserviceaccount = os.getenv('KEYCLOAK_ADMIN_SRVACCOUNT') + keycloakadminservicepassword = os.getenv('KEYCLOAK_ADMIN_SRVPASSWORD') + keycloakadminintakegroupid = os.getenv('KEYCLOAK_ADMIN_INTAKE_GROUPID') + 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 + try: + cache_client = redis.from_url(self.cache_redis_url,decode_responses=True) + _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)) + except BusinessException as exception: + print("Error happened while accessing token on KeycloakAdminService {0}".format(exception.message)) + finally: + cache_client = None + return _accesstoken + + + def getgroups(self, allowedgroups = None): + groups = [] + globalgroups = self.getallgroups() + if allowedgroups is not None: + for allowedgroup in allowedgroups: + for group in globalgroups: + if self.formatgroupname(group["name"]) == self.formatgroupname(allowedgroup["name"]): + groups.append({'id': group['id'],'name':group['name'], 'type': allowedgroup["type"] }) + else: + groups = globalgroups + return groups + + + def getallgroups(self): + url ='{0}/auth/admin/realms/{1}/groups'.format(self.keycloakhost,self.keycloakrealm) + groupsresponse = requests.get(url, headers=self.getheaders()) + groups = [] + if groupsresponse.status_code == 200 and groupsresponse.content != '': + globalgroups = groupsresponse.json() + for group in globalgroups: + groups.append({'id': group['id'],'name':group['name'], 'type':None}) + return groups + + def getgroup(self, groupname, type=None): + groups = [] + allgroups = self.getallgroups() + for entry in allgroups: + if entry["name"] == groupname: + groups.append({'id': entry['id'],'name':entry['name'], 'type': type}) + return groups + + def getgroupsandmembers(self, allowedgroups = None): + allowedgroups = self.getgroups(allowedgroups) + for group in allowedgroups: + group["members"] = self.getgroupmembersbyid(group["id"]) + return allowedgroups + + + # INPUT: groupid (string) - intake group id from the keyclock request + # OUTPUT - group information (Array of strings) e.g. [email address] + # Description: This method will fetch the group information from the keycloak server + # by passing the group id as part of the parameters. The group information contains + # the email address of the group. + def getgroupinformation(self, groupid): + if groupid is None or groupid == '': + return None + groupurl ='{0}/auth/admin/realms/{1}/groups/{2}'.format(self.keycloakhost,self.keycloakrealm,groupid) + groupresponse = requests.get(groupurl, headers=self.getheaders()) + if groupresponse.status_code == 200 and groupresponse.content != '': + return groupresponse.json() + return None + + # INPUT: groupname (string) - intake group name from the keyclock request + # OUTPUT - group information (Array of strings) e.g. [email address] + # Description: This method first extracts the group id from the group name. + # The group id is then passed as a parameter to the getgroupinformation method + # to fetch the group information. The group information contains the email address + def getgroupdetails(self, groupname): + group = self.getallgroups() + for entry in group: + if entry["name"] == groupname: + groupinfo = self.getgroupinformation(entry['id']) + if groupinfo is not None: + return groupinfo + return None + + # 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()) + users = [] + if groupresponse.status_code == 200 and groupresponse.content != '': + for user in groupresponse.json(): + _user = self.__createuser(user) + users.append(_user) + if users not in (None, []): + return sorted(users, key=lambda k: str(k['lastname']), reverse = False) + return users + + def getallusercount(self): + userurl ='{0}/auth/admin/realms/{1}/users/count'.format(self.keycloakhost,self.keycloakrealm) + userresponse = requests.get(userurl, headers=self.getheaders()) + if userresponse.status_code == 200 and userresponse.content != '': + return int(userresponse.content) + return 100 + + def getallusers(self): + usercount = self.getallusercount() + userurl ='{0}/auth/admin/realms/{1}/users?max={2}'.format(self.keycloakhost,self.keycloakrealm,usercount) + userresponse = requests.get(userurl, headers=self.getheaders()) + users = [] + if userresponse.status_code == 200 and userresponse.content != '': + for user in userresponse.json(): + _user = self.__createuser(user) + users.append(_user) + if users not in (None, []): + return sorted(users, key=lambda k: str(k['lastname']), reverse = False) + return users + + def __createuser(self, user): + return { + 'id':user['id'], + 'username': self.__formatusername(user), + 'email': user['email'] if 'email' in user is not None else None, + 'firstname':user['firstName'] if 'firstName' in user is not None else None, + 'lastname': user['lastName'] if 'lastName' in user is not None else None , + 'enabled': user['enabled'], + 'origusername': user['username'] + } + + def __formatusername(self,user): + if "attributes" in user and "idir_username" in user["attributes"]: + _username = user["attributes"]["idir_username"][0].lower() + return _username+"@idir" if _username.endswith("@idir") == False else _username + return user['username'] + + def getheaders(self): + return { + "Authorization": "Bearer " + self.get_token(), + "Content-Type": "application/json", + } + + def formatgroupname(self,input): + return input.lower().replace(' ', '') + + def getmembersbygroupname(self, groupname): + operatingteam = OperatingTeam.getteam(groupname) + if operatingteam is not None: + return self.getmembersbygroupnameandtype(operatingteam['name'], operatingteam['type']) + return [] + + + def getmembersbygroupnameandtype(self, groupname, type): + allowedgroups = self.getgroup(groupname, type) + for group in allowedgroups: + group["members"] = self.getgroupmembersbyid(group["id"]) + return allowedgroups + + + + + \ No newline at end of file diff --git a/historical-search-api/request_api/services/external/storageservice.py b/historical-search-api/request_api/services/external/storageservice.py new file mode 100644 index 000000000..e0c45b018 --- /dev/null +++ b/historical-search-api/request_api/services/external/storageservice.py @@ -0,0 +1,255 @@ +from unicodedata import category +from request_api.schemas.foirequestsformslist import FOIRequestsFormsList +import requests +from aws_requests_auth.aws_auth import AWSRequestsAuth +import os +import uuid +import mimetypes +import logging + +from request_api.models.DocumentPathMapper import DocumentPathMapper +from request_api.utils.enums import DocumentPathMapperCategory + +import boto3 +from botocore.exceptions import ClientError +from botocore.config import Config + + +formsbucket = os.getenv('OSS_S3_FORMS_BUCKET') +accesskey = os.getenv('OSS_S3_FORMS_ACCESS_KEY_ID') +secretkey = os.getenv('OSS_S3_FORMS_SECRET_ACCESS_KEY') +s3host = os.getenv('OSS_S3_HOST') +s3region = os.getenv('OSS_S3_REGION') +s3service = os.getenv('OSS_S3_SERVICE') +s3chunksize = os.getenv('OSS_S3_CHUNK_SIZE') +class storageservice: + """This class is reserved for S3 storage services integration. + """ + accesskey = os.getenv('OSS_S3_FORMS_ACCESS_KEY_ID') + secretkey = os.getenv('OSS_S3_FORMS_SECRET_ACCESS_KEY') + recordsaccesskey = os.getenv('OSS_S3_RECORDS_ACCESS_KEY_ID') + recordssecretkey = os.getenv('OSS_S3_RECORDS_SECRET_ACCESS_KEY') + s3host = os.getenv('OSS_S3_HOST') + s3region = os.getenv('OSS_S3_REGION') + s3service = os.getenv('OSS_S3_SERVICE') + s3environment = os.getenv('OSS_S3_ENVIRONMENT') + s3timeout = 3600 + + def uploadbytes(self, filename, bytes, ministrycode, requestnumber): + try: + auth = AWSRequestsAuth(aws_access_key=accesskey, + aws_secret_access_key=secretkey, + aws_host=s3host, + aws_region=s3region, + aws_service=s3service) + + s3uri = 'https://{0}/{1}/{2}/{3}/{4}'.format(s3host,formsbucket, ministrycode, requestnumber, filename) + response = requests.put(s3uri, data=None, auth=auth) + header = { + 'X-Amz-Date': response.request.headers['x-amz-date'], + 'Authorization': response.request.headers['Authorization'], + 'Content-Type': mimetypes.MimeTypes().guess_type(filename)[0] + } + + #upload to S3 + requests.put(s3uri, data=bytes, headers=header) + attachmentobj = {"success": True, 'filename': filename, 'documentpath': s3uri} + except Exception as ex: + logging.error(ex) + attachmentobj = {"success": False, 'filename': filename, 'documentpath': None} + return attachmentobj + + def upload(self, attachment): + docpathmapper = DocumentPathMapper().getdocumentpath("Attachments") + formsbucket = docpathmapper['bucket'] + + if(self.accesskey is None or self.secretkey is None or self.s3host is None or formsbucket is None): + raise ValueError('accesskey is None or secretkey is None or S3 host is None or formsbucket is None') + + foirequestform = FOIRequestsFormsList().load(attachment) + ministrycode = foirequestform.get('ministrycode') + requestnumber = foirequestform.get('requestnumber') + filestatustransition = foirequestform.get('filestatustransition') + filename = foirequestform.get('filename') + filenamesplittext = os.path.splitext(filename) + uniquefilename = '{0}{1}'.format(uuid.uuid4(),filenamesplittext[1].lower()) + + auth = AWSRequestsAuth(aws_access_key=self.accesskey, + aws_secret_access_key=self.secretkey, + aws_host=self.s3host, + aws_region=self.s3region, + aws_service=self.s3service) + + s3uri = 'https://{0}/{1}/{2}/{3}/{4}/{5}'.format(self.s3host,formsbucket,ministrycode,requestnumber,filestatustransition,uniquefilename) + response = requests.put(s3uri, data=None, auth=auth) + + header = { + 'X-Amz-Date': response.request.headers['x-amz-date'], + 'Authorization': response.request.headers['Authorization'], + 'Content-Type': mimetypes.MimeTypes().guess_type(filename)[0] + } + + #upload to S3 + requests.put(s3uri, data=attachment['file'], headers=header) + + attachmentobj = {'filename': filename, 'documentpath': s3uri, 'category': filestatustransition} + return attachmentobj + + def bulk_upload(self, requestfilejson, category): + + 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 + + for file in requestfilejson: + foirequestform = FOIRequestsFormsList().load(file) + ministrycode = foirequestform.get('ministrycode') + requestnumber = foirequestform.get('requestnumber') + filestatustransition = foirequestform.get('filestatustransition') + filename = foirequestform.get('filename') + s3sourceuri = foirequestform.get('s3sourceuri') + filenamesplittext = os.path.splitext(filename) + uniquefilename = '{0}{1}'.format(uuid.uuid4(),filenamesplittext[1].lower()) + docpathmapper = DocumentPathMapper().getdocumentpath(category,ministrycode) + formsbucket = docpathmapper['bucket'] + auth = AWSRequestsAuth(aws_access_key=docpathmapper['attributes']['s3accesskey'], + aws_secret_access_key=docpathmapper['attributes']['s3secretkey'], + aws_host=self.s3host, + aws_region=self.s3region, + aws_service=self.s3service) + + s3uri = s3sourceuri if s3sourceuri is not None else 'https://{0}/{1}/{2}/{3}/{4}/{5}'.format(self.s3host, formsbucket,ministrycode,requestnumber,filestatustransition,uniquefilename) + response = requests.put(s3uri,data=None,auth=auth) if s3sourceuri is None else requests.get(s3uri,auth=auth) + + file['filepath']=s3uri + file['authheader']=response.request.headers['Authorization'] + file['amzdate']=response.request.headers['x-amz-date'] + file['uniquefilename']=uniquefilename if s3sourceuri is None else '' + 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'] + s3client = self.__get_s3client(category, docpathmapper) + filename, file_extension = os.path.splitext(filepath) + response = s3client.generate_presigned_url( + ClientMethod='get_object', + Params= {'Bucket': formsbucket, 'Key': '{0}'.format(filepath),'ResponseContentType': '{0}/{1}'.format('image' if file_extension in ['.png','.jpg','.jpeg','.gif'] else 'application',file_extension.replace('.',''))}, + ExpiresIn=3600,HttpMethod='GET' + ) + return response + + def bulk_upload_s3_presigned(self, ministryrequestid, requestfilejson, category, bcgovcode=None): + docpathmapper = DocumentPathMapper().getdocumentpath(category, bcgovcode) + formsbucket = docpathmapper['bucket'] + s3client = self.__get_s3client(category, docpathmapper) + for file in requestfilejson: + foirequestform = FOIRequestsFormsList().load(file) + ministrycode = foirequestform.get('ministrycode') + requestnumber = foirequestform.get('requestnumber') + filestatustransition = foirequestform.get('filestatustransition') + filename = foirequestform.get('filename') + filenamesplittext = os.path.splitext(filename) + uniquefilename = '/'.join(file.get('filepath', "").split('/')[5:]) or '{0}{1}'.format(uuid.uuid4(),filenamesplittext[1].lower()) + filepath = '/'.join(file.get('filepath', "").split('/')[4:]) or self.__getfilepath(category,ministrycode,requestnumber,filestatustransition,uniquefilename) + if file.get('multipart', False): + response = s3client.create_multipart_upload(Bucket=formsbucket, Key='{0}'.format(filepath)) + max_size = int(s3chunksize) + uploadid = response['UploadId'] + file['filepaths'] = [] + for part in range(1, file.get("filesize")//max_size + 2): + response = s3client.generate_presigned_url( + ClientMethod='upload_part', + Params= { + 'Bucket': formsbucket, + 'Key': '{0}'.format(filepath), + 'UploadId': uploadid, + 'PartNumber': part + }, + ExpiresIn=self.s3timeout #, HttpMethod='PUT' + ) + file['filepaths'].append(response) + file['uploadid']=uploadid + else: + response = s3client.generate_presigned_url( + ClientMethod='put_object', + Params= { + 'Bucket': formsbucket, + 'Key': '{0}'.format(filepath) + }, + ExpiresIn=self.s3timeout, HttpMethod='PUT' + ) + file['filepath']=response + file['filepathdb']='https://{0}/{1}/{2}'.format(self.s3host,formsbucket,filepath) + file['uniquefilename']=uniquefilename + return requestfilejson + + def complete_upload_s3_presigned(self, requestjson, category, bcgovcode): + docpathmapper = DocumentPathMapper().getdocumentpath(category, bcgovcode) + formsbucket = docpathmapper['bucket'] + s3client = self.__get_s3client(category, docpathmapper) + return s3client.complete_multipart_upload( + Bucket=formsbucket, + Key='/'.join(requestjson.get('filepath', "").split('/')[4:]), + MultipartUpload={'Parts': requestjson.get('parts')}, + UploadId=requestjson.get('uploadid') + ) + + def is_valid_category(self, category): + categories = set(item.value.lower() for item in DocumentPathMapperCategory) + return category in categories + + def __get_s3client(self, category, docpathmapper): + return boto3.client('s3',config=Config(signature_version='s3v4'), + endpoint_url='https://{0}/'.format(self.s3host), + aws_access_key_id= docpathmapper['attributes']['s3accesskey'], + aws_secret_access_key= docpathmapper['attributes']['s3secretkey'], + region_name= self.s3region + ) + + def __getbucket(self, category, programarea=None): + docpathmapper = DocumentPathMapper().getdocumentpath(category, programarea if category.lower() == "attachments" else None) + return docpathmapper['bucket'] + + def __getfilepath(self,category,ministrycode,requestnumber,filestatustransition,uniquefilename): + if category.lower() == 'attachments': + return '{0}/{1}/{2}/{3}'.format(ministrycode,requestnumber,filestatustransition,uniquefilename) + elif category.lower() == 'records': + return '{0}/{1}'.format(requestnumber,uniquefilename) + + def download(self, s3uri): + + if(accesskey is None or secretkey is None or s3host is None or formsbucket is None): + raise ValueError('accesskey is None or secretkey is None or S3 host is None or formsbucket is None') + + auth = AWSRequestsAuth(aws_access_key=accesskey, + aws_secret_access_key=secretkey, + aws_host=s3host, + aws_region=s3region, + aws_service=s3service) + + templatefile= requests.get(s3uri, auth=auth) + return templatefile + + + def downloadtemplate(self, templatepath): + + if(accesskey is None or secretkey is None or s3host is None or formsbucket is None): + raise ValueError('accesskey is None or secretkey is None or S3 host is None or formsbucket is None') + #To DO : make the values of templatetype and templatename dynamic + s3uri = 'https://{0}/{1}{2}'.format(s3host,formsbucket,templatepath) + templatefile= self.download(s3uri) + responsehtml=templatefile.text + return responsehtml diff --git a/historical-search-api/request_api/services/fee_service.py b/historical-search-api/request_api/services/fee_service.py new file mode 100644 index 000000000..a68a6dd31 --- /dev/null +++ b/historical-search-api/request_api/services/fee_service.py @@ -0,0 +1,239 @@ +import base64 +from datetime import date +from datetime import datetime +from typing import Dict +from urllib.parse import unquote_plus, urlencode + +import pytz +import requests +from flask import current_app + +from request_api.exceptions import BusinessException, Error +from request_api.models import FeeCode, Payment, RevenueAccount, FOIRawRequest, FOIMinistryRequest +from request_api.services.cfrfeeservice import cfrfeeservice +from request_api.utils.enums import FeeType +from .hash_service import HashService + + +class FeeService: + """ FOI Fee management service + + This service class manages all CRUD operations related to Fee + + """ + + def __init__(self, request_id: int, code: str=None, payment_id=None): + self.request_id = request_id + if payment_id: + self.payment: Payment = Payment.find_by_id(payment_id) + self.fee_code: FeeCode = FeeCode.find_by_id(self.payment.fee_code_id) + else: + self.payment = None + self.fee_code: FeeCode = FeeCode.get_fee(code=code, valid_date=date.today()) + if not self.fee_code: + raise BusinessException(Error.INVALID_INPUT) + + # If application fee, use raw request id, else use minsitry request id + if self.fee_code.code == FeeType.application.value: + self.request = FOIRawRequest.get_request(request_id) + if self.request is None: + raise BusinessException(Error.INVALID_INPUT) + else: + self.request = FOIMinistryRequest.getrequestbyministryrequestid(request_id) + if self.request is None: + raise BusinessException(Error.INVALID_INPUT) + + @staticmethod + def get_fee(code: str, quantity: int, valid_date: date): + """Return fee details.""" + fee_code: FeeCode = FeeCode.get_fee( + code=code, valid_date=valid_date + ) + if not fee_code: + raise BusinessException(Error.DATA_NOT_FOUND) + + fee_response = dict( + fee_code=code, + fee=fee_code.fee, + quantity=quantity, + total=quantity * fee_code.fee, + description=fee_code.description + ) + + return fee_response + + def init_payment(self, pay_request: Dict): + return_route = pay_request.get('return_route') + quantity = int(pay_request.get('quantity', 1)) + if self.fee_code.code == FeeType.processing.value: + total = self._get_cfr_fee(self.request_id, pay_request) + else: + total = quantity * self.fee_code.fee + self.payment = Payment( + fee_code_id=self.fee_code.fee_code_id, + quantity=quantity, + total=total, + status='PENDING', + request_id=self.request_id + ).flush() + + self.payment.paybc_url = self._get_paybc_url(self.fee_code, return_route) + self.payment.transaction_number = self._get_transaction_number() + self.payment.commit() + pay_response = self._dump() + return pay_response + + def complete_payment(self, pay_response: Dict): + """Complete payment.""" + response_url = pay_response.get('response_url') + current_app.logger.debug('response_url : %s', response_url) + + if self.payment.status == 'PAID' or not response_url: + raise BusinessException(Error.INVALID_INPUT) + + self.payment.response_url = response_url + self.payment.commit() + + parsed_args = HashService.parse_url_params(response_url) + + # Validate transaction number + if self.payment.transaction_number != parsed_args.get('pbcTxnNumber'): + raise BusinessException(Error.INVALID_INPUT) + + # Check if trnApproved is 1=Success, 0=Declined + trn_approved: bool = parsed_args.get('trnApproved') == '1' + if trn_approved: + self._validate_hash(parsed_args, response_url) + + # Add paybc api call to verify + # handle duplicate payment response. + paybc_status = None + if trn_approved or parsed_args.get('trnNumber', '').upper() == 'DUPLICATE PAYMENT': + paybc_status = self._validate_with_paybc(trn_approved) + + self.payment.order_id = parsed_args.get('trnOrderId') + self.payment.completed_on = datetime.now() + self.payment.status = 'PAID' if paybc_status == 'PAID' else parsed_args.get('messageText').upper() + self.payment.commit() + + return self._dump(), parsed_args + + def check_if_paid(self): + """Check payment.""" + + return self.payment.status == 'PAID' + + def _validate_with_paybc(self, trn_approved): + paybc_status = None + paybc_response = self.get_paybc_transaction_details() + if trn_approved and (paybc_status := paybc_response.get('paymentstatus')) != 'PAID': + raise BusinessException(Error.INVALID_INPUT) + if paybc_status == 'PAID' and self.payment.total != float(paybc_response.get('trnamount')): + raise BusinessException(Error.INVALID_INPUT) + return paybc_status + + def _validate_hash(self, parsed_args, response_url): + # validate if hashValue matches with rest of the values hashed + hash_value = parsed_args.pop('hashValue', None) + pay_response_url_without_hash = urlencode(parsed_args) + if not HashService.is_valid_checksum(pay_response_url_without_hash, hash_value): + current_app.logger.warning(f'Transaction is approved, but hash is not matching : {response_url}') + raise BusinessException(Error.INVALID_INPUT) + + def _dump(self): + pay_response = dict( + paybc_url=self.payment.paybc_url, + payment_id=self.payment.payment_id, + request_id=self.payment.request_id, + status=self.payment.status, + total=self.payment.total + ) + return pay_response + + def _get_paybc_url(self, fee_code: FeeCode, return_route): + """Return the payment system url.""" + date_val = datetime.now().astimezone(pytz.timezone(current_app.config['LEGISLATIVE_TIMEZONE'])).strftime( + '%Y-%m-%d') + if self.fee_code.code == FeeType.application.value: + base_url = current_app.config['FOI_WEB_PAY_URL'] + if self.fee_code.code == FeeType.processing.value: + base_url = current_app.config['FOI_FFA_URL'] + return_url = f"{base_url}{return_route if return_route else ''}/{self.payment.request_id}/{self.payment.payment_id}" + revenue_account: RevenueAccount = RevenueAccount.find_by_id(fee_code.revenue_account_id) + + url_params_dict = {'trnDate': date_val, + 'pbcRefNumber': current_app.config.get('PAYBC_REF_NUMBER'), + 'glDate': date_val, + 'description': 'Direct_Sale', + 'trnNumber': self._get_transaction_number(), + 'trnAmount': self.payment.total, + 'paymentMethod': 'CC', + 'redirectUri': return_url, + 'currency': 'CAD', + 'revenue': self._get_gl_coding(self.payment.total, revenue_account) + } + + url_params = urlencode(url_params_dict) + # unquote is used below so that unescaped url string can be hashed + url_params_dict['hashValue'] = HashService.encode(unquote_plus(url_params)) + encoded_query_params = urlencode(url_params_dict) # encode it again to inlcude the hash + paybc_url = current_app.config.get('PAYBC_PORTAL_URL') + return f'{paybc_url}?{encoded_query_params}' + + @staticmethod + def _get_gl_coding(total, revenue_account: RevenueAccount): + return f'1:{revenue_account.client}.{revenue_account.responsibility_centre}.' \ + f'{revenue_account.service_line}.{revenue_account.stob}.{revenue_account.project_code}' \ + f'.000000.0000' \ + f":{format(total, '.2f')}" + + def _get_transaction_number(self): + return f"{current_app.config.get('PAYBC_TXN_PREFIX')}{self.payment.payment_id:0>8}" + + def get_paybc_transaction_details(self): + # Call PAYBC web service, get access token and use it in get txn call + access_token = self.get_paybc_token().json().get('access_token') + + paybc_transaction_url: str = current_app.config.get('PAYBC_API_BASE_URL') + paybc_ref_number: str = current_app.config.get('PAYBC_REF_NUMBER') + + endpoint = f'{paybc_transaction_url}/paybc/payment/{paybc_ref_number}/{self.payment.transaction_number}' + response = requests.get( + endpoint, + headers={ + 'Authorization': f'Bearer {access_token}', + 'Content-Type': 'application/json' + }, + timeout=current_app.config.get('CONNECT_TIMEOUT') + ) + return response.json() + + def get_paybc_token(self): + """Generate oauth token from payBC which will be used for all communication.""" + current_app.logger.debug('Getting token') + return response + + def _get_cfr_fee(self, ministry_request_id, pay_request): + if pay_request.get('retry', False): + return Payment.find_failed_transaction(pay_request['transaction_number']).total + else: + fee = cfrfeeservice().getapprovedcfrfee(ministry_request_id)['feedata']['balanceremaining'] + if pay_request.get('half', False): + return fee/2 + else: + return fee diff --git a/historical-search-api/request_api/services/foirequest/requestservicebuilder.py b/historical-search-api/request_api/services/foirequest/requestservicebuilder.py new file mode 100644 index 000000000..7d1ecb70f --- /dev/null +++ b/historical-search-api/request_api/services/foirequest/requestservicebuilder.py @@ -0,0 +1,197 @@ + +from re import T +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from request_api.models.FOIMinistryRequestDivisions import FOIMinistryRequestDivision +from request_api.models.RequestorType import RequestorType +from request_api.models.FOIRequestContactInformation import FOIRequestContactInformation +from request_api.models.FOIRequestPersonalAttributes import FOIRequestPersonalAttribute +from request_api.models.FOIRequestApplicants import FOIRequestApplicant +from request_api.models.FOIRequestApplicantMappings import FOIRequestApplicantMapping +from request_api.models.FOIRequestTeams import FOIRequestTeam +from request_api.models.FOIRequestStatus import FOIRequestStatus +from request_api.models.FOIRequestOIPC import FOIRequestOIPC + +from datetime import datetime as datetime2 +from request_api.utils.enums import MinistryTeamWithKeycloackGroup, StateName +from request_api.services.foirequest.requestserviceconfigurator import requestserviceconfigurator +from request_api.services.foirequest.requestserviceministrybuilder import requestserviceministrybuilder + + +import json +class requestservicebuilder(requestserviceconfigurator): + """ This class consolidates the helper functions for creating new foi request based on iao actions. + """ + + def createministry(self, requestschema, ministry, activeversion, userid, filenumber=None, ministryid=None): + foiministryrequest = FOIMinistryRequest() + foiministryrequest.__dict__.update(ministry) + foiministryrequest.requeststatusid = self.__getrequeststatusid(requestschema.get("requeststatuslabel")) + foiministryrequest.requeststatuslabel = requestschema.get("requeststatuslabel") + foiministryrequest.isactive = True + foiministryrequest.axisrequestid = requestschema.get("axisRequestId") + foiministryrequest.axissyncdate = requestschema.get("axisSyncDate") + foiministryrequest.axispagecount = requestschema.get("axispagecount") + foiministryrequest.recordspagecount = requestschema.get("recordspagecount") + foiministryrequest.filenumber = self.generatefilenumber(ministry["code"], requestschema.get("foirawrequestid")) if filenumber is None else filenumber + foiministryrequest.programareaid = self.getvalueof("programArea",ministry["code"]) + 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") + foiministryrequest.estimatedpagecount = requestschema.get("estimatedpagecount") + foiministryrequest.estimatedtaggedpagecount = requestschema.get("estimatedtaggedpagecount") + if requestschema.get("isoipcreview") is not None and requestschema.get("isoipcreview") != "": + foiministryrequest.isoipcreview = requestschema.get("isoipcreview") + foiministryrequest.oipcreviews = self.prepareoipc(requestschema, ministryid, activeversion, userid) + + if requestschema.get("cfrDueDate") is not None and requestschema.get("cfrDueDate") != "": + foiministryrequest.cfrduedate = requestschema.get("cfrDueDate") + startdate = "" + if (requestschema.get("startDate") is not None): + startdate = requestschema.get("startDate") + elif (requestschema.get("requestProcessStart") is not None): + startdate = requestschema.get("requestProcessStart") + foiministryrequest.startdate = startdate + foiministryrequest.createdby = userid + requeststatuslabel = self.getpropertyvaluefromschema(requestschema, 'requeststatuslabel') + if requeststatuslabel is not None: + status = self.getstatusname(requeststatuslabel) + if self.isNotBlankorNone(requestschema,"fromDate","main") == True: + foiministryrequest.recordsearchfromdate = requestschema.get("fromDate") + if self.isNotBlankorNone(requestschema,"toDate","main") == True: + foiministryrequest.recordsearchtodate = requestschema.get("toDate") + self.__updateassignedtoandgroup(foiministryrequest, requestschema, ministry, status, filenumber, ministryid) + self.__updateministryassignedtoandgroup(foiministryrequest, requestschema, ministry, status) + + if ministryid is not None: + foiministryrequest.foiministryrequestid = ministryid + activeversion = FOIMinistryRequest.getversionforrequest(ministryid)[0]+1 + divisions = FOIMinistryRequestDivision().getdivisions(ministryid , activeversion-1) + foiministryrequest.divisions = requestserviceministrybuilder().createfoirequestdivisionfromobject(divisions, ministryid, activeversion, userid) + foiministryrequest.documents = requestserviceministrybuilder().createfoirequestdocuments(requestschema,ministryid , activeversion , userid) + foiministryrequest.extensions = requestserviceministrybuilder().createfoirequestextensions(ministryid, activeversion, userid) + if 'subjectCode' in requestschema and requestschema['subjectCode'] is not None and requestschema['subjectCode'] != '': + foiministryrequest.subjectcode = requestserviceministrybuilder().createfoirequestsubjectcode(requestschema, ministryid, activeversion, userid) + foiministryrequest.version = activeversion + foiministryrequest.closedate = self.getpropertyvaluefromschema(requestschema, 'closedate') + foiministryrequest.closereasonid = self.getpropertyvaluefromschema(requestschema, 'closereasonid') + if self.getpropertyvaluefromschema(requestschema, 'isofflinepayment') is not None: + foiministryrequest.isofflinepayment = self.getpropertyvaluefromschema(requestschema, 'isofflinepayment') + return foiministryrequest + + def __updateministryassignedtoandgroup(self, foiministryrequest, requestschema, ministry, status): + if self.__isgrouprequired(status): + foiministryrequest.assignedministrygroup = MinistryTeamWithKeycloackGroup[ministry["code"]].value + if self.isNotBlankorNone(requestschema,"assignedministrygroup","main") == True: + foiministryrequest.assignedministrygroup = requestschema.get("assignedministrygroup") + if self.isNotBlankorNone(requestschema,"assignedministryperson","main") == True and requestschema.get("reopen") != True: + foiministryrequest.assignedministryperson = requestschema.get("assignedministryperson") + requestserviceministrybuilder().createfoiassigneefromobject(requestschema.get("assignedministryperson"), requestschema.get("assignedministrypersonFirstName"), requestschema.get("assignedministrypersonMiddleName"), requestschema.get("assignedministrypersonLastName")) + else: + foiministryrequest.assignedministryperson = None + + def __updateassignedtoandgroup(self, foiministryrequest, requestschema, ministry, status, filenumber=None, ministryid=None): + foiministryrequest.assignedgroup = requestschema.get("assignedGroup") + if self.isNotBlankorNone(requestschema,"assignedTo","main") == True: + foiministryrequest.assignedto = requestschema.get("assignedTo") + requestserviceministrybuilder().createfoiassigneefromobject(requestschema.get("assignedTo"), requestschema.get("assignedToFirstName"), requestschema.get("assignedToMiddleName"), requestschema.get("assignedToLastName")) + else: + foiministryrequest.assignedto = None + + def __isgrouprequired(self,status): + if status == StateName.callforrecords.value or status == StateName.recordsreview.value or status == StateName.consult.value or status == StateName.feeestimate.value or status == StateName.ministrysignoff.value or status == StateName.response.value: + return True + else: + return False + + def __getgroupname(self, requesttype, bcgovcode): + return 'Flex Team' if requesttype == "general" else FOIRequestTeam.getdefaultprocessingteamforpersonal(bcgovcode) + + def __getrequeststatusid(self, requeststatuslabel): + state = FOIRequestStatus.getrequeststatusbylabel( + requeststatuslabel + ) + stateid = ( + state.get("requeststatusid") + if isinstance(state, dict) and state.get("requeststatusid") not in (None, "") + else "" + ) + return stateid + + def createcontactinformation(self,dataformat, name, value, contacttypes, userid): + contactinformation = FOIRequestContactInformation() + contactinformation.contactinformation = value + contactinformation.dataformat = dataformat + contactinformation.createdby = userid + for contacttype in contacttypes: + if contacttype["name"] == name: + contactinformation.contacttypeid =contacttype["contacttypeid"] + return contactinformation + + def createapplicant(self,firstname, lastname, appltcategory, userid, middlename = None,businessname = None, alsoknownas = None, dob = None): + requestapplicant = FOIRequestApplicantMapping() + _applicant = FOIRequestApplicant().saveapplicant(firstname, lastname, middlename, businessname, alsoknownas, dob, userid) + requestapplicant.foirequestapplicantid = _applicant.identifier + if appltcategory is not None: + requestertype = RequestorType().getrequestortype(appltcategory) + requestapplicant.requestortypeid = requestertype["requestortypeid"] + return requestapplicant + + def createpersonalattribute(self, name, value,attributetypes, userid): + personalattribute = FOIRequestPersonalAttribute() + personalattribute.createdby = userid + if value is not None and value !="" and value: + for attributetype in attributetypes: + if attributetype["name"] == name: + personalattribute.personalattributeid = attributetype["attributeid"] + personalattribute.attributevalue = value + return personalattribute + + def prepareoipc(self, requestschema, ministryrequestid, version, userid): + oipcarr = [] + if 'oipcdetails' in requestschema: + for oipc in requestschema['oipcdetails']: + oipcreview = FOIRequestOIPC() + oipcreview.foiministryrequest_id = ministryrequestid + oipcreview.foiministryrequestversion_id=version + oipcreview.oipcno = oipc["oipcno"] + oipcreview.reviewtypeid = oipc["reviewtypeid"] + oipcreview.reasonid = oipc["reasonid"] + oipcreview.statusid = oipc["statusid"] + oipcreview.outcomeid = oipc["outcomeid"] + oipcreview.investigator = oipc["investigator"] if oipc["investigator"] not in (None, "") else None + oipcreview.isinquiry = oipc["isinquiry"] + oipcreview.isjudicialreview = oipc["isjudicialreview"] + oipcreview.issubsequentappeal = oipc["issubsequentappeal"] + oipcreview.issubsequentappeal = oipc["issubsequentappeal"] + oipcreview.receiveddate = oipc["receiveddate"] if oipc["receiveddate"] not in (None, "") else None + oipcreview.closeddate = oipc["closeddate"] if oipc["closeddate"] not in (None, "") else None + if oipc["isinquiry"] == True: + oipcreview.inquiryattributes = self.__formatoipcattributes(oipc["inquiryattributes"]) + oipcreview.createdby=userid + oipcreview.created_at= datetime2.now().isoformat() + oipcarr.append(oipcreview) + return oipcarr + + def __formatoipcattributes(self, inquiryattributes): + if inquiryattributes not in (None, "") and inquiryattributes["inquirydate"] in ("","null"): + inquiryattributes["inquirydate"] = None + return inquiryattributes + + + def isNotBlankorNone(self, dataschema, key, location): + if location == "main": + if key in dataschema and dataschema.get(key) is not None and dataschema.get(key) and dataschema.get(key) != "": + return True + else: + if dataschema.get(location) is not None and key in dataschema.get(location) and dataschema.get(location)[key] and dataschema.get(location)[key] is not None and dataschema.get(location)[key] !="": + return True + return False + + + + + + + diff --git a/historical-search-api/request_api/services/foirequest/requestserviceconfigurator.py b/historical-search-api/request_api/services/foirequest/requestserviceconfigurator.py new file mode 100644 index 000000000..2c31f1674 --- /dev/null +++ b/historical-search-api/request_api/services/foirequest/requestserviceconfigurator.py @@ -0,0 +1,84 @@ + +from re import T +from request_api.models.ProgramAreas import ProgramArea +from request_api.models.ContactTypes import ContactType +from request_api.models.DeliveryModes import DeliveryMode +from request_api.models.ReceivedModes import ReceivedMode +from request_api.models.ApplicantCategories import ApplicantCategory +from request_api.models.FOIRequestStatus import FOIRequestStatus +from request_api.models.SubjectCodes import SubjectCode +from enum import Enum +import datetime +import secrets + +class requestserviceconfigurator: + """This class consolidates helper fiunctions and constants + """ + + def getstatusname(self,requeststatuslabel): + allstatus = FOIRequestStatus().getrequeststatuses() + for status in allstatus: + if status["statuslabel"] == requeststatuslabel: + return status["name"] + return None; + + def getvalueof(self,name,key): + if name == "receivedMode": + rmode = ReceivedMode().getreceivedmode(key) + return rmode["receivedmodeid"] + elif name == "deliveryMode": + dmode = DeliveryMode().getdeliverymode(key) + return dmode["deliverymodeid"] + elif name == "category": + applcategory = ApplicantCategory().getapplicantcategory(key) + return applcategory["applicantcategoryid"] + elif name == "programArea": + pgarea = ProgramArea().getprogramarea(key) + return pgarea["programareaid"] + elif name == "subjectCode": + subjectcode = SubjectCode().getsubjectcodebyname(key) + return subjectcode["subjectcodeid"] if subjectcode is not None else None + + def getpropertyvaluefromschema(self,requestschema,property): + return requestschema.get(property) if property in requestschema else None + + def generatefilenumber(self, code, id): + tmp = str(id) + randomnum = secrets.randbits(32) + return code + "-" + str(datetime.date.today().year) + "-" + tmp + str(randomnum)[:5] + + def contacttypemapping(self): + return [{"name": ContactType.email.value, "key" : "email"}, + {"name": ContactType.homephone.value, "key" : "phonePrimary"}, + {"name": ContactType.workphone.value, "key" : "workPhonePrimary"}, + {"name": ContactType.mobilephone.value, "key" : "phoneSecondary"}, + {"name": ContactType.workphone2.value, "key" : "workPhoneSecondary"}, + {"name": ContactType.streetaddress.value, "key" : "address"}, + {"name": ContactType.streetaddress.value, "key" : "addressSecondary"}, + {"name": ContactType.streetaddress.value, "key" : "city"}, + {"name": ContactType.streetaddress.value, "key" : "province"}, + {"name": ContactType.streetaddress.value, "key" : "postal"}, + {"name": ContactType.streetaddress.value, "key" : "country"}] + + def personalattributemapping(self): + return [{"name": "BC Correctional Service Number", "key" : "correctionalServiceNumber", "location":"main"}, + {"name": "BC Public Service Employee Number", "key" : "publicServiceEmployeeNumber", "location":"main"}, + {"name": "BC Personal Health Care Number", "key" : "personalHealthNumber", "location":"additionalPersonalInfo"}, + {"name": "Adoptive Mother First Name", "key" : "adoptiveMotherFirstName", "location":"additionalPersonalInfo"}, + {"name": "Adoptive Mother Last Name", "key" : "adoptiveMotherLastName", "location":"additionalPersonalInfo"}, + {"name": "Adoptive Father First Name", "key" : "adoptiveFatherFirstName", "location":"additionalPersonalInfo"}, + {"name": "Adoptive Father Last Name", "key" : "adoptiveFatherLastName", "location":"additionalPersonalInfo"} + ] + +class ContactType(Enum): + email = "Email" + homephone = "Home Phone" + workphone = "Work Phone" + mobilephone = "Mobile Phone" + workphone2 = "Work Phone 2" + streetaddress = "Street Address" + + + + + diff --git a/historical-search-api/request_api/services/foirequest/requestservicecreate.py b/historical-search-api/request_api/services/foirequest/requestservicecreate.py new file mode 100644 index 000000000..ac6f24275 --- /dev/null +++ b/historical-search-api/request_api/services/foirequest/requestservicecreate.py @@ -0,0 +1,183 @@ + +from re import T +from request_api.models.FOIRequests import FOIRequest +from request_api.models.ContactTypes import ContactType +from request_api.models.PersonalInformationAttributes import PersonalInformationAttribute +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from request_api.services.watcherservice import watcherservice +from request_api.services.foirequest.requestservicebuilder import requestservicebuilder +from request_api.services.foirequest.requestserviceministrybuilder import requestserviceministrybuilder +from request_api.services.foirequest.requestserviceconfigurator import requestserviceconfigurator +from request_api.models.PersonalInformationAttributes import PersonalInformationAttribute +from request_api.models.FOIRequestContactInformation import FOIRequestContactInformation +from request_api.models.FOIRequestPersonalAttributes import FOIRequestPersonalAttribute +from request_api.models.FOIRequestApplicantMappings import FOIRequestApplicantMapping +from request_api.utils.enums import StateName + +import json +class requestservicecreate: + """ This class consolidates the creation of new FOI request upon scenarios: open, save by both iao and ministry. + """ + + def saverequest(self,foirequestschema, userid, foirequestid=None, ministryid=None, filenumber=None, version=None, rawrequestid=None, wfinstanceid=None): + activeversion = 1 if version is None else version + + # FOI Request + openfoirequest = FOIRequest() + openfoirequest.foirawrequestid = foirequestschema.get("foirawrequestid") if rawrequestid is None else rawrequestid + openfoirequest.version = activeversion + openfoirequest.requesttype = foirequestschema.get("requestType") + openfoirequest.initialdescription = foirequestschema.get("description") + openfoirequest.receiveddate = foirequestschema.get("receivedDate") + openfoirequest.ministryRequests = self.__prepareministries(foirequestschema, activeversion, filenumber,ministryid, userid) + openfoirequest.contactInformations = self.__prearecontactinformation(foirequestschema, userid) + if requestservicebuilder().isNotBlankorNone(foirequestschema,"fromDate","main") == True: + openfoirequest.initialrecordsearchfromdate = foirequestschema.get("fromDate") + if requestservicebuilder().isNotBlankorNone(foirequestschema,"toDate","main") == True: + openfoirequest.initialrecordsearchtodate = foirequestschema.get("toDate") + if requestservicebuilder().isNotBlankorNone(foirequestschema,"deliveryMode","main") == True: + openfoirequest.deliverymodeid = requestserviceconfigurator().getvalueof("deliveryMode",foirequestschema.get("deliveryMode")) + if requestservicebuilder().isNotBlankorNone(foirequestschema,"receivedMode","main") == True: + openfoirequest.receivedmodeid = requestserviceconfigurator().getvalueof("receivedMode",foirequestschema.get("receivedMode")) + if requestservicebuilder().isNotBlankorNone(foirequestschema,"category","main") == True: + openfoirequest.applicantcategoryid = requestserviceconfigurator().getvalueof("category",foirequestschema.get("category")) + openfoirequest.personalAttributes = self._prearepersonalattributes(foirequestschema, userid) + openfoirequest.requestApplicants = self.__prepareapplicants(foirequestschema, userid) + if foirequestid is not None: + openfoirequest.foirequestid = foirequestid + openfoirequest.wfinstanceid = wfinstanceid if wfinstanceid is not None else None + openfoirequest.createdby = userid + return FOIRequest.saverequest(openfoirequest) + + + def saverequestversion(self,foirequestschema, foirequestid , ministryid, userid): + activeversion = 1 + filenumber =foirequestschema.get("idNumber") + #Identify version + if foirequestid is not None: + _foirequest = FOIRequest().getrequest(foirequestid) + if _foirequest != {}: + activeversion = _foirequest["version"] + 1 + else: + return _foirequest + self.__disablewatchers(ministryid, foirequestschema, userid) + result = self.saverequest(foirequestschema, userid, foirequestid,ministryid,filenumber,activeversion,_foirequest["foirawrequestid"],_foirequest["wfinstanceid"]) + if result.success == True: + FOIMinistryRequest.deActivateFileNumberVersion(ministryid, filenumber, userid) + return result + + def saveministryrequestversion(self,ministryrequestschema, foirequestid , ministryid, userid, usertype = None): + _foirequest = FOIRequest().getrequest(foirequestid) + _foiministryrequest = FOIMinistryRequest().getrequestbyministryrequestid(ministryid) + _foirequestapplicant = FOIRequestApplicantMapping().getrequestapplicants(foirequestid,_foirequest["version"]) + _foirequestcontact = FOIRequestContactInformation().getrequestcontactinformation(foirequestid,_foirequest["version"]) + _foirequestpersonalattrbs = FOIRequestPersonalAttribute().getrequestpersonalattributes(foirequestid,_foirequest["version"]) + foiministryrequestarr = [] + foirequest = requestserviceministrybuilder().createfoirequestfromobject(_foirequest, userid) + foiministryrequest = requestserviceministrybuilder().createfoiministryrequestfromobject(_foiministryrequest, ministryrequestschema, userid, usertype) + foiministryrequestarr.append(foiministryrequest) + foirequest.ministryRequests = foiministryrequestarr + foirequest.requestApplicants = requestserviceministrybuilder().createfoirequestappplicantfromobject(_foirequestapplicant, foirequestid, _foirequest['version']+1, userid) + foirequest.contactInformations = requestserviceministrybuilder().createfoirequestcontactfromobject( _foirequestcontact, foirequestid, _foirequest['version']+1, userid) + foirequest.personalAttributes = requestserviceministrybuilder().createfoirequestpersonalattributefromobject(_foirequestpersonalattrbs, foirequestid, _foirequest['version']+1, userid) + result = FOIRequest.saverequest(foirequest) + if result.success == True: + FOIMinistryRequest.deActivateFileNumberVersion(ministryid, _foiministryrequest['filenumber'], userid) + return result + + def __prepareministries(self,foirequestschema, activeversion, filenumber,ministryid, userid): + foiministryrequestarr = [] + if foirequestschema.get("selectedMinistries") is not None: + for ministry in foirequestschema.get("selectedMinistries"): + foiministryrequestarr.append(requestservicebuilder().createministry(foirequestschema, ministry, activeversion, userid, filenumber,ministryid)) + return foiministryrequestarr + + def _prearepersonalattributes(self, foirequestschema, userid): + personalattributearr = [] + if foirequestschema.get("requestType") == "personal": + attributetypes = PersonalInformationAttribute().getpersonalattributes() + for attrb in requestserviceconfigurator().personalattributemapping(): + attrbvalue = None + if attrb["location"] == "main" and requestservicebuilder().isNotBlankorNone(foirequestschema,attrb["key"],"main") == True: + attrbvalue = foirequestschema.get(attrb["key"]) + if attrb["location"] == "additionalPersonalInfo" and requestservicebuilder().isNotBlankorNone(foirequestschema, attrb["key"],"additionalPersonalInfo") == True: + attrbvalue = foirequestschema.get(attrb["location"])[attrb["key"]] + if attrbvalue is not None and attrbvalue and attrbvalue != "": + personalattributearr.append( + requestservicebuilder().createpersonalattribute(attrb["name"], + attrbvalue, + attributetypes, userid) + ) + return personalattributearr + + def __prearecontactinformation(self, foirequestschema, userid): + contactinformationarr = [] + contacttypes = ContactType().getcontacttypes() + for contact in requestserviceconfigurator().contacttypemapping(): + if foirequestschema.get(contact["key"]) is not None: + contactinformationarr.append( + requestservicebuilder().createcontactinformation(contact["key"], + contact["name"], + foirequestschema.get(contact["key"]), + contacttypes, userid) + ) + return contactinformationarr + + def __prepareapplicants(self, foirequestschema, userid): + requestapplicantarr = [] + selfalsoknownas=None + selfdob=None + if foirequestschema.get("additionalPersonalInfo") is not None: + applicantinfo = foirequestschema.get("additionalPersonalInfo") + selfdob = applicantinfo["birthDate"] if requestservicebuilder().isNotBlankorNone(foirequestschema,"birthDate","additionalPersonalInfo") else None + selfalsoknownas = applicantinfo["alsoKnownAs"] if requestservicebuilder().isNotBlankorNone(foirequestschema,"alsoKnownAs","additionalPersonalInfo") else None + requestapplicantarr.append( + requestservicebuilder().createapplicant(foirequestschema.get("firstName"), + foirequestschema.get("lastName"), + "Self", + userid, + foirequestschema.get("middleName"), + foirequestschema.get("businessName"), + selfalsoknownas, + selfdob) + ) + + #Prepare additional applicants + if foirequestschema.get("additionalPersonalInfo") is not None: + addlapplicantinfo = foirequestschema.get("additionalPersonalInfo") + if requestservicebuilder().isNotBlankorNone(foirequestschema,"childFirstName","additionalPersonalInfo"): + requestapplicantarr.append( + requestservicebuilder().createapplicant(self.__getkeyvalue(addlapplicantinfo,"childFirstName") , + self.__getkeyvalue(addlapplicantinfo,"childLastName"), + "Applying for a child under 12", + userid, + self.__getkeyvalue(addlapplicantinfo,"childMiddleName") , + None, + self.__getkeyvalue(addlapplicantinfo,"childAlsoKnownAs") , + self.__getkeyvalue(addlapplicantinfo,"childBirthDate")) + ) + if requestservicebuilder().isNotBlankorNone(foirequestschema,"anotherFirstName","additionalPersonalInfo"): + requestapplicantarr.append( + requestservicebuilder().createapplicant(self.__getkeyvalue(addlapplicantinfo,"anotherFirstName") , + self.__getkeyvalue(addlapplicantinfo,"anotherLastName") , + "Applying for other person", + userid, + self.__getkeyvalue(addlapplicantinfo,"anotherMiddleName") , + None, + self.__getkeyvalue(addlapplicantinfo,"anotherAlsoKnownAs"), + self.__getkeyvalue(addlapplicantinfo,"anotherBirthDate") ) + ) + return requestapplicantarr + + def __disablewatchers(self, ministryid, requestschema, userid): + requeststatuslabel = requestschema.get("requeststatuslabel") if 'requeststatuslabel' in requestschema else None + if requeststatuslabel is not None: + status = requestserviceconfigurator().getstatusname(requeststatuslabel) + if status == StateName.open.value: + watchers = watcherservice().getministryrequestwatchers(int(ministryid), True) + for watcher in watchers: + watcherschema = {"ministryrequestid":ministryid,"watchedbygroup":watcher["watchedbygroup"],"watchedby":watcher["watchedby"],"isactive":False} + watcherservice().createministryrequestwatcher(watcherschema, userid, None) + + def __getkeyvalue(self, inputschema, property): + return inputschema[property] if inputschema is not None and inputschema.get(property) is not None else '' \ No newline at end of file diff --git a/historical-search-api/request_api/services/foirequest/requestservicegetter.py b/historical-search-api/request_api/services/foirequest/requestservicegetter.py new file mode 100644 index 000000000..6079be737 --- /dev/null +++ b/historical-search-api/request_api/services/foirequest/requestservicegetter.py @@ -0,0 +1,338 @@ + +from re import T +from request_api.models.FOIRequests import FOIRequest +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from request_api.models.FOIMinistryRequestDivisions import FOIMinistryRequestDivision +from request_api.models.FOIRequestContactInformation import FOIRequestContactInformation +from request_api.models.FOIRequestPersonalAttributes import FOIRequestPersonalAttribute +from request_api.models.FOIRequestApplicantMappings import FOIRequestApplicantMapping +from request_api.models.FOIMinistryRequestSubjectCodes import FOIMinistryRequestSubjectCode +from request_api.models.FOIRestrictedMinistryRequests import FOIRestrictedMinistryRequest +from request_api.models.FOIRequestOIPC import FOIRequestOIPC +from request_api.services.oipcservice import oipcservice +from dateutil.parser import parse +from request_api.services.cfrfeeservice import cfrfeeservice +from request_api.services.paymentservice import paymentservice +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 +from request_api.utils.enums import StateName + +class requestservicegetter: + """ This class consolidates retrival of FOI request for actors: iao and ministry. + """ + + def getrequest(self,foirequestid,foiministryrequestid): + request = FOIRequest.getrequest(foirequestid) + requestministry = FOIMinistryRequest.getrequestbyministryrequestid(foiministryrequestid) + requestcontactinformation = FOIRequestContactInformation.getrequestcontactinformation(foirequestid,request['version']) + requestapplicants = FOIRequestApplicantMapping.getrequestapplicants(foirequestid,request['version']) + requestministrydivisions = FOIMinistryRequestDivision.getdivisions(foiministryrequestid,requestministry['version']) + iaorestrictrequestdetails = FOIRestrictedMinistryRequest.getrestricteddetails(ministryrequestid=foiministryrequestid,type='iao') + + baserequestinfo = self.__preparebaseinfo(request,foiministryrequestid,requestministry,requestministrydivisions) + baserequestinfo['lastStatusUpdateDate'] = FOIMinistryRequest.getLastStatusUpdateDate(foiministryrequestid, requestministry['requeststatuslabel']).strftime(self.__genericdateformat()), + for contactinfo in requestcontactinformation: + if contactinfo['contacttype.name'] == 'Email': + baserequestinfo.update({'email':contactinfo['contactinformation']}) + else: + baserequestinfo.update({contactinfo['dataformat']:contactinfo['contactinformation']}) + + additionalpersonalinfo ={} + for applicant in requestapplicants: + firstname = applicant['foirequestapplicant.firstname'] + middlename = applicant['foirequestapplicant.middlename'] + lastname = applicant['foirequestapplicant.lastname'] + businessname = applicant['foirequestapplicant.businessname'] + dobraw = applicant['foirequestapplicant.dob'] + dob = parse(dobraw).strftime(self.__genericdateformat()) if dobraw is not None else '' + alsoknownas = applicant['foirequestapplicant.alsoknownas'] + requestortypeid = applicant['requestortype.requestortypeid'] + if requestortypeid == 1: + baserequestinfo.update(self.__prepareapplicant(firstname, middlename, lastname, businessname)) + additionalpersonalinfo.update(self.__prepareadditionalpersonalinfo(requestortypeid, firstname, middlename, lastname, dob, alsoknownas)) + + baserequestdetails, additionalpersonalinfodetails = self.preparepersonalattributes(foirequestid, request['version']) + baserequestinfo.update(baserequestdetails) + additionalpersonalinfo.update(additionalpersonalinfodetails) + + baserequestinfo['additionalPersonalInfo'] = additionalpersonalinfo + 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 + + def preparepersonalattributes(self, foirequestid, version): + personalattributes = FOIRequestPersonalAttribute.getrequestpersonalattributes(foirequestid, version) + baserequestdetails = {} + additionalpersonalinfodetails = {} + + for personalattribute in personalattributes: + attribute, location = self.__preparepersonalattribute(personalattribute) + if location == "main": + baserequestdetails.update(attribute) + elif location =="additionalPersonalInfo": + additionalpersonalinfodetails.update(attribute) + return baserequestdetails, additionalpersonalinfodetails + + def getrequestdetailsforministry(self,foirequestid,foiministryrequestid, authmembershipgroups): + request = FOIRequest.getrequest(foirequestid) + requestministry = FOIMinistryRequest.getrequestbyministryrequestid(foiministryrequestid) + requestministrydivisions = FOIMinistryRequestDivision.getdivisions(foiministryrequestid,requestministry['version']) + ministryrestrictrequestdetails = FOIRestrictedMinistryRequest.getrestricteddetails(foiministryrequestid,type='ministry') + baserequestinfo = {} + if requestministry["assignedministrygroup"] in authmembershipgroups: + baserequestinfo = self.__preparebaseinfo(request,foiministryrequestid,requestministry,requestministrydivisions) + + if request['requesttype'] == 'personal': + requestapplicants = FOIRequestApplicantMapping.getrequestapplicants(foirequestid,request['version']) + additionalpersonalinfo ={} + for applicant in requestapplicants: + firstname = applicant['foirequestapplicant.firstname'] + middlename = applicant['foirequestapplicant.middlename'] + lastname = applicant['foirequestapplicant.lastname'] + dobraw = applicant['foirequestapplicant.dob'] + dob = parse(dobraw).strftime(self.__genericdateformat()) if dobraw is not None else '' + requestortypeid = applicant['requestortype.requestortypeid'] + if requestortypeid == 1: + baserequestinfo.update(self.__prepareapplicant(firstname, middlename, lastname)) + additionalpersonalinfo.update(self.__prepareadditionalpersonalinfo(requestortypeid, firstname, middlename, lastname, dob)) + baserequestdetails, additionalpersonalinfodetails = self.preparepersonalattributes(foirequestid, request['version']) + baserequestinfo.update(baserequestdetails) + additionalpersonalinfo.update(additionalpersonalinfodetails) + baserequestinfo['additionalPersonalInfo'] = additionalpersonalinfo + baserequestinfo['ministryrestricteddetails'] = ministryrestrictrequestdetails + return baserequestinfo + + def getrequestdetails(self,foirequestid, foiministryrequestid): + requestdetails = self.getrequest(foirequestid, foiministryrequestid) + approvedcfrfee = cfrfeeservice().getapprovedcfrfee(foiministryrequestid) + cfrfee = cfrfeeservice().getcfrfee(foiministryrequestid) + payment = paymentservice().getpayment(foirequestid, foiministryrequestid) + if approvedcfrfee is not None and approvedcfrfee != {}: + requestdetails['cfrfee'] = approvedcfrfee + _totaldue = float(approvedcfrfee['feedata']['actualtotaldue']) if float(approvedcfrfee['feedata']['actualtotaldue']) > 0 else float(approvedcfrfee['feedata']['estimatedtotaldue']) + _balancedue = _totaldue - (float(cfrfee['feedata']['amountpaid']) + float(approvedcfrfee['feedata']['feewaiveramount'])) + requestdetails['cfrfee']['feedata']['amountpaid'] = cfrfee['feedata']['amountpaid'] + requestdetails['cfrfee']['feedata']["balanceDue"] = '{:.2f}'.format(_balancedue) + if approvedcfrfee['feedata']['actualtotaldue']: + requestdetails['cfrfee']['feedata']["totalamountdue"] = '{:.2f}'.format(requestdetails['cfrfee']['feedata']["actualtotaldue"]) + else: + requestdetails['cfrfee']['feedata']["totalamountdue"] = '{:.2f}'.format(requestdetails['cfrfee']['feedata']["estimatedtotaldue"]) + + if payment is not None and payment != {}: + paidamount = float(payment['paidamount']) if payment['paidamount'] != None else 0 + requestdetails['cfrfee']['feedata']['paidamount'] = '{:.2f}'.format(paidamount) + # depositpaid field is only accurate and used for outstanding email and receipts + requestdetails['cfrfee']['feedata']['depositpaid'] = '{:.2f}'.format(float(cfrfee['feedata']['amountpaid']) - paidamount) + requestdetails['cfrfee']['feedata']['paymenturl'] = payment['paymenturl'] + requestdetails['cfrfee']['feedata']['paymentdate'] = payment['created_at'][:10] + return requestdetails + + def __preparebaseinfo(self,request,foiministryrequestid,requestministry,requestministrydivisions): + _receiveddate = parse(request['receiveddate']) + axissyncdatenoneorempty = self.__noneorempty(requestministry["axissyncdate"]) + linkedministryrequests= [] + if "linkedrequests" in requestministry and requestministry["linkedrequests"] is not None: + linkedministryrequests = self.__assignministrynames(requestministry["linkedrequests"]) + baserequestinfo = { + 'id': request['foirequestid'], + 'requestType': request['requesttype'], + 'receivedDate': _receiveddate.strftime('%Y %b, %d'), + 'receivedDateUF': parse(request['receiveddate']).strftime('%Y-%m-%d %H:%M:%S.%f'), + 'deliverymodeid':request['deliverymode.deliverymodeid'], + 'deliveryMode':request['deliverymode.name'], + '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"], + 'axisSyncDate': parse(requestministry["axissyncdate"]).strftime('%Y-%m-%d %H:%M:%S.%f') if axissyncdatenoneorempty == False else None, + 'axispagecount': int(requestministry["axispagecount"]) if requestministry["axispagecount"] is not None else 0 , + 'recordspagecount': int(requestministry["recordspagecount"]) if requestministry["recordspagecount"] is not None else 0 , + 'description': requestministry['description'], + 'fromDate': parse(requestministry['recordsearchfromdate']).strftime(self.__genericdateformat()) if requestministry['recordsearchfromdate'] is not None else '', + 'toDate': parse(requestministry['recordsearchtodate']).strftime(self.__genericdateformat()) if requestministry['recordsearchtodate'] is not None else '', + 'currentState':requestministry['requeststatus.name'], + 'requeststatuslabel':requestministry['requeststatuslabel'], + 'requestProcessStart': parse(requestministry['startdate']).strftime(self.__genericdateformat()) if requestministry['startdate'] is not None else '', + 'dueDate':parse(requestministry['duedate']).strftime(self.__genericdateformat()), + 'originalDueDate': parse(requestministry['originalldd']).strftime(self.__genericdateformat()) if requestministry['originalldd'] is not None else parse(requestministry['duedate']).strftime(self.__genericdateformat()), + 'programareaid':requestministry['programarea.programareaid'], + 'bcgovcode':requestministry['programarea.bcgovcode'], + 'category':request['applicantcategory.name'], + 'categoryid':request['applicantcategory.applicantcategoryid'], + 'assignedministrygroup':requestministry["assignedministrygroup"], + 'assignedministryperson':requestministry["assignedministryperson"], + 'selectedMinistries':[{'code':requestministry['programarea.bcgovcode'],'id':requestministry['foiministryrequestid'],'name':requestministry['programarea.name'],'selected':'true'}], + 'divisions': self.getdivisions(requestministrydivisions), + 'isoipcreview': requestministry['isoipcreview'] if (requestministry['isoipcreview'] not in (None, '') and requestministry['isoipcreview'] in (True, False)) else False, + 'isreopened': self.hasreopened(foiministryrequestid), + 'oipcdetails': self.getoipcdetails(foiministryrequestid, requestministry['version']), + 'onholdTransitionDate': self.getonholdtransition(foiministryrequestid), + 'stateTransition': FOIMinistryRequest.getstatesummary(foiministryrequestid), + 'assignedToFirstName': requestministry["assignee.firstname"] if requestministry["assignedto"] != None else None, + 'assignedToLastName': requestministry["assignee.lastname"] if requestministry["assignedto"] != None else None, + 'assignedministrypersonFirstName': requestministry["ministryassignee.firstname"] if requestministry["assignedministryperson"] != None else None, + 'assignedministrypersonLastName': requestministry["ministryassignee.lastname"] if requestministry["assignedministryperson"] != None else None, + '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, + 'identityVerified':requestministry['identityverified'], + 'estimatedpagecount':requestministry['estimatedpagecount'], + 'estimatedtaggedpagecount':requestministry['estimatedtaggedpagecount'], + + } + if requestministry['cfrduedate'] is not None: + baserequestinfo.update({'cfrDueDate':parse(requestministry['cfrduedate']).strftime(self.__genericdateformat())}) + return baserequestinfo + + def __assignministrynames(self, linkedrequests): + areas = programareaservice().getprogramareas() + if linkedrequests is not None: + for entry in linkedrequests: + area = next((a for a in areas if a["programareaid"] == list(entry.values())[0]), None) + if (area is not None): + entry = area["name"] + return linkedrequests + + def getdivisions(self, ministrydivisions): + divisions = [] + if ministrydivisions is not None: + for ministrydivision in ministrydivisions: + division = { + "foiministrydivisionid": ministrydivision["foiministrydivisionid"], + "divisionid": ministrydivision["division.divisionid"], + "divisionname": ministrydivision["division.name"], + "stageid": ministrydivision["stage.stageid"], + "stagename": ministrydivision["stage.name"], + "divisionDueDate": parse(ministrydivision['divisionduedate']).strftime(self.__genericdateformat()) if ministrydivision['divisionduedate'] is not None else None, + "eApproval": ministrydivision["eapproval"], + "divisionReceivedDate": parse(ministrydivision['divisionreceiveddate']).strftime(self.__genericdateformat()) if ministrydivision['divisionreceiveddate'] is not None else None, + } + divisions.append(division) + return divisions + + def getoipcdetails(self, ministryrequestid, ministryrequestversion): + oipcdetails = [] + _oipclist = FOIRequestOIPC.getoipc(ministryrequestid, ministryrequestversion) + inquiryoutcomes = oipcservice().getinquiryoutcomes() + if _oipclist is not None: + for entry in _oipclist: + oipc = { + "oipcid": entry["oipcid"], + "oipcno": entry["oipcno"], + "reviewtypeid": entry["reviewtypeid"], + "reviewetype": entry["reviewtype.name"], + "reasonid": entry["reasonid"], + "reason": entry["reason.name"], + "statusid": entry["statusid"], + "status":entry["status.name"], + "outcomeid": entry["outcomeid"], + "outcome": entry["outcome.name"] if entry["outcomeid"] not in (None, '') else None, + "investigator": entry["investigator"], + "isinquiry": entry["isinquiry"], + "isjudicialreview": entry["isjudicialreview"], + "issubsequentappeal": entry["issubsequentappeal"], + "inquiryattributes": self.formatinquiryattribute(entry["inquiryattributes"], inquiryoutcomes), + "createdby": entry["createdby"], + "receiveddate" : parse(entry["receiveddate"]).strftime('%b, %d %Y') if entry["receiveddate"] is not None else '', + "closeddate": parse(entry["closeddate"]).strftime('%b, %d %Y') if entry["closeddate"] is not None else '', + "created_at": parse(entry["created_at"]).strftime(self.__genericdateformat()) + } + oipcdetails.append(oipc) + return oipcdetails + + def formatinquiryattribute(self, inquiryattribute, inquiryoutcomes): + if inquiryattribute not in (None, {}): + for outcome in inquiryoutcomes: + if inquiryattribute["inquiryoutcome"] == outcome["inquiryoutcomeid"]: + inquiryattribute["inquiryoutcomename"] = outcome["name"] + return inquiryattribute + + + def getonholdtransition(self, foiministryrequestid): + onholddate = None + transitions = FOIMinistryRequest.getrequeststatusById(foiministryrequestid) + for entry in transitions: + if entry['requeststatuslabel'] == StateName.onhold.name: + onholddate = datetimehandler().convert_to_pst(entry['created_at'],'%Y-%m-%d') + else: + if onholddate is not None: + break + return onholddate + + def getministryrequest(self, foiministryrequestid): + requestministry = FOIMinistryRequest.getrequestbyministryrequestid(foiministryrequestid) + return requestministry + + def __genericdateformat(self): + return '%Y-%m-%d' + + def hasreopened(self, requestid): + states = FOIMinistryRequest.getstatesummary(requestid) + if len(states) > 0: + current_state = states[0] + if current_state != "Closed" and any(state['status'] == "Closed" for state in states): + return True + return False + + def __prepareapplicant(self,firstname= None, middlename= None, lastname= None, businessname= None): + return { + 'firstName': firstname, + 'middleName': middlename, + 'lastName': lastname, + 'businessName': businessname, + } + + def __prepareadditionalpersonalinfo(self, requestortypeid, firstname= None, middlename= None, lastname= None, dob= None, alsoknownas= None): + if requestortypeid == 1: + return { + 'birthDate' : dob, + 'alsoKnownAs': alsoknownas + } + elif requestortypeid == 2: + return { + 'anotherFirstName': firstname, + 'anotherMiddleName': middlename, + 'anotherLastName': lastname, + 'anotherBirthDate' : dob, + 'anotherAlsoKnownAs': alsoknownas + } + elif requestortypeid == 3: + return { + 'childFirstName': firstname, + 'childMiddleName': middlename, + 'childLastName': lastname, + 'childAlsoKnownAs': alsoknownas, + 'childBirthDate': dob, + } + + def __preparepersonalattribute(self, personalattribute): + if personalattribute['personalattributeid'] == 1: + return {'publicServiceEmployeeNumber': personalattribute['attributevalue']}, "main" + + elif personalattribute['personalattributeid'] == 2 : + return {'correctionalServiceNumber': personalattribute['attributevalue']}, "main" + + elif personalattribute['personalattributeid'] == 3 : + return {'personalHealthNumber': personalattribute['attributevalue']}, "additionalPersonalInfo" + + elif personalattribute['personalattributeid'] == 4: + return {'adoptiveMotherFirstName': personalattribute['attributevalue']}, "main" + + elif personalattribute['personalattributeid'] == 5: + return {'adoptiveMotherLastName': personalattribute['attributevalue']}, "main" + + elif personalattribute['personalattributeid'] == 6: + return {'adoptiveFatherFirstName': personalattribute['attributevalue']}, "main" + + elif personalattribute['personalattributeid'] == 7: + return {'adoptiveFatherLastName': personalattribute['attributevalue']}, "main" + + def __noneorempty(self, variable): + return True if not variable else False diff --git a/historical-search-api/request_api/services/foirequest/requestserviceministrybuilder.py b/historical-search-api/request_api/services/foirequest/requestserviceministrybuilder.py new file mode 100644 index 000000000..9336eb2d2 --- /dev/null +++ b/historical-search-api/request_api/services/foirequest/requestserviceministrybuilder.py @@ -0,0 +1,354 @@ + +from re import T +from request_api.models.FOIRequests import FOIRequest +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from request_api.models.FOIRequestContactInformation import FOIRequestContactInformation +from request_api.models.FOIRequestPersonalAttributes import FOIRequestPersonalAttribute +from request_api.models.FOIRequestApplicantMappings import FOIRequestApplicantMapping +from request_api.models.FOIMinistryRequestDivisions import FOIMinistryRequestDivision +from request_api.models.FOIMinistryRequestDocuments import FOIMinistryRequestDocument +from request_api.models.FOIRequestExtensions import FOIRequestExtension +from request_api.models.FOIRequestExtensionDocumentMappings import FOIRequestExtensionDocumentMapping +from request_api.models.FOIAssignees import FOIAssignee +from request_api.models.FOIMinistryRequestSubjectCodes import FOIMinistryRequestSubjectCode +from request_api.models.FOIRequestStatus import FOIRequestStatus +from request_api.models.FOIRequestOIPC import FOIRequestOIPC +from request_api.services.foirequest.requestserviceconfigurator import requestserviceconfigurator +from datetime import datetime as datetime2 + +class requestserviceministrybuilder(requestserviceconfigurator): + """ This class consolidates the helper functions for creating new foi request based on ministry actions. + """ + + def createfoirequestfromobject(self, foiobject, userid): + foirequest = FOIRequest() + foirequest.foirawrequestid = foiobject['foirawrequestid'] + foirequest.foirequestid = foiobject['foirequestid'] + foirequest.version = foiobject["version"] + 1 + foirequest.initialdescription = foiobject['initialdescription'] + foirequest.initialrecordsearchfromdate = foiobject['initialrecordsearchfromdate'] if 'initialrecordsearchfromdate' in foiobject else None + foirequest.initialrecordsearchtodate = foiobject['initialrecordsearchfromdate'] if 'initialrecordsearchfromdate' in foiobject else None + foirequest.receiveddate = foiobject['receiveddate'] if 'receiveddate' in foiobject else None + foirequest.requesttype = foiobject['requesttype'] + foirequest.wfinstanceid = foiobject['wfinstanceid'] + foirequest.applicantcategoryid = foiobject["applicantcategory.applicantcategoryid"] + foirequest.deliverymodeid = foiobject["deliverymode.deliverymodeid"] + foirequest.receivedmodeid = foiobject["receivedmode.receivedmodeid"] + foirequest.createdby = userid + return foirequest + + def createfoiministryrequestfromobject(self, ministryschema, requestschema, userid, usertype = None): + requestdict = self.createfoiministryrequestfromobject1(ministryschema, requestschema) + foiministryrequest = FOIMinistryRequest() + foiministryrequest.foiministryrequestid = ministryschema["foiministryrequestid"] + foiministryrequest.version = ministryschema["version"] + 1 + foiministryrequest.foirequest_id = ministryschema["foirequest_id"] + foiministryrequest.foirequestversion_id = ministryschema["foirequestversion_id"] + foiministryrequest.description = ministryschema["description"] + foiministryrequest.recordsearchfromdate = requestdict['recordsearchfromdate'] + foiministryrequest.recordsearchtodate = requestdict['recordsearchtodate'] + foiministryrequest.filenumber = ministryschema["filenumber"] + foiministryrequest.axissyncdate = ministryschema["axissyncdate"] + foiministryrequest.axisrequestid = ministryschema["axisrequestid"] + foiministryrequest.linkedrequests = ministryschema['linkedrequests'] + foiministryrequest.identityverified = ministryschema['identityverified'] + foiministryrequest.originalldd = ministryschema['originalldd'] + foiministryrequest.axispagecount = ministryschema["axispagecount"] + foiministryrequest.recordspagecount = ministryschema["recordspagecount"] + foiministryrequest.cfrduedate = requestdict['cfrduedate'] + foiministryrequest.startdate = requestdict['startdate'] + foiministryrequest.duedate = requestdict['duedate'] + foiministryrequest.assignedministrygroup = requestdict['assignedministrygroup'] + if 'assignedministryperson' in requestschema and requestschema['assignedministryperson'] not in (None,''): + foiministryrequest.assignedministryperson = requestschema['assignedministryperson'] + firstname = requestschema['assignedministrypersonFirstName'] + middlename = None + lastname = requestschema['assignedministrypersonLastName'] + self.createfoiassigneefromobject(requestschema['assignedministryperson'], firstname, middlename, lastname) + else: + foiministryrequest.assignedministryperson = ministryschema["assignedministryperson"] + + foiministryrequest.assignedgroup = requestschema['assignedgroup'] if 'assignedgroup' in requestschema and requestschema['assignedgroup'] not in (None,'') else requestdict['assignedgroup'] + if 'assignedto' in requestschema and requestschema['assignedto'] not in (None,''): + foiministryrequest.assignedto = requestschema['assignedto'] + fn = requestschema['assignedToFirstName'] if requestschema['assignedto'] != None else None + mn = None + ln = requestschema['assignedToLastName'] if requestschema['assignedto'] != None else None + self.createfoiassigneefromobject(requestschema['assignedto'], fn, mn, ln) + 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 = self.__getrequeststatusid(requestdict['requeststatuslabel']) + foiministryrequest.requeststatuslabel = requestdict['requeststatuslabel'] + foiministryrequest.programareaid = requestdict['programareaid'] + foiministryrequest.createdby = userid + + foiministryrequest.divisions = self.__createministrydivisions(requestschema, ministryschema["foiministryrequestid"] ,ministryschema["version"], userid) + foiministryrequest.documents = self.createfoirequestdocuments(requestschema,ministryschema["foiministryrequestid"] ,ministryschema["version"] +1 , userid) + foiministryrequest.extensions = self.createfoirequestextensions(ministryschema["foiministryrequestid"] ,ministryschema["version"] +1 , userid) + foiministryrequest.subjectcode = self.__createministrysubjectcode(requestschema, ministryschema["foiministryrequestid"], ministryschema["version"], userid) + foiministryrequest.closedate = requestdict['closedate'] + foiministryrequest.closereasonid = requestdict['closereasonid'] + foiministryrequest.isoipcreview = ministryschema["isoipcreview"] + foiministryrequest.oipcreviews = self.createfoirequestoipcs(foiministryrequest.isoipcreview, ministryschema["foirequest_id"], ministryschema["version"]) + return foiministryrequest + + def __getrequeststatusid(self, requeststatuslabel): + state = FOIRequestStatus.getrequeststatusbylabel( + requeststatuslabel + ) + stateid = ( + state.get("requeststatusid") + if isinstance(state, dict) and state.get("requeststatusid") not in (None, "") + else "" + ) + return stateid + def __createministrydivisions(self, requestschema, foiministryrequestid, foiministryrequestversion, userid): + if 'divisions' in requestschema: + return self.createfoirequestdivision(requestschema,foiministryrequestid ,foiministryrequestversion + 1, userid) + else: + divisions = FOIMinistryRequestDivision().getdivisions(foiministryrequestid, foiministryrequestversion) + return self.createfoirequestdivisionfromobject(divisions,foiministryrequestid, foiministryrequestversion + 1, userid) + + def __createministrysubjectcode(self, requestschema, foiministryrequestid, foiministryrequestversion, userid): + subjectcode = FOIMinistryRequestSubjectCode().getministrysubjectcode(foiministryrequestid, foiministryrequestversion) + if subjectcode: + return self.createfoirequestsubjectcodefromobject(subjectcode, foiministryrequestid, foiministryrequestversion + 1, userid) + else: + return [] + + def createfoiministryrequestfromobject1(self, ministryschema, requestschema): + return { + 'recordsearchfromdate': ministryschema['recordsearchfromdate'] if 'recordsearchfromdate' in ministryschema else None, + 'recordsearchtodate': ministryschema['recordsearchtodate'] if 'recordsearchtodate' in ministryschema else None, + 'cfrduedate': ministryschema['cfrduedate'] if 'cfrduedate' in ministryschema else None, + 'startdate': ministryschema['startdate'] if 'startdate' in ministryschema else None, + 'duedate': requestschema['duedate'] if 'duedate' in requestschema else ministryschema["duedate"], #and isextension':= True + 'assignedministrygroup': requestschema['assignedministrygroup'] if 'assignedministrygroup' in requestschema else ministryschema["assignedministrygroup"], + 'assignedgroup': requestschema['assignedgroup'] if 'assignedgroup' in requestschema else ministryschema["assignedgroup"], + 'requeststatuslabel': requestschema['requeststatuslabel'] if 'requeststatuslabel' in requestschema else ministryschema["requeststatuslabel"], + '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': requestschema['linkedrequests'] if 'linkedrequests' in requestschema else None, + 'ministrysignoffapproval': requestschema['ministrysignoffapproval'] if 'ministrysignoffapproval' in requestschema else None, + } + + def createfoirequestdocuments(self,requestschema, ministryrequestid, activeversion, userid): + # documents = FOIMinistryRequestDocument().getdocuments(ministryrequestid, activeversion-1) + # make isactive = False for all ministryRequestId and prev ministryVersion + # FOIMinistryRequestDocument.deActivateministrydocumentsversionbyministry(ministryrequestid, activeversion, userid) + + # existingdocuments = self.createfoirequestdocumentfromobject(documents,ministryrequestid ,activeversion, userid) + # documentarr = existingdocuments + if 'documents' in requestschema: + return self.createfoirequestdocument(requestschema,ministryrequestid ,activeversion, userid) + # documentarr = newdocuments #+ existingdocuments + return [] + + def createfoirequestsubjectcode(self,requestschema, ministryrequestid, activeversion, userid): + return self.createsubjectcode(requestschema,ministryrequestid ,activeversion, userid) + + def createfoirequestextensions(self, ministryrequestid, activeversion, userid): + extensions = FOIRequestExtension().getextensions(ministryrequestid, activeversion-1) + + existingextensions = self.createfoirequestextensionfromobject(extensions,ministryrequestid ,activeversion, userid) + if existingextensions is not None: + return existingextensions + return [] + + def createfoirequestappplicantfromobject(self, requestapplicants, requestid, version, userid): + requestapplicantarr = [] + for requestapplicant in requestapplicants: + applicantmapping = FOIRequestApplicantMapping() + applicantmapping.foirequest_id = requestid + applicantmapping.foirequestversion_id = version + applicantmapping.foirequestapplicantid = requestapplicant["foirequestapplicant.foirequestapplicantid"] + applicantmapping.requestortypeid = requestapplicant["requestortype.requestortypeid"] + applicantmapping.createdby = userid + requestapplicantarr.append(applicantmapping) + return requestapplicantarr + + def createfoirequestpersonalattributefromobject(self,personalattributes, requestid, version, userid): + personalattributesarr = [] + for personalattribute in personalattributes: + attribute = FOIRequestPersonalAttribute() + attribute.personalattributeid = personalattribute["personalattributeid"] + attribute.attributevalue = personalattribute["attributevalue"] + attribute.foirequest_id = requestid + attribute.foirequestversion_id = version + attribute.createdby = userid + personalattributesarr.append(attribute) + return personalattributesarr + + def createfoirequestcontactfromobject(self, requestcontacts, requestid, version, userid): + requestcontactarr = [] + for requestcontact in requestcontacts: + contactinfo = FOIRequestContactInformation() + contactinfo.contacttypeid = requestcontact["contacttype.contacttypeid"] + contactinfo.foirequest_id = requestid + contactinfo.foirequestversion_id = version + contactinfo.contactinformation = requestcontact["contactinformation"] + contactinfo.dataformat = requestcontact["dataformat"] + contactinfo.createdby = userid + requestcontactarr.append(contactinfo) + return requestcontactarr + + def createfoirequestdivisionfromobject(self, divisions, requestid, version, userid): + divisionarr = [] + for division in divisions: + ministrydivision = FOIMinistryRequestDivision() + ministrydivision.divisionid = division["division.divisionid"] + ministrydivision.stageid = division["stage.stageid"] + ministrydivision.divisionduedate = division["divisionduedate"] + ministrydivision.eapproval = division["eapproval"] + ministrydivision.divisionreceiveddate = division["divisionreceiveddate"] + ministrydivision.foiministryrequest_id = requestid + ministrydivision.foiministryrequestversion_id = version + ministrydivision.createdby = userid + divisionarr.append(ministrydivision) + return divisionarr + + def createfoirequestdocumentfromobject(self, documents, requestid, activeversion, userid): + documentarr = [] + for document in documents: + ministrydocument = FOIMinistryRequestDocument() + ministrydocument.foiministrydocumentid = document["foiministrydocumentid"] + ministrydocument.documentpath = document["documentpath"] + if 'filename' in document: + ministrydocument.filename = document["filename"] + if 'category' in document: + ministrydocument.category = document['category'] + ministrydocument.version = document['version'] + 1 + ministrydocument.foiministryrequest_id = requestid + ministrydocument.foiministryrequestversion_id = activeversion + ministrydocument.created_at = document["created_at"] + ministrydocument.createdby = document["createdby"] + ministrydocument.updated_at = datetime2.now().isoformat() + ministrydocument.updatedby = userid + documentarr.append(ministrydocument) + return documentarr + + def createfoirequestextensionfromobject(self, extensions, requestid, activeversion, userid): + extensionarr = [] + for extension in extensions: + requestextension = FOIRequestExtension() + requestextension.foirequestextensionid = extension["foirequestextensionid"] + requestextension.extensionreasonid = extension["extensionreasonid"] + requestextension.extensionstatusid = extension["extensionstatusid"] + if 'extendedduedays' in extension: + requestextension.extendedduedays = extension["extendedduedays"] + if 'extendedduedate' in extension: + requestextension.extendedduedate = extension["extendedduedate"] + if 'decisiondate' in extension: + requestextension.decisiondate = extension["decisiondate"] + if 'approvednoofdays' in extension: + requestextension.approvednoofdays = extension["approvednoofdays"] + requestextension.version = extension["version"] + 1 + requestextension.foiministryrequest_id = requestid + requestextension.foiministryrequestversion_id = activeversion + requestextension.created_at = extension["created_at"] + requestextension.createdby = extension["createdby"] + requestextension.updated_at = datetime2.now().isoformat() + requestextension.updatedby = userid + documets = self.createextensiondocumentfromobject(FOIRequestExtensionDocumentMapping().getextensiondocuments(extension["foirequestextensionid"], extension["version"]), extension["version"] + 1, userid) + requestextension.extensiondocuments = documets + extensionarr.append(requestextension) + return extensionarr + + def createextensiondocumentfromobject(self, extensiondocuments, activeversion, userid): + extdocumentarr = [] + for extensiondocument in extensiondocuments: + extensiondocumentmapping = FOIRequestExtensionDocumentMapping() + extensiondocumentmapping.foirequestextensionid = extensiondocument["foirequestextensionid"] + extensiondocumentmapping.extensionversion = activeversion + extensiondocumentmapping.foiministrydocumentid = extensiondocument["foiministrydocumentid"] + extensiondocumentmapping.createdby = userid + extdocumentarr.append(extensiondocumentmapping) + return extdocumentarr + + def createfoirequestdocument(self, requestschema, requestid, version, userid): + documentarr = [] + if 'documents' in requestschema: + for document in requestschema['documents']: + ministrydocument = FOIMinistryRequestDocument() + ministrydocument.documentpath = document["documentpath"] + ministrydocument.filename = document["filename"] + if 'category' in document: + ministrydocument.category = document['category'] + ministrydocument.version = 1 + ministrydocument.foiministryrequest_id = requestid + ministrydocument.foiministryrequestversion_id = version + ministrydocument.createdby = userid + ministrydocument.created_at = datetime2.now().isoformat() + documentarr.append(ministrydocument) + return documentarr + + def createsubjectcode(self, requestschema, requestid, version, userid): + subjectcodearr = [] + ministrysubjectcode = FOIMinistryRequestSubjectCode() + ministrysubjectcode.subjectcodeid = requestserviceconfigurator().getvalueof("subjectCode",requestschema['subjectCode']) + ministrysubjectcode.foiministryrequestid = requestid + ministrysubjectcode.foiministryrequestversion = version + ministrysubjectcode.createdby = userid + ministrysubjectcode.created_at = datetime2.now().isoformat() + subjectcodearr.append(ministrysubjectcode) + return subjectcodearr + + def createfoirequestsubjectcodefromobject(self, subjectcode, requestid, activeversion, userid): + subjectcodearr = [] + ministrysubjectcode = FOIMinistryRequestSubjectCode() + ministrysubjectcode.subjectcodeid = subjectcode['subjectcodeid'] + ministrysubjectcode.foiministryrequestid = requestid + ministrysubjectcode.foiministryrequestversion = activeversion + ministrysubjectcode.createdby = userid + ministrysubjectcode.created_at = datetime2.now().isoformat() + subjectcodearr.append(ministrysubjectcode) + return subjectcodearr + + def createfoirequestdivision(self, requestschema, requestid, version, userid): + divisionarr = [] + if 'divisions' in requestschema: + for division in requestschema['divisions']: + ministrydivision = FOIMinistryRequestDivision() + ministrydivision.divisionid = division["divisionid"] + ministrydivision.stageid = division["stageid"] + ministrydivision.divisionduedate = division["divisionDueDate"] if "divisionDueDate" in division and division["divisionDueDate"] != '' else None + ministrydivision.eapproval = division["eApproval"] if "eApproval" in division else None + ministrydivision.divisionreceiveddate = division["divisionReceivedDate"] if "divisionReceivedDate" in division and division["divisionReceivedDate"] != '' else None + ministrydivision.foiministryrequest_id = requestid + ministrydivision.foiministryrequestversion_id = version + ministrydivision.createdby = userid + divisionarr.append(ministrydivision) + return divisionarr + + def createfoiassigneefromobject(self, username, firstname, middlename, lastname): + return FOIAssignee.saveassignee(username, firstname, middlename, lastname) + + def createfoirequestoipcs(self, isoipcreview, requestid, version): + current_oipcs = FOIRequestOIPC.getoipc(requestid, version) + if (isoipcreview == True): + updated_oipcs = [] + for oipc in current_oipcs: + oipcreview = FOIRequestOIPC() + oipcreview.foiministryrequest_id = requestid + oipcreview.foiministryrequestversion_id = version + 1 + oipcreview.oipcno = oipc["oipcno"] + oipcreview.reviewtypeid = oipc["reviewtypeid"] + oipcreview.reasonid = oipc["reasonid"] + oipcreview.statusid = oipc["statusid"] + oipcreview.outcomeid = oipc["outcomeid"] + oipcreview.investigator = oipc["investigator"] + oipcreview.isinquiry = oipc["isinquiry"] + oipcreview.isjudicialreview = oipc["isjudicialreview"] + oipcreview.issubsequentappeal = oipc["issubsequentappeal"] + oipcreview.issubsequentappeal = oipc["issubsequentappeal"] + oipcreview.receiveddate = oipc["receiveddate"] + oipcreview.closeddate = oipc["closeddate"] + oipcreview.inquiryattributes = oipc["inquiryattributes"] + oipcreview.createdby=oipc["createdby"] + oipcreview.created_at= oipc["created_at"] + updated_oipcs.append(oipcreview) + return updated_oipcs + return [] diff --git a/historical-search-api/request_api/services/foirequest/requestserviceupdate.py b/historical-search-api/request_api/services/foirequest/requestserviceupdate.py new file mode 100644 index 000000000..a85ca38e9 --- /dev/null +++ b/historical-search-api/request_api/services/foirequest/requestserviceupdate.py @@ -0,0 +1,26 @@ + +from re import T +from request_api.models.FOIRequests import FOIRequest +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from request_api.models.FOIRequestStatus import FOIRequestStatus +from request_api.services.foirequest.requestservicebuilder import requestservicebuilder + +import json +class requestserviceupdate(requestservicebuilder): + """ This class consolidates update of FOI request for actors: iao and ministry. + """ + + def updaterequest(self,foirequestschema,foirequestid,userid): + if self.isNotBlankorNone(foirequestschema,"wfinstanceid","main") == True: + return FOIRequest.updateWFInstance(foirequestid, foirequestschema.get("wfinstanceid"), userid) + if foirequestschema.get("selectedMinistries") is not None: + allstatus = FOIRequestStatus().getrequeststatuses() + updatedministries = [] + for ministry in foirequestschema.get("selectedMinistries"): + for status in allstatus: + if ministry["status"] == status["name"]: + updatedministries.append({"filenumber" : ministry["filenumber"], "requeststatuslabel": status["statuslabel"]}) + return FOIRequest.updateStatus(foirequestid, updatedministries, userid) + + def updateministryrequestduedate(self, ministryrequestid, duedate, userid): + return FOIMinistryRequest().updateduedate(ministryrequestid, duedate, userid) \ No newline at end of file diff --git a/historical-search-api/request_api/services/hash_service.py b/historical-search-api/request_api/services/hash_service.py new file mode 100644 index 000000000..70f99d3f7 --- /dev/null +++ b/historical-search-api/request_api/services/hash_service.py @@ -0,0 +1,49 @@ +# Copyright © 2019 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Service for hashing.""" +import hashlib + +from flask import current_app +from typing import Dict +from urllib.parse import parse_qsl + + +class HashService: + """Hash Service class.""" + + @staticmethod + def encode(param: str) -> str: + """Return a hashed string using the static salt from config.""" + current_app.logger.debug(f'encoding for string {param}') + api_key = current_app.config.get('PAYBC_API_KEY') + return hashlib.md5(f'{param}{api_key}'.encode()).hexdigest() # NOSONAR as PayBC needs MD5 + + @staticmethod + def is_valid_checksum(param: str, hash_val: str) -> bool: + """Validate if the checksum matches.""" + api_key = current_app.config.get('PAYBC_API_KEY') + return hashlib.md5(f'{param}{api_key}'.encode()).hexdigest() == hash_val # NOSONAR as PayBC needs MD5 + + @staticmethod + def parse_url_params(url_params: str) -> Dict: + """Parse URL params and return dict of parsed url params.""" + parsed_url: dict = {} + if url_params is not None: + if url_params.startswith('?'): + url_params = url_params[1:] + parsed_url = dict(parse_qsl(url_params)) + + return parsed_url diff --git a/historical-search-api/request_api/services/historicalrequestservice.py b/historical-search-api/request_api/services/historicalrequestservice.py new file mode 100644 index 000000000..9b8de61a0 --- /dev/null +++ b/historical-search-api/request_api/services/historicalrequestservice.py @@ -0,0 +1,14 @@ + +from os import stat +from request_api.models.FOIAssignees import FOIAssignee + +class historicalrequestservice: + """ FOI Assignee management service + + This service class interacts with Keycloak and returns eligible groups and members based on the state of application. + + """ + + def gethistoricalrequest(self, requestid): + + diff --git a/historical-search-api/request_api/services/notifications/notificationconfig.py b/historical-search-api/request_api/services/notifications/notificationconfig.py new file mode 100644 index 000000000..b6b771206 --- /dev/null +++ b/historical-search-api/request_api/services/notifications/notificationconfig.py @@ -0,0 +1,69 @@ + +from os import stat +from re import VERBOSE +import json +import os +from request_api.models.NotificationTypes import NotificationType +from request_api.models.NotificationUserTypes import NotificationUserType +notificationuserfile = open('common/notificationusertypes.json', encoding="utf8") +notificationusertypes_cache = json.load(notificationuserfile) + +notificationfile = open('common/notificationtypes.json', encoding="utf8") +notificationtypes_cache = json.load(notificationfile) + +class notificationconfig: + """ Notfication config + + """ + + # This method is used to get the notification user type label + # It first tries to get the notification user type label from the cache + # If it is not found in the cache, it fetches it from the DB + def getnotificationtypelabel(self, notificationtype): + notificationtype_format = notificationtype.replace(" ", "").lower() + if notificationtype_format in notificationtypes_cache: + return notificationtypes_cache[notificationtype_format]['notificationtypelabel'] + else: + print("Notification type not found in json. Fetching from DB", notificationtype) + id = NotificationType().getnotificationtypeid(notificationtype) + if id is not None: + return id['notificationtypelabel'] + return None + + def getnotificationtypeid(self, notificationtype): + id = NotificationType().getnotificationtypeid(notificationtype) + if id is not None: + return id['notificationtypeid'] + return None + + # This method is used to get the notification user type label + # It first tries to get the notification user type label from the cache + # If it is not found in the cache, it fetches it from the DB + def getnotificationusertypelabel(self, notificationusertype): + notificationusertype_format = notificationusertype.replace(" ", "").lower() + if notificationusertype_format in notificationusertypes_cache: + return notificationusertypes_cache[notificationusertype_format]['notificationusertypelabel'] + else: + print("Notification user type not found in json. Fetching from DB", notificationusertype) + id = NotificationUserType().getnotificationusertypesid(notificationusertype) + if id is not None: + return id['notificationusertypelabel'] + return None + + def getnotificationusertypeid(self, notificationusertype): + id = NotificationUserType().getnotificationusertypesid(notificationusertype) + if id is not None: + return id['notificationusertypeid'] + return None + + def getnotificationdays(self): + if 'FOI_NOTIFICATION_DAYS' in os.environ and os.getenv('FOI_NOTIFICATION_DAYS') != '': + return os.getenv('FOI_NOTIFICATION_DAYS') + else: + return str(14) + + def getmutenotifications(self): + if 'MUTE_NOTIFICATION' in os.environ and os.getenv('MUTE_NOTIFICATION') != '': + return json.loads(os.getenv('MUTE_NOTIFICATION')) + else: + return {} \ No newline at end of file diff --git a/historical-search-api/request_api/services/notifications/notificationuser.py b/historical-search-api/request_api/services/notifications/notificationuser.py new file mode 100644 index 000000000..5f810690a --- /dev/null +++ b/historical-search-api/request_api/services/notifications/notificationuser.py @@ -0,0 +1,133 @@ + +from os import stat +from re import VERBOSE +import json +from request_api.services.watcherservice import watcherservice +from request_api.models.FOIRawRequestComments import FOIRawRequestComment +from request_api.models.FOIRequestComments import FOIRequestComment +from request_api.services.notifications.notificationconfig import notificationconfig +from request_api.services.external.keycloakadminservice import KeycloakAdminService + +class notificationuser: + """ notification user service + + """ + + def getnotificationusers(self, notificationtype, requesttype, userid, foirequest, requestjson=None): + notificationusers = [] + if 'User Assignment Removal' == notificationtype: + _users = self.__getassignees(foirequest, requesttype, notificationtype, requestjson) + elif 'Assignment' in notificationtype: + _users = self.__getassignees(foirequest, requesttype, notificationtype) + elif 'Reply User Comments' in notificationtype: + _users = self.__getcommentusers(foirequest, requestjson, requesttype) + elif 'Tagged User Comments' in notificationtype: + _users = self.__gettaggedusers(requestjson) + elif 'Group Members' in notificationtype: + _users = self.__getgroupmembers(foirequest["assignedministrygroup"]) + elif 'Watcher' in notificationtype: + _users = self.__getwatchers(notificationtype, foirequest, requesttype, requestjson) + else: + _users = self.__getassignees(foirequest, requesttype, notificationtype) + self.__getwatchers(notificationtype, foirequest, requesttype) + for user in _users: + if self.__isignorable(user, notificationusers, userid) == False and (("Tagged User Comments" not in notificationtype and self.__istaggeduser(user, requestjson, notificationtype) == False) or "Tagged User Comments" in notificationtype): + notificationusers.append(user) + return notificationusers + + def __isignorable(self, notificationuser, users, userid): + if notificationuser["userid"] == userid: + return True + else: + for user in users: + if notificationuser["userid"] == user["userid"]: + return True + return False + + def __istaggeduser(self, notificationuser, foicomment, notificationtype): + if "Comment" in notificationtype: + _users = self.__gettaggedusers(foicomment) + if _users is not None: + for user in _users: + if notificationuser["userid"] == user["userid"]: + return True + return False + + def __getwatchers(self, notificationtype, foirequest, requesttype, requestjson=None): + notificationusers = [] + if notificationtype == "Watcher": + notificationusers.append({"userid": requestjson['watchedby'], "usertype":notificationconfig().getnotificationusertypelabel("Watcher")}) + else: + if requesttype == "ministryrequest": + watchers = watcherservice().getallministryrequestwatchers(foirequest["foiministryrequestid"], self.__isministryonly(notificationtype)) + else: + watchers = watcherservice().getrawrequestwatchers(foirequest['requestid']) + for watcher in watchers: + notificationusers.append({"userid":watcher["watchedby"], "usertype":notificationconfig().getnotificationusertypelabel("Watcher")}) + return notificationusers + + def __getassignees(self, foirequest, requesttype, notificationtype, requestjson=None): + notificationusers = [] + notificationusertypelabel = notificationconfig().getnotificationusertypelabel("Assignee") + if notificationtype == 'User Assignment Removal': + notificationusers.append({"userid": requestjson['userid'], "usertype":notificationusertypelabel}) + else: + if requesttype == "ministryrequest" and foirequest["assignedministryperson"] is not None and (notificationtype == 'Ministry Assignment' or 'Assignment' not in notificationtype): + notificationusers.append({"userid":foirequest["assignedministryperson"], "usertype":notificationusertypelabel}) + if self.__isministryonly(notificationtype) == False and foirequest["assignedto"] is not None and foirequest["assignedto"] != '' and (notificationtype == 'IAO Assignment' or 'Assignment' not in notificationtype): + notificationusers.append({"userid":foirequest["assignedto"], "usertype":notificationusertypelabel}) + return notificationusers + + def __isministryonly(self, notificationtype): + return True if notificationtype == "Division Due Reminder" else False + + def __getcommentusers(self, foirequest, comment, requesttype): + _requestusers = self.getnotificationusers("General", requesttype, "nouser", foirequest) + commentusers = [] + commentusers.extend(_requestusers) + taggedusers = self.__gettaggedusers(comment) + if taggedusers is not None: + commentusers.extend(taggedusers) + if comment["parentcommentid"]: + _commentusers = self.__getrelatedusers(comment, requesttype) + for _commentuser in _commentusers: + commentusers.append({"userid":_commentuser["createdby"], "usertype":self.__getcommentusertype(_commentuser["createdby"],_requestusers)}) + _skiptaguserforreplies = False + if _skiptaguserforreplies == False: + taggedusers = self.__gettaggedusers(_commentuser) + if taggedusers is not None: + commentusers.extend(taggedusers) + return commentusers + + def __getcommentusertype(self, userid, requestusers): + for requestuser in requestusers: + if requestuser["userid"] == userid: + return requestuser["usertype"] + return notificationconfig().getnotificationusertypelabel("comment user") + + def __getrelatedusers(self, comment, requesttype): + if requesttype == "ministryrequest": + return FOIRequestComment.getcommentusers(comment["commentid"]) + else: + return FOIRawRequestComment.getcommentusers(comment["commentid"]) + + def __gettaggedusers(self, comment): + if comment["taggedusers"] != '[]': + return self.__preparetaggeduser(json.loads(comment["taggedusers"])) + return None + + def __preparetaggeduser(self, data): + taggedusers = [] + for entry in data: + taggedusers.append({"userid":entry["username"], "usertype":notificationconfig().getnotificationusertypelabel("comment tagged user")}) + return taggedusers + + def __getgroupmembers(self,groupid): + notificationusers = [] + notificationusertypelabel = notificationconfig().getnotificationusertypelabel("Group Members") + usergroupfromkeycloak= KeycloakAdminService().getmembersbygroupname(groupid) + if usergroupfromkeycloak is not None and len(usergroupfromkeycloak) > 0: + for user in usergroupfromkeycloak[0].get("members"): + notificationusers.append({"userid":user["username"], "usertype":notificationusertypelabel}) + return notificationusers + return [] + \ No newline at end of file diff --git a/historical-search-api/request_api/services/notificationservice.py b/historical-search-api/request_api/services/notificationservice.py new file mode 100644 index 000000000..89f8643a9 --- /dev/null +++ b/historical-search-api/request_api/services/notificationservice.py @@ -0,0 +1,352 @@ + +from os import stat +from re import VERBOSE + +from requests.api import request +from request_api.services.watcherservice import watcherservice +from request_api.services.notifications.notificationconfig import notificationconfig +from request_api.services.notifications.notificationuser import notificationuser +from request_api.models.FOIRawRequests import FOIRawRequest +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from request_api.models.FOIRequests import FOIRequest +from request_api.models.FOIRequestNotifications import FOIRequestNotification +from request_api.models.FOIRequestNotificationUsers import FOIRequestNotificationUser +from request_api.models.FOIRawRequestNotifications import FOIRawRequestNotification +from request_api.models.FOIRawRequestNotificationUsers import FOIRawRequestNotificationUser +from request_api.models.FOIRawRequestComments import FOIRawRequestComment +from request_api.models.FOIRequestComments import FOIRequestComment +from request_api.models.NotificationTypes import NotificationType +from request_api.models.default_method_result import DefaultMethodResult +from datetime import datetime as datetime2 +import os +import json +from datetime import datetime +from dateutil.parser import parse +from pytz import timezone +from request_api.services.external.keycloakadminservice import KeycloakAdminService +from request_api.models.OperatingTeams import OperatingTeam +import logging +file = open('common/notificationtypes.json', encoding="utf8") +notificationtypes_cache = json.load(file) + + +class notificationservice: + """ FOI notification management service + """ + + def createnotification(self, message, requestid, requesttype, notificationtype, userid, iscleanup=True): + foirequest = self.getrequest(requestid, requesttype) + if iscleanup == True: + self.__cleanupnotifications(requesttype, notificationtype['name'], foirequest) + return self.__createnotification(message, requestid, requesttype, notificationtype, userid, foirequest) + + def createusernotification(self, message, requestid, requesttype, notificationtypename, notificationuser, userid): + foirequest = self.getrequest(requestid, requesttype) + notificationtype = NotificationType().getnotificationtypeid(notificationtypename) + return self.__createnotification(message, requestid, requesttype, notificationtype, userid, foirequest, {"userid": notificationuser}) + + def createremindernotification(self, message, requestid, requesttype, notificationtype, userid): + foirequest = self.getrequest(requestid, requesttype) + return self.__createnotification(message, requestid, requesttype, notificationtype, userid, foirequest) + + def createcommentnotification(self, message, comment, commenttype, requesttype, userid): + requestid = comment["ministryrequestid"] if requesttype == "ministryrequest" else comment["requestid"] + foirequest = self.getrequest(requestid, requesttype) + notificationtype = NotificationType().getnotificationtypeid(commenttype) + return self.__createnotification(message, requestid, requesttype, notificationtype, userid, foirequest, comment) + + def createwatchernotification(self, message, requesttype, watcher, userid): + requestid = watcher["ministryrequestid"] if requesttype == "ministryrequest" else watcher["requestid"] + foirequest = self.getrequest(requestid, requesttype) + notificationtype = NotificationType().getnotificationtypeid('Watcher') + return self.__createnotification(message, requestid, requesttype, notificationtype, userid, foirequest, watcher) + + def editcommentnotification(self, message, comment, userid): + notificationsusers = FOIRequestNotification.getcommentnotifications(comment['commentid']) + notifications = {} + for notification in notificationsusers: + notifications[notification['notificationid']] = notification + for id in notifications: + notifications[id]['notification'] = message + requesttype = self.__getnotificationtypefromid(notifications[id]['idnumber']) + if requesttype == 'ministryrequest': + result = FOIRequestNotification.updatenotification(notifications[id], userid) + else: + result = FOIRawRequestNotification.updatenotification(notifications[id], userid) + if not result.success: + return result + return DefaultMethodResult(True,'Notifications updated for comment id',comment['commentid']) + + def getnotifications(self, userid): + return FOIRequestNotification.getconsolidatednotifications(userid, notificationconfig().getnotificationdays()) + + def getcommentnotifications(self, commentid): + return FOIRequestNotification.getcommentnotifications(commentid) + + def getextensionnotifications(self, extensionid): + return FOIRequestNotification.getextensionnotifications(extensionid) + + def dismissnotification(self, userid, type, idnumber, notificationuserid): + if type is not None: + return self.__dismissnotificationbytype(userid, type) + else: + if idnumber is not None and notificationuserid is not None: + requesttype = self.__getnotificationtypefromid(idnumber) + return self.__dimissnotificationbyuserid(requesttype, notificationuserid) + else: + return self.__dismissnotificationbyuser(userid) + + def dismissnotificationbyid(self, requesttype, notificationids): + return self.__deletenotificationids(requesttype, notificationids) + + def dismissremindernotification(self, requesttype, notificationtype): + notificationlabel = notificationconfig().getnotificationtypelabel(notificationtype) + if requesttype == "ministryrequest": + _ids = FOIRequestNotification.getnotificationidsbytype(notificationlabel) + else: + _ids = FOIRawRequestNotification.getnotificationidsbytype(notificationlabel) + self.__deletenotificationids(requesttype, _ids) + + def dismissnotifications_by_requestid_type_userid(self, requestid, requesttype, notificationtype, userid): + notificationtypelabels = self.__getcleanupnotificationids(notificationtype) + foirequest = self.getrequest(requestid, requesttype) + if requesttype == "ministryrequest": + idnumber = foirequest["filenumber"] + _ids = FOIRequestNotification.getnotificationidsbynumberandtype(idnumber, notificationtypelabels) + else: + _ids = FOIRawRequestNotification.getnotificationidsbynumberandtype('U-00' + str(foirequest['requestid']), notificationtypelabels[0]) + self.__deletenotificationbyuserandid(requesttype, _ids, userid) + + def dismissnotifications_by_requestid_type(self, requestid, requesttype, notificationtype): + notificationtypelabels = self.__getcleanupnotificationids(notificationtype) + foirequest = self.getrequest(requestid, requesttype) + if requesttype == "ministryrequest": + idnumber = foirequest["filenumber"] + _ids = FOIRequestNotification.getnotificationidsbynumberandtype(idnumber, notificationtypelabels) + else: + _ids = FOIRawRequestNotification.getnotificationidsbynumberandtype('U-00' + str(foirequest['requestid']), notificationtypelabels[0]) + self.__deletenotificationids(requesttype, _ids) + + + def __createnotification(self, message, requestid, requesttype, notificationtype, userid, foirequest, requestjson=None): + notification = self.__preparenotification(message, requesttype, notificationtype, userid, foirequest, requestjson) + if notification is not None: + if requesttype == "ministryrequest": + return FOIRequestNotification.savenotification(notification) + else: + return FOIRawRequestNotification.savenotification(notification) + return DefaultMethodResult(True,'No change',requestid) + + def dismissnotificationsbyrequestid(self,requestid, requesttype): + foirequest = self.getrequest(requestid, requesttype) + if requesttype == "ministryrequest": + idnumber = foirequest["filenumber"] + _ids = FOIRequestNotification.getnotificationidsbynumber(idnumber) + if _ids: + FOIRequestNotificationUser.dismissbynotificationid(_ids) + FOIRequestNotification.dismissnotification(_ids) + else: + _ids = FOIRawRequestNotification.getnotificationidsbynumber('U-00' + str(foirequest['requestid'])) + if _ids: + FOIRawRequestNotificationUser.dismissbynotificationid(_ids) + FOIRawRequestNotification.dismissnotification(_ids) + + def __cleanupnotifications(self, requesttype, notificationtype, foirequest): + notificationtypelabels = self.__getcleanupnotificationids(notificationtype) + if requesttype == "ministryrequest": + idnumber = foirequest["filenumber"] + _ids = FOIRequestNotification.getnotificationidsbynumberandtype(idnumber, notificationtypelabels) + else: + _ids = FOIRawRequestNotification.getnotificationidsbynumberandtype('U-00' + str(foirequest['requestid']), notificationtypelabels[0]) + self.__deletenotificationids(requesttype, _ids) + + + def __getcleanupnotificationids(self, notificationtype): + notificationtypelabels = [] + notificationlabel = notificationconfig().getnotificationtypelabel(notificationtype) + notificationtypelabels.append(notificationlabel) + if notificationtype == "State" or notificationtype.endswith("Assignment"): + notificationtypelabels.append(notificationconfig().getnotificationtypelabel("Group Members")) + return notificationtypelabels + + + def __deletenotificationids(self, requesttype, notificationids): + if notificationids: + if requesttype == "ministryrequest": + cresponse = FOIRequestNotificationUser.dismissbynotificationid(notificationids) + presponse = FOIRequestNotification.dismissnotification(notificationids) + else: + cresponse = FOIRawRequestNotificationUser.dismissbynotificationid(notificationids) + presponse = FOIRawRequestNotification.dismissnotification(notificationids) + if cresponse.success == True and presponse.success == True: + return DefaultMethodResult(True,'Notifications deleted ','|'.join(map(str, notificationids))) + else: + return DefaultMethodResult(False,'Unable to delete the notifications ','|'.join(map(str, notificationids))) + + def __dimissnotificationbyuserid(self, requesttype, notificationuserid): + notficationids = self.__getdismissparentids(requesttype, notificationuserid) + if requesttype == "ministryrequest": + cresponse = FOIRequestNotificationUser.dismissnotification(notificationuserid) + presponse = FOIRequestNotification.dismissnotification(notficationids) + else: + cresponse = FOIRawRequestNotificationUser.dismissnotification(notificationuserid) + presponse = FOIRawRequestNotification.dismissnotification(notficationids) + if cresponse.success == True and presponse.success == True: + return DefaultMethodResult(True,'Notifications deleted for',notificationuserid) + else: + return DefaultMethodResult(False,'Unable to delete the notifications for',notificationuserid) + + def __dismissnotificationbyuser(self, userid): + requestnotificationids = self.__getdismissparentidsbyuser("ministryrequest", userid) + requestnotification = FOIRequestNotificationUser.dismissnotificationbyuser(userid) + rawnotificationids = self.__getdismissparentidsbyuser("rawrequest", userid) + rawnotification = FOIRawRequestNotificationUser.dismissnotificationbyuser(userid) + prequestnotification = FOIRequestNotification.dismissnotification(requestnotificationids) + prawnotification = FOIRawRequestNotification.dismissnotification(rawnotificationids) + if requestnotification.success == True and rawnotification.success == True and prequestnotification.success == True and prawnotification.success == True: + return DefaultMethodResult(True,'Notifications deleted for user',userid) + else: + return DefaultMethodResult(False,'Unable to delete the notifications',userid) + + def __dismissnotificationbytype(self, userid, type): + notificationusertypelabel = notificationconfig().getnotificationusertypelabel(type) + requestnotificationids = self.__getdismissparentidsbyuserandtype("ministryrequest", userid, notificationusertypelabel) + requestnotification = FOIRequestNotificationUser.dismissnotificationbyuserandtype(userid, notificationusertypelabel) + rawnotificationids = self.__getdismissparentidsbyuserandtype("rawrequest", userid, notificationusertypelabel) + rawnotification = FOIRawRequestNotificationUser.dismissnotificationbyuserandtype(userid, notificationusertypelabel) + prequestnotification = FOIRequestNotification.dismissnotification(requestnotificationids) + prawnotification = FOIRawRequestNotification.dismissnotification(rawnotificationids) + if requestnotification.success == True and rawnotification.success == True and prequestnotification.success == True and prawnotification.success == True: + return DefaultMethodResult(True,'Notifications deleted for user',userid) + else: + return DefaultMethodResult(False,'Unable to delete the notifications',userid) + + def __deletenotificationbyuserandid(self, requesttype, notificationids,userid): + if notificationids: + if requesttype == "ministryrequest": + requestnotificationids= FOIRequestNotificationUser.getnotificationidsbyuserandid(userid, notificationids) + cresponse = FOIRequestNotificationUser.dismissbynotificationid(requestnotificationids) + presponse = FOIRequestNotification.dismissnotification(requestnotificationids) + if cresponse.success == True and presponse.success == True: + return DefaultMethodResult(True,'Notifications deleted for id','|'.join(map(str, notificationids))) + else: + return DefaultMethodResult(False,'Unable to delete the notifications for id','|'.join(map(str, notificationids))) + else: + rawnotificationids= FOIRawRequestNotificationUser.getnotificationidsbyuserandid(userid, notificationids) + cresponse = FOIRawRequestNotificationUser.dismissbynotificationid(rawnotificationids) + presponse = FOIRawRequestNotification.dismissnotification(rawnotificationids) + if cresponse.success == True and presponse.success == True: + return DefaultMethodResult(True,'Notifications deleted for id','|'.join(map(str, notificationids))) + else: + return DefaultMethodResult(False,'Unable to delete the notifications for id','|'.join(map(str, notificationids))) + + def __getdismissparentids(self, requesttype, notificationuserid): + if requesttype == "ministryrequest": + _notficationids = FOIRequestNotificationUser.getnotificationsbyid(notificationuserid) + else: + _notficationids = FOIRawRequestNotificationUser.getnotificationsbyid(notificationuserid) + return self.__filterdismissparentids(_notficationids) + + def __getdismissparentidsbyuser(self, requesttype, userid): + if requesttype == "ministryrequest": + _notficationids = FOIRequestNotificationUser.getnotificationsbyuser(userid) + else: + _notficationids = FOIRawRequestNotificationUser.getnotificationsbyuser(userid) + return self.__filterdismissparentids(_notficationids) + + def __getdismissparentidsbyuserandtype(self, requesttype, userid, notificationusertypelabel): + if requesttype == "ministryrequest": + _notficationids = FOIRequestNotificationUser.getnotificationsbyuserandtype(userid, notificationusertypelabel) + else: + _notficationids = FOIRawRequestNotificationUser.getnotificationsbyuserandtype(userid, notificationusertypelabel) + return self.__filterdismissparentids(_notficationids) + + def __filterdismissparentids(self,_notficationids): + notficationids = [] + for notification in _notficationids: + if notification["count"] == 1: + notficationids.append(notification["notificationid"]) + return notficationids + + def __getnotificationtypefromid(self, idnumber): + return 'rawrequest' if idnumber.lower().startswith('u-00') else 'ministryrequest' + + def __preparenotification(self, message, requesttype, notificationtype, userid, foirequest, requestjson=None): + ministryusers = [] + if requesttype == "ministryrequest": + notification = FOIRequestNotification() + notification.requestid = foirequest["foiministryrequestid"] + notification.idnumber = foirequest["filenumber"] + notification.foirequestid = foirequest["foirequest_id"] + + #mute notifications for ministry users + mutenotification = self.__mutenotification(requesttype, notificationtype['name'], foirequest) + usergroupfromkeycloak = KeycloakAdminService().getmembersbygroupname(foirequest["assignedministrygroup"]) + if usergroupfromkeycloak is not None and len(usergroupfromkeycloak) > 0: + for user in usergroupfromkeycloak[0].get("members"): + ministryusers.append(user["username"]) + else: + notification = FOIRawRequestNotification() + notification.requestid = foirequest["requestid"] + notification.idnumber ='U-00' + str(foirequest['requestid']) + mutenotification = False + + notification.notificationtypelabel = notificationtype['notificationtypelabel'] + notification.notificationtypeid = notificationtype['notificationtypeid'] + notification.axisnumber = foirequest["axisrequestid"] + notification.version = foirequest["version"] + notification.createdby = userid + notification.notification = message + notification.isdeleted = False + + notificationusers = notificationuser().getnotificationusers(notificationtype['name'], requesttype, userid, foirequest, requestjson) + users = [] + for _notificationuser in notificationusers: + users.append(self.__preparenotificationuser(requesttype, _notificationuser, userid, mutenotification, ministryusers)) + notification.notificationusers = users + return notification if users else None + + def __preparenotificationuser(self, requesttype, notificationuser, userid, mute=False, ministryusers=[]): + if requesttype == "ministryrequest": + user = FOIRequestNotificationUser() + if notificationuser["userid"] in ministryusers: + user.isdeleted = mute + else: + user.isdeleted = False + else: + user = FOIRawRequestNotificationUser() + user.isdeleted = False + user.notificationusertypelabel = notificationuser["usertype"] + user.notificationusertypeid = notificationconfig().getnotificationusertypeid( notificationuser["usertype"]) + user.userid = notificationuser["userid"] + user.createdby = userid + return user + + def __mutenotification(self, requesttype, notificationtype, request): + #get mute conditions from env + mutenotifications = notificationconfig().getmutenotifications() + if "programarea.bcgovcode" in request: + bcgovcode = request["programarea.bcgovcode"].upper() + if requesttype == "ministryrequest"and bcgovcode in mutenotifications: + foirequest = FOIRequest.getrequest(request["foirequest_id"]) + if foirequest["requesttype"].upper() in (_requesttype.upper() for _requesttype in mutenotifications[bcgovcode]["request_types"]): + if request["requeststatus.name"].upper() in (_state.upper() for _state in mutenotifications[bcgovcode]["state_exceptions"]): + return False + if notificationtype.upper() in (_notificationtype.upper() for _notificationtype in mutenotifications[bcgovcode]["type_exceptions"]): + return False + return True + + return False + + def getrequest(self, requestid, requesttype): + if requesttype == "ministryrequest": + return FOIMinistryRequest.getrequestbyministryrequestid(requestid) + else: + return FOIRawRequest.get_request(requestid) + + + + + + + diff --git a/historical-search-api/request_api/services/oipcservice.py b/historical-search-api/request_api/services/oipcservice.py new file mode 100644 index 000000000..636044430 --- /dev/null +++ b/historical-search-api/request_api/services/oipcservice.py @@ -0,0 +1,27 @@ +from request_api.models.OIPCReviewTypes import OIPCReviewTypes +from request_api.models.OIPCStatuses import OIPCStatuses +from request_api.models.OIPCReasons import OIPCReasons +from request_api.models.OIPCOutcomes import OIPCOutcomes +from request_api.models.OIPCInquiryOutcomes import OIPCInquiryOutcomes +from request_api.models.OIPCReviewTypesReasons import OIPCReviewTypesReasons + +class oipcservice: + """ OIPC service + """ + def getreviewtypes(self): + return OIPCReviewTypes.getreviewtypes() + + def getreviewtypeswithreasons(self): + return OIPCReviewTypesReasons.getreviewtypeswithreasons() + + def getreasons(self): + return OIPCReasons.getreasons() + + def getstatuses(self): + return OIPCStatuses.getstatuses() + + def getoutcomes(self): + return OIPCOutcomes.getoutcomes() + + def getinquiryoutcomes(self): + return OIPCInquiryOutcomes.getinquiryoutcomes() \ No newline at end of file diff --git a/historical-search-api/request_api/services/paymentservice.py b/historical-search-api/request_api/services/paymentservice.py new file mode 100644 index 000000000..3bf598034 --- /dev/null +++ b/historical-search-api/request_api/services/paymentservice.py @@ -0,0 +1,156 @@ + +from os import stat +from re import VERBOSE +from request_api.models.FOIRequestPayments import FOIRequestPayment +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from request_api.services.document_generation_service import DocumentGenerationService +from request_api.models.default_method_result import DefaultMethodResult +from request_api.services.applicantcorrespondence.applicantcorrespondencelog import applicantcorrespondenceservice +from request_api.utils.enums import PaymentEventType, StateName + +import json +from dateutil.parser import parse +from datetime import datetime +import asyncio + +from dateutil import parser +from dateutil import tz +from pytz import timezone +import pytz +import maya +import logging +import re + +class paymentservice: + """ FOI payment management service + Supports creation, update and delete of payment + """ + + def createpayment(self, requestid, ministryrequestid, data, createdby='System'): + payment = FOIRequestPayment() + ministryversion = FOIMinistryRequest.getversionforrequest(ministryrequestid) + payment.foirequestid = requestid + payment.ministryrequestid = ministryrequestid + payment.ministryrequestversion = ministryversion + payment.paymenturl = data['paymenturl'] if 'paymenturl' in data else None + payment.paymentexpirydate = data['paymentExpiryDate'] if 'paymentExpiryDate' in data else self.getpaymentexpirydate(requestid, ministryrequestid) + payment.version = 1 + payment.createdby = createdby + _payment = FOIRequestPayment.getpayment(requestid, ministryrequestid) + if _payment is not None and _payment != {}: + payment.version = _payment["version"] + 1 + payment.paymentid = _payment["paymentid"] + return FOIRequestPayment.savepayment(payment) + + def updatepayment(self, paymentid, data, userid): + return FOIRequestPayment.updatepayment(paymentid, data["paymenturl"], userid) + + def createpaymentversion(self, request_id, ministry_request_id, amountpaid): + payment = self.__createpaymentinstance(request_id, ministry_request_id) + if payment is not None and payment != {}: + payment.paidamount = amountpaid + return FOIRequestPayment.savepayment(payment) + + def cancelpayment(self, request_id, ministry_request_id, paymentschema): + payment = self.__createpaymentinstance(request_id, ministry_request_id) + if payment is not None and payment != {}: + payment.paymenturl = paymentschema['paymenturl'] + payment.paymentexpirydate = datetime.now().isoformat() + payment.createdby = 'System_Cancel' + return FOIRequestPayment.savepayment(payment) + + def __createpaymentinstance(self, request_id, ministry_request_id): + _payment = FOIRequestPayment.getpayment(request_id, ministry_request_id) + ministryversion = FOIMinistryRequest.getversionforrequest(ministry_request_id) + payment = FOIRequestPayment() + payment.foirequestid = request_id + payment.ministryrequestid = ministry_request_id + payment.ministryrequestversion = ministryversion + if _payment is not None and _payment != {}: + payment.paymenturl = _payment['paymenturl'] + payment.paymentexpirydate = _payment['paymentexpirydate'] + payment.version = _payment['version'] + 1 + payment.createdby = 'System' + payment.paymentid = _payment["paymentid"] + return payment + + + def getpayment(self, requestid, ministryrequestid): + return FOIRequestPayment.getpayment(requestid, ministryrequestid) + + def getpaymentexpirydate(self, requestid, ministryrequestid): + payment = FOIRequestPayment.getpayment(requestid, ministryrequestid) + paymentexpirydate = payment["paymentexpirydate"] if payment not in (None, {}) else None + if paymentexpirydate is not None: + return parse(str(paymentexpirydate)).strftime("%Y-%m-%d") + return "" + + def createpaymentreceipt(self, request_id, ministry_request_id, data, parsed_args, previously_paid_amount): + try: + data['previous_amt_paid'] = '{:.2f}'.format(previously_paid_amount) + templatename = self.__getlatesttemplatename(ministry_request_id) + balancedue = float(data['cfrfee']['feedata']["balanceDue"]) + prevstate = data["stateTransition"][1]["status"] if "stateTransition" in data and len(data["stateTransition"]) > 2 else None + basepath = 'request_api/receipt_templates/' + receiptname = 'cfr_fee_payment_receipt' + attachmentcategory = "FEE-ESTIMATE-PAYMENT-RECEIPT" + filename = f"Fee Estimate Payment Receipt {re.sub(r'[^a-zA-Z0-9]', '', data['cfrfee']['created_at'])}.pdf" + if balancedue > 0: + receipt_template_path= basepath + self.getreceiptename('HALFPAYMENT') +".docx" + receiptname = self.getreceiptename('HALFPAYMENT') + else: + receiptname = self.getreceiptename('FULLPAYMENT') + if prevstate.lower() == "response" or (templatename and templatename == 'PAYOUTSTANDING'): + receiptname = self.getreceiptename('PAYOUTSTANDING') + attachmentcategory = "OUTSTANDING-PAYMENT-RECEIPT" + filename = f"Fee Balance Outstanding Payment Receipt {re.sub(r'[^a-zA-Z0-9]', '', data['cfrfee']['created_at'])}.pdf" + receipt_template_path= basepath + receiptname + ".docx" + data['waivedAmount'] = data['cfrfee']['feedata']['estimatedlocatinghrs'] * 30 if data['cfrfee']['feedata']['estimatedlocatinghrs'] < 3 else 90 + data.update({'paymentInfo': { + 'paymentDate': parsed_args.get('trnDate'), + 'orderId': parsed_args.get('trnOrderId'), + 'transactionId': parsed_args.get('pbcTxnNumber'), + 'cardType': parsed_args.get('cardType') + }}) + document_service : DocumentGenerationService = DocumentGenerationService(receiptname) + receipt = document_service.generate_receipt(data,receipt_template_path) + document_service.upload_receipt(filename, receipt.content, ministry_request_id, data['bcgovcode'], data['idNumber'], attachmentcategory) + return DefaultMethodResult(True,'Payment Receipt created',ministry_request_id) + except Exception as ex: + logging.exception(ex) + return DefaultMethodResult(False,'Unable to create Payment Receipt',ministry_request_id) + + def postpayment(self, ministry_request_id, data): + prevstate = data["stateTransition"][1]["status"] if "stateTransition" in data and len(data["stateTransition"]) > 2 else None + nextstatename = StateName.callforrecords.value + + balancedue = float(data['cfrfee']['feedata']["balanceDue"]) + paymenteventtype = PaymentEventType.paid.value + if balancedue > 0: + paymenteventtype = PaymentEventType.depositpaid.value + if prevstate.lower() == "response": + nextstatename = StateName.response.value + templatename = self.__getlatesttemplatename(ministry_request_id) + #outstanding + if balancedue == 0 and ((templatename and templatename == 'PAYOUTSTANDING') or prevstate.lower() == "response"): + paymenteventtype = PaymentEventType.outstandingpaid.value + nextstatename = StateName.response.value + return nextstatename, paymenteventtype + + def getreceiptename(self, key): + if key == "HALFPAYMENT": + return "cfr_fee_payment_receipt_half" + elif key == "FULLPAYMENT": + return "cfr_fee_payment_receipt_full" + elif key == "PAYOUTSTANDING": + return "outstanding_fee_payment_receipt" + else: + logging.info("Unknown key") + return None + + def __getlatesttemplatename(self, ministryrequestid): + latestcorrespondence = applicantcorrespondenceservice().getlatestapplicantcorrespondence(ministryrequestid) + _latesttemplateid = latestcorrespondence['templateid'] if 'templateid' in latestcorrespondence else None + if _latesttemplateid: + return applicantcorrespondenceservice().gettemplatebyid(_latesttemplateid).name + return None diff --git a/historical-search-api/request_api/services/programareadivisionservice.py b/historical-search-api/request_api/services/programareadivisionservice.py new file mode 100644 index 000000000..c3b07c505 --- /dev/null +++ b/historical-search-api/request_api/services/programareadivisionservice.py @@ -0,0 +1,58 @@ +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: + + def getallprogramareadivisions(self): + """ Returns all active program area divisions + """ + 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 + """ + return ProgramAreaDivision.createprogramareadivision(data) + + def updateprogramareadivision(self, divisionid, data,userid): + """ Updates an existing program area division + """ + return ProgramAreaDivision.updateprogramareadivision(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 + """ + divisions = [] + areas = programareaservice().getprogramareas() + for entry in data: + area = next((a for a in areas if a["programareaid"] == entry["programareaid"]), None) + if (area is not None): + entry["programarea"] = area["name"] + entry["areabcgovcode"] = area["bcgovcode"] + entry["areaiaocode"] = area["iaocode"] + divisions.append(entry) + return divisions diff --git a/historical-search-api/request_api/services/programareaservice.py b/historical-search-api/request_api/services/programareaservice.py new file mode 100644 index 000000000..6f8708e4d --- /dev/null +++ b/historical-search-api/request_api/services/programareaservice.py @@ -0,0 +1,16 @@ +from request_api.models.ProgramAreas import ProgramArea + +class programareaservice: + + def getprogramareas(self): + """ Returns the active records + """ + return ProgramArea.getprogramareas() + + def getprogramareabyiaocode(self, iaocode): + return ProgramArea.getprogramareabyiaocode(iaocode) + + def getprogramareasforministryuser(self, groups = None): + """ Returns the active records + """ + return ProgramArea.getprogramareasforministryuser(groups) \ No newline at end of file diff --git a/historical-search-api/request_api/services/rawrequest/rawrequestservicegetter.py b/historical-search-api/request_api/services/rawrequest/rawrequestservicegetter.py new file mode 100644 index 000000000..b8b1cf870 --- /dev/null +++ b/historical-search-api/request_api/services/rawrequest/rawrequestservicegetter.py @@ -0,0 +1,230 @@ + +from typing import Counter + +from request_api.models.FOIRawRequests import FOIRawRequest +from request_api.models.FOIRequestStatus import FOIRequestStatus +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from dateutil.parser import parse +import maya +from request_api.models.FOIAssignees import FOIAssignee +from request_api.utils.enums import StateName + +class rawrequestservicegetter: + """ This class consolidates retrival of FOI raw request for actors: iao. + """ + + def getallrawrequests(self): + requests = FOIRawRequest.getrequests() + unopenedrequests = [] + for request in requests: + firstname , lastname, requesttype = '','','' + if(request.version != 1 and request.sourceofsubmission != "intake") or request.sourceofsubmission == "intake": + firstname = request.requestrawdata['firstName'] + lastname = request.requestrawdata['lastName'] + requesttype = request.requestrawdata['requestType'] + elif (request.sourceofsubmission!= "intake" and request.version == 1): + firstname = request.requestrawdata['contactInfo']['firstName'] + lastname = request.requestrawdata['contactInfo']['lastName'] + requesttype = request.requestrawdata['requestType']['requestType'] + + assignedgroupvalue = request.assignedgroup if request.assignedgroup else "Unassigned" + assignedtovalue = request.assignedto if request.assignedto else "Unassigned" + dt = maya.parse(request.created_at).datetime(to_timezone='America/Vancouver', naive=False) + _createddate = dt + + unopenrequest = {'id': request.requestid, + 'firstName': firstname, + 'lastName': lastname, + 'requestType': requesttype, + 'currentState': request.status, + 'receivedDate': request.requestrawdata["receivedDate"] if "receivedDate" in request.requestrawdata else _createddate.strftime('%Y %b, %d'), + 'receivedDateUF':request.requestrawdata["receivedDateUF"] if "receivedDateUF" in request.requestrawdata else str(_createddate), + 'assignedGroup': assignedgroupvalue, + 'assignedTo': assignedtovalue, + 'xgov': 'No', + 'idNumber': 'U-00' + str(request.requestid), + 'axisRequestId': request.axisrequestid, + 'version':request.version + } + unopenedrequests.append(unopenrequest) + + return unopenedrequests + + def getrawrequestforid(self, requestid): + request = FOIRawRequest.get_request(requestid) + request = self.__attachministriesinfo(request) + if request != {} and (request['version'] == 1 or request['status'] == StateName.unopened.value) and request['sourceofsubmission'] != "intake": + requestrawdata = request['requestrawdata'] + requesttype = requestrawdata['requestType']['requestType'] + baserequestinfo = self.__preparebaserequestinfo(requestid, request, requesttype, requestrawdata) + if self.__ispersonalrequest(requesttype): + baserequestinfo['additionalPersonalInfo'] = self.__prepareadditionalpersonalinfo(requestrawdata) + return baserequestinfo + elif request != {} and request['version'] != 1 and request['sourceofsubmission'] != "intake": + request['requestrawdata']['currentState'] = request['status'] + request['requestrawdata']['requeststatuslabel'] = request['requeststatuslabel'] + request['requestrawdata']['lastStatusUpdateDate'] = FOIRawRequest.getLastStatusUpdateDate(requestid, request['status']).strftime(self.__generaldateformat()) + if request['requeststatuslabel'] == StateName.closed.name: + request['requestrawdata']['stateTransition']= FOIRawRequest.getstatesummary(requestid) + request['requestrawdata']['wfinstanceid'] = request['wfinstanceid'] + request['requestrawdata']['closedate']= self.__getclosedate(request['closedate']) + request['requestrawdata']['isiaorestricted']= request['isiaorestricted'] if request['isiaorestricted'] is not None else False + return request['requestrawdata'] + elif request != {} and request['sourceofsubmission'] == "intake": + requestrawdata = request['requestrawdata'] + requesttype = requestrawdata['requestType'] + additionalpersonalinfo = None + if self.__ispersonalrequest(requesttype) and requestrawdata.get('additionalPersonalInfo') is not None: + additionalpersonalinfo = self.__prepareadditionalpersonalinfoforintakesubmission(requestrawdata) + + request['requestrawdata']['additionalPersonalInfo'] = additionalpersonalinfo + + request['requestrawdata']['wfinstanceid'] = request['wfinstanceid'] + request['requestrawdata']['currentState'] = request['status'] + requeststatus = FOIRequestStatus().getrequeststatus(request['status']) + request['requestrawdata']['requeststatuslabel'] = requeststatus['statuslabel'] + request['requestrawdata']['lastStatusUpdateDate'] = FOIRawRequest.getLastStatusUpdateDate(requestid, request['status']).strftime(self.__generaldateformat()) + request['requestrawdata']['stateTransition']= FOIRawRequest.getstatesummary(requestid) + request['requestrawdata']['closedate']= self.__getclosedate(request['closedate']) + request['requestrawdata']['isiaorestricted']= request['isiaorestricted'] if request['isiaorestricted'] is not None else False + return request['requestrawdata'] + else: + return None + + def __getclosedate(self, requestclosedate): + closedate = parse(requestclosedate).strftime(self.__generaldateformat()) if requestclosedate is not None else None + return closedate + + def getrawrequestfieldsforid(self, requestid, fields): + request = FOIRawRequest.get_request(requestid) + fieldsresp = {} + for field in fields: + if field == "ministries" and request['status'] == 'Archived': + fieldsresp['openedMinistries']= FOIMinistryRequest.getministriesopenedbyuid(request["requestid"]) + return fieldsresp + + def getaxisequestids(self): + return FOIRawRequest.getDistinctAXISRequestIds() + + def getcountofaxisequestidbyaxisequestid(self, axisrequestid): + return FOIRawRequest.getCountOfAXISRequestIdbyAXISRequestId(axisrequestid) + + def __attachministriesinfo(self,request): + if request != {} and request['status'] == 'Archived': + request['requestrawdata']['openedMinistries']= FOIMinistryRequest.getministriesopenedbyuid(request["requestid"]) + return request + + def __preparebaserequestinfo(self, requestid, request, requesttype, requestrawdata): + contactinfo = requestrawdata.get('contactInfo') + dt = maya.parse(request['created_at']).datetime(to_timezone='America/Vancouver', naive=False) + _createddate = dt + decriptiontimeframe = requestrawdata.get('descriptionTimeframe') + contactinfooptions = requestrawdata.get('contactInfoOptions') + _fromdate = parse(decriptiontimeframe['fromDate']) + _todate = parse(decriptiontimeframe['toDate']) + assignee = None + if ("assignedto" in request and request["assignedto"] not in (None,'')): + assignee = FOIAssignee.getassignee(request["assignedto"]) + return {'id': request['requestid'], + 'wfinstanceid': request['wfinstanceid'], + 'ispiiredacted': request['ispiiredacted'], + 'isiaorestricted': request['isiaorestricted'] if request['isiaorestricted'] is not None else False, + 'sourceOfSubmission': request['sourceofsubmission'], + 'requestType': requesttype, + 'firstName': contactinfo['firstName'], + 'middleName': requestrawdata['contactInfo']['middleName'], + 'lastName': contactinfo['lastName'], + 'businessName': contactinfo['businessName'], + 'currentState': request['status'], + 'receivedDate': _createddate.strftime('%Y %b, %d'), + 'receivedDateUF': _createddate.strftime('%Y-%m-%d %H:%M:%S.%f'), + 'assignedGroup': request["assignedgroup"] if "assignedgroup" in request else "Unassigned", + 'assignedTo': request["assignedto"] if "assignedto" in request else "Unassigned", + 'assignedToFirstName': assignee["firstname"] if assignee is not None and "firstname" in assignee else None, + 'assignedToLastName': assignee["lastname"] if assignee is not None and "lastname" in assignee else None, + 'xgov': 'No', + 'idNumber': 'U-00' + str(request['requestid']), + 'axisRequestId': request['axisrequestid'], + 'axisSyncDate': request['axissyncdate'], + 'linkedRequests': request['linkedrequests'], + 'email': contactinfooptions['email'], + 'phonePrimary': contactinfooptions['phonePrimary'], + 'phoneSecondary': contactinfooptions['phoneSecondary'], + 'address': contactinfooptions['address'], + 'city': contactinfooptions['city'], + 'postal': contactinfooptions['postal'], + 'province': contactinfooptions['province'], + 'country': contactinfooptions['country'], + 'description': decriptiontimeframe['description'], + 'fromDate': _fromdate.strftime(self.__generaldateformat()), + 'toDate': _todate.strftime(self.__generaldateformat()), + 'correctionalServiceNumber': decriptiontimeframe['correctionalServiceNumber'], + 'publicServiceEmployeeNumber': decriptiontimeframe['publicServiceEmployeeNumber'], + 'topic': decriptiontimeframe['topic'], + 'selectedMinistries': requestrawdata['ministry']['selectedMinistry'], + 'lastStatusUpdateDate': FOIRawRequest.getLastStatusUpdateDate(requestid, request['status']).strftime(self.__generaldateformat()), + 'stateTransition': FOIRawRequest.getstatesummary(requestid), + 'closedate': request['closedate'].strftime(self.__generaldateformat()) if request['closedate'] is not None else None + } + + def __prepareadditionalpersonalinfo(self, requestrawdata): + childinformation = requestrawdata.get('childInformation') + anotherpersoninformation = requestrawdata.get('anotherInformation') + adoptiveparents = requestrawdata.get('adoptiveParents') + + haschildinfo = self.__ispersonalinfopresent(childinformation) + hasanotherpersoninfo = self.__ispersonalinfopresent(anotherpersoninformation) + hasadoptiveparentinfo = self.__ispersonalinfopresent(adoptiveparents) + contactinfo = requestrawdata.get('contactInfo') + return { + 'alsoKnownAs': contactinfo['alsoKnownAs'], + 'requestFor': requestrawdata['selectAbout'], + 'birthDate': parse(contactinfo['birthDate']).strftime(self.__generaldateformat()) if contactinfo.get('birthDate', None) is not None else '', + + 'childFirstName': self.__getpropertyvalue(childinformation,'firstName', haschildinfo), + 'childMiddleName': self.__getpropertyvalue(childinformation,'middleName', haschildinfo), + 'childLastName': self.__getpropertyvalue(childinformation,'lastName', haschildinfo), + 'childAlsoKnownAs': self.__getpropertyvalue(childinformation,'alsoKnownAs', haschildinfo), + 'childBirthDate': parse(childinformation['dateOfBirth']).strftime(self.__generaldateformat()) if haschildinfo and childinformation['dateOfBirth'] is not None else '', + + 'anotherFirstName': self.__getpropertyvalue(anotherpersoninformation,'firstName', hasanotherpersoninfo), + 'anotherMiddleName': self.__getpropertyvalue(anotherpersoninformation,'middleName', hasanotherpersoninfo), + 'anotherLastName': self.__getpropertyvalue(anotherpersoninformation,'lastName', hasanotherpersoninfo), + 'anotherAlsoKnownAs': self.__getpropertyvalue(anotherpersoninformation,'alsoKnownAs', hasanotherpersoninfo), + 'anotherBirthDate': parse(anotherpersoninformation['dateOfBirth']).strftime(self.__generaldateformat()) if hasanotherpersoninfo and anotherpersoninformation['dateOfBirth'] is not None else '', + + 'adoptiveMotherFirstName': self.__getpropertyvalue(adoptiveparents,'motherFirstName', hasadoptiveparentinfo), + 'adoptiveMotherLastName': self.__getpropertyvalue(adoptiveparents,'motherLastName', hasadoptiveparentinfo), + 'adoptiveFatherLastName': self.__getpropertyvalue(adoptiveparents,'fatherLastName', hasadoptiveparentinfo), + 'adoptiveFatherFirstName': self.__getpropertyvalue(adoptiveparents,'fatherFirstName', hasadoptiveparentinfo) + } + + def __prepareadditionalpersonalinfoforintakesubmission(self,requestrawdata): + _childandanotherpersoninfo = requestrawdata['additionalPersonalInfo'] + additionalpersonalinfo = { + 'childFirstName': _childandanotherpersoninfo['childFirstName'] if _childandanotherpersoninfo.get('childFirstName') is not None else '', + 'childMiddleName': _childandanotherpersoninfo['childMiddleName'] if _childandanotherpersoninfo.get('childMiddleName') is not None else '', + 'childLastName': _childandanotherpersoninfo['childLastName'] if _childandanotherpersoninfo.get('childLastName') is not None else '', + 'childAlsoKnownAs':_childandanotherpersoninfo['childAlsoKnownAs'] if _childandanotherpersoninfo.get('childAlsoKnownAs') is not None else '', + 'childBirthDate': _childandanotherpersoninfo['childBirthDate'] if _childandanotherpersoninfo.get('childBirthDate') is not None else '', + 'anotherFirstName': _childandanotherpersoninfo['anotherFirstName'] if _childandanotherpersoninfo.get('anotherFirstName') is not None else '', + 'anotherMiddleName': _childandanotherpersoninfo['anotherMiddleName'] if _childandanotherpersoninfo.get('anotherMiddleName') is not None else '', + 'anotherLastName':_childandanotherpersoninfo['anotherLastName'] if _childandanotherpersoninfo.get('anotherLastName') is not None else '', + 'anotherAlsoKnownAs': _childandanotherpersoninfo['anotherAlsoKnownAs'] if _childandanotherpersoninfo.get('anotherAlsoKnownAs') is not None else '', + 'anotherBirthDate': _childandanotherpersoninfo['anotherBirthDate'] if _childandanotherpersoninfo.get('anotherBirthDate') is not None else '', + 'personalHealthNumber' : _childandanotherpersoninfo['personalHealthNumber'] if _childandanotherpersoninfo.get('personalHealthNumber') is not None else '', + 'birthDate': _childandanotherpersoninfo['birthDate'] if _childandanotherpersoninfo.get('birthDate') is not None else '' + } + return additionalpersonalinfo + + def __getpropertyvalue(self, inputschema, property, criteria): + return inputschema[property] if inputschema is not None and criteria else '' + + def __ispersonalrequest(self, requesttype): + return True if requesttype == 'personal' else False + + def __ispersonalinfopresent(self, criteria): + return True if criteria != None else False + + def __generaldateformat(self): + return '%Y-%m-%d' diff --git a/historical-search-api/request_api/services/rawrequestservice.py b/historical-search-api/request_api/services/rawrequestservice.py new file mode 100644 index 000000000..c39c67b85 --- /dev/null +++ b/historical-search-api/request_api/services/rawrequestservice.py @@ -0,0 +1,153 @@ + +import re +from typing import Counter + +from flask.signals import request_started +from sqlalchemy.sql.expression import false +from request_api.models.FOIRawRequests import FOIRawRequest +import json +import asyncio +from request_api.utils.redispublisher import RedisPublisherService +from request_api.services.workflowservice import workflowservice +from request_api.services.documentservice import documentservice +from request_api.services.eventservice import eventservice +from request_api.services.rawrequest.rawrequestservicegetter import rawrequestservicegetter +from request_api.exceptions import BusinessException, Error +from request_api.models.default_method_result import DefaultMethodResult +from request_api.models.FOIRawRequestWatchers import FOIRawRequestWatcher +from request_api.services.foirequest.requestserviceconfigurator import requestserviceconfigurator +from request_api.utils.enums import StateName +import logging + +class rawrequestservice: + """ FOI Raw Request management service + + This service class manages all CRUD operations related to an FOI unopened Request + + """ + + def saverawrequest(self, requestdatajson, sourceofsubmission, userid,notes): + assigneegroup = requestdatajson["assignedGroup"] if requestdatajson.get("assignedGroup") != None else None + assignee = requestdatajson["assignedTo"] if requestdatajson.get("assignedTo") not in (None,'') else None + assigneefirstname = requestdatajson["assignedToFirstName"] if requestdatajson.get("assignedToFirstName") != None else None + assigneemiddlename = requestdatajson["assignedToMiddleName"] if requestdatajson.get("assignedToMiddleName") != None else None + assigneelastname = requestdatajson["assignedToLastName"] if requestdatajson.get("assignedToLastName") != None else None + ispiiredacted = requestdatajson["ispiiredacted"] if 'ispiiredacted' in requestdatajson else False + axisrequestid = requestdatajson["axisRequestId"] if 'axisRequestId' in requestdatajson else None + isaxisrequestidpresent = False + if axisrequestid is not None: + isaxisrequestidpresent = self.isaxisrequestidpresent(axisrequestid) + axissyncdate = requestdatajson["axisSyncDate"] if 'axisSyncDate' in requestdatajson else None + linkedrequests = requestdatajson["linkedRequests"] if 'linkedRequests' in requestdatajson else None + requirespayment = rawrequestservice.doesrequirepayment(requestdatajson) if sourceofsubmission == "onlineform" else False + if axisrequestid is None or isaxisrequestidpresent == False: + result = FOIRawRequest.saverawrequest( + _requestrawdata=requestdatajson, + sourceofsubmission= sourceofsubmission, + ispiiredacted=ispiiredacted, + userid= userid, + assigneegroup=assigneegroup, + assignee=assignee, + requirespayment=requirespayment, + notes=notes, + assigneefirstname=assigneefirstname, + assigneemiddlename=assigneemiddlename, + assigneelastname=assigneelastname, + axisrequestid=axisrequestid, + axissyncdate=axissyncdate, + linkedrequests=linkedrequests + ) + else: + raise ValueError("Duplicate AXIS Request ID") + if result.success: + redispubservice = RedisPublisherService() + data = {} + data['id'] = result.identifier + data['assignedGroup'] = assigneegroup + data['assignedTo'] = assignee + json_data = json.dumps(data) + try: + workflowservice().createinstance(redispubservice.foirequestqueueredischannel, json_data) + except Exception as ex: + logging.error(ex) + asyncio.ensure_future(redispubservice.publishrequest(json_data)) + return result + + @staticmethod + def doesrequirepayment(requestdatajson): + if 'requestType' not in requestdatajson or 'requestType' not in requestdatajson['requestType']: + raise BusinessException(Error.DATA_NOT_FOUND) + if requestdatajson['requestType']['requestType'] == "personal": + return False + if 'contactInfo' in requestdatajson: + if requestdatajson['requestType']['requestType'] == "general": + if 'IGE' in requestdatajson['contactInfo'] and requestdatajson['contactInfo']['IGE']: + return False + return True + elif requestdatajson['requestType']['requestType'] == "personal": + return False + else: + if 'requiresPayment' not in requestdatajson: + raise BusinessException(Error.DATA_NOT_FOUND) + return requestdatajson['requiresPayment'] + raise BusinessException(Error.DATA_NOT_FOUND) + + def saverawrequestversion(self, _requestdatajson, _requestid, _assigneegroup, _assignee, status, userid, assigneefirstname, assigneemiddlename, assigneelastname, statuslabel, actiontype=None): + ispiiredacted = _requestdatajson["ispiiredacted"] if 'ispiiredacted' in _requestdatajson else False + #Get documents + if actiontype == "assignee": + result = FOIRawRequest.saverawrequestassigneeversion(_requestid, _assigneegroup, _assignee, userid, assigneefirstname, assigneemiddlename, assigneelastname) + else: + result = FOIRawRequest.saverawrequestversion(_requestdatajson, _requestid, _assigneegroup, _assignee, status,ispiiredacted, userid, statuslabel, assigneefirstname, assigneemiddlename, assigneelastname) + documentservice().createrawrequestdocumentversion(_requestid) + return result + + def saverawrequestiaorestricted(self,_requestid,_iaorestricted,_updatedby): + result = FOIRawRequest.saveiaorestrictedrawrequest(_requestid,_iaorestricted,_updatedby) + return result + + def updateworkflowinstance(self, wfinstanceid, requestid, userid): + return FOIRawRequest.updateworkflowinstance(wfinstanceid, requestid, userid) + + def updateworkflowinstancewithstatus(self, wfinstanceid, requestid,notes, userid): + return FOIRawRequest.updateworkflowinstancewithstatus(wfinstanceid,requestid,notes, userid) + + def posteventtoworkflow(self, id, requestsschema, status): + pid = workflowservice().syncwfinstance("rawrequest", id) + return workflowservice().postunopenedevent(id, pid, requestsschema, status) + + def getrawrequests(self): + return rawrequestservicegetter().getallrawrequests() + + def getrawrequest(self, requestid): + return rawrequestservicegetter().getrawrequestforid(requestid) + + def getrawrequestfields(self, requestid, fields): + return rawrequestservicegetter().getrawrequestfieldsforid(requestid, fields) + + def getstatus(self, foirequest): + statuslabel = foirequest["requeststatuslabel"] if "requeststatuslabel" in foirequest else None + if statuslabel is not None: + try: + return requestserviceconfigurator().getstatusname(statuslabel), statuslabel + # if statusid== 4: + # return 'Redirect' + # if statusid == 3: + # return 'Closed' + # if statusid == 16: + # return 'Peer Review' + except KeyError: + print("Key Error on requeststatusid, ignore will be intake in Progress") + return StateName.intakeinprogress.value, StateName.intakeinprogress.name + + def getaxisequestids(self): + return rawrequestservicegetter().getaxisequestids() + + def isaxisrequestidpresent(self, axisrequestid): + countofaxisrequestid = rawrequestservicegetter().getcountofaxisequestidbyaxisequestid(axisrequestid) + if countofaxisrequestid > 0: + return True + return False + + def israwrequestwatcher(self,requestid, userid): + return FOIRawRequestWatcher.isawatcher(requestid,userid) \ No newline at end of file diff --git a/historical-search-api/request_api/services/receivedmodeservice.py b/historical-search-api/request_api/services/receivedmodeservice.py new file mode 100644 index 000000000..d425000a6 --- /dev/null +++ b/historical-search-api/request_api/services/receivedmodeservice.py @@ -0,0 +1,8 @@ +from request_api.models.ReceivedModes import ReceivedMode + +class receivedmodeservice: + + def getreceivedmodes(self): + """ Returns the active records + """ + return ReceivedMode.getreceivedmodes() \ No newline at end of file diff --git a/historical-search-api/request_api/services/records/recordservicebase.py b/historical-search-api/request_api/services/records/recordservicebase.py new file mode 100644 index 000000000..400dd8e6e --- /dev/null +++ b/historical-search-api/request_api/services/records/recordservicebase.py @@ -0,0 +1,45 @@ +from os import stat, path,getenv +from re import VERBOSE +from request_api.utils.constants import FILE_CONVERSION_FILE_TYPES, DEDUPE_FILE_TYPES, NONREDACTABLE_FILE_TYPES +from request_api.models.FOIRequestRecords import FOIRequestRecord +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from request_api.models.FOIMinistryRequestDivisions import FOIMinistryRequestDivision +from request_api.services.external.eventqueueservice import eventqueueservice +from request_api.models.default_method_result import DefaultMethodResult +from request_api.auth import auth, AuthHelper +import json +from datetime import datetime +import maya +import uuid +import requests +import logging + +class recordservicebase: + """ This class consolidates retrival of FOI Records for actors: iao and ministry. + """ + docreviewerapiurl = getenv("FOI_DOCREVIEWER_BASE_API_URL") + docreviewerapitimeout = getenv("FOI_DOCREVIEWER_BASE_API_TIMEOUT") + + def makedocreviewerrequest(self, method, url, payload=None): + token = AuthHelper.getauthtoken() + try: + response = requests.request( + method=method, + url=self.docreviewerapiurl+url, + data=json.dumps(payload), + headers={'Authorization': token, 'Content-Type': 'application/json'}, + timeout=float(self.docreviewerapitimeout) + ) + response.raise_for_status() + return response.json(), None + except requests.exceptions.HTTPError as err: + logging.error("Doc Reviewer API returned the following message: {0} - {1}".format(err.response.status_code, err.response.text)) + return None, err + except requests.exceptions.RequestException as err: + logging.error(err) + return None, err + + def isvalid(self, property, dict): + if property in dict and dict[property] is not None: + return True + return False \ No newline at end of file diff --git a/historical-search-api/request_api/services/records/recordservicegetter.py b/historical-search-api/request_api/services/records/recordservicegetter.py new file mode 100644 index 000000000..c6997efdd --- /dev/null +++ b/historical-search-api/request_api/services/records/recordservicegetter.py @@ -0,0 +1,272 @@ +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 +import json +from datetime import datetime +import maya +import uuid +import logging +import copy +from request_api.services.records.recordservicebase import recordservicebase + + +class recordservicegetter(recordservicebase): + """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: + _metadata = FOIMinistryRequest.getmetadata(ministryrequestid) + divisions = ProgramAreaDivision.getallprogramareatags(_metadata["programareaid"]) + + uploadedrecords = FOIRequestRecord.fetch(requestid, ministryrequestid) + batchids = [] + resultrecords = [] + if len(uploadedrecords) > 0: + computingresponses, err = self.getdatafromdocreviewer(uploadedrecords, 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): + 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 + result["dedupedfiles"] = _dedupedfiles + result["removedfiles"] = _removedfiles + result["batchcount"] = len(batchids) + result["records"] = resultrecords + except Exception as exp: + print("ERROR Happened while fetching records :.{0}".format(exp)) + logging.info(exp) + raise exp + return result + + def getdatafromdocreviewer(self, uploadedrecords, ministryrequestid): + if len(uploadedrecords) > 0: + computingresponses, err = self.makedocreviewerrequest( + "GET", "/api/dedupestatus/{0}".format(ministryrequestid) + ) + return computingresponses, err + + 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["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"] + 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["isconverted"] = self.__getisconverted(attachment) + _computingresponse_err = self.__getcomputingerror(attachment) + if _computingresponse_err is not None: + _attachement["failed"] = _computingresponse_err + _record["attachments"].append(_attachement) + else: + _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", []) + for division in attribute_divisions: + _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"]), + ) + 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 + + def __mergeduplicatedivisions( + self, _resultrecords, duplicatemasterid, resultmasterid, divisions + ): + for entry in _resultrecords: + if "documentmasterid" in entry and int(entry["documentmasterid"]) == int( + duplicatemasterid + ): + entattributes = entry["attributes"] + 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): + attattributes = attachment["attributes"] + 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", []) + + def __mergedivisions(self, _attributes, divisions, resultmasterid=False): + srcdivisions = self.__getrecorddivisions(_attributes) + merged = srcdivisions + for entry in divisions: + isduplicate = False + if resultmasterid: + entry["duplicatemasterid"] = resultmasterid + for srcdivision in srcdivisions: + if entry["divisionid"] == srcdivision["divisionid"]: + isduplicate = True + if isduplicate == False: + merged.append(entry) + return merged + + 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"] + ] + return filtered_response[0] if len(filtered_response) > 0 else [] + elif filterby == "parentid": + filtered_response = [x for x in response if x["isattachment"] == True] + return self.__getattachments(filtered_response, [], data) + else: + logging.info("not matched") + + 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"] + ) + 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 + ): + result.append(entry) + else: + filtered.append(entry) + 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 + + def __getcomputingsummary(self, computingresponse): + _convertedfiles = _dedupedfiles = _removedfiles = 0 + for entry in computingresponse: + if entry["conversionstatus"] == "completed": + _convertedfiles += 1 + if entry["deduplicationstatus"] == "completed": + _dedupedfiles += 1 + if entry["isduplicate"] == True: + _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") + return record + + def __getdivisionname(self, divisions, divisionid): + for division in divisions: + if division["divisionid"] == divisionid: + return division["name"] + return None diff --git a/historical-search-api/request_api/services/recordservice.py b/historical-search-api/request_api/services/recordservice.py new file mode 100644 index 000000000..887669f53 --- /dev/null +++ b/historical-search-api/request_api/services/recordservice.py @@ -0,0 +1,344 @@ + +from os import stat, path,getenv +from re import VERBOSE +from request_api.utils.constants import FILE_CONVERSION_FILE_TYPES, DEDUPE_FILE_TYPES, NONREDACTABLE_FILE_TYPES +from request_api.models.FOIRequestRecords import FOIRequestRecord +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from request_api.models.FOIMinistryRequestDivisions import FOIMinistryRequestDivision +from request_api.services.external.eventqueueservice import eventqueueservice +from request_api.models.default_method_result import DefaultMethodResult +from request_api.auth import auth, AuthHelper +import json +from datetime import datetime +import maya +import uuid +import requests +import logging +from request_api.services.records.recordservicegetter import recordservicegetter +from request_api.services.records.recordservicebase import recordservicebase + +class recordservice(recordservicebase): + """ FOI record management service + """ + conversionstreamkey = getenv('EVENT_QUEUE_CONVERSION_STREAMKEY') + largefileconversionstreamkey = getenv('EVENT_QUEUE_CONVERSION_LARGE_FILE_STREAM_KEY') + dedupestreamkey = getenv('EVENT_QUEUE_DEDUPE_STREAMKEY') + largefilededupestreamkey = getenv('EVENT_QUEUE_DEDUPE_LARGE_FILE_STREAMKEY') + pdfstitchstreamkey = getenv('EVENT_QUEUE_PDFSTITCH_STREAMKEY') + dedupelargefilesizelimit= getenv('DEDUPE_STREAM_SEPARATION_FILE_SIZE_LIMIT',104857600) + conversionlargefilesizelimit= getenv('CONVERSION_STREAM_SEPARATION_FILE_SIZE_LIMIT',3145728) + stitchinglargefilesizelimit= getenv('STITCHING_STREAM_SEPARATION_FILE_SIZE_LIMIT',524288000) + pdfstitchstreamkey_largefiles = getenv('EVENT_QUEUE_PDFSTITCH_LARGE_FILE_STREAMKEY') + pagecalculatorstreamkey = getenv('EVENT_QUEUE_PAGECALCULATOR_STREAM_KEY') + + def create(self, requestid, ministryrequestid, recordschema, userid): + """Creates a record for a user with document details passed in for an opened request. + """ + return self.__bulkcreate(requestid, ministryrequestid, recordschema.get("records"), userid) + + def fetch(self, 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 = [] + recordids = [r['recordid'] for r in requestdata['records'] if r.get('recordid') is not None] + response = DefaultMethodResult(True, 'No recordids') + if(len(recordids) > 0): + records = FOIRequestRecord.getrecordsbyid(recordids) + for record in records: + record['attributes'] = json.loads(record['attributes']) + if not requestdata['isdelete']: + record['attributes']['divisions'] = requestdata['divisions'] + record.update({'updated_at': datetime.now(), 'updatedby': userid, 'isactive': not requestdata['isdelete']}) + record['version'] += 1 + newrecord = FOIRequestRecord() + newrecord.__dict__.update(record) + newrecords.append(newrecord) + response = FOIRequestRecord.create(newrecords) + if response.success: + if requestdata['isdelete']: + _apiresponse, err = self.makedocreviewerrequest('POST', '/api/document/delete', {'ministryrequestid': ministryrequestid, 'filepaths': [record['filepath'] for record in requestdata['records']]}) + else: + _apiresponse, err = self.makedocreviewerrequest('POST', '/api/document/update', {'ministryrequestid': ministryrequestid, 'documentmasterids': [record['documentmasterid'] for record in requestdata['records']], 'divisions': requestdata['divisions']}) + if err: + return DefaultMethodResult(False,'Error in contacting Doc Reviewer API', -1, [record['documentmasterid'] for record in requestdata['records']]) + return DefaultMethodResult(True,'Record updated in Doc Reviewer DB', -1, [record['documentmasterid'] for record in requestdata['records']]) + else: + return DefaultMethodResult(False,'Error in updating Record', -1, [record['documentmasterid'] for record in requestdata['records']]) + + def retry(self, _requestid, ministryrequestid, data): + _ministryrequest = FOIMinistryRequest.getrequestbyministryrequestid(ministryrequestid) + for record in data['records']: + _filepath, extension = path.splitext(record['s3uripath']) + extension = extension.lower() + if record['service'] == 'deduplication': + if extension not in DEDUPE_FILE_TYPES: + return DefaultMethodResult(False,'Dedupe only accepts the following formats: ' + ', '.join(DEDUPE_FILE_TYPES), -1, record['recordid']) + else: + streamkey = self.dedupestreamkey + elif record['service'] == 'conversion': + if extension not in FILE_CONVERSION_FILE_TYPES: + return DefaultMethodResult(False,'File Conversion only accepts the following formats: ' + ', '.join(FILE_CONVERSION_FILE_TYPES), -1, record['recordid']) + else: + streamkey = self.conversionstreamkey + else: + streamkey = self.dedupestreamkey if extension in DEDUPE_FILE_TYPES else self.conversionstreamkey + jobids, err = self.makedocreviewerrequest('POST', '/api/jobstatus', { + 'records': [record], + 'batch': record['attributes']['batch'], + 'trigger': record['trigger'], + 'ministryrequestid': ministryrequestid + }) + if err: + return DefaultMethodResult(False,'Error in contacting Doc Reviewer API', -1, ministryrequestid) + streamobject = { + "s3filepath": record['s3uripath'], + "requestnumber": _ministryrequest['axisrequestid'], + "bcgovcode": _ministryrequest['programarea.bcgovcode'], + "filename": record['filename'], + "ministryrequestid": ministryrequestid, + "attributes": json.dumps(record['attributes']), + "batch": record['attributes']['batch'], + "jobid": jobids[record['s3uripath']]['jobid'], + "documentmasterid": jobids[record['s3uripath']]['masterid'], + "trigger": record['trigger'], + "createdby": record['createdby'], + "usertoken": AuthHelper.getauthtoken() + } + if record.get('outputdocumentmasterid', False): + streamobject['outputdocumentmasterid'] = record['outputdocumentmasterid'] + if record['trigger'] == 'recordreplace' and record['attributes']['isattachment'] == True: + streamobject['originaldocumentmasterid'] = record['documentmasterid'] + return eventqueueservice().add(streamkey, streamobject) + + def replace(self, _requestid, ministryrequestid, recordid, recordschema, userid): + _ministryrequest = FOIMinistryRequest.getrequestbyministryrequestid(ministryrequestid) + _ministryversion = FOIMinistryRequest.getversionforrequest(ministryrequestid) + recordlist = [] + records = recordschema.get("records") + for _record in records: + replacingrecord = FOIRequestRecord.getrecordbyid(recordid) + _delteeapiresponse, err = self.makedocreviewerrequest('POST', '/api/document/delete', {'ministryrequestid': ministryrequestid, 'filepaths': [replacingrecord['s3uripath']]}) + + if err: + return DefaultMethodResult(False,'Error in contacting Doc Reviewer API', -1, recordid) + record = FOIRequestRecord(foirequestid=_requestid, replacementof = recordid if _record['replacementof'] is None else _record['replacementof'], ministryrequestid = ministryrequestid, ministryrequestversion=_ministryversion, + version = 1, createdby = userid, created_at = datetime.now()) + batch = str(uuid.uuid4()) + _record['attributes']['batch'] = batch + _record['attributes']['lastmodified'] = json.loads(replacingrecord['attributes'])['lastmodified'] + _filepath, extension = path.splitext(_record['filename']) + _record['attributes']['extension'] = extension + _record['attributes']['incompatible'] = extension.lower() in NONREDACTABLE_FILE_TYPES + record.__dict__.update(_record) + recordlist.append(record) + dbresponse = FOIRequestRecord.replace(recordid,recordlist) + if (dbresponse.success): + processingrecords = [{**record, **{"recordid": dbresponse.args[0][record['s3uripath']]['recordid']}} for record in records] + # record all jobs before sending first redis stream message to avoid race condition + jobids, err = self.makedocreviewerrequest('POST', '/api/jobstatus', { + 'records': processingrecords, + 'batch': batch, + 'trigger': 'recordupload', + 'ministryrequestid': ministryrequestid + }) + if err: + return DefaultMethodResult(False,'Error in contacting Doc Reviewer API', -1, ministryrequestid) + # send message to redis stream for each file + for entry in processingrecords: + _filename, extension = path.splitext(entry['s3uripath']) + extension = extension.lower() + if 'error' in jobids[entry['s3uripath']]: + logging.error("Doc Reviewer API was given an unsupported file type - no job triggered - Record ID: {0} File Name: {1} ".format(entry['recordid'], entry['filename'])) + else: + streamobject = { + "s3filepath": entry['s3uripath'], + "requestnumber": _ministryrequest['axisrequestid'], + "bcgovcode": _ministryrequest['programarea.bcgovcode'], + "filename": entry['filename'], + "ministryrequestid": ministryrequestid, + "attributes": json.dumps(entry['attributes']), + "batch": batch, + "jobid": jobids[entry['s3uripath']]['jobid'], + "documentmasterid": jobids[entry['s3uripath']]['masterid'], + "trigger": 'recordupload', + "createdby": userid, + "incompatible": 'true' if extension in NONREDACTABLE_FILE_TYPES else 'false', + "usertoken": AuthHelper.getauthtoken() + } + if extension in FILE_CONVERSION_FILE_TYPES: + eventqueueservice().add(self.conversionstreamkey, streamobject) + if extension in DEDUPE_FILE_TYPES: + eventqueueservice().add(self.dedupestreamkey, streamobject) + return dbresponse + + def triggerpdfstitchservice(self, requestid, ministryrequestid, recordschema, userid): + """Calls the BE job for stitching the documents. + """ + return self.__triggerpdfstitchservice(requestid, ministryrequestid, recordschema, userid) + + def getpdfstitchpackagetodownload(self, ministryid, category): + response, err = self.makedocreviewerrequest('GET', '/api/pdfstitch/{0}/{1}'.format(ministryid, category)) + if response is not None and "createdat" in response: + string_datetime = maya.parse(response["createdat"]).datetime(to_timezone='America/Vancouver', naive=False).strftime('%Y %b %d | %I:%M %p').upper() + response["createdat_datetime"] = string_datetime + return response + + def getpdfstichstatus(self, ministryid, category): + response, err = self.makedocreviewerrequest('GET', '/api/pdfstitchjobstatus/{0}/{1}'.format(ministryid, category)) + if response is not None and "status" in response: + return response.get("status") + return "" + + def isrecordschanged(self, ministryid, category): + response, err = self.makedocreviewerrequest('GET', '/api/recordschanged/{0}/{1}'.format(ministryid, category)) + if response is None: + return {"recordchanged": False} + return response + + def __triggerpdfstitchservice(self, requestid, ministryrequestid, message, userid): + """Call the BE job for stitching the documents. + """ + if self.pdfstitchstreamkey_largefiles or self.pdfstitchstreamkey: + job, err = self.makedocreviewerrequest('POST', '/api/pdfstitchjobstatus', { + "createdby": userid, + "ministryrequestid": ministryrequestid, + "inputfiles":message["attributes"], + "category": message["category"] + }) + if err: + return DefaultMethodResult(False,'Error in contacting Doc Reviewer API', -1, ministryrequestid) + streamobject = { + "jobid": job.get("id"), + "category": message["category"], + "requestnumber": message["requestnumber"], + "bcgovcode": message["bcgovcode"], + "createdby": userid, + "requestid": requestid, + "ministryrequestid": ministryrequestid, + "attributes": json.JSONEncoder().encode(message["attributes"]), + "totalfilesize": message["totalfilesize"] + } + if message["totalfilesize"] > int(self.stitchinglargefilesizelimit) and self.pdfstitchstreamkey_largefiles: + return eventqueueservice().add(self.pdfstitchstreamkey_largefiles, streamobject) + elif self.pdfstitchstreamkey: + return eventqueueservice().add(self.pdfstitchstreamkey, streamobject) + else: + print("pdfstitch stream key is missing. Message is not pushed to the stream.") + return DefaultMethodResult(False,'pdfstitch stream key is missing. Message is not pushed to the stream.', -1, ministryrequestid) + + def __bulkcreate(self, requestid, ministryrequestid, records, userid): + """Creates bulk records for a user with document details passed in for an opened request. + """ + _ministryversion = FOIMinistryRequest.getversionforrequest(ministryrequestid) + _ministryrequest = FOIMinistryRequest.getrequestbyministryrequestid(ministryrequestid) + recordlist = [] + batch = str(uuid.uuid4()) + for entry in records: + entry['attributes']['batch'] = batch + _filepath, extension = path.splitext(entry['filename']) + entry['attributes']['extension'] = extension + entry['attributes']['incompatible'] = extension.lower() in NONREDACTABLE_FILE_TYPES + record = FOIRequestRecord(foirequestid=_ministryrequest['foirequest_id'], ministryrequestid = ministryrequestid, ministryrequestversion=_ministryversion, + version = 1, createdby = userid, created_at = datetime.now()) + record.__dict__.update(entry) + recordlist.append(record) + dbresponse = FOIRequestRecord.create(recordlist) + if (dbresponse.success): + #processingrecords = [{**record, **{"recordid": dbresponse.args[0][record['s3uripath']]['recordid']}} for record in records if not record['attributes'].get('incompatible', False)] + processingrecords = [{**record, **{"recordid": dbresponse.args[0][record['s3uripath']]['recordid']}} for record in records] + + # record all jobs before sending first redis stream message to avoid race condition + jobids, err = self.makedocreviewerrequest('POST', '/api/jobstatus', { + 'records': processingrecords, + 'batch': batch, + 'trigger': 'recordupload', + 'ministryrequestid': ministryrequestid + }) + if err: + return DefaultMethodResult(False,'Error in contacting Doc Reviewer API', -1, ministryrequestid) + # send message to redis stream for each file + for entry in processingrecords: + _filename, extension = path.splitext(entry['s3uripath']) + extension = extension.lower() + if 'error' in jobids[entry['s3uripath']]: + logging.error("Doc Reviewer API was given an unsupported file type - no job triggered - Record ID: {0} File Name: {1} ".format(entry['recordid'], entry['filename'])) + else: + streamobject = { + "s3filepath": entry['s3uripath'], + "requestnumber": _ministryrequest['axisrequestid'], + "bcgovcode": _ministryrequest['programarea.bcgovcode'], + "filename": entry['filename'], + "ministryrequestid": ministryrequestid, + "attributes": json.dumps(entry['attributes']), + "batch": batch, + "jobid": jobids[entry['s3uripath']]['jobid'], + "documentmasterid": jobids[entry['s3uripath']]['masterid'], + "trigger": 'recordupload', + "createdby": userid, + "incompatible": 'true' if extension in NONREDACTABLE_FILE_TYPES else 'false', + "usertoken": AuthHelper.getauthtoken() + } + if extension in FILE_CONVERSION_FILE_TYPES: + if entry['attributes']['filesize'] < int(self.conversionlargefilesizelimit): + assignedstreamkey =self.conversionstreamkey + else: + assignedstreamkey =self.largefileconversionstreamkey + eventqueueservice().add(assignedstreamkey, streamobject) + if extension in DEDUPE_FILE_TYPES: + if 'convertedfilesize' in entry['attributes'] and entry['attributes']['convertedfilesize'] < int(self.dedupelargefilesizelimit) or 'convertedfilesize' not in entry['attributes'] and entry['attributes']['filesize'] < int(self.dedupelargefilesizelimit): + assignedstreamkey= self.dedupestreamkey + else: + assignedstreamkey= self.largefilededupestreamkey + eventqueueservice().add(assignedstreamkey, streamobject) + return dbresponse + + # this is for inflight request pagecount calculation option 1 + async def updatepagecount(self, ministryrequestid, userid): + streamobj = { + 'ministryrequestid': ministryrequestid + } + job, err = self.makedocreviewerrequest('POST', '/api/pagecalculatorjobstatus', streamobj) + if err: + return DefaultMethodResult(False,'Error in contacting Doc Reviewer API for pagecalculatorjobstatus', -1, ministryrequestid) + else: + streamobj["jobid"] = job.get("id") + streamobj["createdby"] = userid + eventqueueservice().add(self.pagecalculatorstreamkey, streamobj) + return DefaultMethodResult(True,'Pushed to PageCountCalculator stream', job.get("id"), ministryrequestid) + + # this is for inflight request pagecount calculation option 2 + async def calculatepagecount(self, requestid, ministryrequestid, userid): + uploadedrecords = FOIRequestRecord.fetch(requestid, ministryrequestid) + if len(uploadedrecords) > 0: + records, err = recordservicegetter().getdatafromdocreviewer(uploadedrecords, ministryrequestid) + if err is None: + pagecount = self.__calculatepagecount(records) + return FOIMinistryRequest().updaterecordspagecount(ministryrequestid, pagecount, userid) + return DefaultMethodResult(True,'No request to update', ministryrequestid) + + # this is for inflight request pagecount calculation option 2 + def __calculatepagecount(self, records): + print(f'records = {records}') + page_count = 0 + for record in records: + if self.__pagecountcalculationneeded(record): + page_count += record.get("pagecount", 0) + attachments = record.get("attachments", []) + for attachment in attachments: + if not attachment.get("isduplicate", False): + page_count += attachment.get("pagecount", 0) + return page_count + + def __pagecountcalculationneeded(self, record): + if not record.get("isduplicate", False) and not record["attributes"].get("isportfolio", False) and not record['attributes'].get('incompatible', False): + return True + return False + + + + + diff --git a/historical-search-api/request_api/services/requestservice.py b/historical-search-api/request_api/services/requestservice.py new file mode 100644 index 000000000..2bc99921a --- /dev/null +++ b/historical-search-api/request_api/services/requestservice.py @@ -0,0 +1,333 @@ +from re import T + +from request_api.services.documentservice import documentservice +from request_api.services.workflowservice import workflowservice +from request_api.services.watcherservice import watcherservice +from request_api.services.commentservice import commentservice +from request_api.services.paymentservice import paymentservice +from request_api.services.foirequest.requestserviceconfigurator import ( + requestserviceconfigurator, +) +from request_api.services.foirequest.requestservicegetter import requestservicegetter +from request_api.services.foirequest.requestservicecreate import requestservicecreate +from request_api.services.foirequest.requestserviceupdate import requestserviceupdate +from request_api.services.applicantcorrespondence.applicantcorrespondencelog import ( + applicantcorrespondenceservice, +) +from request_api.services.subjectcodeservice import subjectcodeservice +from request_api.models.FOIRequestStatus import FOIRequestStatus +from request_api.models.FOIRawRequests import FOIRawRequest +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from request_api.models.FOIMinistryRequestSubjectCodes import ( + FOIMinistryRequestSubjectCode, +) +from request_api.models.SubjectCodes import SubjectCode +from request_api.models.FOIRestrictedMinistryRequests import ( + FOIRestrictedMinistryRequest, +) +from request_api.utils.enums import StateName +from request_api.services.commons.duecalculator import duecalculator +from request_api.utils.commons.datetimehandler import datetimehandler +import os +import json + + +class requestservice: + """FOI Request management service + + This service class manages all CRUD operations related to an FOI opened Request + + """ + + def saverequest( + self, + foirequestschema, + userid, + foirequestid=None, + ministryid=None, + filenumber=None, + version=None, + rawrequestid=None, + wfinstanceid=None, + ): + return requestservicecreate().saverequest( + foirequestschema, + userid, + foirequestid, + ministryid, + filenumber, + version, + rawrequestid, + wfinstanceid, + ) + + def saverequestversion(self, foirequestschema, foirequestid, ministryid, userid): + nextstate = FOIRequestStatus.getrequeststatusbylabel( + foirequestschema["requeststatuslabel"] + ) + nextstatename = ( + nextstate.get("name") + if isinstance(nextstate, dict) and nextstate.get("name") not in (None, "") + else "" + ) + rev_foirequestschema = self.updateduedate( + foirequestid, + ministryid, + datetimehandler().gettoday(), + foirequestschema, + nextstatename, + ) + responseschema = requestservicecreate().saverequestversion( + rev_foirequestschema, foirequestid, ministryid, userid + ) + if "paymentExpiryDate" in foirequestschema and foirequestschema[ + "paymentExpiryDate" + ] not in (None, ""): + paymentservice().createpayment( + foirequestid, ministryid, foirequestschema, userid + ) + return responseschema + + def saveministryrequestversion( + self, ministryrequestschema, foirequestid, ministryid, userid, usertype=None + ): + return requestservicecreate().saveministryrequestversion( + ministryrequestschema, foirequestid, ministryid, userid, usertype + ) + + def updaterequest(self, foirequestschema, foirequestid, userid): + return requestserviceupdate().updaterequest( + foirequestschema, foirequestid, userid + ) + + def updateministryrequestduedate(self, ministryrequestid, duedate, userid): + return requestserviceupdate().updateministryrequestduedate( + ministryrequestid, duedate, userid + ) + + def postpaymentstatetransition( + self, requestid, ministryrequestid, nextstatename, paymentdate + ): + _foirequest = self.getrequest(requestid, ministryrequestid) + foirequest = self.updateduedate( + requestid, ministryrequestid, paymentdate, _foirequest, nextstatename + ) + status = FOIRequestStatus().getrequeststatus(nextstatename) + foirequest["requeststatuslabel"] = status["statuslabel"] + return requestservicecreate().saverequestversion( + foirequest, requestid, ministryrequestid, "Online Payment" + ) + + def updateduedate( + self, requestid, ministryrequestid, offholddate, foirequestschema, nextstatename + ): + foirequest = self.getrequest(requestid, ministryrequestid) + currentstatus = ( + foirequest["stateTransition"][0]["status"] + if "stateTransition" in foirequest + and len(foirequest["stateTransition"]) > 1 + else None + ) + # Check for Off Hold + if ( + currentstatus not in (None, "") + and currentstatus == StateName.onhold.value + and nextstatename != StateName.response.value + ): + skipcalculation = self.__skipduedatecalculation( + ministryrequestid, offholddate, currentstatus, nextstatename + ) + # Skip multiple off hold in a day + if skipcalculation == True: + calc_duedate, calc_cfrduedate = ( + foirequest["dueDate"], + foirequest["cfrDueDate"], + ) + else: + calc_duedate, calc_cfrduedate = self.calculateduedate( + ministryrequestid, foirequest, offholddate + ) + foirequestschema["dueDate"] = calc_duedate + foirequestschema["cfrDueDate"] = calc_cfrduedate + return foirequestschema + + def getrequest(self, foirequestid, foiministryrequestid): + return requestservicegetter().getrequest(foirequestid, foiministryrequestid) + + def getrequestdetailsforministry( + self, foirequestid, foiministryrequestid, authmembershipgroups + ): + return requestservicegetter().getrequestdetailsforministry( + foirequestid, foiministryrequestid, authmembershipgroups + ) + + def getrequestdetails(self, foirequestid, foiministryrequestid): + return requestservicegetter().getrequestdetails( + foirequestid, foiministryrequestid + ) + + def getrequestid(self, foiministryrequestid): + return FOIMinistryRequest.getrequest(foiministryrequestid)["foirequest_id"] + + def copywatchers(self, rawrequestid, ministries, userid): + watchers = watcherservice().getrawrequestwatchers(int(rawrequestid)) + for ministry in ministries: + for watcher in watchers: + watcherschema = { + "ministryrequestid": ministry["id"], + "watchedbygroup": watcher["watchedbygroup"], + "watchedby": watcher["watchedby"], + "isactive": True, + } + watcherservice().createministryrequestwatcher( + watcherschema, userid, None + ) + + def copycomments(self, rawrequestid, ministries, userid): + comments = commentservice().getrawrequestcomments(int(rawrequestid)) + for ministry in ministries: + commentservice().copyrequestcomment(ministry["id"], comments, userid) + + def copydocuments(self, rawrequestid, ministries, userid): + attachments = documentservice().getrequestdocuments( + int(rawrequestid), "rawrequest" + ) + for ministry in ministries: + documentservice().copyrequestdocuments(ministry["id"], attachments, userid) + + def copysubjectcode(self, subjectcode, ministries, userid): + if subjectcode: + for ministry in ministries: + subjectcodeservice().savesubjectcode( + ministry["id"], subjectcode, userid + ) + + def postopeneventtoworkflow(self, id, requestschema, ministries): + pid = workflowservice().syncwfinstance("rawrequest", requestschema["id"]) + workflowservice().postunopenedevent(id, pid, requestschema, StateName.open.value, ministries) + + def postfeeeventtoworkflow( + self, requestid, ministryrequestid, paymentstatus, nextstatename=None + ): + foirequestschema = self.getrequestdetails(requestid, ministryrequestid) + workflowservice().postfeeevent( + requestid, ministryrequestid, foirequestschema, paymentstatus, nextstatename + ) + + def posteventtoworkflow(self, id, requestschema, data, usertype): + requeststatuslabel = ( + requestschema.get("requeststatuslabel") + if "requeststatuslabel" in requestschema + else None + ) + status = ( + requestserviceconfigurator().getstatusname(requeststatuslabel) + if requeststatuslabel is not None + else None + ) + pid = workflowservice().syncwfinstance("ministryrequest", id) + workflowservice().postopenedevent( + id, pid, requestschema, data, status, usertype + ) + + def postcorrespondenceeventtoworkflow( + self, + requestid, + ministryrequestid, + applicantcorrespondenceid, + attributes, + templateid, + ): + foirequestschema = self.getrequestdetails(requestid, ministryrequestid) + templatedetails = applicantcorrespondenceservice().gettemplatebyid(templateid) + wfinstanceid = workflowservice().syncwfinstance( + "ministryrequest", ministryrequestid, True + ) + workflowservice().postcorrenspodenceevent( + wfinstanceid, + ministryrequestid, + foirequestschema, + applicantcorrespondenceid, + templatedetails.name, + attributes, + ) + + + + def calculateduedate(self, ministryrequestid, foirequest, paymentdate): + duedate_includeoffhold, cfrduedate_includeoffhold = self.__isincludeoffhold() + onhold_extend_days = duecalculator().getbusinessdaysbetween(foirequest["onholdTransitionDate"], paymentdate) + isoffhold_businessday = duecalculator().isbusinessday(paymentdate) + duedate_extend_days = onhold_extend_days + 1 if isoffhold_businessday == True and duedate_includeoffhold == True else onhold_extend_days + cfrduedate_extend_days = onhold_extend_days + 1 if isoffhold_businessday == True and cfrduedate_includeoffhold == True else onhold_extend_days + calc_duedate = duecalculator().addbusinessdays(foirequest["dueDate"], duedate_extend_days) + calc_cfrduedate = duecalculator().addbusinessdays(foirequest["cfrDueDate"], cfrduedate_extend_days) + return calc_duedate, calc_cfrduedate + + + + + def __skipduedatecalculation(self, ministryrequestid, offholddate, currentstatus="", nextstatename=""): + previousoffholddate = FOIMinistryRequest.getlastoffholddate(ministryrequestid) + if ( + currentstatus not in (None, "") + and currentstatus == StateName.onhold.value + and nextstatename not in (None, "") + and currentstatus == nextstatename + ): + return True + if previousoffholddate not in (None, ""): + previouspaymentdate_pst = datetimehandler().convert_to_pst( + previousoffholddate + ) + if ( + datetimehandler().getdate(previouspaymentdate_pst).date() + == datetimehandler().getdate(offholddate).date() + ): + return True + foiministry_request = FOIMinistryRequest.getrequest(ministryrequestid) + request_reopened = self.__hasreopened(ministryrequestid, "ministryrequest") + if foiministry_request['isoipcreview'] == True and request_reopened: + return True + return False + + def __isincludeoffhold(self): + payment_config_str = os.getenv("PAYMENT_CONFIG","") + if payment_config_str in (None, ""): + return True, True + _paymentconfig = json.loads(payment_config_str) + duedate_includeoffhold = True if _paymentconfig["duedate"]["includeoffhold"] == "Y" else False + cfrduedate_includeoffhold = True if _paymentconfig["cfrduedate"]["includeoffhold"] == "Y" else False + return duedate_includeoffhold, cfrduedate_includeoffhold + + # intake in progress to open: create a restricted request record for each selected ministries + def createrestrictedrequests(self, ministries, type, isrestricted, userid): + for ministry in ministries: + version = FOIMinistryRequest.getversionforrequest(ministry["id"]) + FOIRestrictedMinistryRequest.disablerestrictedrequests( + ministry["id"], type, userid + ) + FOIRestrictedMinistryRequest.saverestrictedrequest( + ministry["id"], type, isrestricted, version, userid + ) + + def saverestrictedrequest(self, ministryrequestid, type, isrestricted, userid): + version = FOIMinistryRequest.getversionforrequest(ministryrequestid) + FOIRestrictedMinistryRequest.disablerestrictedrequests( + ministryrequestid, type, userid + ) + return FOIRestrictedMinistryRequest.saverestrictedrequest( + ministryrequestid, type, isrestricted, version, userid + ) + + + def __hasreopened(self, requestid, requesttype): + if requesttype == "rawrequest": + states = FOIRawRequest.getstatesummary(requestid) + else: + states = FOIMinistryRequest.getstatesummary(requestid) + if len(states) > 0: + current_state = states[0] + if current_state != "Closed" and any(state['status'] == "Closed" for state in states): + return True + return False diff --git a/historical-search-api/request_api/services/reset.py b/historical-search-api/request_api/services/reset.py new file mode 100644 index 000000000..f43804e09 --- /dev/null +++ b/historical-search-api/request_api/services/reset.py @@ -0,0 +1,32 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Service for reset test data.""" +from typing import Dict + +from request_api.models import User as UserModel +from request_api.models import db +from request_api.services.keycloak import KeycloakService +from request_api.utils.enums import LoginSource +from request_api.utils.roles import Role + + +class ResetTestData: # pylint:disable=too-few-public-methods + """Cleanup all the data from model by created_by column.""" + + def __init__(self): + """Return a reset test data service instance.""" + + + + diff --git a/historical-search-api/request_api/services/rest_service.py b/historical-search-api/request_api/services/rest_service.py new file mode 100644 index 000000000..d19f99e07 --- /dev/null +++ b/historical-search-api/request_api/services/rest_service.py @@ -0,0 +1,183 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Service to invoke Rest services.""" +import json +from collections.abc import Iterable +from typing import Dict + +import requests +from flask import current_app, request +from requests.adapters import HTTPAdapter # pylint:disable=ungrouped-imports +# pylint:disable=ungrouped-imports +from requests.exceptions import ConnectionError as ReqConnectionError +from requests.exceptions import ConnectTimeout, HTTPError +from urllib3.util.retry import Retry + +from auth_api.exceptions import ServiceUnavailableException +from auth_api.utils.enums import AuthHeaderType, ContentType + + +RETRY_ADAPTER = HTTPAdapter(max_retries=Retry(total=5, backoff_factor=1, status_forcelist=[404])) + + +class RestService: + """Service to invoke Rest services which uses OAuth 2.0 implementation.""" + + @staticmethod + def _invoke(rest_method, endpoint, token=None, # pylint: disable=too-many-arguments + auth_header_type: AuthHeaderType = AuthHeaderType.BEARER, + content_type: ContentType = ContentType.JSON, data=None, raise_for_status: bool = True, + additional_headers: dict = None, generate_token: bool = True): + """Invoke different method depending on the input.""" + # just to avoid the duplicate code for PUT and POSt + current_app.logger.debug(f'<_invoke-{rest_method}') + + headers = { + 'Content-Type': content_type.value + } + + if not token and generate_token: + token = _get_token() + + if token: + headers.update({'Authorization': auth_header_type.value.format(token)}) + + if additional_headers: + headers.update(additional_headers) + + if content_type == ContentType.JSON: + data = json.dumps(data) + + current_app.logger.debug('Endpoint : {}'.format(endpoint)) + current_app.logger.debug('headers : {}'.format(headers)) + response = None + try: + invoke_rest_method = getattr(requests, rest_method) + response = invoke_rest_method(endpoint, data=data, headers=headers, + timeout=current_app.config.get('CONNECT_TIMEOUT', 10)) + if raise_for_status: + response.raise_for_status() + except (ReqConnectionError) as exc: + current_app.logger.error('---Error on POST---') + current_app.logger.error(exc) + raise ServiceUnavailableException(exc) from exc + except HTTPError as exc: + current_app.logger.error( + 'HTTPError on POST with status code {}'.format(response.status_code if response else '')) + if response and response.status_code >= 500: + raise ServiceUnavailableException(exc) from exc + raise exc + finally: + RestService.__log_response(response) + + current_app.logger.debug('>post') + return response + + @staticmethod + def __log_response(response): + if response is not None: + current_app.logger.info('Response Headers {}'.format(response.headers)) + if response.headers and isinstance(response.headers, Iterable) and \ + 'Content-Type' in response.headers and \ + response.headers['Content-Type'] == ContentType.JSON.value: + current_app.logger.info('response : {}'.format(response.text if response else '')) + + @staticmethod + def post(endpoint, token=None, # pylint: disable=too-many-arguments + auth_header_type: AuthHeaderType = AuthHeaderType.BEARER, + content_type: ContentType = ContentType.JSON, data=None, raise_for_status: bool = True, + additional_headers: dict = None, generate_token: bool = True): + """POST service.""" + current_app.logger.debug('= 500: + raise ServiceUnavailableException(exc) from exc + raise exc + finally: + current_app.logger.debug(response.headers if response else 'Empty Response Headers') + current_app.logger.info('response : {}'.format(response.text if response else '')) + + current_app.logger.debug('>GET') + return response + + @staticmethod + def get_service_account_token() -> str: + """Generate a service account token.""" + kc_service_id = current_app.config.get('KEYCLOAK_SERVICE_ACCOUNT_ID') + kc_secret = current_app.config.get('KEYCLOAK_SERVICE_ACCOUNT_SECRET') + issuer_url = current_app.config.get('JWT_OIDC_ISSUER') + # https://sso-dev.pathfinder.gov.bc.ca/auth/realms/fcf0kpqr/protocol/openid-connect/token + token_url = issuer_url + '/protocol/openid-connect/token' + auth_response = requests.post(token_url, auth=(kc_service_id, kc_secret), headers={ + 'Content-Type': ContentType.FORM_URL_ENCODED.value}, data='grant_type=client_credentials') + auth_response.raise_for_status() + return auth_response.json().get('access_token') + + +def _get_token() -> str: + token: str = request.headers['Authorization'] if request and 'Authorization' in request.headers else None + return token.replace('Bearer ', '') if token else None diff --git a/historical-search-api/request_api/services/subjectcodeservice.py b/historical-search-api/request_api/services/subjectcodeservice.py new file mode 100644 index 000000000..960e431e2 --- /dev/null +++ b/historical-search-api/request_api/services/subjectcodeservice.py @@ -0,0 +1,50 @@ +from request_api.models.SubjectCodes import SubjectCode +from request_api.models.FOIMinistryRequestSubjectCodes import FOIMinistryRequestSubjectCode +from request_api.models.FOIMinistryRequests import FOIMinistryRequest + +class subjectcodeservice: + + def getsubjectcodes(self): + """ Returns the active records + """ + return SubjectCode.getsubjectcodes() + + def getsubjectcodebyname(self, subjectcode): + """ Returns the subject code + """ + return SubjectCode.getsubjectcodebyname(subjectcode) + + def getsubjectcodebyid(self, subjectcodeid): + """ Returns the subject code + """ + return SubjectCode.getsubjectcodebyid(subjectcodeid) + + def getministrysubjectcode(self, ministryrequestid): + """ Returns the ministry subject code + """ + ministryrequestversion = self.__getministryversionforrequest(ministryrequestid) + return FOIMinistryRequestSubjectCode.getministrysubjectcode(ministryrequestid, ministryrequestversion) + + def savesubjectcode(self, ministryrequestid, subjectcode, userid): + """ Save subject code + """ + ministryrequestversion = self.__getministryversionforrequest(ministryrequestid) + subjectcode = self.getsubjectcodebyname(subjectcode) + return FOIMinistryRequestSubjectCode.savesubjectcode(ministryrequestid, ministryrequestversion, subjectcode['subjectcodeid'], userid) + + def getministrysubjectcodename(self, foiministryrequestid): + """ Returns the ministry subject code name + """ + ministrysubjectcode = self.getministrysubjectcode(foiministryrequestid) + if ministrysubjectcode: + subjectcode = self.getsubjectcodebyid(ministrysubjectcode['subjectcodeid']) + return subjectcode['name'] + else: + return '' + + def __getministryversionforrequest(self, requestid): + """ Returns the active version of the request id based on type. + """ + request = FOIMinistryRequest.getversionforrequest(requestid) + if request: + return request[0] \ No newline at end of file diff --git a/historical-search-api/request_api/services/unopenedreportservice.py b/historical-search-api/request_api/services/unopenedreportservice.py new file mode 100644 index 000000000..61fa1054a --- /dev/null +++ b/historical-search-api/request_api/services/unopenedreportservice.py @@ -0,0 +1,165 @@ + +from request_api.models.FOIRawRequests import FOIRawRequest +from request_api.models.UnopenedReport import UnopenedReport +from request_api.services.email.senderservice import senderservice +from os import getenv +from datetime import timedelta, date +from jaro import jaro_winkler_metric +import json +import logging +from math import inf + +class unopenedreportservice: + """ + This service generates a report of unopened unactioned requests + + """ + + dayscutoff = getenv('UNOPENED_REPORT_CUTOFF_DAYS', 10) + waitdays = getenv('UNOPENED_REPORT_WAIT_DAYS', 5) + jarocutoff = getenv('UNOPENED_REPORT_JARO_CUTOFF', 0.8) + reportemail = getenv('UNOPENED_REPORT_EMAIL_RECIPIENT') + + + async def generateunopenedreport(self): + logging.info("begin unopened report generation") + startdate = date.today() - timedelta(days=int(self.dayscutoff)) + enddate = date.today() - timedelta(days=int(self.waitdays)) + requests = FOIRawRequest.getunopenedunactionedrequests(str(startdate), str(enddate)) + alerts = [] + alertdbrows = [] + for request in requests: + potentialmatches = FOIRawRequest.getpotentialactionedmatches(request) + if len(potentialmatches) == 0: + alert = UnopenedReport() + alert.rawrequestid = request['requestid'] + alert.date = date.today() + alert.rank = 1 + alertdbrows.append(alert) + alerts.append({"request": request, "rank": 1}) + else: + highscore = 0 + for match in potentialmatches: + match['score'] = jaro_winkler_metric( + request['requestrawdata']['descriptionTimeframe']['description'].replace('\n', ' ').replace('\t', ' '), + match['requestrawdata']['description'] + ) + if match['score'] > highscore: + highscore = match['score'] + isCFD = False + for m in request['requestrawdata']['ministry']['selectedMinistry']: + if m['code'] == 'MCF': + isCFD = True + if isCFD and len(potentialmatches) == 1 and potentialmatches[0]['requestrawdata']['description'] == 'CFD': + highscore = 1 + alert = UnopenedReport() + alert.rawrequestid = request['requestid'] + alert.date = date.today() + alert.rank = 2 + alert.potentialmatches = { + "highscore": round(highscore, 2), + "matches": [{ + "requestid": m["requestrawdata"]['axisRequestId'], + "similarity": round(m['score'], 2) + } for m in potentialmatches], + "isCFD": isCFD + } + alertdbrows.append(alert) + alerts.append({"request": request, "rank": 2, "potentialmatches": alert.potentialmatches}) + UnopenedReport.bulkinsert(alertdbrows) + alerts.sort(key=lambda a : a.get('potentialmatches', {'highscore': -1})['highscore']) + senderservice().send( + subject="Intake Unopened Request Report: " + str(date.today()), + content=self.generateemailhtml(alerts), + _messageattachmentlist=[], + requestjson={"email": self.reportemail, "topic": "Unopened Report"} + ) + return alerts + + + def generateemailhtml(self, alerts): + emailhtml = """ +

Unopened Report - """ + str(date.today()) + """

+ +

This is a report for unopened requests in the past """ + self.dayscutoff + """ days that have not yet been actioned.

+

Rank 1: Very likely to be unactioned — unable to find a request with any matching applicant info

+ + + + + + + + + + + + + """ + firstrank2 = True + logging.debug(alerts) + for alert in alerts: + if alert.get('potentialmatches') == None: + emailhtml += ''' + + + + + + + + + + + + ''' + else: + if firstrank2: + emailhtml += """
Unopened IDDate ReceivedMinistry SelectedApplicant First NameApplicant Last NamePayment StatusReceipt NumberApplication FeeDescription
U-00''' + str(alert['request']['requestid']) + '''''' + alert['request']['requestrawdata']['receivedDate'] + '''''' + for m in alert['request']['requestrawdata']['ministry']['selectedMinistry']: + emailhtml += (m['code'] + ' ') + emailhtml += '''''' + alert['request']['requestrawdata']['contactInfo']['firstName'] + '''''' + alert['request']['requestrawdata']['contactInfo']['lastName'] + '''''' + alert['request']['paymentstatus'] + '''''' + alert['request']['txnno'] + '''''' + alert['request']['fee'] + '''''' + alert['request']['requestrawdata']['descriptionTimeframe']['description'][0:99] + '''...
+

Rank 2: Possibly unactioned — requests found but some applicant info is mismatching — please double check

+ + + + + + + + + + + + + + """ + firstrank2 = False + logging.debug(alert) + if alert['potentialmatches']['highscore'] > float(self.jarocutoff): + break + emailhtml += ''' + + + + + + + + + + + + + ''' + logging.debug(emailhtml) + logging.info("finished unopened report generation") + return emailhtml diff --git a/historical-search-api/request_api/services/userservice.py b/historical-search-api/request_api/services/userservice.py new file mode 100644 index 000000000..61e634b54 --- /dev/null +++ b/historical-search-api/request_api/services/userservice.py @@ -0,0 +1,43 @@ + +from os import stat +from re import VERBOSE + +from request_api.models.FOIUsers import FOIUser +from request_api.models.default_method_result import DefaultMethodResult +from request_api.models.OperatingTeams import OperatingTeam +from request_api.services.external.keycloakadminservice import KeycloakAdminService +import logging + +class userservice: + """ FOI user service + + """ + def syncusers(self): + operatingteams = OperatingTeam().getalloperatingteams() + for operatingteam in operatingteams: + groupmembers = KeycloakAdminService().getmembersbygroupnameandtype(operatingteam['name'], operatingteam['type']) + if groupmembers not in (None, '',[]) and len(groupmembers) > 0: + groupdata = groupmembers[0] + if 'members' in groupdata and len(groupdata['members']) > 0: + kcusers = groupdata['members'] + for user in kcusers: + self.__persistuser(user) + + return DefaultMethodResult(True,'Users synced for foi-mod') + + def __persistuser(self, user): + foiuser = FOIUser() + foiuser.username = user["origusername"] + foiuser.preferred_username = user["username"] + foiuser.firstname = user["firstname"] + foiuser.lastname = user["lastname"] + foiuser.email = user["email"] + foiuser.isactive = user["enabled"] + return FOIUser().saveuser(foiuser) + + + def getusers(self): + return FOIUser().getall() + + + \ No newline at end of file diff --git a/historical-search-api/request_api/services/validators/__init__.py b/historical-search-api/request_api/services/validators/__init__.py new file mode 100644 index 000000000..71a73d5e4 --- /dev/null +++ b/historical-search-api/request_api/services/validators/__init__.py @@ -0,0 +1,14 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Exposes all of the Services used in the validator.""" diff --git a/historical-search-api/request_api/services/validators/validator_response.py b/historical-search-api/request_api/services/validators/validator_response.py new file mode 100644 index 000000000..4556bcff0 --- /dev/null +++ b/historical-search-api/request_api/services/validators/validator_response.py @@ -0,0 +1,36 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Common validator response objects.""" +from typing import Dict, List + +from request_api.exceptions import Error + + +class ValidatorResponse: # pylint: disable=too-few-public-methods; convenience class + """A convenience class for managing errors as code outside of Exceptions.""" + + def __init__(self, error: List[Error] = None, info: Dict = None): + """Initialize the error object.""" + self.error = error if error is not None else [] + self.info = info if info is not None else {} + self.is_valid = True + + def add_error(self, error: Error): + """Add error to the response object and make it invalid.""" + self.is_valid = False + self.error.append(error) + + def add_info(self, info_message: dict): + """Add to the response [success cases].""" + self.info.update(info_message) diff --git a/historical-search-api/request_api/services/watcherservice.py b/historical-search-api/request_api/services/watcherservice.py new file mode 100644 index 000000000..e76544ab2 --- /dev/null +++ b/historical-search-api/request_api/services/watcherservice.py @@ -0,0 +1,91 @@ + +from os import stat +from re import VERBOSE +from request_api.models.FOIRequestWatchers import FOIRequestWatcher +from request_api.models.FOIRawRequestWatchers import FOIRawRequestWatcher +from request_api.models.FOIRawRequests import FOIRawRequest +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +import json +class watcherservice: + """ FOI watcher management service + """ + + def createrawrequestwatcher(self, data, userid, usergroups): + """Creates a watcher for a user with groups passed in for an unopened request. + """ + version = FOIRawRequest.getversionforrequest(data["requestid"]) + if 'watchedbygroup' in data: + return FOIRawRequestWatcher.savewatcher(data, version, userid) + else: + return FOIRawRequestWatcher.savewatcherbygroups(data, version, userid, self.__getwatchablegroups(usergroups)) + + + def getrawrequestwatchers(self, requestid): + """Retrieves all watchers associated with an unopened request. + """ + return FOIRawRequestWatcher.getwatchers(requestid) + + + def disablerawrequestwatchers(self, requestid, userid): + """Remove an user from the watched list of an unopened request. + """ + return FOIRawRequestWatcher.disablewatchers(requestid, userid) + + + def createministryrequestwatcher(self, data, userid, usergroups): + """Creates a watcher for a user with groups passed in for an opened request. + """ + version = FOIMinistryRequest.getversionforrequest(data["ministryrequestid"]) + if 'watchedbygroup' in data: + return FOIRequestWatcher.savewatcher(data, version, userid) + else: + return FOIRequestWatcher.savewatcherbygroups(data, version, userid, self.__getwatchablegroups(usergroups)) + + + def getministryrequestwatchers(self, ministryrequestid, isministrymember): + """Retrieves all watchers associated with an opened request. + """ + if isministrymember == True: + return FOIRequestWatcher.getMinistrywatchers(ministryrequestid) + else: + return FOIRequestWatcher.getNonMinistrywatchers(ministryrequestid) + + def getallministryrequestwatchers(self, ministryrequestid, isministryonly=False): + ministrywatchers = FOIRequestWatcher.getMinistrywatchers(ministryrequestid) + if isministryonly == False: + return ministrywatchers + FOIRequestWatcher.getNonMinistrywatchers(ministryrequestid) + return ministrywatchers + + + def disableministryrequestwatchers(self, ministryrequestid, userid): + """Remove an user from the watched list of an opened request. + """ + return FOIRequestWatcher.disablewatchers(ministryrequestid, userid) + + + def __getwatchablegroups(self, groups): + """Returns a list of filtered groups excluding black listed pattern. + """ + watchablegroups = [] + for group in groups: + if self.isexcludegroup(group) == False: + watchablegroups.append(group) + return watchablegroups + + + def isexcludegroup(self, input): + """Identifies whether the given input group is present in excluded groups. + """ + for group in self.__excludegrouppattern(): + if group in input.lower(): + return True + return False + + + def __excludegrouppattern(self): + """Exclude KC groups matching the patterns listed. + """ + return ["formsflow","realm","camunda"] + + + \ No newline at end of file diff --git a/historical-search-api/request_api/services/workflowservice.py b/historical-search-api/request_api/services/workflowservice.py new file mode 100644 index 000000000..1cbcbb1db --- /dev/null +++ b/historical-search-api/request_api/services/workflowservice.py @@ -0,0 +1,322 @@ +import requests +import os +import json +from enum import Enum +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 +from request_api.services.paymentservice import paymentservice +from request_api.models.FOIRawRequests import FOIRawRequest +from request_api.models.FOIMinistryRequests import FOIMinistryRequest +from request_api.models.FOIRequests import FOIRequest +from request_api.utils.enums import StateName +import logging +from request_api.schemas.external.bpmschema import VariableSchema +from request_api.services.external.camundaservice import VariableType +""" +This class is reserved for workflow services integration. +Supported operations: claim + +__author__ = "sumathi.thirumani@aot-technologies.com" + +""" +class workflowservice: + + def createinstance(self, definitionkey, message): + response = bpmservice().createinstance(definitionkey, json.loads(message)) + if response is None: + raise Exception("Unable to create instance for key"+ definitionkey) + return response + + def postunopenedevent(self, id, wfinstanceid, requestsschema, status, ministries=None): + if wfinstanceid in (None,""): + logging.error("WF INSTANCE IS INVALID") + return + assignedgroup = requestsschema["assignedGroup"] if 'assignedGroup' in requestsschema else None + assignedto = requestsschema["assignedTo"] if 'assignedTo' in requestsschema else None + if status == UnopenedEvent.intakeinprogress.value: + messagename = MessageType.intakereopen.value if self.__hasreopened(id, "rawrequest") == True else MessageType.intakeclaim.value + return bpmservice().unopenedsave(wfinstanceid, assignedto, messagename) + else: + if status == UnopenedEvent.open.value: + metadata = json.dumps({"id": id, "status": status, "ministries": ministries, "assignedGroup": assignedgroup, "assignedTo": assignedto}) + else: + metadata = json.dumps({"id": id, "status": status, "assignedGroup": assignedgroup, "assignedTo": assignedto}) + return bpmservice().unopenedcomplete(wfinstanceid, metadata, MessageType.intakecomplete.value) + + def postopenedevent(self, id, wfinstanceid, requestsschema, data, newstatus, usertype, issync=False): + assignedgroup = self.__getopenedassigneevalue(requestsschema, "assignedgroup",usertype) + assignedto = self.__getopenedassigneevalue(requestsschema, "assignedto",usertype) + axisrequestid = self.__getvaluefromschema(requestsschema,"axisRequestId") + if data.get("ministries") is not None: + for ministry in data.get("ministries"): + filenumber = ministry["filenumber"] + if int(ministry["id"]) == int(id): + paymentexpirydate = paymentservice().getpaymentexpirydate(int(ministry["foirequestid"]), int(ministry["id"])) + previousstatus = self.__getpreviousministrystatus(id) if issync == False else self.__getprevioustatusbyversion(id, int(ministry["version"])) + oldstatus = self.__getministrystatus(filenumber, ministry["version"]) if issync == False else previousstatus + activity = self.__getministryactivity(oldstatus,newstatus) if issync == False else Activity.complete.value + isprocessing = self.__isprocessing(id) if issync == False else False + messagename = self.__messagename(oldstatus, activity, usertype, isprocessing) + metadata = json.dumps( + {"id": filenumber, "previousstatus":previousstatus, "status": ministry["status"] , + "assignedGroup": assignedgroup, "assignedTo": assignedto, + "assignedministrygroup":ministry["assignedministrygroup"], + "ministryRequestID": id, "isPaymentActive": self.__ispaymentactive(ministry["foirequestid"], id), + "paymentExpiryDate": paymentexpirydate, "axisRequestId": axisrequestid, "issync": issync, + "isofflinepayment": FOIMinistryRequest.getofflinepaymentflag(id)}) + if issync == True: + _variables = bpmservice().getinstancevariables(wfinstanceid) + if ministry["status"] == OpenedEvent.callforrecords.value and (("status" not in _variables) or (_variables not in (None, []) and "status" in _variables and _variables["status"]["value"] != OpenedEvent.callforrecords.value)): + messagename = MessageType.iaoopencomplete.value + elif _variables not in (None, []) and ("status" in _variables and _variables["status"]["value"] == StateName.closed.value): + return bpmservice().reopenevent(wfinstanceid, metadata, MessageType.iaoreopen.value) + else: + return bpmservice().openedcomplete(wfinstanceid, filenumber, metadata, messagename) + self.__postopenedevent(id, filenumber, metadata, messagename, assignedgroup, assignedto, wfinstanceid, activity) + + def postfeeevent(self, requestid, ministryrequestid, requestsschema, paymentstatus, nextstatename=None): + metadata = json.dumps({ + "id": requestsschema["idNumber"], + "status": requestsschema["currentState"], + "assignedGroup": requestsschema["assignedGroup"], + "assignedTo": requestsschema["assignedTo"], + "assignedministrygroup" : requestsschema["assignedministrygroup"], + "ministryRequestID" : ministryrequestid, + "foiRequestID" :requestid, + "nextStateName": nextstatename + }) + return bpmservice().feeevent(requestsschema["axisRequestId"], metadata, paymentstatus) + + def postcorrenspodenceevent(self, wfinstanceid, ministryid, requestsschema, applicantcorrespondenceid, templatename, attributes): + paymentexpirydate = self.__getvaluefromlist(attributes,"paymentExpiryDate") + axisrequestid = self.__getvaluefromschema(requestsschema,"axisRequestId") + filenumber = self.__getvaluefromschema(requestsschema,"idNumber") + status = self.__getvaluefromschema(requestsschema,"currentState") + metadata = json.dumps({"id": filenumber, "status": status , "ministryRequestID": ministryid, "paymentExpiryDate": paymentexpirydate, "axisRequestId": axisrequestid, "applicantcorrespondenceid": applicantcorrespondenceid, "templatename": templatename.replace(" ", "")}) + bpmservice().correspondanceevent(wfinstanceid, filenumber, metadata) + + def syncwfinstance(self, requesttype, requestid, isallactivity=False): + try: + # Sync and get raw instance details from FOI DB + raw_metadata = self.__sync_raw_request(requesttype, requestid) + if requesttype == "ministryrequest": + req_metadata = self.__sync_foi_request(requestid, raw_metadata) + # Check foi request instance creation - Reconcile by transition to Open + _all_activity_desc = FOIMinistryRequest.getactivitybyid(requestid) + self.__sync_state_transition(requestid, str(req_metadata.wfinstanceid), _all_activity_desc, isallactivity) + return req_metadata.wfinstanceid + return raw_metadata.wfinstanceid + except Exception as ex: + logging.error(ex) + return None + + def __sync_raw_request(self, requesttype, requestid): + # Search for WF ID + requestid = int(requestid) + _raw_metadata = FOIRawRequest.getworkflowinstancebyraw(requestid) if requesttype == "rawrequest" else FOIRawRequest.getworkflowinstancebyministry(requestid) + wf_rawrequest_pid = self.__get_wf_pid("rawrequest", _raw_metadata) + # Check for exists - Reconcile with new instance creation + if wf_rawrequest_pid not in (None, "") and _raw_metadata.wfinstanceid not in (None, "") and str(_raw_metadata.wfinstanceid) == wf_rawrequest_pid: + return _raw_metadata + # WF Instance is not present + if wf_rawrequest_pid in (None, ""): + self.createinstance(RedisPublisherService().foirequestqueueredischannel, json.dumps(self.__prepare_raw_requestobj(_raw_metadata))) + else: + if _raw_metadata.wfinstanceid in (None, "") or str(_raw_metadata.wfinstanceid) != wf_rawrequest_pid: + FOIRawRequest.updateworkflowinstance_n(wf_rawrequest_pid, int(_raw_metadata.requestid), "System") + return FOIRawRequest.getworkflowinstancebyraw(requestid) if requesttype == "rawrequest" else FOIRawRequest.getworkflowinstancebyministry(requestid) + + def __sync_foi_request(self, requestid, raw_metadata): + requestid = int(requestid) + _req_metadata = FOIRequest.getworkflowinstance(requestid) + wf_foirequest_pid = self.__get_wf_pid("ministryrequest", raw_metadata, _req_metadata) + if wf_foirequest_pid not in (None, "") and _req_metadata.wfinstanceid not in (None, "") and str(_req_metadata.wfinstanceid) == wf_foirequest_pid: + return _req_metadata + if wf_foirequest_pid in (None, ""): + _req_ministries = FOIMinistryRequest.getministriesopenedbyuid(raw_metadata.requestid) + self.postunopenedevent(int(_req_metadata.foirequestid), raw_metadata.wfinstanceid, self.__prepare_raw_requestobj(raw_metadata), UnopenedEvent.open.value, _req_ministries) + else: + if _req_metadata.wfinstanceid in (None, "") or str(_req_metadata.wfinstanceid) != wf_foirequest_pid: + FOIRequest.updateWFInstance(_req_metadata.foirequestid, wf_foirequest_pid, "System") + return FOIRequest.getworkflowinstance(requestid) + + def __get_wf_pid(self, requesttype, _raw_metadata, _req_metadata=None): + if requesttype == "rawrequest": + searchby = [{"name":"id" ,"operator":"eq","value": int(_raw_metadata.requestid)}] + return bpmservice().searchinstancebyvariable(ProcessDefinitionKey.rawrequest.value, searchby) + elif requesttype == "ministryrequest": + searchby = [{"name":"foiRequestID","operator":"eq","value": int(_req_metadata.foirequestid)}, + {"name": "rawRequestPID","operator":"eq","value": str(_raw_metadata.wfinstanceid)}] + wf_foirequest_pid = bpmservice().searchinstancebyvariable(ProcessDefinitionKey.ministryrequest.value, searchby) + if wf_foirequest_pid in (None, ""): + searchby = [{"name":"foiRequestID","operator":"eq","value": str(_req_metadata.foirequestid)}, + {"name": "rawRequestPID","operator":"eq","value": str(_raw_metadata.wfinstanceid)}] + wf_foirequest_pid = bpmservice().searchinstancebyvariable(ProcessDefinitionKey.ministryrequest.value, searchby) + return wf_foirequest_pid + else: + logging.info("Unknown requestype %s", requesttype) + return None + + def __sync_state_transition(self, requestid, wfinstanceid, _all_activity_desc, isallactivity): + current = _all_activity_desc[0] + previous = _all_activity_desc[1] if len(_all_activity_desc) > 1 else _all_activity_desc[0] + _activity_itr_desc = _all_activity_desc + if isallactivity == False: + _activity_itr_desc.pop(0) + _variables = bpmservice().getinstancevariables(wfinstanceid) + # SP: Stuck in Open -> Move from Open to CFR + if _variables not in (None, []) and "status" not in _variables: + for entry in _activity_itr_desc: + if entry["status"] == OpenedEvent.callforrecords.value and ((isallactivity == True) or (isallactivity == False and current["status"] != OpenedEvent.callforrecords.value)): + self.__sync_complete_event(requestid, wfinstanceid, entry) + break + # Sync action + _variables = bpmservice().getinstancevariables(wfinstanceid) + oldstatus = self.__getministrystatus(current["filenumber"], current["version"]) + activity = Activity.save.value if isallactivity == True else self.__getministryactivity(oldstatus,current["status"]) + if _variables not in (None, []) and "status" in _variables: + if activity == Activity.save.value and _variables["status"]["value"] != current["status"]: + self.__sync_complete_event(requestid, wfinstanceid, current) + if activity == Activity.complete.value and _variables["status"]["value"] != previous["status"]: + self.__sync_complete_event(requestid, wfinstanceid, previous) + + def __sync_complete_event(self, requestid, wfinstanceid, minrequest): + requestsschema, data = self.__prepare_ministry_complete(minrequest) + self.postopenedevent(requestid, wfinstanceid, requestsschema, data, minrequest["status"], self.__getusertype(minrequest["status"]), True) + + def __prepare_raw_requestobj(self, _rawinstance): + data = {} + data['id'] = _rawinstance.requestid + data['assignedGroup'] = _rawinstance.assignedgroup + data['assignedTo'] = _rawinstance.assignedto + return data + + def __prepare_ministry_complete(self, ministryrequest): + data = {} + data['axisRequestId'] = ministryrequest["axisrequestid"] + data['assignedgroup'] = ministryrequest["assignedgroup"] + data['assignedto'] = ministryrequest["assignedto"] + data['paymentExpiryDate'] = "" + ministry = [] + ministry.append(ministryrequest) + return data, {"ministries": ministry} + + def __getusertype(self, status): + if status in [StateName.feeestimate.value, StateName.harmsassessment.value, StateName.deduplication.value, StateName.recordsreview.value, StateName.ministrysignoff.value]: + return UserType.ministry.value + return UserType.iao.value + + def __postopenedevent(self, id, filenumber, metadata, messagename, assignedgroup, assignedto, wfinstanceid, activity): + if activity == Activity.complete.value: + + if self.__hasreopened(id, "ministryrequest") == True: + bpmservice().reopenevent(wfinstanceid, metadata, MessageType.iaoreopen.value) + else: + bpmservice().openedcomplete(wfinstanceid, filenumber, metadata, messagename) + else: + bpmservice().unopenedsave(filenumber, assignedgroup, assignedto, messagename) + + + def __getopenedassigneevalue(self, requestsschema, property, usertype): + if property == "assignedgroup": + return self.__getvaluefromschema(requestsschema,"assignedgroup") if 'assignedgroup' in requestsschema else self.__getvaluefromschema(requestsschema,"assignedministrygroup") + elif property == "assignedto": + return self.__getvaluefromschema(requestsschema,"assignedto") if 'assignedto' in requestsschema else self.__getvaluefromschema(requestsschema,"assignedministryperson") + else: + return None + + + def __messagename(self, status, activity, usertype, isprocessing=False): + if status == UnopenedEvent.open.value and isprocessing == False: + return MessageType.iaoopencomplete.value if activity == Activity.complete.value else MessageType.iaoopenclaim.value + elif status == OpenedEvent.reopen.value: + return MessageType.iaoreopen.value + else: + if usertype == UserType.ministry.value: + return MessageType.ministrycomplete.value if activity == Activity.complete.value else MessageType.ministryclaim.value + else: + return MessageType.iaocomplete.value if activity == Activity.complete.value else MessageType.iaoclaim.value + + + def __hasreopened(self, requestid, requesttype): + if requesttype == "rawrequest": + states = FOIRawRequest.getstatenavigation(requestid) + else: + states = FOIMinistryRequest.getstatenavigation(requestid) + if len(states) == 2: + newstate = states[0] + oldstate = states[1] + if newstate != oldstate and oldstate == UnopenedEvent.closed.value: + return True + return False + + def __isprocessing(self, requestid): + states = FOIMinistryRequest.getallstatenavigation(requestid) + for state in states: + if state == OpenedEvent.callforrecords.value and states[0] != OpenedEvent.callforrecords.value : + return True + return False + + def __ispaymentactive(self, foirequestid, ministryid): + _payment = cfrfeeservice().getactivepayment(foirequestid, ministryid) + return True if _payment is not None else False + + def __getvaluefromschema(self,requestsschema, property): + return requestsschema.get(property) if property in requestsschema else None + + def __getvaluefromlist(self,attributes, property): + for attribute in attributes: + if property in attribute: + return attribute.get(property) + return "" + + def __getministrystatus(self,filenumber, version): + ministryreq = FOIMinistryRequest.getrequestbyfilenumberandversion(filenumber,version-1) + return ministryreq["requeststatus.name"] + + def __getpreviousministrystatus(self,id): + ministryreq = FOIMinistryRequest.getstatesummary(id) + _len = len(ministryreq) + if _len > 1: + return ministryreq[1]["status"] + elif _len == 1: + return UnopenedEvent.intakeinprogress.value + else: + return None + + def __getprevioustatusbyversion(self,id, version): + ministryreq = FOIMinistryRequest.getstatesummary(id) + _len = len(ministryreq) + if _len > 1: + for entry in ministryreq: + if int(entry["version"]) < version: + return entry["status"] + elif _len == 1: + return UnopenedEvent.intakeinprogress.value + else: + return None + + def __getministryactivity(self, oldstatus, newstatus): + return Activity.complete.value if newstatus is not None and oldstatus != newstatus else Activity.save.value + + +class UserType(Enum): + iao = "iao" + ministry = "ministry" + +class Activity(Enum): + save = "save" + complete = "complete" + +class UnopenedEvent(Enum): + intakeinprogress = "Intake in Progress" + open = "Open" + redirect = "Redirect" + closed = "Closed" + reopen = "Reopen" + +class OpenedEvent(Enum): + callforrecords = "Call For Records" + reopen = "Reopen" \ No newline at end of file diff --git a/historical-search-api/request_api/status.py b/historical-search-api/request_api/status.py new file mode 100644 index 000000000..51898fbb3 --- /dev/null +++ b/historical-search-api/request_api/status.py @@ -0,0 +1,97 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Descriptive HTTP status codes for readability. + +A set of constants built using the HTTP Code Names +https://www.restapitutorial.com/httpstatuscodes.html + +Making it a little easier for those reading code, that don't have the table memorized. +""" + + +def is_informational(code): + """Return that the code is a provisional response.""" + return 100 <= code <= 199 + + +def is_success(code): + """Return that the client's request was successfully received, understood, and accepted.""" + return 200 <= code <= 299 + + +def is_redirect(code): + """Return that further action needs to be taken by the user agent in order to fulfill the request.""" + return 300 <= code <= 399 + + +def is_client_error(code): + """Return that the client seems to have erred.""" + return 400 <= code <= 499 + + +def is_server_error(code): + """Return that the server is aware that it has erred or is incapable of performing the request.""" + return 500 <= code <= 599 + + +HTTP_100_CONTINUE = 100 +HTTP_101_SWITCHING_PROTOCOLS = 101 +HTTP_200_OK = 200 +HTTP_201_CREATED = 201 +HTTP_202_ACCEPTED = 202 +HTTP_203_NON_AUTHORITATIVE_INFORMATION = 203 +HTTP_204_NO_CONTENT = 204 +HTTP_205_RESET_CONTENT = 205 +HTTP_206_PARTIAL_CONTENT = 206 +HTTP_207_MULTI_STATUS = 207 +HTTP_300_MULTIPLE_CHOICES = 300 +HTTP_301_MOVED_PERMANENTLY = 301 +HTTP_302_FOUND = 302 +HTTP_303_SEE_OTHER = 303 +HTTP_304_NOT_MODIFIED = 304 +HTTP_305_USE_PROXY = 305 +HTTP_306_RESERVED = 306 +HTTP_307_TEMPORARY_REDIRECT = 307 +HTTP_308_PERMANENT_REDIRECT = 308 +HTTP_400_BAD_REQUEST = 400 +HTTP_401_UNAUTHORIZED = 401 +HTTP_402_PAYMENT_REQUIRED = 402 +HTTP_403_FORBIDDEN = 403 +HTTP_404_NOT_FOUND = 404 +HTTP_405_METHOD_NOT_ALLOWED = 405 +HTTP_406_NOT_ACCEPTABLE = 406 +HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407 +HTTP_408_REQUEST_TIMEOUT = 408 +HTTP_409_CONFLICT = 409 +HTTP_410_GONE = 410 +HTTP_411_LENGTH_REQUIRED = 411 +HTTP_412_PRECONDITION_FAILED = 412 +HTTP_413_REQUEST_ENTITY_TOO_LARGE = 413 +HTTP_414_REQUEST_URI_TOO_LONG = 414 +HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415 +HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416 +HTTP_417_EXPECTATION_FAILED = 417 +HTTP_428_PRECONDITION_REQUIRED = 428 +HTTP_429_TOO_MANY_REQUESTS = 429 +HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431 +HTTP_444_CONNECTION_CLOSED_WITHOUT_RESPONSE = 444 +HTTP_500_INTERNAL_SERVER_ERROR = 500 +HTTP_501_NOT_IMPLEMENTED = 501 +HTTP_502_BAD_GATEWAY = 502 +HTTP_503_SERVICE_UNAVAILABLE = 503 +HTTP_504_GATEWAY_TIMEOUT = 504 +HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505 +HTTP_508_LOOP_DETECTED = 508 +HTTP_510_NOT_EXTENDED = 510 +HTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 511 diff --git a/historical-search-api/request_api/tracer.py b/historical-search-api/request_api/tracer.py new file mode 100644 index 000000000..e88efcee9 --- /dev/null +++ b/historical-search-api/request_api/tracer.py @@ -0,0 +1,40 @@ +# # Copyright © 2021 Province of British Columbia +# # +# # Licensed under the Apache License, Version 2.0 (the 'License'); +# # you may not use this file except in compliance with the License. +# # You may obtain a copy of the License at +# # +# # http://www.apache.org/licenses/LICENSE-2.0 +# # +# # Unless required by applicable law or agreed to in writing, software +# # distributed under the License is distributed on an 'AS IS' BASIS, +# # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# # See the License for the specific language governing permissions and +# # limitations under the License. +# """Tracing subsystem class. + +# This module initializes and provides the tracing component from sbc_common_components +# """ + +from sbc_common_components.tracing.api_tracer import ApiTracer +from sbc_common_components.tracing.api_tracing import ApiTracing + +class Tracer(): # pylint: disable=too-few-public-methods + """Singleton class that wraps sbc_common_components tracing.""" + + __instance = None + + @staticmethod + def get_instance(): + """Retrieve singleton JWTWrapper.""" + if Tracer.__instance is None: + Tracer() + return Tracer.__instance + + def __init__(self): + """Virtually private constructor.""" + if Tracer.__instance is not None: + raise Exception('Attempt made to create multiple tracing instances') + + api_tracer = ApiTracer() + Tracer.__instance = ApiTracing(api_tracer.tracer) diff --git a/historical-search-api/request_api/utils/__init__.py b/historical-search-api/request_api/utils/__init__.py new file mode 100644 index 000000000..0a77a31a1 --- /dev/null +++ b/historical-search-api/request_api/utils/__init__.py @@ -0,0 +1,14 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""This module holds general utility functions and helpers for the main package.""" diff --git a/historical-search-api/request_api/utils/cache.py b/historical-search-api/request_api/utils/cache.py new file mode 100644 index 000000000..5521ba9a6 --- /dev/null +++ b/historical-search-api/request_api/utils/cache.py @@ -0,0 +1,69 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os + +import request_api +import redis +import logging + +class Config(object): + ## type 'redis' is deprecated + CACHE_TYPE = 'RedisCache' + + CACHE_REDIS_URL = os.getenv('CACHE_REDISURL') + CACHE_DEFAULT_TIMEOUT = os.getenv('CACHE_TIMEOUT') + CACHE_KEY_PPREFIX = 'foi' + + ## include code of function in hash + CACHE_SOURCE_CHECK = True + +cache_client = redis.from_url(os.getenv('CACHE_REDISURL')) + +## If true, bypass cache +def cache_filter(): + if os.getenv('CACHE_ENABLED') != 'Y': + return True + + try: + cache_client.ping() + except Exception: + return True + return False + +## If True, cache response +def response_filter(resp): + if resp[-1] == 200 and resp[0] not in (None, '', '[]'): + return True + else: + return False + + +def clear_cache(): + try: + if os.getenv('CACHE_ENABLED') == 'Y': + cache_client.flushall() + return True + except Exception as ex: + logging.error(ex) + return False + + +def clear_cache_key(key): + try: + if os.getenv('CACHE_ENABLED') == 'Y': + cache_client.delete(key) + return True + except Exception as ex: + logging.error(ex) + return False \ No newline at end of file diff --git a/historical-search-api/request_api/utils/commons/datetimehandler.py b/historical-search-api/request_api/utils/commons/datetimehandler.py new file mode 100644 index 000000000..a465b5eef --- /dev/null +++ b/historical-search-api/request_api/utils/commons/datetimehandler.py @@ -0,0 +1,43 @@ + +from os import stat +from re import VERBOSE +import json +from datetime import datetime as datetime2 +from datetime import datetime, timedelta +import holidays +import maya +import os +from dateutil.parser import parse +from pytz import timezone + +class datetimehandler: + """ Supports common date operations + + """ + def gettoday(self,format=None): + now_pst = maya.parse(maya.now()).datetime(to_timezone=self.getdefaulttimezone(), naive=False) + return self.__formatdate(now_pst, format) + + def now(self): + return maya.parse(maya.now()).datetime(to_timezone=self.getdefaulttimezone(), naive=False) + + def formatdate(self, input, format=None): + _inpdate = self.getdate(input) + return self.__formatdate(_inpdate, format) + + def convert_to_pst(self, input, format=None): + now_pst = maya.parse(self.getdate(input)).datetime(to_timezone=self.getdefaulttimezone(), naive=False) + return self.__formatdate(now_pst, format) + + def __formatdate(self, _inpdate, format): + _format = format if format not in (None, '') else self.getdefaultdateformat() + return _inpdate.strftime(_format) + + def getdate(self, inputdate): + return datetime.strptime(inputdate, "%Y-%m-%d") if isinstance(inputdate, str) else inputdate + + def getdefaulttimezone(self): + return 'America/Vancouver' + + def getdefaultdateformat(self): + return '%Y-%m-%d' diff --git a/historical-search-api/request_api/utils/constants.py b/historical-search-api/request_api/utils/constants.py new file mode 100644 index 000000000..456e97c90 --- /dev/null +++ b/historical-search-api/request_api/utils/constants.py @@ -0,0 +1,42 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import requests +import logging +from os import getenv +"""Constants definitions.""" + +# Group names + +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', + url=getenv("FOI_RECORD_FORMATS"), + headers={'Content-Type': 'application/json'}, + timeout=5 + ) + response.raise_for_status() + FILE_CONVERSION_FILE_TYPES = response.json()['conversion'] + DEDUPE_FILE_TYPES = response.json()['dedupe'] + NONREDACTABLE_FILE_TYPES = response.json()['nonredactable'] +except Exception as err: + logging.error("Unable to retrieve record upload formats from S3") + logging.error(err) + + diff --git a/historical-search-api/request_api/utils/custom_sql.py b/historical-search-api/request_api/utils/custom_sql.py new file mode 100644 index 000000000..1d865d0e6 --- /dev/null +++ b/historical-search-api/request_api/utils/custom_sql.py @@ -0,0 +1,26 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""This manages custom object definition. + +This can be used to define any view/materialized view/stored procedure/function etc. +""" + + +class CustomSql: # pylint:disable=too-few-public-methods + """This is the object for custom Sql definition.""" + + def __init__(self, name, sql): + """Construct the sql with name and sql text.""" + self.name = name + self.sql = sql diff --git a/historical-search-api/request_api/utils/enums.py b/historical-search-api/request_api/utils/enums.py new file mode 100644 index 000000000..356b347e1 --- /dev/null +++ b/historical-search-api/request_api/utils/enums.py @@ -0,0 +1,175 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Enum definitions.""" +from enum import Enum + + +class RequestType(Enum): + """Authorization header types.""" + + PERSONAL = 'Personal' + GENERAL = 'General' + + +class ContentType(Enum): + """Http Content Types.""" + + JSON = 'application/json' + FORM_URL_ENCODED = 'application/x-www-form-urlencoded' + PDF = 'application/pdf' + + +class MinistryTeamWithKeycloackGroup(Enum): + AEST = "AEST Ministry Team" + AGR = "AGR Ministry Team" + AG = "AG Ministry Team" + BRD = "BRD Ministry Team" + CAS = "CAS Ministry Team" + CITZ = "CITZ Ministry Team" + CLB = "CLB Ministry Team" + DAS = "DAS Ministry Team" + EAO = "EAO Ministry Team" + EDU = "EDU Ministry Team" + EMBC = "EMBC Ministry Team" + EMC = "EMC Ministry Team" + EMLI = "EMLI Ministry Team" + ENV = "ENV Ministry Team" + FIN = "FIN Ministry Team" + FOR = "FOR Ministry Team" + GCP = "GCP Ministry Team" + HTH = "HTH Ministry Team" + IIO = "IIO Ministry Team" + IRR = "IRR Ministry Team" + JERI = "JERI Ministry Team" + LBR = "LBR Ministry Team" + LDB = "LDB Ministry Team" + LWR = "LWR Ministry Team" + WLR = "WLR Ministry Team" + MCF = "MCF Ministry Team" + MGC = "MGC Ministry Team" + MMHA = "MMHA Ministry Team" + MUNI = "MUNI Ministry Team" + OBC = "OBC Ministry Team" + OCC = "OCC Ministry Team" + OOP = "OOP Ministry Team" + PSA = "PSA Ministry Team" + PSSG = "PSSG Ministry Team" + MSD = "MSD Ministry Team" + TACS = "TACS Ministry Team" + TIC = "TIC Ministry Team" + TRAN = "TRAN Ministry Team" + PSE = "PSE Ministry Team" + ECC = "ECC Ministry Team" + JED = "JED Ministry Team" + COR = "COR Ministry Team" + HSG = "HSG Ministry Team" + + @staticmethod + def list(): + return list(map(lambda c: c.value, MinistryTeamWithKeycloackGroup)) + +class ProcessingTeamWithKeycloackGroup(Enum): + scanningteam = "Scanning Team" + centralteam = "Central Team" + justicehealthteam = "Justice Health Team" + mcfdpersonalteam = "MCFD Personals Team" + resouceteam = "Resource Team" + socialtechteam = "Social Education" + centraleconteam = "Central and Economy Team" + resourcejusticeteam = "Resource and Justice Team" + communityhealthteam = "Community and Health Team" + childrenfamilyteam = "Children and Family Team" + childreneducationteam = "Children and Education Team" + coordinatedresponseunit = "Coordinated Response Unit" + + @staticmethod + def list(): + return list(map(lambda c: c.value, ProcessingTeamWithKeycloackGroup)) + +class IAOTeamWithKeycloackGroup(Enum): + intake = "Intake Team" + flex = "Flex Team" + + @staticmethod + def list(): + return list(map(lambda c: c.value, IAOTeamWithKeycloackGroup)) + list(map(lambda c: c.value, ProcessingTeamWithKeycloackGroup)) + +class UserGroup(Enum): + intake = "Intake Team" + flex = "Flex Team" + processing = "@processing" + ministry = "@bcgovcode Ministry Team" + +class RequestorType(Enum): + applicant = 1 + onbehalfof = 2 + child = 3 + +class FeeType(Enum): + application = 'FOI0001' + processing = 'FOI0002' + +class PaymentEventType(Enum): + paid = "PAID" + expired = "EXPIRED" + outstandingpaid = "OUTSTANDINGPAID" + depositpaid = "DEPOSITPAID" + reminder = "REMINDER" + +class CommentType(Enum): + """Authorization header types.""" + UserComment = 1 + SystemGenerated = 2 + DivisionStages = 3 + +class DocumentPathMapperCategory(Enum): + Attachments = "Attachments" + Records = "Records" + +class ServiceName(Enum): + payonline = "payonline" + payoutstanding = "payoutstanding" + correspondence = "correspondence" + +class StateName(Enum): + open = "Open" + callforrecords = "Call For Records" + closed = "Closed" + redirect = "Redirect" + unopened = "Unopened" + intakeinprogress = "Intake in Progress" + consult = "Consult" + ministrysignoff = "Ministry Sign Off" + onhold = "On Hold" + deduplication = "Deduplication" + harmsassessment = "Harms Assessment" + response = "Response" + feeestimate = "Fee Estimate" + recordsreview = "Records Review" + recordsreadyforreview = "Records Ready for Review" + archived = "Archived" + peerreview = "Peer Review" + tagging = "Tagging" + readytoscan = "Ready to Scan" + appfeeowing = "App Fee Owing" + section5pending = "Section 5 Pending" +class CacheUrls(Enum): + keycloakusers= "/api/foiassignees" + programareas= "/api/foiflow/programareas" + deliverymodes= "/api/foiflow/deliverymodes" + receivedmodes= "/api/foiflow/receivedmodes" + closereasons= "/api/foiflow/closereasons" + extensionreasons= "/api/foiflow/extensionreasons" + applicantcategories= "/api/foiflow/applicantcategories" + subjectcodes= "/api/foiflow/subjectcodes" diff --git a/historical-search-api/request_api/utils/handlers.py b/historical-search-api/request_api/utils/handlers.py new file mode 100644 index 000000000..bc4e5a6ff --- /dev/null +++ b/historical-search-api/request_api/utils/handlers.py @@ -0,0 +1,26 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Callbacks and signal trapping used in the main loop.""" +from flask import current_app + + +async def error_cb(e): + """Emit error message to the log stream.""" + current_app.logger.error(e) + raise e + + +async def closed_cb(): + """Exit the session after the NATS connection is closed.""" + current_app.logger.info('Connection to NATS is closed.') diff --git a/historical-search-api/request_api/utils/passcode.py b/historical-search-api/request_api/utils/passcode.py new file mode 100644 index 000000000..16aab5701 --- /dev/null +++ b/historical-search-api/request_api/utils/passcode.py @@ -0,0 +1,32 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Using the bcrypt library to securely hash and check hashed passcode.""" +import bcrypt + + +def passcode_hash(passcode: str): + """Return hashed passcode.""" + if passcode: + hashed_passcode: bytes = bcrypt.hashpw(passcode.encode(), bcrypt.gensalt()) + return hashed_passcode.decode() + return None + + +def validate_passcode(passcode: str, hashed_passcode: str): + """Validate passcode and hashed passcode.""" + if passcode and hashed_passcode: + passcode_bytes: str = passcode.encode() + hashed_passcod_bytes: bytes = hashed_passcode.encode() + return bcrypt.checkpw(passcode_bytes, hashed_passcod_bytes) + return False diff --git a/historical-search-api/request_api/utils/redispublisher.py b/historical-search-api/request_api/utils/redispublisher.py new file mode 100644 index 000000000..0cb8a1ff8 --- /dev/null +++ b/historical-search-api/request_api/utils/redispublisher.py @@ -0,0 +1,36 @@ + +import os +from request_api.exceptions import BusinessException, Error + +from flask import current_app +from redis import Redis +from request_api.utils.redissubscriber import RedisSubscriberService +import logging +import redis +class RedisPublisherService: + + foirequestqueueredischannel = os.getenv('FOI_REQUESTQUEUE_REDISCHANNEL') + foicommentqueueredischannel = os.getenv('SOCKETIO_REDIS_COMMENT_CHANNEL') + + foimsgredis = redis.from_url(os.getenv('SOCKETIO_REDISURL'), socket_connect_timeout=int(os.getenv('SOCKETIO_REDIS_CONNECT_TIMEOUT')), retry_on_timeout=True, socket_keepalive=True) + + async def publishrequest(self, message): + self.publishtoredischannel(self.foirequestqueueredischannel, message) + + def publishcommment(self, message): + try: + logging.info(message) + self.publishtoredischannel(self.foicommentqueueredischannel, message) + except Exception as ex: + current_app.logger.error("%s,%s" % ('Unable to get user details', ex)) + raise ex + + def publishtoredischannel(self, channel , message): + try: + if channel == os.getenv('FOI_REQUESTQUEUE_REDISCHANNEL'): + self.foimsgredis.publish(channel, message) + if channel == os.getenv('SOCKETIO_REDIS_COMMENT_CHANNEL'): + RedisSubscriberService().foicommentredis.publish(channel, message) + except BusinessException as exception: + current_app.logger.error("%s,%s" % ('FOI request Queue REDIS Error', exception.message)) + diff --git a/historical-search-api/request_api/utils/redissubscriber.py b/historical-search-api/request_api/utils/redissubscriber.py new file mode 100644 index 000000000..174d9898a --- /dev/null +++ b/historical-search-api/request_api/utils/redissubscriber.py @@ -0,0 +1,31 @@ + +import os +from request_api.exceptions import BusinessException + +from flask import current_app +import time +from request_api import socketio +import json +import redis +import logging +class RedisSubscriberService: + + foicommentredis = redis.from_url(os.getenv('SOCKETIO_REDISURL'), health_check_interval=int(os.getenv('SOCKETIO_REDIS_HEALTHCHECK_INTERVAL')), socket_connect_timeout=int(os.getenv('SOCKETIO_REDIS_CONNECT_TIMEOUT')), retry_on_timeout=True, socket_keepalive=True) + subscription = foicommentredis.pubsub(ignore_subscribe_messages=True) + + @classmethod + def register_subscription(cls): + try: + logging.warning("subscription to channel") + cls.subscription.subscribe(**{os.getenv('SOCKETIO_REDIS_COMMENT_CHANNEL'): event_handler}) + cls.subscription.run_in_thread(sleep_time=float(os.getenv('SOCKETIO_REDIS_SLEEP_TIME')), daemon=True) + except BusinessException as exception: + logging.error("%s,%s" % ('FOI request Queue REDIS Error', exception.message)) + + +def event_handler(msg): + if msg and msg.get('type') == 'message': + data = msg.get('data') + _pushnotification = json.loads(data) + socketio.emit(_pushnotification["userid"], _pushnotification) + diff --git a/historical-search-api/request_api/utils/roles.py b/historical-search-api/request_api/utils/roles.py new file mode 100644 index 000000000..d3ff80608 --- /dev/null +++ b/historical-search-api/request_api/utils/roles.py @@ -0,0 +1,58 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Role definitions.""" +from enum import Enum + +from .enums import OrgStatus, ProductSubscriptionStatus, Status + + +class Role(Enum): + """User Role.""" + + VIEWER = 'view' + EDITOR = 'edit' + PUBLIC_USER = 'public_user' + ACCOUNT_HOLDER = 'account_holder' + GOV_ACCOUNT_USER = 'gov_account_user' + ANONYMOUS_USER = 'anonymous_user' + + SYSTEM = 'system' + TESTER = 'tester' + + STAFF = 'staff' + STAFF_VIEW_ACCOUNTS = 'view_accounts' + STAFF_MANAGE_ACCOUNTS = 'manage_accounts' + STAFF_SEARCH = 'search' + STAFF_CREATE_ACCOUNTS = 'create_accounts' + STAFF_MANAGE_BUSINESS = 'manage_business' + STAFF_SUSPEND_ACCOUNTS = 'suspend_accounts' + + +# Membership types +STAFF = 'STAFF' +COORDINATOR = 'COORDINATOR' +ADMIN = 'ADMIN' +USER = 'USER' + +VALID_STATUSES = (Status.ACTIVE.value, Status.PENDING_APPROVAL.value) +VALID_ORG_STATUSES = (OrgStatus.ACTIVE.value, OrgStatus.NSF_SUSPENDED.value, + OrgStatus.SUSPENDED.value, OrgStatus.PENDING_INVITE_ACCEPT.value, + OrgStatus.PENDING_STAFF_REVIEW.value) +VALID_SUBSCRIPTION_STATUSES = (ProductSubscriptionStatus.ACTIVE.value, + ProductSubscriptionStatus.PENDING_STAFF_REVIEW.value) + +CLIENT_ADMIN_ROLES = (COORDINATOR, ADMIN) +CLIENT_AUTH_ROLES = (*CLIENT_ADMIN_ROLES, USER) +ALL_ALLOWED_ROLES = (*CLIENT_AUTH_ROLES, STAFF) +EXCLUDED_FIELDS = ('status_code', 'type_code') diff --git a/historical-search-api/request_api/utils/run_version.py b/historical-search-api/request_api/utils/run_version.py new file mode 100644 index 000000000..2ede20cd1 --- /dev/null +++ b/historical-search-api/request_api/utils/run_version.py @@ -0,0 +1,29 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Supply version and commit hash info.""" +import os + +from request_api.version import __version__ + + +def _get_build_openshift_commit_hash(): + return os.getenv('OPENSHIFT_BUILD_COMMIT', None) + + +def get_run_version(): + """Return a formatted version string for this service.""" + commit_hash = _get_build_openshift_commit_hash() + if commit_hash: + return f'{__version__}-{commit_hash}' + return __version__ diff --git a/historical-search-api/request_api/utils/util.py b/historical-search-api/request_api/utils/util.py new file mode 100644 index 000000000..eadc8c382 --- /dev/null +++ b/historical-search-api/request_api/utils/util.py @@ -0,0 +1,142 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""CORS pre-flight decorator. + +A simple decorator to add the options method to a Request Class. +""" + +import base64 +import re +import urllib +from functools import wraps + +from humps.main import camelize, decamelize +from flask import request, g +from sqlalchemy.sql.expression import false +from request_api.auth import jwt as _authjwt,AuthHelper +import jwt +import os +from request_api.utils.enums import MinistryTeamWithKeycloackGroup, ProcessingTeamWithKeycloackGroup +# from request_api.services.rawrequestservice import rawrequestservice +# from request_api.models.FOIRequestWatchers import FOIRequestWatcher + + +def cors_preflight(methods): + #Render an option method on the class. + + def wrapper(f): + def options(self, *args, **kwargs): # pylint: disable=unused-argument + return {'Allow': 'GET, DELETE, PUT, POST'}, 200, \ + { + #'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': methods, + 'Access-Control-Allow-Headers': 'Authorization, Content-Type, registries-trace-id, ' + 'invitation_token'} + + setattr(f, 'options', options) + return f + + return wrapper + + +def camelback2snake(camel_dict: dict): + """Convert the passed dictionary's keys from camelBack case to snake_case.""" + return decamelize(camel_dict) + + +def snake2camelback(snake_dict: dict): + """Convert the passed dictionary's keys from snake_case to camelBack case.""" + return camelize(snake_dict) + +def getrequiredmemberships(): + membership ='' + for group in MinistryTeamWithKeycloackGroup: + membership+='{0},'.format(group.value) + for procgroup in ProcessingTeamWithKeycloackGroup: + membership+='{0},'.format(procgroup.value) + membership+='Intake Team,Flex Team' + return membership + +def allowedorigins(): + _allowedcors = os.getenv('CORS_ORIGIN') + allowedcors = [] + if ',' in _allowedcors: + for entry in re.split(",",_allowedcors): + allowedcors.append(entry) + return allowedcors + +class Singleton(type): + """Singleton meta.""" + + _instances = {} + + def __call__(cls, *args, **kwargs): + """Call for meta.""" + if cls not in cls._instances: + cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) + return cls._instances[cls] + + +def digitify(payload: str) -> int: + """Return the digits from the string.""" + return int(re.sub(r'\D', '', payload)) + + +def escape_wam_friendly_url(param): + """Return encoded/escaped url.""" + base64_org_name = base64.b64encode(bytes(param, encoding='utf-8')).decode('utf-8') + encode_org_name = urllib.parse.quote(base64_org_name, safe='') + return encode_org_name + +def str_to_bool(s): + if s == 'True': + return True + elif s == 'False': + return False + else: + raise ValueError # evil ValueError that doesn't tell you what the wrong value was + +def canrestictdata(requestid,assignee,isrestricted,israwrequest): + + _isawatcher = False + currentuser = AuthHelper.getuserid() + if israwrequest : + _isawatcher = rawrequestservice().israwrequestwatcher(requestid,currentuser) + else: + _isawatcher = FOIRequestWatcher.isaiaoministryrequestwatcher(requestid,currentuser) + + isiaorestrictedfilemanager = AuthHelper.isiaorestrictedfilemanager() + # print('Current user is {0} , is a watcher: {1} and is file manager {2} '.format(currentuser,_isawatcher,isiaorestrictedfilemanager)) + if(isrestricted and currentuser != assignee and _isawatcher == False and isiaorestrictedfilemanager == False): + return True + else: + return False + +def canrestictdata_ministry(requestid,assignee,isrestricted): + + _isawatcher = False + currentuser = AuthHelper.getuserid() + _isawatcher = FOIRequestWatcher.isaministryministryrequestwatcher(requestid,currentuser) + + isministryrestrictedfilemanager = AuthHelper.isministryrestrictedfilemanager() + # print('Current user is {0}, assignee is {3}, is a watcher: {1} and is file manager {2} '.format(currentuser,_isawatcher,isministryrestrictedfilemanager,assignee)) + if(isrestricted and currentuser != assignee and _isawatcher == False and isministryrestrictedfilemanager == False): + return True + else: + return False + + + + diff --git a/historical-search-api/request_api/utils/util_logging.py b/historical-search-api/request_api/utils/util_logging.py new file mode 100644 index 000000000..a9146d589 --- /dev/null +++ b/historical-search-api/request_api/utils/util_logging.py @@ -0,0 +1,49 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Centralized setup of logging for the service.""" +import os, logging, logging.config + +LOG_ROOT = os.getenv('LOG_ROOT', "DEBUG").upper() +LOG_BASIC = os.getenv('LOG_BASIC', "WARNING").upper() +LOG_TRACING = os.getenv('LOG_TRACING', "ERROR").upper() +LOGGING_FORMAT = '[%(asctime)s] %(levelname)-8s (%(name)s) <%(module)s.py:%(filename)s:%(lineno)d>.%(funcName)s: %(message)s' + +def configure_logging(): + # Set up basic logging for the application. + logging.basicConfig(format=LOGGING_FORMAT, level=string_to_debug_level(LOG_ROOT)) + temp_logger = logging.getLogger() + print("==> Root logger of '" + temp_logger.name + "' set to level: " + LOG_ROOT) + + # Set up defaults. + for name in logging.root.manager.loggerDict: + module_logger = logging.getLogger(name) + module_prefix = name.split('.')[0] if name not in (None,'') else "NOTSET" + module_logger_level = os.getenv(make_env_name(module_prefix), LOG_BASIC).upper() + print("--> Logger " + name + " set to level LOG_BASIC level of " + module_logger_level) + module_logger.setLevel(string_to_debug_level(module_logger_level)) + + # Tracing Log Config + logging.getLogger("jaeger_tracing").setLevel(string_to_debug_level(LOG_TRACING)) + +def make_env_name(name): + return ("LOG_" + name.upper()) + +def string_to_debug_level(debug_string): + level = debug_string.upper() + if level in ('CRITICAL', 'ERROR', 'WARNING', 'INFO','DEBUG'): + result = logging.getLevelName(level) + else: + result = logging.WARNING + return result + diff --git a/historical-search-api/request_api/version.py b/historical-search-api/request_api/version.py new file mode 100644 index 000000000..1408c41e3 --- /dev/null +++ b/historical-search-api/request_api/version.py @@ -0,0 +1,25 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Version of this service in PEP440. + +[N!]N(.N)*[{a|b|rc}N][.postN][.devN] +Epoch segment: N! +Release segment: N(.N)* +Pre-release segment: {a|b|rc}N +Post-release segment: .postN +Development release segment: .devN +""" + +__version__ = '0.1.0a0.dev' # pylint: disable=invalid-name diff --git a/historical-search-api/requirements.txt b/historical-search-api/requirements.txt new file mode 100644 index 000000000..ba46dc574 --- /dev/null +++ b/historical-search-api/requirements.txt @@ -0,0 +1,81 @@ +Flask-Caching==1.10.1 +Flask-Mail==0.9.1 +Flask-Migrate==2.7.0 +Flask-Moment==0.11.0 +Flask-SQLAlchemy==2.5.1 +Flask-Script==2.0.5 +Flask==2.2.5 +Flask-OpenTracing==1.1.0 +Jinja2==3.0.3 +Mako==1.2.2 +MarkupSafe==2.1.4 +SQLAlchemy-Continuum==1.3.11 +SQLAlchemy-Utils==0.36.8 +SQLAlchemy==1.3.24 +Werkzeug==2.3.8 +alembic==1.5.8 +aniso8601==9.0.1 +asyncio-nats-client==0.11.4 +asyncio-nats-streaming==0.4.0 +attrs==20.3.0 +bcrypt==3.2.0 +blinker==1.4 +boto3==1.24.35 +cachelib==0.1.1 +certifi==2023.7.22 +cffi==1.14.5 +chardet==4.0.0 +click==8.1.3 +flask-jwt-oidc==0.3.0 +flask-marshmallow==0.11.0 +flask-restplus +flask_restx +flask-cors==3.0.10 +gunicorn==20.1.0 +idna==2.10 +itsdangerous==2.0.1 +jaeger-client==4.4.0 +jaro-winkler==2.0.3 +jsonschema==3.2.0 +marshmallow-sqlalchemy==0.23.1 +marshmallow==3.0.0rc7 +minio==7.0.2 +opentracing==2.4.0 +protobuf==3.18.3 +psycopg2-binary==2.8.6 +pycparser==2.20 +pyhumps==1.6.1 +pyrsistent==0.17.3 +python-dateutil==2.8.1 +python-dotenv==0.16.0 +python-editor==1.0.4 +python-jose==3.2.0 +pytz==2021.1 +requests==2.31.0 +rsa==4.7.2 +sentry-sdk==1.14.0 +six==1.15.0 +threadloop==1.0.2 +thrift==0.13.0 +tornado==6.1 +flask-expects-json==1.5.0 +redis==4.1.4 +flask_jwt_oidc==0.3.0 +maya==0.6.1 +pyjwt==2.4.0 +aws-requests-auth==0.4.3 +holidays==0.12 +Flask-SocketIO==5.3.6 +Flask-Login==0.5.0 +eventlet==0.35.2 +uritemplate.py==3.0.2 +urllib3==1.26.15 +ndg-httpsclient +pyopenssl +pyasn1 +secure==0.3.0 +boto3==1.24.35 +walrus==0.8.2 +imap-tools==0.56.0 +more-itertools==10.2.0 +-e git+https://github.com/bcgov/sbc-common-components.git#egg=sbc-common-components&subdirectory=python \ No newline at end of file diff --git a/historical-search-api/sample.env b/historical-search-api/sample.env new file mode 100644 index 000000000..eb77dd167 --- /dev/null +++ b/historical-search-api/sample.env @@ -0,0 +1,101 @@ +DATABASE_USERNAME=postgres +DATABASE_PASSWORD= +DATABASE_NAME=postgres +DATABASE_HOST=localhost +DATABASE_PORT=15432 +FLASK_ENV=production +FOI_REQUESTQUEUE_REDISHOST={IP Address} +FOI_REQUESTQUEUE_REDISPORT=6379 +FOI_REQUESTQUEUE_REDISPASSWORD= +FOI_REQUESTQUEUE_REDISCHANNEL=foi-rawrequest +KEYCLOAK_ADMIN_HOST= +KEYCLOAK_ADMIN_REALM= +KEYCLOAK_ADMIN_CLIENT_ID=foi-lob-api +KEYCLOAK_ADMIN_CLIENT_SECRET= +KEYCLOAK_ADMIN_SRVACCOUNT=foisrcaccount +KEYCLOAK_ADMIN_SRVPASSWORD= +KEYCLOAK_ADMIN_INTAKE_GROUPID= +BPM_ENGINE_REST_URL=http://{IP Address}:8000/camunda/engine-rest +BPM_TOKEN_URL=${KEYCLOAK_URL}/auth/realms/${KEYCLOAK_URL_REALM}/protocol/openid-connect/token +BPM_CLIENT_ID=forms-flow-bpm +BPM_CLIENT_SECRET= +JWT_OIDC_WELL_KNOWN_CONFIG=${KEYCLOAK_URL}/auth/realms/${KEYCLOAK_URL_REALM}/.well-known/openid-configuration +JWT_OIDC_AUDIENCE=forms-flow-web +JWT_OIDC_ISSUER=${KEYCLOAK_URL}/auth/realms/${KEYCLOAK_URL_REALM} +JWT_OIDC_ALGORITHMS=RS256 +JWT_OIDC_JWKS_URI=${KEYCLOAK_URL}/auth/realms/${KEYCLOAK_URL_REALM}/protocol/openid-connect/certs +JWT_OIDC_JWKS_CACHE_TIMEOUT=300 +CORS_ORIGIN=http://localhost:8000,http://localhost:9000,http://localhost:3000 +JWT_OIDC_CACHING_ENABLED=True +TEST_INTAKE_USERID=foiintake@idir +TEST_INTAKE_PASSWORD= +TEST_FLEX_USERID=foiflex@idir +TEST_FLEX_PASSWORD= +TEST_MINISTRY_USERID=foiaed@idir +TEST_MINISTRY_PASSWORD= +CACHE_TIMEOUT=3600 +CACHE_ENABLED=Y +CACHE_REDISURL=redis://{username}:{password}@{host}:password + +FOI_REQUESTQUEUE_REDISURL=redis://{username}:{password}@{host}:password + +OSS_S3_FORMS_BUCKET= +OSS_S3_FORMS_ACCESS_KEY_ID= +OSS_S3_FORMS_SECRET_ACCESS_KEY= +OSS_S3_HOST= +OSS_S3_REGION= +OSS_S3_SERVICE= +OSS_S3_ENVIRONMENT=dev +FOI_WEB_PAY_URL= +PAYBC_REF_NUMBER= +PAYBC_PORTAL_URL= +PAYBC_TXN_PREFIX= +PAYBC_API_KEY= +CDOGS_ACCESS_TOKEN= + +# The below setting defines the criteria of days for which the notification to be fetched. +FOI_NOTIFICATION_DAYS=14 +FOI_ADDITIONAL_HOLIDAYS=30-09-XXXX + +#SocketIO push notifications +SOCKETIO_PING_INTERVAL=30 +SOCKETIO_PING_TIMEOUT=5 +#Supported options : True or False +SOCKETIO_LOG_ENABLED=false +#Supported options : REDIS or IN-MEMORY or NONE (Setting as NONE will turn off push notifications). +SOCKETIO_MESSAGE_QTYPE=REDIS +#Redis QType Settings +SOCKETIO_REDISURL=redis://{username}:{password}@{host}:password +SOCKETIO_REDIS_COMMENT_CHANNEL=foi-comment +SOCKETIO_REDIS_HEALTHCHECK_INTERVAL=10 +SOCKETIO_REDIS_CONNECT_TIMEOUT=5 +SOCKETIO_REDIS_SLEEP_TIME=3.0 +SOCKETIO_CONNECT_URL=http://{IP-ADDRESS}:15000 +SOCKETIO_RECONNECTION_DELAY=15000 +SOCKETIO_RECONNECTION_DELAY_MAX=30000 +SOCKETIO_CONNECT_NONCE=stADzpPW9zV7wA7vh9nH6fWt + +#AXIS base URL +AXIS_API_URL= +SOCKETIO_CONNECT_NONCE= + + +#EMAIL SETTINGS +EMAIL_SERVER_IMAP=imap.gmail.com +EMAIL_SERVER_SMTP=smtp.gmail.com +EMAIL_SERVER_SMTP_PORT=587 +EMAIL_SRUSERID= +EMAIL_SRPWD= +EMAIL_SENDER_ADDRESS= +EMAIL_FOLDER_OUTBOX=[Gmail]/Sent Mail +EMAIL_FOLDER_INBOX=Inbox + +EVENT_QUEUE_HOST= +EVENT_QUEUE_PORT= +EVENT_QUEUE_PASSWORD= +EVENT_QUEUE_CONVERSION_STREAMKEY='file-conversion' +EVENT_QUEUE_DEDUPE_STREAMKEY='FOIDEDUPE' + + +FOI_DOCREVIEWER_BASE_API_URL=http://{IP-ADDRESS}:15500 +EVENT_QUEUE_PAGECALCULATOR_STREAM_KEY= diff --git a/historical-search-api/tests/__init__.py b/historical-search-api/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/historical-search-api/tests/conftest.py b/historical-search-api/tests/conftest.py new file mode 100644 index 000000000..7696b3ae1 --- /dev/null +++ b/historical-search-api/tests/conftest.py @@ -0,0 +1,112 @@ +"""Common setup and fixtures for the pytest suite used by this service.""" + +from random import random + +import pytest + +from flask_migrate import Migrate, upgrade +from request_api import db as _db + +from request_api import create_app +from sqlalchemy import event, text +from request_api.auth import jwt as _jwt + +@pytest.fixture(scope='session') +def app(): + """Return a session-wide application configured in TEST mode.""" + _app = create_app() + return _app + + +@pytest.fixture(scope='function') +def app_request(): + """Return a session-wide application configured in TEST mode.""" + _app = create_app() + return _app + + +@pytest.fixture(scope='session') +def client(app): # pylint: disable=redefined-outer-name + """Return a session-wide Flask test client.""" + return app.test_client() + +@pytest.fixture(scope='session') +def jwt(): + """Return a session-wide jwt manager.""" + _jwt.init_app(app) + return _jwt + +@pytest.fixture(scope='session') +def client_ctx(app): # pylint: disable=redefined-outer-name + """Return session-wide Flask test client.""" + with app.test_client() as _client: + yield _client + + +@pytest.fixture(scope='session') +def db(app): # pylint: disable=redefined-outer-name, invalid-name + """Return a session-wide initialised database. + + Drops schema, and recreate. + """ + with app.app_context(): + drop_schema_sql = """DROP SCHEMA public CASCADE; + CREATE SCHEMA public; + GRANT ALL ON SCHEMA public TO postgres; + GRANT ALL ON SCHEMA public TO public; + """ + + sess = _db.session() + #sess.execute(drop_schema_sql) + #sess.commit() + + # ############################################ + # There are 2 approaches, an empty database, or the same one that the app will use + # create the tables + # _db.create_all() + # or + # Use Alembic to load all of the DB revisions including supporting lookup data + # This is the path we'll use in auth_api!! + + # even though this isn't referenced directly, it sets up the internal configs that upgrade needs + + Migrate(app, _db) + upgrade() + + return _db + + +@pytest.fixture(scope='function') +def session(app, db): # pylint: disable=redefined-outer-name, invalid-name + """Return a function-scoped session.""" + with app.app_context(): + conn = db.engine.connect() + txn = conn.begin() + + options = dict(bind=conn, binds={}) + sess = db.create_scoped_session(options=options) + + # establish a SAVEPOINT just before beginning the test + # (http://docs.sqlalchemy.org/en/latest/orm/session_transaction.html#using-savepoint) + sess.begin_nested() + + @event.listens_for(sess(), 'after_transaction_end') + def restart_savepoint(sess2, trans): # pylint: disable=unused-variable + # Detecting whether this is indeed the nested transaction of the test + if trans.nested and not trans._parent.nested: # pylint: disable=protected-access + # Handle where test DOESN'T session.commit(), + sess2.expire_all() + sess.begin_nested() + + db.session = sess + + sql = text('select 1') + sess.execute(sql) + + yield sess + + # Cleanup + sess.remove() + # This instruction rollsback any commit that were executed in the tests. + txn.rollback() + conn.close() diff --git a/historical-search-api/tests/restapi/__init__.py b/historical-search-api/tests/restapi/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/historical-search-api/tests/restapi/test_fee_api.py b/historical-search-api/tests/restapi/test_fee_api.py new file mode 100644 index 000000000..b52a424ae --- /dev/null +++ b/historical-search-api/tests/restapi/test_fee_api.py @@ -0,0 +1,125 @@ +import json + +import pytest + +from request_api.services.hash_service import HashService + + +@pytest.mark.parametrize('fee_code, expected_status_code', [('FOI0001', 200), ('TEST', 404)]) +def test_get_fee(app, client, fee_code, expected_status_code): + response = client.get(f'/api/fees/{fee_code}', content_type='application/json') + assert response.status_code == expected_status_code + +with open('tests/samplerequestjson/rawrequest.json') as x: + rawrequestjson = json.load(x) +def test_create_payment(app, client): + foi_req = client.post(f'/api/foirawrequests', data=json.dumps(rawrequestjson), content_type='application/json') + request_id = foi_req.json.get('id') + fee_code = 'FOI0001' + pay_response = client.post(f'/api/foirawrequests/{request_id}/payments', data=json.dumps({ + 'fee_code': fee_code, + 'quantity': 5 + }), content_type='application/json') + assert pay_response.status_code == 201 + fee_response = client.get(f'/api/fees/{fee_code}?quantity=5', content_type='application/json') + assert pay_response.json.get('total') == fee_response.json.get('total') + assert pay_response.json.get('status') == 'PENDING' + +with open('tests/samplerequestjson/rawrequest.json') as x: + rawrequestjson = json.load(x) +def test_complete_payment(app, client, monkeypatch): + with app.app_context(): + fee_code = 'FOI0001' + quantity = 5 + + total_fee = client.get(f'/api/fees/{fee_code}?quantity={quantity}', content_type='application/json') + + # Mock paybc response to return success message + def mock_paybc_response(self): # pylint: disable=unused-argument; mocks of library methods + return { + 'paymentstatus': 'PAID', + 'trnamount': total_fee.json.get('total') + } + + monkeypatch.setattr('request_api.services.fee_service.FeeService.get_paybc_transaction_details', + mock_paybc_response) + + foi_req = client.post(f'/api/foirawrequests', data=json.dumps(rawrequestjson), + content_type='application/json') + request_id = foi_req.json.get('id') + + pay_response = client.post(f'/api/foirawrequests/{request_id}/payments', data=json.dumps({ + 'fee_code': fee_code, + 'quantity': quantity + }), content_type='application/json') + pay_id = pay_response.json.get('payment_id') + txn_number = f"{app.config.get('PAYBC_TXN_PREFIX')}{pay_id:0>8}" + response_url = f'trnApproved=1&messageText=Approved&trnOrderId=20595&trnAmount=50.00&paymentMethod=CC&' \ + f'cardType=VI&authCode=TEST&trnDate=2021-10-18&pbcTxnNumber={txn_number}' + response_url = f'{response_url}&hashValue={HashService.encode(response_url)}' + # Update payment + pay_response = client.put(f'/api/foirawrequests/{request_id}/payments/{pay_id}', data=json.dumps({ + 'response_url': response_url + }), content_type='application/json') + assert pay_response.json.get('status') == 'PAID' + +class TestResponse: + def __init__(self, status_code, content = None, headers = {}): + self.status_code = status_code + self.content = content + self.headers = headers + +with open('tests/samplerequestjson/rawrequest.json') as x: + rawrequestjson = json.load(x) +def test_generate_receipt(app, client, monkeypatch): + with app.app_context(): + + foi_req = client.post(f'/api/foirawrequests', data=json.dumps(rawrequestjson), content_type='application/json') + + request_id = foi_req.json.get('id') + fee_code = 'FOI0001' + pay_response = client.post(f'/api/foirawrequests/{request_id}/payments', data=json.dumps({ + 'fee_code': fee_code, + 'quantity': 5 + }), content_type='application/json') + payment_id = pay_response.json.get('payment_id') + + def moch_check_paid(self): + return True + + monkeypatch.setattr('request_api.services.fee_service.FeeService.check_if_paid', + moch_check_paid) + + def mock_get_token(self): + return 'token' + + monkeypatch.setattr('request_api.services.cdogs_api_service.CdogsApiService._get_access_token', + mock_get_token) + + def mock_check_hashed(self, template_hash_code): + return False + + monkeypatch.setattr('request_api.services.cdogs_api_service.CdogsApiService.check_template_cached', + mock_check_hashed) + + def mock_upload_template(self, headers, url, template): + return TestResponse( + status_code= 200, + headers= {'X-Template-Hash': "58G94G"} + ); + + monkeypatch.setattr('request_api.services.cdogs_api_service.CdogsApiService._post_upload_template', + mock_upload_template) + + def mock_generate_receipt(self, json_request_body, headers, ur): + return TestResponse( + content= bytearray([2, 3, 5, 7]), + status_code= 200, + ); + + monkeypatch.setattr('request_api.services.cdogs_api_service.CdogsApiService._post_generate_receipt', + mock_generate_receipt) + receipt_response = client.post(f'/api/foirawrequests/{request_id}/payments/{payment_id}/receipt', data=json.dumps({ + 'requestData': {} + }), content_type='application/json') + assert receipt_response.status_code == 200 \ No newline at end of file diff --git a/historical-search-api/tests/restapi/test_foiassignees_api.py b/historical-search-api/tests/restapi/test_foiassignees_api.py new file mode 100644 index 000000000..4a8d5686a --- /dev/null +++ b/historical-search-api/tests/restapi/test_foiassignees_api.py @@ -0,0 +1,55 @@ +import json +import uuid +import os +import requests +import ast + +TEST_USER_PAYLOAD = { + 'client_id': 'forms-flow-web', + 'grant_type': 'password', + 'username' : os.getenv('TEST_INTAKE_USERID'), + 'password': os.getenv('TEST_INTAKE_PASSWORD') +} + +def factory_auth_header(app, client): + url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) + x = requests.post(url, TEST_USER_PAYLOAD, verify=True).content.decode('utf-8') + return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} + +def test_ping(app, client): + response = client.get('/api/healthz') + assert response.status_code == 200 + +def test_get_foiassigneesforgeneralopen(app, client): + response = client.get('/api/foiassignees/general/open', headers=factory_auth_header(app, client), content_type='application/json') + jsondata = json.loads(response.data) + assert response.status_code == 200 and len(jsondata) >=1 + +def test_get_foiassigneesforgeneralcfr(app, client): + response = client.get('/api/foiassignees/general/callforrecords/edu', headers=factory_auth_header(app, client), content_type='application/json') + jsondata = json.loads(response.data) + assert response.status_code == 200 and len(jsondata) >=1 + +def test_get_foiassigneesforpersonalopen(app, client): + response = client.get('/api/foiassignees/personal/open', headers=factory_auth_header(app, client), content_type='application/json') + jsondata = json.loads(response.data) + assert response.status_code == 200 and len(jsondata) >=1 + +def test_get_foiassigneesforpersonalinvalidstatus(app, client): + response = client.get('/api/foiassignees/test', headers=factory_auth_header(app, client), content_type='application/json') + assert response.status_code == 404 + +def test_get_foiassigneesforgroup(app, client): + response = client.get('/api/foiassignees/group/intaketeam', headers=factory_auth_header(app, client), content_type='application/json') #invalid condition + jsondata = json.loads(response.data) + assert response.status_code == 200 and len(jsondata) >=1 + +def test_get_foiassigneesgeneralteams(app, client): + response = client.get('/api/foiassignees/processingteams/general', headers=factory_auth_header(app, client), content_type='application/json') #invalid condition + jsondata = json.loads(response.data) + assert response.status_code == 200 and len(jsondata) >=1 + +def test_get_foiassigneespersonalteams(app, client): + response = client.get('/api/foiassignees/processingteams/personal', headers=factory_auth_header(app, client), content_type='application/json') #invalid condition + jsondata = json.loads(response.data) + assert response.status_code == 200 and len(jsondata) >=1 \ No newline at end of file diff --git a/historical-search-api/tests/restapi/test_foiaudit_api.py b/historical-search-api/tests/restapi/test_foiaudit_api.py new file mode 100644 index 000000000..637b310c1 --- /dev/null +++ b/historical-search-api/tests/restapi/test_foiaudit_api.py @@ -0,0 +1,66 @@ +import json +import os +import uuid +import requests +import ast + +TEST_INTAKEUSER_PAYLOAD = { + 'client_id': 'forms-flow-web', + 'grant_type': 'password', + 'username' : os.getenv('TEST_INTAKE_USERID'), + 'password': os.getenv('TEST_INTAKE_PASSWORD') +} + +TEST_FLEXUSER_PAYLOAD = { + 'client_id': 'forms-flow-web', + 'grant_type': 'password', + 'username' : os.getenv('TEST_FLEX_USERID'), + 'password': os.getenv('TEST_FLEX_PASSWORD') +} + +def factory_intake_auth_header(app, client): + url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) + x = requests.post(url, TEST_INTAKEUSER_PAYLOAD, verify=True).content.decode('utf-8') + return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} + +def factory_flex_auth_header(app, client): + url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) + x = requests.post(url, TEST_FLEXUSER_PAYLOAD, verify=True).content.decode('utf-8') + return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} + +def test_ping(app, client): + response = client.get('/api/healthz') + assert response.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as f: + requestjson = json.load(f) +def test_get_foiauditfordesccase1(app, client): + response = client.post('/api/foirawrequests',data=json.dumps(requestjson), content_type='application/json') + jsondata = json.loads(response.data) + response = client.get('/api/foiaudit/rawrequest/'+str(jsondata["id"])+'/description', headers=factory_intake_auth_header(app, client), content_type='application/json') + jsondata = json.loads(response.data) + assert response.status_code == 200 and len(jsondata['audit']) >=1 + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: + generalrequestjson = json.load(y) + generalupdaterequestjson = json.load(z) + rawrequestjson = json.load(x) +def test_get_foiauditfordesccase2(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest['requeststatusid'] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + foiupdaterequest = generalupdaterequestjson + foiupdaterequest["id"] = str(foijsondata["id"]) + foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + response = client.get('/api/foiaudit/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/description', headers=factory_flex_auth_header(app, client), content_type='application/json') + jsondata = json.loads(response.data) + assert response.status_code == 200 and len(jsondata['audit']) >=1 + +def test_get_foiauditfordescinvalid(app, client): + response = client.get('/api/foiaudit/invalid/1/invalid', headers=factory_intake_auth_header(app, client), content_type='application/json') + jsondata = json.loads(response.data) + assert response.status_code == 400 and len(jsondata) >=1 diff --git a/historical-search-api/tests/restapi/test_foicfrfee_api.py b/historical-search-api/tests/restapi/test_foicfrfee_api.py new file mode 100644 index 000000000..b3d1576ec --- /dev/null +++ b/historical-search-api/tests/restapi/test_foicfrfee_api.py @@ -0,0 +1,139 @@ +import json +import uuid +import os +import requests +import ast + +TEST_INTAKE_USER_PAYLOAD = { + 'client_id': 'forms-flow-web', + 'grant_type': 'password', + 'username' : os.getenv('TEST_INTAKE_USERID'), + 'password': os.getenv('TEST_INTAKE_PASSWORD') +} + +TEST_MINISTRY_USER_PAYLOAD = { + 'client_id': 'forms-flow-web', + 'grant_type': 'password', + 'username' : os.getenv('TEST_MINISTRY_USERID'), + 'password': os.getenv('TEST_MINISTRY_PASSWORD') +} + +TEST_CFR_FEE_SCHEMA = { + "feedata":{ + "amountpaid":11.00, + "totalamountdue":2.00, + "estimatedlocatinghrs": 11.00, + "actuallocatinghrs": 11.00, + "estimatedproducinghrs": 11.00, + "actualproducinghrs": 11.00, + "estimatediaopreparinghrs": 11.44444433330440, + "estimatedministrypreparinghrs": 11.44444433330440, + "actualiaopreparinghrs": 11.00, + "actualministrypreparinghrs": 11.00, + "estimatedelectronicpages": 11.00, + "actualelectronicpages": 11.00, + "estimatedhardcopypages": 11.00, + "actualhardcopypages": 9 + }, + "overallsuggestions":"test" +} + +TEST_CFR_FEE_SANCTION_SCHEMA = { + "feedata":{ + "amountpaid":5.00 + }, + "status":"approved" +} + + +RAW_REQUEST_POST_URL = '/api/foirawrequests' +REQUEST_POST_URL = '/api/foirequests' +RAW_REQUEST_JSON = 'tests/samplerequestjson/rawrequest.json' +FOI_REQUEST_GENERAL_JSON = 'tests/samplerequestjson/foirequest-general.json' +FOI_REQUEST_GENERAL_UPDATE_JSON = 'tests/samplerequestjson/foirequest-general-update.json' +FOI_REQUEST_GENERAL_CFR_JSON = 'tests/samplerequestjson/foirequest-general-CFR.json' +WF_URL_BASE = '/api/foirawrequestbpm/addwfinstanceid/' +CREATE_CFR_FEE_URL = '/api/foicfrfee/ministryrequest/' +GET_CFR_FEE_URL = '/api/foicfrfee/ministryrequest/' + +CONTENT_TYPE = 'application/json' + +def factory_intake_auth_header(app, client): + url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) + x = requests.post(url, TEST_INTAKE_USER_PAYLOAD, verify=True).content.decode('utf-8') + return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} + + +def factory_ministry_auth_header(app, client): + url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) + x = requests.post(url, TEST_MINISTRY_USER_PAYLOAD, verify=True).content.decode('utf-8') + return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} + +def test_ping(app, client): + response = client.get('/api/healthz') + assert response.status_code == 200 + +with open(RAW_REQUEST_JSON) as x, open(FOI_REQUEST_GENERAL_JSON) as y, open(FOI_REQUEST_GENERAL_UPDATE_JSON) as z, open(FOI_REQUEST_GENERAL_CFR_JSON) as v: + generalcfrrequestjson = json.load(v) + generalupdaterequestjson = json.load(z) + generalrequestjson = json.load(y) + rawrequestjson = json.load(x) + + +def test_foicfrfees_create(app, client): + rawresponse = client.post(RAW_REQUEST_POST_URL,data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + jsondata = json.loads(rawresponse.data) + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post(REQUEST_POST_URL,data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put(WF_URL_BASE+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + cfrrequestposturl = REQUEST_POST_URL+'/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]) + cfrrequest = generalcfrrequestjson + cfrrequest["requeststatusid"] = 2 + foicfrrequestresponse = client.post(cfrrequestposturl,data=json.dumps(cfrrequest), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + foicfrrequestjsondata = json.loads(foicfrrequestresponse.data) + createfoicfrfeesresponse = client.post(CREATE_CFR_FEE_URL+str(foicfrrequestjsondata["ministryRequests"][0]["id"]),data=json.dumps(TEST_CFR_FEE_SCHEMA), headers=factory_ministry_auth_header(app, client), content_type=CONTENT_TYPE) + assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 + + +def test_foicfrfees_sanction(app, client): + rawresponse = client.post(RAW_REQUEST_POST_URL,data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + jsondata = json.loads(rawresponse.data) + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post(REQUEST_POST_URL,data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put(WF_URL_BASE+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + cfrrequestposturl = REQUEST_POST_URL+'/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]) + cfrrequest = generalcfrrequestjson + cfrrequest["requeststatusid"] = 2 + foicfrrequestresponse = client.post(cfrrequestposturl,data=json.dumps(cfrrequest), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + foicfrrequestjsondata = json.loads(foicfrrequestresponse.data) + createfoicfrfeesresponse = client.post(CREATE_CFR_FEE_URL+str(foicfrrequestjsondata["ministryRequests"][0]["id"]),data=json.dumps(TEST_CFR_FEE_SCHEMA), headers=factory_ministry_auth_header(app, client), content_type=CONTENT_TYPE) + sanctionfoicfrfeesresponse = client.post(CREATE_CFR_FEE_URL+str(foicfrrequestjsondata["ministryRequests"][0]["id"])+"/sanction",data=json.dumps(TEST_CFR_FEE_SANCTION_SCHEMA), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 + + +def test_foicfrfees_get(app, client): + rawresponse = client.post(RAW_REQUEST_POST_URL,data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + jsondata = json.loads(rawresponse.data) + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post(REQUEST_POST_URL,data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put(WF_URL_BASE+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + cfrrequestposturl = REQUEST_POST_URL+'/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]) + cfrrequest = generalcfrrequestjson + cfrrequest["requeststatusid"] = 2 + foicfrrequestresponse = client.post(cfrrequestposturl,data=json.dumps(cfrrequest), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + foicfrrequestjsondata = json.loads(foicfrrequestresponse.data) + createfoicfrfeesresponse = client.post(CREATE_CFR_FEE_URL+str(foicfrrequestjsondata["ministryRequests"][0]["id"]),data=json.dumps(TEST_CFR_FEE_SCHEMA), headers=factory_ministry_auth_header(app, client), content_type=CONTENT_TYPE) + getfoicfrfeeresponse = client.get(CREATE_CFR_FEE_URL+str(foicfrrequestjsondata["ministryRequests"][0]["id"]), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 diff --git a/historical-search-api/tests/restapi/test_foicomment_api.py b/historical-search-api/tests/restapi/test_foicomment_api.py new file mode 100644 index 000000000..b0996acb8 --- /dev/null +++ b/historical-search-api/tests/restapi/test_foicomment_api.py @@ -0,0 +1,168 @@ +import json +import uuid +import os +import requests +import ast + +TEST_USER_PAYLOAD = { + 'client_id': 'forms-flow-web', + 'grant_type': 'password', + 'username' : os.getenv('TEST_INTAKE_USERID'), + 'password': os.getenv('TEST_INTAKE_PASSWORD') +} + +def factory_auth_header(app, client): + url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) + x = requests.post(url, TEST_USER_PAYLOAD, verify=True).content.decode('utf-8') + return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} + +def test_ping(app, client): + response = client.get('/api/healthz') + assert response.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as x: + rawrequestjson = json.load(x) +def test_foirawcomment(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + commentjson = { + "requestid":str(jsondata["id"]), + "comment": "test comment", + "isactive": True + } + createcommentresponse = client.post('/api/foicomment/rawrequest', data=json.dumps(commentjson), headers=factory_auth_header(app, client), content_type='application/json') + getcommentresponse = client.get('/api/foicomment/rawrequest/'+str(jsondata["id"]), headers=factory_auth_header(app, client), content_type='application/json') + assert rawresponse.status_code == 200 and createcommentresponse.status_code == 200 and getcommentresponse.status_code == 200 + + +with open('tests/samplerequestjson/rawrequest.json') as x: + rawrequestjson = json.load(x) +def test_foirawcommentdisable(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + commentjson = { + "requestid":str(jsondata["id"]), + "comment": "test comment", + "isactive": True + } + createcommentresponse = client.post('/api/foicomment/rawrequest', data=json.dumps(commentjson), headers=factory_auth_header(app, client), content_type='application/json') + comment = json.loads(createcommentresponse.data) + disablecommentresponse = client.put('/api/foicomment/rawrequest/'+str(comment["id"])+'/disable',data=json.dumps(commentjson), headers=factory_auth_header(app, client), content_type='application/json') + assert rawresponse.status_code == 200 and createcommentresponse.status_code == 200 and disablecommentresponse.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as x: + rawrequestjson = json.load(x) +def test_foirawcommentupdate(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + commentjson = { + "requestid":str(jsondata["id"]), + "comment": "test comment", + "isactive": True + } + createcommentresponse = client.post('/api/foicomment/rawrequest', data=json.dumps(commentjson), headers=factory_auth_header(app, client), content_type='application/json') + comment = json.loads(createcommentresponse.data) + updatecommentjson = { + "comment": "test comment - updated", + } + updatecommentresponse = client.put('/api/foicomment/rawrequest/'+str(comment["id"]),data=json.dumps(updatecommentjson), headers=factory_auth_header(app, client), content_type='application/json') + assert rawresponse.status_code == 200 and createcommentresponse.status_code == 200 and updatecommentresponse.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y: + generalrequestjson = json.load(y) + rawrequestjson = json.load(x) +def test_foiministrycomment(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + commentjson = { + "ministryrequestid":str(foijsondata["id"]), + "comment": "test comment", + "isactive": True + } + createcommentresponse = client.post('/api/foicomment/ministryrequest', data=json.dumps(commentjson), headers=factory_auth_header(app, client), content_type='application/json') + getcommentresponse = client.get('/api/foicomment/ministryrequest/'+str(foijsondata["id"]), headers=factory_auth_header(app, client), content_type='application/json') + assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and createcommentresponse.status_code == 200 and getcommentresponse.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y: + generalrequestjson = json.load(y) + rawrequestjson = json.load(x) +def test_foiministrycommentdisable(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + commentjson = { + "ministryrequestid":str(foijsondata["id"]), + "comment": "test comment", + "isactive": True + } + createcommentresponse = client.post('/api/foicomment/ministryrequest', data=json.dumps(commentjson), headers=factory_auth_header(app, client), content_type='application/json') + comment = json.loads(createcommentresponse.data) + childcommentjson = { + "ministryrequestid":str(foijsondata["id"]), + "comment": "test comment", + "isactive": True, + "parentcommentid": str(comment["id"]) + } + createcommentresponse2 = client.post('/api/foicomment/ministryrequest', data=json.dumps(childcommentjson), headers=factory_auth_header(app, client), content_type='application/json') + disablecommentresponse = client.put('/api/foicomment/rawrequest/'+str(comment["id"])+'/disable',data=json.dumps(commentjson), headers=factory_auth_header(app, client), content_type='application/json') + assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and createcommentresponse.status_code == 200 and createcommentresponse2.status_code == 200 and disablecommentresponse.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y: + generalrequestjson = json.load(y) + rawrequestjson = json.load(x) +def test_foiministrycommentupdate(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + commentjson = { + "ministryrequestid":str(foijsondata["id"]), + "comment": "test comment", + "isactive": True + } + createcommentresponse = client.post('/api/foicomment/ministryrequest', data=json.dumps(commentjson), headers=factory_auth_header(app, client), content_type='application/json') + comment = json.loads(createcommentresponse.data) + updatecommentjson = { + "comment": "test comment - updated", + } + updatecommentresponse = client.put('/api/foicomment/ministryrequest/'+str(comment["id"]),data=json.dumps(updatecommentjson), headers=factory_auth_header(app, client), content_type='application/json') + assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and createcommentresponse.status_code == 200 and updatecommentresponse.status_code == 200 + + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y: + generalrequestjson = json.load(y) + rawrequestjson = json.load(x) +def test_foiministrycommentmigrate(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + commentjson = { + "requestid":str(jsondata["id"]), + "comment": "test comment", + "isactive": True + } + createcommentresponse1 = client.post('/api/foicomment/rawrequest', data=json.dumps(commentjson), headers=factory_auth_header(app, client), content_type='application/json') + comment = json.loads(createcommentresponse1.data) + childcommentjson = { + "requestid":str(jsondata["id"]), + "comment": "test comment", + "isactive": True, + "parentcommentid": str(comment["id"]) + } + createcommentresponse2 = client.post('/api/foicomment/rawrequest', data=json.dumps(childcommentjson), headers=factory_auth_header(app, client), content_type='application/json') + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_auth_header(app, client), content_type='application/json') + assert rawresponse.status_code == 200 and createcommentresponse1.status_code == 200 and createcommentresponse2.status_code == 200 and foiresponse.status_code == 200 \ No newline at end of file diff --git a/historical-search-api/tests/restapi/test_foidocument_api.py b/historical-search-api/tests/restapi/test_foidocument_api.py new file mode 100644 index 000000000..e7caa2954 --- /dev/null +++ b/historical-search-api/tests/restapi/test_foidocument_api.py @@ -0,0 +1,360 @@ +import json +import uuid +import os +import requests +import ast + +TEST_INTAKE_USER_PAYLOAD = { + 'client_id': 'forms-flow-web', + 'grant_type': 'password', + 'username' : os.getenv('TEST_INTAKE_USERID'), + 'password': os.getenv('TEST_INTAKE_PASSWORD') +} + +TEST_MINISTRY_USER_PAYLOAD = { + 'client_id': 'forms-flow-web', + 'grant_type': 'password', + 'username' : os.getenv('TEST_MINISTRY_USERID'), + 'password': os.getenv('TEST_MINISTRY_PASSWORD') +} + +def factory_intake_auth_header(app, client): + url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) + x = requests.post(url, TEST_INTAKE_USER_PAYLOAD, verify=True).content.decode('utf-8') + return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} + +def factory_ministry_auth_header(app, client): + url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) + x = requests.post(url, TEST_MINISTRY_USER_PAYLOAD, verify=True).content.decode('utf-8') + return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} + +def test_ping(app, client): + response = client.get('/api/healthz') + assert response.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z, open('tests/samplerequestjson/foirequest-ministry-general-update.json') as v: + generalministryrequestjson = json.load(v) + generalupdaterequestjson = json.load(z) + generalrequestjson = json.load(y) + rawrequestjson = json.load(x) +def test_foiministrydocument_list(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type='application/json') + foiministryrequest = generalministryrequestjson + foiministryrequest["id"] = str(foijsondata["id"]) + foiministryrequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiministryrequest["requeststatusid"] = 2 + foiministryresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(foiministryrequest), headers=factory_intake_auth_header(app, client), content_type='application/json') + foiministryrequest2 = generalministryrequestjson + foiministryrequest2["id"] = str(foijsondata["id"]) + foiministryrequest2["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiministryrequest2["documents"] = [ + { + "category": "cfr-feeassessed", + "documentpath":"/EDUC/"+str(foijsondata["ministryRequests"][0]["filenumber"])+"/cfr-review/test.docx", + "filename":"test.docx" + } + ] + foiministryrequest2["requeststatusid"] = 7 + foiministryresponse2 = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(foiministryrequest2), headers=factory_intake_auth_header(app, client), content_type='application/json') + getdocumentsresponse = client.get('/api/foidocument/ministryrequest/'+str(foijsondata["id"]), headers=factory_ministry_auth_header(app, client), content_type='application/json') + assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiministryresponse.status_code == 200 and foiministryresponse2.status_code == 200 and getdocumentsresponse.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z, open('tests/samplerequestjson/foirequest-ministry-general-update.json') as v: + generalministryrequestjson = json.load(v) + generalupdaterequestjson = json.load(z) + generalrequestjson = json.load(y) + rawrequestjson = json.load(x) +def test_foiministrydocument_rename(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type='application/json') + foiministryrequest = generalministryrequestjson + foiministryrequest["id"] = str(foijsondata["id"]) + foiministryrequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiministryrequest["requeststatusid"] = 2 + foiministryresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(foiministryrequest), headers=factory_intake_auth_header(app, client), content_type='application/json') + foiministryrequest2 = generalministryrequestjson + foiministryrequest2["id"] = str(foijsondata["id"]) + foiministryrequest2["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiministryrequest2["documents"] = [ + { + "category": "cfr-feeassessed", + "documentpath":"/EDUC/"+str(foijsondata["ministryRequests"][0]["filenumber"])+"/cfr-review/test.docx", + "filename":"test2.docx" + } + ] + foiministryrequest2["requeststatusid"] = 7 + foiministryresponse2 = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(foiministryrequest2), headers=factory_intake_auth_header(app, client), content_type='application/json') + getdocumentsresponse = client.get('/api/foidocument/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]), headers=factory_ministry_auth_header(app, client), content_type='application/json') + getdocumentsresponsejsondata = json.loads(getdocumentsresponse.data) + renamedocumentjson = { + "filename": "newname.docx" + } + renamedocumentresponse = client.post('/api/foidocument/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/documentid/'+str(getdocumentsresponsejsondata[0]['foiministrydocumentid'])+'/rename',data=json.dumps(renamedocumentjson), headers=factory_ministry_auth_header(app, client), content_type='application/json') + assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiministryresponse.status_code == 200 and foiministryresponse2.status_code == 200 and getdocumentsresponse.status_code == 200 and renamedocumentresponse.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z, open('tests/samplerequestjson/foirequest-ministry-general-update.json') as v: + generalministryrequestjson = json.load(v) + generalupdaterequestjson = json.load(z) + generalrequestjson = json.load(y) + rawrequestjson = json.load(x) +def test_foiministrydocument_replace(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type='application/json') + foiministryrequest = generalministryrequestjson + foiministryrequest["id"] = str(foijsondata["id"]) + foiministryrequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiministryrequest["requeststatusid"] = 2 + foiministryresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(foiministryrequest), headers=factory_intake_auth_header(app, client), content_type='application/json') + foiministryrequest2 = generalministryrequestjson + foiministryrequest2["id"] = str(foijsondata["id"]) + foiministryrequest2["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiministryrequest2["documents"] = [ + { + "category": "cfr-feeassessed", + "documentpath":"/EDUC/"+str(foijsondata["ministryRequests"][0]["filenumber"])+"/cfr-review/test.docx", + "filename":"test3.docx" + } + ] + foiministryrequest2["requeststatusid"] = 7 + foiministryresponse2 = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(foiministryrequest2), headers=factory_intake_auth_header(app, client), content_type='application/json') + getdocumentsresponse = client.get('/api/foidocument/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]), headers=factory_ministry_auth_header(app, client), content_type='application/json') + getdocumentsresponsejsondata = json.loads(getdocumentsresponse.data) + replacedocumentjson = { + "category": "cfr-feeassessed", + "documentpath":"/EDUC/"+str(foijsondata["ministryRequests"][0]["filenumber"])+"/cfr-review/testnew.docx", + "filename":"testnew.docx" + } + replacedocumentresponse = client.post('/api/foidocument/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/documentid/'+str(getdocumentsresponsejsondata[0]['foiministrydocumentid'])+'/replace',data=json.dumps(replacedocumentjson), headers=factory_ministry_auth_header(app, client), content_type='application/json') + assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiministryresponse.status_code == 200 and foiministryresponse2.status_code == 200 and getdocumentsresponse.status_code == 200 and replacedocumentresponse.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z, open('tests/samplerequestjson/foirequest-ministry-general-update.json') as v: + generalministryrequestjson = json.load(v) + generalupdaterequestjson = json.load(z) + generalrequestjson = json.load(y) + rawrequestjson = json.load(x) +def test_foiministrydocument_delete(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type='application/json') + foiministryrequest = generalministryrequestjson + foiministryrequest["id"] = str(foijsondata["id"]) + foiministryrequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiministryrequest["requeststatusid"] = 2 + foiministryresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(foiministryrequest), headers=factory_intake_auth_header(app, client), content_type='application/json') + foiministryrequest2 = generalministryrequestjson + foiministryrequest2["id"] = str(foijsondata["id"]) + foiministryrequest2["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiministryrequest2["documents"] = [ + { + "category": "cfr-feeassessed", + "documentpath":"/EDUC/"+str(foijsondata["ministryRequests"][0]["filenumber"])+"/cfr-review/test.docx", + "filename":"test.docx" + } + ] + foiministryrequest2["requeststatusid"] = 7 + foiministryresponse2 = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(foiministryrequest2), headers=factory_intake_auth_header(app, client), content_type='application/json') + getdocumentsresponse = client.get('/api/foidocument/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]), headers=factory_ministry_auth_header(app, client), content_type='application/json') + getdocumentsresponsejsondata = json.loads(getdocumentsresponse.data) + deletedocumentresponse = client.post('/api/foidocument/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/documentid/'+str(getdocumentsresponsejsondata[0]['foiministrydocumentid'])+'/delete', headers=factory_ministry_auth_header(app, client), content_type='application/json') + assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiministryresponse.status_code == 200 and foiministryresponse2.status_code == 200 and getdocumentsresponse.status_code == 200 and deletedocumentresponse.status_code == 200 + + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z, open('tests/samplerequestjson/foirequest-ministry-general-update.json') as v: + generalministryrequestjson = json.load(v) + generalupdaterequestjson = json.load(z) + generalrequestjson = json.load(y) + rawrequestjson = json.load(x) +def test_foiministrydocument_create(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type='application/json') + documentsschema = { + "documents": [ + { + "filename":"doc2.docx", + "documentpath": "/EDU-90909/doc2.docx", + "category": "new" + }, + { + "filename":"doc3.docx", + "documentpath": "/EDU-90909/doc3.docx", + "category": "new-doc3" + } + ] +} + foiministrydocresponse = client.post('/api/foidocument/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(documentsschema), headers=factory_intake_auth_header(app, client), content_type='application/json') + assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiministrydocresponse.status_code == 200 + + + +with open('tests/samplerequestjson/rawrequest.json') as x: + rawrequestjson = json.load(x) +def test_foirawdocument_list(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type='application/json') + documentsschema = { + "documents": [ + { + "filename":"doc2.docx", + "documentpath": "/EDU-90909/doc2.docx", + "category": "new" + }, + { + "filename":"doc3.docx", + "documentpath": "/EDU-90909/doc3.docx", + "category": "new-doc3" + } + ] +} + foirawdocresponse = client.post('/api/foidocument/rawrequest/'+str(jsondata["id"]),data=json.dumps(documentsschema), headers=factory_intake_auth_header(app, client), content_type='application/json') + getdocumentsresponse = client.get('/api/foidocument/rawrequest/'+str(jsondata["id"]), headers=factory_intake_auth_header(app, client), content_type='application/json') + assert rawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foirawdocresponse.status_code == 200 and getdocumentsresponse.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as x: + rawrequestjson = json.load(x) +def test_foirawdocument_rename(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type='application/json') + documentsschema = { + "documents": [ + { + "filename":"doc2.docx", + "documentpath": "/EDU-90909/doc2.docx", + "category": "new" + }, + { + "filename":"doc3.docx", + "documentpath": "/EDU-90909/doc3.docx", + "category": "new-doc3" + } + ] +} + foirawdocresponse = client.post('/api/foidocument/rawrequest/'+str(jsondata["id"]),data=json.dumps(documentsschema), headers=factory_intake_auth_header(app, client), content_type='application/json') + getdocumentsresponse = client.get('/api/foidocument/rawrequest/'+str(jsondata["id"]), headers=factory_intake_auth_header(app, client), content_type='application/json') + getdocumentsresponsejsondata = json.loads(getdocumentsresponse.data) + renamedocumentjson = { + "filename": "newname.docx" + } + renamedocumentresponse = client.post('/api/foidocument/rawrequest/'+str(jsondata["id"])+'/documentid/'+str(getdocumentsresponsejsondata[0]['foidocumentid'])+'/rename',data=json.dumps(renamedocumentjson), headers=factory_intake_auth_header(app, client), content_type='application/json') + assert rawresponse.status_code == 200 and foirawdocresponse.status_code == 200 and wfupdateresponse.status_code == 200 and getdocumentsresponse.status_code == 200 and renamedocumentresponse.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as x: + rawrequestjson = json.load(x) +def test_foirawdocument_replace(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type='application/json') + documentsschema = { + "documents": [ + { + "filename":"doc2.docx", + "documentpath": "/EDU-90909/doc2.docx", + "category": "new" + }, + { + "filename":"doc3.docx", + "documentpath": "/EDU-90909/doc3.docx", + "category": "new-doc3" + } + ] +} + foirawdocresponse = client.post('/api/foidocument/rawrequest/'+str(jsondata["id"]),data=json.dumps(documentsschema), headers=factory_intake_auth_header(app, client), content_type='application/json') + getdocumentsresponse = client.get('/api/foidocument/rawrequest/'+str(jsondata["id"]), headers=factory_intake_auth_header(app, client), content_type='application/json') + getdocumentsresponsejsondata = json.loads(getdocumentsresponse.data) + replacedocumentjson = { + "documentpath":"/EDUC/"+str(jsondata["id"])+"/intake/testnew.docx", + "filename":"testnew.docx" + } + replacedocumentresponse = client.post('/api/foidocument/rawrequest/'+str(jsondata["id"])+'/documentid/'+str(getdocumentsresponsejsondata[0]['foidocumentid'])+'/replace',data=json.dumps(replacedocumentjson), headers=factory_intake_auth_header(app, client), content_type='application/json') + assert rawresponse.status_code == 200 and foirawdocresponse.status_code == 200 and wfupdateresponse.status_code == 200 and getdocumentsresponse.status_code == 200 and replacedocumentresponse.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as x: + rawrequestjson = json.load(x) +def test_foirawdocument_delete(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type='application/json') + documentsschema = { + "documents": [ + { + "filename":"doc2.docx", + "documentpath": "/EDU-90909/doc2.docx", + "category": "new" + }, + { + "filename":"doc3.docx", + "documentpath": "/EDU-90909/doc3.docx", + "category": "new-doc3" + } + ] +} + foirawdocresponse = client.post('/api/foidocument/rawrequest/'+str(jsondata["id"]),data=json.dumps(documentsschema), headers=factory_intake_auth_header(app, client), content_type='application/json') + getdocumentsresponse = client.get('/api/foidocument/rawrequest/'+str(jsondata["id"]), headers=factory_intake_auth_header(app, client), content_type='application/json') + getdocumentsresponsejsondata = json.loads(getdocumentsresponse.data) + deletedocumentresponse = client.post('/api/foidocument/rawrequest/'+str(jsondata["id"])+'/documentid/'+str(getdocumentsresponsejsondata[0]['foidocumentid'])+'/delete', headers=factory_intake_auth_header(app, client), content_type='application/json') + assert rawresponse.status_code == 200 and foirawdocresponse.status_code == 200 and wfupdateresponse.status_code == 200 and getdocumentsresponse.status_code == 200 and deletedocumentresponse.status_code == 200 + + +with open('tests/samplerequestjson/rawrequest.json') as x: + rawrequestjson = json.load(x) +def test_foirawdocument_create(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type='application/json') + documentsschema = { + "documents": [ + { + "filename":"doc2.docx", + "documentpath": "/EDU-90909/doc2.docx", + "category": "new" + }, + { + "filename":"doc3.docx", + "documentpath": "/EDU-90909/doc3.docx", + "category": "new-doc3" + } + ] +} + foirawdocresponse = client.post('/api/foidocument/rawrequest/'+str(jsondata["id"]),data=json.dumps(documentsschema), headers=factory_intake_auth_header(app, client), content_type='application/json') + assert rawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foirawdocresponse.status_code == 200 diff --git a/historical-search-api/tests/restapi/test_foiextension_api.py b/historical-search-api/tests/restapi/test_foiextension_api.py new file mode 100644 index 000000000..baa845326 --- /dev/null +++ b/historical-search-api/tests/restapi/test_foiextension_api.py @@ -0,0 +1,159 @@ +import json +import uuid +import os +import requests +import ast + +TEST_INTAKE_USER_PAYLOAD = { + 'client_id': 'forms-flow-web', + 'grant_type': 'password', + 'username' : os.getenv('TEST_INTAKE_USERID'), + 'password': os.getenv('TEST_INTAKE_PASSWORD') +} + +TEST_MINISTRY_USER_PAYLOAD = { + 'client_id': 'forms-flow-web', + 'grant_type': 'password', + 'username' : os.getenv('TEST_MINISTRY_USERID'), + 'password': os.getenv('TEST_MINISTRY_PASSWORD') +} + +NEW_EXTENSION_SCHEMA = { + "extensionreasonid": 5, + "extendedduedays": 30, + "extendedduedate": "2022-03-04" + } + +RAW_REQUEST_JSON = 'tests/samplerequestjson/rawrequest.json' +FOI_REQUEST_GENERAL_JSON = 'tests/samplerequestjson/foirequest-general.json' +FOI_REQUEST_GENERAL_UPDATE_JSON = 'tests/samplerequestjson/foirequest-general-update.json' +FOI_REQUEST_GENERAL_MINISTRY_UPDATE_JSON = 'tests/samplerequestjson/foirequest-ministry-general-update.json' +RAW_REQUEST_POST_URL = '/api/foirawrequests' +REQUEST_POST_URL = '/api/foirequests' +WF_URL_BASE = '/api/foirawrequestbpm/addwfinstanceid/' +CREATE_EXTN_BASE_URL = '/api/foiextension/foirequest/' +GET_EXTN_BASE_URL = '/api/foiextension/ministryrequest/' + +CONTENT_TYPE = 'application/json' + +def factory_intake_auth_header(app, client): + url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) + x = requests.post(url, TEST_INTAKE_USER_PAYLOAD, verify=True).content.decode('utf-8') + return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} + +def factory_ministry_auth_header(app, client): + url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) + x = requests.post(url, TEST_MINISTRY_USER_PAYLOAD, verify=True).content.decode('utf-8') + return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} + +def test_ping(app, client): + response = client.get('/api/healthz') + assert response.status_code == 200 + +with open(RAW_REQUEST_JSON) as x, open(FOI_REQUEST_GENERAL_JSON) as y, open(FOI_REQUEST_GENERAL_UPDATE_JSON) as z, open(FOI_REQUEST_GENERAL_MINISTRY_UPDATE_JSON) as v: + generalministryrequestjson = json.load(v) + generalupdaterequestjson = json.load(z) + generalrequestjson = json.load(y) + rawrequestjson = json.load(x) + +def test_foiministryextension_list(app, client): + rawresponse = client.post(RAW_REQUEST_POST_URL,data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + jsondata = json.loads(rawresponse.data) + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post(REQUEST_POST_URL,data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put(WF_URL_BASE + str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + foiministryrequest = generalministryrequestjson + foiministryrequest["id"] = str(foijsondata["id"]) + foiministryrequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiministryrequest["requeststatusid"] = 2 + requestposturl = REQUEST_POST_URL+'/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry' + foiministryresponse = client.post(requestposturl,data=json.dumps(foiministryrequest), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + foiministryrequest2 = generalministryrequestjson + foiministryrequest2["id"] = str(foijsondata["id"]) + foiministryrequest2["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiministryrequest2["documents"] = [ + { + "category": "cfr-feeassessed", + "documentpath":"/EDU/"+str(foijsondata["ministryRequests"][0]["filenumber"])+"/cfr-review/test.docx", + "filename":"test.docx" + } + ] + foiministryrequest2["requeststatusid"] = 7 + foiministryresponse2 = client.post(requestposturl,data=json.dumps(foiministryrequest2), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + ministryid = str(foijsondata["ministryRequests"][0]["id"]) + createextnresponse = client.post(CREATE_EXTN_BASE_URL+str(foijsondata["id"])+'/ministryrequest/'+ministryid,data=json.dumps(NEW_EXTENSION_SCHEMA), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + getextensionssresponse = client.get(GET_EXTN_BASE_URL+ministryid, headers=factory_ministry_auth_header(app, client), content_type=CONTENT_TYPE) + assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiministryresponse.status_code == 200 and foiministryresponse2.status_code == 200 and createextnresponse.status_code == 200 and getextensionssresponse.status_code == 200 + +def test_foiministryextension_create(app, client): + rawresponse = client.post(RAW_REQUEST_POST_URL,data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + jsondata = json.loads(rawresponse.data) + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post(REQUEST_POST_URL,data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put(WF_URL_BASE+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + + foiministryextnresponse = client.post(CREATE_EXTN_BASE_URL+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(NEW_EXTENSION_SCHEMA), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiministryextnresponse.status_code == 200 + +def test_foiministryextension_edit(app, client): + rawresponse = client.post(RAW_REQUEST_POST_URL,data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + jsondata = json.loads(rawresponse.data) + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post(REQUEST_POST_URL,data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put(WF_URL_BASE+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + + createextnresponse = client.post(CREATE_EXTN_BASE_URL+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(NEW_EXTENSION_SCHEMA), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + getextensionssresponse = client.get(GET_EXTN_BASE_URL+str(foijsondata["ministryRequests"][0]["id"]), headers=factory_ministry_auth_header(app, client), content_type=CONTENT_TYPE) + getextensionssresponsejsondata = json.loads(getextensionssresponse.data) + extensionschema = { + "extensionreasonid": 7, + "extensionstatusid": 1, + "extendedduedays": 2, + "extendedduedate": "2022-03-07" , + "decisiondate": "2022-01-20", + "approvednoofdays": 3, + "documents": [{ + "documentpath": "https://citz-foi-prod.objectstore.gov.bc.ca/dev-forms-foirequests/CITZ/CITZ-2022-236861/extension/12e4dad1-71b7-4c2d-a770-c16937ecc052.docx", + "filename": "CITZExtensionDenied.docx", + "category": "extension" + }] + } + foirequestid = str(foijsondata["id"]) + ministryid = str(foijsondata["ministryRequests"][0]["id"]) + extensionid = str(getextensionssresponsejsondata[0]['foirequestextensionid']) + foiministryextnresponse = client.post(CREATE_EXTN_BASE_URL+foirequestid+'/ministryrequest/'+ministryid+'/extension/'+extensionid+'/edit',data=json.dumps(extensionschema), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 and createextnresponse.status_code == 200 and foiministryextnresponse.status_code == 200 + + +def test_foiministryextension_delete(app, client): + rawresponse = client.post(RAW_REQUEST_POST_URL,data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + jsondata = json.loads(rawresponse.data) + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post(REQUEST_POST_URL,data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put(WF_URL_BASE+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + + createextnresponse = client.post(CREATE_EXTN_BASE_URL+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(NEW_EXTENSION_SCHEMA), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + getextensionssresponse = client.get(GET_EXTN_BASE_URL+str(foijsondata["ministryRequests"][0]["id"]), headers=factory_ministry_auth_header(app, client), content_type=CONTENT_TYPE) + getextensionssresponsejsondata = json.loads(getextensionssresponse.data) + foirequestid = str(foijsondata["id"]) + ministryid = str(foijsondata["ministryRequests"][0]["id"]) + extensionid = str(getextensionssresponsejsondata[0]['foirequestextensionid']) + foiministryextnresponse = client.post(CREATE_EXTN_BASE_URL+foirequestid+'/ministryrequest/'+ministryid+'/extension/'+extensionid+'/delete', headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) + assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 and createextnresponse.status_code == 200 and foiministryextnresponse.status_code == 200 + diff --git a/historical-search-api/tests/restapi/test_foimasterdata_api.py b/historical-search-api/tests/restapi/test_foimasterdata_api.py new file mode 100644 index 000000000..93d49c533 --- /dev/null +++ b/historical-search-api/tests/restapi/test_foimasterdata_api.py @@ -0,0 +1,58 @@ +import json +import uuid +import jwt, os +import requests +import ast + +TEST_USER_PAYLOAD = { + 'client_id': 'forms-flow-web', + 'grant_type': 'password', + 'username' : os.getenv('TEST_INTAKE_USERID'), + 'password': os.getenv('TEST_INTAKE_PASSWORD') +} + +def factory_user_auth_header(app, client): + url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) + x = requests.post(url, TEST_USER_PAYLOAD, verify=True).content.decode('utf-8') + return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} + +def test_ping(app, client): + response = client.get('/api/healthz') + assert response.status_code == 200 + +def test_get_applicantcategories(app, client): + response = client.get('/api/foiflow/applicantcategories', headers=factory_user_auth_header(app, client), content_type='application/json') + assert response.status_code == 200 + +def test_get_programareas(app, client): + response = client.get('/api/foiflow/programareas', headers=factory_user_auth_header(app, client), content_type='application/json') + assert response.status_code == 200 + +def test_get_deliverymodes(app, client): + response = client.get('/api/foiflow/deliverymodes', headers=factory_user_auth_header(app, client), content_type='application/json') + assert response.status_code == 200 + +def test_get_receivedmodes(app, client): + response = client.get('/api/foiflow/receivedmodes', headers=factory_user_auth_header(app, client), content_type='application/json') + assert response.status_code == 200 + +def test_get_divisions(app, client): + response = client.get('/api/foiflow/divisions/edu', headers=factory_user_auth_header(app, client), content_type='application/json') + assert response.status_code == 200 + +def test_get_closereasons(app, client): + response = client.get('/api/foiflow/closereasons', headers=factory_user_auth_header(app, client), content_type='application/json') + assert response.status_code == 200 + +with open('tests/samplerequestjson/s3storagerequest.json') as f: + s3requestjson = json.load(f) +def test_post_fois3storagerequests(app, client): + response = client.post('api/foiflow/oss/authheader',headers=factory_user_auth_header(app, client),data=json.dumps(s3requestjson), content_type='application/json') + jsonresponse = json.loads(response.data) + for item in jsonresponse: + assert item['authheader'] is not None and item['filepath'] is not None + assert response.status_code == 200 and len(jsonresponse) >=1 + +def test_get_extensionreasons(app, client): + response = client.get('/api/foiflow/extensionreasons', headers=factory_user_auth_header(app, client), content_type='application/json') + assert response.status_code == 200 \ No newline at end of file diff --git a/historical-search-api/tests/restapi/test_foinotification_api.py b/historical-search-api/tests/restapi/test_foinotification_api.py new file mode 100644 index 000000000..2e7b6453a --- /dev/null +++ b/historical-search-api/tests/restapi/test_foinotification_api.py @@ -0,0 +1,159 @@ +import json +import uuid +import os +import requests +import ast + +TEST_USER_PAYLOAD = { + 'client_id': 'forms-flow-web', + 'grant_type': 'password', + 'username' : os.getenv('TEST_INTAKE_USERID'), + 'password': os.getenv('TEST_INTAKE_PASSWORD') +} + +TEST_MINISTRYUSER_PAYLOAD = { + 'client_id': 'forms-flow-web', + 'grant_type': 'password', + 'username' : os.getenv('TEST_MINISTRY_USERID'), + 'password': os.getenv('TEST_MINISTRY_PASSWORD') +} + +def factory_user_auth_header(app, client): + url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) + x = requests.post(url, TEST_USER_PAYLOAD, verify=True).content.decode('utf-8') + return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} + +def factory_ministryuser_auth_header(app, client): + url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) + x = requests.post(url, TEST_MINISTRYUSER_PAYLOAD, verify=True).content.decode('utf-8') + return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} +def test_ping(app, client): + response = client.get('/api/healthz') + assert response.status_code == 200 + + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: + generalrequestjson = json.load(y) + generalupdaterequestjson = json.load(z) + rawrequestjson = json.load(x) +def test_post_get_foirequest_general_cfr_notification(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') + foiupdaterequest = generalupdaterequestjson + foiupdaterequest["id"] = str(foijsondata["id"]) + foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiupdaterequest["requeststatusid"] = 2 + foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foiministryreqresponse = client.get('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',headers=factory_ministryuser_auth_header(app, client), content_type='application/json') + foinotification = client.get('/api/foinotifications',headers=factory_ministryuser_auth_header(app, client), content_type='application/json') + assert foiministryreqresponse.status_code == 200 and foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 and foinotification.status_code == 200 + + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: + generalrequestjson = json.load(y) + generalupdaterequestjson = json.load(z) + rawrequestjson = json.load(x) +def test_dismiss_foirequest_general_cfr_notification(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + watcherjson = { + "requestid":str(jsondata["id"]), + "watchedbygroup": "Intake Team", + "watchedby": "sumathi", + "isactive": True + } + createwatcherresponse = client.post('/api/foiwatcher/rawrequest', data=json.dumps(watcherjson), headers=factory_user_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') + foiupdaterequest = generalupdaterequestjson + foiupdaterequest["id"] = str(foijsondata["id"]) + foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiupdaterequest["requeststatusid"] = 2 + foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foiministryreqresponse = client.get('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',headers=factory_ministryuser_auth_header(app, client), content_type='application/json') + foinotification = client.get('/api/foinotifications',headers=factory_ministryuser_auth_header(app, client), content_type='application/json') + foinotificationdata = json.loads(foinotification.data) + deletefoinotification = client.delete('/api/foinotifications/watcher',headers=factory_ministryuser_auth_header(app, client), content_type='application/json') + assert foiministryreqresponse.status_code == 200 and foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 and foinotification.status_code == 200 and createwatcherresponse.status_code == 200 and deletefoinotification.status_code == 200 + + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: + generalrequestjson = json.load(y) + generalupdaterequestjson = json.load(z) + rawrequestjson = json.load(x) +def test_dismissall_foirequest_general_cfr_notification(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + watcherjson = { + "requestid":str(jsondata["id"]), + "watchedbygroup": "Intake Team", + "watchedby": "sumathi", + "isactive": True + } + createwatcherresponse = client.post('/api/foiwatcher/rawrequest', data=json.dumps(watcherjson), headers=factory_user_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') + foiupdaterequest = generalupdaterequestjson + foiupdaterequest["id"] = str(foijsondata["id"]) + foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiupdaterequest["requeststatusid"] = 2 + foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foiministryreqresponse = client.get('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',headers=factory_ministryuser_auth_header(app, client), content_type='application/json') + foinotification = client.get('/api/foinotifications',headers=factory_user_auth_header(app, client), content_type='application/json') + deletefoinotification = client.delete('/api/foinotifications',headers=factory_user_auth_header(app, client), content_type='application/json') + assert foiministryreqresponse.status_code == 200 and foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 and foinotification.status_code == 200 and createwatcherresponse.status_code == 200 and deletefoinotification.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: + generalrequestjson = json.load(y) + generalupdaterequestjson = json.load(z) + rawrequestjson = json.load(x) +def test_foirequest_general_cfr_notification_reminder(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + watcherjson = { + "requestid":str(jsondata["id"]), + "watchedbygroup": "Intake Team", + "watchedby": "sumathi", + "isactive": True + } + createwatcherresponse = client.post('/api/foiwatcher/rawrequest', data=json.dumps(watcherjson), headers=factory_user_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') + foiupdaterequest = generalupdaterequestjson + foiupdaterequest["id"] = str(foijsondata["id"]) + foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiupdaterequest["requeststatusid"] = 2 + foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foiministryreqresponse = client.get('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',headers=factory_ministryuser_auth_header(app, client), content_type='application/json') + foinotification = client.get('/api/foinotifications',headers=factory_user_auth_header(app, client), content_type='application/json') + reminderfoinotification = client.post('/api/foinotifications/reminder',headers=factory_user_auth_header(app, client), content_type='application/json') + assert foiministryreqresponse.status_code == 200 and foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 and foinotification.status_code == 200 and createwatcherresponse.status_code == 200 and reminderfoinotification.status_code == 200 diff --git a/historical-search-api/tests/restapi/test_foirecord_api.py b/historical-search-api/tests/restapi/test_foirecord_api.py new file mode 100644 index 000000000..6d1f2f26c --- /dev/null +++ b/historical-search-api/tests/restapi/test_foirecord_api.py @@ -0,0 +1,82 @@ +import json +import uuid +import os +import requests +import ast + +TEST_INTAKE_USER_PAYLOAD = { + 'client_id': 'forms-flow-web', + 'grant_type': 'password', + 'username' : os.getenv('TEST_INTAKE_USERID'), + 'password': os.getenv('TEST_INTAKE_PASSWORD') +} + +TEST_MINISTRY_USER_PAYLOAD = { + 'client_id': 'forms-flow-web', + 'grant_type': 'password', + 'username' : os.getenv('TEST_MINISTRY_USERID'), + 'password': os.getenv('TEST_MINISTRY_PASSWORD') +} + +def factory_intake_auth_header(app, client): + url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) + x = requests.post(url, TEST_INTAKE_USER_PAYLOAD, verify=True).content.decode('utf-8') + return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} + +def factory_ministry_auth_header(app, client): + url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) + x = requests.post(url, TEST_MINISTRY_USER_PAYLOAD, verify=True).content.decode('utf-8') + return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} + +def test_ping(app, client): + response = client.get('/api/healthz') + assert response.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z, open('tests/samplerequestjson/foirequest-ministry-general-update.json') as v: + generalministryrequestjson = json.load(v) + generalupdaterequestjson = json.load(z) + generalrequestjson = json.load(y) + rawrequestjson = json.load(x) +def test_foiministrydocument_list(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type='application/json') + foiministryrequest = generalministryrequestjson + foiministryrequest["id"] = str(foijsondata["id"]) + foiministryrequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiministryrequest["requeststatusid"] = 2 + foiministryresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(foiministryrequest), headers=factory_intake_auth_header(app, client), content_type='application/json') + foiministryrequest2 = generalministryrequestjson + foiministryrequest2["id"] = str(foijsondata["id"]) + foiministryrequest2["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiministryrequest2["documents"] = [ + { + "category": "cfr-feeassessed", + "documentpath":"/EDUC/"+str(foijsondata["ministryRequests"][0]["filenumber"])+"/cfr-review/test.docx", + "filename":"test.docx" + } + ] + foiministryrequest2["requeststatusid"] = 7 + foiministryresponse2 = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(foiministryrequest2), headers=factory_intake_auth_header(app, client), content_type='application/json') + getdocumentsresponse = client.get('/api/foidocument/ministryrequest/'+str(foijsondata["id"]), headers=factory_ministry_auth_header(app, client), content_type='application/json') + foirecordrequest = { "records": [{ + "divisionid":1, + "s3uripath":"/s3/test/3434", + "filename": "test.pdf" + }, + { + "divisionid":1, + "s3uripath":"/s3/test/3434", + "filename": "test2.pdf" + }] + } + foirecordresponse1 = client.post('/api/foirecord/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foirecordrequest), headers=factory_ministry_auth_header(app, client), content_type='application/json') + getrecordresponse = client.get('/api/foirecord/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["id"]), headers=factory_ministry_auth_header(app, client), content_type='application/json') + assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiministryresponse.status_code == 200 and foiministryresponse2.status_code == 200 and getdocumentsresponse.status_code == 200 and foirecordresponse1.status_code == 200 and getrecordresponse.status_code == 200 + diff --git a/historical-search-api/tests/restapi/test_foirequest_api.py b/historical-search-api/tests/restapi/test_foirequest_api.py new file mode 100644 index 000000000..da67bce25 --- /dev/null +++ b/historical-search-api/tests/restapi/test_foirequest_api.py @@ -0,0 +1,425 @@ +import json +import uuid +import jwt, os +import requests +import ast + +TEST_USER_PAYLOAD = { + 'client_id': 'forms-flow-web', + 'grant_type': 'password', + 'username' : os.getenv('TEST_INTAKE_USERID'), + 'password': os.getenv('TEST_INTAKE_PASSWORD') +} + +TEST_MINISTRYUSER_PAYLOAD = { + 'client_id': 'forms-flow-web', + 'grant_type': 'password', + 'username' : os.getenv('TEST_MINISTRY_USERID'), + 'password': os.getenv('TEST_MINISTRY_PASSWORD') +} + +def factory_user_auth_header(app, client): + url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) + x = requests.post(url, TEST_USER_PAYLOAD, verify=True).content.decode('utf-8') + return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} + +def factory_ministryuser_auth_header(app, client): + url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) + x = requests.post(url, TEST_MINISTRYUSER_PAYLOAD, verify=True).content.decode('utf-8') + return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} + +TEST_JWT_HEADER = { + "alg": "RS256", + "typ": "JWT", + "kid": "foiunittest" +} + +TEST_JWT_CLAIMS = { + "iss": os.getenv('JWT_OIDC_ISSUER'), + "sub": "3559e79c-7115-41c1-bb26-1a3dc54bbf5e", + "typ": "Bearer", + "aud": [os.getenv('JWT_OIDC_AUDIENCE')], + "firstname": "Test", + "lastname": "User", + "preferred_username": "test@idir", + "given_name": "TEST", + "family_name": "TEST", + "realm_access": { + "roles": [ + "approver_role" + ] + }, + "groups": [ + "/Flex Team" + ], + "preferred_username": "user" +} + +def factory_auth_header(jwt, claims): + """Produce JWT tokens for use in tests.""" + return {'Authorization': 'Bearer ' + jwt.encode(payload=TEST_JWT_CLAIMS,key="secret",headers=TEST_JWT_HEADER)} + +def test_ping(app, client): + response = client.get('/api/healthz') + assert response.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: + generalrequestjson = json.load(y) + generalupdaterequestjson = json.load(z) + rawrequestjson = json.load(x) +def test_post_foirequest_general(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') + foiupdaterequest = generalupdaterequestjson + foiupdaterequest["id"] = str(foijsondata["id"]) + foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiupdaterequest["cfrDueDate"] = '2020-01-02' + foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') + assert foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-personal.json') as y: + personalrequestjson = json.load(y) + rawrequestjson = json.load(x) +def test_post_foirequest_personal(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') + getrawjsondata = json.loads(getrawresponse.data) + #assert rawresponse.status_code == 200 + foirequest = personalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') + assert foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 + + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: + generalrequestjson = json.load(y) + generalupdaterequestjson = json.load(z) + rawrequestjson = json.load(x) +def test_post_foirequest_general_closed(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') + foiupdaterequest = generalupdaterequestjson + foiupdaterequest["id"] = str(foijsondata["id"]) + foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiupdaterequest["requeststatusid"] = 3 + foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') + assert foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 + + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: + generalrequestjson = json.load(y) + generalupdaterequestjson = json.load(z) + rawrequestjson = json.load(x) +def test_post_foirequest_general_cfr(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') + foiupdaterequest = generalupdaterequestjson + foiupdaterequest["id"] = str(foijsondata["id"]) + foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiupdaterequest["requeststatusid"] = 2 + foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') + print('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry') + foiministryreqResponse = client.get('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',headers=factory_ministryuser_auth_header(app, client), content_type='application/json') + assert foiministryreqResponse.status_code == 200 and foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: + generalrequestjson = json.load(y) + generalupdaterequestjson = json.load(z) + rawrequestjson = json.load(x) +def test_post_foirequest_general_cfrtoopen(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') + foiupdaterequest = generalupdaterequestjson + foiupdaterequest["id"] = str(foijsondata["id"]) + foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiupdaterequest["requeststatusid"] = 2 + foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foiupdaterequest["requeststatusid"] = 1 + foireqresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') + assert foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 and foireqresponse.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: + generalrequestjson = json.load(y) + generalupdaterequestjson = json.load(z) + rawrequestjson = json.load(x) +def test_post_foirequest_general_cfrtoreview(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') + foiupdaterequest = generalupdaterequestjson + foiupdaterequest["id"] = str(foijsondata["id"]) + foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiupdaterequest["requeststatusid"] = 2 + foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foiupdaterequest["requeststatusid"] = 7 + foireqresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') + assert foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 and foireqresponse.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: + generalrequestjson = json.load(y) + generalupdaterequestjson = json.load(z) + rawrequestjson = json.load(x) +def test_post_foirequest_general_reviewtoconsult(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') + foiupdaterequest = generalupdaterequestjson + foiupdaterequest["id"] = str(foijsondata["id"]) + foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiupdaterequest["requeststatusid"] = 2 + foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foiupdaterequest["requeststatusid"] = 7 + foireqresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foiupdaterequest["requeststatusid"] = 9 + foireqresponse2 = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') + assert foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 and foireqresponse.status_code == 200 and foireqresponse2.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: + generalrequestjson = json.load(y) + generalupdaterequestjson = json.load(z) + rawrequestjson = json.load(x) +def test_post_foirequest_general_reviewtominsignoff(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') + foiupdaterequest = generalupdaterequestjson + foiupdaterequest["id"] = str(foijsondata["id"]) + foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiupdaterequest["requeststatusid"] = 2 + foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foiupdaterequest["requeststatusid"] = 7 + foireqresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foiupdaterequest["requeststatusid"] = 10 + foireqresponse2 = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') + assert foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 and foireqresponse.status_code == 200 and foireqresponse2.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: + generalrequestjson = json.load(y) + generalupdaterequestjson = json.load(z) + rawrequestjson = json.load(x) +def test_post_foirequest_general_consulttoreview(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') + foiupdaterequest = generalupdaterequestjson + foiupdaterequest["id"] = str(foijsondata["id"]) + foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiupdaterequest["requeststatusid"] = 2 + foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foiupdaterequest["requeststatusid"] = 7 + foireqresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foiupdaterequest["requeststatusid"] = 9 + foireqresponse2 = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foiupdaterequest["requeststatusid"] = 7 + foireqresponse3 = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') + assert foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 and foireqresponse.status_code == 200 and foireqresponse2.status_code == 200 and foireqresponse3.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: + generalrequestjson = json.load(y) + generalupdaterequestjson = json.load(z) + rawrequestjson = json.load(x) +def test_post_foirequest_general_cfr_assignment(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') + foiupdaterequest = generalupdaterequestjson + foiupdaterequest["id"] = str(foijsondata["id"]) + foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiupdaterequest["requeststatusid"] = 2 + foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foiministryreqResponse = client.get('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',headers=factory_ministryuser_auth_header(app, client), content_type='application/json') + foiassignrequest = { + "assignedGroup":"Intake Team", + "assignedTo":"foiintake@idir", + "assignedToFirstName":"FOI", + "assignedToLastName":"Intake", + "assignedministrygroup":"EDU Ministry Team", + "assignedministryperson":"foiedu@idir", + "assignedministrypersonFirstName":"foiedu", + "assignedministrypersonLastName":"foiedu" + } + foicfrassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(foiassignrequest), headers=factory_user_auth_header(app, client), content_type='application/json') + assert foiministryreqResponse.status_code == 200 and foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 and foiministryreqResponse.status_code == 200 and foicfrassignresponse.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: + generalrequestjson = json.load(y) + generalupdaterequestjson = json.load(z) + rawrequestjson = json.load(x) +def test_post_foirequest_general_cfr_division(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') + foiupdaterequest = generalupdaterequestjson + foiupdaterequest["id"] = str(foijsondata["id"]) + foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiupdaterequest["requeststatusid"] = 2 + foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foiministryreqResponse = client.get('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',headers=factory_ministryuser_auth_header(app, client), content_type='application/json') + foidivisionrequest = { + "assignedministrygroup":"EDU Ministry Team", + "assignedministryperson":"foiedu@idir", + "assignedministrypersonFirstName":"foiedu", + "assignedministrypersonLastName":"foiedu", + "requeststatusid": 2, + "divisions": [{"divisionid":1,"stageid":1},{"divisionid":2,"stageid":1}] + } + foicfrdivisionresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(foidivisionrequest), headers=factory_user_auth_header(app, client), content_type='application/json') + assert foiministryreqResponse.status_code == 200 and foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 and foiministryreqResponse.status_code == 200 and foicfrdivisionresponse.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: + generalrequestjson = json.load(y) + generalupdaterequestjson = json.load(z) + rawrequestjson = json.load(x) +def test_post_foirequest_general_cfr_document(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') + foiupdaterequest = generalupdaterequestjson + foiupdaterequest["id"] = str(foijsondata["id"]) + foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiupdaterequest["requeststatusid"] = 2 + foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foiministryreqResponse = client.get('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',headers=factory_ministryuser_auth_header(app, client), content_type='application/json') + foidivisionrequest = { + "assignedministrygroup":"EDU Ministry Team", + "assignedministryperson":"foiedu@idir", + "assignedministrypersonFirstName":"foiedu", + "assignedministrypersonLastName":"foiedu", + "requeststatusid": 2, + "documents": [ + { + "category": "cfr-feeassessed", + "documentpath":"/EDU/"+str(foijsondata["ministryRequests"][0]["filenumber"])+"/cfr-review/test.docx", + "filename":"test.docx" + } + ] + } + foicfrdivisionresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(foidivisionrequest), headers=factory_user_auth_header(app, client), content_type='application/json') + assert foiministryreqResponse.status_code == 200 and foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 and foiministryreqResponse.status_code == 200 and foicfrdivisionresponse.status_code == 200 + + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: + generalrequestjson = json.load(y) + generalupdaterequestjson = json.load(z) + rawrequestjson = json.load(x) +def test_post_foirequest_general_close(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') + foiupdaterequest = generalupdaterequestjson + foiupdaterequest["id"] = str(foijsondata["id"]) + foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiupdaterequest["requeststatusid"] = 3 + foiupdaterequest["closedate"] = '2021-10-25' + foiupdaterequest["closereasonid"] = 1 + foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') + assert foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 + +def test_get_foirequestqueue(app, client): + response = client.get('/api/dashboardpagination?page=1&size=10&sortingitems[]=intakeSorting&sortingitems[]=duedate&sortingorders[]=asc&sortingorders[]=asc&filters[]=firstName&filters[]=lastName&filters[]=requestType&filters[]=idNumber&filters[]=axisRequestId&filters[]=currentState&filters[]=assignedToLastName&filters[]=assignedToFirstName&additionalfilter=myRequests&userid=foiintake@idir', headers=factory_user_auth_header(app, client), content_type='application/json') + jsondata = json.loads(response.data) + assert response.status_code == 200 and len(jsondata) > 0 + +def test_get_foiministryrequestqueue(app, client): + response = client.get('/api/dashboardpagination/ministry?page=1&size=10&sortingitems[]=ministrySorting&sortingitems[]=cfrduedate&sortingorders[]=asc&sortingorders[]=asc&filters[]=applicantcategory&filters[]=requestType&filters[]=idNumber&filters[]=axisRequestId&filters[]=currentState&filters[]=assignedministrypersonLastName&filters[]=assignedministrypersonFirstName&additionalfilter=myRequests&userid=foiedu@idir', headers=factory_ministryuser_auth_header(app, client), content_type='application/json') + jsondata = json.loads(response.data) + assert response.status_code == 200 and len(jsondata) > 0 + + +def test_get_foirequestqueuewithoutheader(app, client): + response = client.get('/api/dashboardpagination?page=1&size=10&sortingitems[]=intakeSorting&sortingitems[]=duedate&sortingorders[]=asc&sortingorders[]=asc&filters[]=firstName&filters[]=lastName&filters[]=requestType&filters[]=idNumber&filters[]=axisRequestId&filters[]=currentState&filters[]=assignedToLastName&filters[]=assignedToFirstName&additionalfilter=myRequests&userid=foiintake@idir', content_type='application/json') + jsondata = json.loads(response.data) + assert response.status_code == 500 diff --git a/historical-search-api/tests/restapi/test_foisystemcomment_api.py b/historical-search-api/tests/restapi/test_foisystemcomment_api.py new file mode 100644 index 000000000..07be0e557 --- /dev/null +++ b/historical-search-api/tests/restapi/test_foisystemcomment_api.py @@ -0,0 +1,94 @@ +import json +import uuid +import os +import requests +import ast + +TEST_USER_PAYLOAD = { + 'client_id': 'forms-flow-web', + 'grant_type': 'password', + 'username' : os.getenv('TEST_INTAKE_USERID'), + 'password': os.getenv('TEST_INTAKE_PASSWORD') +} + +TEST_MINISTRY_USER_PAYLOAD = { + 'client_id': 'forms-flow-web', + 'grant_type': 'password', + 'username' : os.getenv('TEST_MINISTRY_USERID'), + 'password': os.getenv('TEST_MINISTRY_PASSWORD') +} + +FOI_DIVISION_BASE_PAYLOAD = { + "assignedministrygroup":"EDU Ministry Team", + "assignedministryperson":"foiedu@idir", + "assignedministrypersonFirstName":"foiedu", + "assignedministrypersonLastName":"foiedu" +} + +def factory_auth_header(app, client): + url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) + x = requests.post(url, TEST_USER_PAYLOAD, verify=True).content.decode('utf-8') + return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} + +def factory_ministry_auth_header(app, client): + url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) + x = requests.post(url, TEST_MINISTRY_USER_PAYLOAD, verify=True).content.decode('utf-8') + return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} + +def test_ping(app, client): + response = client.get('/api/healthz') + assert response.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: + generalrequestjson = json.load(y) + generalupdaterequestjson = json.load(z) + rawrequestjson = json.load(x) +def test_createrawrequest_state_comment_intakeinprogres_to_open(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + updatejson = generalupdaterequestjson + updatejson["id"] = str(jsondata["id"]) + updatejson['ispiiredacted'] = True + updatejson['requeststatusid'] = 1 + updateresponse = client.post('/api/foirawrequest/'+str(jsondata["id"]),data=json.dumps(updatejson), headers=factory_auth_header(app, client), content_type='application/json') + getcommentresponse = client.get('/api/foicomment/rawrequest/'+str(jsondata["id"]), headers=factory_auth_header(app, client), content_type='application/json') + assert rawresponse.status_code == 200 and updateresponse.status_code == 200 and getcommentresponse.status_code == 200 and len(getcommentresponse.data) >=1 + + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: + generalrequestjson = json.load(y) + generalupdaterequestjson = json.load(z) + rawrequestjson = json.load(x) +def test_createrawrequest_division_comment(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_auth_header(app, client), content_type='application/json') + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_auth_header(app, client), content_type='application/json') + foiupdaterequest = generalupdaterequestjson + foiupdaterequest["id"] = str(foijsondata["id"]) + foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) + foiupdaterequest["requeststatusid"] = 2 + foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_auth_header(app, client), content_type='application/json') + foiministryreqresponse = client.get('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',headers=factory_ministry_auth_header(app, client), content_type='application/json') + + addfoidivisionrequest = FOI_DIVISION_BASE_PAYLOAD + addfoidivisionrequest["requeststatusid"] = 2 + addfoidivisionrequest["divisions"] = [{"divisionid":1,"stageid":1},{"divisionid":2,"stageid":1}] + addfoicfrdivisionresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(addfoidivisionrequest), headers=factory_ministry_auth_header(app, client), content_type='application/json') + modfoidivisionrequest = FOI_DIVISION_BASE_PAYLOAD + modfoidivisionrequest["requeststatusid"] = 2 + modfoidivisionrequest["divisions"] = [{"divisionid":1,"stageid":2},{"divisionid":2,"stageid":1}] + modfoicfrdivisionresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(modfoidivisionrequest), headers=factory_ministry_auth_header(app, client), content_type='application/json') + delfoidivisionrequest = FOI_DIVISION_BASE_PAYLOAD + delfoidivisionrequest["requeststatusid"] = 2 + delfoidivisionrequest["divisions"] = [{"divisionid":2,"stageid":1}] + delfoicfrdivisionresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(delfoidivisionrequest), headers=factory_ministry_auth_header(app, client), content_type='application/json') + getcommentresponse = client.get('/api/foicomment/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]), headers=factory_ministry_auth_header(app, client), content_type='application/json') + assert foiministryreqresponse.status_code == 200 and foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 and addfoicfrdivisionresponse.status_code == 200 and modfoicfrdivisionresponse.status_code == 200 and delfoicfrdivisionresponse.status_code == 200 and getcommentresponse.status_code == 200 and len(getcommentresponse.data) >=1 + \ No newline at end of file diff --git a/historical-search-api/tests/restapi/test_foiwatcher_api.py b/historical-search-api/tests/restapi/test_foiwatcher_api.py new file mode 100644 index 000000000..19808de17 --- /dev/null +++ b/historical-search-api/tests/restapi/test_foiwatcher_api.py @@ -0,0 +1,78 @@ +import json +import uuid +import os +import requests +import ast + +TEST_USER_PAYLOAD = { + 'client_id': 'forms-flow-web', + 'grant_type': 'password', + 'username' : os.getenv('TEST_INTAKE_USERID'), + 'password': os.getenv('TEST_INTAKE_PASSWORD') +} + +TEST_MINISTRY_USER_PAYLOAD = { + 'client_id': 'forms-flow-web', + 'grant_type': 'password', + 'username' : os.getenv('TEST_MINISTRY_USERID'), + 'password': os.getenv('TEST_MINISTRY_PASSWORD') +} + + +def factory_auth_header(app, client): + url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) + x = requests.post(url, TEST_USER_PAYLOAD, verify=True).content.decode('utf-8') + return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} + +def factory_ministry_auth_header(app, client): + url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) + x = requests.post(url, TEST_MINISTRY_USER_PAYLOAD, verify=True).content.decode('utf-8') + return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} + + +def test_ping(app, client): + response = client.get('/api/healthz') + assert response.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as x: + rawrequestjson = json.load(x) +def test_foirawwatcher(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + watcherjson = { + "requestid":str(jsondata["id"]), + "watchedbygroup": "Intake Team", + "watchedby": "sumathi", + "isactive": True + } + createwatcherresponse = client.post('/api/foiwatcher/rawrequest', data=json.dumps(watcherjson), headers=factory_auth_header(app, client), content_type='application/json') + getwatcherresponse = client.get('/api/foiwatcher/rawrequest/'+str(jsondata["id"]), headers=factory_auth_header(app, client), content_type='application/json') + disablewatcherresponse = client.put('/api/foiwatcher/rawrequest/disable/'+str(jsondata["id"]), headers=factory_auth_header(app, client), content_type='application/json') + assert rawresponse.status_code == 200 and createwatcherresponse.status_code == 200 and getwatcherresponse.status_code == 200 and disablewatcherresponse.status_code == 200 + + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y: + generalrequestjson = json.load(y) + rawrequestjson = json.load(x) +def test_foiministrywatcher(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_auth_header(app, client), content_type='application/json') + foijsondata = json.loads(foiresponse.data) + watcherjson = { + "ministryrequestid":str(foijsondata["id"]), + "watchedbygroup": "Intake Team", + "watchedby": "sumathi", + "isactive": True + } + createwatcherresponse = client.post('/api/foiwatcher/ministryrequest', data=json.dumps(watcherjson), headers=factory_ministry_auth_header(app, client), content_type='application/json') + getwatcherresponse = client.get('/api/foiwatcher/ministryrequest/'+str(foijsondata["id"]), headers=factory_ministry_auth_header(app, client), content_type='application/json') + disablewatcherresponse = client.put('/api/foiwatcher/ministryrequest/disable/'+str(jsondata["id"]), headers=factory_ministry_auth_header(app, client), content_type='application/json') + assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and createwatcherresponse.status_code == 200 and getwatcherresponse.status_code == 200 and disablewatcherresponse.status_code == 200 + + + + diff --git a/historical-search-api/tests/restapi/test_rawrequest_api.py b/historical-search-api/tests/restapi/test_rawrequest_api.py new file mode 100644 index 000000000..77456f4f8 --- /dev/null +++ b/historical-search-api/tests/restapi/test_rawrequest_api.py @@ -0,0 +1,109 @@ +import json +import uuid +import os +import requests +import ast + +TEST_USER_PAYLOAD = { + 'client_id': 'forms-flow-web', + 'grant_type': 'password', + 'username' : os.getenv('TEST_INTAKE_USERID'), + 'password': os.getenv('TEST_INTAKE_PASSWORD') +} + +def factory_auth_header(app, client): + url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) + x = requests.post(url, TEST_USER_PAYLOAD, verify=True).content.decode('utf-8') + return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} + +def test_ping(app, client): + response = client.get('/api/healthz') + assert response.status_code == 200 + +with open('tests/samplerequestjson/rawrequest.json') as f: + requestjson = json.load(f) +def test_post_foirawrequests(app, client): + response = client.post('/api/foirawrequests',data=json.dumps(requestjson), content_type='application/json') + jsondata = json.loads(response.data) + print(str(jsondata["id"])) + wfinstanceid={"wfinstanceid":str(uuid.uuid4())} + wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_auth_header(app, client), content_type='application/json') + assert response.status_code == 200 and wfupdateresponse.status_code == 200 and len(jsondata) >=1 + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: + generalrequestjson = json.load(y) + generalupdaterequestjson = json.load(z) + rawrequestjson = json.load(x) +def test_post_foirawrequestspii(app, client): + response = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), content_type='application/json') + jsondata = json.loads(response.data) + updatejson = generalupdaterequestjson + updatejson["id"] = str(jsondata["id"]) + print(str(jsondata["id"])) + updatejson['ispiiredacted'] = True + updatejson['assignedTo'] = "Intake Team" + wfupdateresponse = client.post('/api/foirawrequest/'+str(jsondata["id"]),data=json.dumps(updatejson), headers=factory_auth_header(app, client), content_type='application/json') + assert response.status_code == 200 and wfupdateresponse.status_code == 200 and len(jsondata) >=1 + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: + generalrequestjson = json.load(y) + generalupdaterequestjson = json.load(z) + rawrequestjson = json.load(x) +def test_post_foirawrequestscancel(app, client): + response = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), content_type='application/json') + jsondata = json.loads(response.data) + updatejson = generalupdaterequestjson + updatejson["id"] = str(jsondata["id"]) + print(str(jsondata["id"])) + updatejson['ispiiredacted'] = True + updatejson['requeststatusid'] = 3 + updatejson['closedate'] = '2021-10-26' + updatejson['requeststatusid'] = 2 + wfupdateresponse = client.post('/api/foirawrequest/'+str(jsondata["id"]),data=json.dumps(updatejson), headers=factory_auth_header(app, client), content_type='application/json') + assert response.status_code == 200 and wfupdateresponse.status_code == 200 and len(jsondata) >=1 + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: + generalrequestjson = json.load(y) + generalupdaterequestjson = json.load(z) + rawrequestjson = json.load(x) +def test_post_foirawrequestsredirect(app, client): + response = client.post('/api/foirawrequests',data=json.dumps(requestjson), content_type='application/json') + jsondata = json.loads(response.data) + print(str(jsondata["id"])) + updatejson = generalupdaterequestjson + updatejson['ispiiredacted'] = True + updatejson['assignedTo'] = "Intake Team" + updatejson['requeststatusid'] = 4 + wfupdateresponse = client.post('/api/foirawrequest/'+str(jsondata["id"]),data=json.dumps(updatejson), headers=factory_auth_header(app, client), content_type='application/json') + assert response.status_code == 200 and wfupdateresponse.status_code == 200 and len(jsondata) >=1 + +with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: + generalrequestjson = json.load(y) + generalupdaterequestjson = json.load(z) + rawrequestjson = json.load(x) +def test_post_foirequest_general(app, client): + rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_auth_header(app, client), content_type='application/json') + jsondata = json.loads(rawresponse.data) + getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_auth_header(app, client), content_type='application/json') + foirequest = generalrequestjson + foirequest["id"] = str(jsondata["id"]) + foirequest["requeststatusid"] = 1 + foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_auth_header(app, client), content_type='application/json') + getrawresponsefields = client.get('/api/foirawrequest/'+str(jsondata["id"])+'/fields?names=ministries', headers=factory_auth_header(app, client), content_type='application/json') + assert rawresponse.status_code == 200 and getrawresponse.status_code == 200 and foiresponse.status_code == 200 and getrawresponsefields.status_code == 200 + + +def test_get_programareas(app,client): + response = client.get('api/foiflow/programareas', headers=factory_auth_header(app, client), content_type='application/json') + jsondata = json.loads(response.data) + assert response.status_code == 200 and len(jsondata) >=1 + +def test_get_applicantcategories(app,client): + response = client.get('api/foiflow/applicantcategories', headers=factory_auth_header(app, client), content_type='application/json') + jsondata = json.loads(response.data) + assert response.status_code == 200 and len(jsondata) >=1 + + + + + diff --git a/historical-search-api/tests/samplerequestjson/foirequest-extension-oipc.json b/historical-search-api/tests/samplerequestjson/foirequest-extension-oipc.json new file mode 100644 index 000000000..e06444469 --- /dev/null +++ b/historical-search-api/tests/samplerequestjson/foirequest-extension-oipc.json @@ -0,0 +1,6 @@ +{ + "extensionreasonid": 5, + "extendedduedays": 30, + "extendedduedate": "2022-03-04" + +} \ No newline at end of file diff --git a/historical-search-api/tests/samplerequestjson/foirequest-general-CFR.json b/historical-search-api/tests/samplerequestjson/foirequest-general-CFR.json new file mode 100644 index 000000000..e887f67f4 --- /dev/null +++ b/historical-search-api/tests/samplerequestjson/foirequest-general-CFR.json @@ -0,0 +1,62 @@ +{ + "additionalPersonalInfo": { + "alsoKnownAs": "", + "birthDate": "", + "childFirstName": "", + "childMiddleName": "", + "childLastName": "", + "childAlsoKnownAs": "", + "childBirthDate": "", + "anotherFirstName": "", + "anotherMiddleName": "", + "anotherLastName": "", + "anotherAlsoKnownAs": "", + "anotherBirthDate": "", + "adoptiveMotherFirstName": "", + "adoptiveMotherLastName": "", + "adoptiveFatherLastName": "", + "adoptiveFatherFirstName": "" + }, + "firstName": "Sumathi", + "lastName": "Thirumani", + "email": "sumathi.thirumani@gmail.com", + "phonePrimary": "7787009903", + "address": "4801", + "city": "Victoria", + "postal": "V8Y 2J", + "category": "Business", + "description": "test", + "selectedMinistries": [ + { + "code": "EDU", + "id": 291, + "name": "Ministry of Education and Childcare", + "isSelected": true + } + ], + "stateTransition":[ + { + "status": "Call For Records", + "version": 3 + }, + { + "status": "Open", + "version": 1 + } + ], + "requestType": "general", + "receivedMode": "Email", + "deliveryMode": "Secure File Transfer", + "receivedDate": "2021-08-01", + "receivedDateUF": "2021-08-01T00:00:00.000Z", + "requestProcessStart": "2021-08-09", + "dueDate": "2021-09-21", + "cfrDueDate":"2021-09-29", + "assignedGroup": "Intake Team", + "assignedTo": "foiintake@idir", + "assignedToFirstName": "FOI", + "assignedToLastName": "Intake", + "axisRequestId": "ABC-2099-4208", + "programareaid": 6 +} + diff --git a/historical-search-api/tests/samplerequestjson/foirequest-general-update.json b/historical-search-api/tests/samplerequestjson/foirequest-general-update.json new file mode 100644 index 000000000..4f97a1777 --- /dev/null +++ b/historical-search-api/tests/samplerequestjson/foirequest-general-update.json @@ -0,0 +1,48 @@ +{ + "additionalPersonalInfo":{ + "alsoKnownAs":"", + "birthDate":"" + }, + "address":"4801", + "assignedGroup":"Flex Team", + "assignedTo": "foiflex@idir", + "assignedToFirstName": "foiflex", + "assignedToLastName": "proxy", + "assignedministrygroup":"EDU Ministry Team", + "assignedministryperson":"foiedu@idir", + "assignedministrypersonFirstName":"foiedu", + "assignedministrypersonLastName":"foiedu", + "businessName":"", + "category":"Business", + "categoryid":1, + "city":"Victoria", + "currentState":"Open", + "deliveryMode":"Secure File Transfer", + "deliverymodeid":1, + "description":"test", + "dueDate":"2021-10-04", + "cfrDueDate":"2021-09-29", + "email":"sumathi.thirumani@gmail.com", + "firstName":"Sumathi", + "fromDate":"", + "lastName":"Thirumani", + "middleName":"", + "phonePrimary":"7787009903", + "postal":"V8Y 2J", + "programareaid":10, + "receivedDate":"2021 Aug, 01", + "receivedDateUF":"2021-08-01 00:00:00.000000", + "receivedMode":"Email", + "receivedmodeid":1, + "requestProcessStart":"2021-08-20", + "requestType":"general", + "requeststatusid":1, + "selectedMinistries":[ + { + "code":"LBR", + "name":"Ministry of Labour", + "selected":"true" + } + ], + "toDate":"" + } \ No newline at end of file diff --git a/historical-search-api/tests/samplerequestjson/foirequest-general.json b/historical-search-api/tests/samplerequestjson/foirequest-general.json new file mode 100644 index 000000000..8d5305b8f --- /dev/null +++ b/historical-search-api/tests/samplerequestjson/foirequest-general.json @@ -0,0 +1,51 @@ +{ + "additionalPersonalInfo": { + "alsoKnownAs": "", + "birthDate": "", + "childFirstName": "", + "childMiddleName": "", + "childLastName": "", + "childAlsoKnownAs": "", + "childBirthDate": "", + "anotherFirstName": "", + "anotherMiddleName": "", + "anotherLastName": "", + "anotherAlsoKnownAs": "", + "anotherBirthDate": "", + "adoptiveMotherFirstName": "", + "adoptiveMotherLastName": "", + "adoptiveFatherLastName": "", + "adoptiveFatherFirstName": "" + }, + "firstName": "Sumathi", + "lastName": "Thirumani", + "email": "sumathi.thirumani@gmail.com", + "phonePrimary": "7787009903", + "address": "4801", + "city": "Victoria", + "postal": "V8Y 2J", + "category": "Business", + "description": "test", + "selectedMinistries": [ + { + "code": "EDU", + "id": 291, + "name": "Ministry of Education and Childcare", + "isSelected": true + } + ], + "requestType": "general", + "receivedMode": "Email", + "deliveryMode": "Secure File Transfer", + "receivedDate": "2021-08-01", + "receivedDateUF": "2021-08-01T00:00:00.000Z", + "requestProcessStart": "2021-08-09", + "dueDate": "2021-09-21", + "cfrDueDate":"2021-09-29", + "assignedGroup": "Intake Team", + "assignedTo": "foiintake@idir", + "assignedToFirstName": "FOI", + "assignedToLastName": "Intake", + "axisRequestId": "ABC-2099-4208", + "programareaid": 6 +} \ No newline at end of file diff --git a/historical-search-api/tests/samplerequestjson/foirequest-ministry-general-update.json b/historical-search-api/tests/samplerequestjson/foirequest-ministry-general-update.json new file mode 100644 index 000000000..a62efbd15 --- /dev/null +++ b/historical-search-api/tests/samplerequestjson/foirequest-ministry-general-update.json @@ -0,0 +1,42 @@ +{ + "assignedGroup":"Intake Team", + "assignedTo":"foiintake@idir", + "assignedToFirstName":"FOI", + "assignedToLastName":"Intake", + "assignedministrygroup":"EDU Ministry Team", + "assignedministryperson":"foiedu@idir", + "assignedministrypersonFirstName":"foiedu", + "assignedministrypersonLastName":"foiedu", + "category":"Individual", + "categoryid":2, + "cfrDueDate":"2021-11-26", + "currentState":"Call For Records", + "deliveryMode":"Secure File Transfer", + "deliverymodeid":1, + "description":"test", + "divisions":[ + { + "id":0, + "divisionid":2, + "stageid":1 + } + ], + "dueDate":"2022-01-12", + "fromDate":"", + "onholdTransitionDate":null, + "programareaid":6, + "receivedDate":"2021 Nov, 01", + "receivedDateUF":"2021-11-01 00:00:00.000000", + "receivedMode":"Email", + "receivedmodeid":1, + "requestProcessStart":"2021-11-30", + "requestType":"general", + "selectedMinistries":[ + { + "code":"EDU", + "name":"Ministry of Education and Childcare", + "selected":"true" + } + ], + "toDate":"" + } \ No newline at end of file diff --git a/historical-search-api/tests/samplerequestjson/foirequest-personal.json b/historical-search-api/tests/samplerequestjson/foirequest-personal.json new file mode 100644 index 000000000..421a709ae --- /dev/null +++ b/historical-search-api/tests/samplerequestjson/foirequest-personal.json @@ -0,0 +1,82 @@ +{ + "wfinstanceid":"5eb4325b-f941-11eb-ae3f-0242c0a87002", + "requestType":"personal", + "firstName":"May", + "middleName":null, + "lastName":"C", + "businessName":null, + "currentState":"Intake in Progress", + "receivedDate":"2021 08, 10", + "receivedDateUF":"2021-08-10T00:00:00.000Z", + "assignedGroup": "Intake Team", + "assignedTo": "foiintake@idir", + "assignedToFirstName": "FOI", + "assignedToLastName": "Intake", + "xgov":"No", + "idNumber":"U-0010", + "email":"may@email.com", + "phonePrimary":null, + "phoneSecondary":null, + "address":null, + "city":null, + "postal":null, + "province":null, + "country":null, + "description":"personal request with 4 ministries selected", + "fromDate":"2021-07-04T04:00:00.000Z", + "toDate":"2021-08-01T04:00:00.000Z", + "correctionalServiceNumber":"correc.Service#", + "publicServiceEmployeeNumber":"1234567890", + "topic":"Other", + "selectedMinistries":[ + { + "code":"PSA", + "name":"BC Public Service Agency", + "selected":true + }, + { + "code":"EMBC", + "name":"Emergency Management BC", + "selected":true + }, + { + "code":"EAO", + "name":"Environmental Assessment Office", + "selected":true + }, + { + "code":"JERI", + "name":"Jobs, Economic Recovery and Innovation", + "selected":true + } + ], + "additionalPersonalInfo":{ + "alsoKnownAs":null, + "requestFor":{ + "yourself":true, + "child":null, + "another":null + }, + "birthDate":"1998-04-06T04:00:00.000Z", + "childFirstName":"", + "childMiddleName":"", + "childLastName":"", + "childAlsoKnownAs":"", + "childBirthDate":"", + "anotherFirstName":"", + "anotherMiddleName":"", + "anotherLastName":"", + "anotherAlsoKnownAs":"", + "anotherBirthDate":"", + "adoptiveMotherFirstName":"", + "adoptiveMotherLastName":"", + "adoptiveFatherLastName":"", + "adoptiveFatherFirstName":"" + }, + "category":"Interest Group", + "receivedMode":"Fax", + "deliveryMode":"In Person Pick up", + "requestProcessStart":"2021-08-10", + "dueDate":"2021-09-22", + "cfrDueDate":"2021-09-29" +} \ No newline at end of file diff --git a/historical-search-api/tests/samplerequestjson/rawrequest.json b/historical-search-api/tests/samplerequestjson/rawrequest.json new file mode 100644 index 000000000..1eb5b7d45 --- /dev/null +++ b/historical-search-api/tests/samplerequestjson/rawrequest.json @@ -0,0 +1,63 @@ +{ + "requestData": { + "requestType": { + "requestType": "general" + }, + "ministry": { + "selectedMinistry": [], + "ministryPage": "ABCDEFGHIJK", + "defaultMinistry": { + "code": "ABCDEFGHIJKLMNOP", + "name": "ABCDEFGHIJK", + "defaulted": false, + "selected": true + } + }, + "descriptionTimeframe": { + "description": "DAN DESC", + "fromDate": "01-01-2021", + "toDate": "03-03-2021", + "correctionalServiceNumber": "ABCDEFGHIJK", + "publicServiceEmployeeNumber": null, + "topic": "ABCDEFGHI" + }, + "contactInfo": { + "firstName": "UT - UserFirstName", + "lastName": "UT - UserLastName", + "middleName": null, + "businessName": "JD Business", + "alsoKnownAs": "JD Tech", + "birthDate": "10-12-1975" + }, + "contactInfoOptions": { + "email": "john1@email.com", + "phonePrimary": null, + "phoneSecondary": "ABCDEFGHIJKLMNOPQR", + "address": null, + "city": null, + "postal": "ABCDE", + "province": null, + "country": "ABCDEF" + }, + "choose-idenity": { + "answerYes": null + }, + "selectAbout": { + "yourself": false, + "child": false, + "another": null + }, + "requestTopic": { + "value": null, + "text": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "ministryCode": "ABCDEFGHIJKLMNOPQRSTUVW" + }, + "adoptiveParents": { + "motherFirstName": null, + "motherLastName": null, + "fatherFirstName": "ABCDEFGHIJKLMNO", + "fatherLastName": null + }, + "Attachments": [] + } + } \ No newline at end of file diff --git a/historical-search-api/tests/samplerequestjson/s3storagerequest.json b/historical-search-api/tests/samplerequestjson/s3storagerequest.json new file mode 100644 index 000000000..892225890 --- /dev/null +++ b/historical-search-api/tests/samplerequestjson/s3storagerequest.json @@ -0,0 +1,13 @@ +[{ + "ministrycode":"EDU", + "requestnumber":"EDU-2021-98976", + "filestatustransition":"cfr-review", + "filename":"reviewapproval.pdf" +}, +{ + "ministrycode":"EDU", + "requestnumber":"EDU-2021-98976", + "filestatustransition":"cfr-review", + "filename":"misc.pdf" +} +] \ No newline at end of file diff --git a/historical-search-api/tests/services/__init__.py b/historical-search-api/tests/services/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/historical-search-api/tests/services/test_dashboard_services.py b/historical-search-api/tests/services/test_dashboard_services.py new file mode 100644 index 000000000..d6068166c --- /dev/null +++ b/historical-search-api/tests/services/test_dashboard_services.py @@ -0,0 +1,14 @@ +import pytest +from request_api.services.dashboardservice import dashboardservice +import json +from request_api.utils.enums import MinistryTeamWithKeycloackGroup + +def test_get_requests_dashboard(session): + groups = ["Intake Team","Flex Team"] + queue = dashboardservice().getrequestqueuepagination(groups) + assert queue.data is not None + +def test_get_ministryrequests_dashboard(session): + groups = MinistryTeamWithKeycloackGroup.list() + queue = dashboardservice().getrequestqueuepagination(groups) + assert queue.data is not None \ No newline at end of file diff --git a/historical-search-api/tests/services/test_masterdata_services.py b/historical-search-api/tests/services/test_masterdata_services.py new file mode 100644 index 000000000..3733353f0 --- /dev/null +++ b/historical-search-api/tests/services/test_masterdata_services.py @@ -0,0 +1,35 @@ +from request_api.services.programareaservice import programareaservice +from request_api.services.applicantcategoryservice import applicantcategoryservice +from request_api.services.deliverymodeservice import deliverymodeservice +from request_api.services.receivedmodeservice import receivedmodeservice + +def test_get_programareas(session): + response = programareaservice().getprogramareas() + assert response + # assert the structure is correct by checking for name, description properties in each element + for item in response: + assert item['name'] and item['iaocode'] + + +def test_get_applicantcategories(session): + response = applicantcategoryservice().getapplicantcategories() + assert response + # assert the structure is correct by checking for name, description properties in each element + for item in response: + assert item['name'] and item['description'] + +def test_get_receivedmodes(session): + response = receivedmodeservice().getreceivedmodes() + assert response + # assert the structure is correct by checking for name, description properties in each element + for item in response: + assert item['name'] and item['description'] + +def test_get_deliverymodes(session): + response = deliverymodeservice().getdeliverymodes() + assert response + # assert the structure is correct by checking for name, description properties in each element + for item in response: + assert item['name'] and item['description'] + + diff --git a/historical-search-api/tests/services/test_rawrequest_services.py b/historical-search-api/tests/services/test_rawrequest_services.py new file mode 100644 index 000000000..770b51166 --- /dev/null +++ b/historical-search-api/tests/services/test_rawrequest_services.py @@ -0,0 +1,25 @@ +import pytest +from request_api.services.rawrequestservice import rawrequestservice +import json +import uuid + + + + +with open('tests/samplerequestjson/rawrequest.json') as f: + requestjson = json.load(f) + +def pytest_namespace(): + return {'requestidtoupdate': 0} + +def test_get_rawrequests(session): + response = rawrequestservice().getrawrequests() + assert response + requestid ='' + for item in response: + requestid = item['id'] + assert item['id'] and item['requestType'] + getresponse = rawrequestservice().getrawrequest(requestid) + assert getresponse['requestType'] + + diff --git a/historical-search-api/tests/services/test_requestextension_services.py b/historical-search-api/tests/services/test_requestextension_services.py new file mode 100644 index 000000000..176f78ac2 --- /dev/null +++ b/historical-search-api/tests/services/test_requestextension_services.py @@ -0,0 +1,29 @@ +import pytest +from request_api.services.extensionservice import extensionservice +import json +import uuid + + + + +with open('tests/samplerequestjson/foirequest-extension-oipc.json') as f: + requestjson = json.load(f) + +def pytest_namespace(): + return {'requestidtoupdate': 0} + +def test_create_extension(session): + requestid = 1 + ministryrequestid = 1 + response = extensionservice().createrequestextension(requestid, ministryrequestid, requestjson, userid='dviswana@idir') + requestid = response.identifier + pytest.approxrequestidtoupdate = requestid + assert response.success == True + +def test_get_request_extensions(session): + requestid = 1 + extensions = extensionservice().getrequestextensions(requestid) + for extension in extensions: + assert extension["extensionreson"] + + diff --git a/historical-search-api/wsgi.py b/historical-search-api/wsgi.py new file mode 100644 index 000000000..f910d0b70 --- /dev/null +++ b/historical-search-api/wsgi.py @@ -0,0 +1,76 @@ +from threading import Thread +import eventlet +#Monkey patch to allow for async actions (aka multiple workers) +eventlet.monkey_patch() +from distutils.log import debug + + +import os +from request_api import create_app, socketio +from flask_socketio import ConnectionRefusedError +from flask_socketio import emit +from request_api.auth import AuthHelper +from flask import g, request +from request_api.auth import AuthHelper +from request_api.exceptions import BusinessException +from flask import current_app +from request_api.utils.redissubscriber import RedisSubscriberService +import logging + +@socketio.on('connect') +def connect(message): + userid = __getauthenticateduserid(message) + if userid is not None: + current_app.logger.info('socket connection established for user: ' + userid + ' | sid: ' + request.sid) + else: + disconnect() + raise ConnectionRefusedError('unauthorized!') + + +@socketio.on('disconnect') +def disconnect(): + current_app.logger.info('socket disconnected for sid: '+request.sid) + + +def __getauthenticateduserid(message): + if message.get("userid") is not None and __isvalidnonce(message) == True: + return message.get("userid") + else: + return __validatejwt(message) + + +def __isvalidnonce(message): + if message.get("rkey") is not None and message.get("rkey") == os.getenv("SOCKETIO_CONNECT_NONCE"): + return True + return False + +def __validatejwt(message): + if "x-jwt-token" in message and message.get("x-jwt-token") is not None: + try: + return AuthHelper.getwsuserid(message.get("x-jwt-token")) + except BusinessException as exception: + print("BusinessException >> ", str(exception)) + # current_app.logger.error("%s,%s" % ('Unable to get user details', exception.message)) + except Exception as ex: + print("__validatejwt Exception >>> ", str(ex)) + return None + +@socketio.on_error() +def error_handler(e): + # current_app.logger.error("%s,%s" % ('Socket error', e.message)) + print('Socket error', str(e)) + +APP = create_app() +if __name__ == "__main__": + port = int(os.environ.get('PORT', 5000)) + messagequeue = os.getenv('SOCKETIO_MESSAGE_QUEUE', 'INMEMORY') + if os.getenv("SOCKETIO_MESSAGE_QTYPE") == "REDIS": + RedisSubscriberService().register_subscription() + socketio.init_app(APP, async_mode='eventlet', + path='/api/v1/socket.io') + socketio.run(APP, port=port,host='0.0.0.0', log_output=False, use_reloader=False) + + + + + From c138a373743cc44a76819fda72f2bb194e3b08ae Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Mon, 27 May 2024 15:29:24 -0700 Subject: [PATCH 02/33] Clean up step 1 for historical search --- .../request_api/models/ApplicantCategories.py | 28 - .../ApplicationCorrespondenceTemplates.py | 63 - .../request_api/models/CFRFeeStatus.py | 41 - .../request_api/models/CFRFormReason.py | 41 - .../request_api/models/CloseReasons.py | 32 - .../request_api/models/CommentTypes.py | 24 - .../request_api/models/ContactTypes.py | 22 - .../request_api/models/DeliveryModes.py | 27 - .../request_api/models/DocumentPathMapper.py | 41 - .../request_api/models/DocumentTemplate.py | 43 - .../request_api/models/DocumentType.py | 41 - .../request_api/models/ExtensionReasons.py | 28 - .../request_api/models/ExtensionStatuses.py | 27 - .../FOIApplicantCorrespondenceAttachments.py | 50 - .../models/FOIApplicantCorrespondences.py | 93 - .../request_api/models/FOIAssignees.py | 44 - .../models/FOIMinistryRequestDivisions.py | 58 - .../models/FOIMinistryRequestDocuments.py | 159 -- .../models/FOIMinistryRequestSubjectCodes.py | 54 - .../request_api/models/FOIMinistryRequests.py | 1535 ----------------- .../models/FOIRawRequestComments.py | 165 -- .../models/FOIRawRequestDocuments.py | 88 - .../models/FOIRawRequestNotificationUsers.py | 270 --- .../models/FOIRawRequestNotifications.py | 119 -- .../models/FOIRawRequestWatchers.py | 106 -- .../request_api/models/FOIRawRequests.py | 1161 ------------- .../models/FOIRequestApplicantMappings.py | 48 - .../models/FOIRequestApplicants.py | 67 - .../request_api/models/FOIRequestCFRFees.py | 110 -- .../request_api/models/FOIRequestComments.py | 169 -- .../models/FOIRequestContactInformation.py | 51 - .../FOIRequestExtensionDocumentMappings.py | 60 - .../models/FOIRequestExtensions.py | 189 -- .../models/FOIRequestNotificationDashboard.py | 41 - .../models/FOIRequestNotificationUsers.py | 338 ---- .../models/FOIRequestNotifications.py | 189 -- .../request_api/models/FOIRequestOIPC.py | 63 - .../request_api/models/FOIRequestPayments.py | 85 - .../models/FOIRequestPersonalAttributes.py | 50 - .../request_api/models/FOIRequestRecords.py | 183 -- .../request_api/models/FOIRequestStatus.py | 34 - .../request_api/models/FOIRequestTeams.py | 98 -- .../request_api/models/FOIRequestWatchers.py | 138 -- .../request_api/models/FOIRequests.py | 126 -- .../models/FOIRestrictedMinistryRequests.py | 56 - .../request_api/models/FOIUsers.py | 57 - .../request_api/models/FeeCode.py | 44 - .../request_api/models/NotificationTypes.py | 33 - .../models/NotificationUserTypes.py | 42 - .../request_api/models/OIPCInquiryOutcomes.py | 19 - .../request_api/models/OIPCOutcomes.py | 19 - .../request_api/models/OIPCReasons.py | 20 - .../request_api/models/OIPCReviewTypes.py | 19 - .../models/OIPCReviewTypesReasons.py | 43 - .../request_api/models/OIPCStatuses.py | 19 - .../request_api/models/OperatingTeams.py | 47 - .../request_api/models/Payment.py | 58 - .../models/PersonalInformationAttributes.py | 27 - .../models/ProgramAreaDivisionStages.py | 23 - .../models/ProgramAreaDivisions.py | 125 -- .../request_api/models/ProgramAreas.py | 45 - .../request_api/models/ReceivedModes.py | 27 - .../request_api/models/RequestorType.py | 27 - .../request_api/models/RevenueAccount.py | 23 - .../request_api/models/SubjectCodes.py | 32 - .../request_api/models/UnopenedReport.py | 23 - .../masterdataimport/applicantcategories.sql | 9 - .../models/masterdataimport/deliverymodes.sql | 2 - .../models/masterdataimport/programareas.sql | 37 - .../models/masterdataimport/receivedmodes.sql | 4 - .../models/samplequeries/businessteamdata.sql | 38 - .../models/samplequeries/findcolumnvalue.sql | 21 - .../models/samplequeries/wfmigration.sql | 406 ----- .../models/views/FOINotifications.py | 21 - .../models/views/FOIRawRequests.py | 23 - .../request_api/models/views/FOIRequests.py | 24 - .../request_api/resources/request.py | 111 -- .../request_api/schemas/external/bpmschema.py | 35 - .../schemas/foiapplicantcorrespondencelog.py | 28 - .../request_api/schemas/foiassignee.py | 20 - .../request_api/schemas/foiaxissync.py | 18 - .../request_api/schemas/foicfrfee.py | 69 - .../request_api/schemas/foicomment.py | 50 - .../request_api/schemas/foidocument.py | 50 - .../request_api/schemas/foiemail.py | 13 - .../request_api/schemas/foiextension.py | 40 - .../request_api/schemas/foipayment.py | 19 - .../schemas/foiprogramareadivision.py | 14 - .../request_api/schemas/foirecord.py | 166 -- .../request_api/schemas/foirequest.py | 87 - .../schemas/foirequestsformslist.py | 15 - .../request_api/schemas/foirequestwrapper.py | 215 --- .../request_api/schemas/foiwatcher.py | 35 - .../schemas/schemas/rawrequest.json | 220 --- .../services/applicantcategoryservice.py | 8 - .../applicantcorrespondencelog.py | 98 -- .../request_api/services/assigneeservice.py | 42 - .../request_api/services/auditservice.py | 79 - .../request_api/services/cacheservice.py | 62 - .../request_api/services/cdogs_api_service.py | 127 -- .../request_api/services/cfrfeeservice.py | 114 -- .../services/cfrfeestatusservice.py | 13 - .../services/cfrformreasonservice.py | 13 - .../services/closereasonservice.py | 8 - .../request_api/services/commentservice.py | 185 -- .../services/commons/duecalculator.py | 109 -- .../services/dashboardeventservice.py | 69 - .../request_api/services/dashboardservice.py | 226 --- .../services/deliverymodeservice.py | 8 - .../services/divisionstageservice.py | 68 - .../services/document_generation_service.py | 70 - .../request_api/services/documentservice.py | 211 --- .../services/email/inboxservice.py | 59 - .../services/email/senderservice.py | 85 - .../email/templates/templateconfig.py | 93 - .../email/templates/templatefilters.py | 11 - .../email/templates/templateservice.py | 97 -- .../email/templatevaluebuilderservice.py | 33 - .../request_api/services/emailservice.py | 102 -- .../request_api/services/events/__init__.py | 15 - .../request_api/services/events/assignment.py | 98 -- .../request_api/services/events/cfrdate.py | 76 - .../request_api/services/events/cfrfeeform.py | 100 -- .../request_api/services/events/comment.py | 105 -- .../request_api/services/events/division.py | 136 -- .../services/events/divisiondate.py | 84 - .../request_api/services/events/email.py | 60 - .../request_api/services/events/extension.py | 275 --- .../services/events/legislativedate.py | 78 - .../request_api/services/events/oipc.py | 166 -- .../services/events/oipcduedate.py | 72 - .../request_api/services/events/payment.py | 130 -- .../services/events/section5pending.py | 68 - .../request_api/services/events/state.py | 135 -- .../request_api/services/events/watcher.py | 72 - .../request_api/services/eventservice.py | 144 -- .../services/extensionreasonservice.py | 13 - .../request_api/services/extensionservice.py | 416 ----- .../services/external/axissyncservice.py | 76 - .../services/external/bpmservice.py | 196 --- .../services/external/camundaservice.py | 38 - .../services/external/eventqueueservice.py | 24 - .../services/external/storageservice.py | 255 --- .../request_api/services/fee_service.py | 239 --- .../foirequest/requestservicebuilder.py | 197 --- .../foirequest/requestserviceconfigurator.py | 84 - .../foirequest/requestservicecreate.py | 183 -- .../foirequest/requestservicegetter.py | 338 ---- .../requestserviceministrybuilder.py | 354 ---- .../foirequest/requestserviceupdate.py | 26 - .../request_api/services/hash_service.py | 49 - .../services/historicalrequestservice.py | 14 - .../notifications/notificationconfig.py | 69 - .../notifications/notificationuser.py | 133 -- .../services/notificationservice.py | 352 ---- .../request_api/services/oipcservice.py | 27 - .../request_api/services/paymentservice.py | 156 -- .../services/programareadivisionservice.py | 58 - .../services/programareaservice.py | 16 - .../rawrequest/rawrequestservicegetter.py | 230 --- .../request_api/services/rawrequestservice.py | 153 -- .../services/receivedmodeservice.py | 8 - .../services/records/recordservicebase.py | 45 - .../services/records/recordservicegetter.py | 272 --- .../request_api/services/recordservice.py | 344 ---- .../request_api/services/requestservice.py | 333 ---- .../services/subjectcodeservice.py | 50 - .../services/unopenedreportservice.py | 165 -- .../request_api/services/userservice.py | 43 - .../request_api/services/watcherservice.py | 91 - .../request_api/services/workflowservice.py | 322 ---- .../request_api/utils/constants.py | 42 - .../request_api/utils/enums.py | 175 -- .../tests/restapi/test_fee_api.py | 125 -- .../tests/restapi/test_foiassignees_api.py | 55 - .../tests/restapi/test_foiaudit_api.py | 66 - .../tests/restapi/test_foicfrfee_api.py | 139 -- .../tests/restapi/test_foicomment_api.py | 168 -- .../tests/restapi/test_foidocument_api.py | 360 ---- .../tests/restapi/test_foiextension_api.py | 159 -- .../tests/restapi/test_foimasterdata_api.py | 58 - .../tests/restapi/test_foinotification_api.py | 159 -- .../tests/restapi/test_foirecord_api.py | 82 - .../tests/restapi/test_foirequest_api.py | 425 ----- .../restapi/test_foisystemcomment_api.py | 94 - .../tests/restapi/test_foiwatcher_api.py | 78 - .../tests/restapi/test_rawrequest_api.py | 109 -- .../foirequest-extension-oipc.json | 6 - .../foirequest-general-CFR.json | 62 - .../foirequest-general-update.json | 48 - .../samplerequestjson/foirequest-general.json | 51 - .../foirequest-ministry-general-update.json | 42 - .../foirequest-personal.json | 82 - .../tests/samplerequestjson/rawrequest.json | 63 - .../samplerequestjson/s3storagerequest.json | 13 - .../tests/services/test_dashboard_services.py | 14 - .../services/test_masterdata_services.py | 35 - .../services/test_rawrequest_services.py | 25 - .../test_requestextension_services.py | 29 - 199 files changed, 20954 deletions(-) delete mode 100644 historical-search-api/request_api/models/ApplicantCategories.py delete mode 100644 historical-search-api/request_api/models/ApplicationCorrespondenceTemplates.py delete mode 100644 historical-search-api/request_api/models/CFRFeeStatus.py delete mode 100644 historical-search-api/request_api/models/CFRFormReason.py delete mode 100644 historical-search-api/request_api/models/CloseReasons.py delete mode 100644 historical-search-api/request_api/models/CommentTypes.py delete mode 100644 historical-search-api/request_api/models/ContactTypes.py delete mode 100644 historical-search-api/request_api/models/DeliveryModes.py delete mode 100644 historical-search-api/request_api/models/DocumentPathMapper.py delete mode 100644 historical-search-api/request_api/models/DocumentTemplate.py delete mode 100644 historical-search-api/request_api/models/DocumentType.py delete mode 100644 historical-search-api/request_api/models/ExtensionReasons.py delete mode 100644 historical-search-api/request_api/models/ExtensionStatuses.py delete mode 100644 historical-search-api/request_api/models/FOIApplicantCorrespondenceAttachments.py delete mode 100644 historical-search-api/request_api/models/FOIApplicantCorrespondences.py delete mode 100644 historical-search-api/request_api/models/FOIAssignees.py delete mode 100644 historical-search-api/request_api/models/FOIMinistryRequestDivisions.py delete mode 100644 historical-search-api/request_api/models/FOIMinistryRequestDocuments.py delete mode 100644 historical-search-api/request_api/models/FOIMinistryRequestSubjectCodes.py delete mode 100644 historical-search-api/request_api/models/FOIMinistryRequests.py delete mode 100644 historical-search-api/request_api/models/FOIRawRequestComments.py delete mode 100644 historical-search-api/request_api/models/FOIRawRequestDocuments.py delete mode 100644 historical-search-api/request_api/models/FOIRawRequestNotificationUsers.py delete mode 100644 historical-search-api/request_api/models/FOIRawRequestNotifications.py delete mode 100644 historical-search-api/request_api/models/FOIRawRequestWatchers.py delete mode 100644 historical-search-api/request_api/models/FOIRawRequests.py delete mode 100644 historical-search-api/request_api/models/FOIRequestApplicantMappings.py delete mode 100644 historical-search-api/request_api/models/FOIRequestApplicants.py delete mode 100644 historical-search-api/request_api/models/FOIRequestCFRFees.py delete mode 100644 historical-search-api/request_api/models/FOIRequestComments.py delete mode 100644 historical-search-api/request_api/models/FOIRequestContactInformation.py delete mode 100644 historical-search-api/request_api/models/FOIRequestExtensionDocumentMappings.py delete mode 100644 historical-search-api/request_api/models/FOIRequestExtensions.py delete mode 100644 historical-search-api/request_api/models/FOIRequestNotificationDashboard.py delete mode 100644 historical-search-api/request_api/models/FOIRequestNotificationUsers.py delete mode 100644 historical-search-api/request_api/models/FOIRequestNotifications.py delete mode 100644 historical-search-api/request_api/models/FOIRequestOIPC.py delete mode 100644 historical-search-api/request_api/models/FOIRequestPayments.py delete mode 100644 historical-search-api/request_api/models/FOIRequestPersonalAttributes.py delete mode 100644 historical-search-api/request_api/models/FOIRequestRecords.py delete mode 100644 historical-search-api/request_api/models/FOIRequestStatus.py delete mode 100644 historical-search-api/request_api/models/FOIRequestTeams.py delete mode 100644 historical-search-api/request_api/models/FOIRequestWatchers.py delete mode 100644 historical-search-api/request_api/models/FOIRequests.py delete mode 100644 historical-search-api/request_api/models/FOIRestrictedMinistryRequests.py delete mode 100644 historical-search-api/request_api/models/FOIUsers.py delete mode 100644 historical-search-api/request_api/models/FeeCode.py delete mode 100644 historical-search-api/request_api/models/NotificationTypes.py delete mode 100644 historical-search-api/request_api/models/NotificationUserTypes.py delete mode 100644 historical-search-api/request_api/models/OIPCInquiryOutcomes.py delete mode 100644 historical-search-api/request_api/models/OIPCOutcomes.py delete mode 100644 historical-search-api/request_api/models/OIPCReasons.py delete mode 100644 historical-search-api/request_api/models/OIPCReviewTypes.py delete mode 100644 historical-search-api/request_api/models/OIPCReviewTypesReasons.py delete mode 100644 historical-search-api/request_api/models/OIPCStatuses.py delete mode 100644 historical-search-api/request_api/models/OperatingTeams.py delete mode 100644 historical-search-api/request_api/models/Payment.py delete mode 100644 historical-search-api/request_api/models/PersonalInformationAttributes.py delete mode 100644 historical-search-api/request_api/models/ProgramAreaDivisionStages.py delete mode 100644 historical-search-api/request_api/models/ProgramAreaDivisions.py delete mode 100644 historical-search-api/request_api/models/ProgramAreas.py delete mode 100644 historical-search-api/request_api/models/ReceivedModes.py delete mode 100644 historical-search-api/request_api/models/RequestorType.py delete mode 100644 historical-search-api/request_api/models/RevenueAccount.py delete mode 100644 historical-search-api/request_api/models/SubjectCodes.py delete mode 100644 historical-search-api/request_api/models/UnopenedReport.py delete mode 100644 historical-search-api/request_api/models/masterdataimport/applicantcategories.sql delete mode 100644 historical-search-api/request_api/models/masterdataimport/deliverymodes.sql delete mode 100644 historical-search-api/request_api/models/masterdataimport/programareas.sql delete mode 100644 historical-search-api/request_api/models/masterdataimport/receivedmodes.sql delete mode 100644 historical-search-api/request_api/models/samplequeries/businessteamdata.sql delete mode 100644 historical-search-api/request_api/models/samplequeries/findcolumnvalue.sql delete mode 100644 historical-search-api/request_api/models/samplequeries/wfmigration.sql delete mode 100644 historical-search-api/request_api/models/views/FOINotifications.py delete mode 100644 historical-search-api/request_api/models/views/FOIRawRequests.py delete mode 100644 historical-search-api/request_api/models/views/FOIRequests.py delete mode 100644 historical-search-api/request_api/resources/request.py delete mode 100644 historical-search-api/request_api/schemas/external/bpmschema.py delete mode 100644 historical-search-api/request_api/schemas/foiapplicantcorrespondencelog.py delete mode 100644 historical-search-api/request_api/schemas/foiassignee.py delete mode 100644 historical-search-api/request_api/schemas/foiaxissync.py delete mode 100644 historical-search-api/request_api/schemas/foicfrfee.py delete mode 100644 historical-search-api/request_api/schemas/foicomment.py delete mode 100644 historical-search-api/request_api/schemas/foidocument.py delete mode 100644 historical-search-api/request_api/schemas/foiemail.py delete mode 100644 historical-search-api/request_api/schemas/foiextension.py delete mode 100644 historical-search-api/request_api/schemas/foipayment.py delete mode 100644 historical-search-api/request_api/schemas/foiprogramareadivision.py delete mode 100644 historical-search-api/request_api/schemas/foirecord.py delete mode 100644 historical-search-api/request_api/schemas/foirequest.py delete mode 100644 historical-search-api/request_api/schemas/foirequestsformslist.py delete mode 100644 historical-search-api/request_api/schemas/foirequestwrapper.py delete mode 100644 historical-search-api/request_api/schemas/foiwatcher.py delete mode 100644 historical-search-api/request_api/schemas/schemas/rawrequest.json delete mode 100644 historical-search-api/request_api/services/applicantcategoryservice.py delete mode 100644 historical-search-api/request_api/services/applicantcorrespondence/applicantcorrespondencelog.py delete mode 100644 historical-search-api/request_api/services/assigneeservice.py delete mode 100644 historical-search-api/request_api/services/auditservice.py delete mode 100644 historical-search-api/request_api/services/cacheservice.py delete mode 100644 historical-search-api/request_api/services/cdogs_api_service.py delete mode 100644 historical-search-api/request_api/services/cfrfeeservice.py delete mode 100644 historical-search-api/request_api/services/cfrfeestatusservice.py delete mode 100644 historical-search-api/request_api/services/cfrformreasonservice.py delete mode 100644 historical-search-api/request_api/services/closereasonservice.py delete mode 100644 historical-search-api/request_api/services/commentservice.py delete mode 100644 historical-search-api/request_api/services/commons/duecalculator.py delete mode 100644 historical-search-api/request_api/services/dashboardeventservice.py delete mode 100644 historical-search-api/request_api/services/dashboardservice.py delete mode 100644 historical-search-api/request_api/services/deliverymodeservice.py delete mode 100644 historical-search-api/request_api/services/divisionstageservice.py delete mode 100644 historical-search-api/request_api/services/document_generation_service.py delete mode 100644 historical-search-api/request_api/services/documentservice.py delete mode 100644 historical-search-api/request_api/services/email/inboxservice.py delete mode 100644 historical-search-api/request_api/services/email/senderservice.py delete mode 100644 historical-search-api/request_api/services/email/templates/templateconfig.py delete mode 100644 historical-search-api/request_api/services/email/templates/templatefilters.py delete mode 100644 historical-search-api/request_api/services/email/templates/templateservice.py delete mode 100644 historical-search-api/request_api/services/email/templatevaluebuilderservice.py delete mode 100644 historical-search-api/request_api/services/emailservice.py delete mode 100644 historical-search-api/request_api/services/events/__init__.py delete mode 100644 historical-search-api/request_api/services/events/assignment.py delete mode 100644 historical-search-api/request_api/services/events/cfrdate.py delete mode 100644 historical-search-api/request_api/services/events/cfrfeeform.py delete mode 100644 historical-search-api/request_api/services/events/comment.py delete mode 100644 historical-search-api/request_api/services/events/division.py delete mode 100644 historical-search-api/request_api/services/events/divisiondate.py delete mode 100644 historical-search-api/request_api/services/events/email.py delete mode 100644 historical-search-api/request_api/services/events/extension.py delete mode 100644 historical-search-api/request_api/services/events/legislativedate.py delete mode 100644 historical-search-api/request_api/services/events/oipc.py delete mode 100644 historical-search-api/request_api/services/events/oipcduedate.py delete mode 100644 historical-search-api/request_api/services/events/payment.py delete mode 100644 historical-search-api/request_api/services/events/section5pending.py delete mode 100644 historical-search-api/request_api/services/events/state.py delete mode 100644 historical-search-api/request_api/services/events/watcher.py delete mode 100644 historical-search-api/request_api/services/eventservice.py delete mode 100644 historical-search-api/request_api/services/extensionreasonservice.py delete mode 100644 historical-search-api/request_api/services/extensionservice.py delete mode 100644 historical-search-api/request_api/services/external/axissyncservice.py delete mode 100644 historical-search-api/request_api/services/external/bpmservice.py delete mode 100644 historical-search-api/request_api/services/external/camundaservice.py delete mode 100644 historical-search-api/request_api/services/external/eventqueueservice.py delete mode 100644 historical-search-api/request_api/services/external/storageservice.py delete mode 100644 historical-search-api/request_api/services/fee_service.py delete mode 100644 historical-search-api/request_api/services/foirequest/requestservicebuilder.py delete mode 100644 historical-search-api/request_api/services/foirequest/requestserviceconfigurator.py delete mode 100644 historical-search-api/request_api/services/foirequest/requestservicecreate.py delete mode 100644 historical-search-api/request_api/services/foirequest/requestservicegetter.py delete mode 100644 historical-search-api/request_api/services/foirequest/requestserviceministrybuilder.py delete mode 100644 historical-search-api/request_api/services/foirequest/requestserviceupdate.py delete mode 100644 historical-search-api/request_api/services/hash_service.py delete mode 100644 historical-search-api/request_api/services/historicalrequestservice.py delete mode 100644 historical-search-api/request_api/services/notifications/notificationconfig.py delete mode 100644 historical-search-api/request_api/services/notifications/notificationuser.py delete mode 100644 historical-search-api/request_api/services/notificationservice.py delete mode 100644 historical-search-api/request_api/services/oipcservice.py delete mode 100644 historical-search-api/request_api/services/paymentservice.py delete mode 100644 historical-search-api/request_api/services/programareadivisionservice.py delete mode 100644 historical-search-api/request_api/services/programareaservice.py delete mode 100644 historical-search-api/request_api/services/rawrequest/rawrequestservicegetter.py delete mode 100644 historical-search-api/request_api/services/rawrequestservice.py delete mode 100644 historical-search-api/request_api/services/receivedmodeservice.py delete mode 100644 historical-search-api/request_api/services/records/recordservicebase.py delete mode 100644 historical-search-api/request_api/services/records/recordservicegetter.py delete mode 100644 historical-search-api/request_api/services/recordservice.py delete mode 100644 historical-search-api/request_api/services/requestservice.py delete mode 100644 historical-search-api/request_api/services/subjectcodeservice.py delete mode 100644 historical-search-api/request_api/services/unopenedreportservice.py delete mode 100644 historical-search-api/request_api/services/userservice.py delete mode 100644 historical-search-api/request_api/services/watcherservice.py delete mode 100644 historical-search-api/request_api/services/workflowservice.py delete mode 100644 historical-search-api/request_api/utils/constants.py delete mode 100644 historical-search-api/request_api/utils/enums.py delete mode 100644 historical-search-api/tests/restapi/test_fee_api.py delete mode 100644 historical-search-api/tests/restapi/test_foiassignees_api.py delete mode 100644 historical-search-api/tests/restapi/test_foiaudit_api.py delete mode 100644 historical-search-api/tests/restapi/test_foicfrfee_api.py delete mode 100644 historical-search-api/tests/restapi/test_foicomment_api.py delete mode 100644 historical-search-api/tests/restapi/test_foidocument_api.py delete mode 100644 historical-search-api/tests/restapi/test_foiextension_api.py delete mode 100644 historical-search-api/tests/restapi/test_foimasterdata_api.py delete mode 100644 historical-search-api/tests/restapi/test_foinotification_api.py delete mode 100644 historical-search-api/tests/restapi/test_foirecord_api.py delete mode 100644 historical-search-api/tests/restapi/test_foirequest_api.py delete mode 100644 historical-search-api/tests/restapi/test_foisystemcomment_api.py delete mode 100644 historical-search-api/tests/restapi/test_foiwatcher_api.py delete mode 100644 historical-search-api/tests/restapi/test_rawrequest_api.py delete mode 100644 historical-search-api/tests/samplerequestjson/foirequest-extension-oipc.json delete mode 100644 historical-search-api/tests/samplerequestjson/foirequest-general-CFR.json delete mode 100644 historical-search-api/tests/samplerequestjson/foirequest-general-update.json delete mode 100644 historical-search-api/tests/samplerequestjson/foirequest-general.json delete mode 100644 historical-search-api/tests/samplerequestjson/foirequest-ministry-general-update.json delete mode 100644 historical-search-api/tests/samplerequestjson/foirequest-personal.json delete mode 100644 historical-search-api/tests/samplerequestjson/rawrequest.json delete mode 100644 historical-search-api/tests/samplerequestjson/s3storagerequest.json delete mode 100644 historical-search-api/tests/services/test_dashboard_services.py delete mode 100644 historical-search-api/tests/services/test_masterdata_services.py delete mode 100644 historical-search-api/tests/services/test_rawrequest_services.py delete mode 100644 historical-search-api/tests/services/test_requestextension_services.py diff --git a/historical-search-api/request_api/models/ApplicantCategories.py b/historical-search-api/request_api/models/ApplicantCategories.py deleted file mode 100644 index 08f90109a..000000000 --- a/historical-search-api/request_api/models/ApplicantCategories.py +++ /dev/null @@ -1,28 +0,0 @@ -from .db import db, ma -from .default_method_result import DefaultMethodResult - - -class ApplicantCategory(db.Model): - __tablename__ = 'ApplicantCategories' - # Defining the columns - applicantcategoryid = db.Column(db.Integer, primary_key=True,autoincrement=True) - name = db.Column(db.String(100), unique=False, nullable=False) - description = db.Column(db.String(255), unique=False, nullable=True) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - - @classmethod - def getapplicantcategories(cls): - applicantcategory_schema = ApplicantCategorySchema(many=True) - query = db.session.query(ApplicantCategory).filter_by(isactive=True).all() - return applicantcategory_schema.dump(query) - - @classmethod - def getapplicantcategory(cls,appltcategory): - applicantcategory_schema = ApplicantCategorySchema() - query = db.session.query(ApplicantCategory).filter_by(name=appltcategory).first() - return applicantcategory_schema.dump(query) - - -class ApplicantCategorySchema(ma.Schema): - class Meta: - fields = ('applicantcategoryid', 'name', 'description','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/ApplicationCorrespondenceTemplates.py b/historical-search-api/request_api/models/ApplicationCorrespondenceTemplates.py deleted file mode 100644 index 8aae86aa6..000000000 --- a/historical-search-api/request_api/models/ApplicationCorrespondenceTemplates.py +++ /dev/null @@ -1,63 +0,0 @@ -from __future__ import annotations - -from datetime import date, datetime -import string - -from sqlalchemy import ForeignKey - -from .db import db, ma - - -class ApplicationCorrespondenceTemplate(db.Model): - __tablename__ = 'ApplicantCorrespondenceTemplates' - # Defining the columns - - templateid = db.Column(db.Integer, primary_key=True, autoincrement=True) - documenturipath = db.Column(db.Text, nullable=False) - description = db.Column(db.String(1000), nullable=True) - name = db.Column(db.String(500), nullable=False) - active = db.Column(db.Boolean, nullable=False) - display = db.Column(db.Boolean, nullable=False) - version = db.Column(db.Integer, nullable=False) - created_at = db.Column(db.DateTime, default=datetime.now) - createdby = db.Column(db.String(120), unique=False, nullable=True) - - @classmethod - def get_template_by_id(cls, templateid: int): - """Given a type and optionally an extension, return the template.""" - - query = cls.query.filter_by(templateid = templateid). \ - filter(ApplicationCorrespondenceTemplate.templateid == templateid) - - return query.one_or_none() - - @classmethod - def get_template_by_name(cls, name: string): - """Given a type and optionally an extension, return the template.""" - - query = cls.query.filter_by(name = name). \ - filter(ApplicationCorrespondenceTemplate.name == name) - - return query.one_or_none() - - @classmethod - def getapplicantcorrespondencetemplates(cls): - correspondencetemplate_schema = ApplicationCorrespondenceTemplateSchema(many=True) - query = db.session.query(ApplicationCorrespondenceTemplate).filter_by(active=True, display=True).all() - return correspondencetemplate_schema.dump(query) - - @staticmethod - def commit(): - """Commit the session.""" - db.session.commit() - - def flush(self): - """Save and flush.""" - db.session.add(self) - db.session.flush() - return self - - -class ApplicationCorrespondenceTemplateSchema(ma.Schema): - class Meta: - fields = ('templateid','documenturipath', 'description','name','active', 'display','version','created_at','createdby') diff --git a/historical-search-api/request_api/models/CFRFeeStatus.py b/historical-search-api/request_api/models/CFRFeeStatus.py deleted file mode 100644 index 31ef05cfc..000000000 --- a/historical-search-api/request_api/models/CFRFeeStatus.py +++ /dev/null @@ -1,41 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey -from .db import db, ma -from datetime import datetime as datetime2 -from sqlalchemy.orm import relationship,backref -from .default_method_result import DefaultMethodResult -from sqlalchemy.dialects.postgresql import JSON, UUID -from sqlalchemy.sql.expression import distinct -from sqlalchemy import text, insert -import logging -from sqlalchemy import func - -class CFRFeeStatus(db.Model): - __tablename__ = 'CFRFeeStatuses' - # Defining the columns - cfrfeestatusid = db.Column(db.Integer, primary_key=True,autoincrement=True) - name = db.Column(db.String(25), unique=False, nullable=False) - description = db.Column(db.String(100), unique=False, nullable=False) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - - @classmethod - def getallcfrfeestatuses(cls): - cfrfeestatus_schema = CFRFeeStatusSchema(many=True) - query = db.session.query(CFRFeeStatus).filter_by(isactive=True).order_by(CFRFeeStatus.cfrfeestatusid.asc()).all() - return cfrfeestatus_schema.dump(query) - - @classmethod - def getcfrfeestatus(cls,cfrfeestatusid): - cfrfeestatus_schema = CFRFeeStatusSchema(many=False) - query = db.session.query(CFRFeeStatus).filter_by(cfrfeestatusid=cfrfeestatusid).first() - return cfrfeestatus_schema.dump(query) - - @classmethod - def getcfrfeestatusid(cls,cfrfeestatus): - cfrfeestatus_schema = CFRFeeStatusSchema(many=False) - query = db.session.query(CFRFeeStatus).filter(func.lower(CFRFeeStatus.name) == func.lower(cfrfeestatus)).first() - return cfrfeestatus_schema.dump(query) - -class CFRFeeStatusSchema(ma.Schema): - class Meta: - fields = ('cfrfeestatusid','name','description','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/CFRFormReason.py b/historical-search-api/request_api/models/CFRFormReason.py deleted file mode 100644 index cc2b4e16b..000000000 --- a/historical-search-api/request_api/models/CFRFormReason.py +++ /dev/null @@ -1,41 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey -from .db import db, ma -from datetime import datetime as datetime2 -from sqlalchemy.orm import relationship,backref -from .default_method_result import DefaultMethodResult -from sqlalchemy.dialects.postgresql import JSON, UUID -from sqlalchemy.sql.expression import distinct -from sqlalchemy import text, insert -import logging -from sqlalchemy import func - -class CFRFormReason(db.Model): - __tablename__ = 'CFRFormReasons' - # Defining the columns - cfrformreasonid = db.Column(db.Integer, primary_key=True,autoincrement=True) - name = db.Column(db.String(25), unique=False, nullable=False) - description = db.Column(db.String(100), unique=False, nullable=False) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - - @classmethod - def getallcfrformreasons(cls): - cfrformreason_schema = CFRFormReasonSchema(many=True) - query = db.session.query(CFRFormReason).filter_by(isactive=True).order_by(CFRFormReason.cfrformreasonid.asc()).all() - return cfrformreason_schema.dump(query) - - @classmethod - def getcfrformreason(cls,cfrformreasonid): - cfrformreason_schema = CFRFormReasonSchema(many=False) - query = db.session.query(CFRFormReason).filter_by(cfrformreasonid=cfrformreasonid).first() - return cfrformreason_schema.dump(query) - - @classmethod - def getcfrformreasonid(cls,cfrformreason): - cfrformreason_schema = CFRFormReasonSchema(many=False) - query = db.session.query(CFRFormReason).filter(func.lower(CFRFormReason.name) == func.lower(cfrformreason)).first() - return cfrformreason_schema.dump(query) - -class CFRFormReasonSchema(ma.Schema): - class Meta: - fields = ('cfrformreasonid','name','description','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/CloseReasons.py b/historical-search-api/request_api/models/CloseReasons.py deleted file mode 100644 index 729bc779a..000000000 --- a/historical-search-api/request_api/models/CloseReasons.py +++ /dev/null @@ -1,32 +0,0 @@ -from .db import db, ma -from .default_method_result import DefaultMethodResult -from sqlalchemy.orm import relationship,backref -from datetime import datetime -from sqlalchemy import text - -class CloseReason(db.Model): - __tablename__ = 'CloseReasons' - # Defining the columns - closereasonid = db.Column(db.Integer, primary_key=True,autoincrement=True) - name = db.Column(db.String(500), unique=False, nullable=False) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - created_at = db.Column(db.DateTime, default=datetime.now) - createdby = db.Column(db.String(120), unique=False, default='System') - - @classmethod - def getallclosereasons(cls): - closereason_schema = CloseReasonSchema(many=True) - query = db.session.query(CloseReason).filter_by(isactive=True).order_by(CloseReason.closereasonid.asc()).all() - return closereason_schema.dump(query) - - @classmethod - def getclosereason(cls,closereasonid): - closereason_schema = CloseReasonSchema(many=True) - query = db.session.query(CloseReason).filter_by(closereasonid=closereasonid).first() - return closereason_schema.dump(query) - - - -class CloseReasonSchema(ma.Schema): - class Meta: - fields = ('closereasonid','name','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/CommentTypes.py b/historical-search-api/request_api/models/CommentTypes.py deleted file mode 100644 index f85d6e22b..000000000 --- a/historical-search-api/request_api/models/CommentTypes.py +++ /dev/null @@ -1,24 +0,0 @@ -from .db import db, ma -from .default_method_result import DefaultMethodResult -from sqlalchemy.orm import relationship,backref -from datetime import datetime -from sqlalchemy import text - -class CommentType(db.Model): - __tablename__ = 'CommentTypes' - # Defining the columns - commenttypeid = db.Column(db.Integer, primary_key=True,autoincrement=True) - name = db.Column(db.String(100), unique=False, nullable=False) - description = db.Column(db.String(255), unique=False, nullable=False) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - - @classmethod - def getcommenttypes(cls): - commenttype_schema = CommentTypeSchema(many=True) - query = db.session.query(CommentType).filter_by(isactive=True).all() - return commenttype_schema.dump(query) - - -class CommentTypeSchema(ma.Schema): - class Meta: - fields = ('commenttypeid', 'name', 'description','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/ContactTypes.py b/historical-search-api/request_api/models/ContactTypes.py deleted file mode 100644 index 4e4301820..000000000 --- a/historical-search-api/request_api/models/ContactTypes.py +++ /dev/null @@ -1,22 +0,0 @@ -from .db import db, ma -from .default_method_result import DefaultMethodResult - - -class ContactType(db.Model): - __tablename__ = 'ContactTypes' - # Defining the columns - contacttypeid = db.Column(db.Integer, primary_key=True,autoincrement=True) - name = db.Column(db.String(100), unique=False, nullable=False) - description = db.Column(db.String(255), unique=False, nullable=True) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - - @classmethod - def getcontacttypes(cls): - contacttype_schema = ContactTypeSchema(many=True) - query = db.session.query(ContactType).filter_by(isactive=True).all() - return contacttype_schema.dump(query) - - -class ContactTypeSchema(ma.Schema): - class Meta: - fields = ('contacttypeid', 'name', 'description','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/DeliveryModes.py b/historical-search-api/request_api/models/DeliveryModes.py deleted file mode 100644 index dbe51af6c..000000000 --- a/historical-search-api/request_api/models/DeliveryModes.py +++ /dev/null @@ -1,27 +0,0 @@ -from .db import db, ma -from .default_method_result import DefaultMethodResult - - -class DeliveryMode(db.Model): - __tablename__ = 'DeliveryModes' - # Defining the columns - deliverymodeid = db.Column(db.Integer, primary_key=True,autoincrement=True) - name = db.Column(db.String(100), unique=False, nullable=False) - description = db.Column(db.String(255), unique=False, nullable=True) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - - @classmethod - def getdeliverymodes(cls): - deliverymode_schema = DeliveryModeSchema(many=True) - query = db.session.query(DeliveryMode).filter_by(isactive=True).all() - return deliverymode_schema.dump(query) - - @classmethod - def getdeliverymode(cls,deliverymode): - deliverymode_schema = DeliveryModeSchema() - query = db.session.query(DeliveryMode).filter_by(name=deliverymode).first() - return deliverymode_schema.dump(query) - -class DeliveryModeSchema(ma.Schema): - class Meta: - fields = ('deliverymodeid', 'name', 'description','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/DocumentPathMapper.py b/historical-search-api/request_api/models/DocumentPathMapper.py deleted file mode 100644 index 1e168c5a8..000000000 --- a/historical-search-api/request_api/models/DocumentPathMapper.py +++ /dev/null @@ -1,41 +0,0 @@ -from .db import db, ma -from .default_method_result import DefaultMethodResult -from datetime import datetime -from request_api.utils.enums import DocumentPathMapperCategory -from request_api.exceptions import BusinessException -import json -from sqlalchemy import func -from request_api import status as http_status -from request_api.exceptions.errors import Error - -class DocumentPathMapper(db.Model): - __tablename__ = 'DocumentPathMapper' - # Defining the columns - documentpathid = db.Column(db.Integer, primary_key=True,autoincrement=True) - category = db.Column(db.Text, unique=False, nullable=False) - bucket = db.Column(db.Text, unique=False, nullable=False) - attributes = db.Column(db.Text, unique=False, nullable=False) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - 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) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - - @classmethod - def getdocumentpath(cls, category, programarea=None): - documentpath_schema = DocumentPathMapperSchema() - query = db.session.query(DocumentPathMapper).filter(DocumentPathMapper.isactive == True, func.lower(DocumentPathMapper.category) == category.lower()) - if category.lower() == DocumentPathMapperCategory.Records.value.lower(): - query = query.filter(DocumentPathMapper.bucket.ilike(programarea.lower() + '%')) - pathmap = documentpath_schema.dump(query.first()) - try: - pathmap['attributes'] = json.loads(pathmap['attributes']) - except TypeError: - raise BusinessException(Error.MISSING_ACCESS_KEY) - - return pathmap - - -class DocumentPathMapperSchema(ma.Schema): - class Meta: - fields = ('documentpathid', 'category', 'bucket','attributes') \ No newline at end of file diff --git a/historical-search-api/request_api/models/DocumentTemplate.py b/historical-search-api/request_api/models/DocumentTemplate.py deleted file mode 100644 index 5b26e99ce..000000000 --- a/historical-search-api/request_api/models/DocumentTemplate.py +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import annotations - -from datetime import date, datetime - -from sqlalchemy import ForeignKey - -from .db import db, ma - - -class DocumentTemplate(db.Model): - __tablename__ = 'DocumentTemplates' - # Defining the columns - - template_id = db.Column(db.Integer, primary_key=True, autoincrement=True) - document_type_id = db.Column(db.Integer, ForeignKey('DocumentTypes.document_type_id'), nullable=False) - cdogs_hash_code = db.Column(db.String(64), nullable=True, unique=True) - extension = db.Column(db.String(10), nullable=False) - - @classmethod - def get_template_by_type(cls, document_type_id: int, extension: str = "docx"): - """Given a type and optionally an extension, return the template.""" - - query = cls.query.filter_by(document_type_id = document_type_id). \ - filter(DocumentTemplate.extension == extension) - - return query.one_or_none() - - @staticmethod - def commit(): - """Commit the session.""" - db.session.commit() - - def flush(self): - """Save and flush.""" - db.session.add(self) - db.session.flush() - return self - - -class DocumentTemplateSchema(ma.Schema): - class Meta: - model = DocumentTemplate - exclude = [] diff --git a/historical-search-api/request_api/models/DocumentType.py b/historical-search-api/request_api/models/DocumentType.py deleted file mode 100644 index d5a7d9db5..000000000 --- a/historical-search-api/request_api/models/DocumentType.py +++ /dev/null @@ -1,41 +0,0 @@ -from __future__ import annotations - -from datetime import date, datetime - -from sqlalchemy import ForeignKey - -from .db import db, ma - - -class DocumentType(db.Model): - __tablename__ = 'DocumentTypes' - # Defining the columns - - document_type_id = db.Column(db.Integer, primary_key=True, autoincrement=True) - document_type_name = db.Column(db.String(30), nullable=False, unique=False) - description = db.Column(db.String(100), unique=False, nullable=True) - - @classmethod - def get_document_type_by_name(cls, document_type_name: str): - """Given a type and optionally an extension, return the template.""" - - query = cls.query.filter_by(document_type_name = document_type_name) - - return query.one_or_none() - - @staticmethod - def commit(): - """Commit the session.""" - db.session.commit() - - def flush(self): - """Save and flush.""" - db.session.add(self) - db.session.flush() - return self - - -class DocumentTypeSchema(ma.Schema): - class Meta: - model = DocumentType - exclude = [] diff --git a/historical-search-api/request_api/models/ExtensionReasons.py b/historical-search-api/request_api/models/ExtensionReasons.py deleted file mode 100644 index d7f88b33e..000000000 --- a/historical-search-api/request_api/models/ExtensionReasons.py +++ /dev/null @@ -1,28 +0,0 @@ -from .db import db, ma - -class ExtensionReason(db.Model): - __tablename__ = 'ExtensionReasons' - # Defining the columns - extensionreasonid = db.Column(db.Integer, primary_key=True,autoincrement=True) - reason = db.Column(db.String(100), unique=False, nullable=False) - extensiontype = db.Column(db.String(25), unique=False, nullable=False) - defaultextendedduedays = db.Column(db.Integer, unique=False, nullable=True) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - - @classmethod - def getallextensionreasons(cls): - extensionreason_schema = ExtensionReasonSchema(many=True) - query = db.session.query(ExtensionReason).filter_by(isactive=True).order_by(ExtensionReason.extensionreasonid.asc()).all() - return extensionreason_schema.dump(query) - - @classmethod - def getextensionreasonbyid(cls,extensionreasonid): - extensionreason_schema = ExtensionReasonSchema() - query = db.session.query(ExtensionReason).filter_by(extensionreasonid=extensionreasonid).first() - return extensionreason_schema.dump(query) - - - -class ExtensionReasonSchema(ma.Schema): - class Meta: - fields = ('extensionreasonid','reason','extensiontype','isactive', 'defaultextendedduedays') \ No newline at end of file diff --git a/historical-search-api/request_api/models/ExtensionStatuses.py b/historical-search-api/request_api/models/ExtensionStatuses.py deleted file mode 100644 index 2a179860d..000000000 --- a/historical-search-api/request_api/models/ExtensionStatuses.py +++ /dev/null @@ -1,27 +0,0 @@ -from .db import db, ma - -class ExtensionStatus(db.Model): - __tablename__ = 'ExtensionStatuses' - # Defining the columns - extensionstatusid = db.Column(db.Integer, primary_key=True,autoincrement=True) - name = db.Column(db.String(25), unique=False, nullable=False) - description = db.Column(db.String(100), unique=False, nullable=False) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - - @classmethod - def getallextensionstatuses(cls): - extensionstatus_schema = ExtensionStatusSchema(many=True) - query = db.session.query(ExtensionStatus).filter_by(isactive=True).order_by(ExtensionStatus.extensionstatusid.asc()).all() - return extensionstatus_schema.dump(query) - - @classmethod - def getextensionstatus(cls,extensionstatusid): - extensionstatus_schema = ExtensionStatusSchema(many=True) - query = db.session.query(ExtensionStatus).filter_by(extensionstatusid=extensionstatusid).first() - return extensionstatus_schema.dump(query) - - - -class ExtensionStatusSchema(ma.Schema): - class Meta: - fields = ('extensionstatusid','name','description','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIApplicantCorrespondenceAttachments.py b/historical-search-api/request_api/models/FOIApplicantCorrespondenceAttachments.py deleted file mode 100644 index fd267214f..000000000 --- a/historical-search-api/request_api/models/FOIApplicantCorrespondenceAttachments.py +++ /dev/null @@ -1,50 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint -from .db import db, ma -from datetime import datetime -from sqlalchemy.orm import relationship,backref -from .default_method_result import DefaultMethodResult -from sqlalchemy.sql.expression import distinct -from sqlalchemy import or_,and_,text - -class FOIApplicantCorrespondenceAttachment(db.Model): - # Name of the table in our database - __tablename__ = 'FOIApplicantCorrespondenceAttachments' - __table_args__ = ( - ForeignKeyConstraint( - ["applicantcorrespondenceid"], ["FOIApplicantCorrespondences.applicantcorrespondenceid"] - ), - ) - - # Defining the columns - applicantcorrespondenceattachmentid = db.Column(db.Integer, primary_key=True,autoincrement=True) - attachmentdocumenturipath = db.Column(db.Text, unique=False, nullable=False) - attachmentfilename = db.Column(db.String(500), unique=False, nullable=False) - - created_at = db.Column(db.DateTime, default=datetime.now) - updated_at = db.Column(db.DateTime, nullable=True) - createdby = db.Column(db.String(120), unique=False, nullable=False) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - - applicantcorrespondenceid =db.Column(db.Integer, db.ForeignKey('FOIApplicantCorrespondences.applicantcorrespondenceid')) - - - - - @classmethod - def saveapplicantcorrespondenceattachment(cls, newapplicantcorrepondenceattachment)->DefaultMethodResult: - - db.session.add(newapplicantcorrepondenceattachment) - db.session.commit() - return DefaultMethodResult(True,'applicant correpondence attachment added',newapplicantcorrepondenceattachment.applicantcorrespondenceattachmentid) - - @classmethod - def getapplicantcorrespondenceattachmentsbyapplicantcorrespondenceid(cls,applicantcorrespondenceid): - correspondenceattachment_schema = FOIApplicantCorrespondenceAttachmentSchema(many=True) - query = db.session.query(FOIApplicantCorrespondenceAttachment).filter(FOIApplicantCorrespondenceAttachment.applicantcorrespondenceid == applicantcorrespondenceid).order_by(FOIApplicantCorrespondenceAttachment.applicantcorrespondenceattachmentid.asc()).all() - return correspondenceattachment_schema.dump(query) - -class FOIApplicantCorrespondenceAttachmentSchema(ma.Schema): - class Meta: - fields = ('applicantcorrespondenceattachmentid','applicantcorrespondenceid', 'attachmentdocumenturipath','attachmentfilename','created_at','createdby') - \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIApplicantCorrespondences.py b/historical-search-api/request_api/models/FOIApplicantCorrespondences.py deleted file mode 100644 index 2469b8d3d..000000000 --- a/historical-search-api/request_api/models/FOIApplicantCorrespondences.py +++ /dev/null @@ -1,93 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint -from .db import db, ma -from datetime import datetime -from sqlalchemy.orm import relationship,backref -from .default_method_result import DefaultMethodResult -from sqlalchemy.sql.expression import distinct -from sqlalchemy import or_,and_,text -from .FOIApplicantCorrespondenceAttachments import FOIApplicantCorrespondenceAttachment -from sqlalchemy.dialects.postgresql import JSON, UUID - -class FOIApplicantCorrespondence(db.Model): - # Name of the table in our database - __tablename__ = 'FOIApplicantCorrespondences' - __table_args__ = ( - ForeignKeyConstraint( - ["foiministryrequest_id", "foiministryrequestversion_id"], ["FOIMinistryRequests.foiministryrequestid", "FOIMinistryRequests.version"] - ), - ) - - # Defining the columns - applicantcorrespondenceid = db.Column(db.Integer, primary_key=True,autoincrement=True) - parentapplicantcorrespondenceid = db.Column(db.Integer) - templateid = db.Column(db.Integer, nullable=True) - correspondencemessagejson = db.Column(db.Text, unique=False, nullable=True) - - sentcorrespondencemessage = db.Column(JSON, unique=False, nullable=True) - sent_at = db.Column(db.DateTime, nullable=True) - sentby = db.Column(db.String(120), unique=False, nullable=True) - - created_at = db.Column(db.DateTime, default=datetime.now) - updated_at = db.Column(db.DateTime, nullable=True) - createdby = db.Column(db.String(120), unique=False, nullable=False) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - - #ForeignKey References - foiministryrequest_id =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.foiministryrequestid')) - foiministryrequestversion_id=db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.version')) - - attachments = relationship('FOIApplicantCorrespondenceAttachment', backref=backref("FOIApplicantCorrespondenceAttachments")) - - @classmethod - def getapplicantcorrespondences(cls,ministryrequestid): - comment_schema = FOIApplicantCorrespondenceSchema(many=True) - query = db.session.query(FOIApplicantCorrespondence).filter(FOIApplicantCorrespondence.foiministryrequest_id == ministryrequestid).order_by(FOIApplicantCorrespondence.applicantcorrespondenceid.desc()).all() - return comment_schema.dump(query) - - @classmethod - def getapplicantcorrespondencebyid(cls,applicantcorrespondenceid): - correspondence_schema = FOIApplicantCorrespondenceSchema() - query = db.session.query(FOIApplicantCorrespondence).filter(FOIApplicantCorrespondence.applicantcorrespondenceid == applicantcorrespondenceid).first() - return correspondence_schema.dump(query) - - @classmethod - def getlatestapplicantcorrespondence(cls,ministryrequestid): - correspondence_schema = FOIApplicantCorrespondenceSchema() - query = db.session.query(FOIApplicantCorrespondence).filter(FOIApplicantCorrespondence.foiministryrequest_id == ministryrequestid, FOIApplicantCorrespondence.createdby != 'System Generated Email').order_by(FOIApplicantCorrespondence.applicantcorrespondenceid.desc()).first() - return correspondence_schema.dump(query) - - @classmethod - def saveapplicantcorrespondence(cls, newapplicantcorrepondencelog,attachments)->DefaultMethodResult: - db.session.add(newapplicantcorrepondencelog) - db.session.commit() - try: - if(attachments is not None and len(attachments) > 0): - for _attachment in attachments: - attachment = FOIApplicantCorrespondenceAttachment() - attachment.applicantcorrespondenceid = newapplicantcorrepondencelog.applicantcorrespondenceid - attachment.attachmentdocumenturipath = _attachment['url'] - attachment.attachmentfilename = _attachment['filename'] - attachment.createdby = newapplicantcorrepondencelog.createdby - FOIApplicantCorrespondenceAttachment().saveapplicantcorrespondenceattachment(attachment) - except Exception: - return DefaultMethodResult(False,'applicantcorrepondence log exception while adding attachments',newapplicantcorrepondencelog.applicantcorrespondenceid) - - return DefaultMethodResult(True,'applicantcorrepondence log added',newapplicantcorrepondencelog.applicantcorrespondenceid) - - - @classmethod - def updatesentcorrespondence(cls, applicantcorrespondenceid, content)->DefaultMethodResult: - dbquery = db.session.query(FOIApplicantCorrespondence) - _correspondence = dbquery.filter_by(applicantcorrespondenceid=applicantcorrespondenceid) - if(_correspondence.count() > 0) : - _correspondence.update({FOIApplicantCorrespondence.sentcorrespondencemessage:content, FOIApplicantCorrespondence.sent_at:datetime.now(), FOIApplicantCorrespondence.sentby:"System Generated Email"}, synchronize_session = False) - db.session.commit() - return DefaultMethodResult(True,'Applicant correspondence updated for Id',applicantcorrespondenceid) - else: - return DefaultMethodResult(False,'Applicant correspondence not exists',-1) - -class FOIApplicantCorrespondenceSchema(ma.Schema): - class Meta: - fields = ('applicantcorrespondenceid','parentapplicantcorrespondenceid', 'templateid','correspondencemessagejson','foiministryrequest_id','foiministryrequestversion_id','created_at','createdby','attachments','sentcorrespondencemessage','sent_at','sentby') - \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIAssignees.py b/historical-search-api/request_api/models/FOIAssignees.py deleted file mode 100644 index f5ebf9ab4..000000000 --- a/historical-search-api/request_api/models/FOIAssignees.py +++ /dev/null @@ -1,44 +0,0 @@ -from .db import db, ma -from .default_method_result import DefaultMethodResult -from sqlalchemy.dialects.postgresql import insert - -class FOIAssignee(db.Model): - # Name of the table in our database - __tablename__ = 'FOIAssignees' - # Defining the columns - foiassigneeid = db.Column(db.Integer, primary_key=True, autoincrement=True) - username = db.Column(db.String(120), unique=True, nullable=False) - firstname = db.Column(db.String(100), unique=False, nullable=False) - middlename = db.Column(db.String(100), unique=False, nullable=True) - lastname = db.Column(db.String(100), unique=False, nullable=False) - isactive = db.Column(db.Boolean, unique=False, nullable=False, default=True) - - @classmethod - def saveassignee(cls, username, firstname, middlename, lastname)->DefaultMethodResult: - - insertstmt = insert(FOIAssignee).values( - username=username, - firstname=firstname, - middlename=middlename, - lastname=lastname - ) - updatestmt = insertstmt.on_conflict_do_update(index_elements=[FOIAssignee.username], set_={"firstname": firstname,"middlename":middlename,"lastname":lastname}) - db.session.execute(updatestmt) - db.session.commit() - return DefaultMethodResult(True, 'Assignee added', username) - - @classmethod - def getassignees(cls): - assignee_schema = FOIAssigneeSchema(many=True) - query = db.session.query(FOIAssignee).filter_by(isactive=True).all() - return assignee_schema.dump(query) - - @classmethod - def getassignee(cls,username): - assignee_schema = FOIAssigneeSchema() - query = db.session.query(FOIAssignee).filter_by(username=username).first() - return assignee_schema.dump(query) - -class FOIAssigneeSchema(ma.Schema): - class Meta: - fields = ('foiassigneeid','username','firstname','middlename','lastname','isactive') diff --git a/historical-search-api/request_api/models/FOIMinistryRequestDivisions.py b/historical-search-api/request_api/models/FOIMinistryRequestDivisions.py deleted file mode 100644 index 33af2aeb8..000000000 --- a/historical-search-api/request_api/models/FOIMinistryRequestDivisions.py +++ /dev/null @@ -1,58 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint -from .db import db, ma -from datetime import datetime -from sqlalchemy.orm import relationship,backref -from .default_method_result import DefaultMethodResult -from sqlalchemy.sql.expression import distinct -from sqlalchemy import or_, and_, text - - -class FOIMinistryRequestDivision(db.Model): - # Name of the table in our database - __tablename__ = 'FOIMinistryRequestDivisions' - __table_args__ = ( - ForeignKeyConstraint( - ["foiministryrequest_id", "foiministryrequestversion_id"], ["FOIMinistryRequests.foiministryrequestid", "FOIMinistryRequests.version"] - ), - ) - - - # Defining the columns - foiministrydivisionid = db.Column(db.Integer, primary_key=True,autoincrement=True) - - divisionid = db.Column(db.Integer,ForeignKey('ProgramAreaDivisions.divisionid')) - division = relationship("ProgramAreaDivision",backref=backref("ProgramAreaDivisions"),uselist=False) - - stageid = db.Column(db.Integer,ForeignKey('ProgramAreaDivisionStages.stageid')) - stage = relationship("ProgramAreaDivisionStage",backref=backref("ProgramAreaDivisionStages"),uselist=False) - - divisionduedate = db.Column(db.DateTime, nullable=True) - eapproval = db.Column(db.String(12), nullable=True) - - divisionreceiveddate = db.Column(db.DateTime, nullable=True) - - created_at = db.Column(db.DateTime, default=datetime.now) - updated_at = db.Column(db.DateTime, nullable=True) - createdby = db.Column(db.String(120), unique=False, nullable=True) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - - #ForeignKey References - foiministryrequest_id =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.foiministryrequestid')) - foiministryrequestversion_id = db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.version')) - foiministryrequest = relationship("FOIMinistryRequest",foreign_keys="[FOIMinistryRequestDivision.foiministryrequest_id]") - foiministryrequestversion = relationship("FOIMinistryRequest",foreign_keys="[FOIMinistryRequestDivision.foiministryrequestversion_id]") - - @classmethod - def getdivisions(cls,ministryrequestid,ministryrequestversion): - division_schema = FOIMinistryRequestDivisionSchema(many=True) - _divisions = db.session.query(FOIMinistryRequestDivision).filter(FOIMinistryRequestDivision.foiministryrequest_id == ministryrequestid , FOIMinistryRequestDivision.foiministryrequestversion_id == ministryrequestversion).order_by(FOIMinistryRequestDivision.foiministrydivisionid.asc()).all() - divisioninfos = division_schema.dump(_divisions) - return divisioninfos - - - -class FOIMinistryRequestDivisionSchema(ma.Schema): - class Meta: - fields = ('foiministrydivisionid','division.divisionid','division.name','stage.stageid','stage.name','foiministryrequest_id','foiministryrequestversion_id', 'divisionduedate', 'eapproval', 'divisionreceiveddate') - \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIMinistryRequestDocuments.py b/historical-search-api/request_api/models/FOIMinistryRequestDocuments.py deleted file mode 100644 index 3acdb80f2..000000000 --- a/historical-search-api/request_api/models/FOIMinistryRequestDocuments.py +++ /dev/null @@ -1,159 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint -from .db import db, ma -from datetime import datetime -from sqlalchemy.orm import relationship,backref -from .default_method_result import DefaultMethodResult -from sqlalchemy.sql.expression import distinct -from sqlalchemy import or_,and_,text - -class FOIMinistryRequestDocument(db.Model): - # Name of the table in our database - __tablename__ = 'FOIMinistryRequestDocuments' - __table_args__ = ( - ForeignKeyConstraint( - ["foiministryrequest_id", "foiministryrequestversion_id"], ["FOIMinistryRequests.foiministryrequestid", "FOIMinistryRequests.version"] - ), - ) - - # Defining the columns - foiministrydocumentid = db.Column(db.Integer, primary_key=True,autoincrement=True) - documentpath = db.Column(db.String(1000), unique=False, nullable=False) - filename = db.Column(db.String(120), unique=False, nullable=True) - category = db.Column(db.String(120), unique=False, nullable=True) - version =db.Column(db.Integer, nullable=True) - isactive = db.Column(db.Boolean, unique=False, nullable=False,default=True) - - created_at = db.Column(db.DateTime, default=datetime.now) - updated_at = db.Column(db.DateTime, nullable=True) - createdby = db.Column(db.String(120), unique=False, nullable=True) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - - #ForeignKey References - foiministryrequest_id =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.foiministryrequestid')) - foiministryrequestversion_id = db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.version')) - foiministryrequest = relationship("FOIMinistryRequest",foreign_keys="[FOIMinistryRequestDocument.foiministryrequest_id]") - foiministryrequestversion = relationship("FOIMinistryRequest",foreign_keys="[FOIMinistryRequestDocument.foiministryrequestversion_id]") - - @classmethod - def getdocuments(cls,ministryrequestid,ministryrequestversion): - sql = 'SELECT * FROM (SELECT DISTINCT ON (foiministrydocumentid) foiministrydocumentid, filename, documentpath, category, isactive, created_at , createdby, version FROM "FOIMinistryRequestDocuments" where foiministryrequest_id =:ministryrequestid and foiministryrequestversion_id = :ministryrequestversion ORDER BY foiministrydocumentid, version DESC) AS list ORDER BY created_at DESC' - rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid, 'ministryrequestversion':ministryrequestversion}) - documents = [] - for row in rs: - if row["isactive"] == True: - documents.append({"foiministrydocumentid": row["foiministrydocumentid"], "filename": row["filename"], "documentpath": row["documentpath"], "category": row["category"], "created_at": row["created_at"].strftime('%Y-%m-%d %H:%M:%S.%f'), "createdby": row["createdby"], "version": row["version"]}) - return documents - - @classmethod - def getactivedocuments(cls,ministryrequestid): - 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: - if row["isactive"] == True: - documents.append({"foiministrydocumentid": row["foiministrydocumentid"], "filename": row["filename"], "documentpath": row["documentpath"], "category": row["category"], "created_at": row["created_at"].strftime('%Y-%m-%d %H:%M:%S.%f'), "createdby": row["createdby"], "version": row["version"]}) - return documents - - @classmethod - def getdocumentsbycategory(cls, ministryrequestid, ministryrequestversion, category): - sql = 'SELECT * FROM (SELECT DISTINCT ON (foiministrydocumentid) foiministrydocumentid, filename, documentpath, category, isactive, created_at , createdby, version FROM "FOIMinistryRequestDocuments" where foiministryrequest_id =:ministryrequestid and foiministryrequestversion_id = :ministryrequestversion and category = :category ORDER BY foiministrydocumentid, version DESC) AS list ORDER BY created_at DESC' - rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid, 'ministryrequestversion':ministryrequestversion, 'category': category}) - documents = [] - for row in rs: - if row["isactive"] == True: - documents.append({"foiministrydocumentid": row["foiministrydocumentid"], "filename": row["filename"], "documentpath": row["documentpath"], "category": row["category"], "created_at": row["created_at"].strftime('%Y-%m-%d %H:%M:%S.%f'), "createdby": row["createdby"]}) - return documents - @classmethod - def getdocument(cls,foiministrydocumentid): - document_schema = FOIMinistryRequestDocumentSchema() - request = db.session.query(FOIMinistryRequestDocument).filter_by(foiministrydocumentid=foiministrydocumentid).order_by(FOIMinistryRequestDocument.version.desc()).first() - return document_schema.dump(request) - - @classmethod - def createdocuments(cls,ministryrequestid,ministryrequestversion, documents, userid): - newdocuments = [] - for document in documents: - createuserid = document['createdby'] if 'createdby' in document and document['createdby'] is not None else userid - createdat = document['created_at'] if 'created_at' in document and document['created_at'] is not None else datetime.now() - newdocuments.append(FOIMinistryRequestDocument(documentpath=document["documentpath"], version='1', filename=document["filename"], category=document["category"], isactive=True, foiministryrequest_id=ministryrequestid, foiministryrequestversion_id=ministryrequestversion, created_at=createdat, createdby=createuserid)) - db.session.add_all(newdocuments) - db.session.commit() - return DefaultMethodResult(True,'Documents created') - - @classmethod - def createdocument(cls,ministryrequestid,ministryrequestversion, document, userid): - createuserid = document['createdby'] if 'createdby' in document and document['createdby'] is not None else userid - createdat = document['created_at'] if 'created_at' in document and document['created_at'] is not None else datetime.now() - newdocument = FOIMinistryRequestDocument(documentpath=document["documentpath"], version='1', filename=document["filename"], category=document["category"], isactive=True, foiministryrequest_id=ministryrequestid, foiministryrequestversion_id=ministryrequestversion, created_at=createdat, createdby=createuserid) - db.session.add(newdocument) - db.session.commit() - return DefaultMethodResult(True,'New Document version created', newdocument.foiministrydocumentid) - - @classmethod - def createdocumentversion(cls,ministryrequestid,ministryrequestversion, document, userid): - newdocument = FOIMinistryRequestDocument(documentpath=document["documentpath"], foiministrydocumentid=document["foiministrydocumentid"], version=document["version"], filename=document["filename"], category=document["category"], isactive=document["isactive"], foiministryrequest_id=ministryrequestid, foiministryrequestversion_id=ministryrequestversion, created_at=datetime.now(), createdby=userid) - db.session.add(newdocument) - db.session.commit() - return DefaultMethodResult(True,'New Document version created', newdocument.foiministrydocumentid) - - @classmethod - - def getlatestdocumentsforemail(cls, ministryrequestid, ministryrequestversion, category): - sql = 'SELECT DISTINCT ON (foiministrydocumentid) foiministrydocumentid, filename, documentpath, category, isactive, created_at , createdby, version FROM "FOIMinistryRequestDocuments" where foiministryrequest_id =:ministryrequestid and foiministryrequestversion_id = :ministryrequestversion and lower(category) = lower(:category) ORDER BY foiministrydocumentid DESC' - - rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid, 'ministryrequestversion':ministryrequestversion, 'category': category}) - documents = [] - for row in rs: - if row["isactive"] == True: - documents.append({"foiministrydocumentid": row["foiministrydocumentid"], "filename": row["filename"], "documentpath": row["documentpath"], "category": row["category"], "created_at": row["created_at"].strftime('%Y-%m-%d %H:%M:%S.%f'), "createdby": row["createdby"]}) - return documents - - def getlatestreceiptdocumentforemail(cls, ministryrequestid, category): - sql = 'SELECT DISTINCT ON (foiministrydocumentid) foiministrydocumentid, filename, documentpath, category, isactive, created_at , createdby, version FROM "FOIMinistryRequestDocuments" where foiministryrequest_id =:ministryrequestid and lower(category) = lower(:category) ORDER BY foiministrydocumentid DESC limit 1' - - rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid, 'category': category}) - documents = [] - for row in rs: - if row["isactive"] == True: - documents.append({"foiministrydocumentid": row["foiministrydocumentid"], "filename": row["filename"], "documentpath": row["documentpath"], "category": row["category"], "created_at": row["created_at"].strftime('%Y-%m-%d %H:%M:%S.%f'), "createdby": row["createdby"]}) - return documents - - @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.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.commit() - return DefaultMethodResult(True,'Documents Updated for the ministry',ministryid) - -class FOIMinistryRequestDocumentSchema(ma.Schema): - class Meta: - fields = ('foiministrydocumentid','documentpath', 'filename','category','version','isactive','foiministryrequest_id','foiministryrequestversion_id','created_at','createdby') - diff --git a/historical-search-api/request_api/models/FOIMinistryRequestSubjectCodes.py b/historical-search-api/request_api/models/FOIMinistryRequestSubjectCodes.py deleted file mode 100644 index d1f363122..000000000 --- a/historical-search-api/request_api/models/FOIMinistryRequestSubjectCodes.py +++ /dev/null @@ -1,54 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint -from .db import db, ma -from datetime import datetime -from sqlalchemy.orm import relationship,backref -from .default_method_result import DefaultMethodResult -from sqlalchemy.sql.expression import distinct -from sqlalchemy import or_, and_, text - - -class FOIMinistryRequestSubjectCode(db.Model): - # Name of the table in our database - __tablename__ = 'FOIMinistryRequestSubjectCodes' - __table_args__ = ( - ForeignKeyConstraint( - ["foiministryrequestid", "foiministryrequestversion"], ["FOIMinistryRequests.foiministryrequestid", "FOIMinistryRequests.version"] - ), - ) - - - # Defining the columns - foiministrysubjectcodeid = db.Column(db.Integer, primary_key=True,autoincrement=True) - subjectcodeid = db.Column(db.Integer,ForeignKey('SubjectCodes.subjectcodeid')) - subjectcode = relationship("SubjectCode",backref=backref("SubjectCodes"),uselist=False) - - created_at = db.Column(db.DateTime, default=datetime.now) - updated_at = db.Column(db.DateTime, nullable=True) - createdby = db.Column(db.String(120), unique=False, nullable=True) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - - #ForeignKey References - foiministryrequestid =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.foiministryrequestid')) - foiministryrequestversion = db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.version')) - ministryrequest = relationship("FOIMinistryRequest",foreign_keys="[FOIMinistryRequestSubjectCode.foiministryrequestid]") - ministryrequestversion = relationship("FOIMinistryRequest",foreign_keys="[FOIMinistryRequestSubjectCode.foiministryrequestversion]") - - @classmethod - def getministrysubjectcode(cls, ministryrequestid, ministryrequestversion): - ministrysubjectcode_schema = FOIMinistryRequestSubjectCodeSchema() - ministrysubjectcode = db.session.query(FOIMinistryRequestSubjectCode).filter(FOIMinistryRequestSubjectCode.foiministryrequestid == ministryrequestid , FOIMinistryRequestSubjectCode.foiministryrequestversion == ministryrequestversion).first() - subjectcodeinfos = ministrysubjectcode_schema.dump(ministrysubjectcode) - return subjectcodeinfos - - @classmethod - def savesubjectcode(cls, ministryrequestid, ministryrequestversion, subjectcodeid, userid): - newsubjectcode = FOIMinistryRequestSubjectCode(subjectcodeid=subjectcodeid, foiministryrequestid=ministryrequestid, foiministryrequestversion=ministryrequestversion, created_at=datetime.now(), createdby=userid) - db.session.add(newsubjectcode) - db.session.commit() - return DefaultMethodResult(True,'New subject code added', newsubjectcode.foiministrysubjectcodeid) - -class FOIMinistryRequestSubjectCodeSchema(ma.Schema): - class Meta: - fields = ('foiministrysubjectcodeid','subjectcodeid','subjectcode.name','foiministryrequestid','foiministryrequestversion') - \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIMinistryRequests.py b/historical-search-api/request_api/models/FOIMinistryRequests.py deleted file mode 100644 index 782a3b78b..000000000 --- a/historical-search-api/request_api/models/FOIMinistryRequests.py +++ /dev/null @@ -1,1535 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint -from .db import db, ma -from datetime import datetime -from sqlalchemy.orm import relationship, backref, aliased -from .default_method_result import DefaultMethodResult -from .FOIRequests import FOIRequest, FOIRequestsSchema -from sqlalchemy.sql.expression import distinct -from sqlalchemy import or_, and_, text, func, literal, cast, case, nullslast, nullsfirst, desc, asc -from sqlalchemy.sql.sqltypes import String -from sqlalchemy.dialects.postgresql import JSON -from .FOIRequestApplicantMappings import FOIRequestApplicantMapping -from .FOIRequestApplicants import FOIRequestApplicant -from .FOIRequestStatus import FOIRequestStatus -from .ApplicantCategories import ApplicantCategory -from .FOIRequestWatchers import FOIRequestWatcher -from .FOIRestrictedMinistryRequests import FOIRestrictedMinistryRequest -from .ProgramAreas import ProgramArea -from request_api.utils.enums import ProcessingTeamWithKeycloackGroup, IAOTeamWithKeycloackGroup -from .FOIAssignees import FOIAssignee -from .FOIRequestExtensions import FOIRequestExtension -from request_api.utils.enums import RequestorType -import logging -from sqlalchemy.sql.sqltypes import Date, Integer -from dateutil import parser -from request_api.utils.enums import StateName -from .FOIMinistryRequestSubjectCodes import FOIMinistryRequestSubjectCode -from .SubjectCodes import SubjectCode -from request_api.utils.enums import StateName -from .FOIRequestOIPC import FOIRequestOIPC - -class FOIMinistryRequest(db.Model): - # Name of the table in our database - __tablename__ = 'FOIMinistryRequests' - __table_args__ = ( - ForeignKeyConstraint( - ["foirequest_id", "foirequestversion_id"], ["FOIRequests.foirequestid", "FOIRequests.version"] - ), - ) - - # Defining the columns - foiministryrequestid = db.Column(db.Integer, primary_key=True,autoincrement=True) - version = db.Column(db.Integer, primary_key=True,nullable=False) - isactive = db.Column(db.Boolean, unique=False, nullable=False,default=True) - - filenumber = db.Column(db.String(50), unique=False, nullable=False) - description = db.Column(db.Text, unique=False, nullable=False) - recordsearchfromdate = db.Column(db.DateTime, nullable=True) - recordsearchtodate = db.Column(db.DateTime, nullable=True) - - 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) - - created_at = db.Column(db.DateTime, default=datetime.now) - updated_at = db.Column(db.DateTime, nullable=True) - createdby = db.Column(db.String(120), unique=False, nullable=True) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - assignedministryperson = db.Column(db.String(120), ForeignKey('FOIAssignees.username'), unique=False, nullable=True) - assignedministrygroup = db.Column(db.String(120), unique=False, nullable=True) - closedate = db.Column(db.DateTime, nullable=True) - - axissyncdate = db.Column(db.DateTime, nullable=True) - axisrequestid = db.Column(db.String(120), nullable=True) - axispagecount = db.Column(db.String(20), nullable=True) - axislanpagecount = db.Column(db.String(20), nullable=True) - recordspagecount = db.Column(db.String(20), nullable=True) - estimatedpagecount = db.Column(db.Integer, nullable=True) - estimatedtaggedpagecount = db.Column(db.Integer, 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) - requeststatuslabel = db.Column(db.String(50), nullable=False) - - #ForeignKey References - - closereasonid = db.Column(db.Integer,ForeignKey('CloseReasons.closereasonid')) - closereason = relationship("CloseReason",uselist=False) - - programareaid = db.Column(db.Integer,ForeignKey('ProgramAreas.programareaid')) - programarea = relationship("ProgramArea",backref=backref("ProgramAreas"),uselist=False) - - requeststatusid = db.Column(db.Integer,ForeignKey('FOIRequestStatuses.requeststatusid')) - requeststatus = relationship("FOIRequestStatus",backref=backref("FOIRequestStatuses"),uselist=False) - - foirequest_id =db.Column(db.Integer, db.ForeignKey('FOIRequests.foirequestid')) - foirequestversion_id = db.Column(db.Integer, db.ForeignKey('FOIRequests.version')) - foirequestkey = relationship("FOIRequest",foreign_keys="[FOIMinistryRequest.foirequest_id]") - foirequestversion = relationship("FOIRequest",foreign_keys="[FOIMinistryRequest.foirequestversion_id]") - - divisions = relationship('FOIMinistryRequestDivision', primaryjoin="and_(FOIMinistryRequest.foiministryrequestid==FOIMinistryRequestDivision.foiministryrequest_id, " - "FOIMinistryRequest.version==FOIMinistryRequestDivision.foiministryrequestversion_id)") - documents = relationship('FOIMinistryRequestDocument', primaryjoin="and_(FOIMinistryRequest.foiministryrequestid==FOIMinistryRequestDocument.foiministryrequest_id, " - "FOIMinistryRequest.version==FOIMinistryRequestDocument.foiministryrequestversion_id)") - extensions = relationship('FOIRequestExtension', primaryjoin="and_(FOIMinistryRequest.foiministryrequestid==FOIRequestExtension.foiministryrequest_id, " - "FOIMinistryRequest.version==FOIRequestExtension.foiministryrequestversion_id)") - - oipcreviews = relationship('FOIRequestOIPC', primaryjoin="and_(FOIMinistryRequest.foiministryrequestid==FOIRequestOIPC.foiministryrequest_id, " - "FOIMinistryRequest.version==FOIRequestOIPC.foiministryrequestversion_id)") - - assignee = relationship('FOIAssignee', foreign_keys="[FOIMinistryRequest.assignedto]") - ministryassignee = relationship('FOIAssignee', foreign_keys="[FOIMinistryRequest.assignedministryperson]") - - subjectcode = relationship('FOIMinistryRequestSubjectCode', primaryjoin="and_(FOIMinistryRequest.foiministryrequestid==FOIMinistryRequestSubjectCode.foiministryrequestid, " - "FOIMinistryRequest.version==FOIMinistryRequestSubjectCode.foiministryrequestversion)") - isofflinepayment = db.Column(db.Boolean, unique=False, nullable=True,default=False) - - isoipcreview = db.Column(db.Boolean, unique=False, nullable=True,default=False) - - @classmethod - def getrequest(cls,ministryrequestid): - request_schema = FOIMinistryRequestSchema(many=False) - query = db.session.query(FOIMinistryRequest).filter_by(foiministryrequestid=ministryrequestid).order_by(FOIMinistryRequest.version.desc()).first() - return request_schema.dump(query) - - @classmethod - def getLastStatusUpdateDate(cls,foiministryrequestid,requeststatuslabel): - statusdate = None - try: - sql = """select created_at from "FOIMinistryRequests" - where foiministryrequestid = :foiministryrequestid and requeststatuslabel = :requeststatuslabel - order by version desc limit 1;""" - rs = db.session.execute(text(sql), {'foiministryrequestid': foiministryrequestid, 'requeststatuslabel': requeststatuslabel}) - statusdate = [row[0] for row in rs][0] - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return statusdate - - - - @classmethod - def getassignmenttransition(cls,requestid): - assignments = [] - try: - sql = """select version, assignedto, assignedministryperson from "FOIMinistryRequests" - where foiministryrequestid = :requestid - order by version desc limit 2;""" - rs = db.session.execute(text(sql), {'requestid': requestid}) - for row in rs: - assignments.append({"assignedto": row["assignedto"], "assignedministryperson": row["assignedministryperson"], "version": row["version"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return assignments - - @classmethod - def deActivateFileNumberVersion(cls, ministryid, idnumber, userid)->DefaultMethodResult: - try: - sql = """update "FOIMinistryRequests" set isactive = false, updatedby = :userid, updated_at = now() - where foiministryrequestid = :ministryid and isactive = true and filenumber = :idnumber - and version != (select version from "FOIMinistryRequests" fr where foiministryrequestid = :ministryid order by "version" desc limit 1)""" - db.session.execute(text(sql), {'ministryid': ministryid, 'userid':userid, 'idnumber': idnumber}) - db.session.commit() - return DefaultMethodResult(True,'Request Updated',idnumber) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - - @classmethod - def getrequests(cls, group = None): - _session = db.session - _ministryrequestids = [] - - 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.requeststatuslabel.in_([StateName.open.name,StateName.callforrecords.name,StateName.closed.name,StateName.recordsreview.name,StateName.feeestimate.name,StateName.consult.name,StateName.ministrysignoff.value,StateName.onhold.name,StateName.deduplication.name,StateName.harmsassessment.name,StateName.response.name,StateName.peerreview.name,StateName.tagging.name,StateName.readytoscan.name])))).all() - elif (group in ProcessingTeamWithKeycloackGroup.list()): - _ministryrequestids = _session.query(distinct(FOIMinistryRequest.foiministryrequestid)).filter(and_(FOIMinistryRequest.isactive == True), and_(and_(FOIMinistryRequest.assignedgroup == group),and_(FOIMinistryRequest.requeststatuslabel.in_([StateName.open.name,StateName.callforrecords.name,StateName.closed.name,StateName.recordsreview.name,StateName.feeestimate.name,StateName.consult.name,StateName.ministrysignoff.value,StateName.onhold.name,StateName.response.name,StateName.peerreview.name,StateName.tagging.name,StateName.readytoscan.name])))).all() - else: - _ministryrequestids = _session.query(distinct(FOIMinistryRequest.foiministryrequestid)).filter(and_(FOIMinistryRequest.isactive == True), or_(and_(FOIMinistryRequest.assignedgroup == group),and_(FOIMinistryRequest.assignedministrygroup == group,or_(FOIMinistryRequest.requeststatuslabel.in_([StateName.callforrecords.name,StateName.recordsreview.name,StateName.feeestimate.name,StateName.consult.name,StateName.ministrysignoff.name,StateName.onhold.name,StateName.deduplication.name,StateName.harmsassessment.name,StateName.response.name,StateName.peerreview.name,StateName.tagging.name,StateName.readytoscan.name]))))).all() - - _requests = [] - ministryrequest_schema = FOIMinistryRequestSchema() - for _requestid in _ministryrequestids: - _request ={} - - ministryrequest =ministryrequest_schema.dump(_session.query(FOIMinistryRequest).filter(FOIMinistryRequest.foiministryrequestid == _requestid).order_by(FOIMinistryRequest.version.desc()).first()) - parentrequest = _session.query(FOIRequest).filter(FOIRequest.foirequestid == ministryrequest['foirequest_id'] and FOIRequest.version == ministryrequest['foirequestversion_id']).order_by(FOIRequest.version.desc()).first() - requestapplicants = FOIRequestApplicantMapping.getrequestapplicants(ministryrequest['foirequest_id'],ministryrequest['foirequestversion_id']) - _receiveddate = parentrequest.receiveddate - _request["firstName"] = requestapplicants[0]['foirequestapplicant.firstname'] - _request["lastName"] = requestapplicants[0]['foirequestapplicant.lastname'] - _request["requestType"] = parentrequest.requesttype - _request["idNumber"] = ministryrequest['filenumber'] - _request["axisRequestId"] = ministryrequest['axisrequestid'] - _request["linkedrequests"] = ministryrequest['linkedrequests'] - _request["currentState"] = ministryrequest["requeststatus.name"] - _request["dueDate"] = ministryrequest["duedate"] - _request["cfrDueDate"] = ministryrequest["cfrduedate"] - _request["originalDueDate"] = ministryrequest["originalldd"] - _request["receivedDate"] = _receiveddate.strftime('%Y %b, %d') - _request["receivedDateUF"] =str(_receiveddate) - _request["assignedGroup"]=ministryrequest["assignedgroup"] - _request["assignedTo"]=ministryrequest["assignedto"] - _request["assignedministrygroup"]=ministryrequest["assignedministrygroup"] - _request["assignedministryperson"]=ministryrequest["assignedministryperson"] - _request["xgov"]='No' - _request["version"] = ministryrequest['version'] - _request["id"] = parentrequest.foirequestid - _request["ministryrequestid"] = ministryrequest['foiministryrequestid'] - _request["applicantcategory"]=parentrequest.applicantcategory.name - _request["identityverified"] = ministryrequest['identityverified'] - _requests.append(_request) - - return _requests - - @classmethod - def getrequestbyministryrequestid(cls,ministryrequestid): - request_schema = FOIMinistryRequestSchema() - query = db.session.query(FOIMinistryRequest).filter_by(foiministryrequestid=ministryrequestid).order_by(FOIMinistryRequest.version.desc()).first() - return request_schema.dump(query) - - @classmethod - def getrequestbyfilenumberandversion(cls,filenumber, version): - request_schema = FOIMinistryRequestSchema() - query = db.session.query(FOIMinistryRequest).filter_by(filenumber=filenumber, version = version).order_by(FOIMinistryRequest.version.desc()).first() - return request_schema.dump(query) - - @classmethod - def getrequestById(cls,ministryrequestid): - request_schema = FOIMinistryRequestSchema(many=True) - query = db.session.query(FOIMinistryRequest).filter_by(foiministryrequestid=ministryrequestid).order_by(FOIMinistryRequest.version.asc()) - return request_schema.dump(query) - - @classmethod - def getrequeststatusById(cls,ministryrequestid): - summary = [] - try: - sql = 'select foirequest_id, version, requeststatusid, requeststatuslabel, created_at from "FOIMinistryRequests" fr where foiministryrequestid = :ministryrequestid and requeststatuslabel != :requeststatuslabel order by version desc;' - - rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid, 'requeststatuslabel': StateName.closed.name}) - for row in rs: - summary.append({"requeststatusid": row["requeststatusid"], "requeststatuslabel": row["requeststatuslabel"], "created_at": row["created_at"], "foirequest_id": row["foirequest_id"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return summary - - - @classmethod - def getversionforrequest(cls,ministryrequestid): - return db.session.query(FOIMinistryRequest.version).filter_by(foiministryrequestid=ministryrequestid).order_by(FOIMinistryRequest.version.desc()).first() - - @classmethod - def getstatesummary(cls, ministryrequestid): - transitions = [] - try: - """ - sql =select status, version from (select distinct name as status, version from "FOIMinistryRequests" fm inner join "FOIRequestStatuses" fs2 on fm.requeststatusid = fs2.requeststatusid - where foiministryrequestid=:ministryrequestid order by version asc) as fs3 order by version desc; - """ - sql = """select fm2.version, fs2."name" as status, fm2.created_at from "FOIMinistryRequests" fm2 inner join "FOIRequestStatuses" fs2 on fm2.requeststatusid = fs2.requeststatusid - where fm2.foiministryrequestid=:ministryrequestid order by version desc""" - rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) - _tmp_state = None - for row in rs: - if row["status"] != _tmp_state: - transitions.append({"status": row["status"], "version": row["version"], "created_at": row["created_at"].strftime('%Y-%m-%d %H:%M:%S.%f')}) - _tmp_state = row["status"] - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return transitions - - @classmethod - def getlastoffholddate(cls, ministryrequestid): - transitions = [] - try: - sql = """select fm2.version, fs2."name" as status, fm2.created_at from "FOIMinistryRequests" fm2 inner join "FOIRequestStatuses" fs2 on fm2.requeststatusid = fs2.requeststatusid - where fm2.foiministryrequestid=:ministryrequestid order by version asc""" - rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) - _tmp_state = None - for row in rs: - if row["status"] != _tmp_state: - transitions.append({"status": row["status"], "version": row["version"], "created_at": row["created_at"]}) - _tmp_state = row["status"] - desc_transitions = transitions[::-1] - index = 0 - onhold_occurance = 0 - recent_offhold_index = None - offhold_indicator = False - for entry in desc_transitions: - if entry["status"] == StateName.onhold.value: - onhold_occurance = onhold_occurance + 1 - if onhold_occurance > 1: - recent_offhold_index = index - offhold_indicator = True - index = index + 1 - return None if offhold_indicator == False or recent_offhold_index == 0 else desc_transitions[recent_offhold_index-1]["created_at"] - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - - @classmethod - def getstatenavigation(cls, ministryrequestid): - requeststates = [] - try: - sql = """select fs2."name" as status, version from "FOIMinistryRequests" fm inner join "FOIRequestStatuses" fs2 on fm.requeststatusid = fs2.requeststatusid - where foiministryrequestid=:ministryrequestid order by version desc limit 2""" - rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) - for row in rs: - requeststates.append(row["status"]) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return requeststates - - @classmethod - def getallstatenavigation(cls, ministryrequestid): - requeststates = [] - try: - sql = """select fs2."name" as status, version from "FOIMinistryRequests" fm inner join "FOIRequestStatuses" fs2 on fm.requeststatusid = fs2.requeststatusid - where foiministryrequestid=:ministryrequestid order by version desc""" - rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) - for row in rs: - requeststates.append(row["status"]) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return requeststates - - @classmethod - def getrequestssubquery(cls, groups, filterfields, keyword, additionalfilter, userid, iaoassignee, ministryassignee, requestby='IAO', isiaorestrictedfilemanager=False, isministryrestrictedfilemanager=False): - #for queue/dashboard - _session = db.session - - #ministry filter for group/team - ministryfilter = FOIMinistryRequest.getgroupfilters(groups) - - #subquery for getting latest version & proper group/team for FOIMinistryRequest - subquery_ministry_maxversion = _session.query(FOIMinistryRequest.foiministryrequestid, func.max(FOIMinistryRequest.version).label('max_version')).group_by(FOIMinistryRequest.foiministryrequestid).subquery() - joincondition_ministry = [ - subquery_ministry_maxversion.c.foiministryrequestid == FOIMinistryRequest.foiministryrequestid, - subquery_ministry_maxversion.c.max_version == FOIMinistryRequest.version, - ] - - #subquery for getting extension count - subquery_extension_count = _session.query(FOIRequestExtension.foiministryrequest_id, func.count(distinct(FOIRequestExtension.foirequestextensionid)).filter(FOIRequestExtension.isactive == True).label('extensions')).group_by(FOIRequestExtension.foiministryrequest_id).subquery() - - #subquery for getting all, distinct oipcs for foiministry request - subquery_with_oipc_sql = """ - SELECT distinct on (foiministryrequest_id) foiministryrequest_id, foiministryrequestversion_id, outcomeid - FROM "FOIRequestOIPC" fo - order by foiministryrequest_id, foiministryrequestversion_id desc - """ - subquery_with_oipc = text(subquery_with_oipc_sql).columns(FOIRequestOIPC.foiministryrequest_id, FOIRequestOIPC.foiministryrequestversion_id, FOIRequestOIPC.outcomeid).alias("oipcnoneoutcomes") - joincondition_oipc = [ - subquery_with_oipc.c.foiministryrequest_id == FOIMinistryRequest.foiministryrequestid, - subquery_with_oipc.c.foiministryrequestversion_id == FOIMinistryRequest.version, - ] - - #aliase for onbehalf of applicant info - onbehalf_applicantmapping = aliased(FOIRequestApplicantMapping) - onbehalf_applicant = aliased(FOIRequestApplicant) - - #aliase for getting ministry restricted flag from FOIRestrictedMinistryRequest - ministry_restricted_requests = aliased(FOIRestrictedMinistryRequest) - - #filter/search - if(len(filterfields) > 0 and keyword is not None): - - filtercondition = [] - - if(keyword != "restricted"): - for field in filterfields: - filtercondition.append(FOIMinistryRequest.findfield(field, iaoassignee, ministryassignee).ilike('%'+keyword+'%')) - else: - if(requestby == 'IAO'): - filtercondition.append(FOIRestrictedMinistryRequest.isrestricted == True) - else: - filtercondition.append(ministry_restricted_requests.isrestricted == True) - if (keyword.lower() == "oipc"): - filtercondition.append(FOIMinistryRequest.isoipcreview == True) - - intakesorting = case([ - (and_(FOIMinistryRequest.assignedto == None, FOIMinistryRequest.assignedgroup == 'Intake Team'), # Unassigned requests first - literal(None)), - ], - else_ = FOIRequest.receiveddate).label('intakeSorting') - - defaultsorting = case([ - (FOIMinistryRequest.assignedto == None, # Unassigned requests first - literal(None)), - ], - else_ = FOIMinistryRequest.duedate).label('defaultSorting') - - ministrysorting = case([ - (FOIMinistryRequest.assignedministryperson == None, # Unassigned requests first - literal(None)), - ], - else_ = FOIMinistryRequest.duedate).label('ministrySorting') - - assignedtoformatted = case([ - (and_(iaoassignee.lastname.isnot(None), iaoassignee.firstname.isnot(None)), - func.concat(iaoassignee.lastname, ', ', iaoassignee.firstname)), - (and_(iaoassignee.lastname.isnot(None), iaoassignee.firstname.is_(None)), - iaoassignee.lastname), - (and_(iaoassignee.lastname.is_(None), iaoassignee.firstname.isnot(None)), - iaoassignee.firstname), - ], - else_ = FOIMinistryRequest.assignedgroup).label('assignedToFormatted') - - ministryassignedtoformatted = case([ - (and_(ministryassignee.lastname.isnot(None), ministryassignee.firstname.isnot(None)), - func.concat(ministryassignee.lastname, ', ', ministryassignee.firstname)), - (and_(ministryassignee.lastname.isnot(None), ministryassignee.firstname.is_(None)), - ministryassignee.lastname), - (and_(ministryassignee.lastname.is_(None), ministryassignee.firstname.isnot(None)), - ministryassignee.firstname), - ], - else_ = FOIMinistryRequest.assignedministrygroup).label('ministryAssignedToFormatted') - - duedate = case([ - (FOIMinistryRequest.requeststatuslabel == StateName.onhold.name, # On Hold - literal(None)), - ], - else_ = cast(FOIMinistryRequest.duedate, String)).label('duedate') - - cfrduedate = case([ - (FOIMinistryRequest.requeststatuslabel == StateName.onhold.name, # On Hold - literal(None)), - ], - else_ = cast(FOIMinistryRequest.cfrduedate, String)).label('cfrduedate') - - axispagecount = case ([ - (FOIMinistryRequest.axispagecount.isnot(None), FOIMinistryRequest.axispagecount) - ], - else_= literal("0").label("axispagecount") - ) - axislanpagecount = case ([ - (FOIMinistryRequest.axislanpagecount.isnot(None), FOIMinistryRequest.axislanpagecount) - ], - else_= literal("0").label("axislanpagecount") - ) - recordspagecount = case ([ - (FOIMinistryRequest.recordspagecount.isnot(None), FOIMinistryRequest.recordspagecount) - ], - else_= literal("0").label("recordspagecount") - ) - - requestpagecount = case([ - (and_(FOIMinistryRequest.axispagecount.isnot(None), FOIMinistryRequest.recordspagecount.isnot(None), cast(axispagecount, Integer) > cast(recordspagecount, Integer)), - FOIMinistryRequest.axispagecount), - (and_(FOIMinistryRequest.recordspagecount.isnot(None)), - FOIMinistryRequest.recordspagecount), - (and_(FOIMinistryRequest.axispagecount.isnot(None)), - FOIMinistryRequest.axispagecount), - ], - else_= literal("0")) - - onbehalfformatted = case([ - (and_(onbehalf_applicant.lastname.isnot(None), onbehalf_applicant.firstname.isnot(None)), - func.concat(onbehalf_applicant.lastname, ', ', onbehalf_applicant.firstname)), - (and_(onbehalf_applicant.lastname.isnot(None), onbehalf_applicant.firstname.is_(None)), - onbehalf_applicant.lastname), - (and_(onbehalf_applicant.lastname.is_(None), onbehalf_applicant.firstname.isnot(None)), - onbehalf_applicant.firstname), - ], - else_ = 'N/A').label('onBehalfFormatted') - - extensions = case([ - (subquery_extension_count.c.extensions.is_(None), - 0), - ], - else_ = subquery_extension_count.c.extensions).label('extensions') - - - selectedcolumns = [ - FOIRequest.foirequestid.label('id'), - FOIMinistryRequest.version, - literal(None).label('sourceofsubmission'), - FOIRequestApplicant.firstname.label('firstName'), - FOIRequestApplicant.lastname.label('lastName'), - FOIRequest.requesttype.label('requestType'), - cast(FOIRequest.receiveddate, String).label('receivedDate'), - cast(FOIRequest.receiveddate, String).label('receivedDateUF'), - FOIRequestStatus.name.label('currentState'), - FOIMinistryRequest.assignedgroup.label('assignedGroup'), - FOIMinistryRequest.assignedto.label('assignedTo'), - cast(FOIMinistryRequest.filenumber, String).label('idNumber'), - cast(FOIMinistryRequest.axisrequestid, String).label('axisRequestId'), - cast(requestpagecount, Integer).label('requestpagecount'), - axispagecount, - axislanpagecount, - recordspagecount, - FOIMinistryRequest.foiministryrequestid.label('ministryrequestid'), - FOIMinistryRequest.assignedministrygroup.label('assignedministrygroup'), - FOIMinistryRequest.assignedministryperson.label('assignedministryperson'), - cfrduedate, - duedate, - ApplicantCategory.name.label('applicantcategory'), - FOIRequest.created_at.label('created_at'), - func.lower(ProgramArea.bcgovcode).label('bcgovcode'), - iaoassignee.firstname.label('assignedToFirstName'), - iaoassignee.lastname.label('assignedToLastName'), - ministryassignee.firstname.label('assignedministrypersonFirstName'), - ministryassignee.lastname.label('assignedministrypersonLastName'), - FOIMinistryRequest.description, - cast(FOIMinistryRequest.recordsearchfromdate, String).label('recordsearchfromdate'), - cast(FOIMinistryRequest.recordsearchtodate, String).label('recordsearchtodate'), - onbehalf_applicant.firstname.label('onBehalfFirstName'), - onbehalf_applicant.lastname.label('onBehalfLastName'), - defaultsorting, - intakesorting, - ministrysorting, - assignedtoformatted, - ministryassignedtoformatted, - FOIMinistryRequest.closedate, - onbehalfformatted, - extensions, - FOIRestrictedMinistryRequest.isrestricted.label('isiaorestricted'), - ministry_restricted_requests.isrestricted.label('isministryrestricted'), - SubjectCode.name.label('subjectcode'), - FOIMinistryRequest.isoipcreview.label('isoipcreview'), - literal(None).label('oipc_number'), - ] - - basequery = _session.query( - *selectedcolumns - ).join( - subquery_ministry_maxversion, - and_(*joincondition_ministry) - ).join( - subquery_extension_count, - subquery_extension_count.c.foiministryrequest_id == FOIMinistryRequest.foiministryrequestid, - isouter=True - ).join( - FOIRequest, - and_(FOIRequest.foirequestid == FOIMinistryRequest.foirequest_id, FOIRequest.version == FOIMinistryRequest.foirequestversion_id) - ).join( - FOIRequestStatus, - FOIRequestStatus.requeststatusid == FOIMinistryRequest.requeststatusid - ).join( - FOIRequestApplicantMapping, - and_(FOIRequestApplicantMapping.foirequest_id == FOIMinistryRequest.foirequest_id, FOIRequestApplicantMapping.foirequestversion_id == FOIMinistryRequest.foirequestversion_id, FOIRequestApplicantMapping.requestortypeid == RequestorType['applicant'].value) - ).join( - FOIRequestApplicant, - FOIRequestApplicant.foirequestapplicantid == FOIRequestApplicantMapping.foirequestapplicantid - ).join( - onbehalf_applicantmapping, - and_( - onbehalf_applicantmapping.foirequest_id == FOIMinistryRequest.foirequest_id, - onbehalf_applicantmapping.foirequestversion_id == FOIMinistryRequest.foirequestversion_id, - onbehalf_applicantmapping.requestortypeid == RequestorType['onbehalfof'].value), - isouter=True - ).join( - onbehalf_applicant, - onbehalf_applicant.foirequestapplicantid == onbehalf_applicantmapping.foirequestapplicantid, - isouter=True - ).join( - ApplicantCategory, - and_(ApplicantCategory.applicantcategoryid == FOIRequest.applicantcategoryid, ApplicantCategory.isactive == True) - ).join( - ProgramArea, - FOIMinistryRequest.programareaid == ProgramArea.programareaid - ).join( - iaoassignee, - iaoassignee.username == FOIMinistryRequest.assignedto, - isouter=True - ).join( - ministryassignee, - ministryassignee.username == FOIMinistryRequest.assignedministryperson, - isouter=True - ).join( - FOIRestrictedMinistryRequest, - and_( - FOIRestrictedMinistryRequest.ministryrequestid == FOIMinistryRequest.foiministryrequestid, - FOIRestrictedMinistryRequest.type == 'iao', - FOIRestrictedMinistryRequest.isactive == True), - isouter=True - ).join( - ministry_restricted_requests, - and_( - ministry_restricted_requests.ministryrequestid == FOIMinistryRequest.foiministryrequestid, - ministry_restricted_requests.type == 'ministry', - ministry_restricted_requests.isactive == True), - isouter=True - ).join( - FOIMinistryRequestSubjectCode, - and_(FOIMinistryRequestSubjectCode.foiministryrequestid == FOIMinistryRequest.foiministryrequestid, FOIMinistryRequestSubjectCode.foiministryrequestversion == FOIMinistryRequest.version), - isouter=True - ).join( - SubjectCode, - SubjectCode.subjectcodeid == FOIMinistryRequestSubjectCode.subjectcodeid, - isouter=True - ).join( - subquery_with_oipc, - and_( - *joincondition_oipc - ), - isouter=True - ).filter(or_(FOIMinistryRequest.requeststatuslabel != StateName.closed.name, - and_(FOIMinistryRequest.isoipcreview == True, FOIMinistryRequest.requeststatusid == 3, subquery_with_oipc.c.outcomeid == None))) - - - - if(additionalfilter == 'watchingRequests'): - #watchby - activefilter = and_(FOIMinistryRequest.isactive == True, FOIRequestStatus.isactive == True) - - subquery_watchby = FOIRequestWatcher.getrequestidsbyuserid(userid) - if(requestby == 'IAO'): - dbquery = basequery.join(subquery_watchby, subquery_watchby.c.ministryrequestid == FOIMinistryRequest.foiministryrequestid).filter(activefilter).filter(or_(or_(FOIRestrictedMinistryRequest.isrestricted == False, FOIRestrictedMinistryRequest.isrestricted == None), and_(FOIRestrictedMinistryRequest.isrestricted == True, FOIMinistryRequest.assignedto == userid))) - else: - dbquery = basequery.join(subquery_watchby, subquery_watchby.c.ministryrequestid == FOIMinistryRequest.foiministryrequestid).filter(activefilter).filter(or_(or_(ministry_restricted_requests.isrestricted == isministryrestrictedfilemanager, ministry_restricted_requests.isrestricted == None), and_(ministry_restricted_requests.isrestricted == True, FOIMinistryRequest.assignedministryperson == userid))) - - elif(additionalfilter == 'myRequests'): - #myrequest - if(requestby == 'IAO'): - dbquery = basequery.filter(FOIMinistryRequest.assignedto == userid).filter(ministryfilter) - else: - dbquery = basequery.filter(FOIMinistryRequest.assignedministryperson == userid).filter(ministryfilter) - elif(additionalfilter == 'unassignedRequests'): - if(requestby == 'IAO'): - dbquery = basequery.filter(FOIMinistryRequest.assignedto == None).filter(ministryfilter) - elif(additionalfilter.lower() == 'all'): - if(requestby == 'IAO'): - dbquery = basequery.filter(ministryfilter).filter(FOIMinistryRequest.assignedto.isnot(None)).filter(or_(FOIRestrictedMinistryRequest.isrestricted == isiaorestrictedfilemanager, or_(FOIRestrictedMinistryRequest.isrestricted.is_(None), FOIRestrictedMinistryRequest.isrestricted == False))) - else: - dbquery = basequery.filter(ministryfilter).filter(or_(ministry_restricted_requests.isrestricted == isministryrestrictedfilemanager, or_(ministry_restricted_requests.isrestricted.is_(None), ministry_restricted_requests.isrestricted == False))) - else: - if(isiaorestrictedfilemanager == True or isministryrestrictedfilemanager == True): - dbquery = basequery.filter(ministryfilter) - else: - if(requestby == 'IAO'): - dbquery = basequery.filter(or_(or_(FOIRestrictedMinistryRequest.isrestricted == False, FOIRestrictedMinistryRequest.isrestricted == None), and_(FOIRestrictedMinistryRequest.isrestricted == True, FOIMinistryRequest.assignedto == userid))).filter(ministryfilter) - else: - dbquery = basequery.filter(or_(or_(ministry_restricted_requests.isrestricted == False, ministry_restricted_requests.isrestricted == None), and_(ministry_restricted_requests.isrestricted == True, FOIMinistryRequest.assignedministryperson == userid))).filter(ministryfilter) - - - if(keyword is None): - return dbquery - else: - return dbquery.filter(or_(*filtercondition)) - - @classmethod - def getrequestspagination(cls, group, page, size, sortingitems, sortingorders, filterfields, keyword, additionalfilter, userid, isiaorestrictedfilemanager, isministryrestrictedfilemanager): - iaoassignee = aliased(FOIAssignee) - ministryassignee = aliased(FOIAssignee) - requestby = 'Ministry' - - subquery = FOIMinistryRequest.getrequestssubquery(group, filterfields, keyword, additionalfilter, userid, iaoassignee, ministryassignee, requestby, isiaorestrictedfilemanager, isministryrestrictedfilemanager) - - #sorting - sortingcondition = FOIMinistryRequest.getsorting(sortingitems, sortingorders, iaoassignee, ministryassignee) - - return subquery.order_by(*sortingcondition).paginate(page=page, per_page=size) - - @classmethod - def getsorting(cls, sortingitems, sortingorders, iaoassignee, ministryassignee): - #sorting - sortingcondition = [] - if(len(sortingitems) > 0 and len(sortingorders) > 0 and len(sortingitems) == len(sortingorders)): - for field in sortingitems: - order = sortingorders.pop(0) - sortingcondition.append(FOIMinistryRequest.getfieldforsorting(field, order, iaoassignee, ministryassignee)) - - #default sorting - if(len(sortingcondition) == 0): - sortingcondition.append(FOIMinistryRequest.findfield('currentState', iaoassignee, ministryassignee).asc()) - - #always sort by created_at last to prevent pagination collisions - sortingcondition.append(asc('created_at')) - - return sortingcondition - - @classmethod - def getfieldforsorting(cls, field, order, iaoassignee, ministryassignee): - #get one field - customizedfields = ['assignedToFormatted', 'ministryAssignedToFormatted', 'duedate', 'cfrduedate', 'ministrySorting', 'onBehalfFormatted', 'extensions', 'isministryrestricted'] - if(field in customizedfields): - if(order == 'desc'): - return nullslast(desc(field)) - else: - return nullsfirst(asc(field)) - else: - if(order == 'desc'): - return nullslast(FOIMinistryRequest.findfield(field, iaoassignee, ministryassignee).desc()) - else: - return nullsfirst(FOIMinistryRequest.findfield(field, iaoassignee, ministryassignee).asc()) - - @classmethod - def findfield(cls, x, iaoassignee, ministryassignee): - #add more fields here if need sort/filter/search more columns - axispagecount = case ([ - (FOIMinistryRequest.axispagecount.isnot(None), FOIMinistryRequest.axispagecount) - ], - else_= literal("0").label("axispagecount") - ) - recordspagecount = case ([ - (FOIMinistryRequest.recordspagecount.isnot(None), FOIMinistryRequest.recordspagecount) - ], - else_= literal("0").label("recordspagecount") - ) - requestpagecount = case([ - (and_(FOIMinistryRequest.axispagecount.isnot(None), FOIMinistryRequest.recordspagecount.isnot(None), cast(axispagecount, Integer) > cast(recordspagecount, Integer)), - FOIMinistryRequest.axispagecount), - (and_(FOIMinistryRequest.recordspagecount.isnot(None)), - FOIMinistryRequest.recordspagecount), - (and_(FOIMinistryRequest.axispagecount.isnot(None)), - FOIMinistryRequest.axispagecount), - ], - else_= literal("0")).label('requestpagecount') - - return { - 'firstName': FOIRequestApplicant.firstname, - 'lastName': FOIRequestApplicant.lastname, - 'requestType': FOIRequest.requesttype, - 'idNumber': FOIMinistryRequest.filenumber, - 'axisRequestId': FOIMinistryRequest.axisrequestid, - 'axisrequest_number': FOIMinistryRequest.axisrequestid, - 'rawRequestNumber': FOIMinistryRequest.filenumber, - 'currentState': FOIRequestStatus.name, - 'assignedTo': FOIMinistryRequest.assignedto, - 'receivedDate': FOIRequest.receiveddate, - 'receivedDateUF': FOIRequest.receiveddate, - 'applicantcategory': ApplicantCategory.name, - 'assignedministryperson': FOIMinistryRequest.assignedministryperson, - 'assignedToFirstName': iaoassignee.firstname, - 'assignedToLastName': iaoassignee.lastname, - 'assignedministrypersonFirstName': ministryassignee.firstname, - 'assignedministrypersonLastName': ministryassignee.lastname, - 'description': FOIMinistryRequest.description, - 'requestdescription': FOIMinistryRequest.description, - 'duedate': FOIMinistryRequest.duedate, - 'cfrduedate': FOIMinistryRequest.cfrduedate, - 'DueDateValue': FOIMinistryRequest.duedate, - 'DaysLeftValue': FOIMinistryRequest.duedate, - 'ministry': func.upper(ProgramArea.bcgovcode), - 'requestpagecount': requestpagecount, - 'axispagecount': axispagecount, - 'recordspagecount': recordspagecount, - 'closedate': FOIMinistryRequest.closedate, - 'subjectcode': SubjectCode.name, - 'isoipcreview': FOIMinistryRequest.isoipcreview - }.get(x, FOIMinistryRequest.axisrequestid) - - @classmethod - def getgroupfilters(cls, groups): - #ministry filter for group/team - if groups is None: - ministryfilter = FOIMinistryRequest.isactive == True - else: - groupfilter = [] - statusfilter = None - processinggroups = list(set(groups).intersection(ProcessingTeamWithKeycloackGroup.list())) - if IAOTeamWithKeycloackGroup.intake.value in groups or len(processinggroups) > 0: - groupfilter.append( - and_( - FOIMinistryRequest.assignedgroup.in_(tuple(groups)) - ) - ) - statusfilter = FOIMinistryRequest.requeststatuslabel != StateName.closed.name - else: - groupfilter.append( - or_( - FOIMinistryRequest.assignedgroup.in_(tuple(groups)), - and_( - FOIMinistryRequest.assignedministrygroup.in_(tuple(groups)) - ) - ) - ) - statusfilter = FOIMinistryRequest.requeststatuslabel.in_([StateName.callforrecords.name,StateName.recordsreview.name,StateName.feeestimate.name,StateName.consult.name,StateName.ministrysignoff.name,StateName.onhold.name,StateName.deduplication.name,StateName.harmsassessment.name,StateName.response.name,StateName.peerreview.name,StateName.tagging.name,StateName.readytoscan.name, StateName.recordsreadyforreview.name]) - ministryfilter = and_( - FOIMinistryRequest.isactive == True, - FOIRequestStatus.isactive == True, - or_(*groupfilter) - ) - ministryfilterwithclosedoipc = and_(ministryfilter, - or_(statusfilter, - and_(FOIMinistryRequest.isoipcreview == True, FOIMinistryRequest.requeststatuslabel == StateName.closed.name) - ) - ) - return ministryfilterwithclosedoipc - - @classmethod - def getrequestoriginalduedate(cls,ministryrequestid): - return db.session.query(FOIMinistryRequest.duedate).filter(FOIMinistryRequest.foiministryrequestid == ministryrequestid, FOIMinistryRequest.requeststatuslabel == StateName.open.name).order_by(FOIMinistryRequest.version).first()[0] - - @classmethod - def getduedate(cls,ministryrequestid): - return db.session.query(FOIMinistryRequest.duedate).filter(FOIMinistryRequest.foiministryrequestid == ministryrequestid).order_by(FOIMinistryRequest.version.desc()).first()[0] - - - @classmethod - def getupcomingcfrduerecords(cls): - upcomingduerecords = [] - try: - sql = """select distinct on (filenumber) filenumber, to_char(cfrduedate, 'YYYY-MM-DD') as cfrduedate, foiministryrequestid, version, foirequest_id, created_at, createdby from "FOIMinistryRequests" fpa - where isactive = true and cfrduedate is not null and requeststatuslabel = :requeststatuslabel - and cfrduedate between NOW() - INTERVAL '7 DAY' AND NOW() + INTERVAL '7 DAY' - order by filenumber , version desc;""" - rs = db.session.execute(text(sql), {'requeststatuslabel': StateName.callforrecords.name}) - for row in rs: - upcomingduerecords.append({"filenumber": row["filenumber"], "cfrduedate": row["cfrduedate"],"foiministryrequestid": row["foiministryrequestid"], "version": row["version"], "foirequest_id": row["foirequest_id"], "created_at": row["created_at"], "createdby": row["createdby"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return upcomingduerecords - - @classmethod - def getupcominglegislativeduerecords(cls): - upcomingduerecords = [] - try: - sql = """select distinct on (filenumber) filenumber, to_char(duedate, 'YYYY-MM-DD') as duedate, foiministryrequestid, version, foirequest_id, created_at, createdby from "FOIMinistryRequests" fpa - where isactive = true and duedate is not null and requeststatuslabel not in :requeststatuslabel - and duedate between NOW() - INTERVAL '7 DAY' AND NOW() + INTERVAL '7 DAY' - order by filenumber , version desc;""" - requeststatuslabel = tuple([StateName.closed.name,StateName.redirect.name,StateName.unopened.name,StateName.intakeinprogress.name,StateName.onhold.name,StateName.archived.name]) - rs = db.session.execute(text(sql), {'requeststatuslabel': requeststatuslabel}) - for row in rs: - upcomingduerecords.append({"filenumber": row["filenumber"], "duedate": row["duedate"],"foiministryrequestid": row["foiministryrequestid"], "version": row["version"], "foirequest_id": row["foirequest_id"], "created_at": row["created_at"], "createdby": row["createdby"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return upcomingduerecords - - @classmethod - def getupcomingdivisionduerecords(cls): - upcomingduerecords = [] - try: - sql = """select axisrequestid, filenumber, fma.foiministryrequestid , fma.foiministryrequestversion, fma.foirequest_id, - frd.divisionid, frd.stageid, pad2."name" divisionname, pads."name" stagename, - to_char(divisionduedate, 'YYYY-MM-DD') as duedate, frd.created_at, frd.createdby - from "FOIMinistryRequestDivisions" frd - inner join (select distinct on (fpa.foiministryrequestid) foiministryrequestid, version as foiministryrequestversion, axisrequestid, filenumber, foirequest_id, requeststatusid, requeststatuslabel - from "FOIMinistryRequests" fpa - order by fpa.foiministryrequestid , fpa.version desc) fma on frd.foiministryrequest_id = fma.foiministryrequestid and frd.foiministryrequestversion_id = fma.foiministryrequestversion and fma.requeststatuslabel not in :requeststatuslabel - inner join "ProgramAreaDivisions" pad2 on frd.divisionid = pad2.divisionid - inner join "ProgramAreaDivisionStages" pads on frd.stageid = pads.stageid and frd.stageid in (5, 7, 9) - and frd.divisionduedate between NOW() - INTERVAL '7 DAY' AND NOW() + INTERVAL '7 DAY' - order by frd.foiministryrequest_id , frd.foiministryrequestversion_id desc;""" - requeststatuslabel = tuple([StateName.closed.name,StateName.redirect.name,StateName.unopened.name,StateName.intakeinprogress.name,StateName.onhold.name,StateName.archived.name]) - rs = db.session.execute(text(sql), {'requeststatuslabel': requeststatuslabel}) - - for row in rs: - upcomingduerecords.append({"axisrequestid": row["axisrequestid"], "filenumber": row["filenumber"], - "foiministryrequestid": row["foiministryrequestid"], "version": row["foiministryrequestversion"], - "foirequest_id": row["foirequest_id"], "created_at": row["created_at"], "createdby": row["createdby"], - "divisionid": row["divisionid"],"divisionname": row["divisionname"], - "stageid": row["stageid"], "stagename": row["stagename"], - "duedate": row["duedate"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return upcomingduerecords - - @classmethod - def getupcomingoipcduerecords(cls): - upcomingduerecords = [] - try: - sql = """select axisrequestid, filenumber, fma.foiministryrequestid , fma.foiministryrequestversion, fma.foirequest_id, - frd.oipcid , frd.inquiryattributes ->> 'orderno'as orderno, - frd.inquiryattributes ->> 'inquirydate' as duedate, frd.created_at, frd.createdby - from "FOIRequestOIPC" frd - inner join (select distinct on (fpa.foiministryrequestid) foiministryrequestid, version as foiministryrequestversion, axisrequestid, filenumber, foirequest_id, requeststatusid - from "FOIMinistryRequests" fpa - order by fpa.foiministryrequestid , fpa.version desc) fma on frd.foiministryrequest_id = fma.foiministryrequestid - and frd.foiministryrequestversion_id = fma.foiministryrequestversion and fma.requeststatusid not in (5,6,4,11,3,15) - and inquiryattributes is not null - and frd.inquiryattributes ->> 'inquirydate' not in ('','null') - and (frd.inquiryattributes ->> 'inquirydate')::date between NOW() - INTERVAL '7 DAY' AND NOW() + INTERVAL '7 DAY' - and frd.outcomeid is null - order by frd.foiministryrequest_id , frd.foiministryrequestversion_id desc;""" - rs = db.session.execute(text(sql)) - for row in rs: - upcomingduerecords.append({"axisrequestid": row["axisrequestid"], "filenumber": row["filenumber"], - "foiministryrequestid": row["foiministryrequestid"], "version": row["foiministryrequestversion"], - "foirequest_id": row["foirequest_id"], "created_at": row["created_at"], "createdby": row["createdby"], - "orderno": row["orderno"],"duedate": row["duedate"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return upcomingduerecords - - @classmethod - def updateduedate(cls, ministryrequestid, duedate, userid)->DefaultMethodResult: - currequest = db.session.query(FOIMinistryRequest).filter_by(foiministryrequestid=ministryrequestid).order_by(FOIMinistryRequest.version.desc()).first() - setattr(currequest,'duedate',duedate) - setattr(currequest,'updated_at',datetime.now().isoformat()) - setattr(currequest,'updatedby',userid) - db.session.commit() - return DefaultMethodResult(True,'Request updated',ministryrequestid) - - @classmethod - def getministriesopenedbyuid(cls, rawrequestid): - ministries = [] - try: - """ - sql = select distinct filenumber, axisrequestid, foiministryrequestid, foirequest_id, pa."name" from "FOIMinistryRequests" fpa - inner join "FOIRequests" frt on fpa.foirequest_id = frt.foirequestid and fpa.foirequestversion_id = frt."version" - inner join "ProgramAreas" pa on fpa.programareaid = pa.programareaid - where fpa.isactive = true and frt.isactive =true and frt.foirawrequestid=:rawrequestid; - """ - sql = """select distinct filenumber, axisrequestid, foiministryrequestid, foirequest_id, pa."name", - assignedministrygroup, assignedministryperson, assignedgroup, assignedto, fs2."name" as status - from "FOIMinistryRequests" fpa - inner join "FOIRequests" frt on fpa.foirequest_id = frt.foirequestid and frt."version" = fpa.foirequestversion_id and frt."version" = 1 - inner join "ProgramAreas" pa on fpa.programareaid = pa.programareaid - inner join "FOIRequestStatuses" fs2 on fpa.requeststatusid = fs2.requeststatusid - where frt.foirawrequestid=:rawrequestid; """ - rs = db.session.execute(text(sql), {'rawrequestid': rawrequestid}) - for row in rs: - ministries.append({"filenumber": row["filenumber"], "axisrequestid": row["axisrequestid"], "name": row["name"], "requestid": row["foirequest_id"],"ministryrequestid": row["foiministryrequestid"], - "assignedministrygroup": row["assignedministrygroup"], "assignedministryperson": row["assignedministryperson"], "assignedgroup": row["assignedgroup"], "assignedto": row["assignedto"], - "id": row["foiministryrequestid"], "foirequestid": row["foirequest_id"], "status": row["status"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return ministries - - - @classmethod - def getactivitybyid(cls, ministryrequestid): - ministries = [] - try: - sql = """select fm2.filenumber, fm2.axisrequestid, fm2.foiministryrequestid, fm2.foirequest_id, fm2.version, fs2."name" as status, - fm2.assignedministrygroup, fm2.assignedministryperson, fm2.assignedgroup, fm2.assignedto - from "FOIMinistryRequests" fm2 inner join "FOIRequestStatuses" fs2 on fm2.requeststatusid = fs2.requeststatusid - where fm2.foiministryrequestid=:ministryrequestid order by version desc""" - rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) - _tmp_state = None - for row in rs: - if row["status"] != _tmp_state: - ministries.append({"id": row["foiministryrequestid"], "foirequestid": row["foirequest_id"], "axisrequestid": row["axisrequestid"], "filenumber": row["filenumber"], "status": row["status"], - "assignedministrygroup": row["assignedministrygroup"], "assignedministryperson": row["assignedministryperson"], - "assignedgroup": row["assignedgroup"], "assignedto": row["assignedto"], "version": row["version"] - }) - _tmp_state = row["status"] - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return ministries - - @classmethod - def getclosedaxisids(cls): - axisids = [] - try: - sql = """ select distinct on (foiministryrequestid) foiministryrequestid, version, axisrequestid - from "FOIMinistryRequests" fr - where requeststatuslabel = :requeststatuslabel - order by foiministryrequestid , version desc, axisrequestid""" - rs = db.session.execute(text(sql), {'requeststatuslabel': StateName.closed.name}) - for row in rs: - axisids.append(row["axisrequestid"]) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return axisids - - @classmethod - def getbasequery(cls, iaoassignee, ministryassignee, userid=None, requestby='IAO', isiaorestrictedfilemanager=False, isministryrestrictedfilemanager=False): - #for advanced search - - _session = db.session - - #ministry filter for group/team - ministryfilter = and_(FOIMinistryRequest.isactive == True, FOIRequestStatus.isactive == True) - - #subquery for getting latest version & proper group/team for FOIMinistryRequest - subquery_ministry_maxversion = _session.query(FOIMinistryRequest.foiministryrequestid, func.max(FOIMinistryRequest.version).label('max_version')).group_by(FOIMinistryRequest.foiministryrequestid).subquery() - joincondition_ministry = [ - subquery_ministry_maxversion.c.foiministryrequestid == FOIMinistryRequest.foiministryrequestid, - subquery_ministry_maxversion.c.max_version == FOIMinistryRequest.version, - ] - - #subquery for getting extension count - subquery_extension_count = _session.query(FOIRequestExtension.foiministryrequest_id , func.count(distinct(FOIRequestExtension.foirequestextensionid)).filter(FOIRequestExtension.isactive == True).label('extensions')).group_by(FOIRequestExtension.foiministryrequest_id).subquery() - - #aliase for onbehalf of applicant info - onbehalf_applicantmapping = aliased(FOIRequestApplicantMapping) - onbehalf_applicant = aliased(FOIRequestApplicant) - - #aliase for getting ministry restricted flag from FOIRestrictedMinistryRequest - ministry_restricted_requests = aliased(FOIRestrictedMinistryRequest) - - intakesorting = case([ - (FOIMinistryRequest.assignedto == None, # Unassigned requests first - literal(None)), - ], - else_ = FOIRequest.receiveddate).label('intakeSorting') - - defaultsorting = case([ - (FOIMinistryRequest.assignedto == None, # Unassigned requests first - literal(None)), - ], - else_ = FOIMinistryRequest.duedate).label('defaultSorting') - - ministrysorting = case([ - (FOIMinistryRequest.assignedministryperson == None, # Unassigned requests first - literal(None)), - ], - else_ = FOIMinistryRequest.duedate).label('ministrySorting') - - assignedtoformatted = case([ - (and_(iaoassignee.lastname.isnot(None), iaoassignee.firstname.isnot(None)), - func.concat(iaoassignee.lastname, ', ', iaoassignee.firstname)), - (and_(iaoassignee.lastname.isnot(None), iaoassignee.firstname.is_(None)), - iaoassignee.lastname), - (and_(iaoassignee.lastname.is_(None), iaoassignee.firstname.isnot(None)), - iaoassignee.firstname), - ], - else_ = FOIMinistryRequest.assignedgroup).label('assignedToFormatted') - - ministryassignedtoformatted = case([ - (and_(ministryassignee.lastname.isnot(None), ministryassignee.firstname.isnot(None)), - func.concat(ministryassignee.lastname, ', ', ministryassignee.firstname)), - (and_(ministryassignee.lastname.isnot(None), ministryassignee.firstname.is_(None)), - ministryassignee.lastname), - (and_(ministryassignee.lastname.is_(None), ministryassignee.firstname.isnot(None)), - ministryassignee.firstname), - ], - else_ = FOIMinistryRequest.assignedministrygroup).label('ministryAssignedToFormatted') - - duedate = case([ - (FOIMinistryRequest.requeststatuslabel == StateName.onhold.name, # On Hold - literal(None)), - ], - else_ = cast(FOIMinistryRequest.duedate, String)).label('duedate') - - cfrduedate = case([ - (FOIMinistryRequest.requeststatuslabel == StateName.onhold.name, # On Hold - literal(None)), - ], - else_ = cast(FOIMinistryRequest.cfrduedate, String)).label('cfrduedate') - - axispagecount = case ([ - (FOIMinistryRequest.axispagecount.isnot(None), FOIMinistryRequest.axispagecount) - ], - else_= literal("0").label("axispagecount") - ) - axislanpagecount = case ([ - (FOIMinistryRequest.axislanpagecount.isnot(None), FOIMinistryRequest.axislanpagecount) - ], - else_= literal("0").label("axislanpagecount") - ) - recordspagecount = case ([ - (FOIMinistryRequest.recordspagecount.isnot(None), FOIMinistryRequest.recordspagecount) - ], - else_= literal("0").label("recordspagecount") - ) - requestpagecount = case([ - (and_(FOIMinistryRequest.axispagecount.isnot(None), FOIMinistryRequest.recordspagecount.isnot(None), cast(axispagecount, Integer) > cast(recordspagecount, Integer)), - FOIMinistryRequest.axispagecount), - (and_(FOIMinistryRequest.recordspagecount.isnot(None)), - FOIMinistryRequest.recordspagecount), - (and_(FOIMinistryRequest.axispagecount.isnot(None)), - FOIMinistryRequest.axispagecount), - ], - else_= literal("0")) - - onbehalfformatted = case([ - (and_(onbehalf_applicant.lastname.isnot(None), onbehalf_applicant.firstname.isnot(None)), - func.concat(onbehalf_applicant.lastname, ', ', onbehalf_applicant.firstname)), - (and_(onbehalf_applicant.lastname.isnot(None), onbehalf_applicant.firstname.is_(None)), - onbehalf_applicant.lastname), - (and_(onbehalf_applicant.lastname.is_(None), onbehalf_applicant.firstname.isnot(None)), - onbehalf_applicant.firstname), - ], - else_ = 'N/A').label('onBehalfFormatted') - - extensions = case([ - (subquery_extension_count.c.extensions.is_(None), - 0), - ], - else_ = subquery_extension_count.c.extensions).label('extensions') - - selectedcolumns = [ - FOIRequest.foirequestid.label('id'), - FOIMinistryRequest.version, - literal(None).label('sourceofsubmission'), - FOIRequestApplicant.firstname.label('firstName'), - FOIRequestApplicant.lastname.label('lastName'), - FOIRequest.requesttype.label('requestType'), - cast(FOIRequest.receiveddate, String).label('receivedDate'), - cast(FOIRequest.receiveddate, String).label('receivedDateUF'), - FOIRequestStatus.name.label('currentState'), - FOIMinistryRequest.assignedgroup.label('assignedGroup'), - FOIMinistryRequest.assignedto.label('assignedTo'), - cast(FOIMinistryRequest.filenumber, String).label('idNumber'), - cast(FOIMinistryRequest.axisrequestid, String).label('axisRequestId'), - cast(requestpagecount, Integer).label('requestpagecount'), - axispagecount, - axislanpagecount, - recordspagecount, - FOIMinistryRequest.foiministryrequestid.label('ministryrequestid'), - FOIMinistryRequest.assignedministrygroup.label('assignedministrygroup'), - FOIMinistryRequest.assignedministryperson.label('assignedministryperson'), - cfrduedate, - duedate, - ApplicantCategory.name.label('applicantcategory'), - FOIRequest.created_at.label('created_at'), - func.lower(ProgramArea.bcgovcode).label('bcgovcode'), - iaoassignee.firstname.label('assignedToFirstName'), - iaoassignee.lastname.label('assignedToLastName'), - ministryassignee.firstname.label('assignedministrypersonFirstName'), - ministryassignee.lastname.label('assignedministrypersonLastName'), - FOIMinistryRequest.description, - cast(FOIMinistryRequest.recordsearchfromdate, String).label('recordsearchfromdate'), - cast(FOIMinistryRequest.recordsearchtodate, String).label('recordsearchtodate'), - onbehalf_applicant.firstname.label('onBehalfFirstName'), - onbehalf_applicant.lastname.label('onBehalfLastName'), - defaultsorting, - intakesorting, - ministrysorting, - assignedtoformatted, - ministryassignedtoformatted, - FOIMinistryRequest.closedate, - onbehalfformatted, - extensions, - FOIRestrictedMinistryRequest.isrestricted.label('isiaorestricted'), - ministry_restricted_requests.isrestricted.label('isministryrestricted'), - SubjectCode.name.label('subjectcode'), - FOIMinistryRequest.isoipcreview.label('isoipcreview'), - literal(None).label('oipc_number') - ] - - basequery = _session.query( - *selectedcolumns - ).join( - subquery_ministry_maxversion, - and_(*joincondition_ministry) - ).join( - subquery_extension_count, - subquery_extension_count.c.foiministryrequest_id == FOIMinistryRequest.foiministryrequestid, - isouter=True - ).join( - FOIRequest, - and_(FOIRequest.foirequestid == FOIMinistryRequest.foirequest_id, FOIRequest.version == FOIMinistryRequest.foirequestversion_id) - ).join( - FOIRequestStatus, - FOIRequestStatus.requeststatusid == FOIMinistryRequest.requeststatusid - ).join( - FOIRequestApplicantMapping, - and_(FOIRequestApplicantMapping.foirequest_id == FOIMinistryRequest.foirequest_id, FOIRequestApplicantMapping.foirequestversion_id == FOIMinistryRequest.foirequestversion_id, FOIRequestApplicantMapping.requestortypeid == RequestorType['applicant'].value) - ).join( - FOIRequestApplicant, - FOIRequestApplicant.foirequestapplicantid == FOIRequestApplicantMapping.foirequestapplicantid - ).join( - onbehalf_applicantmapping, - and_( - onbehalf_applicantmapping.foirequest_id == FOIMinistryRequest.foirequest_id, - onbehalf_applicantmapping.foirequestversion_id == FOIMinistryRequest.foirequestversion_id, - onbehalf_applicantmapping.requestortypeid == RequestorType['onbehalfof'].value), - isouter=True - ).join( - onbehalf_applicant, - onbehalf_applicant.foirequestapplicantid == onbehalf_applicantmapping.foirequestapplicantid, - isouter=True - ).join( - ApplicantCategory, - and_(ApplicantCategory.applicantcategoryid == FOIRequest.applicantcategoryid, ApplicantCategory.isactive == True) - ).join( - ProgramArea, - FOIMinistryRequest.programareaid == ProgramArea.programareaid - ).join( - iaoassignee, - iaoassignee.username == FOIMinistryRequest.assignedto, - isouter=True - ).join( - ministryassignee, - ministryassignee.username == FOIMinistryRequest.assignedministryperson, - isouter=True - ).join( - FOIRestrictedMinistryRequest, - and_( - FOIRestrictedMinistryRequest.ministryrequestid == FOIMinistryRequest.foiministryrequestid, - FOIRestrictedMinistryRequest.type == 'iao', - FOIRestrictedMinistryRequest.isactive == True), - isouter=True - ).join( - ministry_restricted_requests, - and_( - ministry_restricted_requests.ministryrequestid == FOIMinistryRequest.foiministryrequestid, - ministry_restricted_requests.type == 'ministry', - ministry_restricted_requests.isactive == True), - isouter=True - ).join( - FOIMinistryRequestSubjectCode, - and_(FOIMinistryRequestSubjectCode.foiministryrequestid == FOIMinistryRequest.foiministryrequestid, FOIMinistryRequestSubjectCode.foiministryrequestversion == FOIMinistryRequest.version), - isouter=True - ).join( - SubjectCode, - SubjectCode.subjectcodeid == FOIMinistryRequestSubjectCode.subjectcodeid, - isouter=True - ) - - - if(isiaorestrictedfilemanager == True or isministryrestrictedfilemanager == True): - dbquery = basequery.filter(ministryfilter) - else: - #watchby - activefilter = and_(FOIMinistryRequest.isactive == True, FOIRequestStatus.isactive == True) - - subquery_watchby = FOIRequestWatcher.getrequestidsbyuserid(userid) - newbasequery = basequery.join( - subquery_watchby, - subquery_watchby.c.ministryrequestid == FOIMinistryRequest.foiministryrequestid, - isouter=True).filter(activefilter) - - if(requestby == 'IAO'): - dbquery = newbasequery.filter( - or_( - or_(FOIRestrictedMinistryRequest.isrestricted == False, FOIRestrictedMinistryRequest.isrestricted.is_(None)), - and_(FOIRestrictedMinistryRequest.isrestricted == True, FOIMinistryRequest.assignedto == userid), - and_(FOIRestrictedMinistryRequest.isrestricted == True, subquery_watchby.c.watchedby == userid), - ) - ).filter(ministryfilter) - else: - dbquery = newbasequery.filter( - or_( - or_(ministry_restricted_requests.isrestricted == False, ministry_restricted_requests.isrestricted.is_(None)), - and_(ministry_restricted_requests.isrestricted == True, FOIMinistryRequest.assignedministryperson == userid), - and_(ministry_restricted_requests.isrestricted == True, subquery_watchby.c.watchedby == userid), - ) - ).filter(ministryfilter) - - return dbquery - - @classmethod - def advancedsearch(cls, params, userid, isministryrestrictedfilemanager = False): - #ministry requests - iaoassignee = aliased(FOIAssignee) - ministryassignee = aliased(FOIAssignee) - - groupfilter = [] - for group in params['groups']: - groupfilter.append(FOIMinistryRequest.assignedministrygroup == group) - - #ministry advanced search show cfr onwards - statefilter = FOIMinistryRequest.requeststatuslabel.in_([StateName.callforrecords.name,StateName.closed.name,StateName.recordsreview.name,StateName.feeestimate.name,StateName.consult.name,StateName.ministrysignoff.name,StateName.onhold.name,StateName.deduplication.name,StateName.harmsassessment.name,StateName.response.name,StateName.peerreview.name,StateName.tagging.name,StateName.readytoscan.name,StateName.recordsreadyforreview.name]) - - ministry_queue = FOIMinistryRequest.advancedsearchsubquery(params, iaoassignee, ministryassignee, userid, 'Ministry', False, isministryrestrictedfilemanager).filter(and_(or_(*groupfilter), statefilter)) - - #sorting - sortingcondition = FOIMinistryRequest.getsorting(params['sortingitems'], params['sortingorders'], iaoassignee, ministryassignee) - - return ministry_queue.order_by(*sortingcondition).paginate(page=params['page'], per_page=params['size']) - - @classmethod - def advancedsearchsubquery(cls, params, iaoassignee, ministryassignee, userid, requestby, isiaorestrictedfilemanager, isministryrestrictedfilemanager=False): - basequery = FOIMinistryRequest.getbasequery(iaoassignee, ministryassignee, userid, requestby, isiaorestrictedfilemanager, isministryrestrictedfilemanager) - - #filter/search - filtercondition = FOIMinistryRequest.getfilterforadvancedsearch(params, iaoassignee, ministryassignee) - return basequery.filter(and_(*filtercondition)) - - @classmethod - def getfilterforadvancedsearch(cls, params, iaoassignee, ministryassignee): - - #filter/search - filtercondition = [] - includeclosed = False - - #request state: unopened, call for records, etc. - if(len(params['requeststate']) > 0): - requeststatecondition = FOIMinistryRequest.getfilterforrequeststate(params, includeclosed) - filtercondition.append(requeststatecondition['condition']) - includeclosed = requeststatecondition['includeclosed'] - - #request status: overdue || on time - if(len(params['requeststatus']) == 1): - requeststatuscondition = FOIMinistryRequest.getfilterforrequeststatus(params, iaoassignee, ministryassignee) - filtercondition.append(requeststatuscondition) - - # return all except closed - if(includeclosed == False): - filtercondition.append(FOIMinistryRequest.requeststatuslabel != StateName.closed.name) - elif(len(params['requeststatus']) > 1 and includeclosed == False): - # return all except closed - filtercondition.append(FOIMinistryRequest.requeststatuslabel != StateName.closed.name) - - #request type: personal, general - if(len(params['requesttype']) > 0): - requesttypecondition = FOIMinistryRequest.getfilterforrequesttype(params, iaoassignee, ministryassignee) - filtercondition.append(requesttypecondition) - - #request flags: restricted, oipc, phased - if(len(params['requestflags']) > 0): - requestflagscondition = FOIMinistryRequest.getfilterforrequestflags(params, iaoassignee, ministryassignee) - filtercondition.append(requestflagscondition) - - #public body: EDUC, etc. - if(len(params['publicbody']) > 0): - publicbodycondition = FOIMinistryRequest.getfilterforpublicbody(params, iaoassignee, ministryassignee) - filtercondition.append(publicbodycondition) - - #axis request #, raw request #, applicant name, assignee name, request description, subject code - if(len(params['keywords']) > 0 and params['search'] is not None): - searchcondition = FOIMinistryRequest.getfilterforsearch(params, iaoassignee, ministryassignee) - filtercondition.append(searchcondition) - - if(params['fromdate'] is not None and params['daterangetype'] is not None): - filtercondition.append(FOIMinistryRequest.findfield(params['daterangetype'], iaoassignee, ministryassignee).cast(Date) >= parser.parse(params['fromdate'])) - - if(params['todate'] is not None and params['daterangetype'] is not None): - filtercondition.append(FOIMinistryRequest.findfield(params['daterangetype'], iaoassignee, ministryassignee).cast(Date) <= parser.parse(params['todate'])) - - return filtercondition - - @classmethod - def getfilterforrequeststate(cls, params, includeclosed): - #request state: unopened, call for records, etc. - requeststatecondition = [] - for statelabel in params['requeststate']: - requeststatecondition.append(FOIMinistryRequest.requeststatuslabel == statelabel) - if(statelabel == StateName.closed.name): - includeclosed = True - return {'condition': or_(*requeststatecondition), 'includeclosed': includeclosed} - - @classmethod - def getfilterforrequeststatus(cls, params, iaoassignee, ministryassignee): - #request status: overdue || on time - if(params['requeststatus'][0] == 'overdue'): - #exclude "on hold" for overdue - statelabel = StateName.onhold.name - return and_(FOIMinistryRequest.findfield('duedate', iaoassignee, ministryassignee) < datetime.now().date(), FOIMinistryRequest.requeststatuslabel != statelabel) - else: - return FOIMinistryRequest.findfield('duedate', iaoassignee, ministryassignee) >= datetime.now().date() - - @classmethod - def getfilterforrequesttype(cls, params, iaoassignee, ministryassignee): - #request type: personal, general - requesttypecondition = [] - for type in params['requesttype']: - requesttypecondition.append(FOIMinistryRequest.findfield('requestType', iaoassignee, ministryassignee) == type) - return or_(*requesttypecondition) - - @classmethod - def getfilterforrequestflags(cls, params, iaoassignee, ministryassignee): - #request flags: restricted, oipc, phased - requestflagscondition = [] - #alias for getting ministry restricted flag from FOIRestrictedMinistryRequest - ministry_restricted_requests = aliased(FOIRestrictedMinistryRequest) - - for flag in params['requestflags']: - if (flag.lower() == 'restricted'): - if(iaoassignee): - requestflagscondition.append(FOIRestrictedMinistryRequest.isrestricted == True) - elif (ministryassignee): - requestflagscondition.append(ministry_restricted_requests.isrestricted == True) - if (flag.lower() == 'oipc'): - requestflagscondition.append(FOIMinistryRequest.findfield('isoipcreview', iaoassignee, ministryassignee) == True) - if (flag.lower() == 'phased'): - # requestflagscondition.append(FOIMinistryRequest.findfield('isphasedrelease', iaoassignee, ministryassignee) == True) - continue - return or_(*requestflagscondition) - - @classmethod - def getfilterforpublicbody(cls, params, iaoassignee, ministryassignee): - #public body: EDUC, etc. - publicbodycondition = [] - for ministry in params['publicbody']: - publicbodycondition.append(FOIMinistryRequest.findfield('ministry', iaoassignee, ministryassignee) == ministry) - return or_(*publicbodycondition) - - @classmethod - def getfilterforsearch(cls, params, iaoassignee, ministryassignee): - #axis request #, raw request #, applicant name, assignee name, request description, subject code - if(len(params['keywords']) > 0 and params['search'] is not None): - if(params['search'] == 'applicantname'): - searchcondition1 = [] - searchcondition2 = [] - for keyword in params['keywords']: - searchcondition1.append(FOIMinistryRequest.findfield('firstName', iaoassignee, ministryassignee).ilike('%'+keyword+'%')) - searchcondition2.append(FOIMinistryRequest.findfield('lastName', iaoassignee, ministryassignee).ilike('%'+keyword+'%')) - return or_(and_(*searchcondition1), and_(*searchcondition2)) - elif(params['search'] == 'assigneename'): - searchcondition1 = [] - searchcondition2 = [] - searchcondition3 = [] - for keyword in params['keywords']: - searchcondition1.append(FOIMinistryRequest.findfield('assignedToFirstName', iaoassignee, ministryassignee).ilike('%'+keyword+'%')) - searchcondition2.append(FOIMinistryRequest.findfield('assignedToLastName', iaoassignee, ministryassignee).ilike('%'+keyword+'%')) - searchcondition3.append(FOIMinistryRequest.assignedgroup.ilike('%'+keyword+'%')) - return or_(and_(*searchcondition1), and_(*searchcondition2), and_(*searchcondition3)) - elif(params['search'] == 'ministryassigneename'): - searchcondition1 = [] - searchcondition2 = [] - for keyword in params['keywords']: - searchcondition1.append(FOIMinistryRequest.findfield('assignedministrypersonFirstName', iaoassignee, ministryassignee).ilike('%'+keyword+'%')) - searchcondition2.append(FOIMinistryRequest.findfield('assignedministrypersonLastName', iaoassignee, ministryassignee).ilike('%'+keyword+'%')) - return or_(and_(*searchcondition1), and_(*searchcondition2)) - elif(params['search'] == 'oipc_number'): - searchcondition1 = [] - searchcondition2 = [] - for keyword in params['keywords']: - oipccondition = FOIRequestOIPC.getrequestidsbyoipcno(keyword) - searchcondition1.append(oipccondition.c.foiministryrequest_id == FOIMinistryRequest.foiministryrequestid) - searchcondition2.append(oipccondition.c.foiministryrequestversion_id == FOIMinistryRequest.version) - return and_(and_(*searchcondition1), and_(*searchcondition2)) - else: - searchcondition = [] - for keyword in params['keywords']: - searchcondition.append(FOIMinistryRequest.findfield(params['search'], iaoassignee, ministryassignee).ilike('%'+keyword+'%')) - return and_(*searchcondition) - @classmethod - def getfilenumberforrequest(cls,requestid, ministryrequestid): - return db.session.query(FOIMinistryRequest.filenumber).filter_by(foiministryrequestid=ministryrequestid, foirequest_id=requestid).first()[0] - - @classmethod - def getaxisrequestidforrequest(cls,requestid, ministryrequestid): - return db.session.query(FOIMinistryRequest.axisrequestid).filter_by(foiministryrequestid=ministryrequestid, foirequest_id=requestid).first()[0] - - @classmethod - def getrequest_by_pgmarea_type(cls,programarea, requesttype): - requestdetails = [] - try: - sql = """select fr.axisrequestid, fr.foiministryrequestid, fr."version", fr.axispagecount, fr.axislanpagecount - from "FOIMinistryRequests" fr join "FOIRequests" f - ON fr.foirequest_id = f.foirequestid and fr.foirequestversion_id = f."version" - where programareaid = :programarea and f.requesttype = :requesttype and fr.isactive = true - order by fr.created_at ;""" - rs = db.session.execute(text(sql), {'programarea': programarea, 'requesttype': requesttype}) - for row in rs: - requestdetails.append({"axisrequestid":row["axisrequestid"], "axispagecount": row["axispagecount"], "axislanpagecount": row["axislanpagecount"], "foiministryrequestid":row["foiministryrequestid"], "version":row["version"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return requestdetails - - @classmethod - def bulk_update_axispagecount(cls, requestdetails): - try: - db.session.bulk_update_mappings(FOIMinistryRequest, requestdetails) - db.session.commit() - return DefaultMethodResult(True,'Request updated', len(requestdetails)) - except Exception as ex: - logging.error(ex) - return DefaultMethodResult(False,'Request update failed', len(requestdetails)) - finally: - db.session.close() - - - @classmethod - def getmetadata(cls,ministryrequestid): - requestdetails = {} - try: - sql = """select fmr.version, assignedto, fa.firstname, fa.lastname, pa.bcgovcode, fmr.programareaid, f.requesttype, fmr.isoipcreview - from "FOIMinistryRequests" fmr join "FOIRequests" f on fmr.foirequest_id = f.foirequestid and fmr.foirequestversion_id = f."version" - FULL OUTER JOIN "FOIAssignees" fa ON fa.username = fmr.assignedto - INNER JOIN "ProgramAreas" pa ON pa.programareaid = fmr.programareaid - where foiministryrequestid = :ministryrequestid - order by fmr.version desc limit 1;""" - rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) - for row in rs: - requestdetails["assignedTo"] = row["assignedto"] - requestdetails["assignedToFirstName"] = row["firstname"] - requestdetails["assignedToLastName"] = row["lastname"] - requestdetails["bcgovcode"] = row["bcgovcode"] - requestdetails["version"] = row["version"] - requestdetails["programareaid"] = row["programareaid"] - requestdetails["requesttype"] = row["requesttype"] - requestdetails["isoipcreview"] = row["isoipcreview"] - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return requestdetails - - @classmethod - def getofflinepaymentflag(cls,ministryrequestid): - try: - sql = """select isofflinepayment from "FOIMinistryRequests" fci where - foiministryrequestid = :ministryrequestid and isofflinepayment = true limit 1;""" - rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) - for row in rs: - if row["isofflinepayment"] == True: - return True - return False - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - - @classmethod - def updaterecordspagecount(cls, ministryrequestid, pagecount, userid): - currequest = db.session.query(FOIMinistryRequest).filter_by(foiministryrequestid=ministryrequestid).order_by(FOIMinistryRequest.version.desc()).first() - setattr(currequest,'recordspagecount',pagecount) - setattr(currequest,'updated_at',datetime.now().isoformat()) - setattr(currequest,'updatedby',userid) - db.session.commit() - return DefaultMethodResult(True,'Request updated',ministryrequestid) - -class FOIMinistryRequestSchema(ma.Schema): - class Meta: - fields = ('foiministryrequestid','version','filenumber','description','recordsearchfromdate','recordsearchtodate', - 'startdate','duedate','assignedgroup','assignedto','programarea.programareaid', - 'foirequest.foirequestid','foirequest.requesttype','foirequest.receiveddate','foirequest.deliverymodeid', - 'foirequest.receivedmodeid','requeststatus.requeststatusid','requeststatuslabel','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', 'axispagecount', 'axislanpagecount', 'linkedrequests', 'ministrysignoffapproval', 'identityverified','originalldd', - 'isoipcreview', 'recordspagecount', 'estimatedpagecount', 'estimatedtaggedpagecount') - diff --git a/historical-search-api/request_api/models/FOIRawRequestComments.py b/historical-search-api/request_api/models/FOIRawRequestComments.py deleted file mode 100644 index 3d55b4d16..000000000 --- a/historical-search-api/request_api/models/FOIRawRequestComments.py +++ /dev/null @@ -1,165 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey -from .db import db, ma -from datetime import datetime -from sqlalchemy.orm import relationship, backref -from .default_method_result import DefaultMethodResult -from sqlalchemy.dialects.postgresql import JSON, UUID, insert -from sqlalchemy.sql.expression import distinct -from sqlalchemy import text -import logging -import json - - -class FOIRawRequestComment(db.Model): - # Name of the table in our database - __tablename__ = 'FOIRawRequestComments' - # Defining the columns - commentid = db.Column(db.Integer, primary_key=True, autoincrement=True) - requestid = db.Column( - db.Integer, db.ForeignKey('FOIRawRequests.requestid')) - version = db.Column(db.Integer, db.ForeignKey('FOIRawRequests.version')) - comment = db.Column(db.Text, unique=False, nullable=True) - taggedusers = db.Column(JSON, unique=False, nullable=True) - parentcommentid = db.Column(db.Integer, nullable=True) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - created_at = db.Column(db.DateTime, default=datetime.now) - createdby = db.Column(db.String(120), unique=False, nullable=True) - updated_at = db.Column(db.DateTime, nullable=True) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - - commenttypeid = db.Column(db.Integer, unique=False, nullable=False) - commentsversion = db.Column(db.Integer, nullable=False) - - @classmethod - def savecomment(cls, commenttypeid, foirequestcomment, version, userid) -> DefaultMethodResult: - commentsversion = 1 - parentcommentid = foirequestcomment["parentcommentid"] if 'parentcommentid' in foirequestcomment else None - taggedusers = foirequestcomment["taggedusers"] if 'taggedusers' in foirequestcomment else None - newcomment = FOIRawRequestComment(commenttypeid=commenttypeid, requestid=foirequestcomment["requestid"], version=version, comment=foirequestcomment["comment"], parentcommentid=parentcommentid, isactive=True, createdby=userid,taggedusers=taggedusers, commentsversion=commentsversion) - db.session.add(newcomment) - db.session.commit() - return DefaultMethodResult(True, 'Comment added', newcomment.commentid) - - @classmethod - def disablecomment(cls, commentid, userid): - dbquery = db.session.query(FOIRawRequestComment) - comment = dbquery.filter_by(commentid=commentid) - if(comment.count() > 0): - childcomments = dbquery.filter_by(parentcommentid=commentid, isactive=True) - if (childcomments.count() > 0) : - return DefaultMethodResult(False,'Cannot delete parent comment with replies',commentid) - comment.update({FOIRawRequestComment.isactive: False, FOIRawRequestComment.updatedby: userid, - FOIRawRequestComment.updated_at: datetime.now()}, synchronize_session=False) - db.session.commit() - return DefaultMethodResult(True, 'Comment disabled', commentid) - else: - return DefaultMethodResult(True, 'No Comment found', commentid) - - @classmethod - def deactivatecomment(cls, commentid, userid, commentsversion): - dbquery = db.session.query(FOIRawRequestComment) - comment = dbquery.filter_by(commentid=commentid, commentsversion=commentsversion) - if(comment.count() > 0) : - comment.update({FOIRawRequestComment.isactive: False, FOIRawRequestComment.updatedby: userid, - FOIRawRequestComment.updated_at: datetime.now()}, synchronize_session=False) - db.session.commit() - return DefaultMethodResult(True,'Comment deactivated',commentid) - else: - return DefaultMethodResult(True,'No Comment found',commentid) - - @classmethod - def updatecomment(cls, commentid, foirequestcomment, userid): - dbquery = db.session.query(FOIRawRequestComment) - comment = dbquery.filter_by(commentid=commentid).order_by(FOIRawRequestComment.commentsversion.desc()).first() - _commentsversion = 0 - _existingtaggedusers = [] - if comment is not None : - _existingtaggedusers = comment.taggedusers - _taggedusers = foirequestcomment["taggedusers"] if 'taggedusers' in foirequestcomment else _existingtaggedusers - _commentsversion = int(comment.commentsversion) - insertstmt = ( - insert(FOIRawRequestComment). - values( - commentid= comment.commentid, - requestid=comment.requestid, - version=comment.version, - comment=foirequestcomment["comment"], - taggedusers=_taggedusers, - parentcommentid=comment.parentcommentid, - isactive=True, - created_at=datetime.now(), - createdby=userid, - updated_at=datetime.now(), - updatedby=userid, - commenttypeid=comment.commenttypeid, - commentsversion=_commentsversion + 1 - ) - ) - updatestmt = insertstmt.on_conflict_do_update(index_elements=[FOIRawRequestComment.commentid, FOIRawRequestComment.commentsversion], - set_={"requestid": comment.requestid, "version":comment.version, "comment": foirequestcomment["comment"], - "taggedusers":_taggedusers, "parentcommentid":comment.parentcommentid, "isactive":True, - "created_at":datetime.now(), "createdby": userid, "updated_at": datetime.now(), "updatedby": userid, - "commenttypeid": comment.commenttypeid - } - ) - db.session.execute(updatestmt) - db.session.commit() - return DefaultMethodResult(True, 'Updated Comment added', commentid, _existingtaggedusers, _commentsversion) - else: - return DefaultMethodResult(True, 'No Comment found', commentid, _existingtaggedusers, _commentsversion) - - @classmethod - def getcomments(cls, requestid) -> DefaultMethodResult: - comment_schema = FOIRawRequestCommentSchema(many=True) - query = db.session.query(FOIRawRequestComment).filter_by( - requestid=requestid, isactive=True).order_by(FOIRawRequestComment.commentid.asc()).all() - return comment_schema.dump(query) - - # comments = [] - # try: - # sql = """SELECT distinct on (commentid) commentid, parentcommentid, requestid, version, comment, - # created_at, createdby, updated_at, updatedby, isactive, commenttypeid, taggedusers, commentsversion - # FROM public."FOIRawRequestComments" where requestid = :requestid and isactive = true order by commentid, commentsversion desc;""" - # rs = db.session.execute(text(sql), {'requestid': requestid}) - # for row in rs: - # comments.append(dict(row)) - # except Exception as ex: - # logging.error(ex) - # raise ex - # finally: - # db.session.close() - # return comments - - - @classmethod - def getcommentbyid(cls, commentid) -> DefaultMethodResult: - comment_schema = FOIRawRequestCommentSchema() - query = db.session.query(FOIRawRequestComment).filter_by( - commentid=commentid, isactive=True).first() - return comment_schema.dump(query) - - - @classmethod - def getcommentusers(cls, commentid): - users = [] - try: - sql = """select commentid, createdby, taggedusers from ( - select commentid, commenttypeid, createdby, taggedusers from "FOIRawRequestComments" frc where commentid = (select parentcommentid from "FOIRawRequestComments" frc where commentid=:commentid) and isactive = true - union all - select commentid, commenttypeid, createdby, taggedusers from "FOIRawRequestComments" frc where commentid <> :commentid and parentcommentid = (select parentcommentid from "FOIRawRequestComments" frc where commentid=:commentid) and isactive = true - ) cmt where commenttypeid =1""" - rs = db.session.execute(text(sql), {'commentid': commentid}) - for row in rs: - users.append({"commentid": row["commentid"], "createdby": row["createdby"], "taggedusers": row["taggedusers"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return users - -class FOIRawRequestCommentSchema(ma.Schema): - class Meta: - fields = ('commentid', 'requestid', 'parentcommentid', 'comment', 'commenttypeid', - 'commenttype', 'isactive', 'created_at', 'createdby', 'updated_at', 'updatedby','taggedusers', 'commentsversion') diff --git a/historical-search-api/request_api/models/FOIRawRequestDocuments.py b/historical-search-api/request_api/models/FOIRawRequestDocuments.py deleted file mode 100644 index 465e3a2cf..000000000 --- a/historical-search-api/request_api/models/FOIRawRequestDocuments.py +++ /dev/null @@ -1,88 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint -from .db import db, ma -from datetime import datetime -from sqlalchemy.orm import relationship,backref -from .default_method_result import DefaultMethodResult -from sqlalchemy.sql.expression import distinct -from sqlalchemy import or_,and_,text -import logging -class FOIRawRequestDocument(db.Model): - # Name of the table in our database - __tablename__ = 'FOIRawRequestDocuments' - __table_args__ = ( - ForeignKeyConstraint( - ["foirequest_id", "foirequestversion_id"], ["FOIRawRequests.requestid", "FOIRawRequests.version"] - ), - ) - - # Defining the columns - foidocumentid = db.Column(db.Integer, primary_key=True,autoincrement=True) - documentpath = db.Column(db.String(1000), unique=False, nullable=False) - filename = db.Column(db.String(120), unique=False, nullable=True) - category = db.Column(db.String(120), unique=False, nullable=True) - version =db.Column(db.Integer, nullable=True) - isactive = db.Column(db.Boolean, unique=False, nullable=False,default=True) - - created_at = db.Column(db.DateTime, default=datetime.now) - updated_at = db.Column(db.DateTime, nullable=True) - createdby = db.Column(db.String(120), unique=False, nullable=True) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - - #ForeignKey References - foirequest_id =db.Column(db.Integer, unique=False, nullable=False) - foirequestversion_id = db.Column(db.Integer, unique=False, nullable=False) - - @classmethod - def getdocuments(cls,requestid, requestversion): - documents = [] - try: - 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: - if row["isactive"] == True: - documents.append({"foidocumentid": row["foidocumentid"], "filename": row["filename"], "documentpath": row["documentpath"], "category": row["category"], "created_at": row["created_at"].strftime('%Y-%m-%d %H:%M:%S.%f'), "createdby": row["createdby"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return documents - - @classmethod - def getdocument(cls,foidocumentid): - document_schema = FOIRawRequestDocumentSchema() - request = db.session.query(FOIRawRequestDocument).filter_by(foidocumentid=foidocumentid).order_by(FOIRawRequestDocument.version.desc()).first() - return document_schema.dump(request) - - @classmethod - def createdocuments(cls,requestid,requestversion, documents, userid): - newdocuments = [] - for document in documents: - createuserid = document['createdby'] if 'createdby' in document and document['createdby'] is not None else userid - createdat = document['created_at'] if 'created_at' in document and document['created_at'] is not None else datetime.now() - newdocuments.append(FOIRawRequestDocument(documentpath=document["documentpath"], version='1', filename=document["filename"], category=document["category"], isactive=True, foirequest_id=requestid, foirequestversion_id=requestversion, created_at=createdat, createdby=createuserid)) - db.session.add_all(newdocuments) - db.session.commit() - return DefaultMethodResult(True,'Documents created') - - - @classmethod - def createdocumentversion(cls,requestid,requestversion, document, userid): - newdocument = FOIRawRequestDocument(documentpath=document["documentpath"], foidocumentid=document["foidocumentid"], version=document["version"], filename=document["filename"], category=document["category"], isactive=document["isactive"], foirequest_id=requestid, foirequestversion_id=requestversion, created_at=datetime.now(), createdby=userid) - db.session.add(newdocument) - db.session.commit() - return DefaultMethodResult(True,'New Document version created', newdocument.foidocumentid) - - @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.commit() - return DefaultMethodResult(True,'Raw Request Document Updated',documentid) - - -class FOIRawRequestDocumentSchema(ma.Schema): - class Meta: - fields = ('foidocumentid','documentpath', 'filename','category','version','isactive','foirequest_id','foirequestversion_id','created_at','createdby') - \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRawRequestNotificationUsers.py b/historical-search-api/request_api/models/FOIRawRequestNotificationUsers.py deleted file mode 100644 index 9d3b15523..000000000 --- a/historical-search-api/request_api/models/FOIRawRequestNotificationUsers.py +++ /dev/null @@ -1,270 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey -from .db import db, ma -from datetime import datetime as datetime2, timezone -from sqlalchemy.orm import relationship,backref -from .default_method_result import DefaultMethodResult -from sqlalchemy.dialects.postgresql import JSON, UUID -from sqlalchemy.sql.expression import distinct -from sqlalchemy import text -import logging -import json -from sqlalchemy.sql.sqltypes import DateTime, String, Date, Integer -from sqlalchemy.orm import relationship, backref, aliased -from sqlalchemy import insert, and_, or_, text, func, literal, cast, asc, desc, case, nullsfirst, nullslast, TIMESTAMP, extract -from .FOIRawRequestNotifications import FOIRawRequestNotification -from .FOIRawRequestWatchers import FOIRawRequestWatcher -from request_api.utils.enums import ProcessingTeamWithKeycloackGroup, IAOTeamWithKeycloackGroup -from request_api.models.views.FOINotifications import FOINotifications -from request_api.models.views.FOIRawRequests import FOIRawRequests -f = open('common/notificationusertypes.json', encoding="utf8") -notificationusertypes_cache = json.load(f) - -file = open('common/notificationtypes.json', encoding="utf8") -notificationtypes_cache = json.load(file) - -class FOIRawRequestNotificationUser(db.Model): - # Name of the table in our database - __tablename__ = 'FOIRawRequestNotificationUsers' - # Defining the columns - notificationuserid = db.Column(db.Integer, primary_key=True,autoincrement=True) - notificationid = db.Column(db.Integer,ForeignKey('FOIRawRequestNotifications.notificationid')) - userid = db.Column(db.String(100), unique=False, nullable=True) - isdeleted = db.Column(db.Boolean, unique=False, nullable=True, default=False) - created_at = db.Column(db.DateTime, default=datetime2.now) - createdby = db.Column(db.String(120), unique=False, nullable=True) - updated_at = db.Column(db.DateTime, nullable=True) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - notificationusertypeid = db.Column(db.Integer,nullable=False) - notificationusertypelabel = db.Column(db.String(120),nullable=False) - - - @classmethod - def dismissnotification(cls, notificationuserid, userid='system'): - exists = bool(db.session.query(FOIRawRequestNotificationUser.notificationuserid).filter_by(notificationuserid=notificationuserid).first()) - if exists == False: - return DefaultMethodResult(False,'Invalid ID',notificationuserid) - db.session.query(FOIRawRequestNotificationUser).filter(FOIRawRequestNotificationUser.notificationuserid == notificationuserid).update({FOIRawRequestNotificationUser.isdeleted: True, FOIRawRequestNotificationUser.updatedby: userid, - FOIRawRequestNotificationUser.updated_at: datetime2.now()}) - db.session.commit() - return DefaultMethodResult(True,'Notification deleted',notificationuserid) - - @classmethod - def dismissnotificationbyuser(cls, userid): - db.session.query(FOIRawRequestNotificationUser).filter(FOIRawRequestNotificationUser.userid == userid).update({FOIRawRequestNotificationUser.isdeleted: True, FOIRawRequestNotificationUser.updatedby: userid, - FOIRawRequestNotificationUser.updated_at: datetime2.now()}) - db.session.commit() - return DefaultMethodResult(True,'Notifications deleted for user',userid) - - @classmethod - def dismissnotificationbyuserandtype(cls, userid, notificationusertypelabel): - db.session.query(FOIRawRequestNotificationUser).filter(FOIRawRequestNotificationUser.userid == userid, FOIRawRequestNotificationUser.notificationusertypelabel == notificationusertypelabel).update({FOIRawRequestNotificationUser.isdeleted: True, FOIRawRequestNotificationUser.updatedby: userid, - FOIRawRequestNotificationUser.updated_at: datetime2.now()}) - db.session.commit() - return DefaultMethodResult(True,'Notifications deleted for user',userid) - - @classmethod - def getnotificationsbyid(cls, notificationuserid): - notifications = [] - try: - sql = """select notificationid, count(1) as relcount from "FOIRawRequestNotificationUsers" frnu - where notificationid in (select notificationid from "FOIRawRequestNotificationUsers" frnu where notificationuserid = :notificationuserid) group by notificationid """ - rs = db.session.execute(text(sql), {'notificationuserid': notificationuserid}) - for row in rs: - notifications.append({"notificationid": row["notificationid"], "count" : row["relcount"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return notifications - - @classmethod - def getnotificationsbyuser(cls, userid): - notifications = [] - try: - sql = """select notificationid, count(1) as relcount from "FOIRawRequestNotificationUsers" frnu - where notificationid in (select notificationid from "FOIRawRequestNotificationUsers" frnu where userid = :userid) group by notificationid """ - rs = db.session.execute(text(sql), {'userid': userid}) - for row in rs: - notifications.append({"notificationid": row["notificationid"], "count" : row["relcount"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return notifications - - @classmethod - def getnotificationsbyuserandtype(cls, userid, notificationusertypelabel): - notifications = [] - try: - sql = """select notificationid, count(1) as relcount from "FOIRawRequestNotificationUsers" frnu - where notificationid in (select notificationid from "FOIRawRequestNotificationUsers" frnu where userid = :userid and notificationusertypelabel = :notificationusertypelabel) group by notificationid """ - rs = db.session.execute(text(sql), {'userid': userid, 'notificationusertypelabel': notificationusertypelabel}) - for row in rs: - notifications.append({"notificationid": row["notificationid"], "count" : row["relcount"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return notifications - - @classmethod - def dismissbynotificationid(cls, notificationids, userid='system'): - db.session.query(FOIRawRequestNotificationUser).filter(FOIRawRequestNotificationUser.notificationid.in_(notificationids), FOIRawRequestNotificationUser.isdeleted == False).update({FOIRawRequestNotificationUser.isdeleted: True, FOIRawRequestNotificationUser.updatedby: userid, - FOIRawRequestNotificationUser.updated_at: datetime2.now()}, synchronize_session=False) - db.session.commit() - return DefaultMethodResult(True,'Notifications deleted for id',notificationids) - - @classmethod - def getnotificationidsbyuserandid(cls, userid, notificationids): - ids = [] - try: - sql = """select notificationid from "FOIRawRequestNotificationUsers" where userid = :userid and notificationid = ANY(:notificationids) """ - rs = db.session.execute(text(sql), {'userid': userid, 'notificationids': notificationids}) - for row in rs: - ids.append(row["notificationid"]) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return ids - - # Begin of Dashboard functions - - @classmethod - def getbasequery(cls, groups, additionalfilter=None, userid=None, isiaorestrictedfilemanager=False): - _session = db.session - - selectedcolumns = [ - FOIRawRequests.crtid.label('crtid'), - FOIRawRequests.axisrequestid.label('axisRequestId'), - FOIRawRequests.rawrequestid.label('rawrequestid'), - FOIRawRequests.foirequest_id.label('requestid'), - FOIRawRequests.ministryrequestid.label('ministryrequestid'), - FOIRawRequests.status.label('status'), - FOIRawRequests.assignedtoformatted.label('assignedToFormatted'), - FOIRawRequests.ministryassignedtoformatted.label('ministryAssignedToFormatted'), - FOIRawRequests.description.label('description'), - FOINotifications.notificationtype.label('notificationtype'), - FOINotifications.notification.label('notification'), - FOINotifications.created_at.label('createdat'), - FOINotifications.createdatformatted.label('createdatformatted'), - FOINotifications.userformatted.label('userFormatted'), - FOINotifications.creatorformatted.label('creatorFormatted'), - FOINotifications.id.label('id') - ] - - basequery = _session.query( - *selectedcolumns - ).join( - FOIRawRequests, - and_(FOIRawRequests.axisrequestid == FOINotifications.axisnumber), - ) - - if additionalfilter is not None: - if(additionalfilter == 'watchingRequests' and userid is not None): - #watchby - subquery_watchby = FOIRawRequestWatcher.getrequestidsbyuserid(userid) - return basequery.join(subquery_watchby, subquery_watchby.c.requestid == cast(FOIRawRequests.rawrequestid, Integer)) - elif(additionalfilter == 'myRequests'): - #myrequest - return basequery.filter(or_(FOIRawRequests.assignedto == userid, and_(FOINotifications.userid == userid, FOINotifications.notificationtypelabel == notificationtypes_cache['taggedusercomments']['notificationtypelabel']))) - else: - if(isiaorestrictedfilemanager == True): - return basequery.filter(FOIRawRequests.assignedgroup.in_(groups)) - else: - return basequery.filter( - and_( - or_(FOIRawRequests.isiaorestricted.is_(None), FOIRawRequests.isiaorestricted == False, and_(FOIRawRequests.isiaorestricted == True, FOIRawRequests.assignedto == userid)))).filter(FOIRawRequests.assignedgroup.in_(groups)) - - - @classmethod - def getrequestssubquery(cls, groups, filterfields, keyword, additionalfilter, userid, isiaorestrictedfilemanager): - basequery = FOIRawRequestNotificationUser.getbasequery(groups, additionalfilter, userid, isiaorestrictedfilemanager) - #filter/search - if(len(filterfields) > 0 and keyword is not None): - filtercondition = FOIRawRequestNotificationUser.getfilterforrequestssubquery(filterfields, keyword) - return basequery.filter(filtercondition) - else: - return basequery - - @classmethod - def getfilterforrequestssubquery(cls, filterfields, keyword): - keyword = keyword.lower() - #filter/search - filtercondition = [] - if(keyword != 'restricted'): - for field in filterfields: - _keyword = FOIRawRequestNotificationUser.getfilterkeyword(keyword, field) - filtercondition.append(FOIRawRequestNotificationUser.findfield(field).ilike('%'+_keyword+'%')) - filtercondition.append(FOIRawRequests.isiaorestricted == True) - - return or_(*filtercondition) - - - - @classmethod - def getfilterkeyword(cls, keyword, field): - _newkeyword = keyword - if(field == 'idNumber'): - _newkeyword = _newkeyword.replace('u-00', '') - return _newkeyword - - @classmethod - def findfield(cls, x): - return { - 'axisRequestId' : FOIRawRequests.axisrequestid, - 'createdat': FOINotifications.created_at, - 'createdatformatted': FOINotifications.createdatformatted, - 'notification': FOINotifications.notification, - 'assignedToFormatted': FOIRawRequests.assignedtoformatted, - 'ministryAssignedToFormatted': FOIRawRequests.ministryassignedtoformatted, - 'userFormatted': FOINotifications.userformatted, - 'creatorFormatted': FOINotifications.creatorformatted - }.get(x, cast(FOINotifications.axisnumber, String)) - - - @classmethod - def getsorting(cls, sortingitems, sortingorders): - sortingcondition = [] - if(len(sortingitems) > 0 and len(sortingorders) > 0 and len(sortingitems) == len(sortingorders)): - for field in sortingitems: - if(FOIRawRequestNotificationUser.validatefield(field)): - order = sortingorders.pop(0) - if(order == 'desc'): - sortingcondition.append(nullslast(desc(field))) - else: - sortingcondition.append(nullsfirst(asc(field))) - #default sorting - if(len(sortingcondition) == 0): - sortingcondition.append(desc('createdat')) - - #always sort by created_at last to prevent pagination collisions - sortingcondition.append(desc('createdat')) - - return sortingcondition - - @classmethod - def validatefield(cls, x): - validfields = [ - 'notification', - 'axisRequestId', - 'createdat', - 'assignedToFormatted', - 'ministryAssignedToFormatted', - 'userFormatted', - 'creatorFormatted' - ] - if x in validfields: - return True - else: - return False - # End of Dashboard functions - -class FOIRawRequestNotificationUserSchema(ma.Schema): - class Meta: - fields = ('notificationid', 'userid', 'notificationusertypeid' , 'notificationusertypelabel','created_at','createdby','updated_at','updatedby') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRawRequestNotifications.py b/historical-search-api/request_api/models/FOIRawRequestNotifications.py deleted file mode 100644 index cb21f0769..000000000 --- a/historical-search-api/request_api/models/FOIRawRequestNotifications.py +++ /dev/null @@ -1,119 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint -from sqlalchemy.sql.schema import ForeignKey -from .db import db, ma -from sqlalchemy.dialects.postgresql import JSON -from datetime import datetime as datetime2, timedelta -from sqlalchemy.orm import relationship,backref -from .default_method_result import DefaultMethodResult -from sqlalchemy.dialects.postgresql import JSON, UUID -from sqlalchemy.sql.expression import distinct -from sqlalchemy import text -import logging -import json -class FOIRawRequestNotification(db.Model): - # Name of the table in our database - __tablename__ = 'FOIRawRequestNotifications' - # Defining the columns - notificationid = db.Column(db.Integer, primary_key=True,autoincrement=True) - requestid =db.Column(db.Integer, db.ForeignKey('FOIRawRequests.requestid')) - version =db.Column(db.Integer, db.ForeignKey('FOIRawRequests.version')) - idnumber = db.Column(db.String(50), unique=False, nullable=True) - axisnumber = db.Column(db.String(50), unique=False, nullable=True) - notification = db.Column(JSON, unique=False, nullable=True) - isdeleted = db.Column(db.Boolean, unique=False, nullable=True, default=False) - created_at = db.Column(db.DateTime, default=datetime2.now) - createdby = db.Column(db.String(120), unique=False, nullable=True) - updated_at = db.Column(db.DateTime, nullable=True) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - - notificationtypeid = db.Column(db.Integer, nullable=False) - notificationtypelabel = db.Column(db.Integer, nullable=False) - - notificationusers = db.relationship('FOIRawRequestNotificationUser', backref='FOIRawRequestNotifications', lazy='dynamic') - - - @classmethod - def savenotification(cls,foinotification)->DefaultMethodResult: - try: - db.session.add(foinotification) - db.session.commit() - return DefaultMethodResult(True,'Notification added',foinotification.requestid) - except: - db.session.rollback() - raise - - @classmethod - def dismissnotification(cls, notificationids, userid='system'): - try: - db.session.query(FOIRawRequestNotification).filter(FOIRawRequestNotification.notificationid.in_(notificationids), FOIRawRequestNotification.isdeleted == False).update({FOIRawRequestNotification.isdeleted: True, FOIRawRequestNotification.updatedby: userid, - FOIRawRequestNotification.updated_at: datetime2.now()}, synchronize_session=False) - db.session.commit() - return DefaultMethodResult(True,'Notifications deleted ', notificationids) - except: - db.session.rollback() - raise - - @classmethod - def updatenotification(cls, foinotification, userid): - dbquery = db.session.query(FOIRawRequestNotification) - _notification = dbquery.filter_by(notificationid=foinotification['notificationid']) - if(_notification.count() > 0) : - _notification.update({ - FOIRawRequestNotification.notification:foinotification['notification'], - FOIRawRequestNotification.updatedby:userid, - FOIRawRequestNotification.updated_at:datetime2.now() - }, synchronize_session = False) - db.session.commit() - return DefaultMethodResult(True,'notification updated',foinotification['notificationid']) - else: - return DefaultMethodResult(True,'No notification found',foinotification['notificationid']) - - @classmethod - def getnotificationidsbynumberandtype(cls, idnumber, notificationtypelabel): - notificationids = [] - try: - sql = """select notificationid from "FOIRawRequestNotifications" where idnumber = :idnumber and notificationtypelabel= :notificationtypelabel """ - rs = db.session.execute(text(sql), {'idnumber': idnumber, 'notificationtypelabel': notificationtypelabel}) - for row in rs: - notificationids.append(row["notificationid"]) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return notificationids - - @classmethod - def getnotificationidsbynumber(cls, idnumber): - notificationids = [] - try: - sql = """select notificationid from "FOIRawRequestNotifications" where idnumber = :idnumber """ - rs = db.session.execute(text(sql), {'idnumber': idnumber}) - for row in rs: - notificationids.append(row["notificationid"]) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return notificationids - - @classmethod - def getnotificationidsbytype(cls, notificationtypelabel): - notificationids = [] - try: - sql = """select notificationid from "FOIRawRequestNotifications" where notificationtypelabel= :notificationtypelabel and isdeleted = false """ - rs = db.session.execute(text(sql), {'notificationtypelabel': notificationtypelabel}) - for row in rs: - notificationids.append(row["notificationid"]) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return notificationids - -class FOIRawRequestNotificationSchema(ma.Schema): - class Meta: - fields = ('notificationid', 'requestid', 'idnumber','notification', 'notificationtypeid', 'notificationtypelabel','created_at','createdby','updated_at','updatedby','notificationusers') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRawRequestWatchers.py b/historical-search-api/request_api/models/FOIRawRequestWatchers.py deleted file mode 100644 index 3080d6c58..000000000 --- a/historical-search-api/request_api/models/FOIRawRequestWatchers.py +++ /dev/null @@ -1,106 +0,0 @@ -from enum import unique - - -from sqlalchemy.sql.expression import distinct -from .db import db, ma -from sqlalchemy.dialects.postgresql import JSON, UUID -from .default_method_result import DefaultMethodResult -from datetime import datetime -from sqlalchemy import insert, and_, text, func -from flask import jsonify -import logging -class FOIRawRequestWatcher(db.Model): - # Name of the table in our database - __tablename__ = 'FOIRawRequestWatchers' - # Defining the columns - watcherid = db.Column(db.Integer, primary_key=True,autoincrement=True) - requestid =db.Column(db.Integer, db.ForeignKey('FOIRawRequests.requestid')) - version =db.Column(db.Integer, db.ForeignKey('FOIRawRequests.version')) - watchedbygroup = db.Column(db.String(250), unique=False, nullable=True) - watchedby = db.Column(db.String(120), unique=False, nullable=True) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - created_at = db.Column(db.DateTime, default=datetime.now) - createdby = db.Column(db.String(120), unique=False, nullable=True) - updated_at = db.Column(db.DateTime, nullable=True) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - - @classmethod - def savewatcher(cls, foirawrequestwatcher, version, userid)->DefaultMethodResult: - newwatcher = FOIRawRequestWatcher(requestid=foirawrequestwatcher["requestid"], version=version, watchedbygroup=foirawrequestwatcher["watchedbygroup"], watchedby=foirawrequestwatcher["watchedby"], isactive=foirawrequestwatcher["isactive"], createdby=userid) - db.session.add(newwatcher) - db.session.commit() - return DefaultMethodResult(True,'Request added') - - @classmethod - def savewatcherbygroups(cls, foirawrequestwatcher, version, userid, watchergroups)->DefaultMethodResult: - for group in watchergroups: - foirawrequestwatcher["watchedbygroup"] = group - newwatcher = FOIRawRequestWatcher(requestid=foirawrequestwatcher["requestid"], version=version, watchedbygroup=foirawrequestwatcher["watchedbygroup"], watchedby=foirawrequestwatcher["watchedby"], isactive=foirawrequestwatcher["isactive"], createdby=userid) - db.session.add(newwatcher) - db.session.commit() - return DefaultMethodResult(True,'Request added') - - @classmethod - def getwatchers(cls, requestid): - watchers = [] - try: - sql = 'select distinct on (watchedby, watchedbygroup) watchedby, watchedbygroup, isactive from "FOIRawRequestWatchers" where requestid=:requestid order by watchedby, watchedbygroup, created_at desc' - rs = db.session.execute(text(sql), {'requestid': requestid}) - for row in rs: - if row["isactive"] == True: - watchers.append({"watchedby": row["watchedby"], "watchedbygroup": row["watchedbygroup"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return watchers - - @classmethod - def isawatcher(cls, requestid,userid): - _iswatcher = False - try: - sql = 'select distinct on (watchedby, watchedbygroup) watchedby, watchedbygroup, isactive from "FOIRawRequestWatchers" where requestid=:requestid and watchedby=:watchedby order by watchedby, watchedbygroup, created_at desc' - rs = db.session.execute(text(sql), {'requestid': requestid,'watchedby':userid}) - for row in rs: - if row["isactive"] == True: - _iswatcher = True - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return _iswatcher - - @classmethod - def getrequestidsbyuserid(cls, userid): - #subquery for getting latest watching status - subquery_max = db.session.query(FOIRawRequestWatcher.requestid, FOIRawRequestWatcher.watchedby ,func.max(FOIRawRequestWatcher.watcherid).label('max_watcherid')).group_by(FOIRawRequestWatcher.requestid, FOIRawRequestWatcher.watchedby).subquery() - joincondition = [ - subquery_max.c.requestid == FOIRawRequestWatcher.requestid, - subquery_max.c.watchedby == FOIRawRequestWatcher.watchedby, - subquery_max.c.max_watcherid == FOIRawRequestWatcher.watcherid, - ] - - return db.session.query( - FOIRawRequestWatcher.requestid, - FOIRawRequestWatcher.watchedby - ).join( - subquery_max, - and_(*joincondition) - ).filter(and_(FOIRawRequestWatcher.watchedby == userid, FOIRawRequestWatcher.isactive == True)).subquery() - - @classmethod - def disablewatchers(cls, requestid, userid): - dbquery = db.session.query(FOIRawRequestWatcher) - requestraqw = dbquery.filter_by(requestid=requestid) - if(requestraqw.count() > 0) : - requestraqw.update({FOIRawRequestWatcher.isactive:False, FOIRawRequestWatcher.updatedby:userid, FOIRawRequestWatcher.updated_at:datetime.now()}, synchronize_session = False) - db.session.commit() - return DefaultMethodResult(True,'Watchers disabled',requestid) - else: - return DefaultMethodResult(True,'No Watchers found',requestid) - -class FOIRawRequestWatcherSchema(ma.Schema): - class Meta: - fields = ('watcherid', 'requestid', 'watchedbygroup','watchedby','isactive','created_at','createdby','updated_at','updatedby') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRawRequests.py b/historical-search-api/request_api/models/FOIRawRequests.py deleted file mode 100644 index b31e90cc6..000000000 --- a/historical-search-api/request_api/models/FOIRawRequests.py +++ /dev/null @@ -1,1161 +0,0 @@ -from enum import unique - -from sqlalchemy.sql.sqltypes import DateTime, String, Date, Integer - -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint -from sqlalchemy.sql.expression import distinct -from .db import db, ma -from sqlalchemy.dialects.postgresql import JSON, UUID -from .default_method_result import DefaultMethodResult -from datetime import datetime -from sqlalchemy.orm import relationship, backref, aliased -from sqlalchemy import insert, and_, or_, text, func, literal, cast, asc, desc, case, nullsfirst, nullslast, TIMESTAMP - -from .FOIMinistryRequests import FOIMinistryRequest -from .FOIRawRequestWatchers import FOIRawRequestWatcher -from .FOIAssignees import FOIAssignee -import logging -from dateutil import parser -import json -from request_api.utils.enums import StateName -from request_api.utils.enums import ProcessingTeamWithKeycloackGroup, IAOTeamWithKeycloackGroup - -class FOIRawRequest(db.Model): - # Name of the table in our database - __tablename__ = 'FOIRawRequests' - # Defining the columns - requestid = db.Column(db.Integer, primary_key=True,autoincrement=True) - version = db.Column(db.Integer, primary_key=True,nullable=False) - requestrawdata = db.Column(JSON, unique=False, nullable=True) - status = db.Column(db.String(25), unique=False, nullable=True) - requeststatuslabel = db.Column(db.String(50), unique=False, nullable=False) - notes = db.Column(db.String(120), unique=False, nullable=True) - wfinstanceid = db.Column(UUID(as_uuid=True), unique=False, 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) - created_at = db.Column(db.DateTime, default=datetime.now) - updated_at = db.Column(db.DateTime, nullable=True) - createdby = db.Column(db.String(120), unique=False, nullable=True) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - sourceofsubmission = db.Column(db.String(120), nullable=True) - ispiiredacted = db.Column(db.Boolean, unique=False, nullable=False,default=False) - closedate = db.Column(db.DateTime, nullable=True) - requirespayment = db.Column(db.Boolean, unique=False, nullable=True, default=False) - - axissyncdate = db.Column(db.DateTime, nullable=True) - axisrequestid = db.Column(db.String(120), nullable=True) - linkedrequests = db.Column(JSON, unique=False, nullable=True) - - - isiaorestricted = db.Column(db.Boolean, unique=False, nullable=False,default=False) - - closereasonid = db.Column(db.Integer,ForeignKey('CloseReasons.closereasonid')) - closereason = relationship("CloseReason", uselist=False) - - assignee = relationship('FOIAssignee', foreign_keys="[FOIRawRequest.assignedto]") - - @classmethod - def saverawrequest(cls, _requestrawdata, sourceofsubmission, ispiiredacted, userid, notes, requirespayment, axisrequestid, axissyncdate, linkedrequests, assigneegroup=None, assignee=None, assigneefirstname=None, assigneemiddlename=None, assigneelastname=None)->DefaultMethodResult: - version = 1 - newrawrequest = FOIRawRequest(requestrawdata=_requestrawdata, status = StateName.unopened.value if sourceofsubmission != "intake" else StateName.intakeinprogress.value, requeststatuslabel = StateName.unopened.name if sourceofsubmission != "intake" else StateName.intakeinprogress.name, createdby=userid, version=version, sourceofsubmission=sourceofsubmission, assignedgroup=assigneegroup, assignedto=assignee, ispiiredacted=ispiiredacted, notes=notes, requirespayment=requirespayment, axisrequestid=axisrequestid, axissyncdate=axissyncdate, linkedrequests=linkedrequests) - if assignee is not None: - FOIAssignee.saveassignee(assignee, assigneefirstname, assigneemiddlename, assigneelastname) - - db.session.add(newrawrequest) - db.session.commit() - return DefaultMethodResult(True,'Request added',newrawrequest.requestid) - - @classmethod - def saverawrequest_foipayment(cls,_requestrawdata,notes, requirespayment, ispiiredacted)->DefaultMethodResult: - version = 1 - newrawrequest = FOIRawRequest(requestrawdata=_requestrawdata, status=StateName.unopened.value, requeststatuslabel = StateName.unopened.name ,createdby=None,version=version,sourceofsubmission="onlineform",assignedgroup=None,assignedto=None,ispiiredacted=ispiiredacted,notes=notes, requirespayment= requirespayment) - db.session.add(newrawrequest) - db.session.commit() - return DefaultMethodResult(True,'Request added',newrawrequest.requestid) - - @classmethod - def saverawrequestversion(cls,_requestrawdata,requestid,assigneegroup,assignee,status,ispiiredacted,userid,statuslabel,assigneefirstname=None,assigneemiddlename=None,assigneelastname=None)->DefaultMethodResult: - request = db.session.query(FOIRawRequest).filter_by(requestid=requestid).order_by(FOIRawRequest.version.desc()).first() - if request is not None: - _assginee = assignee if assignee not in (None,'') else None - if _assginee not in (None,''): - FOIAssignee.saveassignee(_assginee, assigneefirstname, assigneemiddlename, assigneelastname) - - closedate = _requestrawdata["closedate"] if 'closedate' in _requestrawdata else None - closereasonid = _requestrawdata["closereasonid"] if 'closereasonid' in _requestrawdata else None - axisrequestid = _requestrawdata["axisRequestId"] if 'axisRequestId' in _requestrawdata else None - axissyncdate = _requestrawdata["axisSyncDate"] if 'axisSyncDate' in _requestrawdata else None - linkedrequests = _requestrawdata["linkedRequests"] if 'linkedRequests' in _requestrawdata else None - _version = request.version+1 - insertstmt =( - insert(FOIRawRequest). - values( - requestid=request.requestid, - requestrawdata=_requestrawdata, - version=_version, - updatedby=None, - updated_at=datetime.now(), - status=status, - requeststatuslabel=statuslabel, - assignedgroup=assigneegroup, - assignedto=_assginee, - wfinstanceid=request.wfinstanceid, - sourceofsubmission=request.sourceofsubmission, - ispiiredacted=ispiiredacted, - createdby=userid, - closedate=closedate, - closereasonid=closereasonid, - axisrequestid= axisrequestid, - axissyncdate=axissyncdate, - linkedrequests=linkedrequests, - isiaorestricted = request.isiaorestricted - - ) - ) - db.session.execute(insertstmt) - db.session.commit() - return DefaultMethodResult(True,'Request versioned - {0}'.format(str(_version)),requestid,request.wfinstanceid,assignee) - else: - return DefaultMethodResult(True,'No request foound') - - - @classmethod - def saveiaorestrictedrawrequest(cls,requestid,_isiaorestricted=False, _updatedby=None)->DefaultMethodResult: - currentrequest = db.session.query(FOIRawRequest).filter_by(requestid=requestid).order_by(FOIRawRequest.version.desc()).first() - request = currentrequest - _version = currentrequest.version+1 - insertstmt = ( - insert(FOIRawRequest). - values( - requestid=request.requestid, - requestrawdata=request.requestrawdata, - version=_version, - updatedby=_updatedby, - updated_at=datetime.now(), - status=request.status, - assignedgroup=request.assignedgroup, - assignedto=request.assignedto, - wfinstanceid=request.wfinstanceid, - sourceofsubmission=request.sourceofsubmission, - ispiiredacted=request.ispiiredacted, - createdby=request.createdby, - closedate=request.closedate, - closereasonid=request.closereasonid, - axisrequestid= request.axisrequestid, - axissyncdate=request.axissyncdate, - linkedrequests=request.linkedrequests, - created_at=request.created_at, - requirespayment = request.requirespayment, - isiaorestricted =_isiaorestricted, - notes = request.notes, - requeststatuslabel = request.requeststatuslabel, - - ) - ) - db.session.execute(insertstmt) - db.session.commit() - return DefaultMethodResult(True,'Request Updated for iaorestricted - {0}'.format(str(request.version)),requestid,request.wfinstanceid,_isiaorestricted) - - @classmethod - def saverawrequestassigneeversion(cls,requestid,assigneegroup,assignee,userid,assigneefirstname=None,assigneemiddlename=None,assigneelastname=None)->DefaultMethodResult: - request = db.session.query(FOIRawRequest).filter_by(requestid=requestid).order_by(FOIRawRequest.version.desc()).first() - if request is not None: - _assginee = assignee if assignee not in (None,'') else None - if _assginee not in (None,''): - FOIAssignee.saveassignee(_assginee, assigneefirstname, assigneemiddlename, assigneelastname) - - closedate = request.closedate - closereasonid = request.closereasonid - axisrequestid = request.axisrequestid - axissyncdate = request.axissyncdate - linkedrequests=request.linkedrequests - _version = request.version+1 - rawrequest = request.requestrawdata - rawrequest["assignedGroup"] = assigneegroup - rawrequest["assignedTo"] = _assginee - rawrequest["assignedToFirstName"] = assigneefirstname - rawrequest["assignedToLastName"] = assigneelastname - insertstmt =( - insert(FOIRawRequest). - values( - requestid=request.requestid, - requestrawdata=rawrequest, - version=_version, - updatedby=None, - updated_at=datetime.now(), - status=request.status, - assignedgroup=assigneegroup, - assignedto=_assginee, - wfinstanceid=request.wfinstanceid, - sourceofsubmission=request.sourceofsubmission, - ispiiredacted=request.ispiiredacted, - createdby=userid, - closedate=closedate, - closereasonid=closereasonid, - axisrequestid= axisrequestid, - axissyncdate=axissyncdate, - linkedrequests=linkedrequests, - isiaorestricted = request.isiaorestricted, - requeststatuslabel = request.requeststatuslabel - ) - ) - db.session.execute(insertstmt) - db.session.commit() - return DefaultMethodResult(True,'Request versioned - {0}'.format(str(_version)),requestid,request.wfinstanceid,assignee) - else: - return DefaultMethodResult(True,'No request foound') - - @classmethod - def getworkflowinstancebyraw(cls,requestid)->DefaultMethodResult: - request_schema = FOIRawRequestSchema() - try: - sql = """select wfinstanceid, assignedto, assignedgroup, requestid from "FOIRawRequests" fr where requestid = :requestid order by "version" desc limit 1;""" - rs = db.session.execute(text(sql), {'requestid': requestid}) - for row in rs: - request_schema.__dict__.update({"requestid": row["requestid"],"assignedto": row["assignedto"], "assignedgroup": row["assignedgroup"], "wfinstanceid": row["wfinstanceid"]}) - except Exception as ex: - logging.error(ex) - finally: - db.session.close() - return request_schema - - - @classmethod - def getworkflowinstancebyministry(cls,requestid)->DefaultMethodResult: - request_schema = FOIRawRequestSchema() - try: - sql = """select fr.wfinstanceid, fr.assignedto, fr.assignedgroup, fr.requestid - from "FOIMinistryRequests" fr2, "FOIRequests" fr3, "FOIRawRequests" fr - where fr2.foirequest_id = fr3.foirequestid and fr3.foirawrequestid = fr.requestid - and fr2.foiministryrequestid= :requestid order by fr."version" desc limit 1""" - rs = db.session.execute(text(sql), {'requestid': requestid}) - for row in rs: - request_schema.__dict__.update({"requestid": row["requestid"], "assignedto": row["assignedto"], "assignedgroup": row["assignedgroup"], "wfinstanceid": row["wfinstanceid"]}) - except Exception as ex: - logging.error(ex) - finally: - db.session.close() - return request_schema - - @classmethod - def updateworkflowinstance(cls,wfinstanceid,requestid, userid)->DefaultMethodResult: - updatedat = datetime.now() - dbquery = db.session.query(FOIRawRequest) - requestraqw = dbquery.filter_by(requestid=requestid,version = 1) - if(requestraqw.count() > 0) : - existingrequestswithwfid = dbquery.filter_by(wfinstanceid=wfinstanceid) - if(existingrequestswithwfid.count() == 0) : - requestraqw.update({FOIRawRequest.wfinstanceid:wfinstanceid, FOIRawRequest.updated_at:updatedat,FOIRawRequest.updatedby:userid, FOIRawRequest.notes:"WF Instance created"}, synchronize_session = False) - db.session.commit() - return DefaultMethodResult(True,'Request updated with WF Instance Id',requestid) - else: - return DefaultMethodResult(False,'WF instance already exists',requestid) - else: - return DefaultMethodResult(False,'Requestid not exists',-1) - - @classmethod - def updateworkflowinstance_n(cls,wfinstanceid,requestid, userid)->DefaultMethodResult: - updatedat = datetime.now() - dbquery = db.session.query(FOIRawRequest) - _requestraqw = dbquery.filter_by(requestid=requestid).order_by(FOIRawRequest.version.desc()).first() - requestraqw = dbquery.filter_by(requestid=requestid,version = _requestraqw.version) - if(requestraqw.count() > 0) : - requestraqw.update({FOIRawRequest.wfinstanceid:wfinstanceid, FOIRawRequest.updated_at:updatedat,FOIRawRequest.updatedby:userid}, synchronize_session = False) - db.session.commit() - return DefaultMethodResult(True,'Request updated',requestid) - else: - return DefaultMethodResult(False,'Requestid not exists',-1) - - @classmethod - def updateworkflowinstancewithstatus(cls,wfinstanceid,requestid,notes,userid)-> DefaultMethodResult: - updatedat = datetime.now() - dbquery = db.session.query(FOIRawRequest) - _requestraqw = dbquery.filter_by(requestid=requestid).order_by(FOIRawRequest.version.desc()).first() - requestraqw = dbquery.filter_by(requestid=requestid,version = _requestraqw.version) - if(requestraqw.count() > 0) : - request_schema = FOIRawRequestSchema() - request = request_schema.dump(_requestraqw) - status = request["status"] - - requestraqw.update({FOIRawRequest.wfinstanceid:wfinstanceid, FOIRawRequest.updated_at:updatedat,FOIRawRequest.notes:notes,FOIRawRequest.status:status,FOIRawRequest.updatedby:userid}, synchronize_session = False) - db.session.commit() - return DefaultMethodResult(True,'Request updated',requestid) - else: - return DefaultMethodResult(False,'Requestid not exists',-1) - - @classmethod - def getrequests(cls): - _session = db.session - _archivedrequestids = _session.query(distinct(FOIRawRequest.requestid)).filter(FOIRawRequest.status.in_(['Archived'])).all() - _requestids = _session.query(distinct(FOIRawRequest.requestid)).filter(FOIRawRequest.requestid.notin_(_archivedrequestids)).all() - requests = [] - for _requestid in _requestids: - request = _session.query(FOIRawRequest).filter(FOIRawRequest.requestid == _requestid).order_by(FOIRawRequest.version.desc()).first() - requests.append(request) - - return requests - - - @classmethod - def getDescriptionSummaryById(cls, requestid): - requests = [] - try: - sql = """select * , - CASE WHEN description = (select requestrawdata -> 'descriptionTimeframe' ->> 'description' from "FOIRawRequests" where requestid = :requestid and status = :requeststatus and version = 1) - then 'Online Form' - else savedby END as createdby - from (select CASE WHEN lower(status) <> 'unopened' - then requestrawdata ->> 'description' - ELSE requestrawdata -> 'descriptionTimeframe' ->> 'description' END as description , - CASE WHEN lower(status) <> 'unopened' - then requestrawdata ->> 'fromDate' - ELSE requestrawdata -> 'descriptionTimeframe' ->> 'fromDate' END as fromdate, - CASE WHEN lower(status) <> 'unopened' - then requestrawdata ->> 'toDate' - ELSE requestrawdata -> 'descriptionTimeframe' ->> 'toDate' END as todate, - to_char(created_at, 'YYYY-MM-DD HH24:MI:SS') as createdat, status, ispiiredacted, - createdby as savedby from "FOIRawRequests" fr - where requestid = :requestid order by version ) as sq;""" - rs = db.session.execute(text(sql), {'requestid': requestid, 'requeststatus': StateName.unopened.value}) - for row in rs: - requests.append(dict(row)) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return requests - - @classmethod - def get_request(cls,requestid): - request_schema = FOIRawRequestSchema() - request = db.session.query(FOIRawRequest).filter_by(requestid=requestid).order_by(FOIRawRequest.version.desc()).first() - return request_schema.dump(request) - - @classmethod - def getLastStatusUpdateDate(cls,requestid,status): - lastupdatedate = None - try: - sql = """select created_at from "FOIRawRequests" - where requestid = :requestid and status = :status - order by version desc limit 1;""" - rs = db.session.execute(text(sql), {'requestid': requestid, 'status': status}) - lastupdatedate = [row[0] for row in rs][0] - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return lastupdatedate - - @classmethod - def getassignmenttransition(cls,requestid): - assignments = [] - try: - sql = """select version, assignedto, status from "FOIRawRequests" - where requestid = :requestid - order by version desc limit 2;""" - rs = db.session.execute(text(sql), {'requestid': requestid}) - for row in rs: - assignments.append({"assignedto": row["assignedto"], "status": row["status"], "version": row["version"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return assignments - - @classmethod - def getappfeeowingrequests(cls): # with the reminder date - appfeeowingrequests = [] - try: - sql = ''' - SELECT * FROM (SELECT DISTINCT ON (requestid) requestid, updated_at, status FROM public."FOIRawRequests" - ORDER BY requestid ASC, version DESC) r - WHERE r.status = :requeststaus - order by r.updated_at asc - ''' - rs = db.session.execute(text(sql), {'requeststaus': StateName.appfeeowing.value}) - appfeeowingrequests = rs - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return appfeeowingrequests - - @classmethod - def getversionforrequest(cls,requestid): - return db.session.query(FOIRawRequest.version).filter_by(requestid=requestid).order_by(FOIRawRequest.version.desc()).first() - - # @classmethod - # def getstatesummary(cls, requestid): - # transitions = [] - # try: - # sql = """select status, version from (select distinct on (status) status, version from "FOIRawRequests" - # where requestid=:requestid order by status, version asc) as fs3 order by version desc""" - # rs = db.session.execute(text(sql), {'requestid': requestid}) - - # for row in rs: - # transitions.append({"status": row["status"], "version": row["version"]}) - # except Exception as ex: - # logging.error(ex) - # raise ex - # finally: - # db.session.close() - # return transitions - - @classmethod - def getstatesummary(cls, requestid): - transitions = [] - try: - sql = """select status, version from "FOIRawRequests" where requestid=:requestid order by version desc""" - rs = db.session.execute(text(sql), {'requestid': requestid}) - _tmp_state = None - for row in rs: - if row["status"] != _tmp_state: - transitions.append({"status": row["status"], "version": row["version"]}) - _tmp_state = row["status"] - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return transitions - - @classmethod - def getstatenavigation(cls, requestid): - _session = db.session - _requeststates = _session.query(FOIRawRequest.status).filter(FOIRawRequest.requestid == requestid).order_by(FOIRawRequest.version.desc()).limit(2) - requeststates = [] - for _requeststate in _requeststates: - requeststates.append(_requeststate[0]) - return requeststates - - @classmethod - def getbasequery(cls, additionalfilter=None, userid=None, isiaorestrictedfilemanager=False, groups=[]): - _session = db.session - - #rawrequests - #subquery for getting the latest version - subquery_maxversion = _session.query(FOIRawRequest.requestid, func.max(FOIRawRequest.version).label('max_version')).group_by(FOIRawRequest.requestid).subquery() - joincondition = [ - subquery_maxversion.c.requestid == FOIRawRequest.requestid, - subquery_maxversion.c.max_version == FOIRawRequest.version, - ] - - requesttype = case([ - (FOIRawRequest.status == StateName.unopened.value, - FOIRawRequest.requestrawdata['requestType']['requestType'].astext), - ], - else_ = FOIRawRequest.requestrawdata['requestType'].astext).label('requestType') - firstname = case([ - (FOIRawRequest.status == StateName.unopened.value, - FOIRawRequest.requestrawdata['contactInfo']['firstName'].astext), - ], - else_ = FOIRawRequest.requestrawdata['firstName'].astext).label('firstName') - lastname = case([ - (FOIRawRequest.status == StateName.unopened.value, - FOIRawRequest.requestrawdata['contactInfo']['lastName'].astext), - ], - else_ = FOIRawRequest.requestrawdata['lastName'].astext).label('lastName') - description = case([ - (FOIRawRequest.status == StateName.unopened.value, - FOIRawRequest.requestrawdata['descriptionTimeframe']['description'].astext), - ], - else_ = FOIRawRequest.requestrawdata['description'].astext).label('description') - recordsearchfromdate = case([ - (FOIRawRequest.status == StateName.unopened.value, - FOIRawRequest.requestrawdata['descriptionTimeframe']['fromDate'].astext), - ], - else_ = FOIRawRequest.requestrawdata['fromDate'].astext).label('recordsearchfromdate') - recordsearchtodate = case([ - (FOIRawRequest.status == StateName.unopened.value, - FOIRawRequest.requestrawdata['descriptionTimeframe']['toDate'].astext), - ], - else_ = FOIRawRequest.requestrawdata['toDate'].astext).label('recordsearchtodate') - duedate = case([ - (FOIRawRequest.status == StateName.unopened.value, - literal(None)), - ], - else_ = FOIRawRequest.requestrawdata['dueDate'].astext).label('duedate') - receiveddate = case([ - (and_(FOIRawRequest.status == StateName.unopened.value, FOIRawRequest.requestrawdata['receivedDate'].is_(None)), - func.to_char(FOIRawRequest.created_at, 'YYYY-mm-DD')), - ], - else_ = FOIRawRequest.requestrawdata['receivedDate'].astext).label('receivedDate') - receiveddateuf = case([ - (and_(FOIRawRequest.status == StateName.unopened.value, FOIRawRequest.requestrawdata['receivedDateUF'].is_(None)), - func.to_char(FOIRawRequest.created_at, 'YYYY-mm-DD HH:MM:SS')), - ], - else_ = FOIRawRequest.requestrawdata['receivedDateUF'].astext).label('receivedDateUF') - - assignedtoformatted = case([ - (and_(FOIAssignee.lastname.isnot(None), FOIAssignee.firstname.isnot(None)), - func.concat(FOIAssignee.lastname, ', ', FOIAssignee.firstname)), - (and_(FOIAssignee.lastname.isnot(None), FOIAssignee.firstname.is_(None)), - FOIAssignee.lastname), - (and_(FOIAssignee.lastname.is_(None), FOIAssignee.firstname.isnot(None)), - FOIAssignee.firstname), - (and_(FOIAssignee.lastname.is_(None), FOIAssignee.firstname.is_(None), FOIRawRequest.assignedgroup.is_(None)), - 'Unassigned'), - ], - else_ = FOIRawRequest.assignedgroup).label('assignedToFormatted') - - axisrequestid = case([ - (FOIRawRequest.axisrequestid.is_(None), - 'U-00' + cast(FOIRawRequest.requestid, String)), - ], - else_ = cast(FOIRawRequest.axisrequestid, String)).label('axisRequestId') - - requestpagecount = case([ - (FOIRawRequest.requestrawdata['axispagecount'].is_(None), - '0'), - ], - else_ = cast(FOIRawRequest.requestrawdata['axispagecount'], String)) - - intakesorting = case([ - (FOIRawRequest.assignedto == None, # Unassigned requests first - literal(None)), - ], - else_ = cast(FOIRawRequest.requestrawdata['receivedDateUF'].astext, TIMESTAMP)).label('intakeSorting') - - isiaorestricted = case([ - (FOIRawRequest.isiaorestricted.is_(None), - False), - ], - else_ = FOIRawRequest.isiaorestricted).label('isiaorestricted') - - subjectcode = case([ - (FOIRawRequest.requestrawdata['subjectCode'].is_(None), - literal(None)), - ], - else_ = cast(FOIRawRequest.requestrawdata['subjectCode'], String)).label('subjectcode') - - selectedcolumns = [ - FOIRawRequest.requestid.label('id'), - FOIRawRequest.version, - FOIRawRequest.sourceofsubmission, - firstname, - lastname, - requesttype, - receiveddate, - receiveddateuf, - FOIRawRequest.status.label('currentState'), - FOIRawRequest.assignedgroup.label('assignedGroup'), - FOIRawRequest.assignedto.label('assignedTo'), - cast(FOIRawRequest.requestid, String).label('idNumber'), - axisrequestid, - cast(requestpagecount, Integer).label('requestpagecount'), - requestpagecount.label('axispagecount'), - literal(None).label('axislanpagecount'), - literal(None).label('recordspagecount'), - literal(None).label('ministryrequestid'), - literal(None).label('assignedministrygroup'), - literal(None).label('assignedministryperson'), - literal(None).label('cfrduedate'), - duedate, - FOIRawRequest.requestrawdata['category'].astext.label('applicantcategory'), - FOIRawRequest.created_at.label('created_at'), - literal(None).label('bcgovcode'), - FOIAssignee.firstname.label('assignedToFirstName'), - FOIAssignee.lastname.label('assignedToLastName'), - literal(None).label('assignedministrypersonFirstName'), - literal(None).label('assignedministrypersonLastName'), - description, - recordsearchfromdate, - recordsearchtodate, - literal(None).label('onBehalfFirstName'), - literal(None).label('onBehalfLastName'), - literal(None).label('defaultSorting'), - intakesorting, - literal(None).label('ministrySorting'), - assignedtoformatted, - literal(None).label('ministryAssignedToFormatted'), - literal(None).label('closedate'), - literal(None).label('onBehalfFormatted'), - literal(None).label('extensions'), - isiaorestricted, - literal(None).label('isministryrestricted'), - subjectcode, - literal(None).label('isoipcreview'), - literal(None).label('oipc_number') - ] - - basequery = _session.query(*selectedcolumns).join(subquery_maxversion, and_(*joincondition)).join(FOIAssignee, FOIAssignee.username == FOIRawRequest.assignedto, isouter=True) - - return FOIRawRequest.handleadditionalfilter(basequery, additionalfilter, userid, isiaorestrictedfilemanager, groups) - - @classmethod - def handleadditionalfilter(cls, basequery, additionalfilter=None, userid=None, isiaorestrictedfilemanager=False, groups=[]): - if additionalfilter: - return FOIRawRequest.addadditionalfilter(basequery, additionalfilter, userid, isiaorestrictedfilemanager, groups) - return FOIRawRequest.noadditionalfilter(basequery, userid, isiaorestrictedfilemanager) - - @classmethod - def noadditionalfilter(cls, basequery, userid=None, isiaorestrictedfilemanager=False): - if(isiaorestrictedfilemanager == True): - return basequery.filter(FOIRawRequest.status.notin_(['Archived'])) - else: - subquery_watchby = FOIRawRequestWatcher.getrequestidsbyuserid(userid) - - return basequery.join( - subquery_watchby, - subquery_watchby.c.requestid == FOIRawRequest.requestid, - isouter=True - ).filter( - and_( - FOIRawRequest.status.notin_(['Archived']), - or_( - FOIRawRequest.isiaorestricted == False, - and_(FOIRawRequest.isiaorestricted == True, FOIRawRequest.assignedto == userid), - and_(FOIRawRequest.isiaorestricted == True, subquery_watchby.c.watchedby == userid)))) - - @classmethod - def addadditionalfilter(cls, basequery, additionalfilter=None, userid=None, isiaorestrictedfilemanager=False, groups=[]): - isprocessingteam = False - - if groups: - isprocessingteam = any(item in ProcessingTeamWithKeycloackGroup.list() for item in groups) - - if(additionalfilter == 'watchingRequests' and userid is not None): - #watchby - subquery_watchby = FOIRawRequestWatcher.getrequestidsbyuserid(userid) - return basequery.join(subquery_watchby, subquery_watchby.c.requestid == FOIRawRequest.requestid).filter(FOIRawRequest.status.notin_(['Archived'])) - elif(additionalfilter == 'myRequests'): - #myrequest - return basequery.filter(and_(FOIRawRequest.status.notin_(['Archived']), FOIRawRequest.assignedto == userid)) - elif(additionalfilter == 'unassignedRequests'): - return basequery.filter(and_(FOIRawRequest.status.notin_(['Archived']), FOIRawRequest.assignedto == None, FOIRawRequest.assignedgroup.in_(tuple(groups)))) - elif (additionalfilter.lower() == 'all'): - if(isiaorestrictedfilemanager == True): - basequery = basequery.filter(FOIRawRequest.status.notin_(['Archived'])) - else: - if isprocessingteam: - basequery = basequery.filter( - and_( - FOIRawRequest.status.notin_(['Archived']), - or_(and_(FOIRawRequest.isiaorestricted == False, FOIRawRequest.assignedgroup.in_(ProcessingTeamWithKeycloackGroup.list()), FOIRawRequest.assignedgroup.in_(tuple(groups))), and_(FOIRawRequest.isiaorestricted == True, FOIRawRequest.assignedto == userid)))) - basequery = basequery.filter( - and_( - FOIRawRequest.status.notin_(['Archived']), - or_(and_(FOIRawRequest.isiaorestricted == False, FOIRawRequest.assignedgroup == "Intake Team"), and_(FOIRawRequest.isiaorestricted == True, FOIRawRequest.assignedto == userid)))) - return basequery.filter(FOIRawRequest.assignedto != None) - else: - if(isiaorestrictedfilemanager == True): - return basequery.filter(FOIRawRequest.status.notin_(['Archived'])) - else: - return basequery.filter( - and_( - FOIRawRequest.status.notin_(['Archived']), - or_(FOIRawRequest.isiaorestricted == False, and_(FOIRawRequest.isiaorestricted == True, FOIRawRequest.assignedto == userid)))) - - @classmethod - def getrequestssubquery(cls, filterfields, keyword, additionalfilter, userid, isiaorestrictedfilemanager, groups): - basequery = FOIRawRequest.getbasequery(additionalfilter, userid, isiaorestrictedfilemanager, groups) - basequery = basequery.filter(FOIRawRequest.status != 'Unopened').filter(FOIRawRequest.status != 'Closed') - #filter/search - if(len(filterfields) > 0 and keyword is not None): - filtercondition = FOIRawRequest.getfilterforrequestssubquery(filterfields, keyword) - return basequery.filter(filtercondition) - else: - return basequery - - @classmethod - def getfilterforrequestssubquery(cls, filterfields, keyword): - keyword = keyword.lower() - - #filter/search - filtercondition = [] - if(keyword != 'restricted'): - for field in filterfields: - if(field == 'idNumber'): - keyword = keyword.replace('u-00', '') - - filtercondition.append(FOIRawRequest.findfield(field).ilike('%'+keyword+'%')) - if(field == 'firstName'): - filtercondition.append(FOIRawRequest.findfield('contactFirstName').ilike('%'+keyword+'%')) - if(field == 'lastName'): - filtercondition.append(FOIRawRequest.findfield('contactLastName').ilike('%'+keyword+'%')) - if(field == 'requestType'): - filtercondition.append(FOIRawRequest.findfield('requestTypeRequestType').ilike('%'+keyword+'%')) - else: - filtercondition.append(FOIRawRequest.isiaorestricted == True) - - return or_(*filtercondition) - - - @classmethod - def getrequestspagination(cls, groups, page, size, sortingitems, sortingorders, filterfields, keyword, additionalfilter, userid, isiaorestrictedfilemanager, usertype, isministryrestrictedfilemanager=False): - #ministry requests - iaoassignee = aliased(FOIAssignee) - ministryassignee = aliased(FOIAssignee) - subquery_ministry_queue = FOIMinistryRequest.getrequestssubquery(groups, filterfields, keyword, additionalfilter, userid, iaoassignee, ministryassignee, 'IAO', isiaorestrictedfilemanager, isministryrestrictedfilemanager) - - #sorting - sortingcondition = FOIRawRequest.getsorting(sortingitems, sortingorders) - #rawrequests - if usertype == "iao" or groups is None: - subquery_rawrequest_queue = FOIRawRequest.getrequestssubquery(filterfields, keyword, additionalfilter, userid, isiaorestrictedfilemanager, groups) - query_full_queue = subquery_rawrequest_queue.union(subquery_ministry_queue) - return query_full_queue.order_by(*sortingcondition).paginate(page=page, per_page=size) - else: - return subquery_ministry_queue.order_by(*sortingcondition).paginate(page=page, per_page=size) - - @classmethod - def findfield(cls, x): - return { - 'firstName': FOIRawRequest.requestrawdata['firstName'].astext, - 'lastName': FOIRawRequest.requestrawdata['lastName'].astext, - 'contactFirstName': FOIRawRequest.requestrawdata['contactInfo']['firstName'].astext, - 'contactLastName': FOIRawRequest.requestrawdata['contactInfo']['lastName'].astext, - 'requestType': FOIRawRequest.requestrawdata['requestType'].astext, - 'requestTypeRequestType': FOIRawRequest.requestrawdata['requestType']['requestType'].astext, - 'idNumber': cast(FOIRawRequest.requestid, String), - 'axisRequestId': cast(FOIRawRequest.axisrequestid, String), - 'axisrequest_number': cast(FOIRawRequest.axisrequestid, String), - 'currentState': FOIRawRequest.status, - 'assignedTo': FOIRawRequest.assignedto, - 'assignedToFirstName': FOIAssignee.firstname, - 'assignedToLastName': FOIAssignee.lastname, - 'receivedDate': FOIRawRequest.requestrawdata['receivedDate'].astext, - 'description': FOIRawRequest.requestrawdata['description'].astext, - 'descriptionDescription': FOIRawRequest.requestrawdata['descriptionTimeframe']['description'].astext, - 'ministry': FOIRawRequest.requestrawdata['selectedMinistries'].astext, - 'ministryMinistry': FOIRawRequest.requestrawdata['ministry']['selectedMinistry'].astext, - 'duedate': FOIRawRequest.requestrawdata['dueDate'].astext, - 'DueDateValue': FOIRawRequest.requestrawdata['dueDate'].astext, - 'DaysLeftValue': FOIRawRequest.requestrawdata['dueDate'].astext, - 'subjectcode': FOIRawRequest.requestrawdata['subjectCode'].astext - }.get(x, cast(FOIRawRequest.requestid, String)) - - @classmethod - def validatefield(cls, x): - validfields = [ - 'firstName', - 'lastName', - 'requestType', - 'idNumber', - 'axisRequestId', - 'requestpagecount', - 'currentState', - 'assignedTo', - 'receivedDate', - 'receivedDateUF', - 'assignedToFirstName', - 'assignedToLastName', - 'duedate', - 'defaultSorting', - 'intakeSorting', - 'ministrySorting', - 'assignedToFormatted', - 'ministryAssignedToFormatted', - 'cfrduedate', - 'applicantcategory', - 'onBehalfFormatted', - 'extensions', - 'isiaorestricted' - ] - if x in validfields: - return True - else: - return False - - @classmethod - def getsorting(cls, sortingitems, sortingorders): - sortingcondition = [] - if(len(sortingitems) > 0 and len(sortingorders) > 0 and len(sortingitems) == len(sortingorders)): - for field in sortingitems: - if(FOIRawRequest.validatefield(field)): - order = sortingorders.pop(0) - if(order == 'desc'): - sortingcondition.append(nullslast(desc(field))) - else: - sortingcondition.append(nullsfirst(asc(field))) - #default sorting - if(len(sortingcondition) == 0): - sortingcondition.append(asc('currentState')) - - #always sort by created_at last to prevent pagination collisions - sortingcondition.append(asc('created_at')) - - return sortingcondition - - - @classmethod - def advancedsearch(cls, params, userid, isiaorestrictedfilemanager=False): - basequery = FOIRawRequest.getbasequery(None, userid, isiaorestrictedfilemanager) - - #filter/search - filtercondition = FOIRawRequest.getfilterforadvancedsearch(params) - searchquery = basequery.filter(and_(*filtercondition)) - - #ministry requests - iaoassignee = aliased(FOIAssignee) - ministryassignee = aliased(FOIAssignee) - subquery_ministry_queue = FOIMinistryRequest.advancedsearchsubquery(params, iaoassignee, ministryassignee, userid, 'IAO', isiaorestrictedfilemanager) - - #sorting - sortingcondition = FOIRawRequest.getsorting(params['sortingitems'], params['sortingorders']) - - #rawrequests - query_full_queue = searchquery.union(subquery_ministry_queue) - return query_full_queue.order_by(*sortingcondition).paginate(page=params['page'], per_page=params['size']) - - @classmethod - def getfilterforadvancedsearch(cls, params): - - #filter/search - filtercondition = [] - includeclosed = False - - #request state: unopened, call for records, etc. - if(len(params['requeststate']) > 0): - requeststatecondition = FOIRawRequest.getfilterforrequeststate(params, includeclosed) - filtercondition.append(requeststatecondition['condition']) - includeclosed = requeststatecondition['includeclosed'] - else: - filtercondition.append(FOIRawRequest.status != StateName.unopened.value) #not return Unopened by default - - #request status: overdue, on time - no due date for unopen & intake in progress, so return all except closed - if(len(params['requeststatus']) > 0 and includeclosed == False): - if(len(params['requeststatus']) == 1 and params['requeststatus'][0] == 'overdue'): - #no rawrequest returned for this case - filtercondition.append(FOIRawRequest.status == 'ReturnNothing') - else: - filtercondition.append(FOIRawRequest.status != StateName.closed.value) - - #request type: personal, general - if(len(params['requesttype']) > 0): - requesttypecondition = FOIRawRequest.getfilterforrequesttype(params) - filtercondition.append(or_(*requesttypecondition)) - - #request flags: restricted, oipc, phased - if(len(params['requestflags']) > 0): - requestflagscondition = FOIRawRequest.getfilterforrequestflags(params) - filtercondition.append(or_(*requestflagscondition)) - - #public body: EDUC, etc. - if(len(params['publicbody']) > 0): - ministrycondition = FOIRawRequest.getfilterforpublicbody(params) - filtercondition.append(ministrycondition) - - #axis request #, raw request #, applicant name, assignee name, request description, subject code - if(len(params['keywords']) > 0 and params['search'] is not None): - searchcondition = FOIRawRequest.getfilterforsearch(params) - filtercondition.append(searchcondition) - - if(params['daterangetype'] is not None): - filterconditionfordate = FOIRawRequest.getfilterfordate(params) - filtercondition += filterconditionfordate - - return filtercondition - - @classmethod - def getfilterfordate(cls, params): - filterconditionfordate = [] - if(params['daterangetype'] == 'closedate'): - #no rawrequest returned for this case - filterconditionfordate.append(FOIRawRequest.requestid < 0) - else: - if(params['fromdate'] is not None): - if(params['daterangetype'] == 'receivedDate'): - #online form submission has no receivedDate in json - using created_at - filterconditionfordate.append( - or_( - and_(FOIRawRequest.requestrawdata['receivedDate'].is_(None), FOIRawRequest.created_at.cast(Date) >= parser.parse(params['fromdate'])), - and_(FOIRawRequest.requestrawdata['receivedDate'].isnot(None), FOIRawRequest.findfield(params['daterangetype']).cast(Date) >= parser.parse(params['fromdate'])), - ) - ) - else: - filterconditionfordate.append(FOIRawRequest.findfield(params['daterangetype']).cast(Date) >= parser.parse(params['fromdate'])) - - if(params['todate'] is not None): - if(params['daterangetype'] == 'receivedDate'): - #online form submission has no receivedDate in json - using created_at - filterconditionfordate.append( - or_( - and_(FOIRawRequest.requestrawdata['receivedDate'].is_(None), FOIRawRequest.created_at.cast(Date) <= parser.parse(params['todate'])), - and_(FOIRawRequest.requestrawdata['receivedDate'].isnot(None), FOIRawRequest.findfield(params['daterangetype']).cast(Date) <= parser.parse(params['todate'])), - ) - ) - else: - filterconditionfordate.append(FOIRawRequest.findfield(params['daterangetype']).cast(Date) <= parser.parse(params['todate'])) - - return filterconditionfordate - - @classmethod - def getfilterforrequeststate(cls, params, includeclosed): - #request state: unopened, call for records, etc. - requeststatecondition = [] - for state in params['requeststate']: - if(state == StateName.closed.name): - requeststatecondition.append(FOIRawRequest.status == StateName.closed.value) - includeclosed = True - elif(state == StateName.redirect.name): - requeststatecondition.append(FOIRawRequest.status == StateName.redirect.value) - elif(state == StateName.unopened.name): - requeststatecondition.append(FOIRawRequest.status == StateName.unopened.value) - elif(state == StateName.intakeinprogress.name): - requeststatecondition.append(FOIRawRequest.status == StateName.intakeinprogress.value) - - if(len(requeststatecondition) == 0): - requeststatecondition.append(FOIRawRequest.status == 'Not Applicable') #searched state does not apply to rawrequests - - return {'condition': or_(*requeststatecondition), 'includeclosed': includeclosed} - - @classmethod - def getfilterforrequesttype(cls, params): - #request type: personal, general - requesttypecondition = [] - for type in params['requesttype']: - requesttypecondition.append(FOIRawRequest.findfield('requestType') == type) - requesttypecondition.append(FOIRawRequest.findfield('requestTypeRequestType') == type) - - return or_(*requesttypecondition) - - @classmethod - def getfilterforrequestflags(cls, params): - # this search will be done by the ministry union, so returns filter with no results - requestflagscondition = [] - for flag in params['requestflags']: - if (flag.lower() == 'restricted'): # no results for raw restricted - requestflagscondition.append(FOIRawRequest.findfield('axisRequestId') == 'thisismeanttoreturnafilterconditionwith0results') - if (flag.lower() == 'oipc'): # no results for raw oipc - requestflagscondition.append(FOIRawRequest.findfield('axisRequestId') == 'thisismeanttoreturnafilterconditionwith0results') - if (flag.lower() == 'phased'): - # requestflagscondition.append(FOIMinistryRequest.findfield('isphasedrelease', iaoassignee, ministryassignee) == True) - continue - return requestflagscondition - - @classmethod - def getfilterforpublicbody(cls, params): - #public body: EDUC, etc. - ministrycondition = [] - for ministry in params['publicbody']: - ministrycondition.append(FOIRawRequest.findfield('ministry').ilike('%"'+ministry+'"%')) - ministrycondition.append(FOIRawRequest.findfield('ministryMinistry').ilike('%"'+ministry+'"%')) - - return or_(*ministrycondition) - - @classmethod - def getfilterforsearch(cls, params): - #axis request #, raw request #, applicant name, assignee name, request description, subject code - if(params['search'] == 'requestdescription'): - return FOIRawRequest.__getfilterfordescription(params) - elif(params['search'] == 'applicantname'): - return FOIRawRequest.__getfilterforapplicantname(params) - elif(params['search'] == 'assigneename'): - return FOIRawRequest.__getfilterforassigneename(params) - elif(params['search'] == 'idnumber'): - return FOIRawRequest.__getfilterforidnumber(params) - elif(params['search'] == 'axisrequest_number'): - return FOIRawRequest.__getfilterforaxisnumber(params) - else: - searchcondition = [] - for keyword in params['keywords']: - searchcondition.append(FOIRawRequest.findfield(params['search']).ilike('%'+keyword+'%')) - return and_(*searchcondition) - - @classmethod - def __getfilterfordescription(cls,params): - searchcondition1 = [] - searchcondition2 = [] - for keyword in params['keywords']: - searchcondition1.append(FOIRawRequest.findfield('description').ilike('%'+keyword+'%')) - searchcondition2.append(FOIRawRequest.findfield('descriptionDescription').ilike('%'+keyword+'%')) - return or_(and_(*searchcondition1), and_(*searchcondition2)) - - @classmethod - def __getfilterforapplicantname(cls,params): - searchcondition1 = [] - searchcondition2 = [] - searchcondition3 = [] - searchcondition4 = [] - for keyword in params['keywords']: - searchcondition1.append(FOIRawRequest.findfield('firstName').ilike('%'+keyword+'%')) - searchcondition2.append(FOIRawRequest.findfield('lastName').ilike('%'+keyword+'%')) - searchcondition3.append(FOIRawRequest.findfield('contactFirstName').ilike('%'+keyword+'%')) - searchcondition4.append(FOIRawRequest.findfield('contactLastName').ilike('%'+keyword+'%')) - return or_(and_(*searchcondition1), and_(*searchcondition2), and_(*searchcondition3), and_(*searchcondition4)) - - @classmethod - def __getfilterforassigneename(cls,params): - searchcondition1 = [] - searchcondition2 = [] - searchcondition3 = [] - for keyword in params['keywords']: - searchcondition1.append(FOIRawRequest.findfield('assignedToFirstName').ilike('%'+keyword+'%')) - searchcondition2.append(FOIRawRequest.findfield('assignedToLastName').ilike('%'+keyword+'%')) - searchcondition3.append(FOIRawRequest.assignedgroup.ilike('%'+keyword+'%')) - return or_(and_(*searchcondition1), and_(*searchcondition2), and_(*searchcondition3)) - - @classmethod - def __getfilterforidnumber(cls,params): - searchcondition = [] - for keyword in params['keywords']: - keyword = keyword.lower() - keyword = keyword.replace('u-00', '') - searchcondition.append(FOIRawRequest.findfield('idNumber').ilike('%'+keyword+'%')) - return and_(*searchcondition) - - @classmethod - def __getfilterforaxisnumber(cls,params): - searchcondition1 = [] - searchcondition2 = [] - for keyword in params['keywords']: - keyword = keyword.lower() - keyword = keyword.replace('u-00', '') - searchcondition1.append(FOIRawRequest.findfield('idNumber').ilike('%'+keyword+'%')) - searchcondition2.append(FOIRawRequest.findfield('axisrequest_number').ilike('%'+keyword+'%')) - return or_(and_(*searchcondition1), and_(*searchcondition2)) - - - @classmethod - def getDistinctAXISRequestIds(cls): - axisrequestids = [] - try: - sql = """select distinct axisrequestid from "FOIRawRequests" where axisrequestid is not null;""" - axisids = db.session.execute(text(sql)) - for axisid in axisids: - axisrequestids.append(axisid[0]) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return axisrequestids - - @classmethod - def getCountOfAXISRequestIdbyAXISRequestId(cls, axisrequestid): - try: - query = db.session.query(func.count(FOIRawRequest.axisrequestid)).filter_by(axisrequestid=axisrequestid) - return query.scalar() - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - - @classmethod - def getmetadata(cls,requestid): - requestdetails = {} - try: - sql = """select requestrawdata ->> 'assignedTo' as assignedTo, - requestrawdata ->> 'assignedToFirstName' as assignedToFirstName, - requestrawdata ->> 'assignedToLastName' as assignedToLastName, - requestrawdata -> 'selectedMinistries'-> 0 ->> 'code' as bcgovcode from "FOIRawRequests" - where requestid = :requestid - order by version desc limit 1;""" - rs = db.session.execute(text(sql), {'requestid': requestid}) - for row in rs: - requestdetails["assignedTo"] = row['assignedto'] - requestdetails["assignedToFirstName"] = row["assignedtofirstname"] - requestdetails["assignedToLastName"] = row["assignedtolastname"] - requestdetails["bcgovcode"] = row["bcgovcode"] - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return requestdetails - - @classmethod - def getlatestsection5pendings(cls): - section5pendings = [] - try: - sql = """SELECT * FROM - (SELECT DISTINCT ON (requestid) requestid, created_at, version, status, axisrequestid - FROM public."FOIRawRequests" - ORDER BY requestid ASC, version DESC) foireqs - WHERE foireqs.status = :requeststatus;""" - rs = db.session.execute(text(sql), {'requeststatus': StateName.section5pending.value}) - for row in rs: - section5pendings.append({"requestid": row["requestid"], "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 - - @classmethod - def getunopenedunactionedrequests(cls, startdate, enddate): - try: - requests = [] - sql = '''select rr.created_at, rr.requestrawdata, rr.requestid, coalesce(p.status, '') as status, - coalesce(p.transaction_number, '') as txnno, - coalesce(p.total::text, '') as fee - from public."FOIRawRequests" rr - join ( - select max(version) as version, requestid from public."FOIRawRequests" - group by requestid - ) mv on mv.requestid = rr.requestid and mv.version = rr.version - left join ( - select request_id, max(payment_id) from public."Payments" - where fee_code_id = 1 - group by request_id - order by request_id - ) mp on mp.request_id = rr.requestid - left join public."Payments" p on p.payment_id = mp.max - where rr.status = 'Unopened' and rr.version = 1 and created_at > :startdate and created_at < :enddate - order by rr.requestid ''' - rs = db.session.execute(text(sql), {'startdate': startdate, 'enddate': enddate}) - for row in rs: - requests.append({ - "requestid": row["requestid"], - "created_at": row["created_at"], - "requestrawdata": row["requestrawdata"], - "paymentstatus": row["status"], - "fee": row["fee"], - "txnno": row["txnno"] - }) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return requests - - @classmethod - def getpotentialactionedmatches(cls, request): - try: - requests = [] - sql = '''select rr.created_at, rr.* from public."FOIRawRequests" rr - join ( - select max(version) as version, requestid from public."FOIRawRequests" - group by requestid - ) mv on mv.requestid = rr.requestid and mv.version = rr.version - where ( - requestrawdata->>'lastName' ilike :lastName - or requestrawdata->>'firstName' ilike :firstName - or requestrawdata->>'email' ilike :email - or requestrawdata->>'address' ilike :address - or requestrawdata->>'phonePrimary' ilike :phonePrimary - or requestrawdata->>'postal' ilike :postal - ) and substring(rr.requestrawdata->>'receivedDateUF', 1, 10) = :receiveddate - order by requestid desc, version desc ''' - rs = db.session.execute(text(sql), { - 'lastName': request['requestrawdata']['contactInfo']['lastName'], - 'firstName': request['requestrawdata']['contactInfo']['firstName'], - 'email': request['requestrawdata']['contactInfoOptions']['email'], - 'address': request['requestrawdata']['contactInfoOptions']['address'], - 'phonePrimary': request['requestrawdata']['contactInfoOptions']['phonePrimary'], - 'postal': request['requestrawdata']['contactInfoOptions']['postal'], - 'receiveddate': request['requestrawdata']['receivedDateUF'][0:10], - }) - for row in rs: - requests.append({"requestid": row["requestid"], "created_at": row["created_at"], "requestrawdata": row["requestrawdata"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return requests - - - -class FOIRawRequestSchema(ma.Schema): - class Meta: - fields = ('requestid', 'requestrawdata', 'status', 'requeststatuslabel', 'notes','created_at','wfinstanceid','version','updated_at','assignedgroup','assignedto','updatedby','createdby','sourceofsubmission','ispiiredacted','assignee.firstname','assignee.lastname', 'axisrequestid', 'axissyncdate', 'linkedrequests', 'closedate','isiaorestricted') diff --git a/historical-search-api/request_api/models/FOIRequestApplicantMappings.py b/historical-search-api/request_api/models/FOIRequestApplicantMappings.py deleted file mode 100644 index 877646a01..000000000 --- a/historical-search-api/request_api/models/FOIRequestApplicantMappings.py +++ /dev/null @@ -1,48 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint -from .db import db, ma -from datetime import datetime -from sqlalchemy.orm import relationship,backref -from .default_method_result import DefaultMethodResult -from .FOIRequests import FOIRequest - -class FOIRequestApplicantMapping(db.Model): - # Name of the table in our database - __tablename__ = 'FOIRequestApplicantMappings' - __table_args__ = ( - ForeignKeyConstraint( - ["foirequest_id", "foirequestversion_id"], ["FOIRequests.foirequestid", "FOIRequests.version"] - ), - ) - # Defining the columns - - foirequestapplicantmappingid = db.Column(db.Integer, primary_key=True,autoincrement=True) - created_at = db.Column(db.DateTime, default=datetime.now) - updated_at = db.Column(db.DateTime, nullable=True) - createdby = db.Column(db.String(120), unique=False, nullable=True) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - - #ForeignKey References - - requestortypeid = db.Column(db.Integer,ForeignKey('RequestorTypes.requestortypeid')) - requestortype = relationship("RequestorType",backref=backref("RequestorTypes"),uselist=False) - - foirequestapplicantid = db.Column(db.Integer,ForeignKey('FOIRequestApplicants.foirequestapplicantid')) - foirequestapplicant = relationship("FOIRequestApplicant",backref=backref("FOIRequestApplicants"),uselist=False) - - foirequest_id =db.Column(db.Integer, db.ForeignKey('FOIRequests.foirequestid')) - foirequestversion_id = db.Column(db.Integer, db.ForeignKey('FOIRequests.version')) - foirequestkey = relationship("FOIRequest",foreign_keys="[FOIRequestApplicantMapping.foirequest_id]") - foirequestversion = relationship("FOIRequest",foreign_keys="[FOIRequestApplicantMapping.foirequestversion_id]") - - @classmethod - def getrequestapplicants(cls,foirequest_id,foirequestversion): - requestapplicant_schema = FOIRequestApplicantMappingSchema(many=True) - _applicantinfos = db.session.query(FOIRequestApplicantMapping).filter(FOIRequestApplicantMapping.foirequest_id == foirequest_id , FOIRequestApplicantMapping.foirequestversion_id == foirequestversion).order_by(FOIRequestApplicantMapping.foirequestapplicantmappingid.asc()).all() - applicantinfos = requestapplicant_schema.dump(_applicantinfos) - return applicantinfos - -class FOIRequestApplicantMappingSchema(ma.Schema): - class Meta: - fields = ('foirequestapplicantmappingid','foirequest.foirequestid','foirequest.version','requestortype.requestortypeid','requestortype.name','foirequestapplicant.foirequestapplicantid','foirequestapplicant.firstname','foirequestapplicant.lastname','foirequestapplicant.middlename','foirequestapplicant.alsoknownas','foirequestapplicant.dob','foirequestapplicant.businessname') - \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestApplicants.py b/historical-search-api/request_api/models/FOIRequestApplicants.py deleted file mode 100644 index 7f4f8f1ad..000000000 --- a/historical-search-api/request_api/models/FOIRequestApplicants.py +++ /dev/null @@ -1,67 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint -from .db import db, ma -from datetime import datetime -from sqlalchemy.orm import relationship,backref -from .default_method_result import DefaultMethodResult -from .FOIRequests import FOIRequest - -class FOIRequestApplicant(db.Model): - # Name of the table in our database - __tablename__ = 'FOIRequestApplicants' - # Defining the columns - foirequestapplicantid = db.Column(db.Integer, primary_key=True,autoincrement=True) - - - firstname = db.Column(db.String(50), unique=False, nullable=True) - middlename = db.Column(db.String(50), unique=False, nullable=True) - lastname = db.Column(db.String(50), unique=False, nullable=True) - - alsoknownas = db.Column(db.String(50), unique=False, nullable=True) - dob = db.Column(db.DateTime, unique=False, nullable=True) - businessname = db.Column(db.String(255), unique=False, nullable=True) - - created_at = db.Column(db.DateTime, default=datetime.now) - updated_at = db.Column(db.DateTime, nullable=True) - createdby = db.Column(db.String(120), unique=False, nullable=True) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - - @classmethod - def saveapplicant(cls,firstname, lastname, middlename, businessname, alsoknownas, dob, userid): - dbquery = db.session.query(FOIRequestApplicant) - dbquery = dbquery.filter_by(firstname=firstname) - applicant = dbquery.filter_by(lastname=lastname) - if (applicant.count() > 0): - _applicant = { - FOIRequestApplicant.updatedby: userid, - FOIRequestApplicant.updated_at: datetime.now(), - FOIRequestApplicant.middlename: middlename, - FOIRequestApplicant.businessname: businessname, - FOIRequestApplicant.alsoknownas: alsoknownas - } - if dob is not None and dob != "": - _applicant[FOIRequestApplicant.dob] = dob - else: - _applicant[FOIRequestApplicant.dob] = None - applicant.update(_applicant) - return DefaultMethodResult(True,'Applicant updated',applicant.first().foirequestapplicantid) - else: - applicant = FOIRequestApplicant() - applicant.createdby = userid - applicant.firstname = firstname - applicant.lastname = lastname - applicant.middlename = middlename - applicant.businessname = businessname - applicant.alsoknownas = alsoknownas - if dob is not None and dob != "": - applicant.dob = dob - else: - applicant.dob = None - db.session.add(applicant) - db.session.commit() - return DefaultMethodResult(True,'Applicant added',applicant.foirequestapplicantid) - -class FOIRequestApplicantSchema(ma.Schema): - class Meta: - fields = ('foirequestapplicantid','firstname','middlename','lastname','alsoknownas','dob','businessname') - \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestCFRFees.py b/historical-search-api/request_api/models/FOIRequestCFRFees.py deleted file mode 100644 index 471860a78..000000000 --- a/historical-search-api/request_api/models/FOIRequestCFRFees.py +++ /dev/null @@ -1,110 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey -from .db import db, ma -from marshmallow import pre_dump, post_dump -from datetime import datetime as datetime2 -from sqlalchemy.orm import relationship,backref -from .default_method_result import DefaultMethodResult -from sqlalchemy.dialects.postgresql import JSON, UUID -from sqlalchemy.sql.expression import distinct -from sqlalchemy import null, text, insert -from .CFRFeeStatus import CFRFeeStatus -import logging - -class FOIRequestCFRFee(db.Model): - # Name of the table in our database - __tablename__ = 'FOIRequestCFRFees' - # Defining the columns - cfrfeeid = db.Column(db.Integer, primary_key=True,autoincrement=True) - ministryrequestid =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.foiministryrequestid')) - ministryrequestversion=db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.version')) - version =db.Column(db.Integer,primary_key=True,nullable=False) - feedata = db.Column(JSON, unique=False, nullable=True) - overallsuggestions = db.Column(db.Text, unique=False, nullable=True) - cfrfeestatusid =db.Column(db.Integer, db.ForeignKey('CFRFeeStatuses.cfrfeestatusid')) - cfrfeestatus = relationship("CFRFeeStatus",backref=backref("CFRFeeStatus"),uselist=False) - created_at = db.Column(db.DateTime, default=datetime2.now) - createdby = db.Column(db.String(120), unique=False, nullable=True) - updated_at = db.Column(db.DateTime, nullable=True) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - cfrformreasonid =db.Column(db.Integer, db.ForeignKey('CFRFormReasons.cfrformreasonid'), nullable=True) - cfrformreason = relationship("CFRFormReason",backref=backref("CFRFormReason"),uselist=False) - - - @classmethod - def createcfrfee(cls, cfrfee, userid)->DefaultMethodResult: - cfrfee.created_at=datetime2.now().isoformat(), - cfrfee.createdby=userid - db.session.add(cfrfee) - db.session.commit() - return DefaultMethodResult(True,'CFR Fee added for ministry request : '+ str(cfrfee.ministryrequestid), cfrfee.cfrfeeid) - - - @classmethod - def getcfrfee(cls, ministryrequestid)->DefaultMethodResult: - comment_schema = FOIRequestCFRFormSchema(many=False) - query = db.session.query(FOIRequestCFRFee).filter_by(ministryrequestid=ministryrequestid).order_by(FOIRequestCFRFee.cfrfeeid.desc(), FOIRequestCFRFee.version.desc()).first() - return comment_schema.dump(query) - - @classmethod - def getapprovedcfrfee(cls, ministryrequestid)->DefaultMethodResult: - comment_schema = FOIRequestCFRFormSchema(many=False) - query = db.session.query(FOIRequestCFRFee).filter_by(ministryrequestid=ministryrequestid, cfrfeestatusid=2).order_by(FOIRequestCFRFee.cfrfeeid.desc(), FOIRequestCFRFee.version.desc()).first() - return comment_schema.dump(query) - - @classmethod - def getcfrfeehistory(cls, ministryrequestid)->DefaultMethodResult: - comment_schema = FOIRequestCFRFormSchema(many=False) - subquery1 = db.session.query(FOIRequestCFRFee.cfrfeeid, db.func.max(FOIRequestCFRFee.version).label('version')).group_by(FOIRequestCFRFee.cfrfeeid).subquery() - subquery2 = db.session.query(FOIRequestCFRFee.cfrfeeid, FOIRequestCFRFee.created_at.label('version_created_at'), FOIRequestCFRFee.createdby.label('version_createdby')).filter_by(version=1).subquery() - query = db.session.query(FOIRequestCFRFee, subquery2.c.version_created_at, subquery2.c.version_createdby).filter_by( - ministryrequestid=ministryrequestid, cfrfeestatusid=2).join( - subquery1, (FOIRequestCFRFee.cfrfeeid == subquery1.c.cfrfeeid) & (FOIRequestCFRFee.version == subquery1.c.version) - ).join(subquery2, (FOIRequestCFRFee.cfrfeeid == subquery2.c.cfrfeeid)).order_by(FOIRequestCFRFee.cfrfeeid.desc()).all() - history = [] - for row in query: - cfrfee = comment_schema.dump(row[0]) - cfrfee['version_created_at'] = row[1] - cfrfee['version_createdby'] = row[2] - history.append(cfrfee) - return history - - @classmethod - def getcfrfeebyid(cls, cfrfeeid) -> DefaultMethodResult: - comment_schema = FOIRequestCFRFormSchema() - query = db.session.query(FOIRequestCFRFee).filter_by(cfrfeeid=cfrfeeid, isactive=True).first() - return comment_schema.dump(query) - - @classmethod - def getstatenavigation(cls, ministryrequestid, cfrfeeid): - _session = db.session - _entries = _session.query(FOIRequestCFRFee).filter_by(ministryrequestid = ministryrequestid, cfrfeeid = cfrfeeid).order_by(FOIRequestCFRFee.version.desc()).limit(2) - requeststates = [] - for _entry in _entries: - if _entry.cfrfeestatusid: - requeststates.append(_entry.cfrfeestatus.description) - return requeststates - - @classmethod - def getfeedataforamountcomparison(cls, ministryrequestid): - _session = db.session - _entries = _session.query(FOIRequestCFRFee).filter(FOIRequestCFRFee.ministryrequestid == ministryrequestid and FOIRequestCFRFee.feedata is not null).order_by(FOIRequestCFRFee.cfrfeeid.desc(), FOIRequestCFRFee.version.desc()).limit(2) - feedata = [] - for _entry in _entries: - feedata.append(_entry.feedata) - return feedata - - @classmethod - def updatecfrfeedatabyid(cls, ministryrequestid, feedata)->DefaultMethodResult: - sq = db.session.query(FOIRequestCFRFee.cfrfeeid, FOIRequestCFRFee.version).filter_by(ministryrequestid=ministryrequestid).order_by(FOIRequestCFRFee.cfrfeeid.desc(), FOIRequestCFRFee.version.desc()).limit(1).subquery() - cfrfee = db.session.query(FOIRequestCFRFee).filter_by(cfrfeeid=sq.c.cfrfeeid, version=sq.c.version) - cfrfee.update({FOIRequestCFRFee.feedata: feedata, FOIRequestCFRFee.updatedby: 'Online Payment', - FOIRequestCFRFee.updated_at: datetime2.now().isoformat()}, synchronize_session=False) - db.session.commit() - return DefaultMethodResult(True,'CFR Fee Data updated for ministry request : '+ str(ministryrequestid), sq.c.cfrfeeid) - -class FOIRequestCFRFormSchema(ma.Schema): - class Meta: - fields = ('cfrfeeid', 'ministryrequestid', 'feedata', 'overallsuggestions', 'created_at','createdby','updated_at','updatedby','cfrfeestatusid', 'cfrfeestatus.name','cfrfeestatus.description','version','cfrformreasonid','cfrformreason.name','cfrformreason.description') - - diff --git a/historical-search-api/request_api/models/FOIRequestComments.py b/historical-search-api/request_api/models/FOIRequestComments.py deleted file mode 100644 index 1bb0bba11..000000000 --- a/historical-search-api/request_api/models/FOIRequestComments.py +++ /dev/null @@ -1,169 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey -from .db import db, ma -from datetime import datetime as datetime2 -from sqlalchemy.orm import relationship,backref -from .default_method_result import DefaultMethodResult -from sqlalchemy.dialects.postgresql import JSON, UUID, insert -from sqlalchemy.sql.expression import distinct -from sqlalchemy import text -import logging -import json -class FOIRequestComment(db.Model): - # Name of the table in our database - __tablename__ = 'FOIRequestComments' - # Defining the columns - commentid = db.Column(db.Integer, primary_key=True,autoincrement=True) - ministryrequestid =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.foiministryrequestid')) - version =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.version')) - comment = db.Column(db.Text, unique=False, nullable=True) - taggedusers = db.Column(JSON, unique=False, nullable=True) - parentcommentid = db.Column(db.Integer, db.ForeignKey('FOIRequestComments.commentid'), nullable=True) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - created_at = db.Column(db.DateTime, default=datetime2.now) - createdby = db.Column(db.String(120), unique=False, nullable=True) - updated_at = db.Column(db.DateTime, nullable=True) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - parentcomment = relationship("FOIRequestComment", backref=backref("FOIRequestComments"), remote_side=[commentid], uselist=False) - - commenttypeid = db.Column(db.Integer, unique=False, nullable=False) - commentsversion = db.Column(db.Integer, nullable=False) - - - @classmethod - def savecomment(cls, commenttypeid, foirequestcomment, version, userid,commentcreatedate=None)->DefaultMethodResult: - commentsversion = 1 - parentcommentid = foirequestcomment["parentcommentid"] if 'parentcommentid' in foirequestcomment else None - taggedusers = foirequestcomment["taggedusers"] if 'taggedusers' in foirequestcomment else None - _createddate = datetime2.now().isoformat() if commentcreatedate is None else commentcreatedate - newcomment = FOIRequestComment(commenttypeid=commenttypeid, ministryrequestid=foirequestcomment["ministryrequestid"], version=version, comment=foirequestcomment["comment"], parentcommentid=parentcommentid, isactive=True, created_at=_createddate, createdby=userid,taggedusers=taggedusers, commentsversion=commentsversion) - db.session.add(newcomment) - db.session.commit() - return DefaultMethodResult(True,'Comment added',newcomment.commentid) - - @classmethod - def deleteextensioncommentsbyministry(cls, ministryid): - db.session.query(FOIRequestComment).filter(FOIRequestComment.ministryrequestid == ministryid, FOIRequestComment.commenttypeid == 2).delete(synchronize_session=False) - db.session.commit() - return DefaultMethodResult(True,'Extensions comments deleted for the ministry ', ministryid) - - @classmethod - def disablecomment(cls, commentid, userid): - dbquery = db.session.query(FOIRequestComment) - comment = dbquery.filter_by(commentid=commentid) - if(comment.count() > 0) : - childcomments = dbquery.filter_by(parentcommentid=commentid, isactive=True) - if (childcomments.count() > 0) : - return DefaultMethodResult(False,'Cannot delete parent comment with replies',commentid) - comment.update({FOIRequestComment.isactive:False, FOIRequestComment.updatedby:userid, FOIRequestComment.updated_at:datetime2.now()}, synchronize_session = False) - db.session.commit() - return DefaultMethodResult(True,'Comment disabled',commentid) - else: - return DefaultMethodResult(True,'No Comment found',commentid) - - @classmethod - def deactivatecomment(cls, commentid, userid, commentsversion): - dbquery = db.session.query(FOIRequestComment) - comment = dbquery.filter_by(commentid=commentid, commentsversion=commentsversion) - if(comment.count() > 0) : - comment.update({FOIRequestComment.isactive:False, FOIRequestComment.updatedby:userid, FOIRequestComment.updated_at:datetime2.now()}, synchronize_session = False) - db.session.commit() - return DefaultMethodResult(True,'Comment deactivated',commentid) - else: - return DefaultMethodResult(True,'No Comment found',commentid) - - @classmethod - def updatecomment(cls, commentid, foirequestcomment, userid): - dbquery = db.session.query(FOIRequestComment) - comment = dbquery.filter_by(commentid=commentid).order_by(FOIRequestComment.commentsversion.desc()).first() - _existingtaggedusers = [] - _commentsversion = 0 - if comment is not None : - _existingtaggedusers = comment.taggedusers - _taggedusers = foirequestcomment["taggedusers"] if 'taggedusers' in foirequestcomment else _existingtaggedusers - _commentsversion = int(comment.commentsversion) - insertstmt = ( - insert(FOIRequestComment). - values( - commentid=commentid, - ministryrequestid=comment.ministryrequestid, - version=comment.version, - comment=foirequestcomment["comment"], - taggedusers=_taggedusers, - parentcommentid=comment.parentcommentid, - isactive=True, - created_at=datetime2.now(), - createdby=userid, - updated_at=datetime2.now(), - updatedby=userid, - commenttypeid=comment.commenttypeid, - commentsversion=_commentsversion + 1 - ) - ) - updatestmt = insertstmt.on_conflict_do_update(index_elements=[FOIRequestComment.commentid, FOIRequestComment.commentsversion], - set_={"ministryrequestid": comment.ministryrequestid,"version":comment.version, "comment": foirequestcomment["comment"], - "taggedusers":_taggedusers, "parentcommentid":comment.parentcommentid, "isactive":True, - "created_at":datetime2.now(), "createdby": userid, "updated_at": datetime2.now(), "updatedby": userid, - "commenttypeid": comment.commenttypeid - } - ) - db.session.execute(updatestmt) - db.session.commit() - return DefaultMethodResult(True,'Updated Comment added',commentid, _existingtaggedusers, _commentsversion) - else: - return DefaultMethodResult(True,'No Comment found',commentid, _existingtaggedusers, _commentsversion) - - @classmethod - def getcomments(cls, ministryrequestid)->DefaultMethodResult: - comment_schema = FOIRequestCommentSchema(many=True) - query = db.session.query(FOIRequestComment).filter_by(ministryrequestid=ministryrequestid, isactive = True).order_by(FOIRequestComment.commentid.desc()).all() - return comment_schema.dump(query) - # comments = [] - # try: - # sql = """SELECT distinct on (commentid) commentid, parentcommentid, commentsversion, ministryrequestid, version, comment, created_at, createdby, - # updated_at, updatedby, isactive, commenttypeid, taggedusers - # FROM public."FOIRequestComments" where ministryrequestid = :ministryrequestid and isactive = true order by commentid, commentsversion desc;""" - # rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) - # for row in rs: - # # comments.append( - # # {"commentid": row["commentid"], "parentcommentid": row["parentcommentid"], "commentsversion": row["commentsversion"], - # # "ministryrequestid": row["ministryrequestid"], "version": row["version"], "comment": row["comment"], - # # "created_at": row["created_at"], "createdby": row["createdby"], "updated_at": row["updated_at"], "updatedby": row["updatedby"], - # # "isactive": row["isactive"], "commenttypeid": row["commenttypeid"], "taggedusers": row["taggedusers"] - # # } - # # ) - # comments.append(dict(row)) - # except Exception as ex: - # logging.error(ex) - # raise ex - # finally: - # db.session.close() - # return comments - - @classmethod - def getcommentbyid(cls, commentid) -> DefaultMethodResult: - comment_schema = FOIRequestCommentSchema() - query = db.session.query(FOIRequestComment).filter_by(commentid=commentid, isactive=True).first() - return comment_schema.dump(query) - - @classmethod - def getcommentusers(cls, commentid): - users = [] - try: - sql = """select commentid, createdby, taggedusers from ( - select commentid, commenttypeid, createdby, taggedusers from "FOIRequestComments" frc where commentid = (select parentcommentid from "FOIRequestComments" frc where commentid=:commentid) and isactive = true - union all - select commentid, commenttypeid, createdby, taggedusers from "FOIRequestComments" frc where commentid <> :commentid and parentcommentid = (select parentcommentid from "FOIRequestComments" frc where commentid=:commentid) and isactive = true - ) cmt where commenttypeid =1""" - rs = db.session.execute(text(sql), {'commentid': commentid}) - for row in rs: - users.append({"commentid": row["commentid"], "createdby": row["createdby"], "taggedusers": row["taggedusers"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return users -class FOIRequestCommentSchema(ma.Schema): - class Meta: - fields = ('commentid', 'ministryrequestid', 'parentcommentid','comment', 'commenttypeid','commenttype','isactive','created_at','createdby','updated_at','updatedby','taggedusers', 'parentcomment.taggedusers', 'commentsversion') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestContactInformation.py b/historical-search-api/request_api/models/FOIRequestContactInformation.py deleted file mode 100644 index be1b7d6f3..000000000 --- a/historical-search-api/request_api/models/FOIRequestContactInformation.py +++ /dev/null @@ -1,51 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint -from .db import db, ma -from datetime import datetime -from sqlalchemy.orm import relationship,backref -from .default_method_result import DefaultMethodResult -from .FOIRequests import FOIRequest - -class FOIRequestContactInformation(db.Model): - # Name of the table in our database - __tablename__ = 'FOIRequestContactInformation' - __table_args__ = ( - ForeignKeyConstraint( - ["foirequest_id", "foirequestversion_id"], ["FOIRequests.foirequestid", "FOIRequests.version"] - ), - ) - # Defining the columns - foirequestcontactid = db.Column(db.Integer, primary_key=True,autoincrement=True) - - contactinformation = db.Column(db.String(500), unique=False, nullable=False) - dataformat = db.Column(db.String(40), unique=False, nullable=True) - - - created_at = db.Column(db.DateTime, default=datetime.now) - updated_at = db.Column(db.DateTime, nullable=True) - createdby = db.Column(db.String(120), unique=False, nullable=True) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - - #ForeignKey References - - contacttypeid = db.Column(db.Integer,ForeignKey('ContactTypes.contacttypeid')) - contacttype = relationship("ContactType",backref=backref("ContactTypes"),uselist=False) - - foirequest_id =db.Column(db.Integer, db.ForeignKey('FOIRequests.foirequestid')) - foirequestversion_id = db.Column(db.Integer, db.ForeignKey('FOIRequests.version')) - foirequestkey = relationship("FOIRequest",foreign_keys="[FOIRequestContactInformation.foirequest_id]") - foirequestversion = relationship("FOIRequest",foreign_keys="[FOIRequestContactInformation.foirequestversion_id]") - - - @classmethod - def getrequestcontactinformation(cls,foirequest_id,foirequestversion): - requestcontact_schema = FOIRequestContactInformationSchema(many=True) - _contactinfos = db.session.query(FOIRequestContactInformation).filter(FOIRequestContactInformation.foirequest_id == foirequest_id , FOIRequestContactInformation.foirequestversion_id == foirequestversion).order_by(FOIRequestContactInformation.foirequestcontactid.asc()).all() - contactinfos = requestcontact_schema.dump(_contactinfos) - return contactinfos - - -class FOIRequestContactInformationSchema(ma.Schema): - class Meta: - fields = ('foirequestcontactid','contactinformation','dataformat','contacttype.contacttypeid','contacttype.name','foirequest.foirequestid','foirequest.version') - \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestExtensionDocumentMappings.py b/historical-search-api/request_api/models/FOIRequestExtensionDocumentMappings.py deleted file mode 100644 index 3c0965ef3..000000000 --- a/historical-search-api/request_api/models/FOIRequestExtensionDocumentMappings.py +++ /dev/null @@ -1,60 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint -from .db import db, ma -from datetime import datetime -from sqlalchemy.orm import relationship,backref -from .default_method_result import DefaultMethodResult -from sqlalchemy.sql.expression import distinct -from sqlalchemy import or_,and_,text - -class FOIRequestExtensionDocumentMapping(db.Model): - # Name of the table in our database - __tablename__ = 'FOIRequestExtensionDocumentMapping' - - # Defining the columns - foirequestextensiondocumentid = db.Column(db.Integer, primary_key=True,autoincrement=True) - - created_at = db.Column(db.DateTime, default=datetime.now) - updated_at = db.Column(db.DateTime, nullable=True) - createdby = db.Column(db.String(120), unique=False, nullable=False) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - - #ForeignKey References - foirequestextensionid =db.Column(db.Integer, ForeignKey('FOIRequestExtensions.foirequestextensionid')) - extensionversion = db.Column(db.Integer, ForeignKey('FOIRequestExtensions.version')) - foiministrydocumentid =db.Column(db.Integer, ForeignKey('FOIMinistryRequestDocuments.foiministrydocumentid')) - - @classmethod - def getextensiondocument(cls,foirequestextensiondocumentid): - document_schema = FOIRequestExtensionDocumentMappingSchema() - request = db.session.query(FOIRequestExtensionDocumentMapping).filter_by(foirequestextensiondocumentid=foirequestextensiondocumentid) - return document_schema.dump(request) - - @classmethod - def getextensiondocuments(cls,foirequestextensionid, extensionversion): - document_schema = FOIRequestExtensionDocumentMappingSchema(many=True) - request = db.session.query(FOIRequestExtensionDocumentMapping).filter(FOIRequestExtensionDocumentMapping.foirequestextensionid == foirequestextensionid, FOIRequestExtensionDocumentMapping.extensionversion == extensionversion).all() - return document_schema.dump(request) - - @classmethod - def saveextensiondocument(cls, extensionid, documents, version, userid): - newdocuments = [] - for document in documents: - createuserid = document['createdby'] if 'createdby' in document and document['createdby'] is not None else userid - createdat = document['created_at'] if 'created_at' in document and document['created_at'] is not None else datetime.now() - newextensiondocument = FOIRequestExtensionDocumentMapping( - foirequestextensionid=extensionid, - extensionversion=version, - foiministrydocumentid=document["foiministrydocumentid"], - created_at=createdat, - createdby=createuserid - ) - newdocuments.append(newextensiondocument) - db.session.add_all(newdocuments) - db.session.commit() - return DefaultMethodResult(True,'Extension Document Mapping created') - -class FOIRequestExtensionDocumentMappingSchema(ma.Schema): - class Meta: - fields = ('foirequestextensiondocumentid', 'foirequestextensionid', 'foiministrydocumentid', 'extensionversion','created_at','createdby','updated_at','updatedby') - \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestExtensions.py b/historical-search-api/request_api/models/FOIRequestExtensions.py deleted file mode 100644 index fea3ee86b..000000000 --- a/historical-search-api/request_api/models/FOIRequestExtensions.py +++ /dev/null @@ -1,189 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint -from .db import db, ma -from datetime import datetime -from sqlalchemy.orm import relationship,backref -from .default_method_result import DefaultMethodResult -from sqlalchemy.sql.expression import distinct -from sqlalchemy import or_,and_,text -import logging -class FOIRequestExtension(db.Model): - # Name of the table in our database - __tablename__ = 'FOIRequestExtensions' - - # Defining the columns - foirequestextensionid = db.Column(db.Integer, primary_key=True,autoincrement=True) - extendedduedays =db.Column(db.Integer, nullable=True) - extendedduedate = db.Column(db.DateTime, nullable=True) - decisiondate = db.Column(db.DateTime, nullable=True) - approvednoofdays =db.Column(db.Integer, nullable=True) - version =db.Column(db.Integer, nullable=True) - isactive = db.Column(db.Boolean, unique=False, nullable=False,default=True) - - created_at = db.Column(db.DateTime, default=datetime.now) - updated_at = db.Column(db.DateTime, nullable=True) - createdby = db.Column(db.String(120), unique=False, nullable=False) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - - #ForeignKey References - foiministryrequest_id =db.Column(db.Integer, ForeignKey('FOIMinistryRequests.foiministryrequestid')) - foiministryrequestversion_id = db.Column(db.Integer, ForeignKey('FOIMinistryRequests.version')) - - extensionstatusid =db.Column(db.Integer, unique=False, nullable=False) - extensionreasonid =db.Column(db.Integer, unique=False, nullable=False) - - extensiondocuments = relationship('FOIRequestExtensionDocumentMapping', primaryjoin="and_(FOIRequestExtension.foirequestextensionid==FOIRequestExtensionDocumentMapping.foirequestextensionid, " - "FOIRequestExtension.version==FOIRequestExtensionDocumentMapping.extensionversion)") - - @classmethod - def getextension(cls,foirequestextensionid): - document_schema = FOIRequestExtensionSchema() - request = db.session.query(FOIRequestExtension).filter_by(foirequestextensionid=foirequestextensionid).order_by(FOIRequestExtension.version.desc()).first() - return document_schema.dump(request) - - @classmethod - def saveextension(cls,ministryrequestid,ministryrequestversion, extension, extensionreason, userid, newduedate=None): - - createuserid = extension['createdby'] if 'createdby' in extension and extension['createdby'] is not None else userid - createdat = extension['created_at'] if 'created_at' in extension and extension['created_at'] is not None else datetime.now() - approveddate = extension['approveddate'] if 'approveddate' in extension else None - denieddate = extension['denieddate'] if 'denieddate' in extension else None - decisiondate = approveddate if approveddate else denieddate - approvednoofdays = extension['approvednoofdays'] if 'approvednoofdays' in extension else None - - if 'extensiontype' in extensionreason and extensionreason['extensiontype'] == 'Public Body': - extensionstatusid = 2 - elif 'extensionstatusid' in extension: - extensionstatusid = extension['extensionstatusid'] - else: - extensionstatusid = 1 - - newextension = FOIRequestExtension( - extensionreasonid=extension['extensionreasonid'], - extendedduedays=extension['extendedduedays'], - extendedduedate=extension['extendedduedate'], - decisiondate=decisiondate, - approvednoofdays=approvednoofdays, - extensionstatusid=extensionstatusid, - version=1, - isactive=True, - foiministryrequest_id=ministryrequestid, - foiministryrequestversion_id=ministryrequestversion, - created_at=createdat, - createdby=createuserid) - db.session.add(newextension) - db.session.commit() - return DefaultMethodResult(True,'Extension created', newextension.foirequestextensionid, newduedate) - - @classmethod - def saveextensions(cls, newextensions): - if(len(newextensions) > 0): - db.session.add_all(newextensions) - db.session.commit() - extensionids = [] - for extension in newextensions: - extensionids.append(extension.foirequestextensionid) - return DefaultMethodResult(True,'Extensions created',-1,extensionids) - else: - return DefaultMethodResult(True,'No Extensions to add ') - - @classmethod - def createextensionversion(cls,ministryrequestid,ministryrequestversion, extension, userid): - # if 'document' in extension: - # newextensiondocument = - newextesion = FOIRequestExtension( foirequestextensionid=extension["foirequestextensionid"], extensionreasonid=extension['extensionreasonid'], extensionstatusid=extension['extensionstatusid'], extendedduedays=extension["extendedduedays"], extendedduedate=extension["extendedduedate"], decisiondate=extension["decisiondate"], approvednoofdays=extension["approvednoofdays"], version=extension["version"], isactive=extension["isactive"], foiministryrequest_id=ministryrequestid, foiministryrequestversion_id=ministryrequestversion, createdby=userid) - db.session.add(newextesion) - db.session.commit() - return DefaultMethodResult(True,'New Extension version created', newextesion.foirequestextensionid) - - @classmethod - def getextensions(cls,ministryrequestid,ministryrequestversion): - extensions = [] - try: - sql = 'SELECT * FROM (SELECT DISTINCT ON (foirequestextensionid) foirequestextensionid, fre.extensionreasonid, er.reason, er.extensiontype, fre.extensionstatusid, es.name, extendedduedays, extendedduedate, decisiondate, approvednoofdays, fre.isactive, created_at , createdby, fre.version FROM "FOIRequestExtensions" fre INNER JOIN "ExtensionReasons" er ON fre.extensionreasonid = er.extensionreasonid INNER JOIN "ExtensionStatuses" es ON fre.extensionstatusid = es.extensionstatusid where foiministryrequest_id =:ministryrequestid and foiministryrequestversion_id = :ministryrequestversion ORDER BY foirequestextensionid, version DESC) AS list ORDER BY extensionstatusid ASC, created_at DESC' - rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid, 'ministryrequestversion':ministryrequestversion}) - for row in rs: - if row["isactive"] == True: - extensions.append(dict(row)) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return extensions - - @classmethod - def getextensionscount(cls,ministryrequestid): - extensioncount = 0 - try: - sql = """SELECT - count(*) as extensions_count - FROM - ( - SELECT - DISTINCT ON (foirequestextensionid) foirequestextensionid, - fre.extensionstatusid - FROM - "FOIRequestExtensions" fre - INNER JOIN "ExtensionStatuses" es ON fre.extensionstatusid = es.extensionstatusid - WHERE foiministryrequest_id = :ministryrequestid - AND es.extensionstatusid = 2 - AND fre.isactive is True - ) AS list """ - rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) - for row in rs: - extensioncount = row["extensions_count"] - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return extensioncount - - @classmethod - def getlatestapprovedextension(cls, extensionid, ministryrequestid, ministryrequestversion): - extension_schema = FOIRequestExtensionSchema() - request = db.session.query(FOIRequestExtension).filter(FOIRequestExtension.foirequestextensionid != extensionid, FOIRequestExtension.foiministryrequest_id == ministryrequestid, FOIRequestExtension.foiministryrequestversion_id == ministryrequestversion, FOIRequestExtension.extensionstatusid == 2).order_by(FOIRequestExtension.created_at.desc()).first() - return extension_schema.dump(request) - - @classmethod - def getversionforextension(cls,extensionid): - return db.session.query(FOIRequestExtension.version).filter_by(foirequestextensionid=extensionid).order_by(FOIRequestExtension.version.desc()).first() - - @classmethod - def getextensionforversion(cls, foirequestextensionid, version): - extension_schema = FOIRequestExtensionSchema() - extension = db.session.query(FOIRequestExtension).filter(FOIRequestExtension.foirequestextensionid == foirequestextensionid, FOIRequestExtension.version == version).order_by(FOIRequestExtension.version.desc()).first() - return extension_schema.dump(extension) - - @classmethod - def deleteextensionbyministryid(cls, ministryrequestid, userid): - db.session.query(FOIRequestExtension).filter(FOIRequestExtension.foiministryrequest_id == ministryrequestid).update({"isactive": False, "updated_at": datetime.now(),"updatedby": userid}, synchronize_session=False) - db.session.commit() - return DefaultMethodResult(True,'Extensions disabled for the ministry',ministryrequestid) - - @classmethod - def disableextension(cls, foirequestextensionid, userid): - db.session.query(FOIRequestExtension).filter(FOIRequestExtension.foirequestextensionid == foirequestextensionid).update({"isactive": False, "updated_at": datetime.now(),"updatedby": userid}, synchronize_session=False) - db.session.commit() - return DefaultMethodResult(True,'Extensions disabled for extension ',foirequestextensionid) - - @classmethod - def disableoldversions(cls, version, ministryrequestid, userid): - db.session.query(FOIRequestExtension).filter(FOIRequestExtension.foiministryrequest_id == ministryrequestid, FOIRequestExtension.foiministryrequestversion_id != version, FOIRequestExtension.isactive == True).update({"isactive": False, "updated_at": datetime.now(),"updatedby": userid}, synchronize_session=False) - db.session.commit() - return DefaultMethodResult(True,'Previous extensions disabled for ministry request ',ministryrequestid) - - @classmethod - def disableextensions(cls, foirequestextensionids, userid): - if(len(foirequestextensionids) > 0): - db.session.query(FOIRequestExtension).filter(FOIRequestExtension.foirequestextensionid.in_(foirequestextensionids)).update({"isactive": False, "updated_at": datetime.now(),"updatedby": userid}, synchronize_session=False) - db.session.commit() - return DefaultMethodResult(True,'Extensions disabled for extension ids ','',foirequestextensionids) - else: - return DefaultMethodResult(True,'No Extensions to disable ') - -class FOIRequestExtensionSchema(ma.Schema): - class Meta: - fields = ('foirequestextensionid', 'extensionreasonid', 'extensionstatusid', 'foiministryrequest_id', 'foiministryrequestversion_id', 'extendedduedays', 'extendedduedate', 'decisiondate', 'approvednoofdays', 'version', 'isactive') - \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestNotificationDashboard.py b/historical-search-api/request_api/models/FOIRequestNotificationDashboard.py deleted file mode 100644 index 952492eba..000000000 --- a/historical-search-api/request_api/models/FOIRequestNotificationDashboard.py +++ /dev/null @@ -1,41 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey -from .db import db, ma -from datetime import datetime as datetime2, timezone -from sqlalchemy.orm import relationship,backref -from .default_method_result import DefaultMethodResult -from sqlalchemy.dialects.postgresql import JSON, UUID -from sqlalchemy.sql.expression import distinct -from sqlalchemy import text -from sqlalchemy.sql.sqltypes import DateTime, String, Date -from sqlalchemy.orm import relationship, backref, aliased -from sqlalchemy import insert, and_, or_, text, func, literal, cast, asc, desc, case, nullsfirst, nullslast, TIMESTAMP, extract -from .FOIRequestNotificationUsers import FOIRequestNotificationUser -from .FOIRawRequestNotificationUsers import FOIRawRequestNotificationUser - - -class FOIRequestNotificationDashboard: - - @classmethod - def getiaoeventpagination(cls, groups, page, size, sortingitems, sortingorders, filterfields, keyword, additionalfilter, userid, isiaorestrictedfilemanager, isministryrestrictedfilemanager=False): - #ministry requests - subquery_ministry_queue = FOIRequestNotificationUser.geteventsubquery(groups, filterfields, keyword, additionalfilter, userid, 'IAO', isiaorestrictedfilemanager, isministryrestrictedfilemanager) - - #sorting - sortingcondition = FOIRawRequestNotificationUser.getsorting(sortingitems, sortingorders) - #if "Intake Team" in groups or groups is None: - subquery_rawrequest_queue = FOIRawRequestNotificationUser.getrequestssubquery(groups, filterfields, keyword, additionalfilter, userid, isiaorestrictedfilemanager) - query_full_queue = subquery_rawrequest_queue.union_all(subquery_ministry_queue) - return query_full_queue.order_by(*sortingcondition).paginate(page=page, per_page=size) - #else: - # return subquery_ministry_queue.order_by(*sortingcondition).paginate(page=page, per_page=size) - - @classmethod - def getministryeventpagination(cls, group, page, size, sortingitems, sortingorders, filterfields, keyword, additionalfilter, userid,isiaorestrictedfilemanager, isministryrestrictedfilemanager): - requestby = 'Ministry' - - subquery = FOIRequestNotificationUser.geteventsubquery(group, filterfields, keyword, additionalfilter, userid, requestby, isiaorestrictedfilemanager, isministryrestrictedfilemanager) - sortingcondition = FOIRequestNotificationUser.getsorting(sortingitems, sortingorders) - - return subquery.order_by(*sortingcondition).paginate(page=page, per_page=size) - \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestNotificationUsers.py b/historical-search-api/request_api/models/FOIRequestNotificationUsers.py deleted file mode 100644 index 9e80691ca..000000000 --- a/historical-search-api/request_api/models/FOIRequestNotificationUsers.py +++ /dev/null @@ -1,338 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey -from .db import db, ma -from datetime import datetime as datetime2 -from sqlalchemy.orm import relationship,backref, aliased -from .default_method_result import DefaultMethodResult -from sqlalchemy.dialects.postgresql import JSON, UUID -from sqlalchemy.sql.functions import coalesce -from sqlalchemy import or_, and_, text, func, literal, cast, case, nullslast, nullsfirst, desc, asc, extract -from sqlalchemy.sql.sqltypes import String -from sqlalchemy.sql.expression import distinct -from sqlalchemy import text -import logging -import json -f = open('common/notificationtypes.json', encoding="utf8") -notificationtypes_cache = json.load(f) - - -from .FOIRequestApplicantMappings import FOIRequestApplicantMapping -from .FOIRequestApplicants import FOIRequestApplicant -from .FOIRequestStatus import FOIRequestStatus -from .ApplicantCategories import ApplicantCategory -from .FOIRequestWatchers import FOIRequestWatcher -from .FOIRequests import FOIRequest -from .FOIRestrictedMinistryRequests import FOIRestrictedMinistryRequest -from .ProgramAreas import ProgramArea -from request_api.utils.enums import ProcessingTeamWithKeycloackGroup, IAOTeamWithKeycloackGroup -from .FOIAssignees import FOIAssignee -from .FOIRequestExtensions import FOIRequestExtension -from request_api.utils.enums import RequestorType -from request_api.utils.enums import StateName -from .FOIMinistryRequestSubjectCodes import FOIMinistryRequestSubjectCode -from .SubjectCodes import SubjectCode -from .FOIRequestStatus import FOIRequestStatus - -from .FOIRawRequestNotifications import FOIRawRequestNotification -from .FOIRawRequestNotificationUsers import FOIRawRequestNotificationUser -from request_api.models.views.FOINotifications import FOINotifications -from request_api.models.views.FOIRequests import FOIRequests - - -class FOIRequestNotificationUser(db.Model): - # Name of the table in our database - __tablename__ = 'FOIRequestNotificationUsers' - # Defining the columns - notificationuserid = db.Column(db.Integer, primary_key=True,autoincrement=True) - notificationid = db.Column(db.Integer,ForeignKey('FOIRequestNotifications.notificationid')) - userid = db.Column(db.String(100), unique=False, nullable=True) - isdeleted = db.Column(db.Boolean, unique=False, nullable=True, default=False) - created_at = db.Column(db.DateTime, default=datetime2.now) - createdby = db.Column(db.String(120), unique=False, nullable=True) - updated_at = db.Column(db.DateTime, nullable=True) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - notificationusertypelabel = db.Column(db.String(100),nullable=False) - notificationusertypeid = db.Column(db.Integer,nullable=False) - - - @classmethod - def dismissnotification(cls, notificationuserid, userid='system'): - exists = bool(db.session.query(FOIRequestNotificationUser.notificationuserid).filter_by(notificationuserid=notificationuserid).first()) - if exists == False: - return DefaultMethodResult(False,'Invalid ID',notificationuserid) - db.session.query(FOIRequestNotificationUser).filter(FOIRequestNotificationUser.notificationuserid == notificationuserid).update({FOIRequestNotificationUser.isdeleted: True, FOIRequestNotificationUser.updatedby: userid, - FOIRequestNotificationUser.updated_at: datetime2.now()}) - db.session.commit() - return DefaultMethodResult(True,'Notification deleted',notificationuserid) - - @classmethod - def dismissnotificationbyuser(cls, userid): - db.session.query(FOIRequestNotificationUser).filter(FOIRequestNotificationUser.userid == userid).update({FOIRequestNotificationUser.isdeleted: True, FOIRequestNotificationUser.updatedby: userid, - FOIRequestNotificationUser.updated_at: datetime2.now()}) - db.session.commit() - return DefaultMethodResult(True,'Notifications deleted for user',userid) - - @classmethod - def dismissnotificationbyuserandtype(cls, userid, notificationusertypelabel): - db.session.query(FOIRequestNotificationUser).filter(FOIRequestNotificationUser.userid == userid, FOIRequestNotificationUser.notificationusertypelabel == notificationusertypelabel).update({FOIRequestNotificationUser.isdeleted: True, FOIRequestNotificationUser.updatedby: userid, - FOIRequestNotificationUser.updated_at: datetime2.now()}) - db.session.commit() - return DefaultMethodResult(True,'Notifications deleted for user',userid) - - @classmethod - def dismissbynotificationid(cls, notificationids, userid='system'): - db.session.query(FOIRequestNotificationUser).filter(FOIRequestNotificationUser.notificationid.in_(notificationids)).update({FOIRequestNotificationUser.isdeleted: True, FOIRequestNotificationUser.updatedby: userid, - FOIRequestNotificationUser.updated_at: datetime2.now()}, synchronize_session=False) - db.session.commit() - return DefaultMethodResult(True,'Notifications deleted for id',notificationids) - - @classmethod - def getnotificationsbyid(cls, notificationuserid): - notifications = [] - try: - sql = """select notificationid, count(1) as relcount from "FOIRequestNotificationUsers" frnu - where notificationid in (select notificationid from "FOIRequestNotificationUsers" frnu where notificationuserid = :notificationuserid) group by notificationid """ - rs = db.session.execute(text(sql), {'notificationuserid': notificationuserid}) - - for row in rs: - notifications.append({"notificationid": row["notificationid"], "count" : row["relcount"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return notifications - - @classmethod - def getnotificationsbyuser(cls, userid): - notifications = [] - try: - sql = """select notificationid, count(1) as relcount from "FOIRequestNotificationUsers" frnu - where notificationid in (select notificationid from "FOIRequestNotificationUsers" frnu where userid = :userid) group by notificationid """ - rs = db.session.execute(text(sql), {'userid': userid}) - for row in rs: - notifications.append({"notificationid": row["notificationid"], "count" : row["relcount"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return notifications - - @classmethod - def getnotificationsbyuserandtype(cls, userid, notificationusertypelabel): - notifications = [] - try: - sql = """select notificationid, count(1) as relcount from "FOIRequestNotificationUsers" frnu - where notificationid in (select notificationid from "FOIRequestNotificationUsers" frnu where userid = :userid and notificationusertypelabel = :notificationusertypelabel) group by notificationid """ - rs = db.session.execute(text(sql), {'userid': userid, 'notificationusertypelabel':notificationusertypelabel}) - for row in rs: - notifications.append({"notificationid": row["notificationid"], "count" : row["relcount"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return notifications - - @classmethod - def getnotificationidsbyuserandid(cls, userid, notificationids): - ids = [] - try: - sql = """select notificationid from "FOIRequestNotificationUsers" where userid = :userid and notificationid = ANY(:notificationids) """ - rs = db.session.execute(text(sql), {'userid': userid, 'notificationids': notificationids}) - for row in rs: - ids.append(row["notificationid"]) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return ids - - - # Begin of Dashboard functions - @classmethod - def geteventsubquery(cls, groups, filterfields, keyword, additionalfilter, userid, requestby='IAO', isiaorestrictedfilemanager=False, isministryrestrictedfilemanager=False): - #for queue/dashboard - _session = db.session - - #aliase for getting ministry restricted flag from FOIRestrictedMinistryRequest - ministry_restricted_requests = aliased(FOIRestrictedMinistryRequest) - - #ministry filter for group/team - ministryfilter = FOIRequestNotificationUser.getgroupfilters(groups) - - #filter/search - if(len(filterfields) > 0 and keyword is not None): - filtercondition = [] - if(keyword != "restricted"): - for field in filterfields: - _keyword = FOIRequestNotificationUser.getfilterkeyword(keyword, field) - filtercondition.append(FOIRequestNotificationUser.findfield(field).ilike('%'+_keyword+'%')) - else: - if(requestby == 'IAO'): - filtercondition.append(FOIRestrictedMinistryRequest.isrestricted == True) - else: - filtercondition.append(ministry_restricted_requests.isrestricted == True) - - selectedcolumns = [ - FOIRequests.crtid.label('crtid'), - FOIRequests.axisrequestid.label('axisRequestId'), - FOIRequests.rawrequestid.label('rawrequestid'), - FOIRequests.foirequest_id.label('requestid'), - FOIRequests.foiministryrequestid.label('ministryrequestid'), - FOIRequests.status.label('status'), - FOIRequests.assignedtoformatted.label('assignedToFormatted'), - FOIRequests.ministryassignedtoformatted.label('ministryAssignedToFormatted'), - FOIRequests.description, - FOINotifications.notificationtype.label('notificationtype'), - FOINotifications.notification.label('notification'), - FOINotifications.created_at.label('createdat'), - FOINotifications.createdatformatted.label('createdatformatted'), - FOINotifications.userformatted.label('userFormatted'), - FOINotifications.creatorformatted.label('creatorFormatted'), - FOINotifications.id.label('id') - ] - - basequery = _session.query( - *selectedcolumns - ).join( - FOIRestrictedMinistryRequest, - and_( - FOIRestrictedMinistryRequest.ministryrequestid == FOIRequests.foiministryrequestid, - FOIRestrictedMinistryRequest.type == 'iao', - FOIRestrictedMinistryRequest.isactive == True), - isouter=True - ).join( - ministry_restricted_requests, - and_( - ministry_restricted_requests.ministryrequestid == FOIRequests.foiministryrequestid, - ministry_restricted_requests.type == 'ministry', - ministry_restricted_requests.isactive == True), - isouter=True - ).join( - FOINotifications, - and_(FOINotifications.axisnumber == FOIRequests.axisrequestid), - ).filter(FOIRequests.requeststatuslabel != StateName.closed.name) - - if(additionalfilter == 'watchingRequests'): - #watchby - #activefilter = and_(FOIMinistryRequest.isactive == True, FOIRequestStatus.isactive == True) - - subquery_watchby = FOIRequestWatcher.getrequestidsbyuserid(userid) - dbquery = basequery.join(subquery_watchby, subquery_watchby.c.ministryrequestid == FOIRequests.foiministryrequestid)#.filter(activefilter) - elif(additionalfilter == 'myRequests'): - #myrequest - if(requestby == 'IAO'): - dbquery = basequery.filter(or_(and_(FOIRequests.assignedto == userid, ministryfilter),and_(FOINotifications.userid == userid, FOINotifications.notificationtypelabel == notificationtypes_cache['taggedusercomments']['notificationtypelabel']))) - else: - dbquery = basequery.filter(or_(and_(FOIRequests.assignedministryperson == userid, ministryfilter),and_(FOINotifications.userid == userid, FOINotifications.notificationtypelabel == notificationtypes_cache['taggedusercomments']['notificationtypelabel']))) - else: - if(isiaorestrictedfilemanager == True or isministryrestrictedfilemanager == True): - dbquery = basequery - else: - if(requestby == 'IAO'): - dbquery = basequery.filter(or_(or_(FOIRestrictedMinistryRequest.isrestricted == False, FOIRestrictedMinistryRequest.isrestricted == None), and_(FOIRestrictedMinistryRequest.isrestricted == True, FOIRequests.assignedto == userid))).filter(ministryfilter) - else: - dbquery = basequery.filter(or_(or_(ministry_restricted_requests.isrestricted == False, ministry_restricted_requests.isrestricted == None), and_(ministry_restricted_requests.isrestricted == True, FOIRequests.assignedministryperson == userid))).filter(ministryfilter) - - if(keyword is None): - return dbquery - else: - return dbquery.filter(or_(*filtercondition)) - - - @classmethod - def getgroupfilters(cls, groups): - #ministry filter for group/team - if groups is None: - #ministryfilter = FOIMinistryRequest.isactive == True - ministryfilter = None - else: - groupfilter = [] - for group in groups: - if (group == IAOTeamWithKeycloackGroup.flex.value or group in ProcessingTeamWithKeycloackGroup.list()): - groupfilter.append( - and_( - FOIRequests.assignedgroup == group - ) - ) - elif (group == IAOTeamWithKeycloackGroup.intake.value): - groupfilter.append( - or_( - FOIRequests.assignedgroup == group, - and_( - FOIRequests.assignedgroup == IAOTeamWithKeycloackGroup.flex.value, - FOIRequests.requeststatuslabel.in_([StateName.open.name]) - ) - ) - ) - else: - groupfilter.append( - or_( - FOIRequests.assignedgroup == group, - and_( - FOIRequests.assignedministrygroup == group, - FOIRequests.requeststatuslabel.in_([StateName.callforrecords.name,StateName.recordsreview.name,StateName.feeestimate.name,StateName.consult.name,StateName.ministrysignoff.name,StateName.onhold.name,StateName.deduplication.name,StateName.harmsassessment.name,StateName.response.name,StateName.tagging.name,StateName.readytoscan.name]) - ) - ) - ) - - ministryfilter = and_( - or_(*groupfilter) - ) - - return ministryfilter - - @classmethod - def getfilterkeyword(cls, keyword, field): - _newkeyword = keyword - if(field == 'idNumber'): - _newkeyword = _newkeyword.replace('u-00', '') - return _newkeyword - - @classmethod - def getsorting(cls, sortingitems, sortingorders): - #sorting - sortingcondition = [] - if(len(sortingitems) > 0 and len(sortingorders) > 0 and len(sortingitems) == len(sortingorders)): - for field in sortingitems: - order = sortingorders.pop(0) - sortingcondition.append(FOIRequestNotificationUser.getfieldforsorting(field, order)) - - #default sorting - if(len(sortingcondition) == 0): - sortingcondition.append(FOIRequestNotificationUser.findfield('createdat').desc()) - - #always sort by created_at last to prevent pagination collisions - sortingcondition.append(asc('created_at')) - - return sortingcondition - - @classmethod - def getfieldforsorting(cls, field, order): - if(order == 'desc'): - return nullslast(FOIRequestNotificationUser.findfield(field).desc()) - else: - return nullsfirst(FOIRequestNotificationUser.findfield(field).asc()) - - @classmethod - def findfield(cls, x): - #add more fields here if need sort/filter/search more columns - return { - 'axisRequestId' : FOIRequests.axisrequestid, - 'createdat': FOINotifications.created_at, - 'createdatformatted': FOINotifications.createdatformatted, - 'notification': FOINotifications.notification, - 'assignedToFormatted': FOIRequests.assignedtoformatted, - 'ministryAssignedToFormatted': FOIRequests.ministryassignedtoformatted, - 'userFormatted': FOINotifications.userformatted, - 'creatorFormatted': FOINotifications.creatorformatted - }.get(x, cast(FOIRequests.axisrequestid, String)) - - # End of Dashboard functions - -class FOIRequestNotificationUserSchema(ma.Schema): - class Meta: - fields = ('notificationid', 'userid','notificationusertypeid', 'notificationusertypelabel','created_at','createdby','updated_at','updatedby') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestNotifications.py b/historical-search-api/request_api/models/FOIRequestNotifications.py deleted file mode 100644 index fc8728878..000000000 --- a/historical-search-api/request_api/models/FOIRequestNotifications.py +++ /dev/null @@ -1,189 +0,0 @@ -import logging -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint -from sqlalchemy.sql.schema import ForeignKey -from .db import db, ma -from sqlalchemy.dialects.postgresql import JSON -from datetime import datetime as datetime2 -from sqlalchemy.orm import relationship,backref -from .default_method_result import DefaultMethodResult -from sqlalchemy.dialects.postgresql import JSON, UUID -from sqlalchemy.sql.expression import distinct -from sqlalchemy import text -import maya -import json -f = open('common/notificationtypes.json', encoding="utf8") -notificationtypes_cache = json.load(f) - -class FOIRequestNotification(db.Model): - # Name of the table in our database - __tablename__ = 'FOIRequestNotifications' - # Defining the columns - notificationid = db.Column(db.Integer, primary_key=True,autoincrement=True) - foirequestid =db.Column(db.Integer, nullable=False) - requestid =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.foiministryrequestid')) - version =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.version')) - idnumber = db.Column(db.String(50), unique=False, nullable=True) - axisnumber = db.Column(db.String(50), unique=False, nullable=True) - notification = db.Column(JSON, unique=False, nullable=True) - isdeleted = db.Column(db.Boolean, unique=False, nullable=True, default=False) - created_at = db.Column(db.DateTime, default=datetime2.now) - createdby = db.Column(db.String(120), unique=False, nullable=True) - updated_at = db.Column(db.DateTime, nullable=True) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - notificationtypeid = db.Column(db.Integer, nullable=False) - notificationtypelabel = db.Column(db.String(50), nullable=False) - - notificationusers = db.relationship('FOIRequestNotificationUser', backref='FOIRequestNotifications', lazy='dynamic') - - - @classmethod - def savenotification(cls,foinotification)->DefaultMethodResult: - try: - db.session.add(foinotification) - db.session.commit() - return DefaultMethodResult(True,'Notification added',foinotification.requestid) - except: - db.session.rollback() - raise - - @classmethod - def updatenotification(cls, foinotification, userid): - dbquery = db.session.query(FOIRequestNotification) - _notification = dbquery.filter_by(notificationid=foinotification['notificationid']) - if(_notification.count() > 0) : - _notification.update({ - FOIRequestNotification.notification:foinotification['notification'], - FOIRequestNotification.updatedby:userid, - FOIRequestNotification.updated_at:datetime2.now() - }, synchronize_session = False) - db.session.commit() - return DefaultMethodResult(True,'notification updated',foinotification['notificationid']) - else: - return DefaultMethodResult(True,'No notification found',foinotification['notificationid']) - - - @classmethod - def getconsolidatednotifications(cls, userid, days): - notifications = [] - try: - sql = """select idnumber, axisnumber, notificationid, notification , notificationtype, userid, notificationusertype, created_at, createdby, requesttype, requestid, foirequestid from ( - select frn.idnumber, frn.axisnumber, frn.requestid, frns.notificationuserid as notificationid, frn.notification -> 'message' as notification , nty.name as notificationtype, frn.created_at , frns.createdby, frns.userid, ntu.name as notificationusertype, 'ministryrequest' requesttype, frn.foirequestid from "FOIRequestNotifications" frn inner join "FOIRequestNotificationUsers" frns on frn.notificationid = frns.notificationid and frns.isdeleted = false inner join "NotificationTypes" nty on frn.notificationtypelabel = nty.notificationtypelabel inner join "NotificationUserTypes" ntu on frns.notificationusertypelabel = ntu.notificationusertypelabel where frns.userid=:userid and frn.created_at >= current_date - interval :days day - union all - select frn.idnumber, frn.axisnumber, frn.requestid, frns.notificationuserid as notificationid, frn.notification -> 'message' as notification, nty.name as notificationtype, frn.created_at , frns.createdby, frns.userid, ntu.name as notificationusertype, 'rawrequest' requesttype, 0 foirequestid from "FOIRawRequestNotifications" frn inner join "FOIRawRequestNotificationUsers" frns on frn.notificationid = frns.notificationid and frns.isdeleted = false inner join "NotificationTypes" nty on frn.notificationtypelabel = nty.notificationtypelabel inner join "NotificationUserTypes" ntu on frns.notificationusertypelabel = ntu.notificationusertypelabel where frns.userid=:userid and frn.created_at >= current_date - interval :days day - ) as notf order by created_at desc""" - rs = db.session.execute(text(sql), {'userid': userid, 'days': days}) - for row in rs: - dt = maya.parse(row["created_at"]).datetime(to_timezone='America/Vancouver', naive=False) - _createddate = dt - notifications.append({"idnumber": row["idnumber"], "axisnumber": row["axisnumber"], "notificationid": row["notificationid"], "notification": row["notification"], "notificationtype": row["notificationtype"], "notificationusertype": row["notificationusertype"], "created_at": _createddate.strftime('%Y %b %d | %I:%M %p').upper(), "createdby": row["createdby"], "requesttype":row["requesttype"], "requestid":row["requestid"],"foirequestid":row["foirequestid"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return notifications - - @classmethod - def getcommentnotifications(cls, commentid): - notifications = [] - try: - sql = """select idnumber, axisnumber, notificationid, notificationuserid, notification , notificationtype, userid, notificationusertype, created_at, createdby, requesttype, requestid, foirequestid from ( - select frn.idnumber, frn.axisnumber, frn.requestid, frn.notificationid, frns.notificationuserid, frn.notification -> 'message' as notification , nty.name as notificationtype, frn.created_at , frns.createdby, frns.userid, ntu.name as notificationusertype, 'ministryrequest' requesttype, frn.foirequestid from "FOIRequestNotifications" frn inner join "FOIRequestNotificationUsers" frns on frn.notificationid = frns.notificationid and frns.isdeleted = false inner join "NotificationTypes" nty on frn.notificationtypelabel = nty.notificationtypelabel inner join "NotificationUserTypes" ntu on frns.notificationusertypelabel = ntu.notificationusertypelabel where frn.notificationtypelabel in :notificationtypelabel and (frn.notification ->> 'commentid')::int = :commentid - union all - select frn.idnumber, frn.axisnumber, frn.requestid, frn.notificationid, frns.notificationuserid, frn.notification -> 'message' as notification, nty.name as notificationtype, frn.created_at , frns.createdby, frns.userid, ntu.name as notificationusertype, 'rawrequest' requesttype, 0 foirequestid from "FOIRawRequestNotifications" frn inner join "FOIRawRequestNotificationUsers" frns on frn.notificationid = frns.notificationid and frns.isdeleted = false inner join "NotificationTypes" nty on frn.notificationtypelabel = nty.notificationtypelabel inner join "NotificationUserTypes" ntu on frns.notificationusertypelabel = ntu.notificationusertypelabel where frn.notificationtypelabel in :notificationtypelabel and (frn.notification ->> 'commentid')::int = :commentid - ) as notf order by created_at desc""" - notificationtypelabel = tuple([notificationtypes_cache['newusercomments']['notificationtypelabel'], - notificationtypes_cache['replyusercomments']['notificationtypelabel'], - notificationtypes_cache['taggedusercomments']['notificationtypelabel'], - ]) # 3,9,10 - # notificationtypelabel = ','.join(str(e) for e in notificationtypelabel) - rs = db.session.execute(text(sql), {'commentid': commentid, 'notificationtypelabel': notificationtypelabel}) - for row in rs: - dt = maya.parse(row["created_at"]).datetime(to_timezone='America/Vancouver', naive=False) - _createddate = dt - notifications.append({"userid": row["userid"],"idnumber": row["idnumber"], "axisnumber": row["axisnumber"], "notificationid": row["notificationid"], "notificationuserid": row["notificationuserid"], "notification": row["notification"], "notificationtype": row["notificationtype"], "notificationusertype": row["notificationusertype"], "created_at": _createddate.strftime('%Y %b %d | %I:%M %p').upper(), "createdby": row["createdby"], "requesttype":row["requesttype"], "requestid":row["requestid"],"foirequestid":row["foirequestid"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return notifications - - @classmethod - def getextensionnotifications(cls, extensionid): - notifications = [] - try: - sql = sql = """select idnumber, axisnumber, notificationid, notification , notificationtypelabel from "FOIRequestNotifications" where isdeleted = false and notification->>'extensionid' = :extensionid """ - rs = db.session.execute(text(sql), {'extensionid': str(extensionid)}) - for row in rs: - notifications.append({"idnumber": row["idnumber"], "axisnumber": row["axisnumber"], "notificationid": row["notificationid"], "notification": row["notification"], "notificationtypelabel": row["notificationtypelabel"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return notifications - - @classmethod - def dismissnotification(cls, notificationids, userid='system'): - try: - db.session.query(FOIRequestNotification).filter(FOIRequestNotification.notificationid.in_(notificationids)).update({FOIRequestNotification.isdeleted: True, FOIRequestNotification.updatedby: userid, - FOIRequestNotification.updated_at: datetime2.now()}, synchronize_session=False) - db.session.commit() - return DefaultMethodResult(True,'Notifications deleted ', notificationids) - except: - db.session.rollback() - raise - - @classmethod - def getnotificationidsbynumberandtype(cls, idnumber, notificationtypelabels): - notificationids = [] - try: - sql = """select notificationid from "FOIRequestNotifications" where idnumber = :idnumber and notificationtypelabel = ANY(:notificationtypelabels) and isdeleted = false """ - rs = db.session.execute(text(sql), {'idnumber': idnumber, 'notificationtypelabels': notificationtypelabels}) - for row in rs: - notificationids.append(row["notificationid"]) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return notificationids - - @classmethod - def getnotificationidsbynumber(cls, idnumber): - notificationids = [] - try: - sql = """select notificationid from "FOIRequestNotifications" where idnumber = :idnumber and isdeleted = false """ - rs = db.session.execute(text(sql), {'idnumber': idnumber}) - for row in rs: - notificationids.append(row["notificationid"]) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return notificationids - - @classmethod - def getnotificationidsbytype(cls, notificationtypelabel): - sql = """select notificationid from "FOIRequestNotifications" where notificationtypelabel= :notificationtypelabel and isdeleted = false """ - rs = db.session.execute(text(sql), {'notificationtypelabel': notificationtypelabel}) - notificationids = [] - for row in rs: - notificationids.append(row["notificationid"]) - return notificationids - - @classmethod - def getextensionnotificationidsbyministry(cls, ministryid): - sql = """select notificationid from "FOIRequestNotifications" where requestid = :requestid and notificationtypelabel = 4 and isdeleted = false """ - rs = db.session.execute(text(sql), {'requestid': ministryid}) - notificationids = [] - for row in rs: - notificationids.append(row["notificationid"]) - return notificationids - -class FOIRequestNotificationSchema(ma.Schema): - class Meta: - fields = ('notificationid', 'ministryrequestid', 'notification', 'notificationtypeid', 'notificationtypelabel','created_at','createdby','updated_at','updatedby') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestOIPC.py b/historical-search-api/request_api/models/FOIRequestOIPC.py deleted file mode 100644 index 2d65c413d..000000000 --- a/historical-search-api/request_api/models/FOIRequestOIPC.py +++ /dev/null @@ -1,63 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey -from .db import db, ma -from datetime import datetime -from sqlalchemy.orm import relationship,backref -from .default_method_result import DefaultMethodResult -from sqlalchemy.dialects.postgresql import JSON, UUID -from sqlalchemy.sql.expression import distinct -from sqlalchemy import text, and_, func -import logging -import json -from sqlalchemy.dialects.postgresql import JSON, insert - -class FOIRequestOIPC(db.Model): - # Name of the table in our database - __tablename__ = 'FOIRequestOIPC' - # Defining the columns - oipcid = db.Column(db.Integer, primary_key=True,autoincrement=True) - foiministryrequest_id =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.foiministryrequestid')) - foiministryrequestversion_id =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.version')) - oipcno = db.Column(db.String(120), unique=False, nullable=True) - reviewtypeid = db.Column(db.Integer,ForeignKey('OIPCReviewTypes.reviewtypeid')) - reviewtype = relationship("OIPCReviewTypes",backref=backref("OIPCReviewTypes"),uselist=False) - reasonid = db.Column(db.Integer,ForeignKey('OIPCReasons.reasonid')) - reason = relationship("OIPCReasons",backref=backref("OIPCReasons"),uselist=False) - statusid = db.Column(db.Integer,ForeignKey('OIPCStatuses.statusid')) - status = relationship("OIPCStatuses",backref=backref("OIPCStatuses"),uselist=False) - outcomeid = db.Column(db.Integer,ForeignKey('OIPCOutcomes.outcomeid')) - outcome = relationship("OIPCOutcomes",backref=backref("OIPCOutcomes"),uselist=False) - isinquiry = db.Column(db.Boolean, unique=False, nullable=True) - inquiryattributes = db.Column(JSON, unique=False, nullable=True) - isjudicialreview = db.Column(db.Boolean, unique=False, nullable=True) - issubsequentappeal = db.Column(db.Boolean, unique=False, nullable=True) - investigator = db.Column(db.String(500), unique=False, nullable=True) - receiveddate = db.Column(db.Date, nullable=True) - closeddate = db.Column(db.Date, nullable=True) - created_at = db.Column(db.DateTime, default=datetime.now) - createdby = db.Column(db.String(120), unique=False, nullable=False) - updated_at = db.Column(db.DateTime, nullable=True) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - - - @classmethod - def getoipc(cls,ministryrequestid,ministryrequestversion): - oipc_schema = FOIRequestOIPCSchema(many=True) - _oipclist = db.session.query(FOIRequestOIPC).filter(FOIRequestOIPC.foiministryrequest_id == ministryrequestid , FOIRequestOIPC.foiministryrequestversion_id == ministryrequestversion).order_by(FOIRequestOIPC.oipcid.asc()).all() - divisioninfos = oipc_schema.dump(_oipclist) - return divisioninfos - - - @classmethod - def getrequestidsbyoipcno(cls, oipcno): - return db.session.query( - FOIRequestOIPC.foiministryrequest_id, - FOIRequestOIPC.foiministryrequestversion_id - ).filter(FOIRequestOIPC.oipcno.ilike('%'+oipcno+'%')).group_by(FOIRequestOIPC.foiministryrequest_id, FOIRequestOIPC.foiministryrequestversion_id).subquery() - - -class FOIRequestOIPCSchema(ma.Schema): - class Meta: - fields = ('oipcid', 'version', 'foiministryrequest_id', 'investigator', 'foiministryrequestversion_id','oipcno','reviewtypeid','reasonid','statusid','outcomeid','isinquiry','inquiryattributes','isjudicialreview', - 'issubsequentappeal','receiveddate','closeddate','created_at','createdby','updated_at','updatedby', - 'reviewtype.name', 'reason.name', 'status.name', 'outcome.name') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestPayments.py b/historical-search-api/request_api/models/FOIRequestPayments.py deleted file mode 100644 index b0c64b5d7..000000000 --- a/historical-search-api/request_api/models/FOIRequestPayments.py +++ /dev/null @@ -1,85 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey -from .db import db, ma -from datetime import datetime -from sqlalchemy.orm import relationship,backref -from .default_method_result import DefaultMethodResult -from sqlalchemy.dialects.postgresql import JSON, UUID -from sqlalchemy.sql.expression import distinct -from sqlalchemy import text, and_, func -import logging -import maya -import os -from dateutil.parser import parse -from pytz import timezone -import json - -class FOIRequestPayment(db.Model): - # Name of the table in our database - __tablename__ = 'FOIRequestPayments' - # Defining the columns - paymentid = db.Column(db.Integer, primary_key=True,autoincrement=True) - version =db.Column(db.Integer,primary_key=True,nullable=False) - foirequestid =db.Column(db.Integer, nullable=False) - ministryrequestid =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.foiministryrequestid')) - ministryrequestversion=db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.version')) - paymenturl = db.Column(db.Text, unique=False, nullable=True) - paymentexpirydate = db.Column(db.DateTime, nullable=True) - paidamount = db.Column(db.Numeric(10,2), nullable=True) - created_at = db.Column(db.DateTime, default=datetime.now) - createdby = db.Column(db.String(120), unique=False, nullable=True) - updated_at = db.Column(db.DateTime, nullable=True) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - - @classmethod - def savepayment(cls, newpayment)->DefaultMethodResult: - db.session.add(newpayment) - db.session.commit() - return DefaultMethodResult(True,'Payment added') - - @classmethod - def updatepayment(cls, paymentid, paymenturl, userid)->DefaultMethodResult: - currequest = db.session.query(FOIRequestPayment).filter_by(paymentid=paymentid).order_by(FOIRequestPayment.version.desc()).first() - setattr(currequest,'paymenturl',paymenturl) - setattr(currequest,'updated_at',datetime.now().isoformat()) - setattr(currequest,'updatedby',userid) - db.session.commit() - return DefaultMethodResult(True,'Payment updated',paymentid) - - @classmethod - def getpayment(cls, foirequestid, ministryrequestid)->DefaultMethodResult: - payment_schema = FOIRequestPaymentSchema() - payment = db.session.query(FOIRequestPayment).filter(FOIRequestPayment.foirequestid == foirequestid, FOIRequestPayment.ministryrequestid == ministryrequestid).order_by(FOIRequestPayment.paymentid.desc(), FOIRequestPayment.version.desc()).first() - return payment_schema.dump(payment) - - @classmethod - def getactivepayment(cls, foirequestid, ministryrequestid) -> DefaultMethodResult: - now_pst = maya.parse(maya.now()).datetime(to_timezone='America/Vancouver', naive=False) - _psttoday = now_pst.strftime('%Y-%m-%d') - try: - """ - sql = select distinct on (paymentid) paymentid, paymenturl from "FOIRequestPayments" fp where foirequestid = :foirequestid and ministryrequestid = :ministryrequestid - and TO_DATE(paymentexpirydate::TEXT,'YYYY-MM-DD') >= TO_DATE(:today,'YYYY-MM-DD') - order by paymentid, version desc - """ - sql = """select fp1.paymentid , fp1.paymenturl, fp1.version from "FOIRequestPayments" fp1, ( - select distinct on (paymentid) paymentid, paymenturl, createdby, version from "FOIRequestPayments" fp where foirequestid = :foirequestid and ministryrequestid = :ministryrequestid - order by paymentid, version desc) as fp2 - where fp1.paymentid = fp2.paymentid and fp1.version = fp2.version - and fp1.createdby <> 'System_Cancel' and fp1.paymenturl is not null and fp1.paidamount is null - """ - - rs = db.session.execute(text(sql), {'foirequestid': foirequestid, 'ministryrequestid' : ministryrequestid, 'today' : _psttoday}) - for row in rs: - return ({"paymentid": row["paymentid"], "paymenturl": row["paymenturl"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return None - - -class FOIRequestPaymentSchema(ma.Schema): - class Meta: - fields = ('paymentid', 'version', 'foirequestid', 'ministryrequestid', 'paymenturl','created_at','createdby','updated_at','updatedby', 'paymentexpirydate', 'paidamount') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestPersonalAttributes.py b/historical-search-api/request_api/models/FOIRequestPersonalAttributes.py deleted file mode 100644 index 11dbe8a6b..000000000 --- a/historical-search-api/request_api/models/FOIRequestPersonalAttributes.py +++ /dev/null @@ -1,50 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey, ForeignKeyConstraint -from .db import db, ma -from datetime import datetime -from sqlalchemy.orm import relationship,backref -from .default_method_result import DefaultMethodResult -from .FOIRequests import FOIRequest - -class FOIRequestPersonalAttribute(db.Model): - # Name of the table in our database - __tablename__ = 'FOIRequestPersonalAttributes' - __table_args__ = ( - ForeignKeyConstraint( - ["foirequest_id", "foirequestversion_id"], ["FOIRequests.foirequestid", "FOIRequests.version"] - ), - ) - # Defining the columns - foirequestpersonalattributeid = db.Column(db.Integer, primary_key=True,autoincrement=True) - - - attributevalue = db.Column(db.String(256), unique=False, nullable=False) - - - created_at = db.Column(db.DateTime, default=datetime.now) - updated_at = db.Column(db.DateTime, nullable=True) - createdby = db.Column(db.String(120), unique=False, nullable=True) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - - #ForeignKey References - - personalattributeid = db.Column(db.Integer,ForeignKey('PersonalInformationAttributes.attributeid')) - personalattribute = relationship("PersonalInformationAttribute",backref=backref("PersonalInformationAttributes"),uselist=False) - - foirequest_id =db.Column(db.Integer, db.ForeignKey('FOIRequests.foirequestid')) - foirequestversion_id = db.Column(db.Integer, db.ForeignKey('FOIRequests.version')) - foirequestkey = relationship("FOIRequest",foreign_keys="[FOIRequestPersonalAttribute.foirequest_id]") - foirequestversion = relationship("FOIRequest",foreign_keys="[FOIRequestPersonalAttribute.foirequestversion_id]") - - @classmethod - def getrequestpersonalattributes(cls,foirequest_id,foirequestversion): - requestpersonalattribute_schema = FOIRequestPersonalAttributeSchema(many=True) - _personalattributes = db.session.query(FOIRequestPersonalAttribute).filter(FOIRequestPersonalAttribute.foirequest_id == foirequest_id , FOIRequestPersonalAttribute.foirequestversion_id == foirequestversion).order_by(FOIRequestPersonalAttribute.foirequestpersonalattributeid.asc()).all() - personalattributes = requestpersonalattribute_schema.dump(_personalattributes) - return personalattributes - - -class FOIRequestPersonalAttributeSchema(ma.Schema): - class Meta: - fields = ('foirequestpersonalattributeid','attributevalue','foirequest.foirequestid','personalattribute.name','personalattributeid') - \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestRecords.py b/historical-search-api/request_api/models/FOIRequestRecords.py deleted file mode 100644 index a0ad9eebb..000000000 --- a/historical-search-api/request_api/models/FOIRequestRecords.py +++ /dev/null @@ -1,183 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey -from .db import db, ma -from datetime import datetime -from sqlalchemy.orm import relationship,backref -from .default_method_result import DefaultMethodResult -from sqlalchemy.dialects.postgresql import JSON, UUID -from sqlalchemy.sql.expression import distinct -from sqlalchemy import text -import logging -import json -class FOIRequestRecord(db.Model): - # Name of the table in our database - __tablename__ = 'FOIRequestRecords' - # Defining the columns - recordid = db.Column(db.Integer, primary_key=True,autoincrement=True) - version =db.Column(db.Integer,primary_key=True,nullable=False) - foirequestid =db.Column(db.Integer, nullable=False) - ministryrequestid =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.foiministryrequestid')) - ministryrequestversion=db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.version')) - filename = db.Column(db.Text, unique=False, nullable=True) - s3uripath = db.Column(db.Text, unique=False, nullable=True) - attributes = db.Column(JSON, unique=False, nullable=True) - created_at = db.Column(db.DateTime, default=datetime.now) - createdby = db.Column(db.String(120), unique=False, nullable=True) - updated_at = db.Column(db.DateTime, nullable=True) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - isactive = db.Column(db.Boolean, unique=False, nullable=False,default=True) - replacementof = db.Column(db.Integer, unique=False, nullable=False) - - @classmethod - def create(cls, records): - db.session.add_all(records) - db.session.commit() - _recordids = {} - for record in records: - _recordids[record.s3uripath] = {"filename": record.filename, "recordid": record.recordid} - return DefaultMethodResult(True,'Records created', -1, _recordids) - - @classmethod - def fetch(cls, foirequestid, ministryrequestid): - records = [] - try: - sql = """select distinct on (fr1.recordid) recordid, fr1.isactive, fr1.filename, - fr1.s3uripath, fr1."attributes" attributes, json_extract_path_text("attributes" ::json,'batch') as batchid, - fr1.createdby createdby, fr1.created_at,fr1.replacementof - from public."FOIRequestRecords" fr1 - where fr1.foirequestid = :foirequestid and fr1.ministryrequestid = :ministryrequestid - order by recordid desc, version desc - """ - - rs = db.session.execute(text(sql), {'foirequestid': foirequestid, 'ministryrequestid' : ministryrequestid}) - - for row in rs: - if row["isactive"] == True: - _originalfile ='' - _originalfilename ='' - if row["replacementof"] is not None: - originalrecord = FOIRequestRecord.getrecordbyid(row["replacementof"]) - _originalfile = originalrecord["s3uripath"] - _originalfilename = originalrecord["filename"] - records.append({ - "recordid": row["recordid"], - "filename": row["filename"], - "s3uripath": row["s3uripath"], - "attributes": row["attributes"], - "batchid": row["batchid"], - "createdby": row["createdby"], - "created_at": row["created_at"], - "replacementof":row["replacementof"], - "originalfile" : _originalfile, - "originalfilename":_originalfilename - }) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return records - - @classmethod - def getrecordbyid(cls, recordid)->DefaultMethodResult: - comment_schema = FOIRequestRecordSchema(many=False) - query = db.session.query(FOIRequestRecord).filter_by(recordid=recordid).order_by(FOIRequestRecord.version.desc()).first() - return comment_schema.dump(query) - - @classmethod - def getrecordsbyid(cls, recordids): - records = [] - try: - sql = """select - fr1.recordid, fr1.version, fr1.foirequestid, fr1.ministryrequestid, - fr1.ministryrequestversion, fr1.attributes, fr1.filename, fr1.s3uripath, - fr1.created_at, fr1.createdby, fr1.updated_at, fr1.updatedby, fr1.replacementof - from public."FOIRequestRecords" fr1 - inner join ( - select fr2.recordid, max(fr2.version) as maxversion - from public."FOIRequestRecords" fr2 - where fr2.recordid in ("""+ ','.join([str(id) for id in recordids]) +""") - group by fr2.recordid - ) fr3 on fr3.recordid = fr1.recordid and fr3.maxversion = fr1.version - order by fr1.recordid desc - """ - rs = db.session.execute(text(sql)) - - for row in rs: - records.append({ - "recordid": row["recordid"], - "version": row["version"], - "foirequestid": row["foirequestid"], - "ministryrequestid": row["ministryrequestid"], - "ministryrequestversion": row["ministryrequestversion"], - "attributes": row["attributes"], - "filename": row["filename"], - "s3uripath": row["s3uripath"], - "created_at": row["created_at"], - "createdby": row["createdby"], - "updated_at":row["updated_at"], - "updatedby":row["updatedby"], - "updated_at":row["updated_at"], - "replacementof":row["replacementof"] - }) - except Exception as ex: - logging.error(ex) - raise ex - 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() - replacingrecord.isactive=False - db.session.commit() - db.session.add_all(records) - db.session.commit() - _recordids = {} - for record in records: - _recordids[record.s3uripath] = {"filename": record.filename, "recordid": record.recordid} - return DefaultMethodResult(True,'Records replaced', -1, _recordids) - - @classmethod - def getbatchcount(cls, ministryrequestid): - batchcount = 0 - try: - sql = """select count(distinct - json_extract_path_text("attributes" ::json,'batch')) AS batch_count - FROM "FOIRequestRecords" r - join (select max(version), recordid - from public."FOIRequestRecords" - group by recordid) r2 on r2.max = r.version and r2.recordid = r.recordid - where ministryrequestid = :ministryrequestid and isactive = true """ - rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) - for row in rs: - batchcount = row["batch_count"] - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return batchcount - -class FOIRequestRecordSchema(ma.Schema): - class Meta: - fields = ('recordid','version','foirequestid','ministryrequestid','ministryrequestversion','attributes','filename','s3uripath','created_at','createdby','updated_at','updatedby','replacementof') diff --git a/historical-search-api/request_api/models/FOIRequestStatus.py b/historical-search-api/request_api/models/FOIRequestStatus.py deleted file mode 100644 index 439c9f6ed..000000000 --- a/historical-search-api/request_api/models/FOIRequestStatus.py +++ /dev/null @@ -1,34 +0,0 @@ -from .db import db, ma -from .default_method_result import DefaultMethodResult - - -class FOIRequestStatus(db.Model): - __tablename__ = 'FOIRequestStatuses' - # Defining the columns - requeststatusid = db.Column(db.Integer, primary_key=True,autoincrement=True) - name = db.Column(db.String(100), unique=False, nullable=False) - description = db.Column(db.String(255), unique=False, nullable=True) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - statuslabel = db.Column(db.String(50), unique=True, nullable=False) - - @classmethod - def getrequeststatuses(cls): - requeststatus_schema = RequestStatusSchema(many=True) - query = db.session.query(FOIRequestStatus).filter_by(isactive=True).all() - return requeststatus_schema.dump(query) - - @classmethod - def getrequeststatus(cls,status): - requeststatus_schema = RequestStatusSchema() - query = db.session.query(FOIRequestStatus).filter_by(name=status).first() - return requeststatus_schema.dump(query) - - @classmethod - def getrequeststatusbylabel(cls,statuslabel): - requeststatus_schema = RequestStatusSchema() - query = db.session.query(FOIRequestStatus).filter_by(statuslabel=statuslabel, isactive=True).first() - return requeststatus_schema.dump(query) - -class RequestStatusSchema(ma.Schema): - class Meta: - fields = ('requeststatusid', 'name', 'description','isactive','statuslabel') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestTeams.py b/historical-search-api/request_api/models/FOIRequestTeams.py deleted file mode 100644 index e6ddbe50f..000000000 --- a/historical-search-api/request_api/models/FOIRequestTeams.py +++ /dev/null @@ -1,98 +0,0 @@ -from .db import db, ma -from .default_method_result import DefaultMethodResult -from sqlalchemy.orm import relationship,backref -from sqlalchemy.sql.schema import ForeignKey -from sqlalchemy import text -import logging -from request_api.utils.enums import StateName - -class FOIRequestTeam(db.Model): - __tablename__ = 'FOIRequestTeams' - # Defining the columns - requestteamid = db.Column(db.Integer, primary_key=True,autoincrement=True) - requesttype = db.Column(db.String(100), unique=False, nullable=True) - requeststatusid = db.Column(db.Integer,ForeignKey('FOIRequestStatuses.requeststatusid')) - requeststatuslabel = db.Column(db.String(50), unique=False, nullable=False) - teamid = db.Column(db.Integer,ForeignKey('OperatingTeams.teamid')) - programareaid = db.Column(db.Integer,ForeignKey('ProgramAreas.programareaid')) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - - @classmethod - def getrequestteams(cls): - programarea_schema = FOIRequestTeamSchema(many=True) - query = db.session.query(FOIRequestTeam).filter_by(isactive=True).all() - return programarea_schema.dump(query) - - @classmethod - def getteamsbystatusandprogramarea(cls, requesttype, status, bcgovcode): - teams = [] - try: - # and replace(lower(fs2."name"),' ','') = :status - sql = """ - with mappedteams as ( - select ot."name" as name, ot."type" as type, ft.requestteamid as orderby from "FOIRequestTeams" ft inner join "FOIRequestStatuses" fs2 on ft.requeststatusid = fs2.requeststatusid - inner join "OperatingTeams" ot on ft.teamid = ot.teamid - left join "ProgramAreas" pa on ft.programareaid = pa.programareaid - where ft.isactive = true and lower(ft.requesttype) = :requesttype - and ft.requeststatuslabel = :status - and (lower(pa.bcgovcode) = :bcgovcode or ft.programareaid is null) - ) - -- remove the with statement and below query go back to mapped teams only in assignee drop down - select * from mappedteams union - (select name, type, 1 as orderby from "OperatingTeams" where name not in (select name from mappedteams) and type = 'iao' and isactive = true) - order by orderby desc""" - rs = db.session.execute(text(sql), {'requesttype': requesttype, 'status': status,'bcgovcode':bcgovcode}) - - for row in rs: - teams.append({"name":row["name"], "type":row["type"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return teams - - @classmethod - def getprocessingteamsbytype(cls, requesttype): - teams = [] - try: - sql = """select ot."name" as team, pa."name" as ministry, pa.bcgovcode, pa.iaocode from "FOIRequestTeams" ft - inner join "OperatingTeams" ot on ft.teamid = ot.teamid - inner join "ProgramAreas" pa on ft.programareaid = pa.programareaid - where lower(ft.requesttype) = :requesttype and ft.programareaid is not null - and ot."type" = 'iao' - and ft.requeststatuslabel = :requeststatuslabel""" - rs = db.session.execute(text(sql), {'requesttype': requesttype, 'requeststatuslabel': StateName.feeestimate.name}) - for row in rs: - teams.append({"team":row["team"], "ministry":row["ministry"], "bcgovcode":row["bcgovcode"], "iaocode":row["iaocode"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return teams - - @classmethod - def getdefaultprocessingteamforpersonal(cls, bcgovcode): - defaultteam = None - try: - sql = """select ot."name" as name from "FOIRequestTeams" ft inner join "FOIRequestStatuses" fs2 on ft.requeststatusid = fs2.requeststatusid - inner join "OperatingTeams" ot on ft.teamid = ot.teamid - left join "ProgramAreas" pa on ft.programareaid = pa.programareaid - where ft.isactive = true and lower(ft.requesttype) = 'personal' - and replace(lower(fs2."name"),' ','') = 'open' - and ot."name" not in ('Intake Team','Flex Team') - and (lower(pa.bcgovcode) = :bcgovcode or ft.programareaid is null) order by requestteamid desc limit 1""" - rs = db.session.execute(text(sql), {'bcgovcode':bcgovcode.lower()}) - for row in rs: - defaultteam = row["name"] - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return defaultteam - -class FOIRequestTeamSchema(ma.Schema): - class Meta: - fields = ('requestteamid', 'requesttype', 'requeststatusid','teamid','programareaid','isactive', 'requeststatuslabel') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequestWatchers.py b/historical-search-api/request_api/models/FOIRequestWatchers.py deleted file mode 100644 index a95f4913b..000000000 --- a/historical-search-api/request_api/models/FOIRequestWatchers.py +++ /dev/null @@ -1,138 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey -from .db import db, ma -from datetime import datetime -from sqlalchemy.orm import relationship,backref -from .default_method_result import DefaultMethodResult -from sqlalchemy.dialects.postgresql import JSON, UUID -from sqlalchemy.sql.expression import distinct -from sqlalchemy import text, and_, func -import logging -import json -class FOIRequestWatcher(db.Model): - # Name of the table in our database - __tablename__ = 'FOIRequestWatchers' - # Defining the columns - watcherid = db.Column(db.Integer, primary_key=True,autoincrement=True) - ministryrequestid =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.foiministryrequestid')) - version =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.version')) - watchedbygroup = db.Column(db.String(250), unique=False, nullable=True) - watchedby = db.Column(db.String(120), unique=False, nullable=True) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - created_at = db.Column(db.DateTime, default=datetime.now) - createdby = db.Column(db.String(120), unique=False, nullable=True) - updated_at = db.Column(db.DateTime, nullable=True) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - - @classmethod - def savewatcher(cls, foirequestwatcher, version, userid)->DefaultMethodResult: - newwatcher = FOIRequestWatcher(ministryrequestid=foirequestwatcher["ministryrequestid"], version=version, watchedbygroup=foirequestwatcher["watchedbygroup"], watchedby=foirequestwatcher["watchedby"], isactive=foirequestwatcher["isactive"], createdby=userid) - db.session.add(newwatcher) - db.session.commit() - return DefaultMethodResult(True,'Request added') - - @classmethod - def savewatcherbygroups(cls, foirequestwatcher, version, userid, watchergroups)->DefaultMethodResult: - for group in watchergroups: - foirequestwatcher["watchedbygroup"] = group - newwatcher = FOIRequestWatcher(ministryrequestid=foirequestwatcher["ministryrequestid"], version=version, watchedbygroup=foirequestwatcher["watchedbygroup"], watchedby=foirequestwatcher["watchedby"], isactive=foirequestwatcher["isactive"], createdby=userid) - db.session.add(newwatcher) - db.session.commit() - return DefaultMethodResult(True,'Request added') - - @classmethod - def getMinistrywatchers(cls, ministryrequestid): - watchers = [] - try: - sql = 'select distinct on (watchedby, watchedbygroup) watchedby, watchedbygroup, isactive from "FOIRequestWatchers" where ministryrequestid=:ministryrequestid and watchedbygroup like \'%Ministry Team\' order by watchedby, watchedbygroup, created_at desc' - rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) - for row in rs: - if row["isactive"] == True: - watchers.append({"watchedby": row["watchedby"], "watchedbygroup": row["watchedbygroup"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return watchers - - @classmethod - def getNonMinistrywatchers(cls, ministryrequestid): - watchers = [] - try: - sql = 'select distinct on (watchedby, watchedbygroup) watchedby, watchedbygroup, isactive from "FOIRequestWatchers" where ministryrequestid=:ministryrequestid and watchedbygroup not like \'%Ministry Team\' order by watchedby, watchedbygroup, created_at desc' - rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) - for row in rs: - if row["isactive"] == True: - watchers.append({"watchedby": row["watchedby"], "watchedbygroup": row["watchedbygroup"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return watchers - - @classmethod - def isaiaoministryrequestwatcher(cls, ministryrequestid,userid): - _iswatcher = False - try: - sql = 'select distinct on (watchedby, watchedbygroup) watchedby, watchedbygroup, isactive from "FOIRequestWatchers" where ministryrequestid=:ministryrequestid and watchedby=:watchedby and watchedbygroup not like \'%Ministry Team\' order by watchedby, watchedbygroup, created_at desc' - rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid,'watchedby':userid}) - for row in rs: - if row["isactive"] == True: - _iswatcher = True - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return _iswatcher - - @classmethod - def isaministryministryrequestwatcher(cls, ministryrequestid,userid): - _iswatcher = False - try: - sql = 'select distinct on (watchedby, watchedbygroup) watchedby, watchedbygroup, isactive from "FOIRequestWatchers" where ministryrequestid=:ministryrequestid and watchedby=:watchedby and watchedbygroup like \'%Ministry Team\' order by watchedby, watchedbygroup, created_at desc' - rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid,'watchedby':userid}) - for row in rs: - if row["isactive"] == True: - _iswatcher = True - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return _iswatcher - - @classmethod - def getrequestidsbyuserid(cls, userid): - #subquery for getting latest watching status - subquery_max = db.session.query(FOIRequestWatcher.ministryrequestid, FOIRequestWatcher.watchedby ,func.max(FOIRequestWatcher.watcherid).label('max_watcherid')).group_by(FOIRequestWatcher.ministryrequestid, FOIRequestWatcher.watchedby).subquery() - joincondition = [ - subquery_max.c.ministryrequestid == FOIRequestWatcher.ministryrequestid, - subquery_max.c.watchedby == FOIRequestWatcher.watchedby, - subquery_max.c.max_watcherid == FOIRequestWatcher.watcherid, - ] - - return db.session.query( - FOIRequestWatcher.ministryrequestid, - FOIRequestWatcher.watchedby - ).join( - subquery_max, - and_(*joincondition) - ).filter(and_(FOIRequestWatcher.watchedby == userid, FOIRequestWatcher.isactive == True)).subquery() - - @classmethod - def disablewatchers(cls, ministryrequestid, userid): - dbquery = db.session.query(FOIRequestWatcher) - requestraqw = dbquery.filter_by(ministryrequestid=ministryrequestid) - if(requestraqw.count() > 0) : - requestraqw.update({FOIRequestWatcher.isactive:False, FOIRequestWatcher.updatedby:userid, FOIRequestWatcher.updated_at:datetime.now()}, synchronize_session = False) - db.session.commit() - return DefaultMethodResult(True,'Watchers disabled',ministryrequestid) - else: - return DefaultMethodResult(True,'No Watchers found',ministryrequestid) - -class FOIRequestWatcherSchema(ma.Schema): - class Meta: - fields = ('watcherid', 'foirequestid', 'ministryrequestid', 'watchedbygroup','watchedby','isactive','created_at','createdby','updated_at','updatedby') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRequests.py b/historical-search-api/request_api/models/FOIRequests.py deleted file mode 100644 index fcd9ea81a..000000000 --- a/historical-search-api/request_api/models/FOIRequests.py +++ /dev/null @@ -1,126 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey -from .db import db, ma -from datetime import datetime -from sqlalchemy.orm import relationship,backref -from .default_method_result import DefaultMethodResult -from sqlalchemy.dialects.postgresql import JSON, UUID -from sqlalchemy.sql.expression import distinct -from sqlalchemy import text -import logging - -import json -class FOIRequest(db.Model): - # Name of the table in our database - __tablename__ = 'FOIRequests' - # Defining the columns - foirequestid = db.Column(db.Integer, primary_key=True,autoincrement=True) - version = db.Column(db.Integer, primary_key=True,nullable=False) - requesttype = db.Column(db.String(15), unique=False, nullable=False) - receiveddate = db.Column(db.DateTime, default=datetime.now) - isactive = db.Column(db.Boolean, unique=False, nullable=False,default=True) - - initialdescription = db.Column(db.String(500), unique=False, nullable=True) - initialrecordsearchfromdate = db.Column(db.DateTime, nullable=True) - initialrecordsearchtodate = db.Column(db.DateTime, nullable=True) - - created_at = db.Column(db.DateTime, default=datetime.now) - updated_at = db.Column(db.DateTime, nullable=True) - createdby = db.Column(db.String(120), unique=False, nullable=True) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - wfinstanceid = db.Column(UUID(as_uuid=True), unique=False, nullable=True) - - #ForeignKey References - - applicantcategoryid = db.Column(db.Integer,ForeignKey('ApplicantCategories.applicantcategoryid')) - applicantcategory = relationship("ApplicantCategory",backref=backref("ApplicantCategories"),uselist=False) - - deliverymodeid = db.Column(db.Integer,ForeignKey('DeliveryModes.deliverymodeid')) - deliverymode = relationship("DeliveryMode",backref=backref("DeliveryModes"),uselist=False) - - receivedmodeid = db.Column(db.Integer,ForeignKey('ReceivedModes.receivedmodeid')) - receivedmode = relationship("ReceivedMode",backref=backref("ReceivedModes"),uselist=False) - - foirawrequestid = db.Column(db.Integer,unique=False, nullable=True) - - ministryRequests = relationship('FOIMinistryRequest', primaryjoin="and_(FOIRequest.foirequestid==FOIMinistryRequest.foirequest_id, " - "FOIRequest.version==FOIMinistryRequest.foirequestversion_id)") - - contactInformations = relationship('FOIRequestContactInformation', primaryjoin="and_(FOIRequest.foirequestid==FOIRequestContactInformation.foirequest_id, " - "FOIRequest.version==FOIRequestContactInformation.foirequestversion_id)") - - personalAttributes = relationship('FOIRequestPersonalAttribute', primaryjoin="and_(FOIRequest.foirequestid==FOIRequestPersonalAttribute.foirequest_id, " - "FOIRequest.version==FOIRequestPersonalAttribute.foirequestversion_id)") - - requestApplicants = relationship('FOIRequestApplicantMapping', primaryjoin="and_(FOIRequest.foirequestid==FOIRequestApplicantMapping.foirequest_id, " - "FOIRequest.version==FOIRequestApplicantMapping.foirequestversion_id)") - - - - - - @classmethod - def getrequest(cls,foirequestid): - request_schema = FOIRequestsSchema() - query = db.session.query(FOIRequest).filter_by(foirequestid=foirequestid).order_by(FOIRequest.version.desc()).first() - return request_schema.dump(query) - - @classmethod - def saverequest(cls,foirequest)->DefaultMethodResult: - db.session.add(foirequest) - db.session.commit() - ministryarr = [] - for ministry in foirequest.ministryRequests: - assignedministrygroup = ministry.assignedministrygroup if ministry.assignedministrygroup is not None else "" - assignedgroup = ministry.assignedgroup if ministry.assignedgroup is not None else "" - ministryarr.append({"id": ministry.foiministryrequestid, "foirequestid": ministry.foirequest_id, "axisrequestid": ministry.axisrequestid, "filenumber": ministry.filenumber, "status": ministry.requeststatus.name, "assignedministrygroup": assignedministrygroup, "assignedgroup": assignedgroup, "version":ministry.version}) - return DefaultMethodResult(True,'Request added',foirequest.foirequestid,ministryarr,foirequest.wfinstanceid) - - @classmethod - def updateWFInstance(cls, foirequestid, wfinstanceid, userid)->DefaultMethodResult: - if wfinstanceid not in (None, ""): - currequest = db.session.query(FOIRequest).filter_by(foirequestid=foirequestid).order_by(FOIRequest.version.desc()).first() - setattr(currequest,'wfinstanceid',wfinstanceid) - setattr(currequest,'updated_at',datetime.now().isoformat()) - setattr(currequest,'updatedby',userid) - db.session.commit() - return DefaultMethodResult(True,'Request updated',foirequestid) - return DefaultMethodResult(True,'wfinstanceid is None',foirequestid) - - @classmethod - def updateStatus(cls, foirequestid, updatedministries, userid)->DefaultMethodResult: - currequest = db.session.query(FOIRequest).filter_by(foirequestid=foirequestid).order_by(FOIRequest.version.desc()).first() - for ministry in currequest.ministryRequests: - for data in updatedministries: - if ministry.filenumber == data["filenumber"]: - ministry.requeststatusid = data["requeststatusid"] - ministry.updated_at = datetime.now().isoformat() - ministry.updatedby = userid - currequest.updated_at = datetime.now().isoformat() - currequest.updatedby = userid - db.session.commit() - return DefaultMethodResult(True,'Request updated',foirequestid) - - @classmethod - def getworkflowinstance(cls,requestid)->DefaultMethodResult: - request_schema = FOIRequestsSchema() - try: - sql = """select fr3.wfinstanceid, fr3.foirequestid from "FOIMinistryRequests" fr2, "FOIRequests" fr3 - where fr2.foirequest_id = fr3.foirequestid and fr2.foiministryrequestid=:requestid - order by fr3."version" desc limit 1""" - rs = db.session.execute(text(sql), {'requestid': requestid}) - for row in rs: - request_schema.__dict__.update({"wfinstanceid":row["wfinstanceid"] , "foirequestid": row["foirequestid"]}) - except Exception as ex: - logging.error(ex) - finally: - db.session.close() - return request_schema - -class FOIRequestsSchema(ma.Schema): - class Meta: - fields = ('foirequestid','version','foirawrequestid','requesttype','receiveddate','initialdescription', - 'initialrecordSearchFromDate','initialrecordsearchtodate','receivedmode.receivedmodeid', - 'deliverymode.deliverymodeid','receivedmode.name','deliverymode.name', - 'applicantcategory.applicantcategoryid','applicantcategory.name','wfinstanceid','ministryRequests') - \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIRestrictedMinistryRequests.py b/historical-search-api/request_api/models/FOIRestrictedMinistryRequests.py deleted file mode 100644 index 2935b13b4..000000000 --- a/historical-search-api/request_api/models/FOIRestrictedMinistryRequests.py +++ /dev/null @@ -1,56 +0,0 @@ -from flask.app import Flask -from sqlalchemy.sql.schema import ForeignKey -from .db import db, ma -from datetime import datetime -from sqlalchemy.orm import relationship,backref -from .default_method_result import DefaultMethodResult -from sqlalchemy.dialects.postgresql import JSON, UUID -from sqlalchemy.sql.expression import distinct -from sqlalchemy import text, and_, func -import logging -import json - - -class FOIRestrictedMinistryRequest(db.Model): - # Name of the table in our database - __tablename__ = 'FOIRestrictedMinistryRequests' - # Defining the columns - restrictionid = db.Column(db.Integer, primary_key=True,autoincrement=True) - ministryrequestid =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.foiministryrequestid')) - version =db.Column(db.Integer, db.ForeignKey('FOIMinistryRequests.version')) - type = db.Column(db.String(50), unique=False, nullable=False) - isrestricted = db.Column(db.Boolean, unique=False, nullable=False) - isactive = db.Column(db.Boolean, unique=False, nullable=False, default=True) - created_at = db.Column(db.DateTime, default=datetime.now) - createdby = db.Column(db.String(120), unique=False, nullable=False) - - @classmethod - def saverestrictedrequest(cls, ministryrequestid ,type,isrestricted,version, userid)->DefaultMethodResult: - restrictedrequest = FOIRestrictedMinistryRequest(ministryrequestid=ministryrequestid , version=version, type=type, isrestricted=isrestricted, isactive=True, createdby=userid) - db.session.add(restrictedrequest) - db.session.commit() - return DefaultMethodResult(True,'Restricted Request added') - - - @classmethod - def getrestricteddetails(cls,ministryrequestid ,type): - data_schema = FOIRestrictedMinistryRequestSchema() - request = db.session.query(FOIRestrictedMinistryRequest).filter_by(ministryrequestid=ministryrequestid,type=type,isactive=True).order_by(FOIRestrictedMinistryRequest.created_at.desc()).first() - return data_schema.dump(request) - - - @classmethod - def disablerestrictedrequests(cls, ministryrequestid, type, userid): - dbquery = db.session.query(FOIRestrictedMinistryRequest) - prevrestrictedrequestsbytype = dbquery.filter_by(ministryrequestid=ministryrequestid,type=type) - if(prevrestrictedrequestsbytype.count() > 0) : - prevrestrictedrequestsbytype.update({FOIRestrictedMinistryRequest.isactive:False}, synchronize_session = False) - db.session.commit() - return DefaultMethodResult(True,'Restricted Requests disabled',ministryrequestid) - else: - return DefaultMethodResult(True,'No Restricted Requests found',ministryrequestid) - - -class FOIRestrictedMinistryRequestSchema(ma.Schema): - class Meta: - fields = ('restrictionid', 'ministryrequestid', 'version', 'type','isrestricted','isactive','created_at','createdby') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FOIUsers.py b/historical-search-api/request_api/models/FOIUsers.py deleted file mode 100644 index 5faa2dfad..000000000 --- a/historical-search-api/request_api/models/FOIUsers.py +++ /dev/null @@ -1,57 +0,0 @@ -from .db import db, ma -from .default_method_result import DefaultMethodResult -from sqlalchemy.orm import relationship,backref -from datetime import datetime -from sqlalchemy import text - -class FOIUser(db.Model): - __tablename__ = 'FOIUsers' - # Defining the columns - foiuserid = db.Column(db.Integer, primary_key=True,autoincrement=True) - username = db.Column(db.Text, unique=False, nullable=False) - preferred_username = db.Column(db.Text, unique=False, nullable=False) - firstname = db.Column(db.Text, unique=False, nullable=False) - lastname = db.Column(db.Text, unique=False, nullable=False) - email = db.Column(db.Text, unique=False, nullable=False) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - created_at = db.Column(db.DateTime, default=datetime.now) - updated_at = db.Column(db.DateTime, nullable=True) - createdby = db.Column(db.String(120), unique=False, default='System') - updatedby = db.Column(db.String(120), unique=False, nullable=True) - - @classmethod - def createuser(cls, foiuser): - db.session.add(foiuser) - db.session.commit() - return DefaultMethodResult(True,'User added',foiuser.preferred_username) - - - @classmethod - def saveuser(cls, foiuser): - try: - dbquery = db.session.query(FOIUser) - _user = dbquery.filter_by(username=foiuser.username) - if(_user.count() > 0): - _user.update({FOIUser.firstname: foiuser.firstname, - FOIUser.lastname: foiuser.lastname, - FOIUser.email: foiuser.email, - FOIUser.preferred_username: foiuser.preferred_username, - FOIUser.isactive:foiuser.isactive, - FOIUser.updated_at:datetime.now(), FOIUser.updatedby:"System"}, synchronize_session = False) - db.session.commit() - return DefaultMethodResult(True,'User updated for Id',FOIUser.preferred_username) - else: - cls.createuser(foiuser) - except: - db.session.rollback() - raise - - @classmethod - def getall(cls): - user_schema = UserSchema(many=True) - query = db.session.query(FOIUser).order_by(FOIUser.foiuserid.asc()).all() - return user_schema.dump(query) - -class UserSchema(ma.Schema): - class Meta: - fields = ('foiuserid','username','preferred_username','firstname','lastname','email','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/FeeCode.py b/historical-search-api/request_api/models/FeeCode.py deleted file mode 100644 index 8a5dfaec1..000000000 --- a/historical-search-api/request_api/models/FeeCode.py +++ /dev/null @@ -1,44 +0,0 @@ -from __future__ import annotations - -from datetime import date, datetime - -from sqlalchemy import ForeignKey - -from .db import db, ma - - -class FeeCode(db.Model): - __tablename__ = 'FeeCodes' - # Defining the columns - fee_code_id = db.Column(db.Integer, primary_key=True, autoincrement=True) - code = db.Column(db.String(10), nullable=False) - description = db.Column(db.String(100), unique=False, nullable=True) - start_date = db.Column(db.Date, default=date.today(), nullable=False) - end_date = db.Column(db.Date, default=None, nullable=True) - fee = db.Column(db.Float, nullable=False) - revenue_account_id = db.Column(db.Integer, ForeignKey('RevenueAccounts.revenue_account_id'), nullable=False) - - @classmethod - def get_fee(cls, code: str, - valid_date: date - ) -> FeeCode: - """Given a code and date, return the active fee code.""" - if not valid_date: - valid_date = date.today() - - query = cls.query.filter_by(code=code). \ - filter(FeeCode.start_date <= valid_date). \ - filter((FeeCode.end_date.is_(None)) | (FeeCode.end_date >= valid_date)) - - return query.one_or_none() - - @classmethod - def find_by_id(cls, identifier: int) -> FeeCode: - """Return by id.""" - return cls.query.get(identifier) - - -class FeeCodeSchema(ma.Schema): - class Meta: - model = FeeCode - exclude = [] diff --git a/historical-search-api/request_api/models/NotificationTypes.py b/historical-search-api/request_api/models/NotificationTypes.py deleted file mode 100644 index 71be7a7d3..000000000 --- a/historical-search-api/request_api/models/NotificationTypes.py +++ /dev/null @@ -1,33 +0,0 @@ -from .db import db, ma -from .default_method_result import DefaultMethodResult -from sqlalchemy.orm import relationship,backref -from datetime import datetime -from sqlalchemy import text - -class NotificationType(db.Model): - __tablename__ = 'NotificationTypes' - # Defining the columns - notificationtypeid = db.Column(db.Integer, primary_key=True,autoincrement=True) - name = db.Column(db.String(100), unique=False, nullable=False) - description = db.Column(db.String(255), unique=False, nullable=False) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - notificationtypelabel = db.Column(db.String(100), unique=True, nullable=False) - - @classmethod - def getnotificationtypes(cls): - type_schema = NotificationTypeSchema(many=True) - query = db.session.query(NotificationType).filter_by(isactive=True).all() - return type_schema.dump(query) - - # create a class method that returns the notification type id - @classmethod - def getnotificationtypeid(cls, notificationtype): - type_schema = NotificationTypeSchema(many=False) - query = db.session.query(NotificationType).filter_by(name=notificationtype, isactive=True).first() - return type_schema.dump(query) if query is not None else None - - -class NotificationTypeSchema(ma.Schema): - class Meta: - fields = ('notificationtypeid', 'name', 'description','isactive', 'notificationtypelabel') - diff --git a/historical-search-api/request_api/models/NotificationUserTypes.py b/historical-search-api/request_api/models/NotificationUserTypes.py deleted file mode 100644 index c358ffbea..000000000 --- a/historical-search-api/request_api/models/NotificationUserTypes.py +++ /dev/null @@ -1,42 +0,0 @@ -from .db import db, ma -from .default_method_result import DefaultMethodResult -from sqlalchemy.orm import relationship,backref -from datetime import datetime -from sqlalchemy import text -import json -f = open('common/notificationusertypes.json', encoding="utf8") -notificationusertypes_cache = json.load(f) - - -class NotificationUserType(db.Model): - __tablename__ = 'NotificationUserTypes' - # Defining the columns - notificationusertypeid = db.Column(db.Integer, primary_key=True,autoincrement=True) - name = db.Column(db.String(100), unique=False, nullable=False) - description = db.Column(db.String(255), unique=False, nullable=False) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - notificationusertypelabel = db.Column(db.String(100), unique=True, nullable=False) - - @classmethod - def getnotificationusertypes(cls): - usertype_schema = NotificationUserTypeSchema(many=True) - query = db.session.query(NotificationUserType).filter_by(isactive=True).all() - return usertype_schema.dump(query) - - # create a class method that returns the notification type id - @classmethod - def getnotificationusertypesid(cls, notificationusertype): - notificationusertypelabel = None - for usertype in notificationusertypes_cache: - if (notificationusertypes_cache[usertype]['name'] == notificationusertype) or (notificationusertypes_cache[usertype]['notificationusertypelabel'] == notificationusertype): - notificationusertypelabel = notificationusertypes_cache[usertype]['notificationusertypelabel'] - if notificationusertypelabel is None: - return None - type_schema = NotificationUserTypeSchema(many=False) - query = db.session.query(NotificationUserType).filter_by(notificationusertypelabel=notificationusertypelabel, isactive=True).first() - return type_schema.dump(query) if query is not None else None - - -class NotificationUserTypeSchema(ma.Schema): - class Meta: - fields = ('notificationusertypeid', 'notificationusertypelabel', 'name', 'description','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/OIPCInquiryOutcomes.py b/historical-search-api/request_api/models/OIPCInquiryOutcomes.py deleted file mode 100644 index a613f0a7f..000000000 --- a/historical-search-api/request_api/models/OIPCInquiryOutcomes.py +++ /dev/null @@ -1,19 +0,0 @@ -from .db import db, ma - -class OIPCInquiryOutcomes(db.Model): - __tablename__ = 'OIPCInquiryOutcomes' - # Defining the columns - inquiryoutcomeid = db.Column(db.Integer, primary_key=True,autoincrement=True) - name = db.Column(db.String(100), unique=False, nullable=False) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - - @classmethod - def getinquiryoutcomes(cls): - type_schema = InquiryOutcomeSchema(many=True) - query = db.session.query(OIPCInquiryOutcomes).filter_by(isactive=True).all() - return type_schema.dump(query) - - -class InquiryOutcomeSchema(ma.Schema): - class Meta: - fields = ('inquiryoutcomeid', 'name','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/OIPCOutcomes.py b/historical-search-api/request_api/models/OIPCOutcomes.py deleted file mode 100644 index c891a88a8..000000000 --- a/historical-search-api/request_api/models/OIPCOutcomes.py +++ /dev/null @@ -1,19 +0,0 @@ -from .db import db, ma - -class OIPCOutcomes(db.Model): - __tablename__ = 'OIPCOutcomes' - # Defining the columns - outcomeid = db.Column(db.Integer, primary_key=True,autoincrement=True) - name = db.Column(db.String(100), unique=False, nullable=False) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - - @classmethod - def getoutcomes(cls): - type_schema = OutcomeSchema(many=True) - query = db.session.query(OIPCOutcomes).filter_by(isactive=True).all() - return type_schema.dump(query) - - -class OutcomeSchema(ma.Schema): - class Meta: - fields = ('outcomeid', 'name','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/OIPCReasons.py b/historical-search-api/request_api/models/OIPCReasons.py deleted file mode 100644 index 6eadc03b6..000000000 --- a/historical-search-api/request_api/models/OIPCReasons.py +++ /dev/null @@ -1,20 +0,0 @@ - -from .db import db, ma - -class OIPCReasons(db.Model): - __tablename__ = 'OIPCReasons' - # Defining the columns - reasonid = db.Column(db.Integer, primary_key=True,autoincrement=True) - name = db.Column(db.String(100), unique=False, nullable=False) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - - @classmethod - def getreasons(cls): - type_schema = ReasonSchema(many=True) - query = db.session.query(OIPCReasons).filter_by(isactive=True).all() - return type_schema.dump(query) - - -class ReasonSchema(ma.Schema): - class Meta: - fields = ('reasonid', 'name','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/OIPCReviewTypes.py b/historical-search-api/request_api/models/OIPCReviewTypes.py deleted file mode 100644 index bb5317de8..000000000 --- a/historical-search-api/request_api/models/OIPCReviewTypes.py +++ /dev/null @@ -1,19 +0,0 @@ -from .db import db, ma - -class OIPCReviewTypes(db.Model): - __tablename__ = 'OIPCReviewTypes' - # Defining the columns - reviewtypeid = db.Column(db.Integer, primary_key=True,autoincrement=True) - name = db.Column(db.String(100), unique=False, nullable=False) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - - @classmethod - def getreviewtypes(cls): - type_schema = ReviewTypeSchema(many=True) - query = db.session.query(OIPCReviewTypes).filter_by(isactive=True).all() - return type_schema.dump(query) - - -class ReviewTypeSchema(ma.Schema): - class Meta: - fields = ('reviewtypeid', 'name','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/OIPCReviewTypesReasons.py b/historical-search-api/request_api/models/OIPCReviewTypesReasons.py deleted file mode 100644 index 32fe9334b..000000000 --- a/historical-search-api/request_api/models/OIPCReviewTypesReasons.py +++ /dev/null @@ -1,43 +0,0 @@ -from .db import db, ma -from sqlalchemy.orm import relationship, backref -from sqlalchemy.sql.schema import ForeignKey -from sqlalchemy import text - -class OIPCReviewTypesReasons(db.Model): - __tablename__ = 'OIPCReviewTypesReasons' - # Defining the columns - reviewtypereasonid = db.Column(db.Integer, primary_key=True,autoincrement=True) - reviewtypeid = db.Column(db.Integer, ForeignKey('OIPCReviewTypes')) - relationship("OIPCReviewTypes", backref=backref("OIPCReviewTypes"), uselist=False) - reasonid = db.Column(db.Integer, ForeignKey('OIPCReasons')) - relationship("OIPCReasons", backref=backref("OIPCReasons"), uselist=False) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - - @classmethod - def getreviewtypeswithreasons(cls): - type_schema = ReviewTypeReasonSchema(many=True) - sql = ''' - SELECT types.reviewtypeid, reasons.reasonid, reasons.name as reason_name, types.name as type_name, typereason.isactive as reviewtypereason_isactive, reasons.isactive as reason_isactive, types.isactive as reviewtype_isactive FROM public."OIPCReviewTypesReasons" typereason - JOIN public."OIPCReasons" reasons - ON reasons.reasonid = typereason.reasonid - JOIN public."OIPCReviewTypes" types - ON types.reviewtypeid = typereason.reviewtypeid - ORDER BY reviewtypereasonid ASC - ''' - rs = db.session.execute(text(sql)) - reviewtypereasons = [] - for row in rs: - reviewtypereasons.append({ - "reviewtypeid": row["reviewtypeid"], - "reasonid": row["reasonid"], - "reason_name": row["reason_name"], - "type_name": row["type_name"], - "reviewtypereason_isactive": row["reviewtypereason_isactive"], - "reviewtype_isactive": row["reviewtype_isactive"], - "reason_isactive": row["reason_isactive"], - }) - return reviewtypereasons - -class ReviewTypeReasonSchema(ma.Schema): - class Meta: - fields = ('reviewtypereasonid', 'reviewtypeid', 'reasonid', 'isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/OIPCStatuses.py b/historical-search-api/request_api/models/OIPCStatuses.py deleted file mode 100644 index dce07aac2..000000000 --- a/historical-search-api/request_api/models/OIPCStatuses.py +++ /dev/null @@ -1,19 +0,0 @@ -from .db import db, ma - -class OIPCStatuses(db.Model): - __tablename__ = 'OIPCStatuses' - # Defining the columns - statusid = db.Column(db.Integer, primary_key=True,autoincrement=True) - name = db.Column(db.String(100), unique=False, nullable=False) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - - @classmethod - def getstatuses(cls): - type_schema = StatusSchema(many=True) - query = db.session.query(OIPCStatuses).filter_by(isactive=True).all() - return type_schema.dump(query) - - -class StatusSchema(ma.Schema): - class Meta: - fields = ('statusid', 'name','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/OperatingTeams.py b/historical-search-api/request_api/models/OperatingTeams.py deleted file mode 100644 index dbf1778c2..000000000 --- a/historical-search-api/request_api/models/OperatingTeams.py +++ /dev/null @@ -1,47 +0,0 @@ -from .db import db, ma -from .default_method_result import DefaultMethodResult -from sqlalchemy import text -import logging -class OperatingTeam(db.Model): - __tablename__ = 'OperatingTeams' - # Defining the columns - teamid = db.Column(db.Integer, primary_key=True,autoincrement=True) - name = db.Column(db.String(255), unique=False, nullable=False) - description = db.Column(db.String(500), unique=False, nullable=True) - type = db.Column(db.String(100), unique=False, nullable=True) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - - @classmethod - def getalloperatingteams(cls): - teams = [] - try: - sql = """select name, type from "OperatingTeams" ot where ot.isactive = true""" - rs = db.session.execute(text(sql)) - for row in rs: - teams.append({"name":row["name"], "type":row["type"]}) - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return teams - - @classmethod - def getteam(cls, team): - try: - sql = """select type, name from "OperatingTeams" ot - 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']} - except Exception as ex: - logging.error(ex) - raise ex - finally: - db.session.close() - return None - - -class OperatingTeamSchema(ma.Schema): - class Meta: - fields = ('teamid', 'name', 'description','type','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/Payment.py b/historical-search-api/request_api/models/Payment.py deleted file mode 100644 index e0b2610a2..000000000 --- a/historical-search-api/request_api/models/Payment.py +++ /dev/null @@ -1,58 +0,0 @@ -from __future__ import annotations - -from datetime import date, datetime - -from sqlalchemy import ForeignKey - -from .db import db, ma - - -class Payment(db.Model): - __tablename__ = 'Payments' - # Defining the columns - payment_id = db.Column(db.Integer, primary_key=True, autoincrement=True) - fee_code_id = db.Column(db.Integer, ForeignKey('FeeCodes.fee_code_id'), nullable=False) - quantity = db.Column(db.Integer, nullable=False) - total = db.Column(db.Float, nullable=False) - status = db.Column(db.String, nullable=False) - request_id = db.Column(db.Integer, nullable=False) - created_on = db.Column(db.DateTime, default=datetime.now, nullable=False) - completed_on = db.Column(db.DateTime, default=None, nullable=True) - paybc_url = db.Column(db.String, nullable=True) - response_url = db.Column(db.String, nullable=True) - order_id = db.Column(db.String(50), nullable=True) - transaction_number = db.Column(db.String(50), nullable=True) - - @classmethod - def find_by_id(cls, identifier: int) -> Payment: - """Return by id.""" - return cls.query.get(identifier) - - @classmethod - def find_paid_transaction(cls, transaction_number: str) -> Payment: - """Return by transaction_number.""" - a = cls.query.filter_by(transaction_number=transaction_number, status='PAID').one_or_none() - return a - - @classmethod - def find_failed_transaction(cls, transaction_number: str) -> Payment: - """Return by transaction_number.""" - a = cls.query.filter(Payment.transaction_number == transaction_number, Payment.status != 'PAID').one_or_none() - return a - - @staticmethod - def commit(): - """Commit the session.""" - db.session.commit() - - def flush(self): - """Save and flush.""" - db.session.add(self) - db.session.flush() - return self - - -class PaymentSchema(ma.Schema): - class Meta: - model = Payment - exclude = [] diff --git a/historical-search-api/request_api/models/PersonalInformationAttributes.py b/historical-search-api/request_api/models/PersonalInformationAttributes.py deleted file mode 100644 index 0e6ccd936..000000000 --- a/historical-search-api/request_api/models/PersonalInformationAttributes.py +++ /dev/null @@ -1,27 +0,0 @@ -from .db import db, ma -from .default_method_result import DefaultMethodResult - - -class PersonalInformationAttribute(db.Model): - __tablename__ = 'PersonalInformationAttributes' - # Defining the columns - attributeid = db.Column(db.Integer, primary_key=True,autoincrement=True) - name = db.Column(db.String(100), unique=False, nullable=False) - description = db.Column(db.String(255), unique=False, nullable=True) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - - @classmethod - def getpersonalattributes(cls): - requestortype_schema = PersonalInformationAttributesSchema(many=True) - query = db.session.query(PersonalInformationAttribute).filter_by(isactive=True).all() - return requestortype_schema.dump(query) - - @classmethod - def getpersonalattribute(cls,attribname): - deliverymode_schema = PersonalInformationAttributesSchema() - query = db.session.query(PersonalInformationAttribute).filter_by(name=attribname).first() - return deliverymode_schema.dump(query) - -class PersonalInformationAttributesSchema(ma.Schema): - class Meta: - fields = ('attributeid', 'name', 'description','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/ProgramAreaDivisionStages.py b/historical-search-api/request_api/models/ProgramAreaDivisionStages.py deleted file mode 100644 index d467edbda..000000000 --- a/historical-search-api/request_api/models/ProgramAreaDivisionStages.py +++ /dev/null @@ -1,23 +0,0 @@ -from .db import db, ma -from .default_method_result import DefaultMethodResult -from datetime import datetime - -class ProgramAreaDivisionStage(db.Model): - __tablename__ = 'ProgramAreaDivisionStages' - # Defining the columns - stageid = db.Column(db.Integer, primary_key=True,autoincrement=True) - name = db.Column(db.String(500), unique=False, nullable=False) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - created_at = db.Column(db.DateTime, default=datetime.now) - createdby = db.Column(db.String(120), unique=False, default='System') - - @classmethod - def getprogramareadivisionstages(cls): - divisionstage_schema = DivisionStageSchema(many=True) - query = db.session.query(ProgramAreaDivisionStage).filter_by(isactive=True).all() - return divisionstage_schema.dump(query) - - -class DivisionStageSchema(ma.Schema): - class Meta: - fields = ('stageid', 'name','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/ProgramAreaDivisions.py b/historical-search-api/request_api/models/ProgramAreaDivisions.py deleted file mode 100644 index 16c535135..000000000 --- a/historical-search-api/request_api/models/ProgramAreaDivisions.py +++ /dev/null @@ -1,125 +0,0 @@ -from .db import db, ma -from datetime import datetime as datetime2 -from .default_method_result import DefaultMethodResult -from sqlalchemy.orm import relationship,backref -from datetime import datetime -from sqlalchemy import text,or_ - -class ProgramAreaDivision(db.Model): - __tablename__ = 'ProgramAreaDivisions' - # Defining the columns - divisionid = db.Column(db.Integer, primary_key=True,autoincrement=True) - programareaid = db.Column(db.Integer, db.ForeignKey('ProgramAreas.programareaid')) - 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) - updatedby = db.Column(db.String(120), unique=False, nullable=True) - - @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,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, - issection=programareadivision["issection"], - parentid=programareadivision["parentid"], - specifictopersonalrequests=programareadivision["specifictopersonalrequests"] - ) - db.session.add(newprogramareadivision) - db.session.commit() - return DefaultMethodResult(True,'Division added successfully',newprogramareadivision.divisionid) - - @classmethod - def disableprogramareadivision(cls, divisionid, userid): - dbquery = db.session.query(ProgramAreaDivision) - division = dbquery.filter_by(divisionid=divisionid) - if(division.count() > 0) : - division.update({ProgramAreaDivision.isactive:False,ProgramAreaDivision.updatedby:userid, ProgramAreaDivision.updated_at:datetime2.now()}, synchronize_session = False) - db.session.commit() - return DefaultMethodResult(True,'Division disabled successfully',divisionid) - else: - return DefaultMethodResult(True,'No Division found',divisionid) - - @classmethod - 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: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: - return DefaultMethodResult(True,'No Division found',divisionid) - - @classmethod - def getdivisionbynameandprogramarea(cls, programareadivision): - division_schema = ProgramAreaDivisionSchema(many=True) - dbquery = db.session.query(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','issection','parentid', 'specifictopersonalrequests') \ No newline at end of file diff --git a/historical-search-api/request_api/models/ProgramAreas.py b/historical-search-api/request_api/models/ProgramAreas.py deleted file mode 100644 index be1179f42..000000000 --- a/historical-search-api/request_api/models/ProgramAreas.py +++ /dev/null @@ -1,45 +0,0 @@ -from .db import db, ma -from .default_method_result import DefaultMethodResult -from sqlalchemy import or_ - -class ProgramArea(db.Model): - __tablename__ = 'ProgramAreas' - # Defining the columns - programareaid = db.Column(db.Integer, primary_key=True,autoincrement=True) - name = db.Column(db.String(255), unique=False, nullable=False) - iaocode = db.Column(db.String(30), unique=False, nullable=True) - bcgovcode = db.Column(db.String(30), unique=False, nullable=True) - type = db.Column(db.String(100), unique=False, nullable=True) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - - @classmethod - def getprogramareas(cls): - programarea_schema = ProgramAreaSchema(many=True) - query = db.session.query(ProgramArea).filter_by(isactive=True).order_by(ProgramArea.iaocode.asc()).all() - return programarea_schema.dump(query) - - @classmethod - def getprogramareasforministryuser(cls, groups): - bcgovcodefilter = [] - for group in groups: - bcgovcodefilter.append(ProgramArea.bcgovcode == group.replace(' Ministry Team', '')) - - programarea_schema = ProgramAreaSchema(many=True) - query = db.session.query(ProgramArea).filter_by(isactive=True).filter(or_(*bcgovcodefilter)).order_by(ProgramArea.bcgovcode.asc()).all() - return programarea_schema.dump(query) - - @classmethod - def getprogramarea(cls,pgbcgovcode): - programarea_schema = ProgramAreaSchema() - query = db.session.query(ProgramArea).filter_by(bcgovcode=pgbcgovcode.upper()).first() - return programarea_schema.dump(query) - - @classmethod - def getprogramareabyiaocode(cls,iaocode): - programarea_schema = ProgramAreaSchema() - query = db.session.query(ProgramArea).filter_by(iaocode=iaocode.upper()).first() - return programarea_schema.dump(query) - -class ProgramAreaSchema(ma.Schema): - class Meta: - fields = ('programareaid', 'name', 'iaocode','bcgovcode','type','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/ReceivedModes.py b/historical-search-api/request_api/models/ReceivedModes.py deleted file mode 100644 index ebf81304a..000000000 --- a/historical-search-api/request_api/models/ReceivedModes.py +++ /dev/null @@ -1,27 +0,0 @@ -from .db import db, ma -from .default_method_result import DefaultMethodResult - - -class ReceivedMode(db.Model): - __tablename__ = 'ReceivedModes' - # Defining the columns - receivedmodeid = db.Column(db.Integer, primary_key=True,autoincrement=True) - name = db.Column(db.String(100), unique=False, nullable=False) - description = db.Column(db.String(255), unique=False, nullable=True) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - - @classmethod - def getreceivedmodes(cls): - receivedmode_schema = ReceivedModeSchema(many=True) - query = db.session.query(ReceivedMode).filter_by(isactive=True).all() - return receivedmode_schema.dump(query) - - @classmethod - def getreceivedmode(cls,receivedmode): - deliverymode_schema = ReceivedModeSchema() - query = db.session.query(ReceivedMode).filter_by(name=receivedmode).first() - return deliverymode_schema.dump(query) - -class ReceivedModeSchema(ma.Schema): - class Meta: - fields = ('receivedmodeid', 'name', 'description','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/RequestorType.py b/historical-search-api/request_api/models/RequestorType.py deleted file mode 100644 index a891cd6b4..000000000 --- a/historical-search-api/request_api/models/RequestorType.py +++ /dev/null @@ -1,27 +0,0 @@ -from .db import db, ma -from .default_method_result import DefaultMethodResult - - -class RequestorType(db.Model): - __tablename__ = 'RequestorTypes' - # Defining the columns - requestortypeid = db.Column(db.Integer, primary_key=True,autoincrement=True) - name = db.Column(db.String(100), unique=False, nullable=False) - description = db.Column(db.String(255), unique=False, nullable=True) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - - @classmethod - def getrequestortypes(cls): - requestortype_schema = RequestorTypeSchema(many=True) - query = db.session.query(RequestorType).filter_by(isactive=True).all() - return requestortype_schema.dump(query) - - @classmethod - def getrequestortype(cls,type): - programarea_schema = RequestorTypeSchema() - query = db.session.query(RequestorType).filter_by(name=type).first() - return programarea_schema.dump(query) - -class RequestorTypeSchema(ma.Schema): - class Meta: - fields = ('requestortypeid', 'name', 'description','isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/RevenueAccount.py b/historical-search-api/request_api/models/RevenueAccount.py deleted file mode 100644 index e8cd4fa78..000000000 --- a/historical-search-api/request_api/models/RevenueAccount.py +++ /dev/null @@ -1,23 +0,0 @@ -from __future__ import annotations - -from .db import db, ma - - -class RevenueAccount(db.Model): - __tablename__ = 'RevenueAccounts' - # Defining the columns - revenue_account_id = db.Column(db.Integer, primary_key=True, autoincrement=True) - client = db.Column(db.String(3), nullable=True) - responsibility_centre = db.Column(db.String(5), nullable=True) - service_line = db.Column(db.String(5), nullable=True) - stob = db.Column(db.String(4), nullable=True) - project_code = db.Column(db.String(7), nullable=True) - - @classmethod - def find_by_id(cls, identifier: int): - """Return by id.""" - return cls.query.get(identifier) - -class RevenueAccountSchema(ma.Schema): - class Meta: - model = RevenueAccount diff --git a/historical-search-api/request_api/models/SubjectCodes.py b/historical-search-api/request_api/models/SubjectCodes.py deleted file mode 100644 index 1549c44c1..000000000 --- a/historical-search-api/request_api/models/SubjectCodes.py +++ /dev/null @@ -1,32 +0,0 @@ -from .db import db, ma - -class SubjectCode(db.Model): - __tablename__ = 'SubjectCodes' - # Defining the columns - subjectcodeid = db.Column(db.Integer, primary_key=True,autoincrement=True) - name = db.Column(db.String(120), unique=False, nullable=False) - description = db.Column(db.String(255), unique=False, nullable=True) - isaxissubjectcode = db.Column(db.Boolean, unique=False, nullable=False) - isactive = db.Column(db.Boolean, unique=False, nullable=False) - - @classmethod - def getsubjectcodes(cls): - subjectcode_schema = SubjectCodeSchema(many=True) - query = db.session.query(SubjectCode).filter_by(isactive=True).order_by(SubjectCode.name.asc()).all() - return subjectcode_schema.dump(query) - - @classmethod - def getsubjectcodebyname(cls,subjectcode): - subjectcode_schema = SubjectCodeSchema() - query = db.session.query(SubjectCode).filter_by(name=subjectcode).first() - return subjectcode_schema.dump(query) - - @classmethod - def getsubjectcodebyid(cls,subjectcodeid): - subjectcode_schema = SubjectCodeSchema() - query = db.session.query(SubjectCode).filter_by(subjectcodeid=subjectcodeid).first() - return subjectcode_schema.dump(query) - -class SubjectCodeSchema(ma.Schema): - class Meta: - fields = ('subjectcodeid', 'name', 'description','isaxissubjectcode', 'isactive') \ No newline at end of file diff --git a/historical-search-api/request_api/models/UnopenedReport.py b/historical-search-api/request_api/models/UnopenedReport.py deleted file mode 100644 index 72a08b8ee..000000000 --- a/historical-search-api/request_api/models/UnopenedReport.py +++ /dev/null @@ -1,23 +0,0 @@ -from .db import db, ma -from .default_method_result import DefaultMethodResult -from sqlalchemy.orm import relationship,backref -from datetime import datetime -from sqlalchemy import text -from sqlalchemy.dialects.postgresql import JSON -import json - -class UnopenedReport(db.Model): - __tablename__ = 'UnopenedReport' - # Defining the columns - id = db.Column(db.Integer, primary_key=True,autoincrement=True) - rawrequestid = db.Column(db.Text, unique=False, nullable=False) - date = db.Column(db.Text, unique=False, nullable=False) - rank = db.Column(db.Text, unique=False, nullable=False) - potentialmatches = db.Column(JSON, unique=False, nullable=False) - - @classmethod - def bulkinsert(cls, rows): - for row in rows: - db.session.add(row) - db.session.commit() - return DefaultMethodResult(True,'Report Rows added',map(lambda row: row.rawrequestid, rows)) \ No newline at end of file diff --git a/historical-search-api/request_api/models/masterdataimport/applicantcategories.sql b/historical-search-api/request_api/models/masterdataimport/applicantcategories.sql deleted file mode 100644 index eee677d52..000000000 --- a/historical-search-api/request_api/models/masterdataimport/applicantcategories.sql +++ /dev/null @@ -1,9 +0,0 @@ -INSERT INTO public."ApplicantCategories"(name, description, isactive) VALUES ('Business', 'Business', True);commit; -INSERT INTO public."ApplicantCategories"(name, description, isactive) VALUES ('Individual', 'Individual', True);commit; -INSERT INTO public."ApplicantCategories"(name, description, isactive) VALUES ('Interest Group', 'Interest Group', True);commit; -INSERT INTO public."ApplicantCategories"(name, description, isactive) VALUES ('Law Firm', 'Law Firm', True);commit; -INSERT INTO public."ApplicantCategories"(name, description, isactive) VALUES ('Media', 'Media', True);commit; -INSERT INTO public."ApplicantCategories"(name, description, isactive) VALUES ('Political Party', 'Political Party', True);commit; -INSERT INTO public."ApplicantCategories"(name, description, isactive) VALUES ('Researcher', 'Researcher', True);commit; -INSERT INTO public."ApplicantCategories"(name, description, isactive) VALUES ('Other Governments', 'Other Governments', True);commit; -INSERT INTO public."ApplicantCategories"(name, description, isactive) VALUES ('Other Public Body', 'Other Public Body', True);commit; \ No newline at end of file diff --git a/historical-search-api/request_api/models/masterdataimport/deliverymodes.sql b/historical-search-api/request_api/models/masterdataimport/deliverymodes.sql deleted file mode 100644 index c5b28c61a..000000000 --- a/historical-search-api/request_api/models/masterdataimport/deliverymodes.sql +++ /dev/null @@ -1,2 +0,0 @@ -INSERT INTO public."DeliveryModes"(name, description, isactive) VALUES ('Secure File Transfer', 'Secure File Transfer', True);commit; -INSERT INTO public."DeliveryModes"(name, description, isactive) VALUES ('In Person Pick up', 'In Person Pick up', True);commit; \ No newline at end of file diff --git a/historical-search-api/request_api/models/masterdataimport/programareas.sql b/historical-search-api/request_api/models/masterdataimport/programareas.sql deleted file mode 100644 index 38c264910..000000000 --- a/historical-search-api/request_api/models/masterdataimport/programareas.sql +++ /dev/null @@ -1,37 +0,0 @@ - -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Advanced Education and Skills Training','BC GOV Ministry', True,'AEST','AED'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Agriculture and Food','BC GOV Ministry', True,'AGR','AGR'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Attorney General','BC GOV Ministry', True,'AG','MAG'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Children and Family Development','BC GOV Ministry', True,'MCF','CFD'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Citizens’ Services','BC GOV Ministry', True,'CITZ','CTZ'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Education and Childcare','BC GOV Ministry', True,'EDU','EDU'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Energy, Mines and Low Carbon Innovation','BC GOV Ministry', True,'EMLI','EML'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Environment and Climate Change Strategy','BC GOV Ministry', True,'ENV','MOE'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Finance','BC GOV Ministry', True,'FIN','FIN'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Forests','BC GOV Ministry', True,'FOR','FOR'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Health','BC GOV Ministry', True,'HLTH','HTH'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Indigenous Relations and Reconciliation','BC GOV Ministry', True,'IRR','IRR'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Declaration Act Secretariat','BC GOV Ministry', True,'DAS','DAS'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Jobs, Economic Recovery and Innovation','BC GOV Ministry', True,'JERI','JER'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Labour','BC GOV Ministry', True,'LBR','LBR'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Lands, Water and Resource Stewardship','BC GOV Ministry', True,'LWR','LWR'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Mental Health and Addictions','BC GOV Ministry', True,'MMHA','MHA'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Municipal Affairs','BC GOV Ministry', True,'MUNI','MMA'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Public Safety and Solicitor General','BC GOV Ministry', True,'PSSG','PSS'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Social Development and Poverty Reduction','BC GOV Ministry', True,'SDPR','MSD'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Tourism, Arts, Culture and Sport','BC GOV Ministry', True,'TACS','TAC'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Ministry of Transportation and Infrastructure','BC GOV Ministry', True,'TRAN','TRA'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('BC Coroners Service','Other', True,'OCC','OCC'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('BC Public Service Agency','Other', True,'PSA','PSA'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Board Resourcing and Development Office','Other', True,'BRD','BRD'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Community Living BC','Other', True,'CLB','CLB'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Crown Agencies Secretariat','Other', True,'CAS','CAS'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Emergency Management BC','Other', True,'EMBC','EMB'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Environmental Assessment Office','Other', True,'EAO','EAO'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Government Communications and Public Engagement','Other', True,'GCPE','GCP'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Independent Investigations Office','Other', True,'IIO','IIO'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Office of the Premier','Other', True,'PREM','OOP'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Liquor Distribution Branch','Other', True,'LDB','LDB'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Transportation Investment Corporation','Other', True,'TIC','TIC'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Order of British Columbia Advisory Council','Other', True,'OBC','OBC'); -INSERT INTO public."ProgramAreas"(name, type, isactive, bcgovcode, iaocode) VALUES ('Medal of Good Citizenship Selection Committee','Other', True,'MGC','MGC'); diff --git a/historical-search-api/request_api/models/masterdataimport/receivedmodes.sql b/historical-search-api/request_api/models/masterdataimport/receivedmodes.sql deleted file mode 100644 index 59b4d2c48..000000000 --- a/historical-search-api/request_api/models/masterdataimport/receivedmodes.sql +++ /dev/null @@ -1,4 +0,0 @@ -INSERT INTO public."ReceivedModes"(name, description, isactive) VALUES ('Email', 'Email', True);commit; -INSERT INTO public."ReceivedModes"(name, description, isactive) VALUES ('Fax', 'Fax', True);commit; -INSERT INTO public."ReceivedModes"(name, description, isactive) VALUES ('Mail', 'Mail', True);commit; -INSERT INTO public."ReceivedModes"(name, description, isactive) VALUES ('Online Form', 'Online Form', True);commit; \ No newline at end of file diff --git a/historical-search-api/request_api/models/samplequeries/businessteamdata.sql b/historical-search-api/request_api/models/samplequeries/businessteamdata.sql deleted file mode 100644 index 29a754a25..000000000 --- a/historical-search-api/request_api/models/samplequeries/businessteamdata.sql +++ /dev/null @@ -1,38 +0,0 @@ ---get latest version of all ministry ids where assignedgroup = 'Business Team' -SELECT foiministryrequestid, version FROM -public."FOIMinistryRequests" JOIN ( - SELECT foiministryrequestid, MAX(version) as version - FROM public."FOIMinistryRequests" where assignedgroup = 'Business Team' - GROUP BY foiministryrequestid - ) max_version USING (foiministryrequestid, version); - - -select * from public."FOIMinistryRequests" where assignedgroup = 'Business Team' order by foiministryrequestid, version desc -select assignedgroup,* from public."FOIMinistryRequests" where foiministryrequestid in (5214,5215,5216,5255,5257,5258,5260) order by foiministryrequestid, version desc - - -select * from public."FOIRawRequestWatchers" where watchedbygroup = 'Business Team'; -select * from public."FOIRequestWatchers" where watchedbygroup = 'Business Team'; - - - --- only the latest version will get updated. -UPDATE public."FOIMinistryRequests" ministryrequests SET assignedgroup = 'Central Team' FROM ( - SELECT foiministryrequestid, MAX(version) as version - FROM public."FOIMinistryRequests" where assignedgroup = 'Business Team' - GROUP BY foiministryrequestid - ) max_version WHERE ministryrequests.foiministryrequestid = max_version.foiministryrequestid and ministryrequests.version = max_version.version; - -UPDATE public."FOIRequestWatchers" SET watchedbygroup = 'Central Team' WHERE watchedbygroup = 'Business Team'; -UPDATE public."FOIRawRequestWatchers" SET watchedbygroup = 'Central Team' WHERE watchedbygroup = 'Business Team'; - - ---CAMUNDA DB --- -select * from act_ru_variable where text_ like '%Business%'; -select * from act_ru_authorization where group_id_ like '%Business%'; -select * from act_ru_identitylink where group_id_ like '%Business%'; - - -UPDATE act_ru_variable SET text_ = replace(text_,'Business','Central') where text_ like '%Business%'; -UPDATE act_ru_authorization SET group_id_ = 'Central Team' where group_id_ like '%Business%'; -UPDATE act_ru_identitylink SET group_id_ = 'Central Team' where group_id_ like '%Business%'; \ No newline at end of file diff --git a/historical-search-api/request_api/models/samplequeries/findcolumnvalue.sql b/historical-search-api/request_api/models/samplequeries/findcolumnvalue.sql deleted file mode 100644 index f0fc29264..000000000 --- a/historical-search-api/request_api/models/samplequeries/findcolumnvalue.sql +++ /dev/null @@ -1,21 +0,0 @@ -CREATE OR REPLACE FUNCTION search_whole_db(_like_pattern text) - RETURNS TABLE(_tbl regclass, _ctid tid) AS -$func$ -BEGIN - FOR _tbl IN - SELECT c.oid::regclass - FROM pg_class c - JOIN pg_namespace n ON n.oid = relnamespace - WHERE c.relkind = 'r' -- only tables --- AND n.nspname !~ '^(pg_|information_schema)' -- exclude system schemas - ORDER BY n.nspname, c.relname - LOOP - RETURN QUERY EXECUTE format( - 'SELECT $1, ctid FROM %s t WHERE t::text ~~ %L' - , _tbl, '%' || _like_pattern || '%') - USING _tbl; - END LOOP; -END -$func$ LANGUAGE plpgsql; - -SELECT distinct _tbl FROM search_whole_db('Business Team'); \ No newline at end of file diff --git a/historical-search-api/request_api/models/samplequeries/wfmigration.sql b/historical-search-api/request_api/models/samplequeries/wfmigration.sql deleted file mode 100644 index 299e60d92..000000000 --- a/historical-search-api/request_api/models/samplequeries/wfmigration.sql +++ /dev/null @@ -1,406 +0,0 @@ -/* -********************************************************************************************************************* -The purpose of this document is to capture the instructions to do the migration of workflow instances for Fee. - -Change Details: -New FOI Request Processes (related to Fee) -1. FOI Fee Processing -2. FOI Email Processing -Modified FOI Request Processes (related to Fee) -1. FOI Request Processing (Key: foi-request-processing) - -********************************************************************************************************************** -Below given instructions are for migrating foi-request-processing to latest deployed definition version -Note#1: The approach that would be taken for migration is incremental (semi-automation) with manual verification between the steps. -Note#2: The below given instructions to be followed after deploying the WF -********************************************************************************************************************** -*/ - -/* -Step-0 (Login & Navigate to Processes): Login to Camunda application as admin - > Navigate to Cockpit -> Navigate to Processes -*/ - -/* -Step-1 (Identify the latest deployed version - FOI Raw Request Processing) : Click on Process "FOI Raw Request Processing" and get the latest version. -*/ - -/* -Step-2 Migrate Variables from version X to Y -*/ - -do $$ -declare - process_def_key varchar(25) := 'foi-request'; - ru_variable_counter integer := 0; - X_process_definition_id varchar(64) := ''; - Y_process_definition_id varchar(64) := ''; - X integer := 0; - Y integer := 0; - -begin - select count(proc_def_id_) into ru_variable_counter from act_ru_variable where proc_def_id_ = (select id_ from act_re_procdef where key_ = process_def_key and version_ = X); - raise notice'act_ru_variable has % proc_def_id_s found', ru_variable_counter; - - select id_ into X_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = X; - select id_ into Y_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = Y; - raise notice'The X_process_definition_id is %, The Y_process_definition_id is %', X_process_definition_id, Y_process_definition_id; - - if X_process_definition_id is NOT NULL and Y_process_definition_id is NOT NULL and ru_variable_counter > 0 then - - raise notice'inside if: act_ru_variable has % proc_def_id_s found', ru_variable_counter; - - update act_ru_variable set proc_def_id_ = Y_process_definition_id where proc_def_id_ = X_process_definition_id; - - end if; -end $$; -/* -Step-3 Migrate Execution instances from version X to Y -*/ -do $$ -declare - process_def_key varchar(25) := 'foi-request'; - ru_execution_counter integer := 0; - X_process_definition_id varchar(64) := ''; - Y_process_definition_id varchar(64) := ''; - X integer := 0; - Y integer := 0; - -begin - select count(proc_def_id_) into ru_execution_counter from act_ru_execution where proc_def_id_ = (select id_ from act_re_procdef where key_ = process_def_key and version_ = X); - raise notice'act_ru_execution has % proc_def_id_s found', ru_execution_counter; - - select id_ into X_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = X; - select id_ into Y_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = Y; - raise notice'The X_process_definition_id is %, The Y_process_definition_id is %', X_process_definition_id, Y_process_definition_id; - - if X_process_definition_id is NOT NULL and Y_process_definition_id is NOT NULL and ru_execution_counter > 0 then - - raise notice'inside if: act_ru_execution has % proc_def_id_s found', ru_execution_counter; - - update act_ru_execution set proc_def_id_ = Y_process_definition_id where proc_def_id_ = X_process_definition_id; - - end if; -end $$; - -/* -Step-4 Migrate Jobs from version X to Y -*/ - -do $$ -declare - process_def_key varchar(25) := 'foi-request'; - ru_job_counter integer := 0; - X_process_definition_id varchar(64) := ''; - Y_process_definition_id varchar(64) := ''; - X integer := 0; - Y integer := 0; - -begin - select count(process_def_id_) into ru_job_counter from act_ru_job where process_def_id_ = (select id_ from act_re_procdef where key_ = process_def_key and version_ = X); - raise notice'act_ru_job has % proc_def_id_s found', ru_job_counter; - - select id_ into X_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = X; - select id_ into Y_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = Y; - raise notice'The X_process_definition_id is %, The Y_process_definition_id is %', X_process_definition_id, Y_process_definition_id; - - if X_process_definition_id is NOT NULL and Y_process_definition_id is NOT NULL and ru_job_counter > 0 then - - raise notice'inside if: act_ru_job has % proc_def_id_s found', ru_job_counter; - - update act_ru_job set process_def_id_ = Y_process_definition_id where process_def_id_ = X_process_definition_id; - - end if; -end $$; - -/* -Step-5 Migrate Tasks from version X to Y -*/ - -do $$ -declare - process_def_key varchar(25) := 'foi-request'; - ru_task_counter integer := 0; - X_process_definition_id varchar(64) := ''; - Y_process_definition_id varchar(64) := ''; - X integer := 0; - Y integer := 0; - -begin - select count(proc_def_id_) into ru_task_counter from act_ru_task where proc_def_id_ = (select id_ from act_re_procdef where key_ = process_def_key and version_ = X); - raise notice'act_ru_task has % proc_def_id_s found', ru_task_counter; - - select id_ into X_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = X; - select id_ into Y_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = Y; - raise notice'The X_process_definition_id is %, The Y_process_definition_id is %', X_process_definition_id, Y_process_definition_id; - - if X_process_definition_id is NOT NULL and Y_process_definition_id is NOT NULL and ru_task_counter > 0 then - - raise notice'inside if: act_ru_task has % proc_def_id_s found', ru_task_counter; - - update act_ru_task set proc_def_id_ = Y_process_definition_id where proc_def_id_ = X_process_definition_id; - - end if; -end $$; - -/* -Step-1 (Identify the latest deployed version - FOI Request Processing) : Click on Process "FOI Request Processing" and get the latest version. -*/ - -/* -Step-2 Migrate Variables from version X to Y -*/ - -do $$ -declare - process_def_key varchar(25) := 'foi-request-processing'; - ru_variable_counter integer := 0; - X_process_definition_id varchar(64) := ''; - Y_process_definition_id varchar(64) := ''; - X integer := 0; - Y integer := 0; - -begin - select count(proc_def_id_) into ru_variable_counter from act_ru_variable where proc_def_id_ = (select id_ from act_re_procdef where key_ = process_def_key and version_ = X); - raise notice'act_ru_variable has % proc_def_id_s found', ru_variable_counter; - - select id_ into X_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = X; - select id_ into Y_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = Y; - raise notice'The X_process_definition_id is %, The Y_process_definition_id is %', X_process_definition_id, Y_process_definition_id; - - if X_process_definition_id is NOT NULL and Y_process_definition_id is NOT NULL and ru_variable_counter > 0 then - - raise notice'inside if: act_ru_variable has % proc_def_id_s found', ru_variable_counter; - - update act_ru_variable set proc_def_id_ = Y_process_definition_id where proc_def_id_ = X_process_definition_id; - - end if; -end $$; -/* -Step-3 Migrate Execution instances from version X to Y -*/ -do $$ -declare - process_def_key varchar(25) := 'foi-request-processing'; - ru_execution_counter integer := 0; - X_process_definition_id varchar(64) := ''; - Y_process_definition_id varchar(64) := ''; - X integer := 0; - Y integer := 0; - -begin - select count(proc_def_id_) into ru_execution_counter from act_ru_execution where proc_def_id_ = (select id_ from act_re_procdef where key_ = process_def_key and version_ = X); - raise notice'act_ru_execution has % proc_def_id_s found', ru_execution_counter; - - select id_ into X_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = X; - select id_ into Y_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = Y; - raise notice'The X_process_definition_id is %, The Y_process_definition_id is %', X_process_definition_id, Y_process_definition_id; - - if X_process_definition_id is NOT NULL and Y_process_definition_id is NOT NULL and ru_execution_counter > 0 then - - raise notice'inside if: act_ru_execution has % proc_def_id_s found', ru_execution_counter; - - update act_ru_execution set proc_def_id_ = Y_process_definition_id where proc_def_id_ = X_process_definition_id; - - end if; -end $$; - -/* -Step-4 Migrate Jobs from version X to Y -*/ - -do $$ -declare - process_def_key varchar(25) := 'foi-request-processing'; - ru_job_counter integer := 0; - X_process_definition_id varchar(64) := ''; - Y_process_definition_id varchar(64) := ''; - X integer := 0; - Y integer := 0; - -begin - select count(process_def_id_) into ru_job_counter from act_ru_job where process_def_id_ = (select id_ from act_re_procdef where key_ = process_def_key and version_ = X); - raise notice'act_ru_job has % proc_def_id_s found', ru_job_counter; - - select id_ into X_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = X; - select id_ into Y_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = Y; - raise notice'The X_process_definition_id is %, The Y_process_definition_id is %', X_process_definition_id, Y_process_definition_id; - - if X_process_definition_id is NOT NULL and Y_process_definition_id is NOT NULL and ru_job_counter > 0 then - - raise notice'inside if: act_ru_job has % proc_def_id_s found', ru_job_counter; - - update act_ru_job set process_def_id_ = Y_process_definition_id where process_def_id_ = X_process_definition_id; - - end if; -end $$; - -/* -Step-5 Migrate Tasks from version X to Y -*/ - -do $$ -declare - process_def_key varchar(25) := 'foi-request-processing'; - ru_task_counter integer := 0; - X_process_definition_id varchar(64) := ''; - Y_process_definition_id varchar(64) := ''; - X integer := 0; - Y integer := 0; - -begin - select count(proc_def_id_) into ru_task_counter from act_ru_task where proc_def_id_ = (select id_ from act_re_procdef where key_ = process_def_key and version_ = X); - raise notice'act_ru_task has % proc_def_id_s found', ru_task_counter; - - select id_ into X_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = X; - select id_ into Y_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = Y; - raise notice'The X_process_definition_id is %, The Y_process_definition_id is %', X_process_definition_id, Y_process_definition_id; - - if X_process_definition_id is NOT NULL and Y_process_definition_id is NOT NULL and ru_task_counter > 0 then - - raise notice'inside if: act_ru_task has % proc_def_id_s found', ru_task_counter; - - update act_ru_task set proc_def_id_ = Y_process_definition_id where proc_def_id_ = X_process_definition_id; - - end if; -end $$; - -/* -Step-6[SPECIAL-INSTRUCTION] PURPOSE: Create entries for correspondence (Specific to Fee) -Note: This to be ran after completion of Step 2 to 5 i.e. migration of all instances -*/ -INSERT INTO public.act_ru_event_subscr (id_, rev_, event_type_, event_name_, execution_id_, -proc_inst_id_, activity_id_, configuration_, created_, tenant_id_) -select tmp1.proc_inst_id_, 1, 'message', 'foi-iao-correnspodence', ext.execution_id_, tmp1.proc_inst_id_, -'correnspodance', NULL, now(), null from -(select distinct proc_inst_id_ from act_ru_variable arv where name_='status' and text_ <> 'Open' -and proc_inst_id_ not in (select proc_inst_id_ from act_ru_event_subscr arv where event_name_='foi-iao-correnspodence')) as tmp1, -act_ru_task ext where tmp1.proc_inst_id_ = ext.proc_inst_id_ -and name_ like '%IAO'; -commit; - -/* -This needs to be run only in the marshals. - */ -update public.act_ru_event_subscr set activity_id_ = 'Event_1tvpamu' where activity_id_ = 'complete'; - -/* -Step-1 (Identify the latest deployed version - FOI Fee Processing) : Click on Process "FOI Fee Processing" and get the latest version. -*/ - -/* -Step-2 Migrate Variables from version X to Y -*/ - -do $$ -declare - process_def_key varchar(25) := 'foi-fee-processing'; - ru_variable_counter integer := 0; - X_process_definition_id varchar(64) := ''; - Y_process_definition_id varchar(64) := ''; - X integer := 0; - Y integer := 0; - -begin - select count(proc_def_id_) into ru_variable_counter from act_ru_variable where proc_def_id_ = (select id_ from act_re_procdef where key_ = process_def_key and version_ = X); - raise notice'act_ru_variable has % proc_def_id_s found', ru_variable_counter; - - select id_ into X_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = X; - select id_ into Y_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = Y; - raise notice'The X_process_definition_id is %, The Y_process_definition_id is %', X_process_definition_id, Y_process_definition_id; - - if X_process_definition_id is NOT NULL and Y_process_definition_id is NOT NULL and ru_variable_counter > 0 then - - raise notice'inside if: act_ru_variable has % proc_def_id_s found', ru_variable_counter; - - update act_ru_variable set proc_def_id_ = Y_process_definition_id where proc_def_id_ = X_process_definition_id; - - end if; -end $$; -/* -Step-3 Migrate Execution instances from version X to Y -*/ -do $$ -declare - process_def_key varchar(25) := 'foi-fee-processing'; - ru_execution_counter integer := 0; - X_process_definition_id varchar(64) := ''; - Y_process_definition_id varchar(64) := ''; - X integer := 0; - Y integer := 0; - -begin - select count(proc_def_id_) into ru_execution_counter from act_ru_execution where proc_def_id_ = (select id_ from act_re_procdef where key_ = process_def_key and version_ = X); - raise notice'act_ru_execution has % proc_def_id_s found', ru_execution_counter; - - select id_ into X_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = X; - select id_ into Y_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = Y; - raise notice'The X_process_definition_id is %, The Y_process_definition_id is %', X_process_definition_id, Y_process_definition_id; - - if X_process_definition_id is NOT NULL and Y_process_definition_id is NOT NULL and ru_execution_counter > 0 then - - raise notice'inside if: act_ru_execution has % proc_def_id_s found', ru_execution_counter; - - update act_ru_execution set proc_def_id_ = Y_process_definition_id where proc_def_id_ = X_process_definition_id; - - end if; -end $$; - -/* -Step-4 Migrate Jobs from version X to Y -*/ - -do $$ -declare - process_def_key varchar(25) := 'foi-fee-processing'; - ru_job_counter integer := 0; - X_process_definition_id varchar(64) := ''; - Y_process_definition_id varchar(64) := ''; - X integer := 0; - Y integer := 0; - -begin - select count(process_def_id_) into ru_job_counter from act_ru_job where process_def_id_ = (select id_ from act_re_procdef where key_ = process_def_key and version_ = X); - raise notice'act_ru_job has % proc_def_id_s found', ru_job_counter; - - select id_ into X_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = X; - select id_ into Y_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = Y; - raise notice'The X_process_definition_id is %, The Y_process_definition_id is %', X_process_definition_id, Y_process_definition_id; - - if X_process_definition_id is NOT NULL and Y_process_definition_id is NOT NULL and ru_job_counter > 0 then - - raise notice'inside if: act_ru_job has % proc_def_id_s found', ru_job_counter; - - update act_ru_job set process_def_id_ = Y_process_definition_id where process_def_id_ = X_process_definition_id; - - end if; -end $$; - -/* -Step-5 Migrate Tasks from version X to Y -*/ - -do $$ -declare - process_def_key varchar(25) := 'foi-fee-processing'; - ru_task_counter integer := 0; - X_process_definition_id varchar(64) := ''; - Y_process_definition_id varchar(64) := ''; - X integer := 0; - Y integer := 0; - -begin - select count(proc_def_id_) into ru_task_counter from act_ru_task where proc_def_id_ = (select id_ from act_re_procdef where key_ = process_def_key and version_ = X); - raise notice'act_ru_task has % proc_def_id_s found', ru_task_counter; - - select id_ into X_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = X; - select id_ into Y_process_definition_id from act_re_procdef where key_ = process_def_key and version_ = Y; - raise notice'The X_process_definition_id is %, The Y_process_definition_id is %', X_process_definition_id, Y_process_definition_id; - - if X_process_definition_id is NOT NULL and Y_process_definition_id is NOT NULL and ru_task_counter > 0 then - - raise notice'inside if: act_ru_task has % proc_def_id_s found', ru_task_counter; - - update act_ru_task set proc_def_id_ = Y_process_definition_id where proc_def_id_ = X_process_definition_id; - - end if; -end $$; \ No newline at end of file diff --git a/historical-search-api/request_api/models/views/FOINotifications.py b/historical-search-api/request_api/models/views/FOINotifications.py deleted file mode 100644 index dc14ffe7b..000000000 --- a/historical-search-api/request_api/models/views/FOINotifications.py +++ /dev/null @@ -1,21 +0,0 @@ -from .db import db, ma -from sqlalchemy.dialects.postgresql import insert, base - -class FOINotifications(db.Model): - # Name of the table in our database - __tablename__ = 'v_FOINotifications' - # Defining the columns - id = db.Column(db.String(100), primary_key=True) - idnumber = db.Column(db.String(100)) - axisnumber = db.Column(db.String(100)) - notification = db.Column(db.Text) - notificationtypeid = db.Column(db.Integer) - userid = db.Column(db.String(500)) - createdby = db.Column(db.String(500)) - created_at = db.Column(db.DateTime) - createdatformatted = db.Column(db.DateTime) - userformatted = db.Column(db.Text) - creatorformatted = db.Column(db.Text) - notificationtype = db.Column(db.String(500)) - notificationtypelabel = db.Column(db.String(500)) - diff --git a/historical-search-api/request_api/models/views/FOIRawRequests.py b/historical-search-api/request_api/models/views/FOIRawRequests.py deleted file mode 100644 index ac17d19c0..000000000 --- a/historical-search-api/request_api/models/views/FOIRawRequests.py +++ /dev/null @@ -1,23 +0,0 @@ -from .db import db, ma -from sqlalchemy.dialects.postgresql import insert, base - -class FOIRawRequests(db.Model): - # Name of the table in our database - __tablename__ = 'v_FOIRawRequests' - # Defining the columns - rawrequestid = db.Column(db.Integer, primary_key=True) - version = db.Column(db.Integer) - axisrequestid = db.Column(db.String(500)) - foirequest_id = db.Column(db.Integer) - ministryrequestid = db.Column(db.Integer) - status = db.Column(db.String(100)) - assignedto = db.Column(db.String(500)) - assignedgroup = db.Column(db.String(500)) - assignedministryperson = db.Column(db.String(500)) - assignedministrygroup = db.Column(db.String(500)) - assignedtoformatted = db.Column(db.Text) - ministryassignedtoformatted = db.Column(db.String(500)) - description = db.Column(db.Text) - isiaorestricted = db.Column(db.Boolean) - crtid = db.Column(db.Text) - diff --git a/historical-search-api/request_api/models/views/FOIRequests.py b/historical-search-api/request_api/models/views/FOIRequests.py deleted file mode 100644 index baa3395f3..000000000 --- a/historical-search-api/request_api/models/views/FOIRequests.py +++ /dev/null @@ -1,24 +0,0 @@ -from .db import db, ma -from sqlalchemy.dialects.postgresql import insert, base - -class FOIRequests(db.Model): - # Name of the table in our database - __tablename__ = 'v_FOIRequests' - # Defining the columns - foiministryrequestid = db.Column(db.Integer, primary_key=True) - version = db.Column(db.Integer) - foirequest_id = db.Column(db.Integer) - rawrequestid = db.Column(db.Integer) - axisrequestid = db.Column(db.String(500)) - requeststatusid = db.Column(db.Integer) - status = db.Column(db.String(100)) - assignedto = db.Column(db.String(500)) - assignedgroup = db.Column(db.String(500)) - assignedministryperson = db.Column(db.String(500)) - assignedministrygroup = db.Column(db.String(500)) - assignedtoformatted = db.Column(db.Text) - ministryassignedtoformatted = db.Column(db.String(500)) - description = db.Column(db.Text) - crtid = db.Column(db.Text) - requeststatuslabel = db.Column(db.String(50)) - diff --git a/historical-search-api/request_api/resources/request.py b/historical-search-api/request_api/resources/request.py deleted file mode 100644 index 88fdc36da..000000000 --- a/historical-search-api/request_api/resources/request.py +++ /dev/null @@ -1,111 +0,0 @@ -# Copyright © 2021 Province of British Columbia -# -# Licensed under the Apache License, Version 2.0 (the 'License'); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an 'AS IS' BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""API endpoints for managing a FOI Requests resource.""" - - -from flask import g, request -from flask_restx import Namespace, Resource -from flask_expects_json import expects_json -from flask_cors import cross_origin -from request_api.auth import auth, AuthHelper -from request_api.tracer import Tracer -from request_api.utils.util import cors_preflight, allowedorigins,str_to_bool,canrestictdata -from request_api.exceptions import BusinessException -# from request_api.services.rawrequestservice import rawrequestservice -# from request_api.services.documentservice import documentservice -# from request_api.services.eventservice import eventservice -# from request_api.services.unopenedreportservice import unopenedreportservice -from request_api.services.historicalrequestservice import historicalrequestservice -# from request_api.utils.enums import StateName -import json -import asyncio -from jose import jwt as josejwt -import holidays -from datetime import datetime, timedelta -import os -import pytz - - - -API = Namespace('FOIRawRequests', description='Endpoints for FOI request management') -TRACER = Tracer.get_instance() -with open('request_api/schemas/schemas/rawrequest.json') as f: - schema = json.load(f) - -INVALID_REQUEST_ID = 'Invalid Request Id' - -SHORT_DATE_FORMAT = '%Y-%m-%d' -LONG_DATE_FORMAT = '%Y-%m-%d %H:%M:%S' - -@cors_preflight('GET,POST,OPTIONS') -@API.route('/foihistoricalrequest/') -class FOIRawRequest(Resource): - """Retrieve historical request details from EDW""" - - @staticmethod - @TRACER.trace() - @cross_origin(origins=allowedorigins()) - # @auth.require - def get(requestid): - try : - jsondata = {} - statuscode = 200 - # requestidisinteger = int(requestid) - # if requestidisinteger : - # baserequestinfo = rawrequestservice().getrawrequest(requestid) - - # assignee = baserequestinfo['assignedTo'] - # isiaorestricted = baserequestinfo['isiaorestricted'] - # # print('Request # {0} Assigned to {1} and is restricted {2} '.format(requestid,assignee,isiaorestricted)) - # if(isiaorestricted and canrestictdata(requestid,assignee,isiaorestricted,True)): - # jsondata = {'status': 401, 'message':'Restricted Request'} - # statuscode = 401 - # else: - # jsondata = json.dumps(baserequestinfo) - # statuscode = 200 - -# select -# rt.requesttypename, -# rm.receivedmodename, -# dm.deliverymodename, -# rqt.requestertypename, -# r.firstname, r.lastname, r.company, r.email, r.workphone1, r.workphone2, r.mobile, r.home, -# r2.firstname, r2.lastname, -# rs.requeststatusname, -# a.address1, a.address2, a.city, a.state, a.country, a.zipcode, -# rd.description, rd.startdate, rd.closeddate, rd.receiveddate, rd.targetdate AS duedate, -# rd.subject -# --, rd.* -# from public."factRequestDetails" rd -# join public."dimRequestStatuses" rs on rs.requeststatusid = rd.requeststatusid -# join public."factRequestRequesters" rr1 on rr1.requesterid = rd.requesterid and rr1.foirequestid = rd.foirequestid and rr1.activeflag = 'Y' - -# join public."dimRequesters" r on rr1.requesterid = r.requesterid -# left join public."factRequestRequesters" rr2 on rr2.requesterid = rd.onbehalfofrequesterid and rr2.foirequestid = rd.foirequestid and rr2.activeflag = 'Y' -# left join public."dimRequesters" r2 on rr2.requesterid = r2.requesterid -# LEFT JOIN "dimRequesterTypes" rqt ON rd.applicantcategoryid = rqt.requestertypeid -# join public."dimReceivedModes" rm on rm.receivedmodeid = rd.receivedmodeid -# join public."dimAddress" a on a.addressid = rd.shipaddressid -# join public."dimRequestTypes" rt on rt.requesttypeid = rd.requesttypeid -# join public."dimDeliveryModes" dm on dm.deliverymodeid = rd.deliverymodeid -# where rd.visualrequestfilenumber = 'CFD-2023-30109' and rd.activeflag = 'Y' - - - return jsondata , statuscode - except ValueError: - return {'status': 500, 'message':INVALID_REQUEST_ID}, 500 - except BusinessException as exception: - return {'status': exception.status_code, 'message':exception.message}, 500 - - \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/external/bpmschema.py b/historical-search-api/request_api/schemas/external/bpmschema.py deleted file mode 100644 index 0698d0228..000000000 --- a/historical-search-api/request_api/schemas/external/bpmschema.py +++ /dev/null @@ -1,35 +0,0 @@ -from marshmallow import EXCLUDE, Schema, fields - -""" -This class consolidates schemas of bpm operations. - -__author__ = "sumathi.thirumani@aot-technologies.com" - -""" -class VariableSchema(Schema): - type = fields.Str() - value = fields.Str() - - -class MessageSchema(Schema): - - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - - messageName = fields.Str(data_key="messageName") - processInstanceId = fields.Str(data_key="processInstanceId") - processVariables = fields.Dict(keys=fields.String(),values=fields.Nested(VariableSchema)) - localCorrelationKeys = fields.Dict(keys=fields.String(),values=fields.Nested(VariableSchema)) - correlationKeys = fields.Dict(keys=fields.String(),values=fields.Nested(VariableSchema)) - -class VariableMessageSchema(Schema): - - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - - variables = fields.Dict(keys=fields.String(),values=fields.Nested(VariableSchema)) - \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/foiapplicantcorrespondencelog.py b/historical-search-api/request_api/schemas/foiapplicantcorrespondencelog.py deleted file mode 100644 index 56f444e06..000000000 --- a/historical-search-api/request_api/schemas/foiapplicantcorrespondencelog.py +++ /dev/null @@ -1,28 +0,0 @@ -from marshmallow import EXCLUDE, Schema, fields, validate -from request_api.utils.constants import MAX_EXCEPTION_MESSAGE - - -class AttachmentSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - filename = fields.Str(data_key="filename",required=True,allow_none=False, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - url = fields.Str(data_key="url",required=True,allow_none=False, validate=[validate.Length(max=1000, error=MAX_EXCEPTION_MESSAGE)]) - - -class FOIApplicantCorrespondenceSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - templateid = fields.Int(data_key="templateid",allow_none=True) - correspondencemessagejson = fields.Str(data_key="correspondencemessagejson",allow_none=False) - attachments = fields.Nested(AttachmentSchema, many=True, required=False,allow_none=True) - attributes = fields.List( - fields.Dict(fields.Str(), fields.Str()), - data_key="attributes", - required=False, - ) - - \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/foiassignee.py b/historical-search-api/request_api/schemas/foiassignee.py deleted file mode 100644 index d83ed2722..000000000 --- a/historical-search-api/request_api/schemas/foiassignee.py +++ /dev/null @@ -1,20 +0,0 @@ -from marshmallow import EXCLUDE, Schema, fields, validate -from request_api.utils.constants import MAX_EXCEPTION_MESSAGE - -class FOIRequestAssigneeSchema(Schema): - - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - assignedministrygroup = fields.Str(data_key="assignedministrygroup",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - assignedministryperson = fields.Str(data_key="assignedministryperson",allow_none=True, validate=[validate.Length(max=120, 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)]) - assignedgroup = fields.Str(data_key="assignedGroup",allow_none=True, validate=[validate.Length(max=250, error=MAX_EXCEPTION_MESSAGE)]) - assignedto = fields.Str(data_key="assignedTo",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - assignedToFirstName = fields.Str(data_key="assignedToFirstName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) - assignedToMiddleName = fields.Str(data_key="assignedToMiddleName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) - assignedToLastName = fields.Str(data_key="assignedToLastName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) - \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/foiaxissync.py b/historical-search-api/request_api/schemas/foiaxissync.py deleted file mode 100644 index 94b4906e1..000000000 --- a/historical-search-api/request_api/schemas/foiaxissync.py +++ /dev/null @@ -1,18 +0,0 @@ -from marshmallow import EXCLUDE, Schema, fields, validate -from request_api.utils.constants import MAX_EXCEPTION_MESSAGE - -class FOIAxisProgramDetailsSchema(Schema): - - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - bcgovcode = fields.Str(data_key="bcgovcode",allow_none=False) - requesttype = fields.Str(data_key="requesttype",allow_none=False) - -class FOIRequestAxisSyncSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - data = fields.Nested(FOIAxisProgramDetailsSchema, many=True, validate=validate.Length(min=1), required=True,allow_none=False) diff --git a/historical-search-api/request_api/schemas/foicfrfee.py b/historical-search-api/request_api/schemas/foicfrfee.py deleted file mode 100644 index 19d2e2ba8..000000000 --- a/historical-search-api/request_api/schemas/foicfrfee.py +++ /dev/null @@ -1,69 +0,0 @@ - -from marshmallow import EXCLUDE, Schema, fields, validate - -""" -This class consolidates schemas of CFR Fee Form operations. - -__author__ = "aparna.s@aot-technologies.com" - -""" - - -class FOIFeeDataSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - amountpaid = fields.Float(data_key="amountpaid") - estimatedtotaldue = fields.Float(data_key="estimatedtotaldue") - actualtotaldue = fields.Float(data_key="actualtotaldue") - estimatepaymentmethod = fields.Str(data_key="estimatepaymentmethod", validate=validate.OneOf(['moneyorder', 'creditcardphone', 'creditcardonline', 'cheque', 'cash']), required=False) - balancepaymentmethod = fields.Str(data_key="balancepaymentmethod", validate=validate.OneOf(['moneyorder', 'creditcardphone', 'creditcardonline', 'cheque', 'cash']), required=False) - balanceremaining = fields.Float(data_key="balanceremaining") - feewaiveramount = fields.Float(data_key="feewaiveramount") - refundamount = fields.Float(data_key="refundamount") - estimatedlocatinghrs = fields.Float(data_key="estimatedlocatinghrs") - actuallocatinghrs = fields.Float(data_key="actuallocatinghrs") - estimatedproducinghrs = fields.Float(data_key="estimatedproducinghrs") - actualproducinghrs = fields.Float(data_key="actualproducinghrs") - estimatediaopreparinghrs = fields.Float(data_key="estimatediaopreparinghrs") - estimatedministrypreparinghrs = fields.Float(data_key="estimatedministrypreparinghrs") - actualiaopreparinghrs = fields.Float(data_key="actualiaopreparinghrs") - actualministrypreparinghrs = fields.Float(data_key="actualministrypreparinghrs") - estimatedelectronicpages = fields.Int(data_key="estimatedelectronicpages") - actualelectronicpages = fields.Int(data_key="actualelectronicpages") - estimatedhardcopypages = fields.Int(data_key="estimatedhardcopypages") - actualhardcopypages = fields.Int(data_key="actualhardcopypages") - - -class FOICFRFeeSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - feedata = fields.Nested(FOIFeeDataSchema,allow_none=False) - overallsuggestions = fields.Str(data_key="overallsuggestions") - status = fields.Str(data_key="status") - cfrfeeid = fields.Int(data_key="cfrfeeid",required=False, allow_none=True) - reason = fields.Str(data_key="reason") - -class FOIFeeDataSanctionSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - amountpaid = fields.Float(data_key="amountpaid") - estimatepaymentmethod = fields.Str(data_key="estimatepaymentmethod", validate=validate.OneOf(['moneyorder', 'creditcardphone', 'creditcardonline', 'cheque', 'cash']), required=False) - balancepaymentmethod = fields.Str(data_key="balancepaymentmethod", validate=validate.OneOf(['moneyorder', 'creditcardphone', 'creditcardonline', 'cheque', 'cash']), required=False) - estimatediaopreparinghrs = fields.Float(data_key="estimatediaopreparinghrs") - actualiaopreparinghrs = fields.Float(data_key="actualiaopreparinghrs") - estimatedtotaldue = fields.Float(data_key="estimatedtotaldue") - actualtotaldue = fields.Float(data_key="actualtotaldue") - balanceremaining = fields.Float(data_key="balanceremaining") - feewaiveramount = fields.Float(data_key="feewaiveramount") - refundamount = fields.Float(data_key="refundamount") - -class FOICFRFeeSanctionSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - feedata = fields.Nested(FOIFeeDataSanctionSchema,allow_none=False) - status = fields.Str(data_key="status", required=True) - reason = fields.Str(data_key="reason") \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/foicomment.py b/historical-search-api/request_api/schemas/foicomment.py deleted file mode 100644 index 361a900b9..000000000 --- a/historical-search-api/request_api/schemas/foicomment.py +++ /dev/null @@ -1,50 +0,0 @@ - - -from marshmallow import EXCLUDE, Schema, fields - -""" -This class consolidates schemas of watcher operations. - -__author__ = "sumathi.thirumani@aot-technologies.com" - -""" -class FOIRawRequestCommentSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - requestid = fields.Int(data_key="requestid") - comment = fields.Str(data_key="comment") - parentcommentid = fields.Int(data_key="parentcommentid",allow_none=True) - isactive = fields.Bool(data_key="isactive",allow_none=True) - taggedusers = fields.Str(data_key="taggedusers") - -class FOIMinistryRequestCommentSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - ministryrequestid = fields.Int(data_key="ministryrequestid") - comment = fields.Str(data_key="comment") - parentcommentid = fields.Int(data_key="parentcommentid",allow_none=True) - isactive = fields.Bool(data_key="isactive",allow_none=True) - taggedusers = fields.Str(data_key="taggedusers") - -class EditFOIRawRequestCommentSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - comment = fields.Str(data_key="comment") - taggedusers = fields.Str(data_key="taggedusers") - - - -class EditFOIMinistryRequestCommentSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - comment = fields.Str(data_key="comment") - taggedusers = fields.Str(data_key="taggedusers") - \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/foidocument.py b/historical-search-api/request_api/schemas/foidocument.py deleted file mode 100644 index 16400d16b..000000000 --- a/historical-search-api/request_api/schemas/foidocument.py +++ /dev/null @@ -1,50 +0,0 @@ - - -from marshmallow import EXCLUDE, Schema, fields, validate -from request_api.utils.constants import MAX_EXCEPTION_MESSAGE - -""" -This class consolidates schemas of document operations. - -__author__ = "sumathi.thirumani@aot-technologies.com" - -""" -class RenameDocumentSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - 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.""" - - unknown = EXCLUDE - filename = fields.Str(data_key="filename",required=True,allow_none=False, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - documentpath = fields.Str(data_key="documentpath",required=True,allow_none=False, validate=[validate.Length(max=1000, error=MAX_EXCEPTION_MESSAGE)]) - category = fields.Str(data_key="category",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - - -class DocumentSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - filename = fields.Str(data_key="filename",required=True,allow_none=False, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - documentpath = fields.Str(data_key="documentpath",required=True,allow_none=False, validate=[validate.Length(max=1000, error=MAX_EXCEPTION_MESSAGE)]) - category = fields.Str(data_key="category",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - -class CreateDocumentSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - documents = fields.Nested(DocumentSchema, many=True, validate=validate.Length(min=1), required=True,allow_none=False) \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/foiemail.py b/historical-search-api/request_api/schemas/foiemail.py deleted file mode 100644 index d03d51fe9..000000000 --- a/historical-search-api/request_api/schemas/foiemail.py +++ /dev/null @@ -1,13 +0,0 @@ - -from marshmallow import EXCLUDE, Schema, fields - -""" -This class consolidates schemas of email operations. -""" -class FOIEmailSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - templatename = fields.Str(data_key="templateName", allow_none=True) - applicantcorrespondenceid = fields.Int(data_key="applicantCorrespondenceId",allow_none=True) \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/foiextension.py b/historical-search-api/request_api/schemas/foiextension.py deleted file mode 100644 index 623cbfb08..000000000 --- a/historical-search-api/request_api/schemas/foiextension.py +++ /dev/null @@ -1,40 +0,0 @@ - - -from marshmallow import EXCLUDE, Schema, fields, validate -from request_api.utils.constants import MAX_EXCEPTION_MESSAGE - -""" -This class consolidates schemas of extension operations. - -__author__ = "divya.v@aot-technologies.com" - -""" - -class FOIMinistryRequestDocumentSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - foiministrydocumentid = fields.Int(data_key="foiministrydocumentid",required=False, allow_none=True) - documentpath = fields.Str(data_key="documentpath",allow_none=False, validate=[validate.Length(max=1000, error=MAX_EXCEPTION_MESSAGE)]) - filename = fields.Str(data_key="filename",allow_none=False, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - category = fields.Str(data_key="category",allow_none=False, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - -class FOIRequestExtensionSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - extensionreasonid = fields.Int(data_key="extensionreasonid") - extendedduedays = fields.Int(data_key="extendedduedays") - extensionstatusid = fields.Int(data_key="extensionstatusid") - extendedduedate = fields.Str(data_key="extendedduedate") - decisiondate = fields.Str(data_key="decisiondate",required=False, allow_none=True) - denieddate = fields.Str(data_key="denieddate",required=False, allow_none=True) - approveddate = fields.Str(data_key="approveddate",required=False, allow_none=True) - approvednoofdays = fields.Int(data_key="approvednoofdays", required=False, allow_none=True) - version = fields.Int(data_key="version") - foiministryrequest_id = fields.Int(data_key="foiministryrequest_id") - foiministryrequestversion_id = fields.Int(data_key="foiministryrequestversion_id") - isactive = fields.Bool(data_key="isactive",allow_none=True) - documents = fields.Nested(FOIMinistryRequestDocumentSchema, required=False, many=True, allow_none=True) \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/foipayment.py b/historical-search-api/request_api/schemas/foipayment.py deleted file mode 100644 index 3cc36318f..000000000 --- a/historical-search-api/request_api/schemas/foipayment.py +++ /dev/null @@ -1,19 +0,0 @@ - - -from marshmallow import EXCLUDE, Schema, fields - -""" -This class consolidates schemas of payment operations. - -__author__ = "sumathi.thirumani@aot-technologies.com" - -""" -class FOIRequestPaymentSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - paymenturl = fields.Str(data_key="paymenturl") - paymentexpirydate = fields.Date(data_key="paymentexpirydate") - - diff --git a/historical-search-api/request_api/schemas/foiprogramareadivision.py b/historical-search-api/request_api/schemas/foiprogramareadivision.py deleted file mode 100644 index 659d3417f..000000000 --- a/historical-search-api/request_api/schemas/foiprogramareadivision.py +++ /dev/null @@ -1,14 +0,0 @@ -from marshmallow import EXCLUDE, Schema, fields, validates_schema, ValidationError - -class FOIProgramAreaDivisionSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - programareaid = fields.Int(data_key="programareaid") - 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/historical-search-api/request_api/schemas/foirecord.py b/historical-search-api/request_api/schemas/foirecord.py deleted file mode 100644 index 56a739a2f..000000000 --- a/historical-search-api/request_api/schemas/foirecord.py +++ /dev/null @@ -1,166 +0,0 @@ - - -from marshmallow import EXCLUDE, Schema, fields, validate -from request_api.utils.constants import MAX_EXCEPTION_MESSAGE - -""" -This class consolidates schemas of record operations. - -__author__ = "sumathi.thirumani@aot-technologies.com" - -""" -class DivisionSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - divisionid = fields.Int(data_key="divisionid",allow_none=False) - -class CreateRecordAttributeSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - #divisions = fields.List(fields.Int(),data_key="divisions",required=True) - divisions = fields.Nested(DivisionSchema, many=True, validate=validate.Length(min=1), required=True,allow_none=False) - lastmodified = fields.Str(data_key="lastmodified",allow_none=False, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - filesize = fields.Int(data_key="filesize", allow_none=False) - parentpdfmasterid = fields.Int(required=False,allow_none=True) - -class FOIRequestCreateRecordSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - attributes = fields.Nested(CreateRecordAttributeSchema) - s3uripath = fields.Str(data_key="s3uripath",allow_none=False, validate=[validate.Length(max=1000, error=MAX_EXCEPTION_MESSAGE)]) - filename = fields.Str(data_key="filename",allow_none=False, validate=[validate.Length(max=500, error=MAX_EXCEPTION_MESSAGE)]) - - - -class FOIRequestBulkCreateRecordSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - records = fields.Nested(FOIRequestCreateRecordSchema, many=True, validate=validate.Length(min=1), required=True,allow_none=False) - -class RetryRecordAttributeSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - #divisions = fields.List(fields.Int(),data_key="divisions",required=True) - divisions = fields.Nested(DivisionSchema, many=True, validate=validate.Length(min=1), required=True,allow_none=False) - lastmodified = fields.Str(data_key="lastmodified",allow_none=False, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - filesize = fields.Int(data_key="filesize", allow_none=False) - convertedfilesize = fields.Int(data_key="convertedfilesize", allow_none=True) - trigger = fields.Str(data_key="trigger", allow_none=True) - batch = fields.Str(data_key="batch", allow_none=False, validate=validate.Length(min=1), required=True) - incompatible = fields.Boolean(required=True,allow_none=False) - extension = fields.Str(validate=validate.Length(min=1, max=10), required=True,allow_none=False) - isattachment = fields.Boolean(required=False,allow_none=False) - -class ReplaceRecordAttributeSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - divisions = fields.Nested(DivisionSchema, many=True, validate=validate.Length(min=1), required=True,allow_none=False) - lastmodified = fields.Str(data_key="lastmodified",allow_none=False, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - filesize = fields.Int(data_key="filesize", allow_none=False) - batch = fields.Str(data_key="batch", allow_none=False, validate=validate.Length(min=1), required=True) - incompatible = fields.Boolean(required=True,allow_none=False) - extension = fields.Str(validate=validate.Length(min=1, max=10), required=True,allow_none=False) - isattachment = fields.Boolean(required=False,allow_none=False) - hasfilereplaced = fields.Boolean(required=False,allow_none=False) - -class FOIRequestRetryRecordSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - attributes = fields.Nested(RetryRecordAttributeSchema) - s3uripath = fields.Str(data_key="s3uripath",allow_none=False, validate=[validate.Length(max=1000, error=MAX_EXCEPTION_MESSAGE)]) - filename = fields.Str(data_key="filename",allow_none=False, validate=[validate.Length(max=500, error=MAX_EXCEPTION_MESSAGE)]) - trigger = fields.Str(validate=validate.OneOf(['recordreplace', 'recordretry']), required=True,allow_none=False) - service = fields.Str(validate=validate.OneOf(['deduplication', 'conversion',"all"]), required=False,allow_none=False) - documentmasterid = fields.Integer(required=True,allow_none=True) - outputdocumentmasterid = fields.Integer(required=False,allow_none=True) - createdby = fields.Str(validate=validate.Length(min=1),required=True,allow_none=False) - -class FOIRequestReplaceRecordSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - attributes = fields.Nested(ReplaceRecordAttributeSchema) - s3uripath = fields.Str(data_key="s3uripath",allow_none=False, validate=[validate.Length(max=1000, error=MAX_EXCEPTION_MESSAGE)]) - filename = fields.Str(data_key="filename",allow_none=False, validate=[validate.Length(max=500, error=MAX_EXCEPTION_MESSAGE)]) - replacements3uripath = fields.Str(data_key="replacements3uripath",allow_none=False, validate=[validate.Length(max=1000, error=MAX_EXCEPTION_MESSAGE)]) - replacementfilename = fields.Str(data_key="replacementfilename",allow_none=False, validate=[validate.Length(max=500, error=MAX_EXCEPTION_MESSAGE)]) - trigger = fields.Str(data_key="trigger",validate=validate.OneOf(['recordreplace', 'recordretry']),allow_none=False) - service = fields.Str(data_key="service",validate=validate.OneOf(['deduplication', 'conversion']),allow_none=False) - replacementof = fields.Str(data_key="replacementof",allow_none=True) - - -class FOIRequestBulkRetryRecordSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - records = fields.Nested(FOIRequestRetryRecordSchema, many=True, validate=validate.Length(min=1), required=True,allow_none=False) - - -class FileSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - recordid = fields.Int(data_key="recordid",allow_none=True) - filename = fields.Str(data_key="filename",allow_none=False, validate=[validate.Length(max=500, error=MAX_EXCEPTION_MESSAGE)]) - s3uripath = fields.Str(data_key="s3uripath",allow_none=False, validate=[validate.Length(max=1000, error=MAX_EXCEPTION_MESSAGE)]) - lastmodified = fields.Str(data_key="lastmodified",allow_none=False, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - filesize = fields.Number(data_key="filesize") - -class DownloadRecordAttributeSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - files = fields.Nested(FileSchema, many=True, validate=validate.Length(min=1), required=True,allow_none=False) - divisionname = fields.Str(data_key="divisionname",allow_none=False, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - divisionid = fields.Int(data_key="divisionid", allow_none=False) - divisionfilesize = fields.Number(data_key="divisionfilesize") - -class FOIRequestRecordDownloadSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - requestnumber = fields.Str(data_key="requestnumber",allow_none=False, validate=[validate.Length(max=100, error=MAX_EXCEPTION_MESSAGE)]) - bcgovcode = fields.Str(data_key="bcgovcode",allow_none=False, validate=[validate.Length(max=20, error=MAX_EXCEPTION_MESSAGE)]) - category = fields.Str(data_key="category",allow_none=False, validate=[validate.Length(max=25, error=MAX_EXCEPTION_MESSAGE)]) - attributes = fields.Nested(DownloadRecordAttributeSchema, many=True, validate=validate.Length(min=1), required=True,allow_none=False) - totalfilesize = fields.Number(data_key="totalfilesize") - -class RecordSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - recordid = fields.Int(data_key="recordid",allow_none=True) - documentmasterid = fields.Int(data_key="documentmasterid",allow_none=False) - filepath = fields.String(data_key="filepath",allow_none=False,validate=[validate.Length(max=1000, error=MAX_EXCEPTION_MESSAGE)]) - -class FOIRequestRecordUpdateSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - records = fields.Nested(RecordSchema,many=True,data_key="records",required=True) - divisions = fields.Nested(DivisionSchema,many=True,validate=validate.Length(min=1),required=False,allow_none=True) - isdelete = fields.Boolean(required=True,allow_none=False) - - - diff --git a/historical-search-api/request_api/schemas/foirequest.py b/historical-search-api/request_api/schemas/foirequest.py deleted file mode 100644 index 239f320db..000000000 --- a/historical-search-api/request_api/schemas/foirequest.py +++ /dev/null @@ -1,87 +0,0 @@ - - -from marshmallow import EXCLUDE, Schema, fields, validate -from request_api.utils.constants import BLANK_EXCEPTION_MESSAGE, MAX_EXCEPTION_MESSAGE - -""" -This class consolidates schemas of bpm operations. - -__author__ = "sumathi.thirumani@aot-technologies.com" - -""" -class FOIMinistryRequestSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - filenumber = fields.Str(data_key="fileNumber", validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) - description = fields.Str(data_key="description") - recordSearchFromDate = fields.Date(data_key="recordSearchFromDate") - recordSearchToDate = fields.Date(data_key="recordSearchToDate") - startdate = fields.Date(data_key="startDate") - duedate = fields.Date(data_key="dueDate") - cfrduedate = fields.Date(data_key="cfrDueDate", required=False,allow_none=True) - assignedto = fields.Str(data_key="assignedTo", validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - programareaid = fields.Int(data_key="programAreaId") - -class FOIContactInformationSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - contacttypeid = fields.Int(data_key="contactTypeId") - contactinformation = fields.Str(data_key="contactInformation") - dataformat = fields.Str(data_key="dataFormat") - -class FOIPersonalAttributeSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - personalattributeid = fields.Int(data_key="personalAttributeId") - attributevalue = fields.Str(data_key="attributeValue") - -class FOIApplicantSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - firstname = fields.Str(data_key="firstName") - middlename = fields.Str(data_key="middleName") - lastname = fields.Str(data_key="lastName") - alsoknownas = fields.Str(data_key="alsoKnownAs") - dob = fields.Date(data_key="dob") - businessname = fields.Str(data_key="businessName") - -class FOIRequestApplicantSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - applicant = fields.Nested(FOIApplicantSchema, many=False) - requestortypeid = fields.Int(data_key="requestorTypeId") - -class FOIRequestTypeSchema(Schema): - requestType = fields.Str(data_key="requestType", validate=[validate.Length(max=10, error=MAX_EXCEPTION_MESSAGE)]) - -class FOIRequestSchema(Schema): - - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - version = fields.Int(data_key="version") - requestType = fields.Nested(FOIRequestTypeSchema) - initialrecordsearchfromdate = fields.DateTime(data_key="recordSearchFromDate") - initialrecordsearchtodate = fields.DateTime(data_key="recordSearchToDate") - deliverymodeid = fields.Int(data_key="deliveryModeId") - receivedmodeid = fields.Int(data_key="receivedModeId") - foirawrequestid = fields.Int(data_key="foiRawRequestId") - description = fields.Str(data_key="description") - receiveddate = fields.DateTime(data_key="receiveddate") - isactive=fields.Bool(data_key="isactive") - ministryRequests = fields.Nested(FOIMinistryRequestSchema, many=True) - contactInformations = fields.Nested(FOIContactInformationSchema, many=True) - personalAttributes = fields.Nested(FOIPersonalAttributeSchema, many=True) - requestApplicants = fields.Nested(FOIRequestApplicantSchema, many=True) - diff --git a/historical-search-api/request_api/schemas/foirequestsformslist.py b/historical-search-api/request_api/schemas/foirequestsformslist.py deleted file mode 100644 index 381eb07c4..000000000 --- a/historical-search-api/request_api/schemas/foirequestsformslist.py +++ /dev/null @@ -1,15 +0,0 @@ -from marshmallow import EXCLUDE, Schema, fields - - -class FOIRequestsFormsList(Schema): - class Meta: - unknown = EXCLUDE - - ministrycode = fields.Str(data_key="ministrycode",allow_none=False) - requestnumber = fields.Str(data_key="requestnumber",allow_none=False) - filestatustransition = fields.Str(data_key="filestatustransition",allow_none=False) - filename = fields.Str(data_key="filename",allow_none=False) - filepath = fields.Str(data_key="filepath",allow_none=True) - authheader = fields.Str(data_key="authheader",allow_none=True) - amzdate = fields.Str(data_key="amzdate",allow_none=True) - s3sourceuri = fields.Str(data_key="s3sourceuri",allow_none=True) \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/foirequestwrapper.py b/historical-search-api/request_api/schemas/foirequestwrapper.py deleted file mode 100644 index 5242708b1..000000000 --- a/historical-search-api/request_api/schemas/foirequestwrapper.py +++ /dev/null @@ -1,215 +0,0 @@ -from marshmallow import EXCLUDE, Schema, fields, validate -from request_api.utils.constants import BLANK_EXCEPTION_MESSAGE, MAX_EXCEPTION_MESSAGE - -""" -This class consolidates schemas of bpm operations. - -__author__ = "sumathi.thirumani@aot-technologies.com" - -""" -class FOIMinistryRequestWrapperSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - code = fields.Str(data_key="code", validate=[validate.Length(max=30, error=MAX_EXCEPTION_MESSAGE)]) - name = fields.Str(data_key="name", validate=[validate.Length(max=255, error=MAX_EXCEPTION_MESSAGE)]) - isSelected = fields.Bool(data_key="isSelected") - -class FOIAdditionallPersonalInfoWrapperSchema(Schema): - - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - childFirstName = fields.Str(data_key="childFirstName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) - childMiddleName = fields.Str(data_key="childMiddleName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) - childLastName = fields.Str(data_key="childLastName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) - childAlsoKnownAs = fields.Str(data_key="childAlsoKnownAs",allow_none=True) - childBirthDate = fields.Str(data_key="childBirthDate",allow_none=True) - - anotherFirstName = fields.Str(data_key="anotherFirstName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) - anotherMiddleName = fields.Str(data_key="anotherMiddleName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) - anotherLastName = fields.Str(data_key="anotherLastName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) - anotherAlsoKnownAs = fields.Str(data_key="anotherAlsoKnownAs",allow_none=True) - anotherBirthDate = fields.Str(data_key="anotherBirthDate",allow_none=True) - - adoptiveMotherFirstName = fields.Str(data_key="adoptiveMotherFirstName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) - adoptiveMotherLastName = fields.Str(data_key="adoptiveMotherLastName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) - adoptiveFatherFirstName = fields.Str(data_key="adoptiveFatherFirstName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) - 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)]) - - birthDate = fields.Str(data_key="birthDate",allow_none=True) - alsoKnownAs = fields.Str(data_key="alsoKnownAs",allow_none=True) - -class FOIMinistryRequestDocumentSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - documentpath = fields.Str(data_key="documentpath",allow_none=False, validate=[validate.Length(max=1000, error=MAX_EXCEPTION_MESSAGE)]) - filename = fields.Str(data_key="filename",allow_none=False, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - category = fields.Str(data_key="category",allow_none=False, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - -class FOIOIPCInquirySchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - inquirydate = fields.Str(data_key="inquirydate",allow_none=True) - orderno = fields.Str(data_key="orderno",allow_none=True) - inquiryoutcome = fields.Int(data_key="inquiryoutcome",allow_none=True) - -class FOIMinistryRequestOIPCSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - oipcno = fields.Str(data_key="oipcno") - reviewtypeid = fields.Int(data_key="reviewtypeid") - reasonid = fields.Int(data_key="reasonid") - statusid = fields.Int(data_key="statusid") - outcomeid = fields.Int(data_key="outcomeid",allow_none=True) - investigator = fields.Str(data_key="investigator",allow_none=True, validate=[validate.Length(max=500, error=MAX_EXCEPTION_MESSAGE)]) - isinquiry = fields.Bool(data_key="isinquiry") - isjudicialreview = fields.Bool(data_key="isjudicialreview") - issubsequentappeal = fields.Bool(data_key="issubsequentappeal") - inquiryattributes = fields.Nested(FOIOIPCInquirySchema, data_key="inquiryattributes", allow_none=True) - receiveddate = fields.Str(data_key="receiveddate",allow_none=True) - closeddate = fields.Str(data_key="closeddate",allow_none=True) - -class FOIRequestWrapperSchema(Schema): - - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - foirawrequestid = fields.Int(data_key="id") - axisSyncDate = fields.Str(data_key="axisSyncDate",allow_none=True) - axisRequestId = fields.Str(data_key="axisRequestId",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - axispagecount = fields.Int(data_key="axispagecount",allow_none=True) - description = fields.Str(data_key="description", required=True,validate=[validate.Length(min=1, error=BLANK_EXCEPTION_MESSAGE)]) - category = fields.Str(data_key="category", required=True,validate=[validate.Length(min=1, error=BLANK_EXCEPTION_MESSAGE)]) - requestType = fields.Str(data_key="requestType", required=True,validate=[validate.Length(min=1, error=BLANK_EXCEPTION_MESSAGE)]) - firstName = fields.Str(data_key="firstName", required=True,validate=[validate.Length(min=1, error=BLANK_EXCEPTION_MESSAGE, max=50)]) - middleName = fields.Str(data_key="middleName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) - lastName = fields.Str(data_key="lastName", required=True,validate=[validate.Length(min=1, error=BLANK_EXCEPTION_MESSAGE, max=50)]) - email = fields.Str(data_key="email",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - businessName = fields.Str(data_key="businessName",allow_none=True, validate=[validate.Length(max=255, error=MAX_EXCEPTION_MESSAGE)]) - assignedGroup = fields.Str(data_key="assignedGroup",allow_none=True, validate=[validate.Length(max=250, error=MAX_EXCEPTION_MESSAGE)]) - assignedTo = fields.Str(data_key="assignedTo",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - fromDate = fields.Str(data_key="fromDate",allow_none=True) - toDate = fields.Str(data_key="toDate",allow_none=True) - 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)]) - startDate = fields.Str(data_key="requestProcessStart", required=True,validate=[validate.Length(min=1, error=BLANK_EXCEPTION_MESSAGE)]) - assignedministrygroup = fields.Str(data_key="assignedministrygroup",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - assignedministryperson = fields.Str(data_key="assignedministryperson",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - assignedToFirstName = fields.Str(data_key="assignedToFirstName",allow_none=True) - assignedToMiddleName = fields.Str(data_key="assignedToMiddleName",allow_none=True) - assignedToLastName = fields.Str(data_key="assignedToLastName",allow_none=True) - assignedministrypersonFirstName = fields.Str(data_key="assignedministrypersonFirstName",allow_none=True) - assignedministrypersonMiddleName = fields.Str(data_key="assignedministrypersonMiddleName",allow_none=True) - assignedministrypersonLastName = fields.Str(data_key="assignedministrypersonLastName",allow_none=True) - - reopen = fields.Bool(data_key="reopen",allow_none=True) - - phonePrimary = fields.Str(data_key="phonePrimary",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) - workPhonePrimary = fields.Str(data_key="workPhonePrimary",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) - phoneSecondary = fields.Str(data_key="phoneSecondary",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) - workPhoneSecondary = fields.Str(data_key="workPhoneSecondary",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) - address = fields.Str(data_key="address",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - addressSecondary = fields.Str(data_key="addressSecondary",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - city = fields.Str(data_key="city",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - province = fields.Str(data_key="province",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - postal = fields.Str(data_key="postal",allow_none=True, validate=[validate.Length(max=10, error=MAX_EXCEPTION_MESSAGE)]) - country = fields.Str(data_key="country",allow_none=True) - requeststatusid = fields.Int(data_key="requeststatusid",allow_none=True) - requeststatuslabel = fields.Str(data_key="requeststatuslabel",allow_none=False) - closedate = fields.Date(data_key="closedate", required=False,allow_none=True) - closereasonid = fields.Int(data_key="closereasonid",allow_none=True) - correctionalServiceNumber = fields.Str(data_key="correctionalServiceNumber",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) - publicServiceEmployeeNumber = fields.Str(data_key="publicServiceEmployeeNumber",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) - isiaorestricted = fields.Bool(data_key="isiaorestricted") - isoipcreview = fields.Bool(data_key="isoipcreview") - - selectedMinistries = fields.Nested(FOIMinistryRequestWrapperSchema, many=True) - additionalPersonalInfo = fields.Nested(FOIAdditionallPersonalInfoWrapperSchema,required=False,allow_none=True) - documents = fields.Nested(FOIMinistryRequestDocumentSchema, many=True,allow_none=True) - idNumber = fields.Str(data_key="idNumber",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - 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) - - oipcdetails = fields.Nested(FOIMinistryRequestOIPCSchema, many=True,allow_none=True) - - estimatedpagecount = fields.Int(data_key="estimatedpagecount",allow_none=True) - estimatedtaggedpagecount = fields.Int(data_key="estimatedtaggedpagecount",allow_none=True) - - -class EditableFOIMinistryRequestWrapperSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - filenumber = fields.Str(data_key="filenumber", validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) - status = fields.Str(data_key="status", validate=[validate.Length(max=100, error=MAX_EXCEPTION_MESSAGE)]) - -class EditableFOIRequestWrapperSchema(Schema): - wfinstanceid = fields.Str(data_key="wfinstanceId",allow_none=True) - selectedMinistries = fields.Nested(EditableFOIMinistryRequestWrapperSchema, many=True) - -class FOIRequestStatusSchema(Schema): - nextstatename = fields.Str(data_key="nextStateName",allow_none=True) - -class FOIMinistryRequestDivisionSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - divisionid = fields.Int(data_key="divisionid") - stageid = fields.Int(data_key="stageid") - divisionDueDate = fields.Str(data_key="divisionDueDate",allow_none=True) - 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): - - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - assignedministrygroup = fields.Str(data_key="assignedministrygroup",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - assignedministryperson = fields.Str(data_key="assignedministryperson",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - assignedgroup = fields.Str(data_key="assignedGroup",allow_none=True, validate=[validate.Length(max=250, error=MAX_EXCEPTION_MESSAGE)]) - assignedto = fields.Str(data_key="assignedTo",allow_none=True, validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - requeststatusid = fields.Int(data_key="requeststatusid",allow_none=True) - requeststatuslabel = fields.Str(data_key="requeststatuslabel",allow_none=True) - divisions = fields.Nested(FOIMinistryRequestDivisionSchema, many=True,allow_none=True) - documents = fields.Nested(FOIMinistryRequestDocumentSchema, many=True,allow_none=True) - assignedToFirstName = fields.Str(data_key="assignedToFirstName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) - assignedToMiddleName = fields.Str(data_key="assignedToMiddleName",allow_none=True, validate=[validate.Length(max=50, error=MAX_EXCEPTION_MESSAGE)]) - 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)]) - ministrysignoffapproval = fields.Nested(CreateMinistrySignOffApprovalSchema, data_key="ministrysignoffapproval", allow_none=True) \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/foiwatcher.py b/historical-search-api/request_api/schemas/foiwatcher.py deleted file mode 100644 index 1243e2c77..000000000 --- a/historical-search-api/request_api/schemas/foiwatcher.py +++ /dev/null @@ -1,35 +0,0 @@ - - -from marshmallow import EXCLUDE, Schema, fields, validate -from request_api.utils.constants import MAX_EXCEPTION_MESSAGE - -""" -This class consolidates schemas of watcher operations. - -__author__ = "sumathi.thirumani@aot-technologies.com" - -""" -class FOIRawRequestWatcherSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - requestid = fields.Int(data_key="requestid") - watchedbygroup = fields.Str(data_key="watchedbygroup",allow_none=True, validate=[validate.Length(max=250, error=MAX_EXCEPTION_MESSAGE)]) - watchedby = fields.Str(data_key="watchedby", validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - isactive = fields.Bool(data_key="isactive") - fullname = fields.Str(data_key="fullname",allow_none=True ,validate=[validate.Length(max=250, error=MAX_EXCEPTION_MESSAGE)]) - isrestricted = fields.Bool(data_key="isrestricted") - - -class FOIMinistryRequestWatcherSchema(Schema): - class Meta: # pylint: disable=too-few-public-methods - """Exclude unknown fields in the deserialized output.""" - - unknown = EXCLUDE - ministryrequestid = fields.Int(data_key="ministryrequestid") - watchedbygroup = fields.Str(data_key="watchedbygroup",allow_none=True, validate=[validate.Length(max=250, error=MAX_EXCEPTION_MESSAGE)]) - watchedby = fields.Str(data_key="watchedby", validate=[validate.Length(max=120, error=MAX_EXCEPTION_MESSAGE)]) - isactive = fields.Bool(data_key="isactive") - fullname = fields.Str(data_key="fullname",allow_none=True ,validate=[validate.Length(max=250, error=MAX_EXCEPTION_MESSAGE)]) - isrestricted = fields.Bool(data_key="isrestricted") \ No newline at end of file diff --git a/historical-search-api/request_api/schemas/schemas/rawrequest.json b/historical-search-api/request_api/schemas/schemas/rawrequest.json deleted file mode 100644 index b249451dc..000000000 --- a/historical-search-api/request_api/schemas/schemas/rawrequest.json +++ /dev/null @@ -1,220 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties": { - "requestData": { - "type": "object", - "properties": { - "requestType": { - "type": "object", - "properties": { - "requestType": { - "type": "string" - } - }, - "required": [ - "requestType" - ] - }, - "ministry": { - "type": "object", - "properties": { - "selectedMinistry": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "code": { - "type": "string" - }, - "name": { - "type": "string" - }, - "selected": { - "type": "boolean" - } - } - } - ] - }, - "ministryPage": { - "type": "string" - }, - "defaultMinistry": { - "type": "object", - "properties": { - "code": { - "type": "string" - }, - "name": { - "type": "string" - }, - "defaulted": { - "type": "boolean" - }, - "selected": { - "type": "boolean" - } - } - } - }, - "required": [ - "selectedMinistry", - "ministryPage" - ] - }, - "descriptionTimeframe": { - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "fromDate": { - "type": "string" - }, - "toDate": { - "type": "string" - }, - "correctionalServiceNumber": { - "type": ["string","null"] - }, - "publicServiceEmployeeNumber": { - "type": ["string","null"] - }, - "topic": { - "type": "string" - } - }, - "required": [ - "description", - "fromDate", - "toDate" - ] - }, - "contactInfo": { - "type": "object", - "properties": { - "firstName": { - "type": "string" - }, - "middleName": { - "type": ["string","null"] - }, - "lastName": { - "type": "string" - }, - "businessName": { - "type": ["string","null"] - }, - "alsoKnownAs": { - "type":["string","null"] - }, - "birthDate": { - "type": ["string","null"] - } - }, - "required": [ - "firstName", - "lastName" - ] - }, - "contactInfoOptions": { - "type": "object", - "properties": { - "email": { - "type": ["string","null"] - }, - "phonePrimary": { - "type": ["string","null"] - }, - "phoneSecondary": { - "type": ["string","null"] - }, - "address": { - "type": ["string","null"] - }, - "city": { - "type": ["string","null"] - }, - "postal": { - "type": ["string","null"] - }, - "province": { - "type": ["string","null"] - }, - "country": { - "type": ["string","null"] - } - } - }, - "choose-idenity": { - "type": "object", - "properties": { - "answerYes": { - "type": ["string","null"] - } - } - }, - "selectAbout": { - "type": "object", - "properties": { - "yourself": { - "type": ["boolean","null"] - }, - "child": { - "type": ["boolean","null"] - }, - "another": { - "type": ["boolean","null"] - } - } - }, - "requestTopic": { - "type": "object", - "properties": { - "value": { - "type": ["string","null"] - }, - "text": { - "type": ["string","null"] - }, - "ministryCode": { - "type": ["string","null"] - } - } - }, - "adoptiveParents": { - "type": "object", - "properties": { - "motherFirstName": { - "type": ["string","null"] - }, - "motherLastName": { - "type": ["string","null"] - }, - "fatherFirstName": { - "type": ["string","null"] - }, - "fatherLastName": { - "type": ["string","null"] - } - } - }, - "Attachments": { - "type": "array", - "items": {} - } - }, - "required": [ - "requestType", - "ministry", - "descriptionTimeframe", - "contactInfo" - ] - } - }, - "required": [ - "requestData" - ] -} \ No newline at end of file diff --git a/historical-search-api/request_api/services/applicantcategoryservice.py b/historical-search-api/request_api/services/applicantcategoryservice.py deleted file mode 100644 index fecd9cebc..000000000 --- a/historical-search-api/request_api/services/applicantcategoryservice.py +++ /dev/null @@ -1,8 +0,0 @@ -from request_api.models.ApplicantCategories import ApplicantCategory - -class applicantcategoryservice: - - def getapplicantcategories(self): - """ Returns the active records - """ - return ApplicantCategory.getapplicantcategories() \ No newline at end of file diff --git a/historical-search-api/request_api/services/applicantcorrespondence/applicantcorrespondencelog.py b/historical-search-api/request_api/services/applicantcorrespondence/applicantcorrespondencelog.py deleted file mode 100644 index cc265139b..000000000 --- a/historical-search-api/request_api/services/applicantcorrespondence/applicantcorrespondencelog.py +++ /dev/null @@ -1,98 +0,0 @@ -from request_api.models.ApplicationCorrespondenceTemplates import ApplicationCorrespondenceTemplate -from request_api.models.FOIApplicantCorrespondences import FOIApplicantCorrespondence -from request_api.models.FOIMinistryRequests import FOIMinistryRequest - -import maya -import json -import html -from datetime import datetime -class applicantcorrespondenceservice: - - def getapplicantcorrespondencetemplates(self): - """ Returns the active applicant correspondence templates - """ - return ApplicationCorrespondenceTemplate.getapplicantcorrespondencetemplates() - - def gettemplatebyid(self, templateid): - """ Returns the active applicant correspondence templates - """ - return ApplicationCorrespondenceTemplate.get_template_by_id(templateid) - - def getapplicantcorrespondencelogs(self,ministryrequestid): - """ Returns the active applicant correspondence logs - """ - _correspondencelogs = FOIApplicantCorrespondence.getapplicantcorrespondences(ministryrequestid) - correspondencelogs =[] - for _correpondencelog in _correspondencelogs: - attachments = [] - for _attachment in _correpondencelog['attachments']: - attachment = { - "applicantcorrespondenceattachmentid" : _attachment.applicantcorrespondenceattachmentid, - "documenturipath" : _attachment.attachmentdocumenturipath, - "filename" : _attachment.attachmentfilename, - } - attachments.append(attachment) - correpondencelog = self.__createcorrespondencelog(_correpondencelog, attachments) - correspondencelogs.append(correpondencelog) - return correspondencelogs - - def saveapplicantcorrespondencelog(self, requestid, ministryrequestid, data, userid): - applicantcorrespondencelog = FOIApplicantCorrespondence() - applicantcorrespondencelog.templateid = data['templateid'] - applicantcorrespondencelog.foiministryrequest_id = ministryrequestid - applicantcorrespondencelog.foiministryrequestversion_id =FOIMinistryRequest.getversionforrequest(ministryrequestid=ministryrequestid) - if userid == 'system': - applicantcorrespondencelog.sentcorrespondencemessage = data['correspondencemessagejson'] - applicantcorrespondencelog.sentby = 'System Generated Email' - applicantcorrespondencelog.sent_at = datetime.now() - else: - applicantcorrespondencelog.correspondencemessagejson = data['correspondencemessagejson'] - applicantcorrespondencelog.createdby = userid - return FOIApplicantCorrespondence.saveapplicantcorrespondence(applicantcorrespondencelog,data['attachments']) - - - def updateapplicantcorrespondencelog(self, correspondenceid, content): - return FOIApplicantCorrespondence.updatesentcorrespondence(correspondenceid, content) - - def getapplicantcorrespondencelogbyid(self, applicantcorrespondenceid): - applicantcorrespondence = FOIApplicantCorrespondence.getapplicantcorrespondencebyid(applicantcorrespondenceid) - (_correspondencemessagejson, _isjson) = self.__getjsonobject(applicantcorrespondence["correspondencemessagejson"]) - emailhtml_decoded_string = html.unescape(self.__getvaluefromjson(_correspondencemessagejson, 'emailhtml')) - return emailhtml_decoded_string if _isjson else _correspondencemessagejson - - def getlatestapplicantcorrespondence(self, ministryid): - return FOIApplicantCorrespondence().getlatestapplicantcorrespondence(ministryid) - - def __createcorrespondencelog(self, _correpondencelog, attachments): - (_correspondencemessagejson, _isjson) = self.__getjsonobject(_correpondencelog['correspondencemessagejson']) if _correpondencelog['correspondencemessagejson'] is not None else (None, None) - _sentcorrespondencemessagejson = json.loads(_correpondencelog["sentcorrespondencemessage"]) if _correpondencelog['sentcorrespondencemessage'] not in [None,''] else None - correpondencelog ={ - "applicantcorrespondenceid":_correpondencelog['applicantcorrespondenceid'], - "parentapplicantcorrespondenceid":_correpondencelog['parentapplicantcorrespondenceid'], - "templateid":_correpondencelog['templateid'], - "text": self.__getvaluefromschema(_sentcorrespondencemessagejson, 'message') if _sentcorrespondencemessagejson is not None else self.__getvaluefromjson(_correspondencemessagejson, 'emailhtml') if _isjson else None, - "id": self.__getvaluefromjson(_correspondencemessagejson, 'id') if _isjson else None, - "type": self.__getvaluefromjson(_correspondencemessagejson, 'type') if _isjson else None, - "created_at":_correpondencelog['sent_at'] if _sentcorrespondencemessagejson is not None else _correpondencelog['created_at'], - "createdby":_correpondencelog['createdby'] if _correpondencelog['createdby'] is not None else _correpondencelog['sentby'], - "date": self.__pstformat(_correpondencelog['sent_at']) if _sentcorrespondencemessagejson is not None else self.__pstformat(_correpondencelog['created_at']), - "userId": _correpondencelog['createdby'] if _correpondencelog['createdby'] is not None else _correpondencelog['sentby'], - "attachments" : attachments - } - return correpondencelog - - def __getjsonobject(self, correspondencemessagejson): - try: - data = json.loads(correspondencemessagejson) - except ValueError: - return correspondencemessagejson, False - return data, True - - def __getvaluefromjson(self, jsonobject, property): - return jsonobject[property] if jsonobject is not None else None - - def __pstformat(self, _date): - return maya.parse(_date).datetime(to_timezone='America/Vancouver', naive=False).strftime('%Y %b %d | %I:%M %p') - - def __getvaluefromschema(self, schema, property): - return schema.get(property) if property in schema else None \ No newline at end of file diff --git a/historical-search-api/request_api/services/assigneeservice.py b/historical-search-api/request_api/services/assigneeservice.py deleted file mode 100644 index 5ccace8b9..000000000 --- a/historical-search-api/request_api/services/assigneeservice.py +++ /dev/null @@ -1,42 +0,0 @@ - -from os import stat -from request_api.services.external.keycloakadminservice import KeycloakAdminService -from request_api.utils.enums import UserGroup -from request_api.models.FOIAssignees import FOIAssignee -from request_api.models.OperatingTeams import OperatingTeam -from request_api.models.FOIRequestTeams import FOIRequestTeam - -class assigneeservice: - """ FOI Assignee management service - - This service class interacts with Keycloak and returns eligible groups and members based on the state of application. - - """ - - def getgroupsandmembersbytypeandstatus(self, requesttype, status, bcgovcode=None): - if requesttype is None and status is None: - return KeycloakAdminService().getgroupsandmembers(OperatingTeam.getalloperatingteams()) - else: - filteredgroups = self.__getgroups(requesttype, status, bcgovcode) - if filteredgroups is not None: - return KeycloakAdminService().getgroupsandmembers(filteredgroups) - return None - - def getmembersbygroupname(self, groupname): - return KeycloakAdminService().getmembersbygroupname(groupname) - - def getprocessingteamsbyrequesttype(self,requesttype): - return FOIRequestTeam.getprocessingteamsbytype(requesttype) - - def saveassignee(self, username, firstname, middlename, lastname): - # FOIAssignee - newassignee = FOIAssignee() - newassignee.username = username - newassignee.firstname = firstname - newassignee.middlename = middlename - newassignee.lastname = lastname - return FOIAssignee.saveassignee(newassignee) - - def __getgroups(self,requesttype, status=None, bcgovcode=None): - return FOIRequestTeam.getteamsbystatusandprogramarea(requesttype,status,bcgovcode) - diff --git a/historical-search-api/request_api/services/auditservice.py b/historical-search-api/request_api/services/auditservice.py deleted file mode 100644 index d9289b544..000000000 --- a/historical-search-api/request_api/services/auditservice.py +++ /dev/null @@ -1,79 +0,0 @@ - -from os import stat -from request_api.auth import AuthHelper -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -from request_api.models.FOIRequests import FOIRequest -from request_api.models.FOIRawRequests import FOIRawRequest -from datetime import datetime -from request_api.utils.enums import RequestType -import dateutil.parser -import maya -class auditservice: - """ FOI audit management service - - This service class interacts with datastore to retrive the audit of changes. - - """ - - def getauditforfield(self, type, id, field, isall=False): - if field == "description": - return self.__getauditfordescription(type, id, isall) - else: - return None - - - def __getauditfordescription(self, type, id, isall): - _alldescriptions = [] - if type == "ministryrequest": - ministryrsp = self.__getauditfromministryrequest(id) - _alldescriptions = self.__getauditfromrawrequest(type, ministryrsp['foirequestid']) + ministryrsp['audit'] - else: - _alldescriptions = self.__getauditfromrawrequest(type, id) - #Filter summary of changes - datasummary=[] - _data, _startdate, _enddate = None, None, None - if len(_alldescriptions) > 0: - for entry in _alldescriptions: - if isall == True or (isall == False and _data != entry['description'] or _startdate != entry['fromdate'] or _enddate != entry['todate']): - datasummary.append({"description": entry['description'], "fromDate": entry['fromdate'], "toDate": entry['todate'], "createdAt": entry['createdat'], "createdBy": entry['createdby'], "status": entry['status']}) - _data = entry['description'] - _startdate = entry['fromdate'] - _enddate = entry['todate'] - return datasummary[::-1] - - - def __getauditfromministryrequest(self, id): - _ministrydescriptions = [] - ministryrecords = FOIMinistryRequest().getrequestById(id) - foirequestid = 0 - for entry in ministryrecords: - foirequestid = entry['foirequest_id'] - createdat = maya.parse(entry['created_at']).datetime(to_timezone='America/Vancouver', naive=False).strftime('%Y-%m-%d %H:%M:%S') - fromdate = datetime.fromisoformat(entry['recordsearchfromdate']).strftime("%Y-%m-%d") if entry['recordsearchfromdate'] is not None else None - todate = datetime.fromisoformat(entry['recordsearchtodate']).strftime("%Y-%m-%d") if entry['recordsearchtodate'] is not None else None - _ministrydescriptions.append({"description": entry['description'], "fromdate": fromdate, "todate": todate, "createdat": createdat, "createdby": entry['createdby'], "status": entry['requeststatus.name']}) - return {"foirequestid" :foirequestid , "audit":_ministrydescriptions} - - def __getauditfromrawrequest(self, type, id): - _rawdescriptions = [] - if type == "ministryrequest": - requestrecord = FOIRequest().getrequest(id) - rawrequestid= requestrecord['foirawrequestid'] - else: - rawrequestid= id - rawrecords = FOIRawRequest().getDescriptionSummaryById(rawrequestid) - - - for entry in rawrecords: - createdat = maya.parse(entry['createdat']).datetime(to_timezone='America/Vancouver', naive=False).strftime('%Y-%m-%d %H:%M:%S') - fromdate =dateutil.parser.parse(entry['fromdate']).strftime('%Y-%m-%d') if entry['fromdate'] is not None else None - todate = dateutil.parser.parse(entry['todate']).strftime('%Y-%m-%d') if entry['todate'] is not None else None - if AuthHelper.getusertype() == "iao": - _rawdescriptions.append({"description": entry['description'], "fromdate": fromdate, "todate": todate, "createdat": createdat , "createdby": entry['createdby'], "status": entry['status']}) - else: - if requestrecord['requesttype'] == 'personal' or entry['ispiiredacted'] == True: - _rawdescriptions.append({"description": entry['description'], "fromdate": fromdate, "todate": todate, "createdat": createdat , "createdby": entry['createdby'], "status": entry['status']}) - return _rawdescriptions - - - \ No newline at end of file diff --git a/historical-search-api/request_api/services/cacheservice.py b/historical-search-api/request_api/services/cacheservice.py deleted file mode 100644 index b0516eb53..000000000 --- a/historical-search-api/request_api/services/cacheservice.py +++ /dev/null @@ -1,62 +0,0 @@ -import logging -import os -import requests -from request_api.utils.cache import clear_cache, clear_cache_key -from request_api.services.external.keycloakadminservice import KeycloakAdminService -from request_api.utils.enums import CacheUrls -from request_api.exceptions import BusinessException - -class cacheservice: - - request_url = os.getenv("FOI_REQ_MANAGEMENT_API_URL") - - def refreshcache(self, request_json): - try: - result= False - if(request_json is not None and 'key' in request_json): - result= self.__refreshcachebykey(request_json['key']) - else: - resp_flag = clear_cache() - if resp_flag: - result=self.__invokeresources(self.__getapilistbykey()) - return result - except Exception as ex: - logging.error(ex) - return False - - def __refreshcachebykey(self, key): - try: - result= False - resp_flag = clear_cache_key(key) - if resp_flag: - result= self.__invokeresources(self.__getapilistbykey(key)) - return result - except BusinessException as ex: - logging.error(ex) - return {'status': ex.status_code, 'message':ex.message}, 500 - return False - - def __getapilistbykey(self, key=None): - apiurls=[] - if key is not None: - apiurls.append(self.request_url+CacheUrls[key].value) - else: - apiurls.append(self.request_url+CacheUrls.keycloakusers.value) - apiurls.append(self.request_url+CacheUrls.programareas.value) - apiurls.append(self.request_url+CacheUrls.deliverymodes.value) - apiurls.append(self.request_url+CacheUrls.receivedmodes.value) - apiurls.append(self.request_url+CacheUrls.closereasons.value) - apiurls.append(self.request_url+CacheUrls.extensionreasons.value) - apiurls.append(self.request_url+CacheUrls.applicantcategories.value) - apiurls.append(self.request_url+CacheUrls.subjectcodes.value) - return apiurls - - def __invokeresources(self, apiurls): - try: - headers= {"Authorization": "Bearer " + KeycloakAdminService().get_token()} - for url in apiurls: - requests.get(url, headers=headers) - return True - except BusinessException as ex: - logging.error(ex) - return {'status': ex.status_code, 'message':ex.message}, 500 diff --git a/historical-search-api/request_api/services/cdogs_api_service.py b/historical-search-api/request_api/services/cdogs_api_service.py deleted file mode 100644 index 4ec2ddf66..000000000 --- a/historical-search-api/request_api/services/cdogs_api_service.py +++ /dev/null @@ -1,127 +0,0 @@ -# Copyright © 2019 Province of British Columbia -# -# Licensed under the Apache License, Version 2.0 (the 'License'); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an 'AS IS' BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -"""Service for receipt generation.""" -import base64 -import json -import os -import re - -from flask import current_app -import requests -from request_api.exceptions import BusinessException, Error - - -class CdogsApiService: - """cdogs api Service class.""" - - def __init__(self): - self.access_token = self._get_access_token(); - - - - def generate_receipt(self, template_hash_code: str, data): - request_body = { - "options": { - "cachereport": False, - "convertTo": "pdf", - "overwrite": True, - "reportName": "Receipt" - }, - "data": data - } - json_request_body = json.dumps(request_body) - - headers = { - 'Content-Type': 'application/json', - 'Authorization': f'Bearer {self.access_token}' - } - url = f"{current_app.config['CDOGS_BASE_URL']}/api/v2/template/{template_hash_code}/render" - return self._post_generate_receipt(json_request_body, headers, url) - - def _post_generate_receipt(self, json_request_body, headers, url): - return requests.post(url, data= json_request_body, headers= headers) - - def upload_template(self, receipt_template_path: str): - - file_dir = os.path.dirname(os.path.realpath('__file__')) - template_file_path = os.path.join(file_dir, receipt_template_path) - - headers = { - "Authorization": f'Bearer {self.access_token}' - } - - url = f"{current_app.config['CDOGS_BASE_URL']}/api/v2/template" - template = {'template':('template', open(template_file_path, 'rb'), "multipart/form-data")} - - current_app.logger.info('Uploading template %s', template_file_path) - print('Uploading template %s', template_file_path) - response = self._post_upload_template(headers, url, template) - - if response.status_code == 200: - if response.headers.get("X-Template-Hash") is None: - raise BusinessException(Error.DATA_NOT_FOUND) - - current_app.logger.info('Returning new hash %s', response.headers['X-Template-Hash']) - print('Returning new hash %s', response.headers['X-Template-Hash']) - return response.headers['X-Template-Hash']; - - response_json = json.loads(response.content) - - if response.status_code == 405 and response_json['detail'] is not None: - match = re.findall(r"Hash '(.*?)'", response_json['detail']); - if match: - current_app.logger.info('Template already hashed with code %s', match[0]) - print('Template already hashed with code %s', match[0]) - return match[0] - - raise BusinessException(Error.DATA_NOT_FOUND) - - def _post_upload_template(self, headers, url, template): - response = requests.post(url, headers= headers, files= template) - return response - - def check_template_cached(self, template_hash_code: str): - - headers = { - "Authorization": f'Bearer {self.access_token}' - } - - url = f"{current_app.config['CDOGS_BASE_URL']}/api/v2/template/{template_hash_code}" - - response = requests.get(url, headers= headers) - return response.status_code == 200 - - - @staticmethod - def _get_access_token(): - token_url = current_app.config['CDOGS_TOKEN_URL'] - service_client = current_app.config['CDOGS_SERVICE_CLIENT'] - service_client_secret = current_app.config['CDOGS_SERVICE_CLIENT_SECRET'] - - basic_auth_encoded = base64.b64encode( - bytes(service_client + ':' + service_client_secret, 'utf-8')).decode('utf-8') - data = 'grant_type=client_credentials' - response = requests.post( - token_url, - data=data, - headers={ - 'Authorization': f'Basic {basic_auth_encoded}', - 'Content-Type': 'application/x-www-form-urlencoded' - } - ) - - response_json = response.json() - return response_json['access_token'] \ No newline at end of file diff --git a/historical-search-api/request_api/services/cfrfeeservice.py b/historical-search-api/request_api/services/cfrfeeservice.py deleted file mode 100644 index 3dd4cc5fc..000000000 --- a/historical-search-api/request_api/services/cfrfeeservice.py +++ /dev/null @@ -1,114 +0,0 @@ - -from os import stat -from re import VERBOSE -from request_api.models.FOIRequestCFRFees import FOIRequestCFRFee -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -from request_api.models.FOIRequestPayments import FOIRequestPayment -from request_api.services.cfrfeestatusservice import cfrfeestatusservice -from request_api.services.cfrformreasonservice import cfrformreasonservice -from request_api.utils.enums import PaymentEventType -from dateutil.parser import parse - -from dateutil import parser -from dateutil import tz -import pytz -import maya -from datetime import date -from datetime import datetime -import requests -from flask import current_app - -class cfrfeeservice: - """ FOI CFR Fee Form management service - Supports creation, update and delete of CFR fee form - """ - - def createcfrfee(self, ministryrequestid, data, userid): - cfrfee = self.__preparecfrfee(ministryrequestid, data, data.get('cfrfeeid') is not None) - cfrfee.__dict__.update(data) - return FOIRequestCFRFee.createcfrfee(cfrfee, userid) - - def sanctioncfrfee(self, ministryrequestid, data, userid): - cfrfee = self.__preparecfrfee(ministryrequestid, data) - cfrfee.feedata.update(data.get('feedata', {})) - return FOIRequestCFRFee.createcfrfee(cfrfee, userid) - - def getactivepayment(self, foirequestid, ministryrequestid): - return FOIRequestPayment.getactivepayment(foirequestid, ministryrequestid) - - def paycfrfee(self, ministryrequestid, amountpaid): - cfrfee = self.__preparecfrfee(ministryrequestid) - _amountpaid = float(cfrfee.feedata['amountpaid']) + amountpaid - _balanceremaining = cfrfee.feedata['balanceremaining'] - amountpaid - cfrfee.feedata['balanceremaining'] = _balanceremaining - cfrfee.feedata['amountpaid'] = '{:.2f}'.format(_amountpaid) - cfrfee.feedata['paymentdate'] = datetime.now().astimezone(pytz.timezone(current_app.config['LEGISLATIVE_TIMEZONE'])).strftime('%Y-%m-%d') - return FOIRequestCFRFee.createcfrfee(cfrfee, 'Online Payment') - - def updatepaymentmethod(self, ministryrequestid, paymenttype): - cfrfee = FOIRequestCFRFee.getcfrfee(ministryrequestid) - feedata = cfrfee['feedata'] - if (paymenttype == PaymentEventType.outstandingpaid.value): - feedata['balancepaymentmethod'] = 'creditcardonline' - else: - feedata['estimatepaymentmethod'] = 'creditcardonline' - return FOIRequestCFRFee.updatecfrfeedatabyid(ministryrequestid, feedata) - - def __preparecfrfee(self, ministryrequestid, data={}, getprevious=True): - cfrfee = FOIRequestCFRFee() - lkupcfrfee = self.getcfrfee(ministryrequestid) if getprevious else None - _version = 1 - if lkupcfrfee: - cfrfee.__dict__.update(lkupcfrfee) - _version = lkupcfrfee['version'] + 1 - cfrfee.updated_at = None - cfrfee.updatedby = None - cfrfee.version = _version - cfrfee.ministryrequestid = ministryrequestid - cfrfee.ministryrequestversion = FOIMinistryRequest.getversionforrequest(ministryrequestid) - if "status" in data and data['status'] not in (None,''): - cfrfee.cfrfeestatusid = cfrfeestatusservice().getcfrfeestatusidbyname(data['status']) - if "reason" in data and data['reason'] not in (None,''): - cfrfee.cfrformreasonid = cfrformreasonservice().getcfrformreasonidbyname(data['reason']) - return cfrfee - - - def getcfrfee(self, ministryrequestid): - cfrfee = FOIRequestCFRFee.getcfrfee(ministryrequestid) - return self.__formatcfrfee(cfrfee) - - def getapprovedcfrfee(self, ministryrequestid): - cfrfee = FOIRequestCFRFee.getapprovedcfrfee(ministryrequestid) - return self.__formatcfrfee(cfrfee) - - - def getcfrfeehistory(self, ministryrequestid): - cfrfees = [] - _cfrfees = FOIRequestCFRFee.getcfrfeehistory(ministryrequestid) - for cfrfee in _cfrfees: - cfrfees.append(self.__formatcfrfee(cfrfee)) - return cfrfees - - def __formatcfrfee(self,cfrfee): - if cfrfee is not None and cfrfee != {}: - cfrfee['created_at'] = self.__pstformat(cfrfee['created_at']) - if cfrfee.get('version_created_at') is not None: - cfrfee['version_created_at'] = self.__pstformat(cfrfee['version_created_at']) - if cfrfee['cfrfeestatusid'] is not None: - cfrfee['status'] = cfrfee['cfrfeestatus.name'] - cfrfee.pop('cfrfeestatus.name') - else: - cfrfee['status'] = None - if cfrfee['cfrformreasonid'] is not None: - cfrfee['reason'] = cfrfee['cfrformreason.name'] - cfrfee.pop('cfrformreason.name') - else: - cfrfee['reason'] = None - return cfrfee - else: - return {} - - - def __pstformat(self, inpdate): - formateddate = maya.parse(inpdate).datetime(to_timezone='America/Vancouver', naive=False) - return formateddate.strftime('%Y %b %d | %I:%M %p') diff --git a/historical-search-api/request_api/services/cfrfeestatusservice.py b/historical-search-api/request_api/services/cfrfeestatusservice.py deleted file mode 100644 index c547da573..000000000 --- a/historical-search-api/request_api/services/cfrfeestatusservice.py +++ /dev/null @@ -1,13 +0,0 @@ -from request_api.models.CFRFeeStatus import CFRFeeStatus - -class cfrfeestatusservice: - - def getcfrfeestatuses(self): - """ Returns the active records - """ - return CFRFeeStatus().getallcfrfeestatuses() - - def getcfrfeestatusidbyname(self, status): - """ Returns the active records - """ - return CFRFeeStatus().getcfrfeestatusid(status)['cfrfeestatusid'] \ No newline at end of file diff --git a/historical-search-api/request_api/services/cfrformreasonservice.py b/historical-search-api/request_api/services/cfrformreasonservice.py deleted file mode 100644 index 11cadb44c..000000000 --- a/historical-search-api/request_api/services/cfrformreasonservice.py +++ /dev/null @@ -1,13 +0,0 @@ -from request_api.models.CFRFormReason import CFRFormReason - -class cfrformreasonservice: - - def getcfrformreasons(self): - """ Returns the active records - """ - return CFRFormReason().getallcfrformreasons() - - def getcfrformreasonidbyname(self, reason): - """ Returns the active records - """ - return CFRFormReason().getcfrformreasonid(reason)['cfrformreasonid'] \ No newline at end of file diff --git a/historical-search-api/request_api/services/closereasonservice.py b/historical-search-api/request_api/services/closereasonservice.py deleted file mode 100644 index 053aca842..000000000 --- a/historical-search-api/request_api/services/closereasonservice.py +++ /dev/null @@ -1,8 +0,0 @@ -from request_api.models.CloseReasons import CloseReason - -class closereasonservice: - - def getclosereasons(self): - """ Returns the active records - """ - return CloseReason.getallclosereasons() \ No newline at end of file diff --git a/historical-search-api/request_api/services/commentservice.py b/historical-search-api/request_api/services/commentservice.py deleted file mode 100644 index 7528793e9..000000000 --- a/historical-search-api/request_api/services/commentservice.py +++ /dev/null @@ -1,185 +0,0 @@ - -from os import stat -from re import VERBOSE -from operator import itemgetter -from request_api.models.FOIRequestComments import FOIRequestComment -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -from request_api.models.FOIRawRequestComments import FOIRawRequestComment -from request_api.models.FOIRawRequests import FOIRawRequest -from request_api.services.assigneeservice import assigneeservice -from request_api.services.watcherservice import watcherservice -from request_api.models.default_method_result import DefaultMethodResult -import json -from dateutil.parser import parse -import datetime - -from dateutil import parser -from dateutil import tz -from pytz import timezone -import pytz -import maya - - - -class commentservice: - """ FOI comment management service - Supports creation, update and delete of comments for both unopened(raw) and opened(ministry) request - """ - - def createministryrequestcomment(self, data, userid, type=1): - version = FOIMinistryRequest.getversionforrequest(data["ministryrequestid"]) - return FOIRequestComment.savecomment(type, data, version, userid) - - def createrawrequestcomment(self, data, userid, type=1): - version = FOIRawRequest.getversionforrequest(data["requestid"]) - return FOIRawRequestComment.savecomment(type, data, version, userid) - - def createcomments(self, data, userid, type=2): - return FOIRequestComment.savecomment(type, data, data['version'], userid) - - def disableministryrequestcomment(self, commentid, userid): - return FOIRequestComment.disablecomment(commentid, userid) - - def disablerawrequestcomment(self, commentid, userid): - return FOIRawRequestComment.disablecomment(commentid, userid) - - def updateministryrequestcomment(self, commentid, data, userid): - result = FOIRequestComment.updatecomment(commentid, data, userid) - deactivateresult = None - if result.success == True: - commentsversion = result.args[1] - if commentsversion and commentsversion > 0: - deactivateresult = FOIRequestComment.deactivatecomment(commentid, userid, commentsversion) - if result and deactivateresult: - return result - return DefaultMethodResult(False,'Error in editing comment',commentid) - - def updaterawrequestcomment(self, commentid, data, userid): - result = FOIRawRequestComment.updatecomment(commentid, data, userid) - deactivateresult = None - if result.success == True: - commentsversion = result.args[1] - if commentsversion and commentsversion > 0: - deactivateresult = FOIRawRequestComment.deactivatecomment(commentid, userid, commentsversion) - if result and deactivateresult: - return result - return DefaultMethodResult(False,'Error in editing raw request comment',commentid) - - def getministryrequestcomments(self, ministryrequestid): - data = FOIRequestComment.getcomments(ministryrequestid) - result = self.__preparecomments(data) - return result - - def getrawrequestcomments(self, requestid): - data = FOIRawRequestComment.getcomments(requestid) - return self.__preparecomments(data) - - def copyrequestcomment(self, ministryrequestid, comments, userid): - _comments = [] - for comment in comments: - commentresponse=FOIRequestComment.savecomment(comment['commentTypeId'], self.__copyparentcomment(ministryrequestid, comment), 1, userid,comment['dateUF']) - _comments.append({"ministrycommentid":commentresponse.identifier,"rawcommentid":comment['commentId']}) - if comment['replies']: - for reply in comment['replies']: - response=FOIRequestComment.savecomment(reply['commentTypeId'], self.__copyreplycomment(ministryrequestid, reply, commentresponse.identifier), 1, userid,reply['dateUF']) - _comments.append({"ministrycommentid":response.identifier,"rawcommentid":comment['commentId']}) - return _comments - - def __copyparentcomment(self, ministryrequestid, entry): - return { - "ministryrequestid": ministryrequestid, - "comment": entry['text'], - "taggedusers": entry['taggedusers'] - } - - def __copyreplycomment(self, ministryrequestid, entry, parentcommentid): - return { - "ministryrequestid": ministryrequestid, - "comment": entry['text'], - "taggedusers": entry['taggedusers'], - "parentcommentid":parentcommentid - } - - def __preparecomments(self, data): - comments=[] - comments = self.__parentcomments(data) - for entry in data: - if entry['parentcommentid'] is not None: - for _comment in comments: - if entry['parentcommentid'] == _comment['commentId']: - _comment['replies'].append(self.__comment(entry)) - return comments - - def __parentcomments(self, data): - parentcomments = [] - for entry in data: - if entry['parentcommentid'] is None: - _comment = self.__comment(entry) - _comment['replies'] = [] - parentcomments.append(_comment) - return parentcomments - - - def __comment(self, comment): - commentcreateddate = maya.parse(comment["created_at"]).datetime(to_timezone='America/Vancouver', naive=False) - return { - "userId": comment['createdby'], - "commentId": comment['commentid'], - "text": comment['comment'], - "dateUF":maya.parse(comment['created_at']).iso8601(), - "date": commentcreateddate.strftime('%Y %b %d | %I:%M %p'), - "parentCommentId":comment['parentcommentid'], - "commentTypeId":comment['commenttypeid'], - "taggedusers" : comment['taggedusers'], - "edited": comment["commentsversion"] > 1 # edited: True/False - } - - def createcommenttagginguserlist(self,type,requestid): - if type == "ministryrequest": - watchers = watcherservice().getallministryrequestwatchers(requestid) - baserequestinfo = FOIMinistryRequest.getmetadata(requestid) - else: - watchers = watcherservice().getrawrequestwatchers(requestid) - baserequestinfo = FOIRawRequest.getmetadata(requestid) - userlist = [] - watcherteams= [] - if baserequestinfo is not None: - user= self.__formatuserlist(baserequestinfo['assignedTo'], baserequestinfo['assignedToFirstName'], baserequestinfo['assignedToLastName']) - userlist.append(user) - if 'bcgovcode' in baserequestinfo: - teamname = baserequestinfo['bcgovcode'].lower()+"ministryteam" - else: - teamname = baserequestinfo['selectedMinistries'][0]['code'].lower()+"ministryteam" - ministryteam = assigneeservice().getmembersbygroupname(teamname) - for ministry in ministryteam: - for member in ministry['members']: - user= self.__formatuserlist(member['username'], member['firstname'], member['lastname']) - userlist.append(user) - if watchers is not None: - self.__getwatchernames(watchers,watcherteams, userlist) - return userlist - - def __getwatchernames(self, watchers, watcherteams, userlist): - watchergrouplist= list(map(itemgetter('watchedbygroup'), watchers)) - watchergroups = set(watchergrouplist) - for group in watchergroups: - watcherteams.append(assigneeservice().getmembersbygroupname(group)) - for watcher in watchers: - #check if user already in list - existingusernames = list(map(itemgetter('username'), userlist)) - if watcher['watchedby'] not in existingusernames: - for team in watcherteams: - member= list(filter(lambda x: x['username'] == watcher['watchedby'], team[0]['members'])) - if len(member) > 0: - user= self.__formatuserlist(watcher['watchedby'], member[0]['firstname'], member[0]['lastname']) - userlist.append(user) - break - - def __formatuserlist(self, username, firstname, lastname): - user={} - user['username'] = username - user['firstname'] = firstname - user['lastname'] = lastname - user['fullname'] = lastname+", "+firstname - user['name'] = lastname+", "+firstname - return user \ No newline at end of file diff --git a/historical-search-api/request_api/services/commons/duecalculator.py b/historical-search-api/request_api/services/commons/duecalculator.py deleted file mode 100644 index c6ecd061e..000000000 --- a/historical-search-api/request_api/services/commons/duecalculator.py +++ /dev/null @@ -1,109 +0,0 @@ - -from os import stat -from re import VERBOSE -from datetime import datetime as datetime2 -from datetime import datetime, timedelta -import holidays -import os -from dateutil.parser import parse -from pytz import timezone -from request_api.utils.commons.datetimehandler import datetimehandler - -class duecalculator: - """ Due date calculator helper service - - """ - - def getpreviousbusinessday(self, cfrduedate,ca_holidays): - _prevbusinessday = self.__getpreviousweekday(cfrduedate) - if self.__isholiday(_prevbusinessday,ca_holidays) == False: - return _prevbusinessday - else: - return self.getpreviousbusinessday(_prevbusinessday,ca_holidays) - - def getpreviousbusinessday_by_n(self, duedate, ca_holidays, n): - _prevbusinessday = duedate - for i in range(n): - _prevbusinessday = self.getpreviousbusinessday(_prevbusinessday, ca_holidays) - return _prevbusinessday - - def formatduedate(self,input): - return datetimehandler().formatdate(input) - - def addbusinessdays(self, inpdate, days): - _holidays = self.getholidays() - businessdays = 0 - __calcdate = datetimehandler().getdate(inpdate) - while businessdays < days: - __calcdate = __calcdate + timedelta(days=1) - if self.isbusinessday(__calcdate, _holidays) == True: - businessdays += 1 - return __calcdate - - def getbusinessdaysbetween(self, date1, date2=None): - _holidays = self.getholidays() - businessdays = 0 - date2 = date2 if date2 not in (None, '') else self.gettoday() - _date1_date_fmt = datetimehandler().getdate(date1) - _date2_date_fmt = datetimehandler().getdate(date2) - __fromdate = _date1_date_fmt if _date1_date_fmt <= _date2_date_fmt else _date2_date_fmt - __todate = _date2_date_fmt if _date2_date_fmt >= _date1_date_fmt else _date1_date_fmt - __fromcalcdate =__fromdate - while datetimehandler().getdate(__fromcalcdate).date() < datetimehandler().getdate(__todate).date(): - if self.isbusinessday(__fromcalcdate, _holidays) == True: - businessdays += 1 - __fromcalcdate = __fromcalcdate + timedelta(days=1) - return businessdays - - def getholidays(self): - ca_holidays = [] - currentyear = datetime.today().year - years = [currentyear - 1, currentyear, currentyear + 1] - for year in years: - ca_holidays.extend(self.__getholidaysbyyear(year)) - return ca_holidays - - def gettoday(self): - return datetimehandler().gettoday() - - def now(self): - return datetimehandler().now() - - def __getholidaysbyyear(self, year): - ca_holidays = [] - for date, name in sorted(holidays.CA(prov='BC', years=year).items()): - ca_holidays.append(date.strftime(datetimehandler().getdefaultdateformat())) - if 'FOI_ADDITIONAL_HOLIDAYS' in os.environ and os.getenv('FOI_ADDITIONAL_HOLIDAYS') != '': - _addldays = os.getenv('FOI_ADDITIONAL_HOLIDAYS') - for _addlday in _addldays.split(","): - ca_holidays.append(_addlday.strip().replace('XXXX',str(year))) - return ca_holidays - - def isbusinessday(self, inpdate, holidays=None): - _holidays = self.getholidays() if holidays is None else holidays - if datetimehandler().formatdate(inpdate) not in _holidays and self.__isweekday(inpdate) == True: - return True - return False - - def __isweekday(self, inpdate): - _inpdate = datetimehandler().getdate(inpdate) - if _inpdate.weekday() < 5: - return True - else: - return False - - def __getpreviousweekday(self, cfrduedate): - diff = 1 - _cfrduedate = datetimehandler().getdate(cfrduedate) - if _cfrduedate.weekday() == 0: - diff = 3 - elif _cfrduedate.weekday() == 6: - diff = 2 - else : - diff = 1 - res = _cfrduedate - timedelta(days=diff) - return res.strftime(datetimehandler().getdefaultdateformat()) - - def __isholiday(self, input, ca_holidays): - return input in ca_holidays - diff --git a/historical-search-api/request_api/services/dashboardeventservice.py b/historical-search-api/request_api/services/dashboardeventservice.py deleted file mode 100644 index 017b29135..000000000 --- a/historical-search-api/request_api/services/dashboardeventservice.py +++ /dev/null @@ -1,69 +0,0 @@ - -from os import stat -from re import VERBOSE -from request_api.models.FOIRequestNotificationUsers import FOIRequestNotificationUser -from request_api.models.FOIRawRequestNotificationUsers import FOIRawRequestNotificationUser -from request_api.models.FOIRequestNotificationDashboard import FOIRequestNotificationDashboard -from request_api.auth import AuthHelper -from dateutil import tz, parser -from flask import jsonify -from datetime import datetime as datetime2 -from request_api.utils.commons.datetimehandler import datetimehandler -import re - -class dashboardeventservice: - """ FOI Event Dashboard - """ - - def geteventqueuepagination(self, queuetype, groups=None, page=1, size=10, sortingitems=[], sortingorders=[], filterfields=[], keyword=None, additionalfilter='All', userid=None): - _filterfields = self.__validateandtransform(filterfields) - notifications = None - if AuthHelper.getusertype() == "iao" and (queuetype is None or queuetype == "all"): - notifications = FOIRequestNotificationDashboard.getiaoeventpagination(groups, page, size, sortingitems, sortingorders, _filterfields, keyword, additionalfilter, userid, AuthHelper.isiaorestrictedfilemanager()) - elif AuthHelper.getusertype() == "ministry" and (queuetype is not None and queuetype == "ministry"): - notifications = FOIRequestNotificationDashboard.getministryeventpagination(groups, page, size, sortingitems, sortingorders, _filterfields, keyword, additionalfilter, userid, AuthHelper.isiaorestrictedfilemanager(), AuthHelper.isministryrestrictedfilemanager()) - if notifications is not None: - eventqueue = [] - for notification in notifications.items: - eventqueue.append(self.__prepareevent(notification)) - - meta = { - 'page': notifications.page, - 'pages': notifications.pages, - 'total': notifications.total, - 'prev_num': notifications.prev_num, - 'next_num': notifications.next_num, - 'has_next': notifications.has_next, - 'has_prev': notifications.has_prev, - } - return jsonify({'data': eventqueue, 'meta': meta}) - return jsonify({'data': [], 'meta': None}) - - def __validateandtransform(self, filterfields): - return self.__transformfilteringfields(filterfields) - - def __transformfilteringfields(self, filterfields): - return list(map(lambda x: x.replace('createdat', 'createdatformatted'), filterfields)) - - - - def __prepareevent(self, notification): - return { - 'id': notification.id+notification.crtid, - 'status': notification.status, - 'rawrequestid': notification.rawrequestid, - 'requestid': notification.requestid, - 'ministryrequestid': notification.ministryrequestid, - 'createdat' : notification.createdatformatted, - #'createdat' : self.__formatedate(notification.createdat), - 'axisRequestId': notification.axisRequestId, - 'notification': notification.notification, - 'assignedToFormatted': notification.assignedToFormatted, - 'ministryAssignedToFormatted': notification.ministryAssignedToFormatted, - 'userFormatted': notification.userFormatted, - 'creatorFormatted': notification.creatorFormatted, - 'notificationType': notification.notificationtype, - 'description':notification.description - } - - \ No newline at end of file diff --git a/historical-search-api/request_api/services/dashboardservice.py b/historical-search-api/request_api/services/dashboardservice.py deleted file mode 100644 index 000cac1ce..000000000 --- a/historical-search-api/request_api/services/dashboardservice.py +++ /dev/null @@ -1,226 +0,0 @@ -from request_api.models.FOIRawRequests import FOIRawRequest -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -from request_api.models.FOIRestrictedMinistryRequests import FOIRestrictedMinistryRequest -from request_api.models.FOIRawRequestWatchers import FOIRawRequestWatcher -from request_api.models.FOIRequestWatchers import FOIRequestWatcher -from dateutil import tz, parser -import datetime as dt -from pytz import timezone -import pytz -import maya -from request_api.auth import AuthHelper - -from flask import jsonify - -SHORT_DATEFORMAT = '%Y %b, %d' -LONG_DATEFORMAT = '%Y-%m-%d %H:%M:%S.%f' - -class dashboardservice: - """ FOI dashboard management service - - This service class manages dashboard retrival for both unopened and opened request with consideration of user types. - - """ - - def __preparefoirequestinfo(self, request, receiveddate, receiveddateuf, idnumberprefix = ''): - idnumber = self.__getidnumber(idnumberprefix, request.axisRequestId, request.idNumber) - baserequestinfo = self.__preparebaserequestinfo( - request.id, - request.requestType, - request.currentState, - receiveddate, - receiveddateuf, - request.assignedGroup, - request.assignedTo, - idnumberprefix + request.idNumber, - idnumber, - request.version, - request.description, - request.recordsearchfromdate, - request.recordsearchtodate, - ) - baserequestinfo.update({'firstName': request.firstName}) - baserequestinfo.update({'lastName': request.lastName}) - baserequestinfo.update({'xgov': 'No'}) - baserequestinfo.update({'assignedToFirstName': request.assignedToFirstName}) - baserequestinfo.update({'duedate': request.duedate}) - baserequestinfo.update({'cfrduedate': request.cfrduedate}) - baserequestinfo.update({'applicantcategory': request.applicantcategory}) - baserequestinfo.update({'assignedToLastName': request.assignedToLastName}) - baserequestinfo.update({'onBehalfFirstName': request.onBehalfFirstName}) - baserequestinfo.update({'onBehalfLastName': request.onBehalfLastName}) - baserequestinfo.update({'onBehalfFormatted': request.onBehalfFormatted}) - baserequestinfo.update({'requestpagecount': request.requestpagecount}) - baserequestinfo.update({'recordspagecount': request.recordspagecount}) - baserequestinfo.update({'axispagecount': request.axispagecount}) - baserequestinfo.update({'axislanpagecount': request.axislanpagecount}) - baserequestinfo.update({'bcgovcode': request.bcgovcode}) - isoipcreview = request.isoipcreview if request.isoipcreview == True else False - baserequestinfo.update({'isoipcreview': isoipcreview}) - return baserequestinfo - - def __preparebaserequestinfo(self, id, requesttype, status, receiveddate, receiveddateuf, assignedgroup, assignedto, idnumber, axisrequestid, version, description, fromdate, todate): - return {'id': id, - 'requestType': requesttype, - 'currentState': status, - 'receivedDate': receiveddate, - 'receivedDateUF': receiveddateuf, - 'assignedGroup': assignedgroup, - 'assignedTo': assignedto, - 'idNumber': idnumber, - 'axisRequestId': axisrequestid, - 'version':version, - 'description':description, - 'fromdate':fromdate, - 'todate':todate, - } - - def getrequestqueuepagination(self, groups=None, page=1, size=10, sortingitems=[], sortingorders=[], filterfields=[], keyword=None, additionalfilter='All', userid=None): - requests = FOIRawRequest.getrequestspagination(groups, page, size, sortingitems, sortingorders, filterfields, keyword, additionalfilter, userid, AuthHelper.isiaorestrictedfilemanager(), AuthHelper.getusertype()) - requestqueue = [] - for request in requests.items: - - if(request.receivedDateUF is None): #request from online form has no received date in json - _receiveddate = maya.parse(request.created_at).datetime(to_timezone='America/Vancouver', naive=False) - else: - _receiveddate = parser.parse(request.receivedDateUF) - - if(request.ministryrequestid == None): - unopenrequest = self.__preparefoirequestinfo(request, _receiveddate.strftime(SHORT_DATEFORMAT), _receiveddate.strftime(LONG_DATEFORMAT), idnumberprefix= 'U-00') - unopenrequest.update({'assignedToFormatted': request.assignedToFormatted}) - unopenrequest.update({'isiaorestricted': request.isiaorestricted}) - - # isawatcher = FOIRawRequestWatcher.isawatcher(request.id,userid) - if request.isiaorestricted == True: - unopenrequest.update({'lastName': 'Restricted'}) - unopenrequest.update({'firstName': 'Request'}) - - requestqueue.append(unopenrequest) - - else: - _openrequest = self.__preparefoirequestinfo(request, _receiveddate.strftime(SHORT_DATEFORMAT), _receiveddate.strftime(LONG_DATEFORMAT)) - _openrequest.update({'ministryrequestid': request.ministryrequestid}) - _openrequest.update({'extensions': request.extensions}) - _openrequest.update({'assignedToFormatted': request.assignedToFormatted}) - _openrequest.update({'ministryAssignedToFormatted': request.ministryAssignedToFormatted}) - - isiaorestricted = request.isiaorestricted if request.isiaorestricted == True else False - _openrequest.update({'isiaorestricted': isiaorestricted}) - - if isiaorestricted == True: - _openrequest.update({'lastName': 'Restricted'}) - _openrequest.update({'firstName': 'Request'}) - - requestqueue.append(_openrequest) - - - meta = { - 'page': requests.page, - 'pages': requests.pages, - 'total': requests.total, - 'prev_num': requests.prev_num, - 'next_num': requests.next_num, - 'has_next': requests.has_next, - 'has_prev': requests.has_prev, - } - - - - - return jsonify({'data': requestqueue, 'meta': meta}) - - def getministryrequestqueuepagination (self, groups=None, page=1, size=10, sortingitems=[], sortingorders=[], filterfields=[], keyword=None, additionalfilter='All', userid=None): - requests = FOIMinistryRequest.getrequestspagination(groups, page, size, sortingitems, sortingorders, filterfields, keyword, additionalfilter, userid, AuthHelper.isiaorestrictedfilemanager(), AuthHelper.isministryrestrictedfilemanager()) - - requestqueue = [] - for request in requests.items: - _openrequest = self.__preparebaserequestinfo(request.id, request.requestType, request.currentState, - request.receivedDate, request.receivedDateUF, request.assignedGroup, - request.assignedTo, request.idNumber, request.axisRequestId, request.version, - request.description, request.recordsearchfromdate, request.recordsearchtodate) - _openrequest.update({'assignedministrygroup': request.assignedministrygroup}) - _openrequest.update({'assignedministryperson': request.assignedministryperson}) - _openrequest.update({'cfrstatus':'Select Division'}) - _openrequest.update({'cfrduedate': request.cfrduedate}) - _openrequest.update({'duedate': request.duedate}) - _openrequest.update({'ministryrequestid': request.ministryrequestid}) - _openrequest.update({'applicantcategory': request.applicantcategory}) - _openrequest.update({'bcgovcode': request.bcgovcode}) - _openrequest.update({'assignedToFirstName': request.assignedToFirstName}) - _openrequest.update({'assignedToLastName': request.assignedToLastName}) - _openrequest.update({'assignedministrypersonFirstName': request.assignedministrypersonFirstName}) - _openrequest.update({'assignedministrypersonLastName': request.assignedministrypersonLastName}) - _openrequest.update({'assignedToFormatted': request.assignedToFormatted}) - _openrequest.update({'ministryAssignedToFormatted': request.ministryAssignedToFormatted}) - - isministryrestricted = request.isministryrestricted if request.isministryrestricted == True else False - _openrequest.update({'isministryrestricted': isministryrestricted}) - isoipcreview = request.isoipcreview if request.isoipcreview == True else False - _openrequest.update({'isoipcreview': isoipcreview}) - requestqueue.append(_openrequest) - - meta = { - 'page': requests.page, - 'pages': requests.pages, - 'total': requests.total, - 'prev_num': requests.prev_num, - 'next_num': requests.next_num, - 'has_next': requests.has_next, - 'has_prev': requests.has_prev, - } - - return jsonify({'data': requestqueue, 'meta': meta}) - - def advancedsearch(self, params={'usertype': 'iao', 'groups':None, 'page':1, 'size':10, 'sortingitems':[], 'sortingorders':[], 'requeststate':[], 'requeststatus':[], 'requesttype':[], 'requestflags':[], 'publicbody':[], 'daterangetype':None, 'fromdate':None, 'todate':None, 'search':None, 'keywords':[], 'userid':None}): - userid = AuthHelper.getuserid() - - if (params['usertype'] == "iao"): - requests = FOIRawRequest.advancedsearch(params, userid, AuthHelper.isiaorestrictedfilemanager()) - else: - requests = FOIMinistryRequest.advancedsearch(params, userid, AuthHelper.isministryrestrictedfilemanager()) - - requestqueue = [] - for request in requests.items: - if(request.receivedDateUF is None): #request from online form has no received date in json - _receiveddate = maya.parse(request.created_at).datetime(to_timezone='America/Vancouver', naive=False) - else: - _receiveddate = parser.parse(request.receivedDateUF) - - if(request.ministryrequestid == None): - unopenrequest = self.__preparefoirequestinfo(request, _receiveddate.strftime(SHORT_DATEFORMAT), _receiveddate.strftime(LONG_DATEFORMAT), idnumberprefix= 'U-00') - unopenrequest.update({'description':request.description}) - unopenrequest.update({'assignedToFormatted': request.assignedToFormatted}) - unopenrequest.update({'isiaorestricted': request.isiaorestricted}) - - requestqueue.append(unopenrequest) - else: - _openrequest = self.__preparefoirequestinfo(request, _receiveddate.strftime(SHORT_DATEFORMAT), _receiveddate.strftime(LONG_DATEFORMAT)) - _openrequest.update({'ministryrequestid':request.ministryrequestid}) - _openrequest.update({'extensions': request.extensions}) - _openrequest.update({'description':request.description}) - _openrequest.update({'assignedToFormatted': request.assignedToFormatted}) - _openrequest.update({'ministryAssignedToFormatted': request.ministryAssignedToFormatted}) - - isiaorestricted = request.isiaorestricted if request.isiaorestricted == True else False - _openrequest.update({'isiaorestricted': isiaorestricted}) - - requestqueue.append(_openrequest) - - meta = { - 'page': requests.page, - 'pages': requests.pages, - 'total': requests.total, - 'prev_num': requests.prev_num, - 'next_num': requests.next_num, - 'has_next': requests.has_next, - 'has_prev': requests.has_prev, - } - - return jsonify({'data': requestqueue, 'meta': meta}) - - def __getidnumber(self, idprefix, axisrequestid, filenumber): - if axisrequestid is not None: - return axisrequestid - elif idprefix: - return idprefix + filenumber - return "" \ No newline at end of file diff --git a/historical-search-api/request_api/services/deliverymodeservice.py b/historical-search-api/request_api/services/deliverymodeservice.py deleted file mode 100644 index 754824183..000000000 --- a/historical-search-api/request_api/services/deliverymodeservice.py +++ /dev/null @@ -1,8 +0,0 @@ -from request_api.models.DeliveryModes import DeliveryMode - -class deliverymodeservice: - - def getdeliverymodes(self): - """ Returns the active records - """ - return DeliveryMode.getdeliverymodes() \ No newline at end of file diff --git a/historical-search-api/request_api/services/divisionstageservice.py b/historical-search-api/request_api/services/divisionstageservice.py deleted file mode 100644 index c62a8637c..000000000 --- a/historical-search-api/request_api/services/divisionstageservice.py +++ /dev/null @@ -1,68 +0,0 @@ -from request_api.models.ProgramAreas import ProgramArea -from request_api.models.ProgramAreaDivisions import ProgramAreaDivision -from request_api.models.ProgramAreaDivisionStages import ProgramAreaDivisionStage -import json - -class divisionstageservice: - - def getdivisionandstages(self, bcgovcode): - divisionstages = [] - programarea = ProgramArea.getprogramarea(bcgovcode) - divisions = ProgramAreaDivision.getprogramareadivisions(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 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 = [] - division_stages = ProgramAreaDivisionStage.getprogramareadivisionstages() - for stage in division_stages: - if stage['isactive'] == True: - activestages.append({"stageid": stage['stageid'], "name": self.escapestr(stage['name'])}) - return activestages - - def escapestr(self,value): - return value.replace(u"’", u"'") \ No newline at end of file diff --git a/historical-search-api/request_api/services/document_generation_service.py b/historical-search-api/request_api/services/document_generation_service.py deleted file mode 100644 index 0cb8e9ed8..000000000 --- a/historical-search-api/request_api/services/document_generation_service.py +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright © 2019 Province of British Columbia -# -# Licensed under the Apache License, Version 2.0 (the 'License'); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an 'AS IS' BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -"""Service for receipt generation.""" - -from flask import current_app -from request_api.exceptions import BusinessException, Error -from request_api.models import DocumentTemplate, DocumentType -from request_api.services.cdogs_api_service import CdogsApiService -from request_api.services.external.storageservice import storageservice -from request_api.services.email.templates.templateconfig import templateconfig -from request_api.services.documentservice import documentservice -import json -import logging - - -class DocumentGenerationService: - """document generation Service class.""" - - def __init__(self,documenttypename='receipt'): - self.cdgos_api_service = CdogsApiService() - self.documenttypename = documenttypename - receipt_document_type : DocumentType = DocumentType.get_document_type_by_name(self.documenttypename) - if receipt_document_type is None: - raise BusinessException(Error.DATA_NOT_FOUND) - - self.receipt_template : DocumentTemplate = DocumentTemplate \ - .get_template_by_type(document_type_id = receipt_document_type.document_type_id) - if self.receipt_template is None: - raise BusinessException(Error.DATA_NOT_FOUND) - - - def generate_receipt(self, data, receipt_template_path='request_api/receipt_templates/receipt_word.docx'): - template_cached = False - if self.receipt_template.cdogs_hash_code: - current_app.logger.info('Checking if template %s is cached', self.receipt_template.cdogs_hash_code) - template_cached = self.cdgos_api_service.check_template_cached(self.receipt_template.cdogs_hash_code) - - if self.receipt_template.cdogs_hash_code is None or not template_cached: - current_app.logger.info('Uploading new template') - self.receipt_template.cdogs_hash_code = self.cdgos_api_service.upload_template(receipt_template_path) - self.receipt_template.flush() - self.receipt_template.commit() - - current_app.logger.info('Generating receipt') - return self.cdgos_api_service.generate_receipt(template_hash_code= self.receipt_template.cdogs_hash_code, data= data) - - def upload_receipt(self, filename, filebytes, ministryrequestid, ministrycode, filenumber, attachmentcategory): - try: - logging.info("Upload receipt for ministry request id"+ str(ministryrequestid)) - _response = storageservice().uploadbytes(filename, filebytes, ministrycode, filenumber) - logging.info("Upload status for payload"+ json.dumps(_response)) - if _response["success"] == True: - _documentschema = {"documents": [{"filename": _response["filename"], "documentpath": _response["documentpath"], "category": templateconfig().getattachmentcategory(attachmentcategory)}]} - documentservice().createrequestdocument(ministryrequestid, _documentschema, "SYSTEM", "ministryrequest") - return _response - except Exception as ex: - logging.exception(ex) \ No newline at end of file diff --git a/historical-search-api/request_api/services/documentservice.py b/historical-search-api/request_api/services/documentservice.py deleted file mode 100644 index 3fd2c72d1..000000000 --- a/historical-search-api/request_api/services/documentservice.py +++ /dev/null @@ -1,211 +0,0 @@ - -from os import stat -import os -from re import VERBOSE -from request_api.models.FOIMinistryRequestDocuments import FOIMinistryRequestDocument -from request_api.models.FOIRawRequestDocuments import FOIRawRequestDocument -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -from request_api.models.FOIRawRequests import FOIRawRequest -from request_api.schemas.foidocument import CreateDocumentSchema -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 -import maya - -class documentservice: - """ FOI Document management service - """ - - def getrequestdocuments(self, requestid, requesttype, version=None): - requestversion = self.__getversionforrequest(requestid,requesttype) if version is None else version - documents = FOIMinistryRequestDocument.getdocuments(requestid, requestversion) if requesttype == "ministryrequest" else FOIRawRequestDocument.getdocuments(requestid, requestversion) - return self.__formatcreateddate(documents) - - def getactiverequestdocuments(self, requestid, requesttype, version=None): - requestversion = self.__getversionforrequest(requestid,requesttype) if version is None else version - documents = FOIMinistryRequestDocument.getactivedocuments(requestid) if requesttype == "ministryrequest" else FOIRawRequestDocument.getdocuments(requestid, requestversion) - return self.__formatcreateddate(documents) - - 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 document["category"] != "applicant": - filtereddocuments.append(document) - return filtereddocuments - return documents - - def createrequestdocument(self, requestid, documentschema, userid, requesttype): - if requesttype == "ministryrequest": - return self.createministryrequestdocument(requestid, documentschema, userid) - else: - return self.createrawrequestdocument(requestid, documentschema, userid) - - def createrequestdocumentversion(self, requestid, documentid, documentschema, userid, requesttype): - if requesttype == "ministryrequest": - return self.createministrydocumentversion(requestid, documentid, documentschema, userid) - 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) - - def copyrequestdocuments(self, ministryrequestid, documents, userid): - """ Copies request documents upon updates - """ - _documents = [] - for document in documents: - _documents.append({"documentpath":document["documentpath"],"filename":document["filename"],"category":document["category"], "created_at":document["created_at"],"createdby": document["createdby"]}) - documentschema = {"documents": _documents} - return self.createministryrequestdocument(ministryrequestid, documentschema, userid) - - def createministryrequestdocument(self, ministryrequestid, documentschema, userid): - version = self.__getversionforrequest(ministryrequestid, "ministryrequest") - return FOIMinistryRequestDocument.createdocuments(ministryrequestid, version, documentschema['documents'], userid) - - def createrawrequestdocument(self, requestid, documentschema, userid): - version = self.__getversionforrequest(requestid, "rawrequest") - return FOIRawRequestDocument.createdocuments(requestid, version, documentschema['documents'], userid) - - def createministrydocumentversion(self, ministryrequestid, documentid, documentschema, userid): - version = self.__getversionforrequest(ministryrequestid, "ministryrequest") - document = FOIMinistryRequestDocument.getdocument(documentid) - if document: - 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: - return FOIMinistryRequestDocument.createdocument(ministryrequestid, version, documentschema, userid) - - - def createrawdocumentversion(self, requestid, documentid, documentschema, userid): - version = self.__getversionforrequest(requestid, "rawrequest") - document = FOIRawRequestDocument.getdocument(documentid) - FOIRawRequestDocument.deActivaterawdocumentsversion(documentid, document['version'], userid) - return FOIRawRequestDocument.createdocumentversion(requestid, version, self.__copydocumentproperties(document,documentschema,document['version']), userid) - - def createrawrequestdocumentversion(self, requestid): - newversion = self.__getversionforrequest(requestid,"rawrequest") - documents = self.getrequestdocuments(requestid, "rawrequest", newversion-1) - documentarr = [] - for document in documents: - documentarr.append({"documentpath": document["documentpath"], "filename": document["filename"], "category": document['category'], "version": 1, "foirequest_id": requestid, "foirequestversion_id": newversion - 1, "createdby": document['createdby'], "created_at": document['created_at'] }) - return self.createrawrequestdocument(requestid, {"documents": documentarr}, None) - - def uploadpersonaldocuments(self, requestid, attachments): - attachmentlist = [] - if attachments: - for attachment in attachments: - attachment['filestatustransition'] = 'personal' - attachment['ministrycode'] = 'Misc' - attachment['requestnumber'] = str(requestid) - attachment['file'] = base64.b64decode(attachment['base64data']) - attachment.pop('base64data') - attachmentresponse = storageservice().upload(attachment) - attachmentlist.append(attachmentresponse) - - documentschema = CreateDocumentSchema().load({'documents': attachmentlist}) - return self.createrequestdocument(requestid, documentschema, None, "rawrequest") - - def getattachments(self, requestid, requesttype, category): - documents = self.getlatestdocumentsforemail(requestid, requesttype, category) - return self.__getattachmentlist(documents) - - def getreceiptattachments(self, ministryrequestid, category): - _documents = FOIMinistryRequestDocument().getlatestreceiptdocumentforemail(ministryrequestid, category) - documents = self.__formatcreateddate(_documents) - return self.__getattachmentlist(documents) - - def getlatestdocumentsforemail(self, requestid, requesttype, category, version=None): - requestversion = self.__getversionforrequest(requestid,requesttype) if version is None else version - documents = FOIMinistryRequestDocument.getlatestdocumentsforemail(requestid, requestversion, category) if requesttype == "ministryrequest" else FOIRawRequestDocument.getdocuments(requestid, requestversion) - return self.__formatcreateddate(documents) - - def getapplicantcorrespondenceattachmentsbyapplicantcorrespondenceid(self, applicantcorrespondenceid): - documents = FOIApplicantCorrespondenceAttachment.getapplicantcorrespondenceattachmentsbyapplicantcorrespondenceid(applicantcorrespondenceid) - if(documents is None): - raise ValueError('No template found') - attachmentlist = [] - for document in documents: - filename = document.get('attachmentfilename') - s3uri = document.get('attachmentdocumenturipath') - attachment= storageservice().download(s3uri) - attachdocument = {"filename": filename, "file": attachment, "url": s3uri} - attachmentlist.append(attachdocument) - return attachmentlist - - def __getattachmentlist(self, documents): - if(documents is None): - raise ValueError('No template found') - attachmentlist = [] - for document in documents: - filename = document.get('filename') - s3uri = document.get('documentpath') - attachment= storageservice().download(s3uri) - attachdocument = {"filename": filename, "file": attachment, "url": s3uri} - attachmentlist.append(attachdocument) - return attachmentlist - - def __getversionforrequest(self, requestid, requesttype): - """ Returns the active version of the request id based on type. - """ - if requesttype == "ministryrequest": - document = FOIMinistryRequest.getversionforrequest(requestid) - else: - document = FOIRawRequest.getversionforrequest(requestid) - if document: - return document[0] - - def __copydocumentproperties(self, document, documentschema, version): - document['version'] = version +1 - document['filename'] = documentschema['filename'] if 'filename' in documentschema else document['filename'] - document['documentpath'] = documentschema['documentpath'] if 'documentpath' in documentschema else document['documentpath'] - document['category'] = documentschema['category'] if 'category' in documentschema else document['category'] - document['isactive'] = documentschema['isactive'] if 'isactive' in documentschema else True - document['created_at'] = documentschema['created_at'] if 'created_at' in documentschema else None - document['createdby'] = documentschema['createdby'] if 'createdby' in documentschema else None - return document - - def __formatcreateddate(self, documents): - for document in documents: - document = self.__pstformat(document) - return documents - - def __pstformat(self, document): - formatedcreateddate = maya.parse(document['created_at']).datetime(to_timezone='America/Vancouver', naive=False) - document['created_at'] = formatedcreateddate.strftime('%Y %b %d | %I:%M %p') - return document - - - diff --git a/historical-search-api/request_api/services/email/inboxservice.py b/historical-search-api/request_api/services/email/inboxservice.py deleted file mode 100644 index 43a1a8d2e..000000000 --- a/historical-search-api/request_api/services/email/inboxservice.py +++ /dev/null @@ -1,59 +0,0 @@ - -from os import stat -import imaplib -import os -from imap_tools import MailBox, AND -import logging -import email - -MAIL_SERVER_IMAP = os.getenv('EMAIL_SERVER_IMAP') -MAIL_SRV_USERID = os.getenv('EMAIL_SRUSERID') -MAIL_SRV_PASSWORD = os.getenv('EMAIL_SRPWD') - -class inboxservice: - - """ FOI Email Inbox Service - - This service wraps up inbox operations. - - """ - - def get_failure_deliverystatus_as_eml(self, subject, email): - message = self._get_deliverystatus_by_text(subject, email) - if message is not None: - return {"success" : False, "message": "Unable to send", "content": message["content"].obj.__bytes__(), "reason": message["reason"]} - return {"success" : True, "message": "Sent successfully", "content": None, "reason": None} - - def _get_deliverystatus_by_text(self, msgkey, text): - try: - mailbox = MailBox(MAIL_SERVER_IMAP) - mailbox.login(MAIL_SRV_USERID, MAIL_SRV_PASSWORD) - messages = mailbox.fetch(criteria=AND(text=text), reverse = True) - for message in messages: - email_message = email.message_from_bytes(message.obj.__bytes__()).as_string() - if text in email_message and msgkey in email_message: - _reasoncode = 500 - for code in self.__getmanagederrorcodes(): - if code in message.text: - _reasoncode = code - return {"code": _reasoncode, "reason": self.__getreason(_reasoncode), "content": message} - return None - except Exception as ex: - logging.exception(ex) - logging.debug('Read email for '+text) - - - def __getmanagederrorcodes(self): - return ["541", "550", "550 5.1.1", "551", "552"] - - def __getreason(self, code): - if code == "541": - return "Message rejected by the recipient address" - elif str(code).startswith("550"): - return "The email account that you tried to reach does not exist. Please try double-checking the recipient's email address for typos or unnecessary spaces" - elif code == "551": - return "Intended recipient mailbox isn't available on the receiving server" - elif code == "552": - return "Message wasn't sent because the recipient mailbox doesn't have enough storage" - else: - return "Server could not complete the action" \ No newline at end of file diff --git a/historical-search-api/request_api/services/email/senderservice.py b/historical-search-api/request_api/services/email/senderservice.py deleted file mode 100644 index 4c5034e4a..000000000 --- a/historical-search-api/request_api/services/email/senderservice.py +++ /dev/null @@ -1,85 +0,0 @@ - -from audioop import reverse -from os import stat -import os -import smtplib -from email.mime.multipart import MIMEMultipart -from email.mime.text import MIMEText -from email.mime.base import MIMEBase -from email import encoders -from request_api.services.email.templates.templateconfig import templateconfig -import imaplib -from imap_tools import MailBox, AND -import logging -import email -import json -from request_api.services.external.storageservice import storageservice - -MAIL_SERVER_SMTP = os.getenv('EMAIL_SERVER_SMTP') -MAIL_SERVER_SMTP_PORT = os.getenv('EMAIL_SERVER_SMTP_PORT') -MAIL_SERVER_IMAP = os.getenv('EMAIL_SERVER_IMAP') -MAIL_SRV_USERID = os.getenv('EMAIL_SRUSERID') -MAIL_SRV_PASSWORD = os.getenv('EMAIL_SRPWD') -MAIL_FROM_ADDRESS = os.getenv('EMAIL_SENDER_ADDRESS') -MAIL_FOLDER_OUTBOX = os.getenv('EMAIL_FOLDER_OUTBOX') -MAIL_FOLDER_INBOX = os.getenv('EMAIL_FOLDER_INBOX') -class senderservice: - """ Email Sender service - - This service wraps up send operations. - - """ - - def send(self, subject, content, _messageattachmentlist, requestjson): - logging.debug("Begin: Send email for request = "+json.dumps(requestjson)) - msg = MIMEMultipart() - msg['From'] = MAIL_FROM_ADDRESS - msg['To'] = requestjson["email"] - msg['Subject'] = subject - part = MIMEText(content, "html") - msg.attach(part) - # Add Attachment and Set mail headers - for attachment in _messageattachmentlist: - part = MIMEBase("application", "octet-stream") - part.set_payload(attachment.get('file').content) - encoders.encode_base64(part) - part.add_header( - "Content-Disposition", - "attachment", filename= attachment.get('filename') - ) - msg.attach(part) - try: - with smtplib.SMTP(MAIL_SERVER_SMTP, MAIL_SERVER_SMTP_PORT) as smtpobj: - smtpobj.ehlo() - smtpobj.starttls() - smtpobj.ehlo() - #smtpobj.login(MAIL_SRV_USERID, MAIL_SRV_PASSWORD) - smtpobj.sendmail(msg['From'], msg['To'], msg.as_string()) - smtpobj.quit() - logging.debug("End: Send email for request = "+json.dumps(requestjson)) - return {"success" : True, "message": "Sent successfully"} - except Exception as e: - logging.exception(e) - return {"success" : False, "message": "Unable to send"} - - - def read_outbox_as_bytes(self, servicekey, requestjson): - logging.debug("Begin: Read sent item for request = "+json.dumps(requestjson)) - _subject = templateconfig().getsubject(servicekey, requestjson) - try: - mailbox = MailBox(MAIL_SERVER_IMAP) - mailbox.login(MAIL_SRV_USERID, MAIL_SRV_PASSWORD) - #Navigate to sent Items - is_exists = mailbox.folder.exists(MAIL_FOLDER_OUTBOX) - if is_exists == True: - mailbox.folder.set(MAIL_FOLDER_OUTBOX) - messages = mailbox.fetch(criteria=AND(subject=_subject), reverse = True) - for message in messages: - logging.debug("End: Read sent item for request = "+json.dumps(requestjson)) - return message.obj.__bytes__() - except Exception as ex: - logging.exception(ex) - logging.debug("End: Read sent item for request = "+json.dumps(requestjson)) - return None - - diff --git a/historical-search-api/request_api/services/email/templates/templateconfig.py b/historical-search-api/request_api/services/email/templates/templateconfig.py deleted file mode 100644 index b307a2cfe..000000000 --- a/historical-search-api/request_api/services/email/templates/templateconfig.py +++ /dev/null @@ -1,93 +0,0 @@ -import json -import logging - -class templateconfig: - """This class is reserved for consolidating all templates - """ - - def gettemplatename(self, key): - if key == "PAYONLINE": - return "fee_estimate_notification.html" - elif key == "HALFPAYMENT": - return "fee_payment_confirmation_half.html" - elif key == "FULLPAYMENT": - return "fee_payment_confirmation_full.html" - elif key == "PAYOUTSTANDING": - return "fee_estimate_notification_outstanding.html" - elif key == "PAYOUTSTANDINGFULLPAYMENT": - return "fee_payment_confirmation_outstanding.html" - else: - logging.info("Unknown key") - return None - - def getsubject(self, key, requestjson): - if key == "PAYONLINE" or key == "PAYOUTSTANDING" or key == "FEEESTIMATENOTIFICATION" or key == "OUTSTANDINGFEEESTIMATENOTIFICATION": - return "Your FOI Request ["+requestjson["axisRequestId"]+"]" - elif key == "FEE-ESTIMATE-PAYMENT-RECEIPT" or key == "OUTSTANDING-PAYMENT-RECEIPT": - return "Your FOI Request ["+requestjson["axisRequestId"]+"] - Fee Payment Received" - return None - - def getstage(self, key): - if key == "PAYONLINE" or key == "PAYOUTSTANDING": - return "Fee Estimate" - elif key == "FEE-ESTIMATE-PAYMENT-RECEIPT": - return "Fee Estimate - Payment Receipt" - elif key == "OUTSTANDING-PAYMENT-RECEIPT": - return "Fees - Balance Outstanding - Payment Receipt" - return None - - def getattachmentname(self, key): - if key == "PAYONLINE": - return "Fees - Estimate Sent" - elif key == "PAYONLINE-SEND-FAILURE": - return "Fees - Estimate Correspondence Failed" - if key == "FEE-ESTIMATE-PAYMENT-RECEIPT": - return "Fee Estimate - Payment Receipt Sent" - elif key == "FEE-ESTIMATE-PAYMENT-RECEIPT-FAILURE": - return "Fee Estimate - Payment Receipt Correspondence Failed" - elif key == "PAYOUTSTANDING": - return "Fees - Balance Outstanding Sent" - elif key == "PAYOUTSTANDING-SEND-FAILURE": - return "Fees - Balance Outstanding Correspondence Failed" - if key == "OUTSTANDING-PAYMENT-RECEIPT": - return "Fees - Balance Outstanding - Payment Receipt Sent" - elif key == "OUTSTANDING-PAYMENT-RECEIPT-FAILURE": - return "Fees - Balance Outstanding - Payment Receipt Correspondence Failed" - return None - - def getattachmentcategory(self, key): - if key == "PAYONLINE" or key == "FEEESTIMATENOTIFICATION": - return "FEEASSESSED-ONHOLD" - elif key == "PAYONLINE-SUCCESSFUL" or key == "FEEESTIMATENOTIFICATION-SUCCESSFUL": - return "Fee Estimate - Letter" - elif key == "PAYONLINE-FAILED" or key == "FEEESTIMATENOTIFICATION-FAILED": - return "Fee Estimate - Correspondence Failed" - elif key == "FEE-ESTIMATE-PAYMENT-RECEIPT": - return "Fee Estimate - Payment Receipt" - elif key == "FEE-ESTIMATE-PAYMENT-RECEIPT-SUCCESSFUL": - return "Fee Estimate - Payment Success" - elif key == "FEE-ESTIMATE-PAYMENT-RECEIPT-FAILED": - return "Fee Estimate - Payment Success - Correspondence Failed" - elif key == "PAYOUTSTANDING": - return "RESPONSE-ONHOLD" - elif key == "PAYOUTSTANDING-SUCCESSFUL": - return "Fee Balance Outstanding - Letter" - elif key == "PAYOUTSTANDING-FAILED": - return "Fee Balance Outstanding - Correspondence Failed" - elif key == "OUTSTANDING-PAYMENT-RECEIPT": - return "Fee Balance Outstanding - Payment Receipt" - elif key == "OUTSTANDING-PAYMENT-RECEIPT-SUCCESSFUL": - return "Fee Balance Outstanding - Payment Success" - elif key == "OUTSTANDING-PAYMENT-RECEIPT-FAILED": - return "Fee Balance Outstanding - Payment Success - Correspondence Failed" - return None - - - def isnotreceipt(self, key): - if key == "FEE-ESTIMATE-PAYMENT-RECEIPT" or key == "OUTSTANDING-PAYMENT-RECEIPT": - return False - return True - - - - \ No newline at end of file diff --git a/historical-search-api/request_api/services/email/templates/templatefilters.py b/historical-search-api/request_api/services/email/templates/templatefilters.py deleted file mode 100644 index 1d6f64c37..000000000 --- a/historical-search-api/request_api/services/email/templates/templatefilters.py +++ /dev/null @@ -1,11 +0,0 @@ -import jinja2 -from request_api.utils.commons.datetimehandler import datetimehandler -from datetime import datetime - -def formatdate(value, format): - return datetimehandler().formatdate(value, format) - -def init_filters(): - jinja2.filters.FILTERS['formatdate'] = formatdate - - \ No newline at end of file diff --git a/historical-search-api/request_api/services/email/templates/templateservice.py b/historical-search-api/request_api/services/email/templates/templateservice.py deleted file mode 100644 index ef9d64125..000000000 --- a/historical-search-api/request_api/services/email/templates/templateservice.py +++ /dev/null @@ -1,97 +0,0 @@ -from jinja2 import Template -from request_api.services.external.storageservice import storageservice -from request_api.services.email.templates.templateconfig import templateconfig -from request_api.services.requestservice import requestservice -from request_api.services.applicantcorrespondence.applicantcorrespondencelog import applicantcorrespondenceservice -from request_api.models.ApplicationCorrespondenceTemplates import ApplicationCorrespondenceTemplate -import json -import logging -from flask import current_app -from request_api.services.email.templates.templatefilters import init_filters - -init_filters() - -class templateservice: - """This class is reserved for jinja templating services integration. - """ - - - def generate_by_servicename_and_id(self, servicename, requestid, ministryrequestid): - try: - _request = requestservice().getrequestdetails(requestid,ministryrequestid) - requestjson = json.dumps(_request) - return self.generate_by_servicename_and_schema(servicename, requestjson, ministryrequestid) - except Exception as ex: - logging.exception(ex) - return None - - def generate_by_servicename_and_schema(self, servicename, requestjson, ministryrequestid, applicantcorrespondenceid = None): - try: - _template = self.__gettemplate(servicename) - if _template is None: - _templatename = self.__gettemplatenamewrapper(servicename, requestjson, ministryrequestid) - _template = self.__gettemplate(_templatename) - if (applicantcorrespondenceid and applicantcorrespondenceid != 0 and templateconfig().isnotreceipt(servicename)): - emailtemplatehtml = self.__generatecorrespondencetetemplate(applicantcorrespondenceid) - else: - emailtemplatehtml= storageservice().downloadtemplate(_template.documenturipath) - return self.__generatetemplate(requestjson, emailtemplatehtml, _template.description) - except Exception as ex: - logging.exception(ex) - return None - - def __gettemplatenamewrapper(self, servicename, requestjson, ministryrequestid): - _templatename = templateconfig().gettemplatename(servicename) - if _templatename is None: - _latesttemplatename = self.__getlatesttemplatename(ministryrequestid) - if requestjson is not None and requestjson != {}: - balancedue = float(requestjson['cfrfee']['feedata']["balanceDue"]) - prevstate = self.__getprevstate(requestjson) - if balancedue > 0: - return "HALFPAYMENT" - - elif balancedue == 0: - templatekey = "FULLPAYMENT" - if prevstate.lower() == "response" or (_latesttemplatename and _latesttemplatename == 'PAYOUTSTANDING'): - templatekey = "PAYOUTSTANDINGFULLPAYMENT" - return templatekey - - return _templatename - - def __getlatesttemplatename(self, ministryrequestid): - latestcorrespondence = applicantcorrespondenceservice().getlatestapplicantcorrespondence(ministryrequestid) - _latesttemplateid = latestcorrespondence['templateid'] if 'templateid' in latestcorrespondence else None - if _latesttemplateid: - return applicantcorrespondenceservice().gettemplatebyid(_latesttemplateid).name - return None - - def __getprevstate(self, requestjson): - return requestjson["stateTransition"][2]["status"] if "stateTransition" in requestjson and len(requestjson["stateTransition"]) > 3 else None - - def __gettemplate(self, templatename): - return ApplicationCorrespondenceTemplate.get_template_by_name(templatename) - - def __generatetemplate(self, dynamictemplatevalues, emailtemplatehtml, title): - dynamictemplatevalues["ffaurl"] = current_app.config['FOI_FFA_URL'] - headerfooterhtml = storageservice().downloadtemplate('/TEMPLATES/EMAILS/header_footer_template.html') - if(emailtemplatehtml is None): - raise ValueError('No template found') - - if(dynamictemplatevalues is None): - raise ValueError('Values not found') - - if dynamictemplatevalues["assignedTo"] == None: - dynamictemplatevalues["assignedToFirstName"] = "" - dynamictemplatevalues["assignedToLastName"] = "" - dynamictemplatevalues["assignedGroupEmail"] = "" - - contenttemplate = Template(emailtemplatehtml) - content = contenttemplate.render(dynamictemplatevalues) - dynamictemplatevalues["content"] = content - dynamictemplatevalues['title'] = title - finaltemplate = Template(headerfooterhtml) - finaltemplatedhtml = finaltemplate.render(dynamictemplatevalues) - return finaltemplatedhtml, content - - def __generatecorrespondencetetemplate(self, applicantcorrespondenceid): - return applicantcorrespondenceservice().getapplicantcorrespondencelogbyid(applicantcorrespondenceid) diff --git a/historical-search-api/request_api/services/email/templatevaluebuilderservice.py b/historical-search-api/request_api/services/email/templatevaluebuilderservice.py deleted file mode 100644 index 35d50fcbf..000000000 --- a/historical-search-api/request_api/services/email/templatevaluebuilderservice.py +++ /dev/null @@ -1,33 +0,0 @@ -from jinja2 import Template -from request_api.services.external.storageservice import storageservice -import json - -class templatevaluebuilderservice: - """This class is reserved for value builder service - """ - - - def preparemetadata(self, data): - return { - "to": data[""], - "cc": data[""], - "bcc": data[""], - "subject": data[""], - "body": data[""] - } - - def preparevalue(self, emailtemplatename, dynamictemplatevalues): - - emailtemplatehtml= storageservice().download(emailtemplatename) - if(emailtemplatehtml is None): - raise ValueError('No template found') - - if(dynamictemplatevalues is None): - raise ValueError('Values not found') - - template = Template(emailtemplatehtml) - dynamicvalues = dict(dynamictemplatevalues) - templatedhtml = template.render(dynamicvalues) - return templatedhtml - - diff --git a/historical-search-api/request_api/services/emailservice.py b/historical-search-api/request_api/services/emailservice.py deleted file mode 100644 index 6df55855c..000000000 --- a/historical-search-api/request_api/services/emailservice.py +++ /dev/null @@ -1,102 +0,0 @@ - -import os -from re import VERBOSE -import json -import email -import smtplib -import imaplib -from imap_tools import MailBox, AND -import logging -from request_api.services.documentservice import documentservice -from request_api.services.external.storageservice import storageservice -from request_api.services.email.templates.templateservice import templateservice -from request_api.services.email.templates.templateconfig import templateconfig -from request_api.services.email.senderservice import senderservice -from request_api.services.email.inboxservice import inboxservice -from request_api.services.eventservice import eventservice -from request_api.services.requestservice import requestservice -from request_api.services.applicantcorrespondence.applicantcorrespondencelog import applicantcorrespondenceservice -from request_api.utils.enums import ServiceName -import logging - -class emailservice: - """ FOI Email Service - """ - - def send(self, servicename, requestid, ministryrequestid, emailschema): - try: - requestjson = requestservice().getrequestdetails(requestid,ministryrequestid) - _templatename = self.__getvaluefromschema(emailschema, "templatename") - servicename = _templatename if servicename == ServiceName.correspondence.value.upper() else servicename - _applicantcorrespondenceid = self.__getvaluefromschema(emailschema, "applicantcorrespondenceid") - _messagepart, content = templateservice().generate_by_servicename_and_schema(servicename, requestjson, ministryrequestid, _applicantcorrespondenceid) - if (_applicantcorrespondenceid and templateconfig().isnotreceipt(servicename)): - servicename = _templatename.upper() if _templatename else "" - _messageattachmentlist = self.__get_attachments(ministryrequestid, emailschema, servicename) - self.__pre_send_correspondence_audit(requestid, ministryrequestid,emailschema, content, templateconfig().isnotreceipt(servicename), _messageattachmentlist) - subject = templateconfig().getsubject(servicename, requestjson) - return senderservice().send(subject, _messagepart, _messageattachmentlist, requestjson) - except Exception as ex: - logging.exception(ex) - - def acknowledge(self, servicename, requestid, ministryrequestid): - try: - requestjson = requestservice().getrequestdetails(requestid,ministryrequestid) - self.__upload_sent_email(servicename, ministryrequestid, requestjson) - ackresponse = inboxservice().get_failure_deliverystatus_as_eml(templateconfig().getsubject(servicename, requestjson), requestjson["email"]) - if ackresponse["success"] == False: - self.__upload(templateconfig().getattachmentname(servicename+"-SEND-FAILURE")+".eml", ackresponse["content"], ministryrequestid, requestjson, templateconfig().getattachmentcategory(servicename+"-FAILED")) - eventservice().posteventforemailfailure(ministryrequestid, "ministryrequest", templateconfig().getstage(servicename), ackresponse["reason"], requestjson["assignedTo"]) - - return {"success" : True, "message": "Acknowledgement successful"} - except Exception as ex: - logging.exception(ex) - return {"success" : False, "message": "Acknowledgement successful"} - - def __get_attachments(self, ministryrequestid, emailschema, servicename): - _messageattachmentlist = [] - _applicantcorrespondenceid = self.__getvaluefromschema(emailschema, "applicantcorrespondenceid") - if (_applicantcorrespondenceid and templateconfig().isnotreceipt(servicename)): - _messageattachmentlist = documentservice().getapplicantcorrespondenceattachmentsbyapplicantcorrespondenceid(_applicantcorrespondenceid) - elif templateconfig().isnotreceipt(servicename) is not True: - _messageattachmentlist = documentservice().getreceiptattachments(ministryrequestid, templateconfig().getattachmentcategory(servicename).lower()) - else: - _messageattachmentlist = documentservice().getattachments(ministryrequestid, 'ministryrequest', templateconfig().getattachmentcategory(servicename).lower()) - return _messageattachmentlist - - - def __pre_send_correspondence_audit(self, requestid, ministryrequestid, emailschema, content, isnotreceipt, attachmentlist=None): - _applicantcorrespondenceid = self.__getvaluefromschema(emailschema, "applicantcorrespondenceid") - if _applicantcorrespondenceid and isnotreceipt: - return applicantcorrespondenceservice().updateapplicantcorrespondencelog(_applicantcorrespondenceid, {"message": content}) - else: - data = { - "templateid": None, - "correspondencemessagejson": {"message": content}, - "attachments": attachmentlist - } - return applicantcorrespondenceservice().saveapplicantcorrespondencelog(requestid, ministryrequestid, data, 'system') - - - def __upload_sent_email(self, servicekey, ministryrequestid, requestjson): - try: - _originalmsg = senderservice().read_outbox_as_bytes(servicekey, requestjson) - if _originalmsg is not None: - return self.__upload(templateconfig().getattachmentname(servicekey)+".eml",_originalmsg, ministryrequestid, requestjson, templateconfig().getattachmentcategory(servicekey+"-SUCCESSFUL")) - except Exception as ex: - logging.exception(ex) - - def __upload(self, filename, filebytes, ministryrequestid, requestjson, category): - try: - logging.info("Upload file for payload"+ json.dumps(requestjson)) - _response = storageservice().uploadbytes(filename, filebytes, requestjson["bcgovcode"], requestjson["idNumber"]) - logging.info("Upload status for payload"+ json.dumps(_response)) - if _response["success"] == True: - _documentschema = {"documents": [{"filename": _response["filename"], "documentpath": _response["documentpath"], "category": category}]} - documentservice().createrequestdocument(ministryrequestid, _documentschema, "SYSTEM", "ministryrequest") - return _response - except Exception as ex: - logging.exception(ex) - - def __getvaluefromschema(self, emailschema, property): - return emailschema.get(property) if property in emailschema else None diff --git a/historical-search-api/request_api/services/events/__init__.py b/historical-search-api/request_api/services/events/__init__.py deleted file mode 100644 index 00409e2cb..000000000 --- a/historical-search-api/request_api/services/events/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright © 2021 Province of British Columbia -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Exposes all of the Services used in the API.""" - diff --git a/historical-search-api/request_api/services/events/assignment.py b/historical-search-api/request_api/services/events/assignment.py deleted file mode 100644 index 538317aba..000000000 --- a/historical-search-api/request_api/services/events/assignment.py +++ /dev/null @@ -1,98 +0,0 @@ - -from cmath import asin -from os import stat -from re import VERBOSE -from request_api.services.commentservice import commentservice -from request_api.services.notificationservice import notificationservice -from request_api.models.FOIRawRequests import FOIRawRequest -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -from request_api.models.FOIRequestStatus import FOIRequestStatus -from request_api.models.NotificationTypes import NotificationType -import json -from request_api.models.default_method_result import DefaultMethodResult -from request_api.utils.enums import CommentType - -class assignmentevent: - """ FOI Event management service - - """ - def createassignmentevent(self, requestid, requesttype, userid, isministryuser,assigneename,username): - changeresp = self.__haschanged(requestid, requesttype) - ischanged = changeresp['ischanged'] - previousassignee = changeresp['previous'] - currentassignee = changeresp['current'] - commentrespose = self.__createcomment(assigneename,username,requestid,userid,requesttype) - if ischanged == True: - notificationservice().dismissnotifications_by_requestid_type_userid(requestid, requesttype, 'User Assignment Removal', currentassignee) - notificationresponse = self.__createnotification(requestid, requesttype, userid, isministryuser) - #Previous Assignee Notification - previousassigneenotification = None - if self.__isnoneorblank(previousassignee) == False and previousassignee != userid: - previousassigneenotification = self.__createnotificationforremoval(requestid, requesttype, userid, username, previousassignee) - if notificationresponse.success == True and commentrespose.success == True and \ - (previousassigneenotification is None or previousassigneenotification.success == True): - return DefaultMethodResult(True,'Assignment Notification, Comment has been created',requestid) - else: - return DefaultMethodResult(False,'unable to create notification and/or comment for assignment',requestid) - - return DefaultMethodResult(True,'No change',requestid) - - def __createnotification(self, requestid, requesttype, userid, isministryuser): - notification = self.__preparenotification() - notificationtype = NotificationType().getnotificationtypeid(self.__assignmenttype(isministryuser)) - return notificationservice().createnotification({"message" : notification}, requestid, requesttype, notificationtype, userid) - - def __createnotificationforremoval(self, requestid, requesttype, userid, username, previousassignee): - notification = self.__preparenotification(username, True) - return notificationservice().createusernotification({"message" : notification}, requestid, requesttype, 'User Assignment Removal', previousassignee, userid) - - def __preparenotification(self, username=None, isremoved=False): - return self.__notificationmessage(username,isremoved) - - def __haschanged(self, requestid, requesttype): - assignments = self.__getassignments(requestid, requesttype) - previousassignee = "" - currentassignee = "" - if len(assignments) == 1 and self.__isnoneorblank(assignments[0]) == False: - return {"ischanged": True, "previous": previousassignee, "current": currentassignee } - if len(assignments) == 2 and \ - ((assignments[0]['assignedto'] != assignments[1]['assignedto'] and self.__isnoneorblank(assignments[0]['assignedto']) == False) \ - or (requesttype == "ministryrequest" and \ - assignments[0]['assignedministryperson'] != assignments[1]['assignedministryperson'] \ - and self.__isnoneorblank(assignments[0]['assignedministryperson']) == False)): - previousassignee= assignments[1]['assignedto'] if assignments[0]['assignedto'] != assignments[1]['assignedto'] else assignments[1]['assignedministryperson'] - currentassignee= assignments[0]['assignedto'] if assignments[0]['assignedto'] != assignments[1]['assignedto'] else assignments[0]['assignedministryperson'] - return {"ischanged": True, "previous": previousassignee, "current": currentassignee } - return {"ischanged": False, "previous": previousassignee, "current": currentassignee } - - def __isnoneorblank(self, value): - if value is not None and value != '': - return False - return True - def __getassignments(self, requestid, requesttype): - if requesttype == "ministryrequest": - return FOIMinistryRequest.getassignmenttransition(requestid) - else: - return FOIRawRequest.getassignmenttransition(requestid) - - def __notificationmessage(self, username=None, isremoved=False): - if isremoved == True: - return username+' has removed your assignment to this request' - else: - return 'New Request Assigned to You.' - - def __assignmenttype(self, isministryuser): - return 'Ministry Assignment' if isministryuser == True else 'IAO Assignment' - - def __createcomment(self, assigneename, username,requestid,userid,requesttype): - if(assigneename !=''): - _commentmessage ='{0} assigned this request to {1}'.format(username,assigneename) - if requesttype == "ministryrequest": - comment = {"ministryrequestid": requestid, "comment": _commentmessage} - return commentservice().createministryrequestcomment(comment, userid, CommentType.SystemGenerated.value) - else: - comment = {"requestid": requestid, "comment": _commentmessage} - return commentservice().createrawrequestcomment(comment, userid, CommentType.SystemGenerated.value) - else: - return DefaultMethodResult(True,'No Assignee',requestid) - \ No newline at end of file diff --git a/historical-search-api/request_api/services/events/cfrdate.py b/historical-search-api/request_api/services/events/cfrdate.py deleted file mode 100644 index 6d742234a..000000000 --- a/historical-search-api/request_api/services/events/cfrdate.py +++ /dev/null @@ -1,76 +0,0 @@ - -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.FOIMinistryRequests import FOIMinistryRequest -from request_api.models.FOIRequestComments import FOIRequestComment -from request_api.models.NotificationTypes import NotificationType -import json -from request_api.models.default_method_result import DefaultMethodResult -from enum import Enum -from request_api.exceptions import BusinessException -from datetime import datetime -import holidays -import maya -import os -from flask import current_app -from dateutil.parser import parse -import time as t - -class cfrdateevent(duecalculator): - """ FOI Event management service - - """ - def createdueevent(self): - try: - _today = self.gettoday() - notificationservice().dismissremindernotification("ministryrequest", self.__notificationtype()) - - ca_holidays = self.getholidays() - _upcomingdues = FOIMinistryRequest.getupcomingcfrduerecords() - notificationtype = NotificationType().getnotificationtypeid(self.__notificationtype()) - for entry in _upcomingdues: - _duedate = self.formatduedate(entry['cfrduedate']) - message = None - if _duedate == _today: - message = self.__todayduemessage() - elif self.getpreviousbusinessday(entry['cfrduedate'],ca_holidays) == _today: - message = self.__upcomingduemessage(_duedate) - self.__createnotification(message,entry['foiministryrequestid'], notificationtype) - self.__createcomment(entry, message) - return DefaultMethodResult(True,'CFR reminder notifications created',_today) - except BusinessException as exception: - current_app.logger.error("%s,%s" % ('CFR reminder Notification Error', exception.message)) - return DefaultMethodResult(False,'CFR reminder notifications failed',_today) - - def __createnotification(self, message, requestid, notificationtype): - if message is not None: - return notificationservice().createremindernotification({"message" : message}, requestid, "ministryrequest", notificationtype, self.__defaultuserid()) - - def __createcomment(self, entry, message): - if message is not None: - _comment = self.__preparecomment(entry, message) - return commentservice().createcomments(_comment, self.__defaultuserid(), 2) - - def __preparecomment(self, foirequest, message): - _comment = dict() - _comment['comment'] = message - _comment['ministryrequestid'] = foirequest["foiministryrequestid"] - _comment['version'] = foirequest["version"] - _comment['taggedusers'] = None - _comment['parentcommentid'] = None - return _comment - - def __upcomingduemessage(self, duedate): - return 'Call for Records due on ' + parse(str(duedate)).strftime("%Y %b %d").upper() - - def __todayduemessage(self): - return 'Call for Records due Today' - - def __notificationtype(self): - return "CFR Due Reminder" - - def __defaultuserid(self): - return "System" \ No newline at end of file diff --git a/historical-search-api/request_api/services/events/cfrfeeform.py b/historical-search-api/request_api/services/events/cfrfeeform.py deleted file mode 100644 index e98711244..000000000 --- a/historical-search-api/request_api/services/events/cfrfeeform.py +++ /dev/null @@ -1,100 +0,0 @@ - -from os import stat -from re import VERBOSE -from request_api.models.FOIRequestCFRFees import FOIRequestCFRFee -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -from request_api.services.commentservice import commentservice -from request_api.services.cfrfeeservice import cfrfeeservice -from request_api.services.notificationservice import notificationservice -from request_api.models.NotificationTypes import NotificationType -import json -from request_api.models.default_method_result import DefaultMethodResult -from enum import Enum -from request_api.exceptions import BusinessException -from datetime import datetime -import holidays -import maya -import os -from flask import current_app -from dateutil.parser import parse - -class cfrfeeformevent: - """ CFR Form Event management service - - """ - def createstatetransitionevent(self, requestid, userid, username): - cfrfee = cfrfeeservice().getcfrfee(requestid) - state = self.__haschanged(requestid, cfrfee['cfrfeeid']) - if state is not None: - _commentresponse = self.__createcomment(requestid, state, userid, username) - _notificationresponse = self.__createnotification(requestid, state, userid) - if _commentresponse.success == True and _notificationresponse.success == True : - return DefaultMethodResult(True,'Comment posted',requestid) - else: - return DefaultMethodResult(False,'unable to post comment',requestid) - return DefaultMethodResult(True,'No change',requestid) - - - def createeventforupdatedamounts(self, requestid, userid, username): - feedata = FOIRequestCFRFee.getfeedataforamountcomparison(requestid) - updatedamounts={} - _feewaivercommentresponse=DefaultMethodResult(True,'No change',requestid) - _refundcommentresponse=DefaultMethodResult(True,'No change',requestid) - if len(feedata) == 2: - newfeedata = feedata[0] - oldfeedata = feedata[1] - newfeewaiveramount = self.__getvaluefromschema(newfeedata, "feewaiveramount") - oldfeewaiveramount = self.__getvaluefromschema(oldfeedata, "feewaiveramount") - if newfeewaiveramount != oldfeewaiveramount: - updatedamounts = {'feewaiveramountchanged': newfeewaiveramount} - _feewaivercommentresponse = self.__createcomment(requestid, None, userid, username, updatedamounts) - newrefundamount = self.__getvaluefromschema(newfeedata, "refundamount") - oldrefundamount = self.__getvaluefromschema(oldfeedata, "refundamount") - if newrefundamount != oldrefundamount: - updatedamounts = {'refundamountchanged': newrefundamount} - _refundcommentresponse = self.__createcomment(requestid, None, userid, username, updatedamounts) - return _feewaivercommentresponse, _refundcommentresponse - - def __createcomment(self, requestid, state, userid, username, updatedamounts=None): - comment = self.__preparecomment(requestid, state, username, updatedamounts) - return commentservice().createministryrequestcomment(comment, userid, 2) - - def __createnotification(self, requestid, state, userid): - notification = self.__preparenotification(state) - notificationtype = NotificationType().getnotificationtypeid("CFR Fee Form") - return notificationservice().createnotification({"message" : notification}, requestid, "ministryrequest", notificationtype, userid) - - def __preparecomment(self, requestid, state, username, updatedamounts): - comment = {"comment": self.__commentmessage(state, username, updatedamounts)} - comment['ministryrequestid']= requestid - return comment - - def __preparenotification(self, state): - return self.__notificationmessage(state) - - def __haschanged(self, requestid, cfrfeeid): - states = FOIRequestCFRFee.getstatenavigation(requestid, cfrfeeid) - if len(states) == 2: - newstate = states[0] - oldstate = states[1] - if newstate != oldstate: - return newstate - elif len(states) == 1: - return states[0] - return None - - def __commentmessage(self, state, username, updatedamounts): - if state is not None: - return username+' updated Fee Estimate status to '+state - if updatedamounts is not None: - if 'feewaiveramountchanged' in updatedamounts and updatedamounts['feewaiveramountchanged'] is not None: - return username+ ' entered a fee waiver for the amount of $'+"{:.2f}".format(updatedamounts['feewaiveramountchanged']) - if 'refundamountchanged' in updatedamounts and updatedamounts['refundamountchanged'] is not None: - return username+ ' entered a refund for the amount of $'+"{:.2f}".format(updatedamounts['refundamountchanged']) - - - def __notificationmessage(self, state): - return 'Updated Fee Estimate Status to '+state - - def __getvaluefromschema(self,requestsschema, property): - return requestsschema.get(property) if property in requestsschema else None diff --git a/historical-search-api/request_api/services/events/comment.py b/historical-search-api/request_api/services/events/comment.py deleted file mode 100644 index 3e382d6ae..000000000 --- a/historical-search-api/request_api/services/events/comment.py +++ /dev/null @@ -1,105 +0,0 @@ - -from os import stat -from re import VERBOSE -from request_api.services.notificationservice import notificationservice -from request_api.models.FOIRawRequestComments import FOIRawRequestComment -from request_api.models.FOIRequestComments import FOIRequestComment -import json -from request_api.models.default_method_result import DefaultMethodResult -from enum import Enum -from request_api.exceptions import BusinessException -from datetime import datetime -import holidays -import maya -import os -from flask import current_app -from dateutil.parser import parse -from request_api import socketio -import asyncio -from request_api.utils.redispublisher import RedisPublisherService -class commentevent: - """ FOI Event management service - """ - def createcommentevent(self, commentid, requesttype, userid, isdelete=False, existingtaggedusers=False): - try: - if isdelete == True: - return self.__deletecommentnotification(commentid, userid) - elif existingtaggedusers: - return self.__editcommentnotification(commentid, requesttype, existingtaggedusers, userid) - else: - return self.__createcommentnotification(commentid, requesttype, userid) - except BusinessException as exception: - current_app.logger.error("%s,%s" % ('Comment Notification Error', exception.message)) - return DefaultMethodResult(False,'Comment notifications failed',commentid) - - def __createcommentnotification(self, commentid, requesttype, userid): - _comment = self.__getcomment(commentid,requesttype) - notificationservice().createcommentnotification(self.getcommentmessage(commentid, _comment), _comment, self.__getcommenttype(_comment), requesttype, userid) - if _comment["taggedusers"] != '[]': - notificationservice().createcommentnotification(self.getcommentmessage(commentid, _comment), _comment, "Tagged User Comments", requesttype, userid) - self.__pushcommentnotification(commentid) - return DefaultMethodResult(True,'Comment notifications created',commentid) - - def __deletecommentnotification(self, commentid, userid): - _pushnotifications = notificationservice().getcommentnotifications(commentid) - for _pushnotification in _pushnotifications: - notificationservice().dismissnotification(userid, None, _pushnotification["idnumber"], _pushnotification["notificationuserid"]) - _pushnotification["action"] = "delete" - RedisPublisherService().publishcommment(json.dumps(_pushnotification)) - return DefaultMethodResult(True,'Comment notifications deleted',commentid) - - def __editcommentnotification(self, commentid, requesttype, existingtaggedusers, userid): - _comment = self.__getcomment(commentid,requesttype) - notificationservice().editcommentnotification(self.getcommentmessage(commentid, _comment), _comment, userid) - newtaggedusers = [user for user in json.loads(_comment["taggedusers"]) if user not in json.loads(existingtaggedusers)] - if newtaggedusers: - _comment["taggedusers"] = json.dumps(newtaggedusers) - notificationservice().createcommentnotification(self.getcommentmessage(commentid, _comment), _comment, "Tagged User Comments", requesttype, userid) - self.__pushcommentnotification(commentid) - return DefaultMethodResult(True,'Comment notifications created',commentid) - - def __pushcommentnotification(self,commentid, _pushnotifications): - try: - if os.getenv("SOCKETIO_MESSAGE_QTYPE") != "NONE": - for _pushnotification in _pushnotifications: - if os.getenv("SOCKETIO_MESSAGE_QTYPE") == "REDIS": - RedisPublisherService().publishcommment(json.dumps(_pushnotification)) - if os.getenv("SOCKETIO_MESSAGE_QTYPE") == "IN-MEMORY": - socketio.emit(_pushnotification["userid"], _pushnotification) - except BusinessException as exception: - current_app.logger.error("%s,%s" % ('Comment Notification publish Error', exception.message)) - return DefaultMethodResult(False,'Comment notifications publish failed',commentid) - - def __pushcommentnotification(self, commentid): - _pushnotifications = notificationservice().getcommentnotifications(commentid) - for _pushnotification in _pushnotifications: - RedisPublisherService().publishcommment(json.dumps(_pushnotification)) - - def getcommentmessage(self, commentid, comment): - return {"commentid":commentid, "message" :self.__formatmessage(comment)} - - def __formatmessage(self, comment): - _comment = json.loads(comment["comment"]) - msg = _comment["blocks"][0]["text"] - if comment["taggedusers"] != '[]': - msg = self.__formattaggedmessage(msg, json.loads(comment["taggedusers"])) - msg = msg.strip() - return msg - - def __formattaggedmessage(self, message, taggedusers): - for _taguser in taggedusers: - message = message.replace(_taguser["name"], "") - return message - - def __getcomment(self, commentid, requesttype): - if requesttype == "ministryrequest": - return FOIRequestComment.getcommentbyid(commentid) - else: - return FOIRawRequestComment.getcommentbyid(commentid) - - def __getcommenttype(self, comment): - if not comment["parentcommentid"]: - return "New User Comments" - else: - return "Reply User Comments" - \ No newline at end of file diff --git a/historical-search-api/request_api/services/events/division.py b/historical-search-api/request_api/services/events/division.py deleted file mode 100644 index 7c17eb7f4..000000000 --- a/historical-search-api/request_api/services/events/division.py +++ /dev/null @@ -1,136 +0,0 @@ - -from os import stat -from re import VERBOSE -from request_api.models.FOIMinistryRequestDivisions import FOIMinistryRequestDivision -from request_api.services.commentservice import commentservice -from request_api.models.FOIRawRequests import FOIRawRequest -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -from request_api.models.FOIRequestStatus import FOIRequestStatus -import json -from request_api.models.default_method_result import DefaultMethodResult -from enum import Enum -from request_api.exceptions import BusinessException -from dateutil.parser import parse -from request_api.utils.enums import CommentType - -class divisionevent: - """ FOI Event management service - - """ - def createdivisionevent(self, requestid, requesttype, userid): - if requesttype != "ministryrequest": - return DefaultMethodResult(True,'No division required',requestid) - version = FOIMinistryRequest.getversionforrequest(requestid) - curdivisions = FOIMinistryRequestDivision.getdivisions(requestid, version) - prevdivisions = FOIMinistryRequestDivision.getdivisions(requestid, version[0]-1) - divisionsummary = self.__maintained(curdivisions, prevdivisions) + self.__deleted(curdivisions, prevdivisions) - if divisionsummary is None or (divisionsummary and len(divisionsummary) <1): - return DefaultMethodResult(True,'No change',requestid) - try: - for division in divisionsummary: - self.createcomment(requestid, division, userid) - return DefaultMethodResult(True,'Comment posted',requestid) - except BusinessException as exception: - return DefaultMethodResult(False,'unable to post comment - '+exception.message,requestid) - - - def createcomment(self, requestid, division, userid): - comment = {"ministryrequestid": requestid, "comment": self.__preparemessage(division)} - commentservice().createministryrequestcomment(comment, userid, CommentType.DivisionStages.value) - - - def __maintained(self,cdivisions, pdivisions): - divisions = [] - for cdivision in cdivisions: - if self.__isdivisionpresent(self.__getdivisioname(cdivision), pdivisions) == False: - divisions.append(self.__createdivisionsummary(cdivision, EventType.add.value)) - else: - if self.__isstagechanged(self.__getdivisioname(cdivision), self.__getstagename(cdivision), pdivisions) == True or self.__isdivisionduedatechanged(self.__getdivisioname(cdivision), self.__getdivisionduedate(cdivision), pdivisions) or self.__iseapprovalchanged(self.__getdivisioname(cdivision), self.__geteaproval(cdivision), pdivisions) or self.__isdivisionreceiveddatechanged(self.__getdivisioname(cdivision), self.__getdivisionreceiveddate(cdivision), pdivisions) : - divisions.append(self.__createdivisionsummary(cdivision, EventType.modify.value)) - return divisions - - def __deleted(self,cdivisions, pdivisions): - divisions = [] - for pdivision in pdivisions: - if self.__isdivisionpresent(self.__getdivisioname(pdivision), cdivisions) == False: - divisions.append(self.__createdivisionsummary(pdivision, EventType.delete.value)) - return divisions - - def __isdivisionpresent(self, divisionid, divisionlist): - for division in divisionlist: - if self.__getdivisioname(division) == divisionid: - return True - return False - - def __isstagechanged(self, divisionid, stageid, divisionlist): - for division in divisionlist: - if self.__getdivisioname(division) == divisionid and self.__getstagename(division) != stageid: - return True - return False - - def __isdivisionduedatechanged(self, divisionid, cdivisionduedate, divisionlist): - for division in divisionlist: - if self.__getdivisioname(division) == divisionid and self.__getdivisionduedate(division) != cdivisionduedate: - return True - return False - - def __iseapprovalchanged(self, divisionid, ceapproval, divisionlist): - for division in divisionlist: - if self.__getdivisioname(division) == divisionid and self.__geteaproval(division) != ceapproval: - return True - return False - - def __isdivisionreceiveddatechanged(self, divisionid, cdivisionreceiveddate, divisionlist): - for division in divisionlist: - if self.__getdivisioname(division) == divisionid and self.__getdivisionreceiveddate(division) != cdivisionreceiveddate: - return True - return False - - def __createdivisionsummary(self, division, event): - return {'division': self.__getdivisioname(division), - 'stage': self.__getstagename(division), - 'divisionduedate':self.__getdivisionduedate(division), - 'eapproval': self.__geteaproval(division), - 'divisionreceiveddate': self.__getdivisionreceiveddate(division), - 'event': event} - - def __getdivisioname(self, dataschema): - return dataschema['division.name'] - - def __getstagename(self, dataschema): - return dataschema['stage.name'] - - def __getdivisionduedate(self, dataschema): - return parse(dataschema['divisionduedate']).strftime(self.__genericdateformat()) if dataschema['divisionduedate'] is not None else None - - def __geteaproval(self, dataschema): - return dataschema['eapproval'] - - def __getdivisionreceiveddate(self, dataschema): - return parse(dataschema['divisionreceiveddate']).strftime(self.__genericdateformat()) if dataschema['divisionreceiveddate'] is not None else None - - def __preparemessage(self, division): - message = "" - if division['eapproval'] is not None: - message = ' E-App/Other reference Number: ' + division['eapproval'] - if division['divisionduedate'] is not None: - message += ' Due on ' + division['divisionduedate'] - if division['divisionreceiveddate'] is not None: - message += ' Received on ' + division['divisionreceiveddate'] - - if division['event'] == EventType.modify.value: - return self.__formatmessage(division['division'])+' division has been updated to stage '+ self.__formatmessage(division['stage']) + message - elif division['event'] == EventType.add.value: - return self.__formatmessage(division['division'])+' division has been added with stage '+ self.__formatmessage(division['stage']) + message - else: - return self.__formatmessage(division['division'])+' division with stage '+ self.__formatmessage(division['stage']) + message + ' has been removed' - - - def __formatmessage(self, data): - return ''+data+'' - def __genericdateformat(self): - return '%Y-%m-%d' -class EventType(Enum): - add = "add" - delete = "delete" - modify = "modify" diff --git a/historical-search-api/request_api/services/events/divisiondate.py b/historical-search-api/request_api/services/events/divisiondate.py deleted file mode 100644 index c208581dd..000000000 --- a/historical-search-api/request_api/services/events/divisiondate.py +++ /dev/null @@ -1,84 +0,0 @@ -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.FOIMinistryRequests import FOIMinistryRequest -from request_api.models.FOIRequestComments import FOIRequestComment -from request_api.models.NotificationTypes import NotificationType -import json -from request_api.models.default_method_result import DefaultMethodResult -from enum import Enum -from request_api.exceptions import BusinessException -from datetime import datetime -import holidays -import maya -import os -from flask import current_app -from dateutil.parser import parse - -class divisiondateevent(duecalculator): - """ FOI Event management service - """ - - def createdueevent(self): - try: - _today = self.gettoday() - notificationservice().dismissremindernotification("ministryrequest", self.__notificationtype()) - ca_holidays = self.getholidays() - _upcomingdues = FOIMinistryRequest.getupcomingdivisionduerecords() - notificationtype = NotificationType().getnotificationtypeid(self.__notificationtype()) - for entry in _upcomingdues: - _duedate = self.formatduedate(entry['duedate']) - message = None - if _duedate == _today: - message = self.__todayduemessage(entry) - elif self.getpreviousbusinessday(entry['duedate'],ca_holidays) == _today: - message = self.__upcomingduemessage(entry) - self.__createnotification(message,entry['foiministryrequestid'], notificationtype) - self.__createcomment(entry, message) - return DefaultMethodResult(True,'Division reminder notifications created',_today) - except BusinessException as exception: - current_app.logger.error("%s,%s" % ('Legislative reminder Notification Error', exception.message)) - return DefaultMethodResult(False,'Division reminder notifications failed',_today) - - def __createnotification(self, message, requestid, notificationtype): - if message is not None: - return notificationservice().createnotification({"message" : message}, requestid, "ministryrequest", notificationtype, self.__defaultuserid(), False) - - def __createcomment(self, entry, message): - if message is not None: - _comment = self.__preparecomment(entry, message) - return commentservice().createcomments(_comment, self.__defaultuserid(), 2) - - - def __preparecomment(self, foirequest, message): - _comment = dict() - _comment['comment'] = message - _comment['ministryrequestid'] = foirequest["foiministryrequestid"] - _comment['version'] = foirequest["version"] - _comment['taggedusers'] = None - _comment['parentcommentid'] = None - return _comment - - def __upcomingduemessage(self, data): - return self.__getformattedprefix(data)+ ' due on ' + parse(str(data['duedate'])).strftime("%Y %b %d").upper() - - def __todayduemessage(self, data): - return self.__getformattedprefix(data)+ ' due Today' - - def __getformattedprefix(self,data): - _message = data['divisionname'] - if data['stageid'] == 5: - _message += " records are" - elif data['stageid'] == 7: - _message += " harms are" - elif data['stageid'] == 9: - _message += " sign off is" - return _message - - def __notificationtype(self): - return "Division Due Reminder" - - def __defaultuserid(self): - return "System" \ No newline at end of file diff --git a/historical-search-api/request_api/services/events/email.py b/historical-search-api/request_api/services/events/email.py deleted file mode 100644 index 8011ad324..000000000 --- a/historical-search-api/request_api/services/events/email.py +++ /dev/null @@ -1,60 +0,0 @@ - -from os import stat -from re import VERBOSE -from request_api.services.commentservice import commentservice -from request_api.services.notificationservice import notificationservice -from request_api.exceptions import BusinessException -from request_api.models.FOIRawRequests import FOIRawRequest -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -from request_api.models.FOIRequestStatus import FOIRequestStatus -from request_api.models.NotificationTypes import NotificationType -import json -from request_api.models.default_method_result import DefaultMethodResult -import logging - -class emailevent: - """ FOI Event management service - """ - def createemailevent(self, requestid, requesttype, stage, reason, userid): - try: - _commentresponse = self.__createcomment(requestid, requesttype, stage, reason, userid) - _notificationresponse = self.__createnotification(requestid, requesttype, stage) - if _commentresponse.success == True and _notificationresponse.success == True and _notificationresponse.success == True: - return DefaultMethodResult(True,'Email Failure Notification posted',requestid) - else: - return DefaultMethodResult(False,'Email Failure Notification - failed',requestid) - except BusinessException as ex: - logging.exception(ex) - return DefaultMethodResult(False,'Email Failure Notification - failed',requestid) - - def __createcomment(self, requestid, requesttype, stage, reason, userid): - comment = self.__preparecomment(requestid, requesttype, stage, reason) - if requesttype == "ministryrequest": - return commentservice().createministryrequestcomment(comment, userid, 2) - else: - logging.info("Unsupported requesttype") - - def __createnotification(self, requestid, requesttype, stage): - notification = self.__preparenotification(stage) - notificationtype = NotificationType().getnotificationtypeid("Email Failure") - return notificationservice().createnotification({"message" : notification}, requestid, requesttype, notificationtype, self.__defaultuserid()) - - def __preparenotification(self, stage): - return self.__notificationmessage(stage) - - def __preparecomment(self, requestid, requesttype, stage, reason): - comment = {"comment": self.__commentmessage(stage, reason)} - if requesttype == "ministryrequest": - comment['ministryrequestid']= requestid - else: - comment['requestid']=requestid - return comment - - def __commentmessage(self, stage, reason): - return stage+' correspondence failed to send to applicant due to reason: "'+reason+'". See attachment log for details.' - - def __notificationmessage(self, stage): - return stage+' correspondence failed to send to applicant. - see attachment log for details.' - - def __defaultuserid(self): - return "System" \ No newline at end of file diff --git a/historical-search-api/request_api/services/events/extension.py b/historical-search-api/request_api/services/events/extension.py deleted file mode 100644 index dec4a8447..000000000 --- a/historical-search-api/request_api/services/events/extension.py +++ /dev/null @@ -1,275 +0,0 @@ - -from os import stat -from re import VERBOSE -from request_api.models.FOIRequestExtensions import FOIRequestExtension -from request_api.services.commentservice import commentservice -from request_api.services.extensionreasonservice import extensionreasonservice -from request_api.services.notificationservice import notificationservice -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -from request_api.models.FOIRequestComments import FOIRequestComment -from request_api.models.FOIRequestNotifications import FOIRequestNotification -from request_api.models.NotificationTypes import NotificationType -import json -from request_api.models.default_method_result import DefaultMethodResult -from enum import Enum -from request_api.exceptions import BusinessException -import dateutil.parser - -MSG_NO_CHANGE='No change' - -class extensionevent: - """ FOI Event management service - - """ - def createextensionevent(self, ministryrequestid, extensionid, userid, username, event): - version = FOIRequestExtension.getversionforextension(extensionid) - curextension = FOIRequestExtension().getextensionforversion(extensionid, version) - prevextension = FOIRequestExtension().getextensionforversion(extensionid, version[0]-1) - extensionsummaryforcomment = self.__maintained(curextension, prevextension, event) - message = "" - try: - notificationresponse = self.createnotification(ministryrequestid, extensionid, curextension, prevextension, userid, event) - if extensionsummaryforcomment is None or (extensionsummaryforcomment and len(extensionsummaryforcomment) < 1): - return DefaultMethodResult(True, MSG_NO_CHANGE ,extensionid) - else: - commentresponse = self.createcomment(ministryrequestid, userid, username, extensionsummaryforcomment) - if commentresponse.success == True: - message += 'Comment posted' - if notificationresponse.success == True and event != EventType.delete.value: - message += 'Notification posted' - return DefaultMethodResult(True, message, extensionid) - except BusinessException as exception: - return DefaultMethodResult(False,'unable to post comment - '+exception.message,extensionid) - - def deleteexistingrelatedevents(self, ministryrequestid): - try: - notificationids = FOIRequestNotification().getextensionnotificationidsbyministry(ministryrequestid) - if notificationids: - self.__deleteaxisextensionnotifications(notificationids) - FOIRequestComment().deleteextensioncommentsbyministry(ministryrequestid) - except BusinessException as exception: - return DefaultMethodResult(False,'Issue in deleting previous event related to ministry id - '+exception.message,ministryrequestid) - - def createcomment(self, ministryrequestid, userid, username, extensionsummary): - _comment = self.__preparemessage(username, extensionsummary) - if _comment not in [None,'']: - return commentservice().createministryrequestcomment({"ministryrequestid": ministryrequestid, "comment": _comment}, userid, 2) - else: - return DefaultMethodResult(True, MSG_NO_CHANGE ,ministryrequestid) - - def createnotification(self, ministryrequestid, extensionid, curextension, prevextension, userid, event): - - nootificationrequired = self.__nonotificationrequired(curextension, prevextension, event) - onlycleanuprequired = self.__onlycleanuprequired(curextension, prevextension, event) - onlynotificationrequired = self.__onlynotificationrequired(curextension, prevextension, event) - notificationandcleanup = self.__bothnotificationandcleanup(curextension, prevextension, event) - if nootificationrequired == True: - return DefaultMethodResult(True, "No Notification", ministryrequestid) - elif onlycleanuprequired == True: - self.__deleteextensionnotification(extensionid) - return DefaultMethodResult(True, "Delete Extension", ministryrequestid) - elif onlynotificationrequired == True or notificationandcleanup == True: - extensionsummary = self.__createnotificationsummary(curextension, prevextension, event) - notification = self.__preparenotification(extensionsummary) - if notificationandcleanup == True: - self.__deleteextensionnotification(extensionid) - notificationtype = NotificationType().getnotificationtypeid("Extension") - return notificationservice().createnotification({"extensionid": extensionid, "message": notification}, ministryrequestid, "ministryrequest", notificationtype, userid, False) - - def __deleteaxisextensionnotifications(self, notificationids): - notificationservice().dismissnotificationbyid("ministryrequest", notificationids) - - def __deleteextensionnotification(self, extensionid): - _extensionnotifications = notificationservice().getextensionnotifications(extensionid) - noticiationids = [] - for _extensionnotification in _extensionnotifications: - noticiationids.append(_extensionnotification["notificationid"]) - notificationservice().dismissnotificationbyid("ministryrequest", noticiationids) - return DefaultMethodResult(True,'Extension notifications deleted', extensionid) - - def __preparenotification(self, extensionsummary): - ispublicbody = self.__getvalueof('ispublicbody', extensionsummary) - isdenied = self.__getvalueof('isdenied', extensionsummary) - isapproved = self.__getvalueof('isapproved', extensionsummary) - extension = self.__getvalueof('extension', extensionsummary) - prevextension = self.__getvalueof('prevextension', extensionsummary) - prevapprovedays = self.__getvalueof('approvednoofdays', prevextension) if prevextension else None - approveddays = self.__getvalueof('approvednoofdays', extension) - prevextendeddays = self.__getvalueof('extendedduedays', prevextension) if prevextension else None - extendedduedays = self.__getvalueof('extendedduedays', extension) - newduedate = self.__formatdate(self.__getvalueof('extendedduedate', extension), self.__genericdateformat()) - - approveddayschanged = True if prevapprovedays != approveddays else False - extendeddayschanged = True if prevextendeddays and prevextendeddays != extendedduedays else False - - if isdenied == True: - return "Extension request to OIPC has been denied." - elif ispublicbody == True and isapproved == True or (extendeddayschanged == True): - return "Extension taken for " + str(extendedduedays) + " days. The new legislated due date is "+ newduedate + "." - elif isapproved == True and not ispublicbody or (approveddayschanged == True): - return "Extension taken for " + str(approveddays) + " days. The new legislated due date is "+ newduedate + "." - else: - return "Extension has been edited." - - # modified attributes without changing state - def __isstatechanged(self, curextension, prevextension, event): - if event == EventType.modify.value and self.__getvalueof('extensionstatusid',curextension) != ExtensionStatus.pending.value and self.__getvalueof('extensionstatusid',curextension) == self.__getvalueof('extensionstatusid',prevextension): - return False - else: - return True - - def __getextensionreason(self, reasonid): - return extensionreasonservice().getextensionreasonbyid(reasonid) - - def __ispublicbody(self, curextension): - extnreson = extensionreasonservice().getextensionreasonbyid(curextension['extensionreasonid']) - extntype = self.__getextensiontype(extnreson) - if extntype == ExtensionType.publicbody.value: - return True - return False - - # add denied or modify to denied - def __isdenied(self, curextension, prevextension, event): - return self.__isvalidaction(curextension, prevextension, event, ExtensionStatus.denied.value) - - # add approved or modify to approved - def __isapproved(self, curextension, prevextension, event): - return self.__isvalidaction(curextension, prevextension, event, ExtensionStatus.approved.value) - - def __isvalidaction(self,curextension, prevextension, event, status): - curextensionstatusid = self.__getvalueof('extensionstatusid',curextension) - prevextensionstatusid = self.__getvalueof('extensionstatusid',prevextension) - if (event == EventType.modify.value and curextensionstatusid != prevextensionstatusid) or (event == EventType.add.value): - if curextensionstatusid in [ExtensionStatus.denied.value,ExtensionStatus.approved.value] and status == curextensionstatusid: - return True - return False - - def __nonotificationrequired(self, curextension, prevextension, event): - curextensionstatusid = self.__getvalueof('extensionstatusid',curextension) - prevextensionstatusid = self.__getvalueof('extensionstatusid',prevextension) - curapproveddays = self.__getvalueof('approvednoofdays',curextension) - prevapproveddays = self.__getvalueof('approvednoofdays',prevextension) - if (event == EventType.add.value and curextensionstatusid == 1) or (event == EventType.modify.value and curextensionstatusid == prevextensionstatusid and curapproveddays == prevapproveddays): - return True - return False - - def __onlycleanuprequired(self, curextension, prevextension, event): - curextensionstatusid = self.__getvalueof('extensionstatusid',curextension) - prevextensionstatusid = self.__getvalueof('extensionstatusid',prevextension) - if event == EventType.delete.value or (event == EventType.modify.value and str(prevextensionstatusid) in [str(ExtensionStatus.denied.value), str(ExtensionStatus.approved.value)] and curextensionstatusid == 1): - return True - return False - - def __onlynotificationrequired(self, curextension, prevextension, event): - if self.__isdenied(curextension, prevextension, event) == True or self.__isapproved(curextension, prevextension, event) == True or self.__ispublicbody(curextension) == True: - return True - return False - - def __bothnotificationandcleanup(self, curextension, prevextension, event): - curextensionstatusid = self.__getvalueof('extensionstatusid',curextension) - prevextensionstatusid = self.__getvalueof('extensionstatusid',prevextension) - curapproveddays = self.__getvalueof('approvednoofdays',curextension) - prevapproveddays = self.__getvalueof('approvednoofdays',prevextension) - if (event == EventType.modify.value and curextensionstatusid in [ExtensionStatus.approved.value, ExtensionStatus.denied.value] and prevextensionstatusid in [ExtensionStatus.approved.value, ExtensionStatus.denied.value]) or (event == EventType.modify.value and curextensionstatusid == ExtensionStatus.approved.value and curextensionstatusid == prevextensionstatusid and prevapproveddays != curapproveddays): - return True - return False - - def __maintained(self, curextension, prevextension, event): - return self.__createextensionsummary(curextension, prevextension, event) - - def __createextensionsummary(self, curextension, prevextension, event): - _extensionsummary = {'extension': curextension, 'prevextension':prevextension, 'reason': self.__getreasonfromid(self.__getvalueof("extensionreasonid", curextension)), 'action': event} - if event == EventType.delete.value: - _extensionsummary['isdelete'] = True - elif curextension["extensionstatusid"] != ExtensionStatus.pending.value and (event == EventType.modify.value or event == EventType.add.value): - _extensionsummary['isdelete'] = False - _extensionsummary['isdenied'] = self.__isdenied(curextension, prevextension, event) - _extensionsummary['ispublicbody'] = self.__ispublicbody(curextension) - _extensionsummary['isapproved'] = self.__isapproved(curextension, prevextension, event) - return _extensionsummary - - def __createnotificationsummary(self, curextension, prevextension, event): - isdenied = self.__isdenied(curextension, prevextension, event) - ispublicbody = self.__ispublicbody(curextension) - isapproved = self.__isapproved(curextension, prevextension, event) - - if event == EventType.modify.value: - return {'extension': curextension, 'prevextension': prevextension, 'ispublicbody': ispublicbody, 'isdenied': isdenied, 'isapproved': isapproved} - elif event == EventType.add.value and curextension["extensionstatusid"] != ExtensionStatus.pending.value: - return {'extension': curextension, 'ispublicbody': ispublicbody, 'isdenied': isdenied, 'isapproved': isapproved} - - def __preparemessage(self, username, extensionsummary): - action = self.__getvalueof('action', extensionsummary) - extension = self.__getvalueof('extension', extensionsummary) - prevextension = self.__getvalueof('prevextension', extensionsummary) - ispublicbody = self.__getvalueof('ispublicbody', extensionsummary) - isapproved = self.__getvalueof('isapproved', extensionsummary) - message = "" - if action == EventType.delete.value: - message = "Extension for " + self.__getvalueof('reason',extensionsummary) + " has been deleted." - elif action in [EventType.modify.value, EventType.add.value]: - if self.__isstatechanged(extension, prevextension, action) == False: - message = self.__preparemodifycomment(extensionsummary) - else: - if self.__getvalueof('isdenied', extensionsummary) == True: - message = "The OIPC has denied a "+ str(self.__getvalueof('extendedduedays',extension)) +" day extension." - elif isapproved == True: - if ispublicbody == True: - message = username + " has taken a "+ str(self.__getvalueof('extendedduedays',extension)) +" day Public Body extension." - else: - message = "The OIPC has granted a "+ str(self.__getvalueof('approvednoofdays',extension) ) +" day extension." - message += " The new legislated due date is "+ self.__formatdate(self.__getvalueof('extendedduedate',extension), self.__genericdateformat()) - else: - message = "Extension for " + self.__getvalueof('reason',extensionsummary) + " has been edited." - return message - - def __preparemodifycomment(self,extensionsummary): - extension = self.__getvalueof('extension', extensionsummary) - prevextension = self.__getvalueof('prevextension', extensionsummary) - isapproved = True if self.__getvalueof('extensionstatusid', extension) == ExtensionStatus.approved.value else False - comment = "" - if self.__getvalueof('extensionreasonid', extension) != self.__getvalueof('extensionreasonid', prevextension): - comment = "Extension reason has been updated to " + self.__getvalueof('reason', extensionsummary) - - if isapproved is True: - if self.__getvalueof('approvednoofdays', extension) != self.__getvalueof('approvednoofdays', prevextension): - comment += " AND " if comment not in [None,""] else "" - comment +="Approved number of days for extension has changed to " + str(self.__getvalueof('approvednoofdays', extension))+ "." - comment += " The new legislated due date is "+ self.__formatdate(self.__getvalueof('extendedduedate', extension), self.__genericdateformat()) - else: - if self.__getvalueof('extendedduedays', extension) != self.__getvalueof('extendedduedays', prevextension): - comment += " AND " if comment not in [None,""] else "" - comment +="Denied number of days for extension has changed to " + str(self.__getvalueof('extendedduedays', extension))+ "." - return comment - - def __getreasonfromid(self, curreasonid): - return self.__getextensionreasonvalue(self.__getextensionreason(curreasonid)) - - def __getextensionreasonvalue(self, extnreson): - return extnreson["reason"] - - def __getextensiontype(self, extnreson): - return extnreson["extensiontype"] - - def __getvalueof(self, key, extensionsummary): - return extensionsummary[key] if key in extensionsummary else None - - def __formatdate(self, datevalue, format): - return dateutil.parser.parse(datevalue).strftime(format) if datevalue is not None else None - - def __genericdateformat(self): - return '%b %d %Y' - -class EventType(Enum): - add = "add" - delete = "delete" - modify = "modify" - -class ExtensionStatus(Enum): - pending = 1 - approved = 2 - denied = 3 - -class ExtensionType(Enum): - publicbody = "Public Body" - oipc = "OIPC" \ No newline at end of file diff --git a/historical-search-api/request_api/services/events/legislativedate.py b/historical-search-api/request_api/services/events/legislativedate.py deleted file mode 100644 index 56885014c..000000000 --- a/historical-search-api/request_api/services/events/legislativedate.py +++ /dev/null @@ -1,78 +0,0 @@ - -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.FOIMinistryRequests import FOIMinistryRequest -from request_api.models.FOIRequestComments import FOIRequestComment -from request_api.models.NotificationTypes import NotificationType -import json -from request_api.models.default_method_result import DefaultMethodResult -from enum import Enum -from request_api.exceptions import BusinessException -from datetime import datetime -import holidays -import maya -import os -from flask import current_app -from dateutil.parser import parse -import time as t - -class legislativedateevent(duecalculator): - """ FOI Event management service - - """ - - def createdueevent(self): - try: - _today = self.gettoday() - notificationservice().dismissremindernotification("ministryrequest", self.__notificationtype()) - ca_holidays = self.getholidays() - _upcomingdues = FOIMinistryRequest.getupcominglegislativeduerecords() - notificationtype = NotificationType().getnotificationtypeid(self.__notificationtype()) - for entry in _upcomingdues: - _duedate = self.formatduedate(entry['duedate']) - message = None - if _duedate == _today: - message = self.__todayduemessage() - elif self.getpreviousbusinessday(entry['duedate'],ca_holidays) == _today or self.getbusinessdaysbetween(entry['duedate'],_today) == 5: - message = self.__upcomingduemessage(_duedate) - - self.__createnotification(message,entry['foiministryrequestid'], notificationtype) - self.__createcomment(entry, message) - return DefaultMethodResult(True,'Legislative reminder notifications created',_today) - except BusinessException as exception: - current_app.logger.error("%s,%s" % ('Legislative reminder Notification Error', exception.message)) - return DefaultMethodResult(False,'Legislative reminder notifications failed',_today) - - def __createnotification(self, message, requestid, notificationtype): - if message is not None: - return notificationservice().createnotification({"message" : message}, requestid, "ministryrequest", notificationtype, self.__defaultuserid(), False) - - def __createcomment(self, entry, message): - if message is not None: - _comment = self.__preparecomment(entry, message) - return commentservice().createcomments(_comment, self.__defaultuserid(), 2) - - def __preparecomment(self, foirequest, message): - _comment = dict() - _comment['comment'] = message - _comment['ministryrequestid'] = foirequest["foiministryrequestid"] - _comment['version'] = foirequest["version"] - _comment['taggedusers'] = None - _comment['parentcommentid'] = None - return _comment - - def __upcomingduemessage(self, duedate): - return 'Legislative Due Date due on ' + parse(str(duedate)).strftime("%Y %b %d").upper() - - def __todayduemessage(self): - return 'Legislative Due Date is Today' - - def __notificationtype(self): - return "Legislative Due Reminder" - - def __defaultuserid(self): - return "System" - diff --git a/historical-search-api/request_api/services/events/oipc.py b/historical-search-api/request_api/services/events/oipc.py deleted file mode 100644 index df3305dec..000000000 --- a/historical-search-api/request_api/services/events/oipc.py +++ /dev/null @@ -1,166 +0,0 @@ - -from os import stat -from re import VERBOSE -from request_api.models.FOIRequestOIPC import FOIRequestOIPC -from request_api.services.commentservice import commentservice -from request_api.services.oipcservice import oipcservice -from request_api.services.notificationservice import notificationservice -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -import json -from request_api.models.default_method_result import DefaultMethodResult -from enum import Enum -from request_api.exceptions import BusinessException -from dateutil.parser import parse -from request_api.utils.enums import CommentType -from request_api.models.NotificationTypes import NotificationType - -class oipcevent: - """ FOI OIPC Event management service - """ - - def createoipcevent(self, requestid, requesttype, userid): - if requesttype != "ministryrequest": - return DefaultMethodResult(True,'No change',requestid) - ministryrequest = FOIMinistryRequest.getmetadata(requestid) - if ministryrequest["isoipcreview"] in (None, False): - notificationservice().dismissnotifications_by_requestid_type(requestid, "ministryrequest", self.__notificationtype()) - return DefaultMethodResult(True,'Dismiss OIPC events',requestid) - inquiryoutcomes = oipcservice().getinquiryoutcomes() - version = ministryrequest["version"] - curoipcs = FOIRequestOIPC.getoipc(requestid, version) - prevoipcs = FOIRequestOIPC.getoipc(requestid, version-1) - oipcsummary = self.__maintained(curoipcs, prevoipcs, inquiryoutcomes) - if oipcsummary is None or (oipcsummary and len(oipcsummary) <1): - return DefaultMethodResult(True,'No change',requestid) - try: - for oipc in oipcsummary: - self.__createcomment(requestid, oipc, userid) - self.__createnotification(requestid, oipc, userid) - return DefaultMethodResult(True,'Comment posted',requestid) - except BusinessException as exception: - return DefaultMethodResult(False,'unable to post comment - '+exception.message,requestid) - - - def __createcomment(self, requestid, oipc, userid): - comment = {"ministryrequestid": requestid, "comment": self.__preparemessage(oipc)} - commentservice().createministryrequestcomment(comment, userid, CommentType.SystemGenerated.value) - - def __createnotification(self, requestid, oipc, userid): - notificationtype = NotificationType().getnotificationtypeid(self.__notificationtype()) - return notificationservice().createnotification({"message" : self.__preparemessage(oipc)}, requestid, "ministryrequest", notificationtype, userid, False) - - - def __maintained(self,coipcs, poipcs, inquiryoutcomes): - oipcs = [] - for coipc in coipcs: - if self.__isoipcpresent(self.__getoipcnumber(coipc), poipcs) == False: - oipcs.append(self.__createoipcsummary(coipc, EventType.add.value, inquiryoutcomes)) - else: - if self.__isoutcomeclosed(coipc, poipcs) == True: - oipcs.append(self.__createoipcsummary(coipc, EventType.close.value, inquiryoutcomes)) - if self.__isinquirychanged(coipc, poipcs) == True: - oipcs.append(self.__createoipcsummary(coipc, EventType.inquirychange.value, inquiryoutcomes)) - elif self.__isinquiryoutcomechanged(coipc, poipcs, inquiryoutcomes) == True: - oipcs.append(self.__createoipcsummary(coipc, EventType.inquiryoutcome.value, inquiryoutcomes)) - return oipcs - - # def __deleted(self, coipcs, poipcs): - # oipcs = [] - # for poipc in poipcs: - # if self.__isoipcpresent(self.__getoipcnumber(poipc), coipcs) == False: - # oipcs.append(self.__createoipcsummary(poipc, EventType.delete.value)) - # return oipcs - - def __isoipcpresent(self, oipcno, poipcs): - for oipc in poipcs: - if self.__getoipcnumber(oipc) == oipcno: - return True - return False - - def __isoutcomeclosed(self, coipc, poipcs): - for oipc in poipcs: - if self.__getoipcnumber(oipc) == self.__getoipcnumber(coipc) and self.__getoutcome(oipc) != self.__getoutcome(coipc) and self.__getoutcome(coipc) == "Closed": - return True - return False - - def __isinquirychanged(self, coipc, poipcs): - for oipc in poipcs: - if self.__getoipcnumber(oipc) == self.__getoipcnumber(coipc) and self.__getinquiry(oipc) != self.__getinquiry(coipc): - if self.__getinquirycomplydate(coipc) not in (None, "") and self.__getinquiryorderno(coipc) not in (None, "") and (self.__getinquiryorderno(oipc) != self.__getinquiryorderno(coipc) or self.__getinquirycomplydate(oipc) != self.__getinquirycomplydate(coipc)): - return True - return False - - def __isinquiryoutcomechanged(self, coipc, poipcs, inquiryoutcomes): - for oipc in poipcs: - if self.__getoipcnumber(oipc) == self.__getoipcnumber(coipc) and self.__getinquiry(oipc) != self.__getinquiry(coipc): - if self.__getinquiryoutcome(coipc, inquiryoutcomes) not in (None, "") and self.__getinquiryoutcome(oipc, inquiryoutcomes) != self.__getinquiryoutcome(coipc, inquiryoutcomes): - return True - return False - - def __createoipcsummary(self, oipc, event, inquiryoutcomes): - return {'oipcno': self.__getoipcnumber(oipc), - 'reviewtype': self.__getoipcreviewtype(oipc), - 'reason':self.__getreason(oipc), - 'outcome': self.__getoutcome(oipc), - 'inquirycomplydate': self.__getinquirycomplydate(oipc), - 'inquiryorderno': self.__getinquirycomplydate(oipc), - 'inquiryoutcome': self.__getinquiryoutcome(oipc, inquiryoutcomes), - 'event': event} - - def __getoipcnumber(self, dataschema): - return dataschema['oipcno'] - - def __getoipcreviewtype(self, dataschema): - return dataschema['reviewtype.name'] - - def __getreason(self, dataschema): - return dataschema['reason.name'] - - def __getoutcome(self, dataschema): - return dataschema['outcome.name'] if dataschema['outcomeid'] not in (None,"") else None - - def __getinquirycomplydate(self, dataschema): - return self.__getinquiry(dataschema)['inquirydate'] if dataschema['inquiryattributes'] not in (None,"") else None - - def __getinquiryoutcome(self, dataschema, inquiryoutcomes): - if dataschema['inquiryattributes'] not in (None,""): - inquiryoutcomeid = self.__getinquiry(dataschema)['inquiryoutcome'] - if inquiryoutcomeid not in (None,""): - return self.__getinquiryoutcomename(inquiryoutcomeid, inquiryoutcomes) - return None - - def __getinquiryorderno(self, dataschema): - return self.__getinquiry(dataschema)['orderno'] if dataschema['inquiryattributes'] not in (None,"") else None - - def __getinquiry(self, dataschema): - return dataschema['inquiryattributes'] - - def __getinquiryoutcomename(self, inquiryoutcomeid, inquiryoutcomes): - for outcome in inquiryoutcomes: - if inquiryoutcomeid == outcome["inquiryoutcomeid"]: - return outcome["name"] - return None - - def __preparemessage(self, oipc): - if oipc['event'] == EventType.add.value: - return 'OIPC '+ oipc['reviewtype'] +' opened for '+ oipc['reason'] - elif oipc['event'] == EventType.close.value: - return 'OIPC '+ oipc['reviewtype'] +' closed for '+ oipc['reason'] - elif oipc['event'] == EventType.inquirychange.value: - _inquirychange_msg = 'OIPC Inquiry Order '+ oipc['inquiryorderno'] +' compliance date due '+oipc['inquirycomplydate'] - if oipc['inquiryoutcome'] not in (None, ""): - return _inquirychange_msg+' .Inquiry Decision:' + oipc['inquiryoutcome'] - else: - return _inquirychange_msg - elif oipc['event'] == EventType.inquiryoutcome.value: - return 'OIPC '+ oipc['reviewtype'] +' Inquiry Decision: '+ oipc['inquiryoutcome'] - - def __notificationtype(self): - return "OIPC" - -class EventType(Enum): - add = "add" - delete = "delete" - close = "close" - inquirychange = "inquirychange" - inquiryoutcome = "inquiryoutcome" diff --git a/historical-search-api/request_api/services/events/oipcduedate.py b/historical-search-api/request_api/services/events/oipcduedate.py deleted file mode 100644 index a8db3021a..000000000 --- a/historical-search-api/request_api/services/events/oipcduedate.py +++ /dev/null @@ -1,72 +0,0 @@ - -from os import stat -from re import VERBOSE -from request_api.services.commentservice import commentservice -from request_api.services.commons.duecalculator import duecalculator -from request_api.services.notificationservice import notificationservice -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -from request_api.models.NotificationTypes import NotificationType -import json -from request_api.models.default_method_result import DefaultMethodResult -from request_api.exceptions import BusinessException -from dateutil.parser import parse -from flask import current_app - -class oipcduedateevent(duecalculator): - """ FOI OIPC Due Date Event management service - """ - - - def createdueevent(self): - try: - _today = self.gettoday() - notificationservice().dismissremindernotification("ministryrequest", self.__notificationtype()) - ca_holidays = self.getholidays() - _upcomingdues = FOIMinistryRequest.getupcomingoipcduerecords() - for entry in _upcomingdues: - _duedate = self.formatduedate(entry['duedate']) - message = None - if _duedate == _today: - message = self.__todayduemessage(entry) - elif self.getpreviousbusinessday(entry['duedate'],ca_holidays) == _today: - message = self.__upcomingduemessage(entry) - elif self.getpreviousbusinessday_by_n(entry['duedate'],ca_holidays, 2) == _today: - message = self.__upcomingduemessage(entry) - self.__createnotification(message,entry['foiministryrequestid']) - self.__createcomment(entry, message) - return DefaultMethodResult(True,'OIPC reminder notifications created',_today) - except BusinessException as exception: - current_app.logger.error("%s,%s" % ('OIPC reminder Notification Error', exception.message)) - return DefaultMethodResult(False,'OIPC reminder notifications failed',_today) - - def __createnotification(self, message, requestid): - if message is not None: - notificationtype = NotificationType().getnotificationtypeid(self.__notificationtype()) - return notificationservice().createnotification({"message" : message}, requestid, "ministryrequest", notificationtype, self.__defaultuserid(), False) - - def __createcomment(self, entry, message): - if message is not None: - _comment = self.__preparecomment(entry, message) - return commentservice().createcomments(_comment, self.__defaultuserid(), 2) - - - def __preparecomment(self, foirequest, message): - _comment = dict() - _comment['comment'] = message - _comment['ministryrequestid'] = foirequest["foiministryrequestid"] - _comment['version'] = foirequest["version"] - _comment['taggedusers'] = None - _comment['parentcommentid'] = None - return _comment - - def __upcomingduemessage(self, data): - return 'OIPC Inquiry Order '+data['orderno'] +' compliance date due on ' + parse(str(data['duedate'])).strftime("%Y %b %d").upper() - - def __todayduemessage(self, data): - return 'OIPC Inquiry Order '+data['orderno'] +' compliance due Today' - - def __notificationtype(self): - return "OIPC Due Reminder" - - def __defaultuserid(self): - return "System" \ No newline at end of file diff --git a/historical-search-api/request_api/services/events/payment.py b/historical-search-api/request_api/services/events/payment.py deleted file mode 100644 index f22cbb83b..000000000 --- a/historical-search-api/request_api/services/events/payment.py +++ /dev/null @@ -1,130 +0,0 @@ - -from os import stat -from re import VERBOSE -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 -from request_api.models.NotificationTypes import NotificationType -import json -from request_api.models.default_method_result import DefaultMethodResult -from enum import Enum -import maya -import os -from dateutil.parser import parse -from pytz import timezone -from request_api.utils.enums import PaymentEventType -from request_api.utils.commons.datetimehandler import datetimehandler -from request_api.services.commons.duecalculator import duecalculator -from request_api.exceptions import BusinessException -from flask import current_app - -class paymentevent: - """ FOI Event management service - - """ - def createpaymentevent(self, requestid, eventtype): - _commentresponse = self.__createcomment(requestid, eventtype) - _notificationresponse = self.__createnotification(requestid, eventtype) - if _commentresponse.success == True and _notificationresponse.success == True: - return DefaultMethodResult(True,'Payment notification posted',requestid) - else: - return DefaultMethodResult(False,'Unable to post Payment notification',requestid) - - def createpaymentexpiredevent(self, requestid): - _commentresponse = self.__createcomment(requestid, PaymentEventType.expired.value) - _notificationresponse = self.__createnotification(requestid, PaymentEventType.expired.value) - if _commentresponse.success == True and _notificationresponse.success == True: - return DefaultMethodResult(True,'Payment Expiry notification posted',requestid) - else: - return DefaultMethodResult(False,'Unable to post Payment Expiry notification',requestid) - - def createpaymentreminderevent(self): - try: - _today = datetimehandler().gettoday() - - notificationservice().dismissremindernotification("rawrequest", self.__notificationtype()) - eventtype = PaymentEventType.reminder.value - _onholdrequests = FOIRawRequest.getappfeeowingrequests() - notificationtype = NotificationType().getnotificationtypeid(self.__notificationtype()) - for entry in _onholdrequests: - _dateofstatechange = datetimehandler().formatdate(entry['updated_at']) - businessdayselapsed = duecalculator().getbusinessdaysbetween(_dateofstatechange) - if businessdayselapsed >= 20 and duecalculator().isbusinessday(_today): - commentexists = False - existingcomments = commentservice().getrawrequestcomments(entry['requestid']) - for comment in existingcomments: - if comment['text'] == self.__preparecomment(entry['requestid'], eventtype)['comment']: #checks if comment already exists - commentexists = True - if not commentexists: - self.__createcommentforrawrequest(entry['requestid'], eventtype) - self.__createnotificationforrawrequest(entry['requestid'], eventtype, notificationtype) - 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, notificationtype): - notification = self.__preparenotification(requestid, eventtype) - return notificationservice().createnotification({"message" : notification}, requestid, "rawrequest", notificationtype, "system") - - def __createcomment(self, requestid, eventtype): - comment = self.__preparecomment(requestid, eventtype) - return commentservice().createministryrequestcomment(comment, self.__defaultuserid(), 2) - - def __createnotification(self, requestid, eventtype): - notificationtype = NotificationType().getnotificationtypeid(self.__notificationtype()) - notification = self.__preparenotification(requestid, eventtype) - return notificationservice().createnotification({"message" : notification}, requestid, "ministryrequest", notificationtype, self.__defaultuserid()) - - def __preparenotification(self, requestid, eventtype): - return self.__notificationmessage(requestid, eventtype) - - def __preparecomment(self, requestid, eventtype): - if eventtype == PaymentEventType.paid.value: - comment = {"comment": "Applicant has paid required fee. New LDD is " + FOIMinistryRequest.getduedate(requestid).strftime("%m/%d/%Y")} - elif eventtype == PaymentEventType.expired.value: - comment = {"comment": "Fees were due to be paid by " + self.gettoday() + ", you may consider closing the request as abandoned."} - elif eventtype == PaymentEventType.outstandingpaid.value: - 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": "20 business days has passed awaiting payment, you can consider closing the request as abandoned"} - else: - comment = None - if comment is not None: - if eventtype == PaymentEventType.reminder.value: - comment['requestid'] = requestid - else: - comment['ministryrequestid']= requestid - return comment - - def __notificationmessage(self, requestid, eventtype): - if eventtype == PaymentEventType.paid.value: - return "Applicant has paid required fee, resume gathering. New LDD is " + FOIMinistryRequest.getduedate(requestid).strftime("%m/%d/%Y") - elif eventtype == PaymentEventType.expired.value: - return "Fees were due to be paid by " + self.gettoday() + ", you may consider closing the request as abandoned." - elif eventtype == PaymentEventType.outstandingpaid.value: - 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 - - def __defaultuserid(self): - return "Online Payment" - - def gettoday(self): - now_pst = maya.parse(maya.now()).datetime(to_timezone='America/Vancouver', naive=False) - return now_pst.strftime('%m/%d/%Y') - - def __notificationtype(self): - return "Payment" \ No newline at end of file diff --git a/historical-search-api/request_api/services/events/section5pending.py b/historical-search-api/request_api/services/events/section5pending.py deleted file mode 100644 index 84e6897f5..000000000 --- a/historical-search-api/request_api/services/events/section5pending.py +++ /dev/null @@ -1,68 +0,0 @@ -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.NotificationTypes import NotificationType -from request_api.models.default_method_result import DefaultMethodResult -from enum import Enum -from request_api.exceptions import BusinessException -from request_api.utils.commons.datetimehandler import datetimehandler -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() - notificationtype = NotificationType().getnotificationtypeid(self.__notificationtype()) - for entry in section5pendings: - _dateofstatechange = datetimehandler().formatdate(entry['created_at']) - businessdayselapsed = self.getbusinessdaysbetween(_dateofstatechange) - if businessdayselapsed >= 10 and self.isbusinessday(_today): - message = self.__passeddueremindermessage() - commentexists = False - existingcomments = commentservice().getrawrequestcomments(entry['requestid']) - for comment in existingcomments: - if comment['text'] == message: #checks if comment already exists - commentexists = True - if not commentexists: - self.__createcomment(entry, message) - self.__createnotification(message, entry['requestid'], notificationtype) - 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, notificationtype): - if message is not None: - return notificationservice().createremindernotification({"message" : message}, requestid, "rawrequest", 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/historical-search-api/request_api/services/events/state.py b/historical-search-api/request_api/services/events/state.py deleted file mode 100644 index d5b015d43..000000000 --- a/historical-search-api/request_api/services/events/state.py +++ /dev/null @@ -1,135 +0,0 @@ - -from os import stat -from re import VERBOSE -from request_api.services.commentservice import commentservice -from request_api.services.notificationservice import notificationservice -from request_api.services.cfrfeeservice import cfrfeeservice -from request_api.models.FOIRawRequests import FOIRawRequest -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -from request_api.models.FOIRequestStatus import FOIRequestStatus -from request_api.models.NotificationTypes import NotificationType -import json -from request_api.models.default_method_result import DefaultMethodResult -from request_api.utils.enums import StateName - -class stateevent: - """ FOI Event management service - - """ - def createstatetransitionevent(self, requestid, requesttype, userid, username): - state = self.__haschanged(requestid, requesttype) - if state is not None: - _commentresponse = self.__createcommentwrapper(requestid, state, requesttype, userid, username) - _notificationresponse = self.__createnotification(requestid, state, requesttype, userid) - _cfrresponse = self.__createcfrentry(state, requestid, userid) - if _commentresponse.success == True and _notificationresponse.success == True and _cfrresponse.success == True: - return DefaultMethodResult(True,'Comment posted',requestid) - else: - return DefaultMethodResult(False,'unable to post comment',requestid) - return DefaultMethodResult(True,'No change',requestid) - - def __haschanged(self, requestid, requesttype): - if requesttype == "rawrequest": - states = FOIRawRequest.getstatenavigation(requestid) - else: - states = FOIMinistryRequest.getstatenavigation(requestid) - if len(states) == 2: - newstate = states[0] - oldstate = states[1] - if newstate != oldstate and newstate != StateName.intakeinprogress.value: - return newstate - return None - - def __createcommentwrapper(self, requestid, state, requesttype, userid, username): - if state == StateName.archived.value: - _openedministries = FOIMinistryRequest.getministriesopenedbyuid(requestid) - for ministry in _openedministries: - response=self.__createcomment(ministry["ministryrequestid"], state, 'ministryrequest', userid, username) - else: - response=self.__createcomment(requestid, state, requesttype, userid, username) - return response - - - def __createcomment(self, requestid, state, requesttype, userid, username): - comment = self.__preparecomment(requestid, state, requesttype, username) - if requesttype == "ministryrequest": - return commentservice().createministryrequestcomment(comment, userid, 2) - else: - return commentservice().createrawrequestcomment(comment, userid,2) - - - def __createnotification(self, requestid, state, requesttype, userid): - _notificationtype = "State" - if state == StateName.callforrecords.value and requesttype == "ministryrequest": - foirequest = notificationservice().getrequest(requestid, requesttype) - _notificationtype = "Group Members" if foirequest['assignedministryperson'] is None else "State" - notification = self.__preparenotification(state) - if state == StateName.response.value and requesttype == "ministryrequest": - signgoffapproval = FOIMinistryRequest().getrequest(requestid)['ministrysignoffapproval'] - if signgoffapproval: - notification = notification + f". Approved by {signgoffapproval['approvername']}, {signgoffapproval['approvertitle']} on {signgoffapproval['approveddate']}" - if state == StateName.closed.value or state == StateName.archived.value: - notificationservice().dismissnotificationsbyrequestid(requestid, requesttype) - if state == StateName.archived.value: - _openedministries = FOIMinistryRequest.getministriesopenedbyuid(requestid) - for ministry in _openedministries: - notificationtype = NotificationType().getnotificationtypeid("State") - response = notificationservice().createnotification({"message" : notification}, ministry["ministryrequestid"], 'ministryrequest', notificationtype, userid) - else: - notificationtype = NotificationType().getnotificationtypeid("State") - response = notificationservice().createnotification({"message" : notification}, requestid, requesttype, notificationtype, userid) - if _notificationtype == "Group Members": - notificationtype = NotificationType().getnotificationtypeid(_notificationtype) - 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) - else: - return DefaultMethodResult(False,'Unable to add notification',requestid) - if response.success == True: - return DefaultMethodResult(True,'Notification added',requestid) - return DefaultMethodResult(True,'No change',requestid) - - def __preparenotification(self, state): - return self.__notificationmessage(state) - - def __preparegroupmembernotification(self, state, requestid): - if state == StateName.callforrecords.value: - return self.__notificationcfrmessage(requestid) - return self.__groupmembernotificationmessage(state) - - def __preparecomment(self, requestid, state,requesttype, username): - comment = {"comment": self.__commentmessage(state, username, requesttype, requestid)} - if requesttype == "ministryrequest": - comment['ministryrequestid']= requestid - else: - comment['requestid']=requestid - return comment - - def __formatstate(self, state): - return StateName.open.value if state == StateName.archived.value else state - - def __commentmessage(self, state, username, requesttype, requestid): - comment = username+' changed the state of the request to '+self.__formatstate(state) - if state == StateName.response.value and requesttype == "ministryrequest": - signgoffapproval = FOIMinistryRequest().getrequest(requestid)['ministrysignoffapproval'] - if signgoffapproval: - 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 == StateName.feeestimate.value and cfrfee['cfrfeestatusid'] in (None, '')): - return cfrfeeservice().sanctioncfrfee(ministryrequestid, {"status": "review"}, userid) - else: - return DefaultMethodResult(True,'No action needed',ministryrequestid) - - def __groupmembernotificationmessage(self, state): - return 'New request is in '+state \ No newline at end of file diff --git a/historical-search-api/request_api/services/events/watcher.py b/historical-search-api/request_api/services/events/watcher.py deleted file mode 100644 index 45043ca88..000000000 --- a/historical-search-api/request_api/services/events/watcher.py +++ /dev/null @@ -1,72 +0,0 @@ -from cmath import asin -from os import stat -from re import VERBOSE -from request_api.services.notificationservice import notificationservice -from request_api.services.commentservice import commentservice -import json -from request_api.models.default_method_result import DefaultMethodResult -from request_api.utils.enums import CommentType - -class watcherevent: - - def createwatcherevent(self, requestid, watcher, requesttype, userid, username): - #Create Notification - Watcher inactive - if watcher['isactive'] == False: - notificationresponse = self.__createnotification(requesttype, watcher, userid, username) - if notificationresponse.success == True: - return DefaultMethodResult(True,'Watcher Notification has been created',requestid) - else: - return DefaultMethodResult(False,'unable to create notification for removed watcher',requestid) - else: - #Dismiss Notification By ID & Userid - Watcher active - notificationservice().dismissnotifications_by_requestid_type_userid(requestid, requesttype, 'Watcher', watcher['watchedby']) - return DefaultMethodResult(True,'No change',requestid) - - def createwatchereventforrestricted(self, requestid, watcher, requesttype, userid, username): - #Create comment - add/remove Watcher - #Create Notification - Watcher inactive - if watcher['isactive'] == False: - notificationresponse = self.__createnotification(requesttype, watcher, userid, username) - commentrespose = self.__createcommentforremove(watcher['fullname'],username,requestid,userid,requesttype) - if notificationresponse.success == True and commentrespose.success == True: - return DefaultMethodResult(True,'Watcher Notification, Comment have been created',requestid) - else: - return DefaultMethodResult(False,'unable to create notification and/or comment for removed watcher',requestid) - else: - #Dismiss Notification By ID & Userid - Watcher active - notificationservice().dismissnotifications_by_requestid_type_userid(requestid, requesttype, 'Watcher', watcher['watchedby']) - commentrespose = self.__createcommentforadd(watcher['fullname'],username,requestid,userid,requesttype) - if commentrespose.success == True: - return DefaultMethodResult(True,'Watcher Comment has been created',requestid) - else: - return DefaultMethodResult(False,'unable to create comment for removed watcher',requestid) - # return DefaultMethodResult(True,'No change',requestid) - - - def __createnotification(self, requesttype, watcher, userid, username): - notification = self.__preparenotification(username) - return notificationservice().createwatchernotification({"message" : notification}, requesttype, watcher, userid) - - def __preparenotification(self,username): - return self.__notificationmessage(username) - - def __notificationmessage(self,username): - return username+' has removed you as a watcher to this request' - - def __createcommentforadd(self, assigneename, username, requestid, userid, requesttype): - _commentmessage ='{0} has made {1} a watcher'.format(username,assigneename) - if requesttype == "ministryrequest": - comment = {"ministryrequestid": requestid, "comment": _commentmessage} - return commentservice().createministryrequestcomment(comment, userid, CommentType.SystemGenerated.value) - else: - comment = {"requestid": requestid, "comment": _commentmessage} - return commentservice().createrawrequestcomment(comment, userid, CommentType.SystemGenerated.value) - - def __createcommentforremove(self, assigneename, username, requestid, userid, requesttype): - _commentmessage ='{0} has removed {1} as a watcher'.format(username,assigneename) - if requesttype == "ministryrequest": - comment = {"ministryrequestid": requestid, "comment": _commentmessage} - return commentservice().createministryrequestcomment(comment, userid, CommentType.SystemGenerated.value) - else: - comment = {"requestid": requestid, "comment": _commentmessage} - return commentservice().createrawrequestcomment(comment, userid, CommentType.SystemGenerated.value) diff --git a/historical-search-api/request_api/services/eventservice.py b/historical-search-api/request_api/services/eventservice.py deleted file mode 100644 index 96ba48dcc..000000000 --- a/historical-search-api/request_api/services/eventservice.py +++ /dev/null @@ -1,144 +0,0 @@ - -from os import stat -from re import VERBOSE -from request_api.services.events.state import stateevent -from request_api.services.events.division import divisionevent -from request_api.services.events.oipc import oipcevent -from request_api.services.events.assignment import assignmentevent -from request_api.services.events.cfrdate import cfrdateevent -from request_api.services.events.comment import commentevent -from request_api.services.events.watcher import watcherevent -from request_api.services.events.legislativedate import legislativedateevent -from request_api.services.events.divisiondate import divisiondateevent -from request_api.services.events.oipcduedate import oipcduedateevent -from request_api.services.events.extension import extensionevent -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 -import time as timer - -import json -from flask import current_app -class eventservice: - """ FOI event management service - - """ - - async def postevent(self, requestid, requesttype, userid, username, isministryuser,assigneename=''): - self.posteventsync(requestid, requesttype, userid, username, isministryuser,assigneename) - - def posteventsync(self, requestid, requesttype, userid, username, isministryuser,assigneename=''): - try: - stateeventresponse = stateevent().createstatetransitionevent(requestid, requesttype, userid, username) - divisioneventresponse = divisionevent().createdivisionevent(requestid, requesttype, userid) - assignmentresponse = assignmentevent().createassignmentevent(requestid, requesttype, userid, isministryuser,assigneename,username) - oipcresponse = oipcevent().createoipcevent(requestid, requesttype, userid) - if stateeventresponse.success == False or divisioneventresponse.success == False or assignmentresponse.success == False or oipcresponse.success == False: - current_app.logger.error("FOI Notification failed for event for request= %s ; state response=%s ; division response=%s ; assignment response=%s ; oipc response=%s" % (requestid, stateeventresponse.message, divisioneventresponse.message, assignmentresponse.message, oipcresponse.message)) - except BusinessException as exception: - self.__logbusinessexception(exception) - - def posteventforextension(self, ministryrequestid, extensionid, userid, username, event): - try: - extensioneventresponse = extensionevent().createextensionevent(ministryrequestid, extensionid, userid, username, event) - if extensioneventresponse.success == False: - current_app.logger.error("FOI Notification failed for event for extension= %s" % (extensionid)) - except BusinessException as exception: - self.__logbusinessexception(exception) - - def posteventforaxisextension(self, ministryrequestid, extensionids, userid, username, event): - try: - for extensionid in extensionids: - extensioneventresponse = extensionevent().createextensionevent(ministryrequestid, extensionid, userid, username, event) - if extensioneventresponse.success == False: - current_app.logger.error("FOI Notification failed for event for extension= %s" % (extensionid)) - except BusinessException as exception: - self.__logbusinessexception(exception) - - def postreminderevent(self): - try: - cfreventresponse = cfrdateevent().createdueevent() - legislativeeventresponse = legislativedateevent().createdueevent() - divisioneventresponse = divisiondateevent().createdueevent() - oipceventresponse = oipcduedateevent().createdueevent() - 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 or oipceventresponse == 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 ; oipcduereminder response=%s" % (cfreventresponse.message, legislativeeventresponse.message, divisioneventresponse.message, paymentremindereventresponse.message, section5pendingresponse.message, oipceventresponse.message)) - return DefaultMethodResult(False,'Due reminder notifications failed',cfreventresponse.identifier) - return DefaultMethodResult(True,'Due reminder notifications created',cfreventresponse.identifier) - except BusinessException as exception: - self.__logbusinessexception(exception) - - async def postcommentevent(self, commentid, requesttype, userid, isdelete=False, existingtaggedusers=False): - try: - commentresponse = commentevent().createcommentevent(commentid, requesttype, userid, isdelete, existingtaggedusers) - if commentresponse.success == False : - current_app.logger.error("FOI Notification failed for comment event=%s" % (commentresponse.message)) - return DefaultMethodResult(False,'Comment notifications failed',commentresponse.identifier) - return DefaultMethodResult(True,'Comment notifications created',commentresponse.identifier) - except BusinessException as exception: - self.__logbusinessexception(exception) - - def posteventforwatcher(self, requestid, request, requesttype, userid, username): - try: - if request['isrestricted']: - watcherresponse = watcherevent().createwatchereventforrestricted(requestid, request, requesttype, userid, username) - else: - watcherresponse = watcherevent().createwatcherevent(requestid, request, requesttype, userid, username) - if watcherresponse.success == False: - current_app.logger.error("FOI Notification failed for event for request= %s ; watcher response=%s" % (requestid, watcherresponse.message)) - except BusinessException as exception: - self.__logbusinessexception(exception) - - - async def posteventforsanctioncfrfeeform(self, ministryrequestid, userid, username): - self.__posteventforsanctioncfrfeeform(ministryrequestid, userid, username) - - async def posteventforcfrfeeform(self, ministryrequestid, userid, username): - try: - feewaivercommentresponse, refundcommentresponse= cfrfeeformevent().createeventforupdatedamounts(ministryrequestid, userid, username) - if feewaivercommentresponse.success == False or refundcommentresponse.success == False: - current_app.logger.error("FOI Comment failed for amount update event for CFRFEEFORM= %s" % (ministryrequestid)) - except BusinessException as exception: - self.__logbusinessexception(exception) - - async def postpaymentevent(self, requestid, paymenteventtype = PaymentEventType.paid.value): - try: - paymeneteventresponse = paymentevent().createpaymentevent(requestid, paymenteventtype) - if paymeneteventresponse.success == False: - current_app.logger.error("FOI Notification failed for payment event for request= %s ; event response=%s" % (requestid, paymeneteventresponse.message)) - except BusinessException as exception: - self.__logbusinessexception(exception) - - def posteventforemailfailure(self, ministryrequestid, requesttype, stage, reason, userid): - try: - emaileventresponse = emailevent().createemailevent(ministryrequestid, requesttype, stage, reason, userid) - if emaileventresponse.success == False: - current_app.logger.error("FOI Notification failed for email event = %s" % (emaileventresponse)) - except BusinessException as exception: - self.__logbusinessexception(exception) - - def postpaymentexpiryevent(self, ministryrequestid): - try: - paymeneteventresponse = paymentevent().createpaymentexpiredevent(ministryrequestid) - if paymeneteventresponse.success == False: - current_app.logger.error("FOI Expiry Notification failed for payment event for request= %s ; event response=%s" % (ministryrequestid, paymeneteventresponse.message)) - return paymeneteventresponse - except BusinessException as exception: - self.__logbusinessexception(exception) - - def __logbusinessexception(self, exception): - current_app.logger.error("%s,%s" % ('FOI Comment Notification Error', exception.message)) - - def __posteventforsanctioncfrfeeform(self, ministryrequestid, userid, username): - try: - cfrfeeeventresponse = cfrfeeformevent().createstatetransitionevent(ministryrequestid, userid, username) - if cfrfeeeventresponse.success == False: - current_app.logger.error("FOI Notification failed for event for CFRFEEFORM= %s" % (ministryrequestid)) - except BusinessException as exception: - self.__logbusinessexception(exception) \ No newline at end of file diff --git a/historical-search-api/request_api/services/extensionreasonservice.py b/historical-search-api/request_api/services/extensionreasonservice.py deleted file mode 100644 index a3022059a..000000000 --- a/historical-search-api/request_api/services/extensionreasonservice.py +++ /dev/null @@ -1,13 +0,0 @@ -from request_api.models.ExtensionReasons import ExtensionReason - -class extensionreasonservice: - - def getextensionreasons(self): - """ Returns the active records - """ - return ExtensionReason().getallextensionreasons() - - def getextensionreasonbyid(self, extensionreasonid): - """ Returns the active records - """ - return ExtensionReason().getextensionreasonbyid(extensionreasonid) \ No newline at end of file diff --git a/historical-search-api/request_api/services/extensionservice.py b/historical-search-api/request_api/services/extensionservice.py deleted file mode 100644 index 7013cb81c..000000000 --- a/historical-search-api/request_api/services/extensionservice.py +++ /dev/null @@ -1,416 +0,0 @@ -from os import stat -from re import VERBOSE -from request_api.models.FOIRequestExtensions import FOIRequestExtension -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -from request_api.models.FOIMinistryRequestDocuments import FOIMinistryRequestDocument -from request_api.models.FOIRequestExtensionDocumentMappings import FOIRequestExtensionDocumentMapping -from request_api.services.requestservice import requestservice -from request_api.services.documentservice import documentservice -from request_api.services.extensionreasonservice import extensionreasonservice -from request_api.services.eventservice import eventservice -from request_api.services.events.extension import ExtensionType -from datetime import datetime -import asyncio -import json -import base64 -from request_api.exceptions import BusinessException, Error - -class extensionservice: - """ FOI Extension management service - """ - otherdateformat = '%Y-%m-%d' - - def getrequestextensions(self, requestid, version=None): - extensions = [] - created_atdateformat = '%Y-%m-%d %H:%M:%S.%f' - requestversion = self.__getversionforrequest(requestid) if version is None else version - extensionrecords = FOIRequestExtension.getextensions(requestid, requestversion) - for entry in extensionrecords: - extensions.append({"foirequestextensionid": entry["foirequestextensionid"], - "extensionreasonid": entry["extensionreasonid"], - "extensionreson": entry["reason"], - "extensiontype": entry["extensiontype"], - "extensionstatusid": entry["extensionstatusid"], - "extensionstatus": entry["name"], - "extendedduedays": entry["extendedduedays"], - "extendedduedate": self.__formatdate(entry["extendedduedate"], self.otherdateformat), - "decisiondate": self.__formatdate(entry["decisiondate"], self.otherdateformat), - "approvednoofdays": entry["approvednoofdays"], - "created_at": self.__formatdate(entry["created_at"], created_atdateformat), - "createdby": entry["createdby"]}) - return extensions - - def __ispublicbodyextension(self, reasonid): - extensionreason = extensionreasonservice().getextensionreasonbyid(reasonid) - return 'extensiontype' in extensionreason and extensionreason['extensiontype'] == ExtensionType.publicbody.value - - def getrequestextension(self, extensionid): - requestextension = FOIRequestExtension().getextension(extensionid) - extensiondocuments = self.__getextensiondocuments(requestextension["foirequestextensionid"], requestextension["version"]) - documents = self.__getextensiondocumentsinfo(extensiondocuments) - extensionreason = extensionreasonservice().getextensionreasonbyid(requestextension['extensionreasonid']) - requestextensionwithdocuments = self.__createextensionobject(requestextension, documents, extensionreason) - return requestextensionwithdocuments - - def createrequestextension(self, foirequestid, ministryrequestid, extensionschema, userid): - version = self.__getversionforrequest(ministryrequestid) - reasonid = extensionschema['extensionreasonid'] - extensionreason = extensionreasonservice().getextensionreasonbyid(reasonid) - ispublicbodyextension = self.__ispublicbodyextension(reasonid) - if ('extensionstatusid' in extensionschema and extensionschema['extensionstatusid'] == 2) or ispublicbodyextension == True: - self.validatecreateextension(ministryrequestid, extensionschema, ispublicbodyextension) - ministryrequestschema = { - "duedate": extensionschema['extendedduedate'] - } - result = requestservice().saveministryrequestversion(ministryrequestschema, foirequestid, ministryrequestid, userid) - - newduedate = \ - ministryrequestschema['duedate'] \ - if isinstance(ministryrequestschema['duedate'], str) \ - else self.__formatdate(ministryrequestschema['duedate'], self.otherdateformat) - - if result.success == True: - version = self.__getversionforrequest(ministryrequestid) - #Set isactive:false for extensions with previous ministry request version - FOIRequestExtension.disableoldversions(version,ministryrequestid, userid) - extnsionresult = FOIRequestExtension.saveextension(ministryrequestid, version, extensionschema, extensionreason, userid, newduedate= newduedate) - else: - extnsionresult = FOIRequestExtension.saveextension(ministryrequestid, version, extensionschema, extensionreason, userid) - if 'documents' in extensionschema and extensionschema['extensionstatusid'] != 1: - self.saveextensiondocument(extensionschema['documents'], ministryrequestid, userid, extnsionresult.identifier) - return extnsionresult - - - def validatecreateextension(self, ministryrequestid, extensionschema, ispublicbodyextension= None): - if ispublicbodyextension is None: - ispublicbodyextension = self.__ispublicbodyextension(extensionschema['extensionreasonid']) - - if not ispublicbodyextension: - return - - extensions = self.getrequestextensions(ministryrequestid) - publicbodyextensiondays = [extension['extendedduedays'] for extension in extensions if extension['extensiontype'] == ExtensionType.publicbody.value] - if sum(publicbodyextensiondays) + extensionschema['extendedduedays'] > 30: - raise BusinessException(Error.INVALID_INPUT) - - - def getextensiontobesaved(self, ministryrequestid, extensions,version): - extensionstoadd=[] - extensionstodelete=[] - extensionidstodelete=[] - existingextensions = FOIRequestExtension.getextensions(ministryrequestid, version) - if(len(existingextensions) > 0): - identifiersforexisting = [] - identifiersforaxis = [] - for existingextension in existingextensions: - datestring = str(existingextension["extendedduedate"]).split(" ",1)[0] - identifierforexisting= str(existingextension["extensionreasonid"])+datestring+str(existingextension["extensionstatusid"]) - identifiersforexisting.append(identifierforexisting) - for axisextension in extensions: - datestring = str(axisextension["extendedduedate"]).split(" ",1)[0] - identifierforaxis= str(axisextension["extensionreasonid"])+datestring+str(axisextension["extensionstatusid"]) - identifiersforaxis.append(identifierforaxis) - if(identifierforaxis not in identifiersforexisting): - extensionstoadd.append(axisextension) - for existingextension in existingextensions: - datestring = str(existingextension["extendedduedate"]).split(" ",1)[0] - identifierforexisting= str(existingextension["extensionreasonid"])+datestring+str(existingextension["extensionstatusid"]) - if(identifierforexisting not in identifiersforaxis): - extensionstodelete.append(existingextension) - extensionidstodelete.append(existingextension["foirequestextensionid"]) - else: - extensionstoadd = extensions - - return extensionstoadd, extensionstodelete, extensionidstodelete - - def saveaxisrequestextension(self, ministryrequestid, extensions, userid, username): - version = self.__getversionforrequest(ministryrequestid) - extensionstoadd, extensionstodelete, extensionidstodelete = self.getextensiontobesaved(ministryrequestid, extensions,version) - if(len(extensionstodelete) > 0): - for existingextension in extensionstodelete: - self.deletedocuments(existingextension['foirequestextensionid'], existingextension['version'], ministryrequestid, userid) - deletedextensionresult= FOIRequestExtension.disableextensions(extensionidstodelete, userid) - newextensions = [] - if(len(extensionstoadd) > 0): - for extension in extensionstoadd: - newextensions.append(self.__createextension(extension, ministryrequestid, version, userid)) - extnsionresult = FOIRequestExtension.saveextensions(newextensions) - if deletedextensionresult.success == True and len(deletedextensionresult.args) > 0: - # Post event for system generated comments & notifications for deleted extensions - eventservice().posteventforaxisextension(ministryrequestid, deletedextensionresult.args[0], userid, username, "delete") - return extnsionresult - - def __createextension(self, extension, ministryrequestid, ministryrequestversion, userid): - createuserid = extension['createdby'] if 'createdby' in extension and extension['createdby'] is not None else userid - createdat = extension['created_at'] if 'created_at' in extension and extension['created_at'] is not None else datetime.now() - approveddate = extension['approveddate'] if 'approveddate' in extension else None - denieddate = extension['denieddate'] if 'denieddate' in extension else None - decisiondate = approveddate if approveddate else denieddate - approvednoofdays = extension['approvednoofdays'] if 'approvednoofdays' in extension else None - - if 'extensionstatusid' in extension: - extensionstatusid = extension['extensionstatusid'] - else: - extensionstatusid = 1 - - return FOIRequestExtension( - extensionreasonid=extension['extensionreasonid'], - extendedduedays=extension['extendedduedays'], - extendedduedate=extension['extendedduedate'], - decisiondate=decisiondate, - approvednoofdays=approvednoofdays, - extensionstatusid=extensionstatusid, - version=1, - isactive=True, - foiministryrequest_id=ministryrequestid, - foiministryrequestversion_id=ministryrequestversion, - created_at=createdat, - createdby=createuserid) - - # This is used for edit/approve/deny extension - # Edit can be normal edit like Pending -> Pending, Approved -> Approved, Denied -> Denied - # or it can be complex edit like Pending -> Approved/Denied, Approved -> Pending/Denied, Denied -> Approved/Pending - # if Pending -> Approved then the due date needs to be updated (new ministry version will get created), documents need to be mapped (if any) - # if Approved -> Pending/Denied then, due date needs to be reverted back (new ministry version will get created), documents need to be deleted (if any) - # any new ministry version created will create a new entry in FOIRequestExtensions, FOIMinistryDocuments (if any), FOIRequestExtensionDocumentsMapping (if any) tables - def createrequestextensionversion(self, foirequestid, ministryrequestid, extensionid, extensionschema, userid, username): - updatedduedate = None - ministryversion = self.__getversionforrequest(ministryrequestid) - extension = FOIRequestExtension.getextension(extensionid) - extensionversion = extension['version'] - prevstatus = extension["extensionstatusid"] - currentstatus = extensionschema["extensionstatusid"] - isstatuschangedfromapproved = self.__isstatuschangedfromapproved(prevstatus, currentstatus) - if isstatuschangedfromapproved == True: - # gets the latest approved request - approvedextension = self.getlatestapprovedrequest(extensionid, ministryrequestid, ministryversion) - # gets the latest approved due date if any else gets the original due date - updatedduedate = self.getlatestapprovedduedate(prevstatus, ministryrequestid, approvedextension) - - isdeletedocument = self.__isdeletedocument(isstatuschangedfromapproved, extensionid, extensionversion) - if isdeletedocument == True: - self.deletedocuments(extensionid, extensionversion, ministryrequestid, userid) - - #copyextension has the updated extension with data passed from FE with the new version of extension - updatedextension = self.__copyextensionproperties(extension, extensionschema, extensionversion) - # if current state is approved then gets the current extended due date - extendedduedate = self.getextendedduedate(updatedextension) - - extensionresult = FOIRequestExtension.createextensionversion(ministryrequestid, ministryversion, updatedextension, userid) - # Post event for system generated comments - eventservice().posteventforextension(ministryrequestid, extensionid, userid, username, "modify") - # save documents if it is part of current extension (update to the ministrydocuments table and extensiondocumentmapping table) - if 'documents' in updatedextension and updatedextension['documents'] and updatedextension['extensionstatusid'] != 1: - self.saveextensiondocument(updatedextension['documents'], ministryrequestid, userid, extensionid) - - # updates the duedate to extendedduedate or updatedduedate - # new ministry, extension, extensionmapping and document version gets created - if extensionresult.success == True and (isstatuschangedfromapproved == True or updatedextension['extensionstatusid'] == 2): - ministryrequestschema = { - "duedate": extendedduedate if extendedduedate else updatedduedate - } - requestservice().saveministryrequestversion(ministryrequestschema, foirequestid, ministryrequestid, userid) - - version = self.__getversionforrequest(ministryrequestid) - FOIRequestExtension.disableoldversions(version,ministryrequestid, userid) - - newduedate = \ - ministryrequestschema['duedate'] \ - if isinstance(ministryrequestschema['duedate'], str) \ - else self.__formatdate(ministryrequestschema['duedate'], self.otherdateformat) - - extensionresult.args = (*extensionresult.args, newduedate) - return extensionresult - - def deleterequestextension(self, requestid, ministryrequestid, extensionid, userid): - return self.createrequestextensionversionfordelete(requestid, ministryrequestid, extensionid, userid) - - # This is used for delete extension - # soft delete of extension and related documents - # due date reverted back to the prev approved due date - def createrequestextensionversionfordelete(self, requestid, ministryrequestid, extensionid, userid): - ministryversion = self.__getversionforrequest(ministryrequestid) - extension = FOIRequestExtension.getextension(extensionid) - prevstatus = extension["extensionstatusid"] - extensionversion = extension['version'] - - # this will be true if any document is attched to the extension - isdeletedocument = self.__isdeletedocument(True, extensionid, extensionversion) - - # gets the latest approvedextension if any - approvedextension = self.getlatestapprovedrequest(extensionid, ministryrequestid, ministryversion) - # gets the latest approved due date if any else gets the original due date - updatedduedate = self.getlatestapprovedduedate(prevstatus, ministryrequestid, approvedextension) - - - #copyextension has the updated extension with soft delete(isactive: False) with the new version of extension - # updatedextension = self.__copyextensionproperties(extension, extensionschema, extensionversion) - # this will create a new version of extension with isactive = False - # extensionresult = FOIRequestExtension.createextensionversion(ministryrequestid, ministryversion, updatedextension, userid) - #LATEST UPDATE :- updates existing isactive field to false if delete performed & no new version will be created for delete. - extensionresult = FOIRequestExtension.disableextension(extension["foirequestextensionid"], userid) - # once soft deleted, revert back the due date to prev due date - # creates a new version of ministry request, extension, extensiondocuments(if any) and documents(if any) - if extensionresult.success == True and prevstatus == 2: - ministryrequestschema = { - "duedate": updatedduedate - } - requestservice().saveministryrequestversion(ministryrequestschema, requestid, ministryrequestid, userid) - version = self.__getversionforrequest(ministryrequestid) - #Set isactive:false for extensions with previous ministry request version - FOIRequestExtension.disableoldversions(version,ministryrequestid, userid) - ## return due date - - newduedate = \ - ministryrequestschema['duedate'] \ - if isinstance(ministryrequestschema['duedate'], str) \ - else self.__formatdate(ministryrequestschema['duedate'], self.otherdateformat) - - extensionresult.args = (*extensionresult.args, newduedate) - # soft delete the documents attached to the extension - if extensionresult.success == True and isdeletedocument == True: - self.deletedocuments(extensionid, extensionversion, ministryrequestid, userid) - - return extensionresult - - def __isstatuschangedfromapproved(self, prevstatus, currentstatus): - if prevstatus == 2 and currentstatus != prevstatus: - return True - - def getduedatetoupdate(self, extension, ministryrequestid, updatedextension, approvedextension): - # gets the latest approved due date if any else gets the original due date - updatedduedate = self.getlatestapprovedduedate(extension, ministryrequestid, approvedextension) - # if current state is approved then gets the current extended due date - extendedduedate = self.getextendedduedate(updatedextension) - return extendedduedate if extendedduedate else updatedduedate - - def saveextensiondocument(self, extensiondocuments, ministryrequestid, userid, extensionid): - documents = [] - documentids = self.__savedocumentversion(ministryrequestid, extensiondocuments, userid) - for documentid in documentids: - documents.append(FOIMinistryRequestDocument().getdocument(documentid)) - self.saveextensiondocumentversion(extensionid, documents, userid) - - def __isdeletedocument(self, isstatuschangedfromapproved, extensionid, extensionversion): - isdeletedocument = False - documents = self.__getextensiondocuments(extensionid, extensionversion) - # 1. if prev status is Approved and current status is Pending or Denied - # 2. Prev extension has documents - # if any of the above condition is true then deletedocuments - if isstatuschangedfromapproved == True or documents: - isdeletedocument = True - return isdeletedocument - - def deletedocuments(self,extensionid, extensionversion, ministryrequestid, userid): - documents = self.__getextensiondocuments(extensionid, extensionversion) - for document in documents: - documentservice().deleterequestdocument(ministryrequestid, document["foiministrydocumentid"], userid, "ministryrequest") - - def getextendedduedate(self, extensionschema): - extensionreason = extensionreasonservice().getextensionreasonbyid(extensionschema['extensionreasonid']) - # if status is Approved or reason is Public Body then directly take the extendedduedate - if ('extensionstatusid' in extensionschema and extensionschema['extensionstatusid'] == 2) or extensionreason['extensiontype'] == ExtensionType.publicbody.value: - return extensionschema['extendedduedate'] - - def getlatestapprovedrequest(self, extensionid, ministryrequestid, ministryversion): - return FOIRequestExtension().getlatestapprovedextension(extensionid, ministryrequestid, ministryversion) - - def getlatestapprovedduedate(self, prevstatus, ministryrequestid, approvedextension): - if approvedextension and len(approvedextension) != 0: - return approvedextension['extendedduedate'] - # if Prev extension status was Approved and no approved extension in FOIRequestExtension table then get the original DueDate from FOIMinisrtRequests table - elif prevstatus == 2 and not approvedextension: - duedate = FOIMinistryRequest.getrequestoriginalduedate(ministryrequestid) - return duedate - #if current and prev status is Pending or Denied - else: - return None - - def saveextensiondocumentversion(self, extensionid, documents, userid): - extensionversion = self.__getextensionversion(extensionid) - if documents: - return FOIRequestExtensionDocumentMapping.saveextensiondocument(extensionid, documents, extensionversion, userid) - - - def __getextensionversion(self, extensionid): - return FOIRequestExtension().getversionforextension(extensionid) - - def __createextensionobject(self, requestextension, documents, extensionreason): - - decisiondate =requestextension['decisiondate'] if 'decisiondate' in requestextension else None - approvednoofdays = requestextension['approvednoofdays'] if 'approvednoofdays' in requestextension else None - extension = { - "foirequestextensionid": requestextension["foirequestextensionid"], - "extensionreasonid": requestextension["extensionreasonid"], - "extensionstatusid": requestextension["extensionstatusid"], - "extendedduedays": requestextension["extendedduedays"], - "extendedduedate": requestextension["extendedduedate"], - "extensiontype": extensionreason["extensiontype"], - "approvednoofdays": approvednoofdays, - "documents": documents - } - if requestextension["extensionstatusid"] == 2: - extension["approveddate"] = decisiondate - elif requestextension["extensionstatusid"] == 3: - extension["denieddate"] = decisiondate - return extension - - def __getextensiondocuments(self, extensionid, extensionversion): - return FOIRequestExtensionDocumentMapping().getextensiondocuments(extensionid, extensionversion) - - def __getextensiondocumentsinfo(self, extensiondocuments): - reqdocuments = [] - for extensiondocument in extensiondocuments: - document = FOIMinistryRequestDocument().getdocument(extensiondocument["foiministrydocumentid"]) - if document["isactive"] == True: - reqdocuments.append({"foiministrydocumentid": document["foiministrydocumentid"], "filename": document["filename"], "documentpath": document["documentpath"], "category": document["category"]}) - return reqdocuments - - def __savedocumentversion(self, ministryrequestid, extensiondocumentschema, userid): - documentids = [] - for document in extensiondocumentschema: - if 'foiministrydocumentid' in document: - documentid = document['foiministrydocumentid'] - else: - documentid = 0 - documentresult = documentservice().createministrydocumentversion(ministryrequestid, documentid, document, userid) - documentids.append(documentresult.identifier) - return documentids - - def __copyextensionproperties(self, copyextension, extensionschema, version): - copyextension['version'] = version +1 - copyextension['extensionreasonid'] = extensionschema['extensionreasonid'] if 'extensionreasonid' in extensionschema else copyextension['extensionreasonid'] - - ispublicbodyextension = self.__ispublicbodyextension(copyextension['extensionreasonid']) - if ispublicbodyextension == True: - extensionstatusid = 2 - else: - extensionstatusid = extensionschema['extensionstatusid'] if 'extensionstatusid' in extensionschema else copyextension['extensionstatusid'] - copyextension['extensionstatusid'] = extensionstatusid - copyextension['extendedduedays'] = extensionschema['extendedduedays'] if 'extendedduedays' in extensionschema else copyextension['extendedduedays'] - copyextension['extendedduedate'] = extensionschema['extendedduedate'] if 'extendedduedate' in extensionschema else copyextension['extendedduedate'] - approveddate = extensionschema['approveddate'] if 'approveddate' in extensionschema else copyextension['decisiondate'] - denieddate = extensionschema['denieddate'] if 'denieddate' in extensionschema else copyextension['decisiondate'] - decisiondate = approveddate if extensionstatusid == 2 else denieddate - copyextension['decisiondate'] = decisiondate - copyextension['approvednoofdays'] = extensionschema['approvednoofdays'] if 'approvednoofdays' in extensionschema else copyextension['approvednoofdays'] - - copyextension['documents'] = extensionschema['documents'] if 'documents' in extensionschema else None - copyextension['isactive'] = extensionschema['isactive'] if 'isactive' in extensionschema else True - copyextension['created_at'] = extensionschema['created_at'] if 'created_at' in extensionschema else None - copyextension['createdby'] = extensionschema['createdby'] if 'createdby' in extensionschema else None - return copyextension - - def __getversionforrequest(self, requestid): - """ Returns the active version of the request id based on type. - """ - return FOIMinistryRequest.getversionforrequest(requestid)[0] - - def __formatdate(self, datevalue, format): - return datevalue.strftime(format) if datevalue is not None else None - - def getrequestextensionscount(self, requestid): - extensionrecordscount = FOIRequestExtension.getextensionscount(requestid) - return extensionrecordscount - - diff --git a/historical-search-api/request_api/services/external/axissyncservice.py b/historical-search-api/request_api/services/external/axissyncservice.py deleted file mode 100644 index 2d3e10308..000000000 --- a/historical-search-api/request_api/services/external/axissyncservice.py +++ /dev/null @@ -1,76 +0,0 @@ -import requests -import os -from enum import Enum -from request_api.services.programareaservice import programareaservice -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -from request_api.models.default_method_result import DefaultMethodResult -import more_itertools -from datetime import datetime as datetime2 -from request_api.services.external.keycloakadminservice import KeycloakAdminService -from flask import current_app -import json -class axissyncservice: - - AXIS_BASE_URL = os.getenv('AXIS_API_URL', None) - DEFAULT_SYNC_BATCHSIZE = 250 - AXIS_SYNC_BATCHSIZE = int(os.getenv('AXIS_SYNC_BATCHSIZE')) if os.getenv('AXIS_SYNC_BATCHSIZE') not in (None,'') else DEFAULT_SYNC_BATCHSIZE - - def syncpagecounts(self, axispgmrequests): - for entry in axispgmrequests: - self.__syncpagecounts(entry["bcgovcode"], entry["requesttype"]) - return DefaultMethodResult(True,'Batch execution completed', axispgmrequests) - - def __syncpagecounts(self, bcgovcode, requesttype): - programeara = programareaservice().getprogramareabyiaocode(bcgovcode) - requests = FOIMinistryRequest.getrequest_by_pgmarea_type(programeara['programareaid'], requesttype) - for batch in list(more_itertools.batched(requests, self.AXIS_SYNC_BATCHSIZE)): - batchrequest = list(batch) - axisids = self.__getaxisids(batchrequest) - #Fetch pagecount from axis : Begin - axis_pageinfo = self.axis_getpageinfo(axisids) - #Fetch pagecount from axis : End - if axis_pageinfo != {}: - response = FOIMinistryRequest.bulk_update_axispagecount(self.updatepagecount(batchrequest, axis_pageinfo)) - if response.success == False: - print("batch update failed for ids=", axisids) - else: - print("axis page response is empty for ids=", axisids) - return DefaultMethodResult(True,'Batch execution completed for bcgovcode=%s | requesttype=%s', bcgovcode, requesttype) - - - def axis_getpageinfo(self, axis_payload): - try: - if self.AXIS_BASE_URL not in (None,''): - access_token = KeycloakAdminService().get_token() - axis_page_endpoint = f'{self.AXIS_BASE_URL}/api/requestspagecount' - response = requests.post( - axis_page_endpoint, - headers={ - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json' - }, - timeout=current_app.config.get('CONNECT_TIMEOUT'), - data=json.dumps(axis_payload) - ) - response.raise_for_status() - if response.status_code == 200: - return response.json() - return {} - except Exception as ex: - print('Exception occured in fetching page details', ex) - return {} - - def updatepagecount(self, requests, axisresponse): - for entry in requests: - axisrequestid = entry["axisrequestid"] - entry["updatedby"] = 'System' - entry["updated_at"] = datetime2.now() - entry["axispagecount"] = axisresponse[axisrequestid] if axisrequestid in axisresponse else entry["axispagecount"] - entry["axislanpagecount"] = axisresponse[axisrequestid] if axisrequestid in axisresponse else entry["axislanpagecount"] - return requests - - - def __getaxisids(self, requests): - return [entry['axisrequestid'] for entry in requests] - - diff --git a/historical-search-api/request_api/services/external/bpmservice.py b/historical-search-api/request_api/services/external/bpmservice.py deleted file mode 100644 index 1ef074eb0..000000000 --- a/historical-search-api/request_api/services/external/bpmservice.py +++ /dev/null @@ -1,196 +0,0 @@ -import requests -import os -import json -from enum import Enum - -from request_api.schemas.external.bpmschema import MessageSchema, VariableSchema, VariableMessageSchema -from request_api.services.external.camundaservice import camundaservice, VariableType - -""" -This class is reserved for workflow services integration. -Supported operations: claim - -__author__ = "sumathi.thirumani@aot-technologies.com" - -""" -class bpmservice(camundaservice): - - def createinstance(self, messagequeue, message, token=None): - if self.bpmengineresturl is not None: - _variables = {"variables":{}} - for key in message: - _variabletype = VariableType.Integer.value if key in ["id"] else VariableType.String.value - _variables["variables"][key] = {"type" : _variabletype, "value": message[key]} - variableschema = VariableMessageSchema().dump(_variables) - createresponce = requests.post(self._getUrl_(None,self._geProcessDefinitionKey_(messagequeue)), data=json.dumps(variableschema), headers = self._getHeaders_(token)) - if createresponce.ok: - _createresponce = json.loads(createresponce.content) - return _createresponce["id"] - return None - - def getinstancevariables(self, instanceid, token=None): - if self.bpmengineresturl is not None: - response = requests.get(self.bpmengineresturl+"/process-instance/"+str(instanceid)+"/variables", headers = self._getHeaders_(token)) - return json.loads(response.content) if response.ok else None - return None - - def searchinstancebyvariable(self, definitionkey, searchby, token=None): - if self.bpmengineresturl is not None: - searchschema = {"processDefinitionKey": definitionkey, - "variables": searchby, - "sortBy":"definitionId","sortOrder":"desc", - "maxResults":1 - } - searchresponse = requests.post(self.bpmengineresturl+"/process-instance",data=json.dumps(searchschema), headers = self._getHeaders_(token)) - if searchresponse.ok: - _search_content = json.loads(searchresponse.content) - if _search_content not in ([], None) and len(_search_content) > 0: - return self.searchprocessinstance(str(_search_content[0]["id"])) - return None - return None - - def searchprocessinstance(self, pid, token=None): - if self.bpmengineresturl is not None: - if pid not in (None, ""): - idresponse = requests.get(self.bpmengineresturl+"/process-instance/"+pid, headers = self._getHeaders_(token)) - if idresponse.ok: - return pid - return None - return None - - def unopenedsave(self,processinstanceid, userid, messagetype, token=None): - if self.bpmengineresturl is not None: - messageschema = MessageSchema().dump({"processInstanceId": processinstanceid, - "messageName": messagetype, - "processVariables":{ - "assignedTo": VariableSchema().dump({"type" : VariableType.String.value, "value": userid}) - } - }) - return self.__post_message(messagetype, messageschema, token) - else: - return - - - def unopenedcomplete(self,processinstanceid, data, messagetype, token=None): - if self.bpmengineresturl is not None: - messageschema = MessageSchema().dump({"processInstanceId": processinstanceid, - "messageName": messagetype, - "processVariables":{ - "foiRequestMetaData": VariableSchema().dump({"data" : VariableType.String.value, "value": data}) - } - }) - return self.__post_message(messagetype, messageschema, token) - else: - return - - """" - def opened(self, filenumber, groupname, userid, messagetype, token=None): - if self.bpmengineresturl is not None: - messageschema = MessageSchema().dump({"messageName": messagetype, - "localCorrelationKeys":{ - "id": VariableSchema().dump({"type" : VariableType.String.value, "value": filenumber}) - }, - "processVariables":{ - "filenumber": VariableSchema().dump({"type" : VariableType.String.value, "value": filenumber}), - "assignedGroup": VariableSchema().dump({"type" : VariableType.String.value, "value": groupname}), - "assignedTo": VariableSchema().dump({"type" : VariableType.String.value, "value": userid}) - } - }) - return requests.post(self._getUrl_(messagetype), data=json.dumps(messageschema), headers = self._getHeaders_(token)) - else: - return - """ - - def openedcomplete(self, wfinstanceid, filenumber, data, messagetype, token=None): - if self.bpmengineresturl is not None: - messageschema = MessageSchema().dump({"messageName": messagetype, - "processInstanceId": wfinstanceid, - "localCorrelationKeys":{ - "id": VariableSchema().dump({"type" : VariableType.String.value, "value": filenumber}) - }, - "processVariables":{ - "foiRequestMetaData": VariableSchema().dump({"data" : VariableType.String.value, "value": data})} - }) - return self.__post_message(messagetype, messageschema, token) - else: - return - - def feeevent(self,axisrequestid, data, paymentstatus, token=None): - if self.bpmengineresturl is not None: - messageschema = MessageSchema().dump({"messageName": MessageType.managepayment.value, - "correlationKeys":{ - "axisRequestId": VariableSchema().dump({"type" : VariableType.String.value, "value": axisrequestid}) - }, - "processVariables":{ - "foiRequestMetaData": VariableSchema().dump({"data" : VariableType.String.value, "value": data}), - "paymentstatus": VariableSchema().dump({"type" : VariableType.String.value, "value": paymentstatus})} - }) - return self.__post_message(MessageType.managepayment.value, messageschema, token) - else: - return - - def correspondanceevent(self,wfinstanceid, filenumber, data, token=None): - if self.bpmengineresturl is not None: - messageschema = MessageSchema().dump({"messageName": MessageType.iaocorrenspodence.value, - "processInstanceId": wfinstanceid, - "localCorrelationKeys":{ - "id": VariableSchema().dump({"type" : VariableType.String.value, "value": filenumber}) - }, - "processVariables":{ - "foiRequestMetaData": VariableSchema().dump({"data" : VariableType.String.value, "value": data})} - }) - return self.__post_message(MessageType.iaocorrenspodence.value, messageschema, token) - else: - return - - def reopenevent(self,processinstanceid, data, messagetype, token=None): - return self.unopenedcomplete(processinstanceid, data, messagetype, token) - - def __post_message(self, messagetype, messageschema, token=None): - return requests.post(self._getUrl_(messagetype), data=json.dumps(messageschema), headers = self._getHeaders_(token)) - - def _getUrl_(self, messagetype, definitionkey=None): - if messagetype is not None: - return self.bpmengineresturl+"/message" - elif definitionkey is not None: - return self.bpmengineresturl+"/process-definition/key/"+definitionkey+"/start" - return self.bpmengineresturl - - def _geProcessDefinitionKey_(self, messagequeue): - if messagequeue == "foi-rawrequest": - return "foi-request" - return None - - def _getserviceaccounttoken_(self): - auth_response = requests.post(self.bpmtokenurl, auth=(self.bpmclientid, self.bpmclientsecret), headers={ - 'Content-Type': 'application/x-www-form-urlencoded'}, data='grant_type=client_credentials') - return auth_response.json().get('access_token') - - def _getHeaders_(self, token): - """Generate headers.""" - if token is None: - token = self._getserviceaccounttoken_(); - return { - "Authorization": "Bearer " + token, - "Content-Type": "application/json", - } - -class MessageType(Enum): - intakeclaim = "foi-intake-assignment" - intakecomplete = "foi-intake-complete" - intakereopen = "foi-intake-reopen" - iaoopenclaim = "foi-iao-open-assignment" - iaoopencomplete = "foi-iao-open-complete" - iaoclaim = "foi-iao-assignment" - iaocomplete = "foi-iao-complete" - iaoreopen = "foi-iao-reopen" - ministryclaim = "foi-ministry-assignment" - ministrycomplete = "foi-ministry-complete" - feepayment = "foi-fee-payment" - managepayment = "foi-manage-payment" - iaocorrenspodence = "foi-iao-correnspodence" - -class ProcessDefinitionKey(Enum): - rawrequest = "foi-request" - ministryrequest = "foi-request-processing" - \ No newline at end of file diff --git a/historical-search-api/request_api/services/external/camundaservice.py b/historical-search-api/request_api/services/external/camundaservice.py deleted file mode 100644 index be25070ac..000000000 --- a/historical-search-api/request_api/services/external/camundaservice.py +++ /dev/null @@ -1,38 +0,0 @@ -import requests -import os -from enum import Enum - -""" -This class is reserved for workflow services integration. -Supported operations: claim - -__author__ = "sumathi.thirumani@aot-technologies.com" - -""" -class camundaservice: - - bpmengineresturl = os.getenv('BPM_ENGINE_REST_URL') - bpmtokenurl = os.getenv("BPM_TOKEN_URL") - bpmclientid = os.getenv("BPM_CLIENT_ID") - bpmclientsecret = os.getenv("BPM_CLIENT_SECRET") - bpmgranttype = os.getenv("BPM_GRANT_TYPE") - - - def _getserviceaccounttoken_(self): - auth_response = requests.post(self.bpmtokenurl, auth=(self.bpmclientid, self.bpmclientsecret), headers={ - 'Content-Type': 'application/x-www-form-urlencoded'}, data='grant_type=client_credentials') - return auth_response.json().get('access_token') - - - def _getheaders_(self, token): - """Generate headers.""" - if token is None: - token = self._getserviceaccounttoken_(); - return { - "Authorization": "Bearer " + token, - "Content-Type": "application/json", - } - -class VariableType(Enum): - String = "String" - Integer = "Integer" \ No newline at end of file diff --git a/historical-search-api/request_api/services/external/eventqueueservice.py b/historical-search-api/request_api/services/external/eventqueueservice.py deleted file mode 100644 index 6b11cec88..000000000 --- a/historical-search-api/request_api/services/external/eventqueueservice.py +++ /dev/null @@ -1,24 +0,0 @@ -import os -from walrus import Database -from request_api.models.default_method_result import DefaultMethodResult -from request_api.exceptions import BusinessException -import logging - -class eventqueueservice: - """This class is reserved for integration with event queue (currently redis streams). - """ - host = os.getenv('EVENT_QUEUE_HOST') - port = os.getenv('EVENT_QUEUE_PORT') - password = os.getenv('EVENT_QUEUE_PASSWORD') - - db = Database(host=host, port=port, db=0,password=password) - - def add(self, streamkey, payload): - try: - stream = self.db.Stream(streamkey) - msgid = stream.add(payload, id="*") - return DefaultMethodResult(True,'Added to stream',msgid.decode('utf-8')) - except Exception as err: - logging.error("Error in contacting Redis Stream") - logging.error(err) - return DefaultMethodResult(False,err,-1) \ No newline at end of file diff --git a/historical-search-api/request_api/services/external/storageservice.py b/historical-search-api/request_api/services/external/storageservice.py deleted file mode 100644 index e0c45b018..000000000 --- a/historical-search-api/request_api/services/external/storageservice.py +++ /dev/null @@ -1,255 +0,0 @@ -from unicodedata import category -from request_api.schemas.foirequestsformslist import FOIRequestsFormsList -import requests -from aws_requests_auth.aws_auth import AWSRequestsAuth -import os -import uuid -import mimetypes -import logging - -from request_api.models.DocumentPathMapper import DocumentPathMapper -from request_api.utils.enums import DocumentPathMapperCategory - -import boto3 -from botocore.exceptions import ClientError -from botocore.config import Config - - -formsbucket = os.getenv('OSS_S3_FORMS_BUCKET') -accesskey = os.getenv('OSS_S3_FORMS_ACCESS_KEY_ID') -secretkey = os.getenv('OSS_S3_FORMS_SECRET_ACCESS_KEY') -s3host = os.getenv('OSS_S3_HOST') -s3region = os.getenv('OSS_S3_REGION') -s3service = os.getenv('OSS_S3_SERVICE') -s3chunksize = os.getenv('OSS_S3_CHUNK_SIZE') -class storageservice: - """This class is reserved for S3 storage services integration. - """ - accesskey = os.getenv('OSS_S3_FORMS_ACCESS_KEY_ID') - secretkey = os.getenv('OSS_S3_FORMS_SECRET_ACCESS_KEY') - recordsaccesskey = os.getenv('OSS_S3_RECORDS_ACCESS_KEY_ID') - recordssecretkey = os.getenv('OSS_S3_RECORDS_SECRET_ACCESS_KEY') - s3host = os.getenv('OSS_S3_HOST') - s3region = os.getenv('OSS_S3_REGION') - s3service = os.getenv('OSS_S3_SERVICE') - s3environment = os.getenv('OSS_S3_ENVIRONMENT') - s3timeout = 3600 - - def uploadbytes(self, filename, bytes, ministrycode, requestnumber): - try: - auth = AWSRequestsAuth(aws_access_key=accesskey, - aws_secret_access_key=secretkey, - aws_host=s3host, - aws_region=s3region, - aws_service=s3service) - - s3uri = 'https://{0}/{1}/{2}/{3}/{4}'.format(s3host,formsbucket, ministrycode, requestnumber, filename) - response = requests.put(s3uri, data=None, auth=auth) - header = { - 'X-Amz-Date': response.request.headers['x-amz-date'], - 'Authorization': response.request.headers['Authorization'], - 'Content-Type': mimetypes.MimeTypes().guess_type(filename)[0] - } - - #upload to S3 - requests.put(s3uri, data=bytes, headers=header) - attachmentobj = {"success": True, 'filename': filename, 'documentpath': s3uri} - except Exception as ex: - logging.error(ex) - attachmentobj = {"success": False, 'filename': filename, 'documentpath': None} - return attachmentobj - - def upload(self, attachment): - docpathmapper = DocumentPathMapper().getdocumentpath("Attachments") - formsbucket = docpathmapper['bucket'] - - if(self.accesskey is None or self.secretkey is None or self.s3host is None or formsbucket is None): - raise ValueError('accesskey is None or secretkey is None or S3 host is None or formsbucket is None') - - foirequestform = FOIRequestsFormsList().load(attachment) - ministrycode = foirequestform.get('ministrycode') - requestnumber = foirequestform.get('requestnumber') - filestatustransition = foirequestform.get('filestatustransition') - filename = foirequestform.get('filename') - filenamesplittext = os.path.splitext(filename) - uniquefilename = '{0}{1}'.format(uuid.uuid4(),filenamesplittext[1].lower()) - - auth = AWSRequestsAuth(aws_access_key=self.accesskey, - aws_secret_access_key=self.secretkey, - aws_host=self.s3host, - aws_region=self.s3region, - aws_service=self.s3service) - - s3uri = 'https://{0}/{1}/{2}/{3}/{4}/{5}'.format(self.s3host,formsbucket,ministrycode,requestnumber,filestatustransition,uniquefilename) - response = requests.put(s3uri, data=None, auth=auth) - - header = { - 'X-Amz-Date': response.request.headers['x-amz-date'], - 'Authorization': response.request.headers['Authorization'], - 'Content-Type': mimetypes.MimeTypes().guess_type(filename)[0] - } - - #upload to S3 - requests.put(s3uri, data=attachment['file'], headers=header) - - attachmentobj = {'filename': filename, 'documentpath': s3uri, 'category': filestatustransition} - return attachmentobj - - def bulk_upload(self, requestfilejson, category): - - 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 - - for file in requestfilejson: - foirequestform = FOIRequestsFormsList().load(file) - ministrycode = foirequestform.get('ministrycode') - requestnumber = foirequestform.get('requestnumber') - filestatustransition = foirequestform.get('filestatustransition') - filename = foirequestform.get('filename') - s3sourceuri = foirequestform.get('s3sourceuri') - filenamesplittext = os.path.splitext(filename) - uniquefilename = '{0}{1}'.format(uuid.uuid4(),filenamesplittext[1].lower()) - docpathmapper = DocumentPathMapper().getdocumentpath(category,ministrycode) - formsbucket = docpathmapper['bucket'] - auth = AWSRequestsAuth(aws_access_key=docpathmapper['attributes']['s3accesskey'], - aws_secret_access_key=docpathmapper['attributes']['s3secretkey'], - aws_host=self.s3host, - aws_region=self.s3region, - aws_service=self.s3service) - - s3uri = s3sourceuri if s3sourceuri is not None else 'https://{0}/{1}/{2}/{3}/{4}/{5}'.format(self.s3host, formsbucket,ministrycode,requestnumber,filestatustransition,uniquefilename) - response = requests.put(s3uri,data=None,auth=auth) if s3sourceuri is None else requests.get(s3uri,auth=auth) - - file['filepath']=s3uri - file['authheader']=response.request.headers['Authorization'] - file['amzdate']=response.request.headers['x-amz-date'] - file['uniquefilename']=uniquefilename if s3sourceuri is None else '' - 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'] - s3client = self.__get_s3client(category, docpathmapper) - filename, file_extension = os.path.splitext(filepath) - response = s3client.generate_presigned_url( - ClientMethod='get_object', - Params= {'Bucket': formsbucket, 'Key': '{0}'.format(filepath),'ResponseContentType': '{0}/{1}'.format('image' if file_extension in ['.png','.jpg','.jpeg','.gif'] else 'application',file_extension.replace('.',''))}, - ExpiresIn=3600,HttpMethod='GET' - ) - return response - - def bulk_upload_s3_presigned(self, ministryrequestid, requestfilejson, category, bcgovcode=None): - docpathmapper = DocumentPathMapper().getdocumentpath(category, bcgovcode) - formsbucket = docpathmapper['bucket'] - s3client = self.__get_s3client(category, docpathmapper) - for file in requestfilejson: - foirequestform = FOIRequestsFormsList().load(file) - ministrycode = foirequestform.get('ministrycode') - requestnumber = foirequestform.get('requestnumber') - filestatustransition = foirequestform.get('filestatustransition') - filename = foirequestform.get('filename') - filenamesplittext = os.path.splitext(filename) - uniquefilename = '/'.join(file.get('filepath', "").split('/')[5:]) or '{0}{1}'.format(uuid.uuid4(),filenamesplittext[1].lower()) - filepath = '/'.join(file.get('filepath', "").split('/')[4:]) or self.__getfilepath(category,ministrycode,requestnumber,filestatustransition,uniquefilename) - if file.get('multipart', False): - response = s3client.create_multipart_upload(Bucket=formsbucket, Key='{0}'.format(filepath)) - max_size = int(s3chunksize) - uploadid = response['UploadId'] - file['filepaths'] = [] - for part in range(1, file.get("filesize")//max_size + 2): - response = s3client.generate_presigned_url( - ClientMethod='upload_part', - Params= { - 'Bucket': formsbucket, - 'Key': '{0}'.format(filepath), - 'UploadId': uploadid, - 'PartNumber': part - }, - ExpiresIn=self.s3timeout #, HttpMethod='PUT' - ) - file['filepaths'].append(response) - file['uploadid']=uploadid - else: - response = s3client.generate_presigned_url( - ClientMethod='put_object', - Params= { - 'Bucket': formsbucket, - 'Key': '{0}'.format(filepath) - }, - ExpiresIn=self.s3timeout, HttpMethod='PUT' - ) - file['filepath']=response - file['filepathdb']='https://{0}/{1}/{2}'.format(self.s3host,formsbucket,filepath) - file['uniquefilename']=uniquefilename - return requestfilejson - - def complete_upload_s3_presigned(self, requestjson, category, bcgovcode): - docpathmapper = DocumentPathMapper().getdocumentpath(category, bcgovcode) - formsbucket = docpathmapper['bucket'] - s3client = self.__get_s3client(category, docpathmapper) - return s3client.complete_multipart_upload( - Bucket=formsbucket, - Key='/'.join(requestjson.get('filepath', "").split('/')[4:]), - MultipartUpload={'Parts': requestjson.get('parts')}, - UploadId=requestjson.get('uploadid') - ) - - def is_valid_category(self, category): - categories = set(item.value.lower() for item in DocumentPathMapperCategory) - return category in categories - - def __get_s3client(self, category, docpathmapper): - return boto3.client('s3',config=Config(signature_version='s3v4'), - endpoint_url='https://{0}/'.format(self.s3host), - aws_access_key_id= docpathmapper['attributes']['s3accesskey'], - aws_secret_access_key= docpathmapper['attributes']['s3secretkey'], - region_name= self.s3region - ) - - def __getbucket(self, category, programarea=None): - docpathmapper = DocumentPathMapper().getdocumentpath(category, programarea if category.lower() == "attachments" else None) - return docpathmapper['bucket'] - - def __getfilepath(self,category,ministrycode,requestnumber,filestatustransition,uniquefilename): - if category.lower() == 'attachments': - return '{0}/{1}/{2}/{3}'.format(ministrycode,requestnumber,filestatustransition,uniquefilename) - elif category.lower() == 'records': - return '{0}/{1}'.format(requestnumber,uniquefilename) - - def download(self, s3uri): - - if(accesskey is None or secretkey is None or s3host is None or formsbucket is None): - raise ValueError('accesskey is None or secretkey is None or S3 host is None or formsbucket is None') - - auth = AWSRequestsAuth(aws_access_key=accesskey, - aws_secret_access_key=secretkey, - aws_host=s3host, - aws_region=s3region, - aws_service=s3service) - - templatefile= requests.get(s3uri, auth=auth) - return templatefile - - - def downloadtemplate(self, templatepath): - - if(accesskey is None or secretkey is None or s3host is None or formsbucket is None): - raise ValueError('accesskey is None or secretkey is None or S3 host is None or formsbucket is None') - #To DO : make the values of templatetype and templatename dynamic - s3uri = 'https://{0}/{1}{2}'.format(s3host,formsbucket,templatepath) - templatefile= self.download(s3uri) - responsehtml=templatefile.text - return responsehtml diff --git a/historical-search-api/request_api/services/fee_service.py b/historical-search-api/request_api/services/fee_service.py deleted file mode 100644 index a68a6dd31..000000000 --- a/historical-search-api/request_api/services/fee_service.py +++ /dev/null @@ -1,239 +0,0 @@ -import base64 -from datetime import date -from datetime import datetime -from typing import Dict -from urllib.parse import unquote_plus, urlencode - -import pytz -import requests -from flask import current_app - -from request_api.exceptions import BusinessException, Error -from request_api.models import FeeCode, Payment, RevenueAccount, FOIRawRequest, FOIMinistryRequest -from request_api.services.cfrfeeservice import cfrfeeservice -from request_api.utils.enums import FeeType -from .hash_service import HashService - - -class FeeService: - """ FOI Fee management service - - This service class manages all CRUD operations related to Fee - - """ - - def __init__(self, request_id: int, code: str=None, payment_id=None): - self.request_id = request_id - if payment_id: - self.payment: Payment = Payment.find_by_id(payment_id) - self.fee_code: FeeCode = FeeCode.find_by_id(self.payment.fee_code_id) - else: - self.payment = None - self.fee_code: FeeCode = FeeCode.get_fee(code=code, valid_date=date.today()) - if not self.fee_code: - raise BusinessException(Error.INVALID_INPUT) - - # If application fee, use raw request id, else use minsitry request id - if self.fee_code.code == FeeType.application.value: - self.request = FOIRawRequest.get_request(request_id) - if self.request is None: - raise BusinessException(Error.INVALID_INPUT) - else: - self.request = FOIMinistryRequest.getrequestbyministryrequestid(request_id) - if self.request is None: - raise BusinessException(Error.INVALID_INPUT) - - @staticmethod - def get_fee(code: str, quantity: int, valid_date: date): - """Return fee details.""" - fee_code: FeeCode = FeeCode.get_fee( - code=code, valid_date=valid_date - ) - if not fee_code: - raise BusinessException(Error.DATA_NOT_FOUND) - - fee_response = dict( - fee_code=code, - fee=fee_code.fee, - quantity=quantity, - total=quantity * fee_code.fee, - description=fee_code.description - ) - - return fee_response - - def init_payment(self, pay_request: Dict): - return_route = pay_request.get('return_route') - quantity = int(pay_request.get('quantity', 1)) - if self.fee_code.code == FeeType.processing.value: - total = self._get_cfr_fee(self.request_id, pay_request) - else: - total = quantity * self.fee_code.fee - self.payment = Payment( - fee_code_id=self.fee_code.fee_code_id, - quantity=quantity, - total=total, - status='PENDING', - request_id=self.request_id - ).flush() - - self.payment.paybc_url = self._get_paybc_url(self.fee_code, return_route) - self.payment.transaction_number = self._get_transaction_number() - self.payment.commit() - pay_response = self._dump() - return pay_response - - def complete_payment(self, pay_response: Dict): - """Complete payment.""" - response_url = pay_response.get('response_url') - current_app.logger.debug('response_url : %s', response_url) - - if self.payment.status == 'PAID' or not response_url: - raise BusinessException(Error.INVALID_INPUT) - - self.payment.response_url = response_url - self.payment.commit() - - parsed_args = HashService.parse_url_params(response_url) - - # Validate transaction number - if self.payment.transaction_number != parsed_args.get('pbcTxnNumber'): - raise BusinessException(Error.INVALID_INPUT) - - # Check if trnApproved is 1=Success, 0=Declined - trn_approved: bool = parsed_args.get('trnApproved') == '1' - if trn_approved: - self._validate_hash(parsed_args, response_url) - - # Add paybc api call to verify - # handle duplicate payment response. - paybc_status = None - if trn_approved or parsed_args.get('trnNumber', '').upper() == 'DUPLICATE PAYMENT': - paybc_status = self._validate_with_paybc(trn_approved) - - self.payment.order_id = parsed_args.get('trnOrderId') - self.payment.completed_on = datetime.now() - self.payment.status = 'PAID' if paybc_status == 'PAID' else parsed_args.get('messageText').upper() - self.payment.commit() - - return self._dump(), parsed_args - - def check_if_paid(self): - """Check payment.""" - - return self.payment.status == 'PAID' - - def _validate_with_paybc(self, trn_approved): - paybc_status = None - paybc_response = self.get_paybc_transaction_details() - if trn_approved and (paybc_status := paybc_response.get('paymentstatus')) != 'PAID': - raise BusinessException(Error.INVALID_INPUT) - if paybc_status == 'PAID' and self.payment.total != float(paybc_response.get('trnamount')): - raise BusinessException(Error.INVALID_INPUT) - return paybc_status - - def _validate_hash(self, parsed_args, response_url): - # validate if hashValue matches with rest of the values hashed - hash_value = parsed_args.pop('hashValue', None) - pay_response_url_without_hash = urlencode(parsed_args) - if not HashService.is_valid_checksum(pay_response_url_without_hash, hash_value): - current_app.logger.warning(f'Transaction is approved, but hash is not matching : {response_url}') - raise BusinessException(Error.INVALID_INPUT) - - def _dump(self): - pay_response = dict( - paybc_url=self.payment.paybc_url, - payment_id=self.payment.payment_id, - request_id=self.payment.request_id, - status=self.payment.status, - total=self.payment.total - ) - return pay_response - - def _get_paybc_url(self, fee_code: FeeCode, return_route): - """Return the payment system url.""" - date_val = datetime.now().astimezone(pytz.timezone(current_app.config['LEGISLATIVE_TIMEZONE'])).strftime( - '%Y-%m-%d') - if self.fee_code.code == FeeType.application.value: - base_url = current_app.config['FOI_WEB_PAY_URL'] - if self.fee_code.code == FeeType.processing.value: - base_url = current_app.config['FOI_FFA_URL'] - return_url = f"{base_url}{return_route if return_route else ''}/{self.payment.request_id}/{self.payment.payment_id}" - revenue_account: RevenueAccount = RevenueAccount.find_by_id(fee_code.revenue_account_id) - - url_params_dict = {'trnDate': date_val, - 'pbcRefNumber': current_app.config.get('PAYBC_REF_NUMBER'), - 'glDate': date_val, - 'description': 'Direct_Sale', - 'trnNumber': self._get_transaction_number(), - 'trnAmount': self.payment.total, - 'paymentMethod': 'CC', - 'redirectUri': return_url, - 'currency': 'CAD', - 'revenue': self._get_gl_coding(self.payment.total, revenue_account) - } - - url_params = urlencode(url_params_dict) - # unquote is used below so that unescaped url string can be hashed - url_params_dict['hashValue'] = HashService.encode(unquote_plus(url_params)) - encoded_query_params = urlencode(url_params_dict) # encode it again to inlcude the hash - paybc_url = current_app.config.get('PAYBC_PORTAL_URL') - return f'{paybc_url}?{encoded_query_params}' - - @staticmethod - def _get_gl_coding(total, revenue_account: RevenueAccount): - return f'1:{revenue_account.client}.{revenue_account.responsibility_centre}.' \ - f'{revenue_account.service_line}.{revenue_account.stob}.{revenue_account.project_code}' \ - f'.000000.0000' \ - f":{format(total, '.2f')}" - - def _get_transaction_number(self): - return f"{current_app.config.get('PAYBC_TXN_PREFIX')}{self.payment.payment_id:0>8}" - - def get_paybc_transaction_details(self): - # Call PAYBC web service, get access token and use it in get txn call - access_token = self.get_paybc_token().json().get('access_token') - - paybc_transaction_url: str = current_app.config.get('PAYBC_API_BASE_URL') - paybc_ref_number: str = current_app.config.get('PAYBC_REF_NUMBER') - - endpoint = f'{paybc_transaction_url}/paybc/payment/{paybc_ref_number}/{self.payment.transaction_number}' - response = requests.get( - endpoint, - headers={ - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json' - }, - timeout=current_app.config.get('CONNECT_TIMEOUT') - ) - return response.json() - - def get_paybc_token(self): - """Generate oauth token from payBC which will be used for all communication.""" - current_app.logger.debug('Getting token') - return response - - def _get_cfr_fee(self, ministry_request_id, pay_request): - if pay_request.get('retry', False): - return Payment.find_failed_transaction(pay_request['transaction_number']).total - else: - fee = cfrfeeservice().getapprovedcfrfee(ministry_request_id)['feedata']['balanceremaining'] - if pay_request.get('half', False): - return fee/2 - else: - return fee diff --git a/historical-search-api/request_api/services/foirequest/requestservicebuilder.py b/historical-search-api/request_api/services/foirequest/requestservicebuilder.py deleted file mode 100644 index 7d1ecb70f..000000000 --- a/historical-search-api/request_api/services/foirequest/requestservicebuilder.py +++ /dev/null @@ -1,197 +0,0 @@ - -from re import T -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -from request_api.models.FOIMinistryRequestDivisions import FOIMinistryRequestDivision -from request_api.models.RequestorType import RequestorType -from request_api.models.FOIRequestContactInformation import FOIRequestContactInformation -from request_api.models.FOIRequestPersonalAttributes import FOIRequestPersonalAttribute -from request_api.models.FOIRequestApplicants import FOIRequestApplicant -from request_api.models.FOIRequestApplicantMappings import FOIRequestApplicantMapping -from request_api.models.FOIRequestTeams import FOIRequestTeam -from request_api.models.FOIRequestStatus import FOIRequestStatus -from request_api.models.FOIRequestOIPC import FOIRequestOIPC - -from datetime import datetime as datetime2 -from request_api.utils.enums import MinistryTeamWithKeycloackGroup, StateName -from request_api.services.foirequest.requestserviceconfigurator import requestserviceconfigurator -from request_api.services.foirequest.requestserviceministrybuilder import requestserviceministrybuilder - - -import json -class requestservicebuilder(requestserviceconfigurator): - """ This class consolidates the helper functions for creating new foi request based on iao actions. - """ - - def createministry(self, requestschema, ministry, activeversion, userid, filenumber=None, ministryid=None): - foiministryrequest = FOIMinistryRequest() - foiministryrequest.__dict__.update(ministry) - foiministryrequest.requeststatusid = self.__getrequeststatusid(requestschema.get("requeststatuslabel")) - foiministryrequest.requeststatuslabel = requestschema.get("requeststatuslabel") - foiministryrequest.isactive = True - foiministryrequest.axisrequestid = requestschema.get("axisRequestId") - foiministryrequest.axissyncdate = requestschema.get("axisSyncDate") - foiministryrequest.axispagecount = requestschema.get("axispagecount") - foiministryrequest.recordspagecount = requestschema.get("recordspagecount") - foiministryrequest.filenumber = self.generatefilenumber(ministry["code"], requestschema.get("foirawrequestid")) if filenumber is None else filenumber - foiministryrequest.programareaid = self.getvalueof("programArea",ministry["code"]) - 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") - foiministryrequest.estimatedpagecount = requestschema.get("estimatedpagecount") - foiministryrequest.estimatedtaggedpagecount = requestschema.get("estimatedtaggedpagecount") - if requestschema.get("isoipcreview") is not None and requestschema.get("isoipcreview") != "": - foiministryrequest.isoipcreview = requestschema.get("isoipcreview") - foiministryrequest.oipcreviews = self.prepareoipc(requestschema, ministryid, activeversion, userid) - - if requestschema.get("cfrDueDate") is not None and requestschema.get("cfrDueDate") != "": - foiministryrequest.cfrduedate = requestschema.get("cfrDueDate") - startdate = "" - if (requestschema.get("startDate") is not None): - startdate = requestschema.get("startDate") - elif (requestschema.get("requestProcessStart") is not None): - startdate = requestschema.get("requestProcessStart") - foiministryrequest.startdate = startdate - foiministryrequest.createdby = userid - requeststatuslabel = self.getpropertyvaluefromschema(requestschema, 'requeststatuslabel') - if requeststatuslabel is not None: - status = self.getstatusname(requeststatuslabel) - if self.isNotBlankorNone(requestschema,"fromDate","main") == True: - foiministryrequest.recordsearchfromdate = requestschema.get("fromDate") - if self.isNotBlankorNone(requestschema,"toDate","main") == True: - foiministryrequest.recordsearchtodate = requestschema.get("toDate") - self.__updateassignedtoandgroup(foiministryrequest, requestschema, ministry, status, filenumber, ministryid) - self.__updateministryassignedtoandgroup(foiministryrequest, requestschema, ministry, status) - - if ministryid is not None: - foiministryrequest.foiministryrequestid = ministryid - activeversion = FOIMinistryRequest.getversionforrequest(ministryid)[0]+1 - divisions = FOIMinistryRequestDivision().getdivisions(ministryid , activeversion-1) - foiministryrequest.divisions = requestserviceministrybuilder().createfoirequestdivisionfromobject(divisions, ministryid, activeversion, userid) - foiministryrequest.documents = requestserviceministrybuilder().createfoirequestdocuments(requestschema,ministryid , activeversion , userid) - foiministryrequest.extensions = requestserviceministrybuilder().createfoirequestextensions(ministryid, activeversion, userid) - if 'subjectCode' in requestschema and requestschema['subjectCode'] is not None and requestschema['subjectCode'] != '': - foiministryrequest.subjectcode = requestserviceministrybuilder().createfoirequestsubjectcode(requestschema, ministryid, activeversion, userid) - foiministryrequest.version = activeversion - foiministryrequest.closedate = self.getpropertyvaluefromschema(requestschema, 'closedate') - foiministryrequest.closereasonid = self.getpropertyvaluefromschema(requestschema, 'closereasonid') - if self.getpropertyvaluefromschema(requestschema, 'isofflinepayment') is not None: - foiministryrequest.isofflinepayment = self.getpropertyvaluefromschema(requestschema, 'isofflinepayment') - return foiministryrequest - - def __updateministryassignedtoandgroup(self, foiministryrequest, requestschema, ministry, status): - if self.__isgrouprequired(status): - foiministryrequest.assignedministrygroup = MinistryTeamWithKeycloackGroup[ministry["code"]].value - if self.isNotBlankorNone(requestschema,"assignedministrygroup","main") == True: - foiministryrequest.assignedministrygroup = requestschema.get("assignedministrygroup") - if self.isNotBlankorNone(requestschema,"assignedministryperson","main") == True and requestschema.get("reopen") != True: - foiministryrequest.assignedministryperson = requestschema.get("assignedministryperson") - requestserviceministrybuilder().createfoiassigneefromobject(requestschema.get("assignedministryperson"), requestschema.get("assignedministrypersonFirstName"), requestschema.get("assignedministrypersonMiddleName"), requestschema.get("assignedministrypersonLastName")) - else: - foiministryrequest.assignedministryperson = None - - def __updateassignedtoandgroup(self, foiministryrequest, requestschema, ministry, status, filenumber=None, ministryid=None): - foiministryrequest.assignedgroup = requestschema.get("assignedGroup") - if self.isNotBlankorNone(requestschema,"assignedTo","main") == True: - foiministryrequest.assignedto = requestschema.get("assignedTo") - requestserviceministrybuilder().createfoiassigneefromobject(requestschema.get("assignedTo"), requestschema.get("assignedToFirstName"), requestschema.get("assignedToMiddleName"), requestschema.get("assignedToLastName")) - else: - foiministryrequest.assignedto = None - - def __isgrouprequired(self,status): - if status == StateName.callforrecords.value or status == StateName.recordsreview.value or status == StateName.consult.value or status == StateName.feeestimate.value or status == StateName.ministrysignoff.value or status == StateName.response.value: - return True - else: - return False - - def __getgroupname(self, requesttype, bcgovcode): - return 'Flex Team' if requesttype == "general" else FOIRequestTeam.getdefaultprocessingteamforpersonal(bcgovcode) - - def __getrequeststatusid(self, requeststatuslabel): - state = FOIRequestStatus.getrequeststatusbylabel( - requeststatuslabel - ) - stateid = ( - state.get("requeststatusid") - if isinstance(state, dict) and state.get("requeststatusid") not in (None, "") - else "" - ) - return stateid - - def createcontactinformation(self,dataformat, name, value, contacttypes, userid): - contactinformation = FOIRequestContactInformation() - contactinformation.contactinformation = value - contactinformation.dataformat = dataformat - contactinformation.createdby = userid - for contacttype in contacttypes: - if contacttype["name"] == name: - contactinformation.contacttypeid =contacttype["contacttypeid"] - return contactinformation - - def createapplicant(self,firstname, lastname, appltcategory, userid, middlename = None,businessname = None, alsoknownas = None, dob = None): - requestapplicant = FOIRequestApplicantMapping() - _applicant = FOIRequestApplicant().saveapplicant(firstname, lastname, middlename, businessname, alsoknownas, dob, userid) - requestapplicant.foirequestapplicantid = _applicant.identifier - if appltcategory is not None: - requestertype = RequestorType().getrequestortype(appltcategory) - requestapplicant.requestortypeid = requestertype["requestortypeid"] - return requestapplicant - - def createpersonalattribute(self, name, value,attributetypes, userid): - personalattribute = FOIRequestPersonalAttribute() - personalattribute.createdby = userid - if value is not None and value !="" and value: - for attributetype in attributetypes: - if attributetype["name"] == name: - personalattribute.personalattributeid = attributetype["attributeid"] - personalattribute.attributevalue = value - return personalattribute - - def prepareoipc(self, requestschema, ministryrequestid, version, userid): - oipcarr = [] - if 'oipcdetails' in requestschema: - for oipc in requestschema['oipcdetails']: - oipcreview = FOIRequestOIPC() - oipcreview.foiministryrequest_id = ministryrequestid - oipcreview.foiministryrequestversion_id=version - oipcreview.oipcno = oipc["oipcno"] - oipcreview.reviewtypeid = oipc["reviewtypeid"] - oipcreview.reasonid = oipc["reasonid"] - oipcreview.statusid = oipc["statusid"] - oipcreview.outcomeid = oipc["outcomeid"] - oipcreview.investigator = oipc["investigator"] if oipc["investigator"] not in (None, "") else None - oipcreview.isinquiry = oipc["isinquiry"] - oipcreview.isjudicialreview = oipc["isjudicialreview"] - oipcreview.issubsequentappeal = oipc["issubsequentappeal"] - oipcreview.issubsequentappeal = oipc["issubsequentappeal"] - oipcreview.receiveddate = oipc["receiveddate"] if oipc["receiveddate"] not in (None, "") else None - oipcreview.closeddate = oipc["closeddate"] if oipc["closeddate"] not in (None, "") else None - if oipc["isinquiry"] == True: - oipcreview.inquiryattributes = self.__formatoipcattributes(oipc["inquiryattributes"]) - oipcreview.createdby=userid - oipcreview.created_at= datetime2.now().isoformat() - oipcarr.append(oipcreview) - return oipcarr - - def __formatoipcattributes(self, inquiryattributes): - if inquiryattributes not in (None, "") and inquiryattributes["inquirydate"] in ("","null"): - inquiryattributes["inquirydate"] = None - return inquiryattributes - - - def isNotBlankorNone(self, dataschema, key, location): - if location == "main": - if key in dataschema and dataschema.get(key) is not None and dataschema.get(key) and dataschema.get(key) != "": - return True - else: - if dataschema.get(location) is not None and key in dataschema.get(location) and dataschema.get(location)[key] and dataschema.get(location)[key] is not None and dataschema.get(location)[key] !="": - return True - return False - - - - - - - diff --git a/historical-search-api/request_api/services/foirequest/requestserviceconfigurator.py b/historical-search-api/request_api/services/foirequest/requestserviceconfigurator.py deleted file mode 100644 index 2c31f1674..000000000 --- a/historical-search-api/request_api/services/foirequest/requestserviceconfigurator.py +++ /dev/null @@ -1,84 +0,0 @@ - -from re import T -from request_api.models.ProgramAreas import ProgramArea -from request_api.models.ContactTypes import ContactType -from request_api.models.DeliveryModes import DeliveryMode -from request_api.models.ReceivedModes import ReceivedMode -from request_api.models.ApplicantCategories import ApplicantCategory -from request_api.models.FOIRequestStatus import FOIRequestStatus -from request_api.models.SubjectCodes import SubjectCode -from enum import Enum -import datetime -import secrets - -class requestserviceconfigurator: - """This class consolidates helper fiunctions and constants - """ - - def getstatusname(self,requeststatuslabel): - allstatus = FOIRequestStatus().getrequeststatuses() - for status in allstatus: - if status["statuslabel"] == requeststatuslabel: - return status["name"] - return None; - - def getvalueof(self,name,key): - if name == "receivedMode": - rmode = ReceivedMode().getreceivedmode(key) - return rmode["receivedmodeid"] - elif name == "deliveryMode": - dmode = DeliveryMode().getdeliverymode(key) - return dmode["deliverymodeid"] - elif name == "category": - applcategory = ApplicantCategory().getapplicantcategory(key) - return applcategory["applicantcategoryid"] - elif name == "programArea": - pgarea = ProgramArea().getprogramarea(key) - return pgarea["programareaid"] - elif name == "subjectCode": - subjectcode = SubjectCode().getsubjectcodebyname(key) - return subjectcode["subjectcodeid"] if subjectcode is not None else None - - def getpropertyvaluefromschema(self,requestschema,property): - return requestschema.get(property) if property in requestschema else None - - def generatefilenumber(self, code, id): - tmp = str(id) - randomnum = secrets.randbits(32) - return code + "-" + str(datetime.date.today().year) + "-" + tmp + str(randomnum)[:5] - - def contacttypemapping(self): - return [{"name": ContactType.email.value, "key" : "email"}, - {"name": ContactType.homephone.value, "key" : "phonePrimary"}, - {"name": ContactType.workphone.value, "key" : "workPhonePrimary"}, - {"name": ContactType.mobilephone.value, "key" : "phoneSecondary"}, - {"name": ContactType.workphone2.value, "key" : "workPhoneSecondary"}, - {"name": ContactType.streetaddress.value, "key" : "address"}, - {"name": ContactType.streetaddress.value, "key" : "addressSecondary"}, - {"name": ContactType.streetaddress.value, "key" : "city"}, - {"name": ContactType.streetaddress.value, "key" : "province"}, - {"name": ContactType.streetaddress.value, "key" : "postal"}, - {"name": ContactType.streetaddress.value, "key" : "country"}] - - def personalattributemapping(self): - return [{"name": "BC Correctional Service Number", "key" : "correctionalServiceNumber", "location":"main"}, - {"name": "BC Public Service Employee Number", "key" : "publicServiceEmployeeNumber", "location":"main"}, - {"name": "BC Personal Health Care Number", "key" : "personalHealthNumber", "location":"additionalPersonalInfo"}, - {"name": "Adoptive Mother First Name", "key" : "adoptiveMotherFirstName", "location":"additionalPersonalInfo"}, - {"name": "Adoptive Mother Last Name", "key" : "adoptiveMotherLastName", "location":"additionalPersonalInfo"}, - {"name": "Adoptive Father First Name", "key" : "adoptiveFatherFirstName", "location":"additionalPersonalInfo"}, - {"name": "Adoptive Father Last Name", "key" : "adoptiveFatherLastName", "location":"additionalPersonalInfo"} - ] - -class ContactType(Enum): - email = "Email" - homephone = "Home Phone" - workphone = "Work Phone" - mobilephone = "Mobile Phone" - workphone2 = "Work Phone 2" - streetaddress = "Street Address" - - - - - diff --git a/historical-search-api/request_api/services/foirequest/requestservicecreate.py b/historical-search-api/request_api/services/foirequest/requestservicecreate.py deleted file mode 100644 index ac6f24275..000000000 --- a/historical-search-api/request_api/services/foirequest/requestservicecreate.py +++ /dev/null @@ -1,183 +0,0 @@ - -from re import T -from request_api.models.FOIRequests import FOIRequest -from request_api.models.ContactTypes import ContactType -from request_api.models.PersonalInformationAttributes import PersonalInformationAttribute -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -from request_api.services.watcherservice import watcherservice -from request_api.services.foirequest.requestservicebuilder import requestservicebuilder -from request_api.services.foirequest.requestserviceministrybuilder import requestserviceministrybuilder -from request_api.services.foirequest.requestserviceconfigurator import requestserviceconfigurator -from request_api.models.PersonalInformationAttributes import PersonalInformationAttribute -from request_api.models.FOIRequestContactInformation import FOIRequestContactInformation -from request_api.models.FOIRequestPersonalAttributes import FOIRequestPersonalAttribute -from request_api.models.FOIRequestApplicantMappings import FOIRequestApplicantMapping -from request_api.utils.enums import StateName - -import json -class requestservicecreate: - """ This class consolidates the creation of new FOI request upon scenarios: open, save by both iao and ministry. - """ - - def saverequest(self,foirequestschema, userid, foirequestid=None, ministryid=None, filenumber=None, version=None, rawrequestid=None, wfinstanceid=None): - activeversion = 1 if version is None else version - - # FOI Request - openfoirequest = FOIRequest() - openfoirequest.foirawrequestid = foirequestschema.get("foirawrequestid") if rawrequestid is None else rawrequestid - openfoirequest.version = activeversion - openfoirequest.requesttype = foirequestschema.get("requestType") - openfoirequest.initialdescription = foirequestschema.get("description") - openfoirequest.receiveddate = foirequestschema.get("receivedDate") - openfoirequest.ministryRequests = self.__prepareministries(foirequestschema, activeversion, filenumber,ministryid, userid) - openfoirequest.contactInformations = self.__prearecontactinformation(foirequestschema, userid) - if requestservicebuilder().isNotBlankorNone(foirequestschema,"fromDate","main") == True: - openfoirequest.initialrecordsearchfromdate = foirequestschema.get("fromDate") - if requestservicebuilder().isNotBlankorNone(foirequestschema,"toDate","main") == True: - openfoirequest.initialrecordsearchtodate = foirequestschema.get("toDate") - if requestservicebuilder().isNotBlankorNone(foirequestschema,"deliveryMode","main") == True: - openfoirequest.deliverymodeid = requestserviceconfigurator().getvalueof("deliveryMode",foirequestschema.get("deliveryMode")) - if requestservicebuilder().isNotBlankorNone(foirequestschema,"receivedMode","main") == True: - openfoirequest.receivedmodeid = requestserviceconfigurator().getvalueof("receivedMode",foirequestschema.get("receivedMode")) - if requestservicebuilder().isNotBlankorNone(foirequestschema,"category","main") == True: - openfoirequest.applicantcategoryid = requestserviceconfigurator().getvalueof("category",foirequestschema.get("category")) - openfoirequest.personalAttributes = self._prearepersonalattributes(foirequestschema, userid) - openfoirequest.requestApplicants = self.__prepareapplicants(foirequestschema, userid) - if foirequestid is not None: - openfoirequest.foirequestid = foirequestid - openfoirequest.wfinstanceid = wfinstanceid if wfinstanceid is not None else None - openfoirequest.createdby = userid - return FOIRequest.saverequest(openfoirequest) - - - def saverequestversion(self,foirequestschema, foirequestid , ministryid, userid): - activeversion = 1 - filenumber =foirequestschema.get("idNumber") - #Identify version - if foirequestid is not None: - _foirequest = FOIRequest().getrequest(foirequestid) - if _foirequest != {}: - activeversion = _foirequest["version"] + 1 - else: - return _foirequest - self.__disablewatchers(ministryid, foirequestschema, userid) - result = self.saverequest(foirequestschema, userid, foirequestid,ministryid,filenumber,activeversion,_foirequest["foirawrequestid"],_foirequest["wfinstanceid"]) - if result.success == True: - FOIMinistryRequest.deActivateFileNumberVersion(ministryid, filenumber, userid) - return result - - def saveministryrequestversion(self,ministryrequestschema, foirequestid , ministryid, userid, usertype = None): - _foirequest = FOIRequest().getrequest(foirequestid) - _foiministryrequest = FOIMinistryRequest().getrequestbyministryrequestid(ministryid) - _foirequestapplicant = FOIRequestApplicantMapping().getrequestapplicants(foirequestid,_foirequest["version"]) - _foirequestcontact = FOIRequestContactInformation().getrequestcontactinformation(foirequestid,_foirequest["version"]) - _foirequestpersonalattrbs = FOIRequestPersonalAttribute().getrequestpersonalattributes(foirequestid,_foirequest["version"]) - foiministryrequestarr = [] - foirequest = requestserviceministrybuilder().createfoirequestfromobject(_foirequest, userid) - foiministryrequest = requestserviceministrybuilder().createfoiministryrequestfromobject(_foiministryrequest, ministryrequestschema, userid, usertype) - foiministryrequestarr.append(foiministryrequest) - foirequest.ministryRequests = foiministryrequestarr - foirequest.requestApplicants = requestserviceministrybuilder().createfoirequestappplicantfromobject(_foirequestapplicant, foirequestid, _foirequest['version']+1, userid) - foirequest.contactInformations = requestserviceministrybuilder().createfoirequestcontactfromobject( _foirequestcontact, foirequestid, _foirequest['version']+1, userid) - foirequest.personalAttributes = requestserviceministrybuilder().createfoirequestpersonalattributefromobject(_foirequestpersonalattrbs, foirequestid, _foirequest['version']+1, userid) - result = FOIRequest.saverequest(foirequest) - if result.success == True: - FOIMinistryRequest.deActivateFileNumberVersion(ministryid, _foiministryrequest['filenumber'], userid) - return result - - def __prepareministries(self,foirequestschema, activeversion, filenumber,ministryid, userid): - foiministryrequestarr = [] - if foirequestschema.get("selectedMinistries") is not None: - for ministry in foirequestschema.get("selectedMinistries"): - foiministryrequestarr.append(requestservicebuilder().createministry(foirequestschema, ministry, activeversion, userid, filenumber,ministryid)) - return foiministryrequestarr - - def _prearepersonalattributes(self, foirequestschema, userid): - personalattributearr = [] - if foirequestschema.get("requestType") == "personal": - attributetypes = PersonalInformationAttribute().getpersonalattributes() - for attrb in requestserviceconfigurator().personalattributemapping(): - attrbvalue = None - if attrb["location"] == "main" and requestservicebuilder().isNotBlankorNone(foirequestschema,attrb["key"],"main") == True: - attrbvalue = foirequestschema.get(attrb["key"]) - if attrb["location"] == "additionalPersonalInfo" and requestservicebuilder().isNotBlankorNone(foirequestschema, attrb["key"],"additionalPersonalInfo") == True: - attrbvalue = foirequestschema.get(attrb["location"])[attrb["key"]] - if attrbvalue is not None and attrbvalue and attrbvalue != "": - personalattributearr.append( - requestservicebuilder().createpersonalattribute(attrb["name"], - attrbvalue, - attributetypes, userid) - ) - return personalattributearr - - def __prearecontactinformation(self, foirequestschema, userid): - contactinformationarr = [] - contacttypes = ContactType().getcontacttypes() - for contact in requestserviceconfigurator().contacttypemapping(): - if foirequestschema.get(contact["key"]) is not None: - contactinformationarr.append( - requestservicebuilder().createcontactinformation(contact["key"], - contact["name"], - foirequestschema.get(contact["key"]), - contacttypes, userid) - ) - return contactinformationarr - - def __prepareapplicants(self, foirequestschema, userid): - requestapplicantarr = [] - selfalsoknownas=None - selfdob=None - if foirequestschema.get("additionalPersonalInfo") is not None: - applicantinfo = foirequestschema.get("additionalPersonalInfo") - selfdob = applicantinfo["birthDate"] if requestservicebuilder().isNotBlankorNone(foirequestschema,"birthDate","additionalPersonalInfo") else None - selfalsoknownas = applicantinfo["alsoKnownAs"] if requestservicebuilder().isNotBlankorNone(foirequestschema,"alsoKnownAs","additionalPersonalInfo") else None - requestapplicantarr.append( - requestservicebuilder().createapplicant(foirequestschema.get("firstName"), - foirequestschema.get("lastName"), - "Self", - userid, - foirequestschema.get("middleName"), - foirequestschema.get("businessName"), - selfalsoknownas, - selfdob) - ) - - #Prepare additional applicants - if foirequestschema.get("additionalPersonalInfo") is not None: - addlapplicantinfo = foirequestschema.get("additionalPersonalInfo") - if requestservicebuilder().isNotBlankorNone(foirequestschema,"childFirstName","additionalPersonalInfo"): - requestapplicantarr.append( - requestservicebuilder().createapplicant(self.__getkeyvalue(addlapplicantinfo,"childFirstName") , - self.__getkeyvalue(addlapplicantinfo,"childLastName"), - "Applying for a child under 12", - userid, - self.__getkeyvalue(addlapplicantinfo,"childMiddleName") , - None, - self.__getkeyvalue(addlapplicantinfo,"childAlsoKnownAs") , - self.__getkeyvalue(addlapplicantinfo,"childBirthDate")) - ) - if requestservicebuilder().isNotBlankorNone(foirequestschema,"anotherFirstName","additionalPersonalInfo"): - requestapplicantarr.append( - requestservicebuilder().createapplicant(self.__getkeyvalue(addlapplicantinfo,"anotherFirstName") , - self.__getkeyvalue(addlapplicantinfo,"anotherLastName") , - "Applying for other person", - userid, - self.__getkeyvalue(addlapplicantinfo,"anotherMiddleName") , - None, - self.__getkeyvalue(addlapplicantinfo,"anotherAlsoKnownAs"), - self.__getkeyvalue(addlapplicantinfo,"anotherBirthDate") ) - ) - return requestapplicantarr - - def __disablewatchers(self, ministryid, requestschema, userid): - requeststatuslabel = requestschema.get("requeststatuslabel") if 'requeststatuslabel' in requestschema else None - if requeststatuslabel is not None: - status = requestserviceconfigurator().getstatusname(requeststatuslabel) - if status == StateName.open.value: - watchers = watcherservice().getministryrequestwatchers(int(ministryid), True) - for watcher in watchers: - watcherschema = {"ministryrequestid":ministryid,"watchedbygroup":watcher["watchedbygroup"],"watchedby":watcher["watchedby"],"isactive":False} - watcherservice().createministryrequestwatcher(watcherschema, userid, None) - - def __getkeyvalue(self, inputschema, property): - return inputschema[property] if inputschema is not None and inputschema.get(property) is not None else '' \ No newline at end of file diff --git a/historical-search-api/request_api/services/foirequest/requestservicegetter.py b/historical-search-api/request_api/services/foirequest/requestservicegetter.py deleted file mode 100644 index 6079be737..000000000 --- a/historical-search-api/request_api/services/foirequest/requestservicegetter.py +++ /dev/null @@ -1,338 +0,0 @@ - -from re import T -from request_api.models.FOIRequests import FOIRequest -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -from request_api.models.FOIMinistryRequestDivisions import FOIMinistryRequestDivision -from request_api.models.FOIRequestContactInformation import FOIRequestContactInformation -from request_api.models.FOIRequestPersonalAttributes import FOIRequestPersonalAttribute -from request_api.models.FOIRequestApplicantMappings import FOIRequestApplicantMapping -from request_api.models.FOIMinistryRequestSubjectCodes import FOIMinistryRequestSubjectCode -from request_api.models.FOIRestrictedMinistryRequests import FOIRestrictedMinistryRequest -from request_api.models.FOIRequestOIPC import FOIRequestOIPC -from request_api.services.oipcservice import oipcservice -from dateutil.parser import parse -from request_api.services.cfrfeeservice import cfrfeeservice -from request_api.services.paymentservice import paymentservice -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 -from request_api.utils.enums import StateName - -class requestservicegetter: - """ This class consolidates retrival of FOI request for actors: iao and ministry. - """ - - def getrequest(self,foirequestid,foiministryrequestid): - request = FOIRequest.getrequest(foirequestid) - requestministry = FOIMinistryRequest.getrequestbyministryrequestid(foiministryrequestid) - requestcontactinformation = FOIRequestContactInformation.getrequestcontactinformation(foirequestid,request['version']) - requestapplicants = FOIRequestApplicantMapping.getrequestapplicants(foirequestid,request['version']) - requestministrydivisions = FOIMinistryRequestDivision.getdivisions(foiministryrequestid,requestministry['version']) - iaorestrictrequestdetails = FOIRestrictedMinistryRequest.getrestricteddetails(ministryrequestid=foiministryrequestid,type='iao') - - baserequestinfo = self.__preparebaseinfo(request,foiministryrequestid,requestministry,requestministrydivisions) - baserequestinfo['lastStatusUpdateDate'] = FOIMinistryRequest.getLastStatusUpdateDate(foiministryrequestid, requestministry['requeststatuslabel']).strftime(self.__genericdateformat()), - for contactinfo in requestcontactinformation: - if contactinfo['contacttype.name'] == 'Email': - baserequestinfo.update({'email':contactinfo['contactinformation']}) - else: - baserequestinfo.update({contactinfo['dataformat']:contactinfo['contactinformation']}) - - additionalpersonalinfo ={} - for applicant in requestapplicants: - firstname = applicant['foirequestapplicant.firstname'] - middlename = applicant['foirequestapplicant.middlename'] - lastname = applicant['foirequestapplicant.lastname'] - businessname = applicant['foirequestapplicant.businessname'] - dobraw = applicant['foirequestapplicant.dob'] - dob = parse(dobraw).strftime(self.__genericdateformat()) if dobraw is not None else '' - alsoknownas = applicant['foirequestapplicant.alsoknownas'] - requestortypeid = applicant['requestortype.requestortypeid'] - if requestortypeid == 1: - baserequestinfo.update(self.__prepareapplicant(firstname, middlename, lastname, businessname)) - additionalpersonalinfo.update(self.__prepareadditionalpersonalinfo(requestortypeid, firstname, middlename, lastname, dob, alsoknownas)) - - baserequestdetails, additionalpersonalinfodetails = self.preparepersonalattributes(foirequestid, request['version']) - baserequestinfo.update(baserequestdetails) - additionalpersonalinfo.update(additionalpersonalinfodetails) - - baserequestinfo['additionalPersonalInfo'] = additionalpersonalinfo - 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 - - def preparepersonalattributes(self, foirequestid, version): - personalattributes = FOIRequestPersonalAttribute.getrequestpersonalattributes(foirequestid, version) - baserequestdetails = {} - additionalpersonalinfodetails = {} - - for personalattribute in personalattributes: - attribute, location = self.__preparepersonalattribute(personalattribute) - if location == "main": - baserequestdetails.update(attribute) - elif location =="additionalPersonalInfo": - additionalpersonalinfodetails.update(attribute) - return baserequestdetails, additionalpersonalinfodetails - - def getrequestdetailsforministry(self,foirequestid,foiministryrequestid, authmembershipgroups): - request = FOIRequest.getrequest(foirequestid) - requestministry = FOIMinistryRequest.getrequestbyministryrequestid(foiministryrequestid) - requestministrydivisions = FOIMinistryRequestDivision.getdivisions(foiministryrequestid,requestministry['version']) - ministryrestrictrequestdetails = FOIRestrictedMinistryRequest.getrestricteddetails(foiministryrequestid,type='ministry') - baserequestinfo = {} - if requestministry["assignedministrygroup"] in authmembershipgroups: - baserequestinfo = self.__preparebaseinfo(request,foiministryrequestid,requestministry,requestministrydivisions) - - if request['requesttype'] == 'personal': - requestapplicants = FOIRequestApplicantMapping.getrequestapplicants(foirequestid,request['version']) - additionalpersonalinfo ={} - for applicant in requestapplicants: - firstname = applicant['foirequestapplicant.firstname'] - middlename = applicant['foirequestapplicant.middlename'] - lastname = applicant['foirequestapplicant.lastname'] - dobraw = applicant['foirequestapplicant.dob'] - dob = parse(dobraw).strftime(self.__genericdateformat()) if dobraw is not None else '' - requestortypeid = applicant['requestortype.requestortypeid'] - if requestortypeid == 1: - baserequestinfo.update(self.__prepareapplicant(firstname, middlename, lastname)) - additionalpersonalinfo.update(self.__prepareadditionalpersonalinfo(requestortypeid, firstname, middlename, lastname, dob)) - baserequestdetails, additionalpersonalinfodetails = self.preparepersonalattributes(foirequestid, request['version']) - baserequestinfo.update(baserequestdetails) - additionalpersonalinfo.update(additionalpersonalinfodetails) - baserequestinfo['additionalPersonalInfo'] = additionalpersonalinfo - baserequestinfo['ministryrestricteddetails'] = ministryrestrictrequestdetails - return baserequestinfo - - def getrequestdetails(self,foirequestid, foiministryrequestid): - requestdetails = self.getrequest(foirequestid, foiministryrequestid) - approvedcfrfee = cfrfeeservice().getapprovedcfrfee(foiministryrequestid) - cfrfee = cfrfeeservice().getcfrfee(foiministryrequestid) - payment = paymentservice().getpayment(foirequestid, foiministryrequestid) - if approvedcfrfee is not None and approvedcfrfee != {}: - requestdetails['cfrfee'] = approvedcfrfee - _totaldue = float(approvedcfrfee['feedata']['actualtotaldue']) if float(approvedcfrfee['feedata']['actualtotaldue']) > 0 else float(approvedcfrfee['feedata']['estimatedtotaldue']) - _balancedue = _totaldue - (float(cfrfee['feedata']['amountpaid']) + float(approvedcfrfee['feedata']['feewaiveramount'])) - requestdetails['cfrfee']['feedata']['amountpaid'] = cfrfee['feedata']['amountpaid'] - requestdetails['cfrfee']['feedata']["balanceDue"] = '{:.2f}'.format(_balancedue) - if approvedcfrfee['feedata']['actualtotaldue']: - requestdetails['cfrfee']['feedata']["totalamountdue"] = '{:.2f}'.format(requestdetails['cfrfee']['feedata']["actualtotaldue"]) - else: - requestdetails['cfrfee']['feedata']["totalamountdue"] = '{:.2f}'.format(requestdetails['cfrfee']['feedata']["estimatedtotaldue"]) - - if payment is not None and payment != {}: - paidamount = float(payment['paidamount']) if payment['paidamount'] != None else 0 - requestdetails['cfrfee']['feedata']['paidamount'] = '{:.2f}'.format(paidamount) - # depositpaid field is only accurate and used for outstanding email and receipts - requestdetails['cfrfee']['feedata']['depositpaid'] = '{:.2f}'.format(float(cfrfee['feedata']['amountpaid']) - paidamount) - requestdetails['cfrfee']['feedata']['paymenturl'] = payment['paymenturl'] - requestdetails['cfrfee']['feedata']['paymentdate'] = payment['created_at'][:10] - return requestdetails - - def __preparebaseinfo(self,request,foiministryrequestid,requestministry,requestministrydivisions): - _receiveddate = parse(request['receiveddate']) - axissyncdatenoneorempty = self.__noneorempty(requestministry["axissyncdate"]) - linkedministryrequests= [] - if "linkedrequests" in requestministry and requestministry["linkedrequests"] is not None: - linkedministryrequests = self.__assignministrynames(requestministry["linkedrequests"]) - baserequestinfo = { - 'id': request['foirequestid'], - 'requestType': request['requesttype'], - 'receivedDate': _receiveddate.strftime('%Y %b, %d'), - 'receivedDateUF': parse(request['receiveddate']).strftime('%Y-%m-%d %H:%M:%S.%f'), - 'deliverymodeid':request['deliverymode.deliverymodeid'], - 'deliveryMode':request['deliverymode.name'], - '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"], - 'axisSyncDate': parse(requestministry["axissyncdate"]).strftime('%Y-%m-%d %H:%M:%S.%f') if axissyncdatenoneorempty == False else None, - 'axispagecount': int(requestministry["axispagecount"]) if requestministry["axispagecount"] is not None else 0 , - 'recordspagecount': int(requestministry["recordspagecount"]) if requestministry["recordspagecount"] is not None else 0 , - 'description': requestministry['description'], - 'fromDate': parse(requestministry['recordsearchfromdate']).strftime(self.__genericdateformat()) if requestministry['recordsearchfromdate'] is not None else '', - 'toDate': parse(requestministry['recordsearchtodate']).strftime(self.__genericdateformat()) if requestministry['recordsearchtodate'] is not None else '', - 'currentState':requestministry['requeststatus.name'], - 'requeststatuslabel':requestministry['requeststatuslabel'], - 'requestProcessStart': parse(requestministry['startdate']).strftime(self.__genericdateformat()) if requestministry['startdate'] is not None else '', - 'dueDate':parse(requestministry['duedate']).strftime(self.__genericdateformat()), - 'originalDueDate': parse(requestministry['originalldd']).strftime(self.__genericdateformat()) if requestministry['originalldd'] is not None else parse(requestministry['duedate']).strftime(self.__genericdateformat()), - 'programareaid':requestministry['programarea.programareaid'], - 'bcgovcode':requestministry['programarea.bcgovcode'], - 'category':request['applicantcategory.name'], - 'categoryid':request['applicantcategory.applicantcategoryid'], - 'assignedministrygroup':requestministry["assignedministrygroup"], - 'assignedministryperson':requestministry["assignedministryperson"], - 'selectedMinistries':[{'code':requestministry['programarea.bcgovcode'],'id':requestministry['foiministryrequestid'],'name':requestministry['programarea.name'],'selected':'true'}], - 'divisions': self.getdivisions(requestministrydivisions), - 'isoipcreview': requestministry['isoipcreview'] if (requestministry['isoipcreview'] not in (None, '') and requestministry['isoipcreview'] in (True, False)) else False, - 'isreopened': self.hasreopened(foiministryrequestid), - 'oipcdetails': self.getoipcdetails(foiministryrequestid, requestministry['version']), - 'onholdTransitionDate': self.getonholdtransition(foiministryrequestid), - 'stateTransition': FOIMinistryRequest.getstatesummary(foiministryrequestid), - 'assignedToFirstName': requestministry["assignee.firstname"] if requestministry["assignedto"] != None else None, - 'assignedToLastName': requestministry["assignee.lastname"] if requestministry["assignedto"] != None else None, - 'assignedministrypersonFirstName': requestministry["ministryassignee.firstname"] if requestministry["assignedministryperson"] != None else None, - 'assignedministrypersonLastName': requestministry["ministryassignee.lastname"] if requestministry["assignedministryperson"] != None else None, - '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, - 'identityVerified':requestministry['identityverified'], - 'estimatedpagecount':requestministry['estimatedpagecount'], - 'estimatedtaggedpagecount':requestministry['estimatedtaggedpagecount'], - - } - if requestministry['cfrduedate'] is not None: - baserequestinfo.update({'cfrDueDate':parse(requestministry['cfrduedate']).strftime(self.__genericdateformat())}) - return baserequestinfo - - def __assignministrynames(self, linkedrequests): - areas = programareaservice().getprogramareas() - if linkedrequests is not None: - for entry in linkedrequests: - area = next((a for a in areas if a["programareaid"] == list(entry.values())[0]), None) - if (area is not None): - entry = area["name"] - return linkedrequests - - def getdivisions(self, ministrydivisions): - divisions = [] - if ministrydivisions is not None: - for ministrydivision in ministrydivisions: - division = { - "foiministrydivisionid": ministrydivision["foiministrydivisionid"], - "divisionid": ministrydivision["division.divisionid"], - "divisionname": ministrydivision["division.name"], - "stageid": ministrydivision["stage.stageid"], - "stagename": ministrydivision["stage.name"], - "divisionDueDate": parse(ministrydivision['divisionduedate']).strftime(self.__genericdateformat()) if ministrydivision['divisionduedate'] is not None else None, - "eApproval": ministrydivision["eapproval"], - "divisionReceivedDate": parse(ministrydivision['divisionreceiveddate']).strftime(self.__genericdateformat()) if ministrydivision['divisionreceiveddate'] is not None else None, - } - divisions.append(division) - return divisions - - def getoipcdetails(self, ministryrequestid, ministryrequestversion): - oipcdetails = [] - _oipclist = FOIRequestOIPC.getoipc(ministryrequestid, ministryrequestversion) - inquiryoutcomes = oipcservice().getinquiryoutcomes() - if _oipclist is not None: - for entry in _oipclist: - oipc = { - "oipcid": entry["oipcid"], - "oipcno": entry["oipcno"], - "reviewtypeid": entry["reviewtypeid"], - "reviewetype": entry["reviewtype.name"], - "reasonid": entry["reasonid"], - "reason": entry["reason.name"], - "statusid": entry["statusid"], - "status":entry["status.name"], - "outcomeid": entry["outcomeid"], - "outcome": entry["outcome.name"] if entry["outcomeid"] not in (None, '') else None, - "investigator": entry["investigator"], - "isinquiry": entry["isinquiry"], - "isjudicialreview": entry["isjudicialreview"], - "issubsequentappeal": entry["issubsequentappeal"], - "inquiryattributes": self.formatinquiryattribute(entry["inquiryattributes"], inquiryoutcomes), - "createdby": entry["createdby"], - "receiveddate" : parse(entry["receiveddate"]).strftime('%b, %d %Y') if entry["receiveddate"] is not None else '', - "closeddate": parse(entry["closeddate"]).strftime('%b, %d %Y') if entry["closeddate"] is not None else '', - "created_at": parse(entry["created_at"]).strftime(self.__genericdateformat()) - } - oipcdetails.append(oipc) - return oipcdetails - - def formatinquiryattribute(self, inquiryattribute, inquiryoutcomes): - if inquiryattribute not in (None, {}): - for outcome in inquiryoutcomes: - if inquiryattribute["inquiryoutcome"] == outcome["inquiryoutcomeid"]: - inquiryattribute["inquiryoutcomename"] = outcome["name"] - return inquiryattribute - - - def getonholdtransition(self, foiministryrequestid): - onholddate = None - transitions = FOIMinistryRequest.getrequeststatusById(foiministryrequestid) - for entry in transitions: - if entry['requeststatuslabel'] == StateName.onhold.name: - onholddate = datetimehandler().convert_to_pst(entry['created_at'],'%Y-%m-%d') - else: - if onholddate is not None: - break - return onholddate - - def getministryrequest(self, foiministryrequestid): - requestministry = FOIMinistryRequest.getrequestbyministryrequestid(foiministryrequestid) - return requestministry - - def __genericdateformat(self): - return '%Y-%m-%d' - - def hasreopened(self, requestid): - states = FOIMinistryRequest.getstatesummary(requestid) - if len(states) > 0: - current_state = states[0] - if current_state != "Closed" and any(state['status'] == "Closed" for state in states): - return True - return False - - def __prepareapplicant(self,firstname= None, middlename= None, lastname= None, businessname= None): - return { - 'firstName': firstname, - 'middleName': middlename, - 'lastName': lastname, - 'businessName': businessname, - } - - def __prepareadditionalpersonalinfo(self, requestortypeid, firstname= None, middlename= None, lastname= None, dob= None, alsoknownas= None): - if requestortypeid == 1: - return { - 'birthDate' : dob, - 'alsoKnownAs': alsoknownas - } - elif requestortypeid == 2: - return { - 'anotherFirstName': firstname, - 'anotherMiddleName': middlename, - 'anotherLastName': lastname, - 'anotherBirthDate' : dob, - 'anotherAlsoKnownAs': alsoknownas - } - elif requestortypeid == 3: - return { - 'childFirstName': firstname, - 'childMiddleName': middlename, - 'childLastName': lastname, - 'childAlsoKnownAs': alsoknownas, - 'childBirthDate': dob, - } - - def __preparepersonalattribute(self, personalattribute): - if personalattribute['personalattributeid'] == 1: - return {'publicServiceEmployeeNumber': personalattribute['attributevalue']}, "main" - - elif personalattribute['personalattributeid'] == 2 : - return {'correctionalServiceNumber': personalattribute['attributevalue']}, "main" - - elif personalattribute['personalattributeid'] == 3 : - return {'personalHealthNumber': personalattribute['attributevalue']}, "additionalPersonalInfo" - - elif personalattribute['personalattributeid'] == 4: - return {'adoptiveMotherFirstName': personalattribute['attributevalue']}, "main" - - elif personalattribute['personalattributeid'] == 5: - return {'adoptiveMotherLastName': personalattribute['attributevalue']}, "main" - - elif personalattribute['personalattributeid'] == 6: - return {'adoptiveFatherFirstName': personalattribute['attributevalue']}, "main" - - elif personalattribute['personalattributeid'] == 7: - return {'adoptiveFatherLastName': personalattribute['attributevalue']}, "main" - - def __noneorempty(self, variable): - return True if not variable else False diff --git a/historical-search-api/request_api/services/foirequest/requestserviceministrybuilder.py b/historical-search-api/request_api/services/foirequest/requestserviceministrybuilder.py deleted file mode 100644 index 9336eb2d2..000000000 --- a/historical-search-api/request_api/services/foirequest/requestserviceministrybuilder.py +++ /dev/null @@ -1,354 +0,0 @@ - -from re import T -from request_api.models.FOIRequests import FOIRequest -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -from request_api.models.FOIRequestContactInformation import FOIRequestContactInformation -from request_api.models.FOIRequestPersonalAttributes import FOIRequestPersonalAttribute -from request_api.models.FOIRequestApplicantMappings import FOIRequestApplicantMapping -from request_api.models.FOIMinistryRequestDivisions import FOIMinistryRequestDivision -from request_api.models.FOIMinistryRequestDocuments import FOIMinistryRequestDocument -from request_api.models.FOIRequestExtensions import FOIRequestExtension -from request_api.models.FOIRequestExtensionDocumentMappings import FOIRequestExtensionDocumentMapping -from request_api.models.FOIAssignees import FOIAssignee -from request_api.models.FOIMinistryRequestSubjectCodes import FOIMinistryRequestSubjectCode -from request_api.models.FOIRequestStatus import FOIRequestStatus -from request_api.models.FOIRequestOIPC import FOIRequestOIPC -from request_api.services.foirequest.requestserviceconfigurator import requestserviceconfigurator -from datetime import datetime as datetime2 - -class requestserviceministrybuilder(requestserviceconfigurator): - """ This class consolidates the helper functions for creating new foi request based on ministry actions. - """ - - def createfoirequestfromobject(self, foiobject, userid): - foirequest = FOIRequest() - foirequest.foirawrequestid = foiobject['foirawrequestid'] - foirequest.foirequestid = foiobject['foirequestid'] - foirequest.version = foiobject["version"] + 1 - foirequest.initialdescription = foiobject['initialdescription'] - foirequest.initialrecordsearchfromdate = foiobject['initialrecordsearchfromdate'] if 'initialrecordsearchfromdate' in foiobject else None - foirequest.initialrecordsearchtodate = foiobject['initialrecordsearchfromdate'] if 'initialrecordsearchfromdate' in foiobject else None - foirequest.receiveddate = foiobject['receiveddate'] if 'receiveddate' in foiobject else None - foirequest.requesttype = foiobject['requesttype'] - foirequest.wfinstanceid = foiobject['wfinstanceid'] - foirequest.applicantcategoryid = foiobject["applicantcategory.applicantcategoryid"] - foirequest.deliverymodeid = foiobject["deliverymode.deliverymodeid"] - foirequest.receivedmodeid = foiobject["receivedmode.receivedmodeid"] - foirequest.createdby = userid - return foirequest - - def createfoiministryrequestfromobject(self, ministryschema, requestschema, userid, usertype = None): - requestdict = self.createfoiministryrequestfromobject1(ministryschema, requestschema) - foiministryrequest = FOIMinistryRequest() - foiministryrequest.foiministryrequestid = ministryschema["foiministryrequestid"] - foiministryrequest.version = ministryschema["version"] + 1 - foiministryrequest.foirequest_id = ministryschema["foirequest_id"] - foiministryrequest.foirequestversion_id = ministryschema["foirequestversion_id"] - foiministryrequest.description = ministryschema["description"] - foiministryrequest.recordsearchfromdate = requestdict['recordsearchfromdate'] - foiministryrequest.recordsearchtodate = requestdict['recordsearchtodate'] - foiministryrequest.filenumber = ministryschema["filenumber"] - foiministryrequest.axissyncdate = ministryschema["axissyncdate"] - foiministryrequest.axisrequestid = ministryschema["axisrequestid"] - foiministryrequest.linkedrequests = ministryschema['linkedrequests'] - foiministryrequest.identityverified = ministryschema['identityverified'] - foiministryrequest.originalldd = ministryschema['originalldd'] - foiministryrequest.axispagecount = ministryschema["axispagecount"] - foiministryrequest.recordspagecount = ministryschema["recordspagecount"] - foiministryrequest.cfrduedate = requestdict['cfrduedate'] - foiministryrequest.startdate = requestdict['startdate'] - foiministryrequest.duedate = requestdict['duedate'] - foiministryrequest.assignedministrygroup = requestdict['assignedministrygroup'] - if 'assignedministryperson' in requestschema and requestschema['assignedministryperson'] not in (None,''): - foiministryrequest.assignedministryperson = requestschema['assignedministryperson'] - firstname = requestschema['assignedministrypersonFirstName'] - middlename = None - lastname = requestschema['assignedministrypersonLastName'] - self.createfoiassigneefromobject(requestschema['assignedministryperson'], firstname, middlename, lastname) - else: - foiministryrequest.assignedministryperson = ministryschema["assignedministryperson"] - - foiministryrequest.assignedgroup = requestschema['assignedgroup'] if 'assignedgroup' in requestschema and requestschema['assignedgroup'] not in (None,'') else requestdict['assignedgroup'] - if 'assignedto' in requestschema and requestschema['assignedto'] not in (None,''): - foiministryrequest.assignedto = requestschema['assignedto'] - fn = requestschema['assignedToFirstName'] if requestschema['assignedto'] != None else None - mn = None - ln = requestschema['assignedToLastName'] if requestschema['assignedto'] != None else None - self.createfoiassigneefromobject(requestschema['assignedto'], fn, mn, ln) - 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 = self.__getrequeststatusid(requestdict['requeststatuslabel']) - foiministryrequest.requeststatuslabel = requestdict['requeststatuslabel'] - foiministryrequest.programareaid = requestdict['programareaid'] - foiministryrequest.createdby = userid - - foiministryrequest.divisions = self.__createministrydivisions(requestschema, ministryschema["foiministryrequestid"] ,ministryschema["version"], userid) - foiministryrequest.documents = self.createfoirequestdocuments(requestschema,ministryschema["foiministryrequestid"] ,ministryschema["version"] +1 , userid) - foiministryrequest.extensions = self.createfoirequestextensions(ministryschema["foiministryrequestid"] ,ministryschema["version"] +1 , userid) - foiministryrequest.subjectcode = self.__createministrysubjectcode(requestschema, ministryschema["foiministryrequestid"], ministryschema["version"], userid) - foiministryrequest.closedate = requestdict['closedate'] - foiministryrequest.closereasonid = requestdict['closereasonid'] - foiministryrequest.isoipcreview = ministryschema["isoipcreview"] - foiministryrequest.oipcreviews = self.createfoirequestoipcs(foiministryrequest.isoipcreview, ministryschema["foirequest_id"], ministryschema["version"]) - return foiministryrequest - - def __getrequeststatusid(self, requeststatuslabel): - state = FOIRequestStatus.getrequeststatusbylabel( - requeststatuslabel - ) - stateid = ( - state.get("requeststatusid") - if isinstance(state, dict) and state.get("requeststatusid") not in (None, "") - else "" - ) - return stateid - def __createministrydivisions(self, requestschema, foiministryrequestid, foiministryrequestversion, userid): - if 'divisions' in requestschema: - return self.createfoirequestdivision(requestschema,foiministryrequestid ,foiministryrequestversion + 1, userid) - else: - divisions = FOIMinistryRequestDivision().getdivisions(foiministryrequestid, foiministryrequestversion) - return self.createfoirequestdivisionfromobject(divisions,foiministryrequestid, foiministryrequestversion + 1, userid) - - def __createministrysubjectcode(self, requestschema, foiministryrequestid, foiministryrequestversion, userid): - subjectcode = FOIMinistryRequestSubjectCode().getministrysubjectcode(foiministryrequestid, foiministryrequestversion) - if subjectcode: - return self.createfoirequestsubjectcodefromobject(subjectcode, foiministryrequestid, foiministryrequestversion + 1, userid) - else: - return [] - - def createfoiministryrequestfromobject1(self, ministryschema, requestschema): - return { - 'recordsearchfromdate': ministryschema['recordsearchfromdate'] if 'recordsearchfromdate' in ministryschema else None, - 'recordsearchtodate': ministryschema['recordsearchtodate'] if 'recordsearchtodate' in ministryschema else None, - 'cfrduedate': ministryschema['cfrduedate'] if 'cfrduedate' in ministryschema else None, - 'startdate': ministryschema['startdate'] if 'startdate' in ministryschema else None, - 'duedate': requestschema['duedate'] if 'duedate' in requestschema else ministryschema["duedate"], #and isextension':= True - 'assignedministrygroup': requestschema['assignedministrygroup'] if 'assignedministrygroup' in requestschema else ministryschema["assignedministrygroup"], - 'assignedgroup': requestschema['assignedgroup'] if 'assignedgroup' in requestschema else ministryschema["assignedgroup"], - 'requeststatuslabel': requestschema['requeststatuslabel'] if 'requeststatuslabel' in requestschema else ministryschema["requeststatuslabel"], - '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': requestschema['linkedrequests'] if 'linkedrequests' in requestschema else None, - 'ministrysignoffapproval': requestschema['ministrysignoffapproval'] if 'ministrysignoffapproval' in requestschema else None, - } - - def createfoirequestdocuments(self,requestschema, ministryrequestid, activeversion, userid): - # documents = FOIMinistryRequestDocument().getdocuments(ministryrequestid, activeversion-1) - # make isactive = False for all ministryRequestId and prev ministryVersion - # FOIMinistryRequestDocument.deActivateministrydocumentsversionbyministry(ministryrequestid, activeversion, userid) - - # existingdocuments = self.createfoirequestdocumentfromobject(documents,ministryrequestid ,activeversion, userid) - # documentarr = existingdocuments - if 'documents' in requestschema: - return self.createfoirequestdocument(requestschema,ministryrequestid ,activeversion, userid) - # documentarr = newdocuments #+ existingdocuments - return [] - - def createfoirequestsubjectcode(self,requestschema, ministryrequestid, activeversion, userid): - return self.createsubjectcode(requestschema,ministryrequestid ,activeversion, userid) - - def createfoirequestextensions(self, ministryrequestid, activeversion, userid): - extensions = FOIRequestExtension().getextensions(ministryrequestid, activeversion-1) - - existingextensions = self.createfoirequestextensionfromobject(extensions,ministryrequestid ,activeversion, userid) - if existingextensions is not None: - return existingextensions - return [] - - def createfoirequestappplicantfromobject(self, requestapplicants, requestid, version, userid): - requestapplicantarr = [] - for requestapplicant in requestapplicants: - applicantmapping = FOIRequestApplicantMapping() - applicantmapping.foirequest_id = requestid - applicantmapping.foirequestversion_id = version - applicantmapping.foirequestapplicantid = requestapplicant["foirequestapplicant.foirequestapplicantid"] - applicantmapping.requestortypeid = requestapplicant["requestortype.requestortypeid"] - applicantmapping.createdby = userid - requestapplicantarr.append(applicantmapping) - return requestapplicantarr - - def createfoirequestpersonalattributefromobject(self,personalattributes, requestid, version, userid): - personalattributesarr = [] - for personalattribute in personalattributes: - attribute = FOIRequestPersonalAttribute() - attribute.personalattributeid = personalattribute["personalattributeid"] - attribute.attributevalue = personalattribute["attributevalue"] - attribute.foirequest_id = requestid - attribute.foirequestversion_id = version - attribute.createdby = userid - personalattributesarr.append(attribute) - return personalattributesarr - - def createfoirequestcontactfromobject(self, requestcontacts, requestid, version, userid): - requestcontactarr = [] - for requestcontact in requestcontacts: - contactinfo = FOIRequestContactInformation() - contactinfo.contacttypeid = requestcontact["contacttype.contacttypeid"] - contactinfo.foirequest_id = requestid - contactinfo.foirequestversion_id = version - contactinfo.contactinformation = requestcontact["contactinformation"] - contactinfo.dataformat = requestcontact["dataformat"] - contactinfo.createdby = userid - requestcontactarr.append(contactinfo) - return requestcontactarr - - def createfoirequestdivisionfromobject(self, divisions, requestid, version, userid): - divisionarr = [] - for division in divisions: - ministrydivision = FOIMinistryRequestDivision() - ministrydivision.divisionid = division["division.divisionid"] - ministrydivision.stageid = division["stage.stageid"] - ministrydivision.divisionduedate = division["divisionduedate"] - ministrydivision.eapproval = division["eapproval"] - ministrydivision.divisionreceiveddate = division["divisionreceiveddate"] - ministrydivision.foiministryrequest_id = requestid - ministrydivision.foiministryrequestversion_id = version - ministrydivision.createdby = userid - divisionarr.append(ministrydivision) - return divisionarr - - def createfoirequestdocumentfromobject(self, documents, requestid, activeversion, userid): - documentarr = [] - for document in documents: - ministrydocument = FOIMinistryRequestDocument() - ministrydocument.foiministrydocumentid = document["foiministrydocumentid"] - ministrydocument.documentpath = document["documentpath"] - if 'filename' in document: - ministrydocument.filename = document["filename"] - if 'category' in document: - ministrydocument.category = document['category'] - ministrydocument.version = document['version'] + 1 - ministrydocument.foiministryrequest_id = requestid - ministrydocument.foiministryrequestversion_id = activeversion - ministrydocument.created_at = document["created_at"] - ministrydocument.createdby = document["createdby"] - ministrydocument.updated_at = datetime2.now().isoformat() - ministrydocument.updatedby = userid - documentarr.append(ministrydocument) - return documentarr - - def createfoirequestextensionfromobject(self, extensions, requestid, activeversion, userid): - extensionarr = [] - for extension in extensions: - requestextension = FOIRequestExtension() - requestextension.foirequestextensionid = extension["foirequestextensionid"] - requestextension.extensionreasonid = extension["extensionreasonid"] - requestextension.extensionstatusid = extension["extensionstatusid"] - if 'extendedduedays' in extension: - requestextension.extendedduedays = extension["extendedduedays"] - if 'extendedduedate' in extension: - requestextension.extendedduedate = extension["extendedduedate"] - if 'decisiondate' in extension: - requestextension.decisiondate = extension["decisiondate"] - if 'approvednoofdays' in extension: - requestextension.approvednoofdays = extension["approvednoofdays"] - requestextension.version = extension["version"] + 1 - requestextension.foiministryrequest_id = requestid - requestextension.foiministryrequestversion_id = activeversion - requestextension.created_at = extension["created_at"] - requestextension.createdby = extension["createdby"] - requestextension.updated_at = datetime2.now().isoformat() - requestextension.updatedby = userid - documets = self.createextensiondocumentfromobject(FOIRequestExtensionDocumentMapping().getextensiondocuments(extension["foirequestextensionid"], extension["version"]), extension["version"] + 1, userid) - requestextension.extensiondocuments = documets - extensionarr.append(requestextension) - return extensionarr - - def createextensiondocumentfromobject(self, extensiondocuments, activeversion, userid): - extdocumentarr = [] - for extensiondocument in extensiondocuments: - extensiondocumentmapping = FOIRequestExtensionDocumentMapping() - extensiondocumentmapping.foirequestextensionid = extensiondocument["foirequestextensionid"] - extensiondocumentmapping.extensionversion = activeversion - extensiondocumentmapping.foiministrydocumentid = extensiondocument["foiministrydocumentid"] - extensiondocumentmapping.createdby = userid - extdocumentarr.append(extensiondocumentmapping) - return extdocumentarr - - def createfoirequestdocument(self, requestschema, requestid, version, userid): - documentarr = [] - if 'documents' in requestschema: - for document in requestschema['documents']: - ministrydocument = FOIMinistryRequestDocument() - ministrydocument.documentpath = document["documentpath"] - ministrydocument.filename = document["filename"] - if 'category' in document: - ministrydocument.category = document['category'] - ministrydocument.version = 1 - ministrydocument.foiministryrequest_id = requestid - ministrydocument.foiministryrequestversion_id = version - ministrydocument.createdby = userid - ministrydocument.created_at = datetime2.now().isoformat() - documentarr.append(ministrydocument) - return documentarr - - def createsubjectcode(self, requestschema, requestid, version, userid): - subjectcodearr = [] - ministrysubjectcode = FOIMinistryRequestSubjectCode() - ministrysubjectcode.subjectcodeid = requestserviceconfigurator().getvalueof("subjectCode",requestschema['subjectCode']) - ministrysubjectcode.foiministryrequestid = requestid - ministrysubjectcode.foiministryrequestversion = version - ministrysubjectcode.createdby = userid - ministrysubjectcode.created_at = datetime2.now().isoformat() - subjectcodearr.append(ministrysubjectcode) - return subjectcodearr - - def createfoirequestsubjectcodefromobject(self, subjectcode, requestid, activeversion, userid): - subjectcodearr = [] - ministrysubjectcode = FOIMinistryRequestSubjectCode() - ministrysubjectcode.subjectcodeid = subjectcode['subjectcodeid'] - ministrysubjectcode.foiministryrequestid = requestid - ministrysubjectcode.foiministryrequestversion = activeversion - ministrysubjectcode.createdby = userid - ministrysubjectcode.created_at = datetime2.now().isoformat() - subjectcodearr.append(ministrysubjectcode) - return subjectcodearr - - def createfoirequestdivision(self, requestschema, requestid, version, userid): - divisionarr = [] - if 'divisions' in requestschema: - for division in requestschema['divisions']: - ministrydivision = FOIMinistryRequestDivision() - ministrydivision.divisionid = division["divisionid"] - ministrydivision.stageid = division["stageid"] - ministrydivision.divisionduedate = division["divisionDueDate"] if "divisionDueDate" in division and division["divisionDueDate"] != '' else None - ministrydivision.eapproval = division["eApproval"] if "eApproval" in division else None - ministrydivision.divisionreceiveddate = division["divisionReceivedDate"] if "divisionReceivedDate" in division and division["divisionReceivedDate"] != '' else None - ministrydivision.foiministryrequest_id = requestid - ministrydivision.foiministryrequestversion_id = version - ministrydivision.createdby = userid - divisionarr.append(ministrydivision) - return divisionarr - - def createfoiassigneefromobject(self, username, firstname, middlename, lastname): - return FOIAssignee.saveassignee(username, firstname, middlename, lastname) - - def createfoirequestoipcs(self, isoipcreview, requestid, version): - current_oipcs = FOIRequestOIPC.getoipc(requestid, version) - if (isoipcreview == True): - updated_oipcs = [] - for oipc in current_oipcs: - oipcreview = FOIRequestOIPC() - oipcreview.foiministryrequest_id = requestid - oipcreview.foiministryrequestversion_id = version + 1 - oipcreview.oipcno = oipc["oipcno"] - oipcreview.reviewtypeid = oipc["reviewtypeid"] - oipcreview.reasonid = oipc["reasonid"] - oipcreview.statusid = oipc["statusid"] - oipcreview.outcomeid = oipc["outcomeid"] - oipcreview.investigator = oipc["investigator"] - oipcreview.isinquiry = oipc["isinquiry"] - oipcreview.isjudicialreview = oipc["isjudicialreview"] - oipcreview.issubsequentappeal = oipc["issubsequentappeal"] - oipcreview.issubsequentappeal = oipc["issubsequentappeal"] - oipcreview.receiveddate = oipc["receiveddate"] - oipcreview.closeddate = oipc["closeddate"] - oipcreview.inquiryattributes = oipc["inquiryattributes"] - oipcreview.createdby=oipc["createdby"] - oipcreview.created_at= oipc["created_at"] - updated_oipcs.append(oipcreview) - return updated_oipcs - return [] diff --git a/historical-search-api/request_api/services/foirequest/requestserviceupdate.py b/historical-search-api/request_api/services/foirequest/requestserviceupdate.py deleted file mode 100644 index a85ca38e9..000000000 --- a/historical-search-api/request_api/services/foirequest/requestserviceupdate.py +++ /dev/null @@ -1,26 +0,0 @@ - -from re import T -from request_api.models.FOIRequests import FOIRequest -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -from request_api.models.FOIRequestStatus import FOIRequestStatus -from request_api.services.foirequest.requestservicebuilder import requestservicebuilder - -import json -class requestserviceupdate(requestservicebuilder): - """ This class consolidates update of FOI request for actors: iao and ministry. - """ - - def updaterequest(self,foirequestschema,foirequestid,userid): - if self.isNotBlankorNone(foirequestschema,"wfinstanceid","main") == True: - return FOIRequest.updateWFInstance(foirequestid, foirequestschema.get("wfinstanceid"), userid) - if foirequestschema.get("selectedMinistries") is not None: - allstatus = FOIRequestStatus().getrequeststatuses() - updatedministries = [] - for ministry in foirequestschema.get("selectedMinistries"): - for status in allstatus: - if ministry["status"] == status["name"]: - updatedministries.append({"filenumber" : ministry["filenumber"], "requeststatuslabel": status["statuslabel"]}) - return FOIRequest.updateStatus(foirequestid, updatedministries, userid) - - def updateministryrequestduedate(self, ministryrequestid, duedate, userid): - return FOIMinistryRequest().updateduedate(ministryrequestid, duedate, userid) \ No newline at end of file diff --git a/historical-search-api/request_api/services/hash_service.py b/historical-search-api/request_api/services/hash_service.py deleted file mode 100644 index 70f99d3f7..000000000 --- a/historical-search-api/request_api/services/hash_service.py +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright © 2019 Province of British Columbia -# -# Licensed under the Apache License, Version 2.0 (the 'License'); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an 'AS IS' BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -"""Service for hashing.""" -import hashlib - -from flask import current_app -from typing import Dict -from urllib.parse import parse_qsl - - -class HashService: - """Hash Service class.""" - - @staticmethod - def encode(param: str) -> str: - """Return a hashed string using the static salt from config.""" - current_app.logger.debug(f'encoding for string {param}') - api_key = current_app.config.get('PAYBC_API_KEY') - return hashlib.md5(f'{param}{api_key}'.encode()).hexdigest() # NOSONAR as PayBC needs MD5 - - @staticmethod - def is_valid_checksum(param: str, hash_val: str) -> bool: - """Validate if the checksum matches.""" - api_key = current_app.config.get('PAYBC_API_KEY') - return hashlib.md5(f'{param}{api_key}'.encode()).hexdigest() == hash_val # NOSONAR as PayBC needs MD5 - - @staticmethod - def parse_url_params(url_params: str) -> Dict: - """Parse URL params and return dict of parsed url params.""" - parsed_url: dict = {} - if url_params is not None: - if url_params.startswith('?'): - url_params = url_params[1:] - parsed_url = dict(parse_qsl(url_params)) - - return parsed_url diff --git a/historical-search-api/request_api/services/historicalrequestservice.py b/historical-search-api/request_api/services/historicalrequestservice.py deleted file mode 100644 index 9b8de61a0..000000000 --- a/historical-search-api/request_api/services/historicalrequestservice.py +++ /dev/null @@ -1,14 +0,0 @@ - -from os import stat -from request_api.models.FOIAssignees import FOIAssignee - -class historicalrequestservice: - """ FOI Assignee management service - - This service class interacts with Keycloak and returns eligible groups and members based on the state of application. - - """ - - def gethistoricalrequest(self, requestid): - - diff --git a/historical-search-api/request_api/services/notifications/notificationconfig.py b/historical-search-api/request_api/services/notifications/notificationconfig.py deleted file mode 100644 index b6b771206..000000000 --- a/historical-search-api/request_api/services/notifications/notificationconfig.py +++ /dev/null @@ -1,69 +0,0 @@ - -from os import stat -from re import VERBOSE -import json -import os -from request_api.models.NotificationTypes import NotificationType -from request_api.models.NotificationUserTypes import NotificationUserType -notificationuserfile = open('common/notificationusertypes.json', encoding="utf8") -notificationusertypes_cache = json.load(notificationuserfile) - -notificationfile = open('common/notificationtypes.json', encoding="utf8") -notificationtypes_cache = json.load(notificationfile) - -class notificationconfig: - """ Notfication config - - """ - - # This method is used to get the notification user type label - # It first tries to get the notification user type label from the cache - # If it is not found in the cache, it fetches it from the DB - def getnotificationtypelabel(self, notificationtype): - notificationtype_format = notificationtype.replace(" ", "").lower() - if notificationtype_format in notificationtypes_cache: - return notificationtypes_cache[notificationtype_format]['notificationtypelabel'] - else: - print("Notification type not found in json. Fetching from DB", notificationtype) - id = NotificationType().getnotificationtypeid(notificationtype) - if id is not None: - return id['notificationtypelabel'] - return None - - def getnotificationtypeid(self, notificationtype): - id = NotificationType().getnotificationtypeid(notificationtype) - if id is not None: - return id['notificationtypeid'] - return None - - # This method is used to get the notification user type label - # It first tries to get the notification user type label from the cache - # If it is not found in the cache, it fetches it from the DB - def getnotificationusertypelabel(self, notificationusertype): - notificationusertype_format = notificationusertype.replace(" ", "").lower() - if notificationusertype_format in notificationusertypes_cache: - return notificationusertypes_cache[notificationusertype_format]['notificationusertypelabel'] - else: - print("Notification user type not found in json. Fetching from DB", notificationusertype) - id = NotificationUserType().getnotificationusertypesid(notificationusertype) - if id is not None: - return id['notificationusertypelabel'] - return None - - def getnotificationusertypeid(self, notificationusertype): - id = NotificationUserType().getnotificationusertypesid(notificationusertype) - if id is not None: - return id['notificationusertypeid'] - return None - - def getnotificationdays(self): - if 'FOI_NOTIFICATION_DAYS' in os.environ and os.getenv('FOI_NOTIFICATION_DAYS') != '': - return os.getenv('FOI_NOTIFICATION_DAYS') - else: - return str(14) - - def getmutenotifications(self): - if 'MUTE_NOTIFICATION' in os.environ and os.getenv('MUTE_NOTIFICATION') != '': - return json.loads(os.getenv('MUTE_NOTIFICATION')) - else: - return {} \ No newline at end of file diff --git a/historical-search-api/request_api/services/notifications/notificationuser.py b/historical-search-api/request_api/services/notifications/notificationuser.py deleted file mode 100644 index 5f810690a..000000000 --- a/historical-search-api/request_api/services/notifications/notificationuser.py +++ /dev/null @@ -1,133 +0,0 @@ - -from os import stat -from re import VERBOSE -import json -from request_api.services.watcherservice import watcherservice -from request_api.models.FOIRawRequestComments import FOIRawRequestComment -from request_api.models.FOIRequestComments import FOIRequestComment -from request_api.services.notifications.notificationconfig import notificationconfig -from request_api.services.external.keycloakadminservice import KeycloakAdminService - -class notificationuser: - """ notification user service - - """ - - def getnotificationusers(self, notificationtype, requesttype, userid, foirequest, requestjson=None): - notificationusers = [] - if 'User Assignment Removal' == notificationtype: - _users = self.__getassignees(foirequest, requesttype, notificationtype, requestjson) - elif 'Assignment' in notificationtype: - _users = self.__getassignees(foirequest, requesttype, notificationtype) - elif 'Reply User Comments' in notificationtype: - _users = self.__getcommentusers(foirequest, requestjson, requesttype) - elif 'Tagged User Comments' in notificationtype: - _users = self.__gettaggedusers(requestjson) - elif 'Group Members' in notificationtype: - _users = self.__getgroupmembers(foirequest["assignedministrygroup"]) - elif 'Watcher' in notificationtype: - _users = self.__getwatchers(notificationtype, foirequest, requesttype, requestjson) - else: - _users = self.__getassignees(foirequest, requesttype, notificationtype) + self.__getwatchers(notificationtype, foirequest, requesttype) - for user in _users: - if self.__isignorable(user, notificationusers, userid) == False and (("Tagged User Comments" not in notificationtype and self.__istaggeduser(user, requestjson, notificationtype) == False) or "Tagged User Comments" in notificationtype): - notificationusers.append(user) - return notificationusers - - def __isignorable(self, notificationuser, users, userid): - if notificationuser["userid"] == userid: - return True - else: - for user in users: - if notificationuser["userid"] == user["userid"]: - return True - return False - - def __istaggeduser(self, notificationuser, foicomment, notificationtype): - if "Comment" in notificationtype: - _users = self.__gettaggedusers(foicomment) - if _users is not None: - for user in _users: - if notificationuser["userid"] == user["userid"]: - return True - return False - - def __getwatchers(self, notificationtype, foirequest, requesttype, requestjson=None): - notificationusers = [] - if notificationtype == "Watcher": - notificationusers.append({"userid": requestjson['watchedby'], "usertype":notificationconfig().getnotificationusertypelabel("Watcher")}) - else: - if requesttype == "ministryrequest": - watchers = watcherservice().getallministryrequestwatchers(foirequest["foiministryrequestid"], self.__isministryonly(notificationtype)) - else: - watchers = watcherservice().getrawrequestwatchers(foirequest['requestid']) - for watcher in watchers: - notificationusers.append({"userid":watcher["watchedby"], "usertype":notificationconfig().getnotificationusertypelabel("Watcher")}) - return notificationusers - - def __getassignees(self, foirequest, requesttype, notificationtype, requestjson=None): - notificationusers = [] - notificationusertypelabel = notificationconfig().getnotificationusertypelabel("Assignee") - if notificationtype == 'User Assignment Removal': - notificationusers.append({"userid": requestjson['userid'], "usertype":notificationusertypelabel}) - else: - if requesttype == "ministryrequest" and foirequest["assignedministryperson"] is not None and (notificationtype == 'Ministry Assignment' or 'Assignment' not in notificationtype): - notificationusers.append({"userid":foirequest["assignedministryperson"], "usertype":notificationusertypelabel}) - if self.__isministryonly(notificationtype) == False and foirequest["assignedto"] is not None and foirequest["assignedto"] != '' and (notificationtype == 'IAO Assignment' or 'Assignment' not in notificationtype): - notificationusers.append({"userid":foirequest["assignedto"], "usertype":notificationusertypelabel}) - return notificationusers - - def __isministryonly(self, notificationtype): - return True if notificationtype == "Division Due Reminder" else False - - def __getcommentusers(self, foirequest, comment, requesttype): - _requestusers = self.getnotificationusers("General", requesttype, "nouser", foirequest) - commentusers = [] - commentusers.extend(_requestusers) - taggedusers = self.__gettaggedusers(comment) - if taggedusers is not None: - commentusers.extend(taggedusers) - if comment["parentcommentid"]: - _commentusers = self.__getrelatedusers(comment, requesttype) - for _commentuser in _commentusers: - commentusers.append({"userid":_commentuser["createdby"], "usertype":self.__getcommentusertype(_commentuser["createdby"],_requestusers)}) - _skiptaguserforreplies = False - if _skiptaguserforreplies == False: - taggedusers = self.__gettaggedusers(_commentuser) - if taggedusers is not None: - commentusers.extend(taggedusers) - return commentusers - - def __getcommentusertype(self, userid, requestusers): - for requestuser in requestusers: - if requestuser["userid"] == userid: - return requestuser["usertype"] - return notificationconfig().getnotificationusertypelabel("comment user") - - def __getrelatedusers(self, comment, requesttype): - if requesttype == "ministryrequest": - return FOIRequestComment.getcommentusers(comment["commentid"]) - else: - return FOIRawRequestComment.getcommentusers(comment["commentid"]) - - def __gettaggedusers(self, comment): - if comment["taggedusers"] != '[]': - return self.__preparetaggeduser(json.loads(comment["taggedusers"])) - return None - - def __preparetaggeduser(self, data): - taggedusers = [] - for entry in data: - taggedusers.append({"userid":entry["username"], "usertype":notificationconfig().getnotificationusertypelabel("comment tagged user")}) - return taggedusers - - def __getgroupmembers(self,groupid): - notificationusers = [] - notificationusertypelabel = notificationconfig().getnotificationusertypelabel("Group Members") - usergroupfromkeycloak= KeycloakAdminService().getmembersbygroupname(groupid) - if usergroupfromkeycloak is not None and len(usergroupfromkeycloak) > 0: - for user in usergroupfromkeycloak[0].get("members"): - notificationusers.append({"userid":user["username"], "usertype":notificationusertypelabel}) - return notificationusers - return [] - \ No newline at end of file diff --git a/historical-search-api/request_api/services/notificationservice.py b/historical-search-api/request_api/services/notificationservice.py deleted file mode 100644 index 89f8643a9..000000000 --- a/historical-search-api/request_api/services/notificationservice.py +++ /dev/null @@ -1,352 +0,0 @@ - -from os import stat -from re import VERBOSE - -from requests.api import request -from request_api.services.watcherservice import watcherservice -from request_api.services.notifications.notificationconfig import notificationconfig -from request_api.services.notifications.notificationuser import notificationuser -from request_api.models.FOIRawRequests import FOIRawRequest -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -from request_api.models.FOIRequests import FOIRequest -from request_api.models.FOIRequestNotifications import FOIRequestNotification -from request_api.models.FOIRequestNotificationUsers import FOIRequestNotificationUser -from request_api.models.FOIRawRequestNotifications import FOIRawRequestNotification -from request_api.models.FOIRawRequestNotificationUsers import FOIRawRequestNotificationUser -from request_api.models.FOIRawRequestComments import FOIRawRequestComment -from request_api.models.FOIRequestComments import FOIRequestComment -from request_api.models.NotificationTypes import NotificationType -from request_api.models.default_method_result import DefaultMethodResult -from datetime import datetime as datetime2 -import os -import json -from datetime import datetime -from dateutil.parser import parse -from pytz import timezone -from request_api.services.external.keycloakadminservice import KeycloakAdminService -from request_api.models.OperatingTeams import OperatingTeam -import logging -file = open('common/notificationtypes.json', encoding="utf8") -notificationtypes_cache = json.load(file) - - -class notificationservice: - """ FOI notification management service - """ - - def createnotification(self, message, requestid, requesttype, notificationtype, userid, iscleanup=True): - foirequest = self.getrequest(requestid, requesttype) - if iscleanup == True: - self.__cleanupnotifications(requesttype, notificationtype['name'], foirequest) - return self.__createnotification(message, requestid, requesttype, notificationtype, userid, foirequest) - - def createusernotification(self, message, requestid, requesttype, notificationtypename, notificationuser, userid): - foirequest = self.getrequest(requestid, requesttype) - notificationtype = NotificationType().getnotificationtypeid(notificationtypename) - return self.__createnotification(message, requestid, requesttype, notificationtype, userid, foirequest, {"userid": notificationuser}) - - def createremindernotification(self, message, requestid, requesttype, notificationtype, userid): - foirequest = self.getrequest(requestid, requesttype) - return self.__createnotification(message, requestid, requesttype, notificationtype, userid, foirequest) - - def createcommentnotification(self, message, comment, commenttype, requesttype, userid): - requestid = comment["ministryrequestid"] if requesttype == "ministryrequest" else comment["requestid"] - foirequest = self.getrequest(requestid, requesttype) - notificationtype = NotificationType().getnotificationtypeid(commenttype) - return self.__createnotification(message, requestid, requesttype, notificationtype, userid, foirequest, comment) - - def createwatchernotification(self, message, requesttype, watcher, userid): - requestid = watcher["ministryrequestid"] if requesttype == "ministryrequest" else watcher["requestid"] - foirequest = self.getrequest(requestid, requesttype) - notificationtype = NotificationType().getnotificationtypeid('Watcher') - return self.__createnotification(message, requestid, requesttype, notificationtype, userid, foirequest, watcher) - - def editcommentnotification(self, message, comment, userid): - notificationsusers = FOIRequestNotification.getcommentnotifications(comment['commentid']) - notifications = {} - for notification in notificationsusers: - notifications[notification['notificationid']] = notification - for id in notifications: - notifications[id]['notification'] = message - requesttype = self.__getnotificationtypefromid(notifications[id]['idnumber']) - if requesttype == 'ministryrequest': - result = FOIRequestNotification.updatenotification(notifications[id], userid) - else: - result = FOIRawRequestNotification.updatenotification(notifications[id], userid) - if not result.success: - return result - return DefaultMethodResult(True,'Notifications updated for comment id',comment['commentid']) - - def getnotifications(self, userid): - return FOIRequestNotification.getconsolidatednotifications(userid, notificationconfig().getnotificationdays()) - - def getcommentnotifications(self, commentid): - return FOIRequestNotification.getcommentnotifications(commentid) - - def getextensionnotifications(self, extensionid): - return FOIRequestNotification.getextensionnotifications(extensionid) - - def dismissnotification(self, userid, type, idnumber, notificationuserid): - if type is not None: - return self.__dismissnotificationbytype(userid, type) - else: - if idnumber is not None and notificationuserid is not None: - requesttype = self.__getnotificationtypefromid(idnumber) - return self.__dimissnotificationbyuserid(requesttype, notificationuserid) - else: - return self.__dismissnotificationbyuser(userid) - - def dismissnotificationbyid(self, requesttype, notificationids): - return self.__deletenotificationids(requesttype, notificationids) - - def dismissremindernotification(self, requesttype, notificationtype): - notificationlabel = notificationconfig().getnotificationtypelabel(notificationtype) - if requesttype == "ministryrequest": - _ids = FOIRequestNotification.getnotificationidsbytype(notificationlabel) - else: - _ids = FOIRawRequestNotification.getnotificationidsbytype(notificationlabel) - self.__deletenotificationids(requesttype, _ids) - - def dismissnotifications_by_requestid_type_userid(self, requestid, requesttype, notificationtype, userid): - notificationtypelabels = self.__getcleanupnotificationids(notificationtype) - foirequest = self.getrequest(requestid, requesttype) - if requesttype == "ministryrequest": - idnumber = foirequest["filenumber"] - _ids = FOIRequestNotification.getnotificationidsbynumberandtype(idnumber, notificationtypelabels) - else: - _ids = FOIRawRequestNotification.getnotificationidsbynumberandtype('U-00' + str(foirequest['requestid']), notificationtypelabels[0]) - self.__deletenotificationbyuserandid(requesttype, _ids, userid) - - def dismissnotifications_by_requestid_type(self, requestid, requesttype, notificationtype): - notificationtypelabels = self.__getcleanupnotificationids(notificationtype) - foirequest = self.getrequest(requestid, requesttype) - if requesttype == "ministryrequest": - idnumber = foirequest["filenumber"] - _ids = FOIRequestNotification.getnotificationidsbynumberandtype(idnumber, notificationtypelabels) - else: - _ids = FOIRawRequestNotification.getnotificationidsbynumberandtype('U-00' + str(foirequest['requestid']), notificationtypelabels[0]) - self.__deletenotificationids(requesttype, _ids) - - - def __createnotification(self, message, requestid, requesttype, notificationtype, userid, foirequest, requestjson=None): - notification = self.__preparenotification(message, requesttype, notificationtype, userid, foirequest, requestjson) - if notification is not None: - if requesttype == "ministryrequest": - return FOIRequestNotification.savenotification(notification) - else: - return FOIRawRequestNotification.savenotification(notification) - return DefaultMethodResult(True,'No change',requestid) - - def dismissnotificationsbyrequestid(self,requestid, requesttype): - foirequest = self.getrequest(requestid, requesttype) - if requesttype == "ministryrequest": - idnumber = foirequest["filenumber"] - _ids = FOIRequestNotification.getnotificationidsbynumber(idnumber) - if _ids: - FOIRequestNotificationUser.dismissbynotificationid(_ids) - FOIRequestNotification.dismissnotification(_ids) - else: - _ids = FOIRawRequestNotification.getnotificationidsbynumber('U-00' + str(foirequest['requestid'])) - if _ids: - FOIRawRequestNotificationUser.dismissbynotificationid(_ids) - FOIRawRequestNotification.dismissnotification(_ids) - - def __cleanupnotifications(self, requesttype, notificationtype, foirequest): - notificationtypelabels = self.__getcleanupnotificationids(notificationtype) - if requesttype == "ministryrequest": - idnumber = foirequest["filenumber"] - _ids = FOIRequestNotification.getnotificationidsbynumberandtype(idnumber, notificationtypelabels) - else: - _ids = FOIRawRequestNotification.getnotificationidsbynumberandtype('U-00' + str(foirequest['requestid']), notificationtypelabels[0]) - self.__deletenotificationids(requesttype, _ids) - - - def __getcleanupnotificationids(self, notificationtype): - notificationtypelabels = [] - notificationlabel = notificationconfig().getnotificationtypelabel(notificationtype) - notificationtypelabels.append(notificationlabel) - if notificationtype == "State" or notificationtype.endswith("Assignment"): - notificationtypelabels.append(notificationconfig().getnotificationtypelabel("Group Members")) - return notificationtypelabels - - - def __deletenotificationids(self, requesttype, notificationids): - if notificationids: - if requesttype == "ministryrequest": - cresponse = FOIRequestNotificationUser.dismissbynotificationid(notificationids) - presponse = FOIRequestNotification.dismissnotification(notificationids) - else: - cresponse = FOIRawRequestNotificationUser.dismissbynotificationid(notificationids) - presponse = FOIRawRequestNotification.dismissnotification(notificationids) - if cresponse.success == True and presponse.success == True: - return DefaultMethodResult(True,'Notifications deleted ','|'.join(map(str, notificationids))) - else: - return DefaultMethodResult(False,'Unable to delete the notifications ','|'.join(map(str, notificationids))) - - def __dimissnotificationbyuserid(self, requesttype, notificationuserid): - notficationids = self.__getdismissparentids(requesttype, notificationuserid) - if requesttype == "ministryrequest": - cresponse = FOIRequestNotificationUser.dismissnotification(notificationuserid) - presponse = FOIRequestNotification.dismissnotification(notficationids) - else: - cresponse = FOIRawRequestNotificationUser.dismissnotification(notificationuserid) - presponse = FOIRawRequestNotification.dismissnotification(notficationids) - if cresponse.success == True and presponse.success == True: - return DefaultMethodResult(True,'Notifications deleted for',notificationuserid) - else: - return DefaultMethodResult(False,'Unable to delete the notifications for',notificationuserid) - - def __dismissnotificationbyuser(self, userid): - requestnotificationids = self.__getdismissparentidsbyuser("ministryrequest", userid) - requestnotification = FOIRequestNotificationUser.dismissnotificationbyuser(userid) - rawnotificationids = self.__getdismissparentidsbyuser("rawrequest", userid) - rawnotification = FOIRawRequestNotificationUser.dismissnotificationbyuser(userid) - prequestnotification = FOIRequestNotification.dismissnotification(requestnotificationids) - prawnotification = FOIRawRequestNotification.dismissnotification(rawnotificationids) - if requestnotification.success == True and rawnotification.success == True and prequestnotification.success == True and prawnotification.success == True: - return DefaultMethodResult(True,'Notifications deleted for user',userid) - else: - return DefaultMethodResult(False,'Unable to delete the notifications',userid) - - def __dismissnotificationbytype(self, userid, type): - notificationusertypelabel = notificationconfig().getnotificationusertypelabel(type) - requestnotificationids = self.__getdismissparentidsbyuserandtype("ministryrequest", userid, notificationusertypelabel) - requestnotification = FOIRequestNotificationUser.dismissnotificationbyuserandtype(userid, notificationusertypelabel) - rawnotificationids = self.__getdismissparentidsbyuserandtype("rawrequest", userid, notificationusertypelabel) - rawnotification = FOIRawRequestNotificationUser.dismissnotificationbyuserandtype(userid, notificationusertypelabel) - prequestnotification = FOIRequestNotification.dismissnotification(requestnotificationids) - prawnotification = FOIRawRequestNotification.dismissnotification(rawnotificationids) - if requestnotification.success == True and rawnotification.success == True and prequestnotification.success == True and prawnotification.success == True: - return DefaultMethodResult(True,'Notifications deleted for user',userid) - else: - return DefaultMethodResult(False,'Unable to delete the notifications',userid) - - def __deletenotificationbyuserandid(self, requesttype, notificationids,userid): - if notificationids: - if requesttype == "ministryrequest": - requestnotificationids= FOIRequestNotificationUser.getnotificationidsbyuserandid(userid, notificationids) - cresponse = FOIRequestNotificationUser.dismissbynotificationid(requestnotificationids) - presponse = FOIRequestNotification.dismissnotification(requestnotificationids) - if cresponse.success == True and presponse.success == True: - return DefaultMethodResult(True,'Notifications deleted for id','|'.join(map(str, notificationids))) - else: - return DefaultMethodResult(False,'Unable to delete the notifications for id','|'.join(map(str, notificationids))) - else: - rawnotificationids= FOIRawRequestNotificationUser.getnotificationidsbyuserandid(userid, notificationids) - cresponse = FOIRawRequestNotificationUser.dismissbynotificationid(rawnotificationids) - presponse = FOIRawRequestNotification.dismissnotification(rawnotificationids) - if cresponse.success == True and presponse.success == True: - return DefaultMethodResult(True,'Notifications deleted for id','|'.join(map(str, notificationids))) - else: - return DefaultMethodResult(False,'Unable to delete the notifications for id','|'.join(map(str, notificationids))) - - def __getdismissparentids(self, requesttype, notificationuserid): - if requesttype == "ministryrequest": - _notficationids = FOIRequestNotificationUser.getnotificationsbyid(notificationuserid) - else: - _notficationids = FOIRawRequestNotificationUser.getnotificationsbyid(notificationuserid) - return self.__filterdismissparentids(_notficationids) - - def __getdismissparentidsbyuser(self, requesttype, userid): - if requesttype == "ministryrequest": - _notficationids = FOIRequestNotificationUser.getnotificationsbyuser(userid) - else: - _notficationids = FOIRawRequestNotificationUser.getnotificationsbyuser(userid) - return self.__filterdismissparentids(_notficationids) - - def __getdismissparentidsbyuserandtype(self, requesttype, userid, notificationusertypelabel): - if requesttype == "ministryrequest": - _notficationids = FOIRequestNotificationUser.getnotificationsbyuserandtype(userid, notificationusertypelabel) - else: - _notficationids = FOIRawRequestNotificationUser.getnotificationsbyuserandtype(userid, notificationusertypelabel) - return self.__filterdismissparentids(_notficationids) - - def __filterdismissparentids(self,_notficationids): - notficationids = [] - for notification in _notficationids: - if notification["count"] == 1: - notficationids.append(notification["notificationid"]) - return notficationids - - def __getnotificationtypefromid(self, idnumber): - return 'rawrequest' if idnumber.lower().startswith('u-00') else 'ministryrequest' - - def __preparenotification(self, message, requesttype, notificationtype, userid, foirequest, requestjson=None): - ministryusers = [] - if requesttype == "ministryrequest": - notification = FOIRequestNotification() - notification.requestid = foirequest["foiministryrequestid"] - notification.idnumber = foirequest["filenumber"] - notification.foirequestid = foirequest["foirequest_id"] - - #mute notifications for ministry users - mutenotification = self.__mutenotification(requesttype, notificationtype['name'], foirequest) - usergroupfromkeycloak = KeycloakAdminService().getmembersbygroupname(foirequest["assignedministrygroup"]) - if usergroupfromkeycloak is not None and len(usergroupfromkeycloak) > 0: - for user in usergroupfromkeycloak[0].get("members"): - ministryusers.append(user["username"]) - else: - notification = FOIRawRequestNotification() - notification.requestid = foirequest["requestid"] - notification.idnumber ='U-00' + str(foirequest['requestid']) - mutenotification = False - - notification.notificationtypelabel = notificationtype['notificationtypelabel'] - notification.notificationtypeid = notificationtype['notificationtypeid'] - notification.axisnumber = foirequest["axisrequestid"] - notification.version = foirequest["version"] - notification.createdby = userid - notification.notification = message - notification.isdeleted = False - - notificationusers = notificationuser().getnotificationusers(notificationtype['name'], requesttype, userid, foirequest, requestjson) - users = [] - for _notificationuser in notificationusers: - users.append(self.__preparenotificationuser(requesttype, _notificationuser, userid, mutenotification, ministryusers)) - notification.notificationusers = users - return notification if users else None - - def __preparenotificationuser(self, requesttype, notificationuser, userid, mute=False, ministryusers=[]): - if requesttype == "ministryrequest": - user = FOIRequestNotificationUser() - if notificationuser["userid"] in ministryusers: - user.isdeleted = mute - else: - user.isdeleted = False - else: - user = FOIRawRequestNotificationUser() - user.isdeleted = False - user.notificationusertypelabel = notificationuser["usertype"] - user.notificationusertypeid = notificationconfig().getnotificationusertypeid( notificationuser["usertype"]) - user.userid = notificationuser["userid"] - user.createdby = userid - return user - - def __mutenotification(self, requesttype, notificationtype, request): - #get mute conditions from env - mutenotifications = notificationconfig().getmutenotifications() - if "programarea.bcgovcode" in request: - bcgovcode = request["programarea.bcgovcode"].upper() - if requesttype == "ministryrequest"and bcgovcode in mutenotifications: - foirequest = FOIRequest.getrequest(request["foirequest_id"]) - if foirequest["requesttype"].upper() in (_requesttype.upper() for _requesttype in mutenotifications[bcgovcode]["request_types"]): - if request["requeststatus.name"].upper() in (_state.upper() for _state in mutenotifications[bcgovcode]["state_exceptions"]): - return False - if notificationtype.upper() in (_notificationtype.upper() for _notificationtype in mutenotifications[bcgovcode]["type_exceptions"]): - return False - return True - - return False - - def getrequest(self, requestid, requesttype): - if requesttype == "ministryrequest": - return FOIMinistryRequest.getrequestbyministryrequestid(requestid) - else: - return FOIRawRequest.get_request(requestid) - - - - - - - diff --git a/historical-search-api/request_api/services/oipcservice.py b/historical-search-api/request_api/services/oipcservice.py deleted file mode 100644 index 636044430..000000000 --- a/historical-search-api/request_api/services/oipcservice.py +++ /dev/null @@ -1,27 +0,0 @@ -from request_api.models.OIPCReviewTypes import OIPCReviewTypes -from request_api.models.OIPCStatuses import OIPCStatuses -from request_api.models.OIPCReasons import OIPCReasons -from request_api.models.OIPCOutcomes import OIPCOutcomes -from request_api.models.OIPCInquiryOutcomes import OIPCInquiryOutcomes -from request_api.models.OIPCReviewTypesReasons import OIPCReviewTypesReasons - -class oipcservice: - """ OIPC service - """ - def getreviewtypes(self): - return OIPCReviewTypes.getreviewtypes() - - def getreviewtypeswithreasons(self): - return OIPCReviewTypesReasons.getreviewtypeswithreasons() - - def getreasons(self): - return OIPCReasons.getreasons() - - def getstatuses(self): - return OIPCStatuses.getstatuses() - - def getoutcomes(self): - return OIPCOutcomes.getoutcomes() - - def getinquiryoutcomes(self): - return OIPCInquiryOutcomes.getinquiryoutcomes() \ No newline at end of file diff --git a/historical-search-api/request_api/services/paymentservice.py b/historical-search-api/request_api/services/paymentservice.py deleted file mode 100644 index 3bf598034..000000000 --- a/historical-search-api/request_api/services/paymentservice.py +++ /dev/null @@ -1,156 +0,0 @@ - -from os import stat -from re import VERBOSE -from request_api.models.FOIRequestPayments import FOIRequestPayment -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -from request_api.services.document_generation_service import DocumentGenerationService -from request_api.models.default_method_result import DefaultMethodResult -from request_api.services.applicantcorrespondence.applicantcorrespondencelog import applicantcorrespondenceservice -from request_api.utils.enums import PaymentEventType, StateName - -import json -from dateutil.parser import parse -from datetime import datetime -import asyncio - -from dateutil import parser -from dateutil import tz -from pytz import timezone -import pytz -import maya -import logging -import re - -class paymentservice: - """ FOI payment management service - Supports creation, update and delete of payment - """ - - def createpayment(self, requestid, ministryrequestid, data, createdby='System'): - payment = FOIRequestPayment() - ministryversion = FOIMinistryRequest.getversionforrequest(ministryrequestid) - payment.foirequestid = requestid - payment.ministryrequestid = ministryrequestid - payment.ministryrequestversion = ministryversion - payment.paymenturl = data['paymenturl'] if 'paymenturl' in data else None - payment.paymentexpirydate = data['paymentExpiryDate'] if 'paymentExpiryDate' in data else self.getpaymentexpirydate(requestid, ministryrequestid) - payment.version = 1 - payment.createdby = createdby - _payment = FOIRequestPayment.getpayment(requestid, ministryrequestid) - if _payment is not None and _payment != {}: - payment.version = _payment["version"] + 1 - payment.paymentid = _payment["paymentid"] - return FOIRequestPayment.savepayment(payment) - - def updatepayment(self, paymentid, data, userid): - return FOIRequestPayment.updatepayment(paymentid, data["paymenturl"], userid) - - def createpaymentversion(self, request_id, ministry_request_id, amountpaid): - payment = self.__createpaymentinstance(request_id, ministry_request_id) - if payment is not None and payment != {}: - payment.paidamount = amountpaid - return FOIRequestPayment.savepayment(payment) - - def cancelpayment(self, request_id, ministry_request_id, paymentschema): - payment = self.__createpaymentinstance(request_id, ministry_request_id) - if payment is not None and payment != {}: - payment.paymenturl = paymentschema['paymenturl'] - payment.paymentexpirydate = datetime.now().isoformat() - payment.createdby = 'System_Cancel' - return FOIRequestPayment.savepayment(payment) - - def __createpaymentinstance(self, request_id, ministry_request_id): - _payment = FOIRequestPayment.getpayment(request_id, ministry_request_id) - ministryversion = FOIMinistryRequest.getversionforrequest(ministry_request_id) - payment = FOIRequestPayment() - payment.foirequestid = request_id - payment.ministryrequestid = ministry_request_id - payment.ministryrequestversion = ministryversion - if _payment is not None and _payment != {}: - payment.paymenturl = _payment['paymenturl'] - payment.paymentexpirydate = _payment['paymentexpirydate'] - payment.version = _payment['version'] + 1 - payment.createdby = 'System' - payment.paymentid = _payment["paymentid"] - return payment - - - def getpayment(self, requestid, ministryrequestid): - return FOIRequestPayment.getpayment(requestid, ministryrequestid) - - def getpaymentexpirydate(self, requestid, ministryrequestid): - payment = FOIRequestPayment.getpayment(requestid, ministryrequestid) - paymentexpirydate = payment["paymentexpirydate"] if payment not in (None, {}) else None - if paymentexpirydate is not None: - return parse(str(paymentexpirydate)).strftime("%Y-%m-%d") - return "" - - def createpaymentreceipt(self, request_id, ministry_request_id, data, parsed_args, previously_paid_amount): - try: - data['previous_amt_paid'] = '{:.2f}'.format(previously_paid_amount) - templatename = self.__getlatesttemplatename(ministry_request_id) - balancedue = float(data['cfrfee']['feedata']["balanceDue"]) - prevstate = data["stateTransition"][1]["status"] if "stateTransition" in data and len(data["stateTransition"]) > 2 else None - basepath = 'request_api/receipt_templates/' - receiptname = 'cfr_fee_payment_receipt' - attachmentcategory = "FEE-ESTIMATE-PAYMENT-RECEIPT" - filename = f"Fee Estimate Payment Receipt {re.sub(r'[^a-zA-Z0-9]', '', data['cfrfee']['created_at'])}.pdf" - if balancedue > 0: - receipt_template_path= basepath + self.getreceiptename('HALFPAYMENT') +".docx" - receiptname = self.getreceiptename('HALFPAYMENT') - else: - receiptname = self.getreceiptename('FULLPAYMENT') - if prevstate.lower() == "response" or (templatename and templatename == 'PAYOUTSTANDING'): - receiptname = self.getreceiptename('PAYOUTSTANDING') - attachmentcategory = "OUTSTANDING-PAYMENT-RECEIPT" - filename = f"Fee Balance Outstanding Payment Receipt {re.sub(r'[^a-zA-Z0-9]', '', data['cfrfee']['created_at'])}.pdf" - receipt_template_path= basepath + receiptname + ".docx" - data['waivedAmount'] = data['cfrfee']['feedata']['estimatedlocatinghrs'] * 30 if data['cfrfee']['feedata']['estimatedlocatinghrs'] < 3 else 90 - data.update({'paymentInfo': { - 'paymentDate': parsed_args.get('trnDate'), - 'orderId': parsed_args.get('trnOrderId'), - 'transactionId': parsed_args.get('pbcTxnNumber'), - 'cardType': parsed_args.get('cardType') - }}) - document_service : DocumentGenerationService = DocumentGenerationService(receiptname) - receipt = document_service.generate_receipt(data,receipt_template_path) - document_service.upload_receipt(filename, receipt.content, ministry_request_id, data['bcgovcode'], data['idNumber'], attachmentcategory) - return DefaultMethodResult(True,'Payment Receipt created',ministry_request_id) - except Exception as ex: - logging.exception(ex) - return DefaultMethodResult(False,'Unable to create Payment Receipt',ministry_request_id) - - def postpayment(self, ministry_request_id, data): - prevstate = data["stateTransition"][1]["status"] if "stateTransition" in data and len(data["stateTransition"]) > 2 else None - nextstatename = StateName.callforrecords.value - - balancedue = float(data['cfrfee']['feedata']["balanceDue"]) - paymenteventtype = PaymentEventType.paid.value - if balancedue > 0: - paymenteventtype = PaymentEventType.depositpaid.value - if prevstate.lower() == "response": - nextstatename = StateName.response.value - templatename = self.__getlatesttemplatename(ministry_request_id) - #outstanding - if balancedue == 0 and ((templatename and templatename == 'PAYOUTSTANDING') or prevstate.lower() == "response"): - paymenteventtype = PaymentEventType.outstandingpaid.value - nextstatename = StateName.response.value - return nextstatename, paymenteventtype - - def getreceiptename(self, key): - if key == "HALFPAYMENT": - return "cfr_fee_payment_receipt_half" - elif key == "FULLPAYMENT": - return "cfr_fee_payment_receipt_full" - elif key == "PAYOUTSTANDING": - return "outstanding_fee_payment_receipt" - else: - logging.info("Unknown key") - return None - - def __getlatesttemplatename(self, ministryrequestid): - latestcorrespondence = applicantcorrespondenceservice().getlatestapplicantcorrespondence(ministryrequestid) - _latesttemplateid = latestcorrespondence['templateid'] if 'templateid' in latestcorrespondence else None - if _latesttemplateid: - return applicantcorrespondenceservice().gettemplatebyid(_latesttemplateid).name - return None diff --git a/historical-search-api/request_api/services/programareadivisionservice.py b/historical-search-api/request_api/services/programareadivisionservice.py deleted file mode 100644 index c3b07c505..000000000 --- a/historical-search-api/request_api/services/programareadivisionservice.py +++ /dev/null @@ -1,58 +0,0 @@ -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: - - def getallprogramareadivisions(self): - """ Returns all active program area divisions - """ - 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 - """ - return ProgramAreaDivision.createprogramareadivision(data) - - def updateprogramareadivision(self, divisionid, data,userid): - """ Updates an existing program area division - """ - return ProgramAreaDivision.updateprogramareadivision(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 - """ - divisions = [] - areas = programareaservice().getprogramareas() - for entry in data: - area = next((a for a in areas if a["programareaid"] == entry["programareaid"]), None) - if (area is not None): - entry["programarea"] = area["name"] - entry["areabcgovcode"] = area["bcgovcode"] - entry["areaiaocode"] = area["iaocode"] - divisions.append(entry) - return divisions diff --git a/historical-search-api/request_api/services/programareaservice.py b/historical-search-api/request_api/services/programareaservice.py deleted file mode 100644 index 6f8708e4d..000000000 --- a/historical-search-api/request_api/services/programareaservice.py +++ /dev/null @@ -1,16 +0,0 @@ -from request_api.models.ProgramAreas import ProgramArea - -class programareaservice: - - def getprogramareas(self): - """ Returns the active records - """ - return ProgramArea.getprogramareas() - - def getprogramareabyiaocode(self, iaocode): - return ProgramArea.getprogramareabyiaocode(iaocode) - - def getprogramareasforministryuser(self, groups = None): - """ Returns the active records - """ - return ProgramArea.getprogramareasforministryuser(groups) \ No newline at end of file diff --git a/historical-search-api/request_api/services/rawrequest/rawrequestservicegetter.py b/historical-search-api/request_api/services/rawrequest/rawrequestservicegetter.py deleted file mode 100644 index b8b1cf870..000000000 --- a/historical-search-api/request_api/services/rawrequest/rawrequestservicegetter.py +++ /dev/null @@ -1,230 +0,0 @@ - -from typing import Counter - -from request_api.models.FOIRawRequests import FOIRawRequest -from request_api.models.FOIRequestStatus import FOIRequestStatus -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -from dateutil.parser import parse -import maya -from request_api.models.FOIAssignees import FOIAssignee -from request_api.utils.enums import StateName - -class rawrequestservicegetter: - """ This class consolidates retrival of FOI raw request for actors: iao. - """ - - def getallrawrequests(self): - requests = FOIRawRequest.getrequests() - unopenedrequests = [] - for request in requests: - firstname , lastname, requesttype = '','','' - if(request.version != 1 and request.sourceofsubmission != "intake") or request.sourceofsubmission == "intake": - firstname = request.requestrawdata['firstName'] - lastname = request.requestrawdata['lastName'] - requesttype = request.requestrawdata['requestType'] - elif (request.sourceofsubmission!= "intake" and request.version == 1): - firstname = request.requestrawdata['contactInfo']['firstName'] - lastname = request.requestrawdata['contactInfo']['lastName'] - requesttype = request.requestrawdata['requestType']['requestType'] - - assignedgroupvalue = request.assignedgroup if request.assignedgroup else "Unassigned" - assignedtovalue = request.assignedto if request.assignedto else "Unassigned" - dt = maya.parse(request.created_at).datetime(to_timezone='America/Vancouver', naive=False) - _createddate = dt - - unopenrequest = {'id': request.requestid, - 'firstName': firstname, - 'lastName': lastname, - 'requestType': requesttype, - 'currentState': request.status, - 'receivedDate': request.requestrawdata["receivedDate"] if "receivedDate" in request.requestrawdata else _createddate.strftime('%Y %b, %d'), - 'receivedDateUF':request.requestrawdata["receivedDateUF"] if "receivedDateUF" in request.requestrawdata else str(_createddate), - 'assignedGroup': assignedgroupvalue, - 'assignedTo': assignedtovalue, - 'xgov': 'No', - 'idNumber': 'U-00' + str(request.requestid), - 'axisRequestId': request.axisrequestid, - 'version':request.version - } - unopenedrequests.append(unopenrequest) - - return unopenedrequests - - def getrawrequestforid(self, requestid): - request = FOIRawRequest.get_request(requestid) - request = self.__attachministriesinfo(request) - if request != {} and (request['version'] == 1 or request['status'] == StateName.unopened.value) and request['sourceofsubmission'] != "intake": - requestrawdata = request['requestrawdata'] - requesttype = requestrawdata['requestType']['requestType'] - baserequestinfo = self.__preparebaserequestinfo(requestid, request, requesttype, requestrawdata) - if self.__ispersonalrequest(requesttype): - baserequestinfo['additionalPersonalInfo'] = self.__prepareadditionalpersonalinfo(requestrawdata) - return baserequestinfo - elif request != {} and request['version'] != 1 and request['sourceofsubmission'] != "intake": - request['requestrawdata']['currentState'] = request['status'] - request['requestrawdata']['requeststatuslabel'] = request['requeststatuslabel'] - request['requestrawdata']['lastStatusUpdateDate'] = FOIRawRequest.getLastStatusUpdateDate(requestid, request['status']).strftime(self.__generaldateformat()) - if request['requeststatuslabel'] == StateName.closed.name: - request['requestrawdata']['stateTransition']= FOIRawRequest.getstatesummary(requestid) - request['requestrawdata']['wfinstanceid'] = request['wfinstanceid'] - request['requestrawdata']['closedate']= self.__getclosedate(request['closedate']) - request['requestrawdata']['isiaorestricted']= request['isiaorestricted'] if request['isiaorestricted'] is not None else False - return request['requestrawdata'] - elif request != {} and request['sourceofsubmission'] == "intake": - requestrawdata = request['requestrawdata'] - requesttype = requestrawdata['requestType'] - additionalpersonalinfo = None - if self.__ispersonalrequest(requesttype) and requestrawdata.get('additionalPersonalInfo') is not None: - additionalpersonalinfo = self.__prepareadditionalpersonalinfoforintakesubmission(requestrawdata) - - request['requestrawdata']['additionalPersonalInfo'] = additionalpersonalinfo - - request['requestrawdata']['wfinstanceid'] = request['wfinstanceid'] - request['requestrawdata']['currentState'] = request['status'] - requeststatus = FOIRequestStatus().getrequeststatus(request['status']) - request['requestrawdata']['requeststatuslabel'] = requeststatus['statuslabel'] - request['requestrawdata']['lastStatusUpdateDate'] = FOIRawRequest.getLastStatusUpdateDate(requestid, request['status']).strftime(self.__generaldateformat()) - request['requestrawdata']['stateTransition']= FOIRawRequest.getstatesummary(requestid) - request['requestrawdata']['closedate']= self.__getclosedate(request['closedate']) - request['requestrawdata']['isiaorestricted']= request['isiaorestricted'] if request['isiaorestricted'] is not None else False - return request['requestrawdata'] - else: - return None - - def __getclosedate(self, requestclosedate): - closedate = parse(requestclosedate).strftime(self.__generaldateformat()) if requestclosedate is not None else None - return closedate - - def getrawrequestfieldsforid(self, requestid, fields): - request = FOIRawRequest.get_request(requestid) - fieldsresp = {} - for field in fields: - if field == "ministries" and request['status'] == 'Archived': - fieldsresp['openedMinistries']= FOIMinistryRequest.getministriesopenedbyuid(request["requestid"]) - return fieldsresp - - def getaxisequestids(self): - return FOIRawRequest.getDistinctAXISRequestIds() - - def getcountofaxisequestidbyaxisequestid(self, axisrequestid): - return FOIRawRequest.getCountOfAXISRequestIdbyAXISRequestId(axisrequestid) - - def __attachministriesinfo(self,request): - if request != {} and request['status'] == 'Archived': - request['requestrawdata']['openedMinistries']= FOIMinistryRequest.getministriesopenedbyuid(request["requestid"]) - return request - - def __preparebaserequestinfo(self, requestid, request, requesttype, requestrawdata): - contactinfo = requestrawdata.get('contactInfo') - dt = maya.parse(request['created_at']).datetime(to_timezone='America/Vancouver', naive=False) - _createddate = dt - decriptiontimeframe = requestrawdata.get('descriptionTimeframe') - contactinfooptions = requestrawdata.get('contactInfoOptions') - _fromdate = parse(decriptiontimeframe['fromDate']) - _todate = parse(decriptiontimeframe['toDate']) - assignee = None - if ("assignedto" in request and request["assignedto"] not in (None,'')): - assignee = FOIAssignee.getassignee(request["assignedto"]) - return {'id': request['requestid'], - 'wfinstanceid': request['wfinstanceid'], - 'ispiiredacted': request['ispiiredacted'], - 'isiaorestricted': request['isiaorestricted'] if request['isiaorestricted'] is not None else False, - 'sourceOfSubmission': request['sourceofsubmission'], - 'requestType': requesttype, - 'firstName': contactinfo['firstName'], - 'middleName': requestrawdata['contactInfo']['middleName'], - 'lastName': contactinfo['lastName'], - 'businessName': contactinfo['businessName'], - 'currentState': request['status'], - 'receivedDate': _createddate.strftime('%Y %b, %d'), - 'receivedDateUF': _createddate.strftime('%Y-%m-%d %H:%M:%S.%f'), - 'assignedGroup': request["assignedgroup"] if "assignedgroup" in request else "Unassigned", - 'assignedTo': request["assignedto"] if "assignedto" in request else "Unassigned", - 'assignedToFirstName': assignee["firstname"] if assignee is not None and "firstname" in assignee else None, - 'assignedToLastName': assignee["lastname"] if assignee is not None and "lastname" in assignee else None, - 'xgov': 'No', - 'idNumber': 'U-00' + str(request['requestid']), - 'axisRequestId': request['axisrequestid'], - 'axisSyncDate': request['axissyncdate'], - 'linkedRequests': request['linkedrequests'], - 'email': contactinfooptions['email'], - 'phonePrimary': contactinfooptions['phonePrimary'], - 'phoneSecondary': contactinfooptions['phoneSecondary'], - 'address': contactinfooptions['address'], - 'city': contactinfooptions['city'], - 'postal': contactinfooptions['postal'], - 'province': contactinfooptions['province'], - 'country': contactinfooptions['country'], - 'description': decriptiontimeframe['description'], - 'fromDate': _fromdate.strftime(self.__generaldateformat()), - 'toDate': _todate.strftime(self.__generaldateformat()), - 'correctionalServiceNumber': decriptiontimeframe['correctionalServiceNumber'], - 'publicServiceEmployeeNumber': decriptiontimeframe['publicServiceEmployeeNumber'], - 'topic': decriptiontimeframe['topic'], - 'selectedMinistries': requestrawdata['ministry']['selectedMinistry'], - 'lastStatusUpdateDate': FOIRawRequest.getLastStatusUpdateDate(requestid, request['status']).strftime(self.__generaldateformat()), - 'stateTransition': FOIRawRequest.getstatesummary(requestid), - 'closedate': request['closedate'].strftime(self.__generaldateformat()) if request['closedate'] is not None else None - } - - def __prepareadditionalpersonalinfo(self, requestrawdata): - childinformation = requestrawdata.get('childInformation') - anotherpersoninformation = requestrawdata.get('anotherInformation') - adoptiveparents = requestrawdata.get('adoptiveParents') - - haschildinfo = self.__ispersonalinfopresent(childinformation) - hasanotherpersoninfo = self.__ispersonalinfopresent(anotherpersoninformation) - hasadoptiveparentinfo = self.__ispersonalinfopresent(adoptiveparents) - contactinfo = requestrawdata.get('contactInfo') - return { - 'alsoKnownAs': contactinfo['alsoKnownAs'], - 'requestFor': requestrawdata['selectAbout'], - 'birthDate': parse(contactinfo['birthDate']).strftime(self.__generaldateformat()) if contactinfo.get('birthDate', None) is not None else '', - - 'childFirstName': self.__getpropertyvalue(childinformation,'firstName', haschildinfo), - 'childMiddleName': self.__getpropertyvalue(childinformation,'middleName', haschildinfo), - 'childLastName': self.__getpropertyvalue(childinformation,'lastName', haschildinfo), - 'childAlsoKnownAs': self.__getpropertyvalue(childinformation,'alsoKnownAs', haschildinfo), - 'childBirthDate': parse(childinformation['dateOfBirth']).strftime(self.__generaldateformat()) if haschildinfo and childinformation['dateOfBirth'] is not None else '', - - 'anotherFirstName': self.__getpropertyvalue(anotherpersoninformation,'firstName', hasanotherpersoninfo), - 'anotherMiddleName': self.__getpropertyvalue(anotherpersoninformation,'middleName', hasanotherpersoninfo), - 'anotherLastName': self.__getpropertyvalue(anotherpersoninformation,'lastName', hasanotherpersoninfo), - 'anotherAlsoKnownAs': self.__getpropertyvalue(anotherpersoninformation,'alsoKnownAs', hasanotherpersoninfo), - 'anotherBirthDate': parse(anotherpersoninformation['dateOfBirth']).strftime(self.__generaldateformat()) if hasanotherpersoninfo and anotherpersoninformation['dateOfBirth'] is not None else '', - - 'adoptiveMotherFirstName': self.__getpropertyvalue(adoptiveparents,'motherFirstName', hasadoptiveparentinfo), - 'adoptiveMotherLastName': self.__getpropertyvalue(adoptiveparents,'motherLastName', hasadoptiveparentinfo), - 'adoptiveFatherLastName': self.__getpropertyvalue(adoptiveparents,'fatherLastName', hasadoptiveparentinfo), - 'adoptiveFatherFirstName': self.__getpropertyvalue(adoptiveparents,'fatherFirstName', hasadoptiveparentinfo) - } - - def __prepareadditionalpersonalinfoforintakesubmission(self,requestrawdata): - _childandanotherpersoninfo = requestrawdata['additionalPersonalInfo'] - additionalpersonalinfo = { - 'childFirstName': _childandanotherpersoninfo['childFirstName'] if _childandanotherpersoninfo.get('childFirstName') is not None else '', - 'childMiddleName': _childandanotherpersoninfo['childMiddleName'] if _childandanotherpersoninfo.get('childMiddleName') is not None else '', - 'childLastName': _childandanotherpersoninfo['childLastName'] if _childandanotherpersoninfo.get('childLastName') is not None else '', - 'childAlsoKnownAs':_childandanotherpersoninfo['childAlsoKnownAs'] if _childandanotherpersoninfo.get('childAlsoKnownAs') is not None else '', - 'childBirthDate': _childandanotherpersoninfo['childBirthDate'] if _childandanotherpersoninfo.get('childBirthDate') is not None else '', - 'anotherFirstName': _childandanotherpersoninfo['anotherFirstName'] if _childandanotherpersoninfo.get('anotherFirstName') is not None else '', - 'anotherMiddleName': _childandanotherpersoninfo['anotherMiddleName'] if _childandanotherpersoninfo.get('anotherMiddleName') is not None else '', - 'anotherLastName':_childandanotherpersoninfo['anotherLastName'] if _childandanotherpersoninfo.get('anotherLastName') is not None else '', - 'anotherAlsoKnownAs': _childandanotherpersoninfo['anotherAlsoKnownAs'] if _childandanotherpersoninfo.get('anotherAlsoKnownAs') is not None else '', - 'anotherBirthDate': _childandanotherpersoninfo['anotherBirthDate'] if _childandanotherpersoninfo.get('anotherBirthDate') is not None else '', - 'personalHealthNumber' : _childandanotherpersoninfo['personalHealthNumber'] if _childandanotherpersoninfo.get('personalHealthNumber') is not None else '', - 'birthDate': _childandanotherpersoninfo['birthDate'] if _childandanotherpersoninfo.get('birthDate') is not None else '' - } - return additionalpersonalinfo - - def __getpropertyvalue(self, inputschema, property, criteria): - return inputschema[property] if inputschema is not None and criteria else '' - - def __ispersonalrequest(self, requesttype): - return True if requesttype == 'personal' else False - - def __ispersonalinfopresent(self, criteria): - return True if criteria != None else False - - def __generaldateformat(self): - return '%Y-%m-%d' diff --git a/historical-search-api/request_api/services/rawrequestservice.py b/historical-search-api/request_api/services/rawrequestservice.py deleted file mode 100644 index c39c67b85..000000000 --- a/historical-search-api/request_api/services/rawrequestservice.py +++ /dev/null @@ -1,153 +0,0 @@ - -import re -from typing import Counter - -from flask.signals import request_started -from sqlalchemy.sql.expression import false -from request_api.models.FOIRawRequests import FOIRawRequest -import json -import asyncio -from request_api.utils.redispublisher import RedisPublisherService -from request_api.services.workflowservice import workflowservice -from request_api.services.documentservice import documentservice -from request_api.services.eventservice import eventservice -from request_api.services.rawrequest.rawrequestservicegetter import rawrequestservicegetter -from request_api.exceptions import BusinessException, Error -from request_api.models.default_method_result import DefaultMethodResult -from request_api.models.FOIRawRequestWatchers import FOIRawRequestWatcher -from request_api.services.foirequest.requestserviceconfigurator import requestserviceconfigurator -from request_api.utils.enums import StateName -import logging - -class rawrequestservice: - """ FOI Raw Request management service - - This service class manages all CRUD operations related to an FOI unopened Request - - """ - - def saverawrequest(self, requestdatajson, sourceofsubmission, userid,notes): - assigneegroup = requestdatajson["assignedGroup"] if requestdatajson.get("assignedGroup") != None else None - assignee = requestdatajson["assignedTo"] if requestdatajson.get("assignedTo") not in (None,'') else None - assigneefirstname = requestdatajson["assignedToFirstName"] if requestdatajson.get("assignedToFirstName") != None else None - assigneemiddlename = requestdatajson["assignedToMiddleName"] if requestdatajson.get("assignedToMiddleName") != None else None - assigneelastname = requestdatajson["assignedToLastName"] if requestdatajson.get("assignedToLastName") != None else None - ispiiredacted = requestdatajson["ispiiredacted"] if 'ispiiredacted' in requestdatajson else False - axisrequestid = requestdatajson["axisRequestId"] if 'axisRequestId' in requestdatajson else None - isaxisrequestidpresent = False - if axisrequestid is not None: - isaxisrequestidpresent = self.isaxisrequestidpresent(axisrequestid) - axissyncdate = requestdatajson["axisSyncDate"] if 'axisSyncDate' in requestdatajson else None - linkedrequests = requestdatajson["linkedRequests"] if 'linkedRequests' in requestdatajson else None - requirespayment = rawrequestservice.doesrequirepayment(requestdatajson) if sourceofsubmission == "onlineform" else False - if axisrequestid is None or isaxisrequestidpresent == False: - result = FOIRawRequest.saverawrequest( - _requestrawdata=requestdatajson, - sourceofsubmission= sourceofsubmission, - ispiiredacted=ispiiredacted, - userid= userid, - assigneegroup=assigneegroup, - assignee=assignee, - requirespayment=requirespayment, - notes=notes, - assigneefirstname=assigneefirstname, - assigneemiddlename=assigneemiddlename, - assigneelastname=assigneelastname, - axisrequestid=axisrequestid, - axissyncdate=axissyncdate, - linkedrequests=linkedrequests - ) - else: - raise ValueError("Duplicate AXIS Request ID") - if result.success: - redispubservice = RedisPublisherService() - data = {} - data['id'] = result.identifier - data['assignedGroup'] = assigneegroup - data['assignedTo'] = assignee - json_data = json.dumps(data) - try: - workflowservice().createinstance(redispubservice.foirequestqueueredischannel, json_data) - except Exception as ex: - logging.error(ex) - asyncio.ensure_future(redispubservice.publishrequest(json_data)) - return result - - @staticmethod - def doesrequirepayment(requestdatajson): - if 'requestType' not in requestdatajson or 'requestType' not in requestdatajson['requestType']: - raise BusinessException(Error.DATA_NOT_FOUND) - if requestdatajson['requestType']['requestType'] == "personal": - return False - if 'contactInfo' in requestdatajson: - if requestdatajson['requestType']['requestType'] == "general": - if 'IGE' in requestdatajson['contactInfo'] and requestdatajson['contactInfo']['IGE']: - return False - return True - elif requestdatajson['requestType']['requestType'] == "personal": - return False - else: - if 'requiresPayment' not in requestdatajson: - raise BusinessException(Error.DATA_NOT_FOUND) - return requestdatajson['requiresPayment'] - raise BusinessException(Error.DATA_NOT_FOUND) - - def saverawrequestversion(self, _requestdatajson, _requestid, _assigneegroup, _assignee, status, userid, assigneefirstname, assigneemiddlename, assigneelastname, statuslabel, actiontype=None): - ispiiredacted = _requestdatajson["ispiiredacted"] if 'ispiiredacted' in _requestdatajson else False - #Get documents - if actiontype == "assignee": - result = FOIRawRequest.saverawrequestassigneeversion(_requestid, _assigneegroup, _assignee, userid, assigneefirstname, assigneemiddlename, assigneelastname) - else: - result = FOIRawRequest.saverawrequestversion(_requestdatajson, _requestid, _assigneegroup, _assignee, status,ispiiredacted, userid, statuslabel, assigneefirstname, assigneemiddlename, assigneelastname) - documentservice().createrawrequestdocumentversion(_requestid) - return result - - def saverawrequestiaorestricted(self,_requestid,_iaorestricted,_updatedby): - result = FOIRawRequest.saveiaorestrictedrawrequest(_requestid,_iaorestricted,_updatedby) - return result - - def updateworkflowinstance(self, wfinstanceid, requestid, userid): - return FOIRawRequest.updateworkflowinstance(wfinstanceid, requestid, userid) - - def updateworkflowinstancewithstatus(self, wfinstanceid, requestid,notes, userid): - return FOIRawRequest.updateworkflowinstancewithstatus(wfinstanceid,requestid,notes, userid) - - def posteventtoworkflow(self, id, requestsschema, status): - pid = workflowservice().syncwfinstance("rawrequest", id) - return workflowservice().postunopenedevent(id, pid, requestsschema, status) - - def getrawrequests(self): - return rawrequestservicegetter().getallrawrequests() - - def getrawrequest(self, requestid): - return rawrequestservicegetter().getrawrequestforid(requestid) - - def getrawrequestfields(self, requestid, fields): - return rawrequestservicegetter().getrawrequestfieldsforid(requestid, fields) - - def getstatus(self, foirequest): - statuslabel = foirequest["requeststatuslabel"] if "requeststatuslabel" in foirequest else None - if statuslabel is not None: - try: - return requestserviceconfigurator().getstatusname(statuslabel), statuslabel - # if statusid== 4: - # return 'Redirect' - # if statusid == 3: - # return 'Closed' - # if statusid == 16: - # return 'Peer Review' - except KeyError: - print("Key Error on requeststatusid, ignore will be intake in Progress") - return StateName.intakeinprogress.value, StateName.intakeinprogress.name - - def getaxisequestids(self): - return rawrequestservicegetter().getaxisequestids() - - def isaxisrequestidpresent(self, axisrequestid): - countofaxisrequestid = rawrequestservicegetter().getcountofaxisequestidbyaxisequestid(axisrequestid) - if countofaxisrequestid > 0: - return True - return False - - def israwrequestwatcher(self,requestid, userid): - return FOIRawRequestWatcher.isawatcher(requestid,userid) \ No newline at end of file diff --git a/historical-search-api/request_api/services/receivedmodeservice.py b/historical-search-api/request_api/services/receivedmodeservice.py deleted file mode 100644 index d425000a6..000000000 --- a/historical-search-api/request_api/services/receivedmodeservice.py +++ /dev/null @@ -1,8 +0,0 @@ -from request_api.models.ReceivedModes import ReceivedMode - -class receivedmodeservice: - - def getreceivedmodes(self): - """ Returns the active records - """ - return ReceivedMode.getreceivedmodes() \ No newline at end of file diff --git a/historical-search-api/request_api/services/records/recordservicebase.py b/historical-search-api/request_api/services/records/recordservicebase.py deleted file mode 100644 index 400dd8e6e..000000000 --- a/historical-search-api/request_api/services/records/recordservicebase.py +++ /dev/null @@ -1,45 +0,0 @@ -from os import stat, path,getenv -from re import VERBOSE -from request_api.utils.constants import FILE_CONVERSION_FILE_TYPES, DEDUPE_FILE_TYPES, NONREDACTABLE_FILE_TYPES -from request_api.models.FOIRequestRecords import FOIRequestRecord -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -from request_api.models.FOIMinistryRequestDivisions import FOIMinistryRequestDivision -from request_api.services.external.eventqueueservice import eventqueueservice -from request_api.models.default_method_result import DefaultMethodResult -from request_api.auth import auth, AuthHelper -import json -from datetime import datetime -import maya -import uuid -import requests -import logging - -class recordservicebase: - """ This class consolidates retrival of FOI Records for actors: iao and ministry. - """ - docreviewerapiurl = getenv("FOI_DOCREVIEWER_BASE_API_URL") - docreviewerapitimeout = getenv("FOI_DOCREVIEWER_BASE_API_TIMEOUT") - - def makedocreviewerrequest(self, method, url, payload=None): - token = AuthHelper.getauthtoken() - try: - response = requests.request( - method=method, - url=self.docreviewerapiurl+url, - data=json.dumps(payload), - headers={'Authorization': token, 'Content-Type': 'application/json'}, - timeout=float(self.docreviewerapitimeout) - ) - response.raise_for_status() - return response.json(), None - except requests.exceptions.HTTPError as err: - logging.error("Doc Reviewer API returned the following message: {0} - {1}".format(err.response.status_code, err.response.text)) - return None, err - except requests.exceptions.RequestException as err: - logging.error(err) - return None, err - - def isvalid(self, property, dict): - if property in dict and dict[property] is not None: - return True - return False \ No newline at end of file diff --git a/historical-search-api/request_api/services/records/recordservicegetter.py b/historical-search-api/request_api/services/records/recordservicegetter.py deleted file mode 100644 index c6997efdd..000000000 --- a/historical-search-api/request_api/services/records/recordservicegetter.py +++ /dev/null @@ -1,272 +0,0 @@ -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 -import json -from datetime import datetime -import maya -import uuid -import logging -import copy -from request_api.services.records.recordservicebase import recordservicebase - - -class recordservicegetter(recordservicebase): - """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: - _metadata = FOIMinistryRequest.getmetadata(ministryrequestid) - divisions = ProgramAreaDivision.getallprogramareatags(_metadata["programareaid"]) - - uploadedrecords = FOIRequestRecord.fetch(requestid, ministryrequestid) - batchids = [] - resultrecords = [] - if len(uploadedrecords) > 0: - computingresponses, err = self.getdatafromdocreviewer(uploadedrecords, 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): - 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 - result["dedupedfiles"] = _dedupedfiles - result["removedfiles"] = _removedfiles - result["batchcount"] = len(batchids) - result["records"] = resultrecords - except Exception as exp: - print("ERROR Happened while fetching records :.{0}".format(exp)) - logging.info(exp) - raise exp - return result - - def getdatafromdocreviewer(self, uploadedrecords, ministryrequestid): - if len(uploadedrecords) > 0: - computingresponses, err = self.makedocreviewerrequest( - "GET", "/api/dedupestatus/{0}".format(ministryrequestid) - ) - return computingresponses, err - - 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["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"] - 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["isconverted"] = self.__getisconverted(attachment) - _computingresponse_err = self.__getcomputingerror(attachment) - if _computingresponse_err is not None: - _attachement["failed"] = _computingresponse_err - _record["attachments"].append(_attachement) - else: - _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", []) - for division in attribute_divisions: - _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"]), - ) - 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 - - def __mergeduplicatedivisions( - self, _resultrecords, duplicatemasterid, resultmasterid, divisions - ): - for entry in _resultrecords: - if "documentmasterid" in entry and int(entry["documentmasterid"]) == int( - duplicatemasterid - ): - entattributes = entry["attributes"] - 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): - attattributes = attachment["attributes"] - 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", []) - - def __mergedivisions(self, _attributes, divisions, resultmasterid=False): - srcdivisions = self.__getrecorddivisions(_attributes) - merged = srcdivisions - for entry in divisions: - isduplicate = False - if resultmasterid: - entry["duplicatemasterid"] = resultmasterid - for srcdivision in srcdivisions: - if entry["divisionid"] == srcdivision["divisionid"]: - isduplicate = True - if isduplicate == False: - merged.append(entry) - return merged - - 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"] - ] - return filtered_response[0] if len(filtered_response) > 0 else [] - elif filterby == "parentid": - filtered_response = [x for x in response if x["isattachment"] == True] - return self.__getattachments(filtered_response, [], data) - else: - logging.info("not matched") - - 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"] - ) - 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 - ): - result.append(entry) - else: - filtered.append(entry) - 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 - - def __getcomputingsummary(self, computingresponse): - _convertedfiles = _dedupedfiles = _removedfiles = 0 - for entry in computingresponse: - if entry["conversionstatus"] == "completed": - _convertedfiles += 1 - if entry["deduplicationstatus"] == "completed": - _dedupedfiles += 1 - if entry["isduplicate"] == True: - _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") - return record - - def __getdivisionname(self, divisions, divisionid): - for division in divisions: - if division["divisionid"] == divisionid: - return division["name"] - return None diff --git a/historical-search-api/request_api/services/recordservice.py b/historical-search-api/request_api/services/recordservice.py deleted file mode 100644 index 887669f53..000000000 --- a/historical-search-api/request_api/services/recordservice.py +++ /dev/null @@ -1,344 +0,0 @@ - -from os import stat, path,getenv -from re import VERBOSE -from request_api.utils.constants import FILE_CONVERSION_FILE_TYPES, DEDUPE_FILE_TYPES, NONREDACTABLE_FILE_TYPES -from request_api.models.FOIRequestRecords import FOIRequestRecord -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -from request_api.models.FOIMinistryRequestDivisions import FOIMinistryRequestDivision -from request_api.services.external.eventqueueservice import eventqueueservice -from request_api.models.default_method_result import DefaultMethodResult -from request_api.auth import auth, AuthHelper -import json -from datetime import datetime -import maya -import uuid -import requests -import logging -from request_api.services.records.recordservicegetter import recordservicegetter -from request_api.services.records.recordservicebase import recordservicebase - -class recordservice(recordservicebase): - """ FOI record management service - """ - conversionstreamkey = getenv('EVENT_QUEUE_CONVERSION_STREAMKEY') - largefileconversionstreamkey = getenv('EVENT_QUEUE_CONVERSION_LARGE_FILE_STREAM_KEY') - dedupestreamkey = getenv('EVENT_QUEUE_DEDUPE_STREAMKEY') - largefilededupestreamkey = getenv('EVENT_QUEUE_DEDUPE_LARGE_FILE_STREAMKEY') - pdfstitchstreamkey = getenv('EVENT_QUEUE_PDFSTITCH_STREAMKEY') - dedupelargefilesizelimit= getenv('DEDUPE_STREAM_SEPARATION_FILE_SIZE_LIMIT',104857600) - conversionlargefilesizelimit= getenv('CONVERSION_STREAM_SEPARATION_FILE_SIZE_LIMIT',3145728) - stitchinglargefilesizelimit= getenv('STITCHING_STREAM_SEPARATION_FILE_SIZE_LIMIT',524288000) - pdfstitchstreamkey_largefiles = getenv('EVENT_QUEUE_PDFSTITCH_LARGE_FILE_STREAMKEY') - pagecalculatorstreamkey = getenv('EVENT_QUEUE_PAGECALCULATOR_STREAM_KEY') - - def create(self, requestid, ministryrequestid, recordschema, userid): - """Creates a record for a user with document details passed in for an opened request. - """ - return self.__bulkcreate(requestid, ministryrequestid, recordschema.get("records"), userid) - - def fetch(self, 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 = [] - recordids = [r['recordid'] for r in requestdata['records'] if r.get('recordid') is not None] - response = DefaultMethodResult(True, 'No recordids') - if(len(recordids) > 0): - records = FOIRequestRecord.getrecordsbyid(recordids) - for record in records: - record['attributes'] = json.loads(record['attributes']) - if not requestdata['isdelete']: - record['attributes']['divisions'] = requestdata['divisions'] - record.update({'updated_at': datetime.now(), 'updatedby': userid, 'isactive': not requestdata['isdelete']}) - record['version'] += 1 - newrecord = FOIRequestRecord() - newrecord.__dict__.update(record) - newrecords.append(newrecord) - response = FOIRequestRecord.create(newrecords) - if response.success: - if requestdata['isdelete']: - _apiresponse, err = self.makedocreviewerrequest('POST', '/api/document/delete', {'ministryrequestid': ministryrequestid, 'filepaths': [record['filepath'] for record in requestdata['records']]}) - else: - _apiresponse, err = self.makedocreviewerrequest('POST', '/api/document/update', {'ministryrequestid': ministryrequestid, 'documentmasterids': [record['documentmasterid'] for record in requestdata['records']], 'divisions': requestdata['divisions']}) - if err: - return DefaultMethodResult(False,'Error in contacting Doc Reviewer API', -1, [record['documentmasterid'] for record in requestdata['records']]) - return DefaultMethodResult(True,'Record updated in Doc Reviewer DB', -1, [record['documentmasterid'] for record in requestdata['records']]) - else: - return DefaultMethodResult(False,'Error in updating Record', -1, [record['documentmasterid'] for record in requestdata['records']]) - - def retry(self, _requestid, ministryrequestid, data): - _ministryrequest = FOIMinistryRequest.getrequestbyministryrequestid(ministryrequestid) - for record in data['records']: - _filepath, extension = path.splitext(record['s3uripath']) - extension = extension.lower() - if record['service'] == 'deduplication': - if extension not in DEDUPE_FILE_TYPES: - return DefaultMethodResult(False,'Dedupe only accepts the following formats: ' + ', '.join(DEDUPE_FILE_TYPES), -1, record['recordid']) - else: - streamkey = self.dedupestreamkey - elif record['service'] == 'conversion': - if extension not in FILE_CONVERSION_FILE_TYPES: - return DefaultMethodResult(False,'File Conversion only accepts the following formats: ' + ', '.join(FILE_CONVERSION_FILE_TYPES), -1, record['recordid']) - else: - streamkey = self.conversionstreamkey - else: - streamkey = self.dedupestreamkey if extension in DEDUPE_FILE_TYPES else self.conversionstreamkey - jobids, err = self.makedocreviewerrequest('POST', '/api/jobstatus', { - 'records': [record], - 'batch': record['attributes']['batch'], - 'trigger': record['trigger'], - 'ministryrequestid': ministryrequestid - }) - if err: - return DefaultMethodResult(False,'Error in contacting Doc Reviewer API', -1, ministryrequestid) - streamobject = { - "s3filepath": record['s3uripath'], - "requestnumber": _ministryrequest['axisrequestid'], - "bcgovcode": _ministryrequest['programarea.bcgovcode'], - "filename": record['filename'], - "ministryrequestid": ministryrequestid, - "attributes": json.dumps(record['attributes']), - "batch": record['attributes']['batch'], - "jobid": jobids[record['s3uripath']]['jobid'], - "documentmasterid": jobids[record['s3uripath']]['masterid'], - "trigger": record['trigger'], - "createdby": record['createdby'], - "usertoken": AuthHelper.getauthtoken() - } - if record.get('outputdocumentmasterid', False): - streamobject['outputdocumentmasterid'] = record['outputdocumentmasterid'] - if record['trigger'] == 'recordreplace' and record['attributes']['isattachment'] == True: - streamobject['originaldocumentmasterid'] = record['documentmasterid'] - return eventqueueservice().add(streamkey, streamobject) - - def replace(self, _requestid, ministryrequestid, recordid, recordschema, userid): - _ministryrequest = FOIMinistryRequest.getrequestbyministryrequestid(ministryrequestid) - _ministryversion = FOIMinistryRequest.getversionforrequest(ministryrequestid) - recordlist = [] - records = recordschema.get("records") - for _record in records: - replacingrecord = FOIRequestRecord.getrecordbyid(recordid) - _delteeapiresponse, err = self.makedocreviewerrequest('POST', '/api/document/delete', {'ministryrequestid': ministryrequestid, 'filepaths': [replacingrecord['s3uripath']]}) - - if err: - return DefaultMethodResult(False,'Error in contacting Doc Reviewer API', -1, recordid) - record = FOIRequestRecord(foirequestid=_requestid, replacementof = recordid if _record['replacementof'] is None else _record['replacementof'], ministryrequestid = ministryrequestid, ministryrequestversion=_ministryversion, - version = 1, createdby = userid, created_at = datetime.now()) - batch = str(uuid.uuid4()) - _record['attributes']['batch'] = batch - _record['attributes']['lastmodified'] = json.loads(replacingrecord['attributes'])['lastmodified'] - _filepath, extension = path.splitext(_record['filename']) - _record['attributes']['extension'] = extension - _record['attributes']['incompatible'] = extension.lower() in NONREDACTABLE_FILE_TYPES - record.__dict__.update(_record) - recordlist.append(record) - dbresponse = FOIRequestRecord.replace(recordid,recordlist) - if (dbresponse.success): - processingrecords = [{**record, **{"recordid": dbresponse.args[0][record['s3uripath']]['recordid']}} for record in records] - # record all jobs before sending first redis stream message to avoid race condition - jobids, err = self.makedocreviewerrequest('POST', '/api/jobstatus', { - 'records': processingrecords, - 'batch': batch, - 'trigger': 'recordupload', - 'ministryrequestid': ministryrequestid - }) - if err: - return DefaultMethodResult(False,'Error in contacting Doc Reviewer API', -1, ministryrequestid) - # send message to redis stream for each file - for entry in processingrecords: - _filename, extension = path.splitext(entry['s3uripath']) - extension = extension.lower() - if 'error' in jobids[entry['s3uripath']]: - logging.error("Doc Reviewer API was given an unsupported file type - no job triggered - Record ID: {0} File Name: {1} ".format(entry['recordid'], entry['filename'])) - else: - streamobject = { - "s3filepath": entry['s3uripath'], - "requestnumber": _ministryrequest['axisrequestid'], - "bcgovcode": _ministryrequest['programarea.bcgovcode'], - "filename": entry['filename'], - "ministryrequestid": ministryrequestid, - "attributes": json.dumps(entry['attributes']), - "batch": batch, - "jobid": jobids[entry['s3uripath']]['jobid'], - "documentmasterid": jobids[entry['s3uripath']]['masterid'], - "trigger": 'recordupload', - "createdby": userid, - "incompatible": 'true' if extension in NONREDACTABLE_FILE_TYPES else 'false', - "usertoken": AuthHelper.getauthtoken() - } - if extension in FILE_CONVERSION_FILE_TYPES: - eventqueueservice().add(self.conversionstreamkey, streamobject) - if extension in DEDUPE_FILE_TYPES: - eventqueueservice().add(self.dedupestreamkey, streamobject) - return dbresponse - - def triggerpdfstitchservice(self, requestid, ministryrequestid, recordschema, userid): - """Calls the BE job for stitching the documents. - """ - return self.__triggerpdfstitchservice(requestid, ministryrequestid, recordschema, userid) - - def getpdfstitchpackagetodownload(self, ministryid, category): - response, err = self.makedocreviewerrequest('GET', '/api/pdfstitch/{0}/{1}'.format(ministryid, category)) - if response is not None and "createdat" in response: - string_datetime = maya.parse(response["createdat"]).datetime(to_timezone='America/Vancouver', naive=False).strftime('%Y %b %d | %I:%M %p').upper() - response["createdat_datetime"] = string_datetime - return response - - def getpdfstichstatus(self, ministryid, category): - response, err = self.makedocreviewerrequest('GET', '/api/pdfstitchjobstatus/{0}/{1}'.format(ministryid, category)) - if response is not None and "status" in response: - return response.get("status") - return "" - - def isrecordschanged(self, ministryid, category): - response, err = self.makedocreviewerrequest('GET', '/api/recordschanged/{0}/{1}'.format(ministryid, category)) - if response is None: - return {"recordchanged": False} - return response - - def __triggerpdfstitchservice(self, requestid, ministryrequestid, message, userid): - """Call the BE job for stitching the documents. - """ - if self.pdfstitchstreamkey_largefiles or self.pdfstitchstreamkey: - job, err = self.makedocreviewerrequest('POST', '/api/pdfstitchjobstatus', { - "createdby": userid, - "ministryrequestid": ministryrequestid, - "inputfiles":message["attributes"], - "category": message["category"] - }) - if err: - return DefaultMethodResult(False,'Error in contacting Doc Reviewer API', -1, ministryrequestid) - streamobject = { - "jobid": job.get("id"), - "category": message["category"], - "requestnumber": message["requestnumber"], - "bcgovcode": message["bcgovcode"], - "createdby": userid, - "requestid": requestid, - "ministryrequestid": ministryrequestid, - "attributes": json.JSONEncoder().encode(message["attributes"]), - "totalfilesize": message["totalfilesize"] - } - if message["totalfilesize"] > int(self.stitchinglargefilesizelimit) and self.pdfstitchstreamkey_largefiles: - return eventqueueservice().add(self.pdfstitchstreamkey_largefiles, streamobject) - elif self.pdfstitchstreamkey: - return eventqueueservice().add(self.pdfstitchstreamkey, streamobject) - else: - print("pdfstitch stream key is missing. Message is not pushed to the stream.") - return DefaultMethodResult(False,'pdfstitch stream key is missing. Message is not pushed to the stream.', -1, ministryrequestid) - - def __bulkcreate(self, requestid, ministryrequestid, records, userid): - """Creates bulk records for a user with document details passed in for an opened request. - """ - _ministryversion = FOIMinistryRequest.getversionforrequest(ministryrequestid) - _ministryrequest = FOIMinistryRequest.getrequestbyministryrequestid(ministryrequestid) - recordlist = [] - batch = str(uuid.uuid4()) - for entry in records: - entry['attributes']['batch'] = batch - _filepath, extension = path.splitext(entry['filename']) - entry['attributes']['extension'] = extension - entry['attributes']['incompatible'] = extension.lower() in NONREDACTABLE_FILE_TYPES - record = FOIRequestRecord(foirequestid=_ministryrequest['foirequest_id'], ministryrequestid = ministryrequestid, ministryrequestversion=_ministryversion, - version = 1, createdby = userid, created_at = datetime.now()) - record.__dict__.update(entry) - recordlist.append(record) - dbresponse = FOIRequestRecord.create(recordlist) - if (dbresponse.success): - #processingrecords = [{**record, **{"recordid": dbresponse.args[0][record['s3uripath']]['recordid']}} for record in records if not record['attributes'].get('incompatible', False)] - processingrecords = [{**record, **{"recordid": dbresponse.args[0][record['s3uripath']]['recordid']}} for record in records] - - # record all jobs before sending first redis stream message to avoid race condition - jobids, err = self.makedocreviewerrequest('POST', '/api/jobstatus', { - 'records': processingrecords, - 'batch': batch, - 'trigger': 'recordupload', - 'ministryrequestid': ministryrequestid - }) - if err: - return DefaultMethodResult(False,'Error in contacting Doc Reviewer API', -1, ministryrequestid) - # send message to redis stream for each file - for entry in processingrecords: - _filename, extension = path.splitext(entry['s3uripath']) - extension = extension.lower() - if 'error' in jobids[entry['s3uripath']]: - logging.error("Doc Reviewer API was given an unsupported file type - no job triggered - Record ID: {0} File Name: {1} ".format(entry['recordid'], entry['filename'])) - else: - streamobject = { - "s3filepath": entry['s3uripath'], - "requestnumber": _ministryrequest['axisrequestid'], - "bcgovcode": _ministryrequest['programarea.bcgovcode'], - "filename": entry['filename'], - "ministryrequestid": ministryrequestid, - "attributes": json.dumps(entry['attributes']), - "batch": batch, - "jobid": jobids[entry['s3uripath']]['jobid'], - "documentmasterid": jobids[entry['s3uripath']]['masterid'], - "trigger": 'recordupload', - "createdby": userid, - "incompatible": 'true' if extension in NONREDACTABLE_FILE_TYPES else 'false', - "usertoken": AuthHelper.getauthtoken() - } - if extension in FILE_CONVERSION_FILE_TYPES: - if entry['attributes']['filesize'] < int(self.conversionlargefilesizelimit): - assignedstreamkey =self.conversionstreamkey - else: - assignedstreamkey =self.largefileconversionstreamkey - eventqueueservice().add(assignedstreamkey, streamobject) - if extension in DEDUPE_FILE_TYPES: - if 'convertedfilesize' in entry['attributes'] and entry['attributes']['convertedfilesize'] < int(self.dedupelargefilesizelimit) or 'convertedfilesize' not in entry['attributes'] and entry['attributes']['filesize'] < int(self.dedupelargefilesizelimit): - assignedstreamkey= self.dedupestreamkey - else: - assignedstreamkey= self.largefilededupestreamkey - eventqueueservice().add(assignedstreamkey, streamobject) - return dbresponse - - # this is for inflight request pagecount calculation option 1 - async def updatepagecount(self, ministryrequestid, userid): - streamobj = { - 'ministryrequestid': ministryrequestid - } - job, err = self.makedocreviewerrequest('POST', '/api/pagecalculatorjobstatus', streamobj) - if err: - return DefaultMethodResult(False,'Error in contacting Doc Reviewer API for pagecalculatorjobstatus', -1, ministryrequestid) - else: - streamobj["jobid"] = job.get("id") - streamobj["createdby"] = userid - eventqueueservice().add(self.pagecalculatorstreamkey, streamobj) - return DefaultMethodResult(True,'Pushed to PageCountCalculator stream', job.get("id"), ministryrequestid) - - # this is for inflight request pagecount calculation option 2 - async def calculatepagecount(self, requestid, ministryrequestid, userid): - uploadedrecords = FOIRequestRecord.fetch(requestid, ministryrequestid) - if len(uploadedrecords) > 0: - records, err = recordservicegetter().getdatafromdocreviewer(uploadedrecords, ministryrequestid) - if err is None: - pagecount = self.__calculatepagecount(records) - return FOIMinistryRequest().updaterecordspagecount(ministryrequestid, pagecount, userid) - return DefaultMethodResult(True,'No request to update', ministryrequestid) - - # this is for inflight request pagecount calculation option 2 - def __calculatepagecount(self, records): - print(f'records = {records}') - page_count = 0 - for record in records: - if self.__pagecountcalculationneeded(record): - page_count += record.get("pagecount", 0) - attachments = record.get("attachments", []) - for attachment in attachments: - if not attachment.get("isduplicate", False): - page_count += attachment.get("pagecount", 0) - return page_count - - def __pagecountcalculationneeded(self, record): - if not record.get("isduplicate", False) and not record["attributes"].get("isportfolio", False) and not record['attributes'].get('incompatible', False): - return True - return False - - - - - diff --git a/historical-search-api/request_api/services/requestservice.py b/historical-search-api/request_api/services/requestservice.py deleted file mode 100644 index 2bc99921a..000000000 --- a/historical-search-api/request_api/services/requestservice.py +++ /dev/null @@ -1,333 +0,0 @@ -from re import T - -from request_api.services.documentservice import documentservice -from request_api.services.workflowservice import workflowservice -from request_api.services.watcherservice import watcherservice -from request_api.services.commentservice import commentservice -from request_api.services.paymentservice import paymentservice -from request_api.services.foirequest.requestserviceconfigurator import ( - requestserviceconfigurator, -) -from request_api.services.foirequest.requestservicegetter import requestservicegetter -from request_api.services.foirequest.requestservicecreate import requestservicecreate -from request_api.services.foirequest.requestserviceupdate import requestserviceupdate -from request_api.services.applicantcorrespondence.applicantcorrespondencelog import ( - applicantcorrespondenceservice, -) -from request_api.services.subjectcodeservice import subjectcodeservice -from request_api.models.FOIRequestStatus import FOIRequestStatus -from request_api.models.FOIRawRequests import FOIRawRequest -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -from request_api.models.FOIMinistryRequestSubjectCodes import ( - FOIMinistryRequestSubjectCode, -) -from request_api.models.SubjectCodes import SubjectCode -from request_api.models.FOIRestrictedMinistryRequests import ( - FOIRestrictedMinistryRequest, -) -from request_api.utils.enums import StateName -from request_api.services.commons.duecalculator import duecalculator -from request_api.utils.commons.datetimehandler import datetimehandler -import os -import json - - -class requestservice: - """FOI Request management service - - This service class manages all CRUD operations related to an FOI opened Request - - """ - - def saverequest( - self, - foirequestschema, - userid, - foirequestid=None, - ministryid=None, - filenumber=None, - version=None, - rawrequestid=None, - wfinstanceid=None, - ): - return requestservicecreate().saverequest( - foirequestschema, - userid, - foirequestid, - ministryid, - filenumber, - version, - rawrequestid, - wfinstanceid, - ) - - def saverequestversion(self, foirequestschema, foirequestid, ministryid, userid): - nextstate = FOIRequestStatus.getrequeststatusbylabel( - foirequestschema["requeststatuslabel"] - ) - nextstatename = ( - nextstate.get("name") - if isinstance(nextstate, dict) and nextstate.get("name") not in (None, "") - else "" - ) - rev_foirequestschema = self.updateduedate( - foirequestid, - ministryid, - datetimehandler().gettoday(), - foirequestschema, - nextstatename, - ) - responseschema = requestservicecreate().saverequestversion( - rev_foirequestschema, foirequestid, ministryid, userid - ) - if "paymentExpiryDate" in foirequestschema and foirequestschema[ - "paymentExpiryDate" - ] not in (None, ""): - paymentservice().createpayment( - foirequestid, ministryid, foirequestschema, userid - ) - return responseschema - - def saveministryrequestversion( - self, ministryrequestschema, foirequestid, ministryid, userid, usertype=None - ): - return requestservicecreate().saveministryrequestversion( - ministryrequestschema, foirequestid, ministryid, userid, usertype - ) - - def updaterequest(self, foirequestschema, foirequestid, userid): - return requestserviceupdate().updaterequest( - foirequestschema, foirequestid, userid - ) - - def updateministryrequestduedate(self, ministryrequestid, duedate, userid): - return requestserviceupdate().updateministryrequestduedate( - ministryrequestid, duedate, userid - ) - - def postpaymentstatetransition( - self, requestid, ministryrequestid, nextstatename, paymentdate - ): - _foirequest = self.getrequest(requestid, ministryrequestid) - foirequest = self.updateduedate( - requestid, ministryrequestid, paymentdate, _foirequest, nextstatename - ) - status = FOIRequestStatus().getrequeststatus(nextstatename) - foirequest["requeststatuslabel"] = status["statuslabel"] - return requestservicecreate().saverequestversion( - foirequest, requestid, ministryrequestid, "Online Payment" - ) - - def updateduedate( - self, requestid, ministryrequestid, offholddate, foirequestschema, nextstatename - ): - foirequest = self.getrequest(requestid, ministryrequestid) - currentstatus = ( - foirequest["stateTransition"][0]["status"] - if "stateTransition" in foirequest - and len(foirequest["stateTransition"]) > 1 - else None - ) - # Check for Off Hold - if ( - currentstatus not in (None, "") - and currentstatus == StateName.onhold.value - and nextstatename != StateName.response.value - ): - skipcalculation = self.__skipduedatecalculation( - ministryrequestid, offholddate, currentstatus, nextstatename - ) - # Skip multiple off hold in a day - if skipcalculation == True: - calc_duedate, calc_cfrduedate = ( - foirequest["dueDate"], - foirequest["cfrDueDate"], - ) - else: - calc_duedate, calc_cfrduedate = self.calculateduedate( - ministryrequestid, foirequest, offholddate - ) - foirequestschema["dueDate"] = calc_duedate - foirequestschema["cfrDueDate"] = calc_cfrduedate - return foirequestschema - - def getrequest(self, foirequestid, foiministryrequestid): - return requestservicegetter().getrequest(foirequestid, foiministryrequestid) - - def getrequestdetailsforministry( - self, foirequestid, foiministryrequestid, authmembershipgroups - ): - return requestservicegetter().getrequestdetailsforministry( - foirequestid, foiministryrequestid, authmembershipgroups - ) - - def getrequestdetails(self, foirequestid, foiministryrequestid): - return requestservicegetter().getrequestdetails( - foirequestid, foiministryrequestid - ) - - def getrequestid(self, foiministryrequestid): - return FOIMinistryRequest.getrequest(foiministryrequestid)["foirequest_id"] - - def copywatchers(self, rawrequestid, ministries, userid): - watchers = watcherservice().getrawrequestwatchers(int(rawrequestid)) - for ministry in ministries: - for watcher in watchers: - watcherschema = { - "ministryrequestid": ministry["id"], - "watchedbygroup": watcher["watchedbygroup"], - "watchedby": watcher["watchedby"], - "isactive": True, - } - watcherservice().createministryrequestwatcher( - watcherschema, userid, None - ) - - def copycomments(self, rawrequestid, ministries, userid): - comments = commentservice().getrawrequestcomments(int(rawrequestid)) - for ministry in ministries: - commentservice().copyrequestcomment(ministry["id"], comments, userid) - - def copydocuments(self, rawrequestid, ministries, userid): - attachments = documentservice().getrequestdocuments( - int(rawrequestid), "rawrequest" - ) - for ministry in ministries: - documentservice().copyrequestdocuments(ministry["id"], attachments, userid) - - def copysubjectcode(self, subjectcode, ministries, userid): - if subjectcode: - for ministry in ministries: - subjectcodeservice().savesubjectcode( - ministry["id"], subjectcode, userid - ) - - def postopeneventtoworkflow(self, id, requestschema, ministries): - pid = workflowservice().syncwfinstance("rawrequest", requestschema["id"]) - workflowservice().postunopenedevent(id, pid, requestschema, StateName.open.value, ministries) - - def postfeeeventtoworkflow( - self, requestid, ministryrequestid, paymentstatus, nextstatename=None - ): - foirequestschema = self.getrequestdetails(requestid, ministryrequestid) - workflowservice().postfeeevent( - requestid, ministryrequestid, foirequestschema, paymentstatus, nextstatename - ) - - def posteventtoworkflow(self, id, requestschema, data, usertype): - requeststatuslabel = ( - requestschema.get("requeststatuslabel") - if "requeststatuslabel" in requestschema - else None - ) - status = ( - requestserviceconfigurator().getstatusname(requeststatuslabel) - if requeststatuslabel is not None - else None - ) - pid = workflowservice().syncwfinstance("ministryrequest", id) - workflowservice().postopenedevent( - id, pid, requestschema, data, status, usertype - ) - - def postcorrespondenceeventtoworkflow( - self, - requestid, - ministryrequestid, - applicantcorrespondenceid, - attributes, - templateid, - ): - foirequestschema = self.getrequestdetails(requestid, ministryrequestid) - templatedetails = applicantcorrespondenceservice().gettemplatebyid(templateid) - wfinstanceid = workflowservice().syncwfinstance( - "ministryrequest", ministryrequestid, True - ) - workflowservice().postcorrenspodenceevent( - wfinstanceid, - ministryrequestid, - foirequestschema, - applicantcorrespondenceid, - templatedetails.name, - attributes, - ) - - - - def calculateduedate(self, ministryrequestid, foirequest, paymentdate): - duedate_includeoffhold, cfrduedate_includeoffhold = self.__isincludeoffhold() - onhold_extend_days = duecalculator().getbusinessdaysbetween(foirequest["onholdTransitionDate"], paymentdate) - isoffhold_businessday = duecalculator().isbusinessday(paymentdate) - duedate_extend_days = onhold_extend_days + 1 if isoffhold_businessday == True and duedate_includeoffhold == True else onhold_extend_days - cfrduedate_extend_days = onhold_extend_days + 1 if isoffhold_businessday == True and cfrduedate_includeoffhold == True else onhold_extend_days - calc_duedate = duecalculator().addbusinessdays(foirequest["dueDate"], duedate_extend_days) - calc_cfrduedate = duecalculator().addbusinessdays(foirequest["cfrDueDate"], cfrduedate_extend_days) - return calc_duedate, calc_cfrduedate - - - - - def __skipduedatecalculation(self, ministryrequestid, offholddate, currentstatus="", nextstatename=""): - previousoffholddate = FOIMinistryRequest.getlastoffholddate(ministryrequestid) - if ( - currentstatus not in (None, "") - and currentstatus == StateName.onhold.value - and nextstatename not in (None, "") - and currentstatus == nextstatename - ): - return True - if previousoffholddate not in (None, ""): - previouspaymentdate_pst = datetimehandler().convert_to_pst( - previousoffholddate - ) - if ( - datetimehandler().getdate(previouspaymentdate_pst).date() - == datetimehandler().getdate(offholddate).date() - ): - return True - foiministry_request = FOIMinistryRequest.getrequest(ministryrequestid) - request_reopened = self.__hasreopened(ministryrequestid, "ministryrequest") - if foiministry_request['isoipcreview'] == True and request_reopened: - return True - return False - - def __isincludeoffhold(self): - payment_config_str = os.getenv("PAYMENT_CONFIG","") - if payment_config_str in (None, ""): - return True, True - _paymentconfig = json.loads(payment_config_str) - duedate_includeoffhold = True if _paymentconfig["duedate"]["includeoffhold"] == "Y" else False - cfrduedate_includeoffhold = True if _paymentconfig["cfrduedate"]["includeoffhold"] == "Y" else False - return duedate_includeoffhold, cfrduedate_includeoffhold - - # intake in progress to open: create a restricted request record for each selected ministries - def createrestrictedrequests(self, ministries, type, isrestricted, userid): - for ministry in ministries: - version = FOIMinistryRequest.getversionforrequest(ministry["id"]) - FOIRestrictedMinistryRequest.disablerestrictedrequests( - ministry["id"], type, userid - ) - FOIRestrictedMinistryRequest.saverestrictedrequest( - ministry["id"], type, isrestricted, version, userid - ) - - def saverestrictedrequest(self, ministryrequestid, type, isrestricted, userid): - version = FOIMinistryRequest.getversionforrequest(ministryrequestid) - FOIRestrictedMinistryRequest.disablerestrictedrequests( - ministryrequestid, type, userid - ) - return FOIRestrictedMinistryRequest.saverestrictedrequest( - ministryrequestid, type, isrestricted, version, userid - ) - - - def __hasreopened(self, requestid, requesttype): - if requesttype == "rawrequest": - states = FOIRawRequest.getstatesummary(requestid) - else: - states = FOIMinistryRequest.getstatesummary(requestid) - if len(states) > 0: - current_state = states[0] - if current_state != "Closed" and any(state['status'] == "Closed" for state in states): - return True - return False diff --git a/historical-search-api/request_api/services/subjectcodeservice.py b/historical-search-api/request_api/services/subjectcodeservice.py deleted file mode 100644 index 960e431e2..000000000 --- a/historical-search-api/request_api/services/subjectcodeservice.py +++ /dev/null @@ -1,50 +0,0 @@ -from request_api.models.SubjectCodes import SubjectCode -from request_api.models.FOIMinistryRequestSubjectCodes import FOIMinistryRequestSubjectCode -from request_api.models.FOIMinistryRequests import FOIMinistryRequest - -class subjectcodeservice: - - def getsubjectcodes(self): - """ Returns the active records - """ - return SubjectCode.getsubjectcodes() - - def getsubjectcodebyname(self, subjectcode): - """ Returns the subject code - """ - return SubjectCode.getsubjectcodebyname(subjectcode) - - def getsubjectcodebyid(self, subjectcodeid): - """ Returns the subject code - """ - return SubjectCode.getsubjectcodebyid(subjectcodeid) - - def getministrysubjectcode(self, ministryrequestid): - """ Returns the ministry subject code - """ - ministryrequestversion = self.__getministryversionforrequest(ministryrequestid) - return FOIMinistryRequestSubjectCode.getministrysubjectcode(ministryrequestid, ministryrequestversion) - - def savesubjectcode(self, ministryrequestid, subjectcode, userid): - """ Save subject code - """ - ministryrequestversion = self.__getministryversionforrequest(ministryrequestid) - subjectcode = self.getsubjectcodebyname(subjectcode) - return FOIMinistryRequestSubjectCode.savesubjectcode(ministryrequestid, ministryrequestversion, subjectcode['subjectcodeid'], userid) - - def getministrysubjectcodename(self, foiministryrequestid): - """ Returns the ministry subject code name - """ - ministrysubjectcode = self.getministrysubjectcode(foiministryrequestid) - if ministrysubjectcode: - subjectcode = self.getsubjectcodebyid(ministrysubjectcode['subjectcodeid']) - return subjectcode['name'] - else: - return '' - - def __getministryversionforrequest(self, requestid): - """ Returns the active version of the request id based on type. - """ - request = FOIMinistryRequest.getversionforrequest(requestid) - if request: - return request[0] \ No newline at end of file diff --git a/historical-search-api/request_api/services/unopenedreportservice.py b/historical-search-api/request_api/services/unopenedreportservice.py deleted file mode 100644 index 61fa1054a..000000000 --- a/historical-search-api/request_api/services/unopenedreportservice.py +++ /dev/null @@ -1,165 +0,0 @@ - -from request_api.models.FOIRawRequests import FOIRawRequest -from request_api.models.UnopenedReport import UnopenedReport -from request_api.services.email.senderservice import senderservice -from os import getenv -from datetime import timedelta, date -from jaro import jaro_winkler_metric -import json -import logging -from math import inf - -class unopenedreportservice: - """ - This service generates a report of unopened unactioned requests - - """ - - dayscutoff = getenv('UNOPENED_REPORT_CUTOFF_DAYS', 10) - waitdays = getenv('UNOPENED_REPORT_WAIT_DAYS', 5) - jarocutoff = getenv('UNOPENED_REPORT_JARO_CUTOFF', 0.8) - reportemail = getenv('UNOPENED_REPORT_EMAIL_RECIPIENT') - - - async def generateunopenedreport(self): - logging.info("begin unopened report generation") - startdate = date.today() - timedelta(days=int(self.dayscutoff)) - enddate = date.today() - timedelta(days=int(self.waitdays)) - requests = FOIRawRequest.getunopenedunactionedrequests(str(startdate), str(enddate)) - alerts = [] - alertdbrows = [] - for request in requests: - potentialmatches = FOIRawRequest.getpotentialactionedmatches(request) - if len(potentialmatches) == 0: - alert = UnopenedReport() - alert.rawrequestid = request['requestid'] - alert.date = date.today() - alert.rank = 1 - alertdbrows.append(alert) - alerts.append({"request": request, "rank": 1}) - else: - highscore = 0 - for match in potentialmatches: - match['score'] = jaro_winkler_metric( - request['requestrawdata']['descriptionTimeframe']['description'].replace('\n', ' ').replace('\t', ' '), - match['requestrawdata']['description'] - ) - if match['score'] > highscore: - highscore = match['score'] - isCFD = False - for m in request['requestrawdata']['ministry']['selectedMinistry']: - if m['code'] == 'MCF': - isCFD = True - if isCFD and len(potentialmatches) == 1 and potentialmatches[0]['requestrawdata']['description'] == 'CFD': - highscore = 1 - alert = UnopenedReport() - alert.rawrequestid = request['requestid'] - alert.date = date.today() - alert.rank = 2 - alert.potentialmatches = { - "highscore": round(highscore, 2), - "matches": [{ - "requestid": m["requestrawdata"]['axisRequestId'], - "similarity": round(m['score'], 2) - } for m in potentialmatches], - "isCFD": isCFD - } - alertdbrows.append(alert) - alerts.append({"request": request, "rank": 2, "potentialmatches": alert.potentialmatches}) - UnopenedReport.bulkinsert(alertdbrows) - alerts.sort(key=lambda a : a.get('potentialmatches', {'highscore': -1})['highscore']) - senderservice().send( - subject="Intake Unopened Request Report: " + str(date.today()), - content=self.generateemailhtml(alerts), - _messageattachmentlist=[], - requestjson={"email": self.reportemail, "topic": "Unopened Report"} - ) - return alerts - - - def generateemailhtml(self, alerts): - emailhtml = """ -

Unopened Report - """ + str(date.today()) + """

- -

This is a report for unopened requests in the past """ + self.dayscutoff + """ days that have not yet been actioned.

-

Rank 1: Very likely to be unactioned — unable to find a request with any matching applicant info

-
Unopened IDDate ReceivedMinistry SelectedApplicant First NameApplicant Last NamePayment StatusReceipt NumberApplication FeePotential MatchesDescription
U-00''' + str(alert['request']['requestid']) + '''''' + alert['request']['requestrawdata']['receivedDate'] + '''''' + for m in alert['request']['requestrawdata']['ministry']['selectedMinistry']: + emailhtml += (m['code'] + ' ') + emailhtml += '''''' + alert['request']['requestrawdata']['contactInfo']['firstName'] + '''''' + alert['request']['requestrawdata']['contactInfo']['lastName'] + '''''' + alert['request']['paymentstatus'] + '''''' + alert['request']['txnno'] + '''''' + alert['request']['fee'] + ''' + ''' + for m in alert['potentialmatches']['matches']: + emailhtml += ((m['requestid'] or '') + " - similarity: " + str(m['similarity']*100) + "%
") + emailhtml = emailhtml[:-4] + emailhtml += '''
''' + alert['request']['requestrawdata']['descriptionTimeframe']['description'][0:99] + '''...
- - - - - - - - - - - - """ - firstrank2 = True - logging.debug(alerts) - for alert in alerts: - if alert.get('potentialmatches') == None: - emailhtml += ''' - - - - - - - - - - - - ''' - else: - if firstrank2: - emailhtml += """
Unopened IDDate ReceivedMinistry SelectedApplicant First NameApplicant Last NamePayment StatusReceipt NumberApplication FeeDescription
U-00''' + str(alert['request']['requestid']) + '''''' + alert['request']['requestrawdata']['receivedDate'] + '''''' - for m in alert['request']['requestrawdata']['ministry']['selectedMinistry']: - emailhtml += (m['code'] + ' ') - emailhtml += '''''' + alert['request']['requestrawdata']['contactInfo']['firstName'] + '''''' + alert['request']['requestrawdata']['contactInfo']['lastName'] + '''''' + alert['request']['paymentstatus'] + '''''' + alert['request']['txnno'] + '''''' + alert['request']['fee'] + '''''' + alert['request']['requestrawdata']['descriptionTimeframe']['description'][0:99] + '''...
-

Rank 2: Possibly unactioned — requests found but some applicant info is mismatching — please double check

- - - - - - - - - - - - - - """ - firstrank2 = False - logging.debug(alert) - if alert['potentialmatches']['highscore'] > float(self.jarocutoff): - break - emailhtml += ''' - - - - - - - - - - - - - ''' - logging.debug(emailhtml) - logging.info("finished unopened report generation") - return emailhtml diff --git a/historical-search-api/request_api/services/userservice.py b/historical-search-api/request_api/services/userservice.py deleted file mode 100644 index 61e634b54..000000000 --- a/historical-search-api/request_api/services/userservice.py +++ /dev/null @@ -1,43 +0,0 @@ - -from os import stat -from re import VERBOSE - -from request_api.models.FOIUsers import FOIUser -from request_api.models.default_method_result import DefaultMethodResult -from request_api.models.OperatingTeams import OperatingTeam -from request_api.services.external.keycloakadminservice import KeycloakAdminService -import logging - -class userservice: - """ FOI user service - - """ - def syncusers(self): - operatingteams = OperatingTeam().getalloperatingteams() - for operatingteam in operatingteams: - groupmembers = KeycloakAdminService().getmembersbygroupnameandtype(operatingteam['name'], operatingteam['type']) - if groupmembers not in (None, '',[]) and len(groupmembers) > 0: - groupdata = groupmembers[0] - if 'members' in groupdata and len(groupdata['members']) > 0: - kcusers = groupdata['members'] - for user in kcusers: - self.__persistuser(user) - - return DefaultMethodResult(True,'Users synced for foi-mod') - - def __persistuser(self, user): - foiuser = FOIUser() - foiuser.username = user["origusername"] - foiuser.preferred_username = user["username"] - foiuser.firstname = user["firstname"] - foiuser.lastname = user["lastname"] - foiuser.email = user["email"] - foiuser.isactive = user["enabled"] - return FOIUser().saveuser(foiuser) - - - def getusers(self): - return FOIUser().getall() - - - \ No newline at end of file diff --git a/historical-search-api/request_api/services/watcherservice.py b/historical-search-api/request_api/services/watcherservice.py deleted file mode 100644 index e76544ab2..000000000 --- a/historical-search-api/request_api/services/watcherservice.py +++ /dev/null @@ -1,91 +0,0 @@ - -from os import stat -from re import VERBOSE -from request_api.models.FOIRequestWatchers import FOIRequestWatcher -from request_api.models.FOIRawRequestWatchers import FOIRawRequestWatcher -from request_api.models.FOIRawRequests import FOIRawRequest -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -import json -class watcherservice: - """ FOI watcher management service - """ - - def createrawrequestwatcher(self, data, userid, usergroups): - """Creates a watcher for a user with groups passed in for an unopened request. - """ - version = FOIRawRequest.getversionforrequest(data["requestid"]) - if 'watchedbygroup' in data: - return FOIRawRequestWatcher.savewatcher(data, version, userid) - else: - return FOIRawRequestWatcher.savewatcherbygroups(data, version, userid, self.__getwatchablegroups(usergroups)) - - - def getrawrequestwatchers(self, requestid): - """Retrieves all watchers associated with an unopened request. - """ - return FOIRawRequestWatcher.getwatchers(requestid) - - - def disablerawrequestwatchers(self, requestid, userid): - """Remove an user from the watched list of an unopened request. - """ - return FOIRawRequestWatcher.disablewatchers(requestid, userid) - - - def createministryrequestwatcher(self, data, userid, usergroups): - """Creates a watcher for a user with groups passed in for an opened request. - """ - version = FOIMinistryRequest.getversionforrequest(data["ministryrequestid"]) - if 'watchedbygroup' in data: - return FOIRequestWatcher.savewatcher(data, version, userid) - else: - return FOIRequestWatcher.savewatcherbygroups(data, version, userid, self.__getwatchablegroups(usergroups)) - - - def getministryrequestwatchers(self, ministryrequestid, isministrymember): - """Retrieves all watchers associated with an opened request. - """ - if isministrymember == True: - return FOIRequestWatcher.getMinistrywatchers(ministryrequestid) - else: - return FOIRequestWatcher.getNonMinistrywatchers(ministryrequestid) - - def getallministryrequestwatchers(self, ministryrequestid, isministryonly=False): - ministrywatchers = FOIRequestWatcher.getMinistrywatchers(ministryrequestid) - if isministryonly == False: - return ministrywatchers + FOIRequestWatcher.getNonMinistrywatchers(ministryrequestid) - return ministrywatchers - - - def disableministryrequestwatchers(self, ministryrequestid, userid): - """Remove an user from the watched list of an opened request. - """ - return FOIRequestWatcher.disablewatchers(ministryrequestid, userid) - - - def __getwatchablegroups(self, groups): - """Returns a list of filtered groups excluding black listed pattern. - """ - watchablegroups = [] - for group in groups: - if self.isexcludegroup(group) == False: - watchablegroups.append(group) - return watchablegroups - - - def isexcludegroup(self, input): - """Identifies whether the given input group is present in excluded groups. - """ - for group in self.__excludegrouppattern(): - if group in input.lower(): - return True - return False - - - def __excludegrouppattern(self): - """Exclude KC groups matching the patterns listed. - """ - return ["formsflow","realm","camunda"] - - - \ No newline at end of file diff --git a/historical-search-api/request_api/services/workflowservice.py b/historical-search-api/request_api/services/workflowservice.py deleted file mode 100644 index 1cbcbb1db..000000000 --- a/historical-search-api/request_api/services/workflowservice.py +++ /dev/null @@ -1,322 +0,0 @@ -import requests -import os -import json -from enum import Enum -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 -from request_api.services.paymentservice import paymentservice -from request_api.models.FOIRawRequests import FOIRawRequest -from request_api.models.FOIMinistryRequests import FOIMinistryRequest -from request_api.models.FOIRequests import FOIRequest -from request_api.utils.enums import StateName -import logging -from request_api.schemas.external.bpmschema import VariableSchema -from request_api.services.external.camundaservice import VariableType -""" -This class is reserved for workflow services integration. -Supported operations: claim - -__author__ = "sumathi.thirumani@aot-technologies.com" - -""" -class workflowservice: - - def createinstance(self, definitionkey, message): - response = bpmservice().createinstance(definitionkey, json.loads(message)) - if response is None: - raise Exception("Unable to create instance for key"+ definitionkey) - return response - - def postunopenedevent(self, id, wfinstanceid, requestsschema, status, ministries=None): - if wfinstanceid in (None,""): - logging.error("WF INSTANCE IS INVALID") - return - assignedgroup = requestsschema["assignedGroup"] if 'assignedGroup' in requestsschema else None - assignedto = requestsschema["assignedTo"] if 'assignedTo' in requestsschema else None - if status == UnopenedEvent.intakeinprogress.value: - messagename = MessageType.intakereopen.value if self.__hasreopened(id, "rawrequest") == True else MessageType.intakeclaim.value - return bpmservice().unopenedsave(wfinstanceid, assignedto, messagename) - else: - if status == UnopenedEvent.open.value: - metadata = json.dumps({"id": id, "status": status, "ministries": ministries, "assignedGroup": assignedgroup, "assignedTo": assignedto}) - else: - metadata = json.dumps({"id": id, "status": status, "assignedGroup": assignedgroup, "assignedTo": assignedto}) - return bpmservice().unopenedcomplete(wfinstanceid, metadata, MessageType.intakecomplete.value) - - def postopenedevent(self, id, wfinstanceid, requestsschema, data, newstatus, usertype, issync=False): - assignedgroup = self.__getopenedassigneevalue(requestsschema, "assignedgroup",usertype) - assignedto = self.__getopenedassigneevalue(requestsschema, "assignedto",usertype) - axisrequestid = self.__getvaluefromschema(requestsschema,"axisRequestId") - if data.get("ministries") is not None: - for ministry in data.get("ministries"): - filenumber = ministry["filenumber"] - if int(ministry["id"]) == int(id): - paymentexpirydate = paymentservice().getpaymentexpirydate(int(ministry["foirequestid"]), int(ministry["id"])) - previousstatus = self.__getpreviousministrystatus(id) if issync == False else self.__getprevioustatusbyversion(id, int(ministry["version"])) - oldstatus = self.__getministrystatus(filenumber, ministry["version"]) if issync == False else previousstatus - activity = self.__getministryactivity(oldstatus,newstatus) if issync == False else Activity.complete.value - isprocessing = self.__isprocessing(id) if issync == False else False - messagename = self.__messagename(oldstatus, activity, usertype, isprocessing) - metadata = json.dumps( - {"id": filenumber, "previousstatus":previousstatus, "status": ministry["status"] , - "assignedGroup": assignedgroup, "assignedTo": assignedto, - "assignedministrygroup":ministry["assignedministrygroup"], - "ministryRequestID": id, "isPaymentActive": self.__ispaymentactive(ministry["foirequestid"], id), - "paymentExpiryDate": paymentexpirydate, "axisRequestId": axisrequestid, "issync": issync, - "isofflinepayment": FOIMinistryRequest.getofflinepaymentflag(id)}) - if issync == True: - _variables = bpmservice().getinstancevariables(wfinstanceid) - if ministry["status"] == OpenedEvent.callforrecords.value and (("status" not in _variables) or (_variables not in (None, []) and "status" in _variables and _variables["status"]["value"] != OpenedEvent.callforrecords.value)): - messagename = MessageType.iaoopencomplete.value - elif _variables not in (None, []) and ("status" in _variables and _variables["status"]["value"] == StateName.closed.value): - return bpmservice().reopenevent(wfinstanceid, metadata, MessageType.iaoreopen.value) - else: - return bpmservice().openedcomplete(wfinstanceid, filenumber, metadata, messagename) - self.__postopenedevent(id, filenumber, metadata, messagename, assignedgroup, assignedto, wfinstanceid, activity) - - def postfeeevent(self, requestid, ministryrequestid, requestsschema, paymentstatus, nextstatename=None): - metadata = json.dumps({ - "id": requestsschema["idNumber"], - "status": requestsschema["currentState"], - "assignedGroup": requestsschema["assignedGroup"], - "assignedTo": requestsschema["assignedTo"], - "assignedministrygroup" : requestsschema["assignedministrygroup"], - "ministryRequestID" : ministryrequestid, - "foiRequestID" :requestid, - "nextStateName": nextstatename - }) - return bpmservice().feeevent(requestsschema["axisRequestId"], metadata, paymentstatus) - - def postcorrenspodenceevent(self, wfinstanceid, ministryid, requestsschema, applicantcorrespondenceid, templatename, attributes): - paymentexpirydate = self.__getvaluefromlist(attributes,"paymentExpiryDate") - axisrequestid = self.__getvaluefromschema(requestsschema,"axisRequestId") - filenumber = self.__getvaluefromschema(requestsschema,"idNumber") - status = self.__getvaluefromschema(requestsschema,"currentState") - metadata = json.dumps({"id": filenumber, "status": status , "ministryRequestID": ministryid, "paymentExpiryDate": paymentexpirydate, "axisRequestId": axisrequestid, "applicantcorrespondenceid": applicantcorrespondenceid, "templatename": templatename.replace(" ", "")}) - bpmservice().correspondanceevent(wfinstanceid, filenumber, metadata) - - def syncwfinstance(self, requesttype, requestid, isallactivity=False): - try: - # Sync and get raw instance details from FOI DB - raw_metadata = self.__sync_raw_request(requesttype, requestid) - if requesttype == "ministryrequest": - req_metadata = self.__sync_foi_request(requestid, raw_metadata) - # Check foi request instance creation - Reconcile by transition to Open - _all_activity_desc = FOIMinistryRequest.getactivitybyid(requestid) - self.__sync_state_transition(requestid, str(req_metadata.wfinstanceid), _all_activity_desc, isallactivity) - return req_metadata.wfinstanceid - return raw_metadata.wfinstanceid - except Exception as ex: - logging.error(ex) - return None - - def __sync_raw_request(self, requesttype, requestid): - # Search for WF ID - requestid = int(requestid) - _raw_metadata = FOIRawRequest.getworkflowinstancebyraw(requestid) if requesttype == "rawrequest" else FOIRawRequest.getworkflowinstancebyministry(requestid) - wf_rawrequest_pid = self.__get_wf_pid("rawrequest", _raw_metadata) - # Check for exists - Reconcile with new instance creation - if wf_rawrequest_pid not in (None, "") and _raw_metadata.wfinstanceid not in (None, "") and str(_raw_metadata.wfinstanceid) == wf_rawrequest_pid: - return _raw_metadata - # WF Instance is not present - if wf_rawrequest_pid in (None, ""): - self.createinstance(RedisPublisherService().foirequestqueueredischannel, json.dumps(self.__prepare_raw_requestobj(_raw_metadata))) - else: - if _raw_metadata.wfinstanceid in (None, "") or str(_raw_metadata.wfinstanceid) != wf_rawrequest_pid: - FOIRawRequest.updateworkflowinstance_n(wf_rawrequest_pid, int(_raw_metadata.requestid), "System") - return FOIRawRequest.getworkflowinstancebyraw(requestid) if requesttype == "rawrequest" else FOIRawRequest.getworkflowinstancebyministry(requestid) - - def __sync_foi_request(self, requestid, raw_metadata): - requestid = int(requestid) - _req_metadata = FOIRequest.getworkflowinstance(requestid) - wf_foirequest_pid = self.__get_wf_pid("ministryrequest", raw_metadata, _req_metadata) - if wf_foirequest_pid not in (None, "") and _req_metadata.wfinstanceid not in (None, "") and str(_req_metadata.wfinstanceid) == wf_foirequest_pid: - return _req_metadata - if wf_foirequest_pid in (None, ""): - _req_ministries = FOIMinistryRequest.getministriesopenedbyuid(raw_metadata.requestid) - self.postunopenedevent(int(_req_metadata.foirequestid), raw_metadata.wfinstanceid, self.__prepare_raw_requestobj(raw_metadata), UnopenedEvent.open.value, _req_ministries) - else: - if _req_metadata.wfinstanceid in (None, "") or str(_req_metadata.wfinstanceid) != wf_foirequest_pid: - FOIRequest.updateWFInstance(_req_metadata.foirequestid, wf_foirequest_pid, "System") - return FOIRequest.getworkflowinstance(requestid) - - def __get_wf_pid(self, requesttype, _raw_metadata, _req_metadata=None): - if requesttype == "rawrequest": - searchby = [{"name":"id" ,"operator":"eq","value": int(_raw_metadata.requestid)}] - return bpmservice().searchinstancebyvariable(ProcessDefinitionKey.rawrequest.value, searchby) - elif requesttype == "ministryrequest": - searchby = [{"name":"foiRequestID","operator":"eq","value": int(_req_metadata.foirequestid)}, - {"name": "rawRequestPID","operator":"eq","value": str(_raw_metadata.wfinstanceid)}] - wf_foirequest_pid = bpmservice().searchinstancebyvariable(ProcessDefinitionKey.ministryrequest.value, searchby) - if wf_foirequest_pid in (None, ""): - searchby = [{"name":"foiRequestID","operator":"eq","value": str(_req_metadata.foirequestid)}, - {"name": "rawRequestPID","operator":"eq","value": str(_raw_metadata.wfinstanceid)}] - wf_foirequest_pid = bpmservice().searchinstancebyvariable(ProcessDefinitionKey.ministryrequest.value, searchby) - return wf_foirequest_pid - else: - logging.info("Unknown requestype %s", requesttype) - return None - - def __sync_state_transition(self, requestid, wfinstanceid, _all_activity_desc, isallactivity): - current = _all_activity_desc[0] - previous = _all_activity_desc[1] if len(_all_activity_desc) > 1 else _all_activity_desc[0] - _activity_itr_desc = _all_activity_desc - if isallactivity == False: - _activity_itr_desc.pop(0) - _variables = bpmservice().getinstancevariables(wfinstanceid) - # SP: Stuck in Open -> Move from Open to CFR - if _variables not in (None, []) and "status" not in _variables: - for entry in _activity_itr_desc: - if entry["status"] == OpenedEvent.callforrecords.value and ((isallactivity == True) or (isallactivity == False and current["status"] != OpenedEvent.callforrecords.value)): - self.__sync_complete_event(requestid, wfinstanceid, entry) - break - # Sync action - _variables = bpmservice().getinstancevariables(wfinstanceid) - oldstatus = self.__getministrystatus(current["filenumber"], current["version"]) - activity = Activity.save.value if isallactivity == True else self.__getministryactivity(oldstatus,current["status"]) - if _variables not in (None, []) and "status" in _variables: - if activity == Activity.save.value and _variables["status"]["value"] != current["status"]: - self.__sync_complete_event(requestid, wfinstanceid, current) - if activity == Activity.complete.value and _variables["status"]["value"] != previous["status"]: - self.__sync_complete_event(requestid, wfinstanceid, previous) - - def __sync_complete_event(self, requestid, wfinstanceid, minrequest): - requestsschema, data = self.__prepare_ministry_complete(minrequest) - self.postopenedevent(requestid, wfinstanceid, requestsschema, data, minrequest["status"], self.__getusertype(minrequest["status"]), True) - - def __prepare_raw_requestobj(self, _rawinstance): - data = {} - data['id'] = _rawinstance.requestid - data['assignedGroup'] = _rawinstance.assignedgroup - data['assignedTo'] = _rawinstance.assignedto - return data - - def __prepare_ministry_complete(self, ministryrequest): - data = {} - data['axisRequestId'] = ministryrequest["axisrequestid"] - data['assignedgroup'] = ministryrequest["assignedgroup"] - data['assignedto'] = ministryrequest["assignedto"] - data['paymentExpiryDate'] = "" - ministry = [] - ministry.append(ministryrequest) - return data, {"ministries": ministry} - - def __getusertype(self, status): - if status in [StateName.feeestimate.value, StateName.harmsassessment.value, StateName.deduplication.value, StateName.recordsreview.value, StateName.ministrysignoff.value]: - return UserType.ministry.value - return UserType.iao.value - - def __postopenedevent(self, id, filenumber, metadata, messagename, assignedgroup, assignedto, wfinstanceid, activity): - if activity == Activity.complete.value: - - if self.__hasreopened(id, "ministryrequest") == True: - bpmservice().reopenevent(wfinstanceid, metadata, MessageType.iaoreopen.value) - else: - bpmservice().openedcomplete(wfinstanceid, filenumber, metadata, messagename) - else: - bpmservice().unopenedsave(filenumber, assignedgroup, assignedto, messagename) - - - def __getopenedassigneevalue(self, requestsschema, property, usertype): - if property == "assignedgroup": - return self.__getvaluefromschema(requestsschema,"assignedgroup") if 'assignedgroup' in requestsschema else self.__getvaluefromschema(requestsschema,"assignedministrygroup") - elif property == "assignedto": - return self.__getvaluefromschema(requestsschema,"assignedto") if 'assignedto' in requestsschema else self.__getvaluefromschema(requestsschema,"assignedministryperson") - else: - return None - - - def __messagename(self, status, activity, usertype, isprocessing=False): - if status == UnopenedEvent.open.value and isprocessing == False: - return MessageType.iaoopencomplete.value if activity == Activity.complete.value else MessageType.iaoopenclaim.value - elif status == OpenedEvent.reopen.value: - return MessageType.iaoreopen.value - else: - if usertype == UserType.ministry.value: - return MessageType.ministrycomplete.value if activity == Activity.complete.value else MessageType.ministryclaim.value - else: - return MessageType.iaocomplete.value if activity == Activity.complete.value else MessageType.iaoclaim.value - - - def __hasreopened(self, requestid, requesttype): - if requesttype == "rawrequest": - states = FOIRawRequest.getstatenavigation(requestid) - else: - states = FOIMinistryRequest.getstatenavigation(requestid) - if len(states) == 2: - newstate = states[0] - oldstate = states[1] - if newstate != oldstate and oldstate == UnopenedEvent.closed.value: - return True - return False - - def __isprocessing(self, requestid): - states = FOIMinistryRequest.getallstatenavigation(requestid) - for state in states: - if state == OpenedEvent.callforrecords.value and states[0] != OpenedEvent.callforrecords.value : - return True - return False - - def __ispaymentactive(self, foirequestid, ministryid): - _payment = cfrfeeservice().getactivepayment(foirequestid, ministryid) - return True if _payment is not None else False - - def __getvaluefromschema(self,requestsschema, property): - return requestsschema.get(property) if property in requestsschema else None - - def __getvaluefromlist(self,attributes, property): - for attribute in attributes: - if property in attribute: - return attribute.get(property) - return "" - - def __getministrystatus(self,filenumber, version): - ministryreq = FOIMinistryRequest.getrequestbyfilenumberandversion(filenumber,version-1) - return ministryreq["requeststatus.name"] - - def __getpreviousministrystatus(self,id): - ministryreq = FOIMinistryRequest.getstatesummary(id) - _len = len(ministryreq) - if _len > 1: - return ministryreq[1]["status"] - elif _len == 1: - return UnopenedEvent.intakeinprogress.value - else: - return None - - def __getprevioustatusbyversion(self,id, version): - ministryreq = FOIMinistryRequest.getstatesummary(id) - _len = len(ministryreq) - if _len > 1: - for entry in ministryreq: - if int(entry["version"]) < version: - return entry["status"] - elif _len == 1: - return UnopenedEvent.intakeinprogress.value - else: - return None - - def __getministryactivity(self, oldstatus, newstatus): - return Activity.complete.value if newstatus is not None and oldstatus != newstatus else Activity.save.value - - -class UserType(Enum): - iao = "iao" - ministry = "ministry" - -class Activity(Enum): - save = "save" - complete = "complete" - -class UnopenedEvent(Enum): - intakeinprogress = "Intake in Progress" - open = "Open" - redirect = "Redirect" - closed = "Closed" - reopen = "Reopen" - -class OpenedEvent(Enum): - callforrecords = "Call For Records" - reopen = "Reopen" \ No newline at end of file diff --git a/historical-search-api/request_api/utils/constants.py b/historical-search-api/request_api/utils/constants.py deleted file mode 100644 index 456e97c90..000000000 --- a/historical-search-api/request_api/utils/constants.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright © 2021 Province of British Columbia -# -# Licensed under the Apache License, Version 2.0 (the 'License'); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an 'AS IS' BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import requests -import logging -from os import getenv -"""Constants definitions.""" - -# Group names - -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', - url=getenv("FOI_RECORD_FORMATS"), - headers={'Content-Type': 'application/json'}, - timeout=5 - ) - response.raise_for_status() - FILE_CONVERSION_FILE_TYPES = response.json()['conversion'] - DEDUPE_FILE_TYPES = response.json()['dedupe'] - NONREDACTABLE_FILE_TYPES = response.json()['nonredactable'] -except Exception as err: - logging.error("Unable to retrieve record upload formats from S3") - logging.error(err) - - diff --git a/historical-search-api/request_api/utils/enums.py b/historical-search-api/request_api/utils/enums.py deleted file mode 100644 index 356b347e1..000000000 --- a/historical-search-api/request_api/utils/enums.py +++ /dev/null @@ -1,175 +0,0 @@ -# Copyright © 2021 Province of British Columbia -# -# Licensed under the Apache License, Version 2.0 (the 'License'); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an 'AS IS' BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Enum definitions.""" -from enum import Enum - - -class RequestType(Enum): - """Authorization header types.""" - - PERSONAL = 'Personal' - GENERAL = 'General' - - -class ContentType(Enum): - """Http Content Types.""" - - JSON = 'application/json' - FORM_URL_ENCODED = 'application/x-www-form-urlencoded' - PDF = 'application/pdf' - - -class MinistryTeamWithKeycloackGroup(Enum): - AEST = "AEST Ministry Team" - AGR = "AGR Ministry Team" - AG = "AG Ministry Team" - BRD = "BRD Ministry Team" - CAS = "CAS Ministry Team" - CITZ = "CITZ Ministry Team" - CLB = "CLB Ministry Team" - DAS = "DAS Ministry Team" - EAO = "EAO Ministry Team" - EDU = "EDU Ministry Team" - EMBC = "EMBC Ministry Team" - EMC = "EMC Ministry Team" - EMLI = "EMLI Ministry Team" - ENV = "ENV Ministry Team" - FIN = "FIN Ministry Team" - FOR = "FOR Ministry Team" - GCP = "GCP Ministry Team" - HTH = "HTH Ministry Team" - IIO = "IIO Ministry Team" - IRR = "IRR Ministry Team" - JERI = "JERI Ministry Team" - LBR = "LBR Ministry Team" - LDB = "LDB Ministry Team" - LWR = "LWR Ministry Team" - WLR = "WLR Ministry Team" - MCF = "MCF Ministry Team" - MGC = "MGC Ministry Team" - MMHA = "MMHA Ministry Team" - MUNI = "MUNI Ministry Team" - OBC = "OBC Ministry Team" - OCC = "OCC Ministry Team" - OOP = "OOP Ministry Team" - PSA = "PSA Ministry Team" - PSSG = "PSSG Ministry Team" - MSD = "MSD Ministry Team" - TACS = "TACS Ministry Team" - TIC = "TIC Ministry Team" - TRAN = "TRAN Ministry Team" - PSE = "PSE Ministry Team" - ECC = "ECC Ministry Team" - JED = "JED Ministry Team" - COR = "COR Ministry Team" - HSG = "HSG Ministry Team" - - @staticmethod - def list(): - return list(map(lambda c: c.value, MinistryTeamWithKeycloackGroup)) - -class ProcessingTeamWithKeycloackGroup(Enum): - scanningteam = "Scanning Team" - centralteam = "Central Team" - justicehealthteam = "Justice Health Team" - mcfdpersonalteam = "MCFD Personals Team" - resouceteam = "Resource Team" - socialtechteam = "Social Education" - centraleconteam = "Central and Economy Team" - resourcejusticeteam = "Resource and Justice Team" - communityhealthteam = "Community and Health Team" - childrenfamilyteam = "Children and Family Team" - childreneducationteam = "Children and Education Team" - coordinatedresponseunit = "Coordinated Response Unit" - - @staticmethod - def list(): - return list(map(lambda c: c.value, ProcessingTeamWithKeycloackGroup)) - -class IAOTeamWithKeycloackGroup(Enum): - intake = "Intake Team" - flex = "Flex Team" - - @staticmethod - def list(): - return list(map(lambda c: c.value, IAOTeamWithKeycloackGroup)) + list(map(lambda c: c.value, ProcessingTeamWithKeycloackGroup)) - -class UserGroup(Enum): - intake = "Intake Team" - flex = "Flex Team" - processing = "@processing" - ministry = "@bcgovcode Ministry Team" - -class RequestorType(Enum): - applicant = 1 - onbehalfof = 2 - child = 3 - -class FeeType(Enum): - application = 'FOI0001' - processing = 'FOI0002' - -class PaymentEventType(Enum): - paid = "PAID" - expired = "EXPIRED" - outstandingpaid = "OUTSTANDINGPAID" - depositpaid = "DEPOSITPAID" - reminder = "REMINDER" - -class CommentType(Enum): - """Authorization header types.""" - UserComment = 1 - SystemGenerated = 2 - DivisionStages = 3 - -class DocumentPathMapperCategory(Enum): - Attachments = "Attachments" - Records = "Records" - -class ServiceName(Enum): - payonline = "payonline" - payoutstanding = "payoutstanding" - correspondence = "correspondence" - -class StateName(Enum): - open = "Open" - callforrecords = "Call For Records" - closed = "Closed" - redirect = "Redirect" - unopened = "Unopened" - intakeinprogress = "Intake in Progress" - consult = "Consult" - ministrysignoff = "Ministry Sign Off" - onhold = "On Hold" - deduplication = "Deduplication" - harmsassessment = "Harms Assessment" - response = "Response" - feeestimate = "Fee Estimate" - recordsreview = "Records Review" - recordsreadyforreview = "Records Ready for Review" - archived = "Archived" - peerreview = "Peer Review" - tagging = "Tagging" - readytoscan = "Ready to Scan" - appfeeowing = "App Fee Owing" - section5pending = "Section 5 Pending" -class CacheUrls(Enum): - keycloakusers= "/api/foiassignees" - programareas= "/api/foiflow/programareas" - deliverymodes= "/api/foiflow/deliverymodes" - receivedmodes= "/api/foiflow/receivedmodes" - closereasons= "/api/foiflow/closereasons" - extensionreasons= "/api/foiflow/extensionreasons" - applicantcategories= "/api/foiflow/applicantcategories" - subjectcodes= "/api/foiflow/subjectcodes" diff --git a/historical-search-api/tests/restapi/test_fee_api.py b/historical-search-api/tests/restapi/test_fee_api.py deleted file mode 100644 index b52a424ae..000000000 --- a/historical-search-api/tests/restapi/test_fee_api.py +++ /dev/null @@ -1,125 +0,0 @@ -import json - -import pytest - -from request_api.services.hash_service import HashService - - -@pytest.mark.parametrize('fee_code, expected_status_code', [('FOI0001', 200), ('TEST', 404)]) -def test_get_fee(app, client, fee_code, expected_status_code): - response = client.get(f'/api/fees/{fee_code}', content_type='application/json') - assert response.status_code == expected_status_code - -with open('tests/samplerequestjson/rawrequest.json') as x: - rawrequestjson = json.load(x) -def test_create_payment(app, client): - foi_req = client.post(f'/api/foirawrequests', data=json.dumps(rawrequestjson), content_type='application/json') - request_id = foi_req.json.get('id') - fee_code = 'FOI0001' - pay_response = client.post(f'/api/foirawrequests/{request_id}/payments', data=json.dumps({ - 'fee_code': fee_code, - 'quantity': 5 - }), content_type='application/json') - assert pay_response.status_code == 201 - fee_response = client.get(f'/api/fees/{fee_code}?quantity=5', content_type='application/json') - assert pay_response.json.get('total') == fee_response.json.get('total') - assert pay_response.json.get('status') == 'PENDING' - -with open('tests/samplerequestjson/rawrequest.json') as x: - rawrequestjson = json.load(x) -def test_complete_payment(app, client, monkeypatch): - with app.app_context(): - fee_code = 'FOI0001' - quantity = 5 - - total_fee = client.get(f'/api/fees/{fee_code}?quantity={quantity}', content_type='application/json') - - # Mock paybc response to return success message - def mock_paybc_response(self): # pylint: disable=unused-argument; mocks of library methods - return { - 'paymentstatus': 'PAID', - 'trnamount': total_fee.json.get('total') - } - - monkeypatch.setattr('request_api.services.fee_service.FeeService.get_paybc_transaction_details', - mock_paybc_response) - - foi_req = client.post(f'/api/foirawrequests', data=json.dumps(rawrequestjson), - content_type='application/json') - request_id = foi_req.json.get('id') - - pay_response = client.post(f'/api/foirawrequests/{request_id}/payments', data=json.dumps({ - 'fee_code': fee_code, - 'quantity': quantity - }), content_type='application/json') - pay_id = pay_response.json.get('payment_id') - txn_number = f"{app.config.get('PAYBC_TXN_PREFIX')}{pay_id:0>8}" - response_url = f'trnApproved=1&messageText=Approved&trnOrderId=20595&trnAmount=50.00&paymentMethod=CC&' \ - f'cardType=VI&authCode=TEST&trnDate=2021-10-18&pbcTxnNumber={txn_number}' - response_url = f'{response_url}&hashValue={HashService.encode(response_url)}' - # Update payment - pay_response = client.put(f'/api/foirawrequests/{request_id}/payments/{pay_id}', data=json.dumps({ - 'response_url': response_url - }), content_type='application/json') - assert pay_response.json.get('status') == 'PAID' - -class TestResponse: - def __init__(self, status_code, content = None, headers = {}): - self.status_code = status_code - self.content = content - self.headers = headers - -with open('tests/samplerequestjson/rawrequest.json') as x: - rawrequestjson = json.load(x) -def test_generate_receipt(app, client, monkeypatch): - with app.app_context(): - - foi_req = client.post(f'/api/foirawrequests', data=json.dumps(rawrequestjson), content_type='application/json') - - request_id = foi_req.json.get('id') - fee_code = 'FOI0001' - pay_response = client.post(f'/api/foirawrequests/{request_id}/payments', data=json.dumps({ - 'fee_code': fee_code, - 'quantity': 5 - }), content_type='application/json') - payment_id = pay_response.json.get('payment_id') - - def moch_check_paid(self): - return True - - monkeypatch.setattr('request_api.services.fee_service.FeeService.check_if_paid', - moch_check_paid) - - def mock_get_token(self): - return 'token' - - monkeypatch.setattr('request_api.services.cdogs_api_service.CdogsApiService._get_access_token', - mock_get_token) - - def mock_check_hashed(self, template_hash_code): - return False - - monkeypatch.setattr('request_api.services.cdogs_api_service.CdogsApiService.check_template_cached', - mock_check_hashed) - - def mock_upload_template(self, headers, url, template): - return TestResponse( - status_code= 200, - headers= {'X-Template-Hash': "58G94G"} - ); - - monkeypatch.setattr('request_api.services.cdogs_api_service.CdogsApiService._post_upload_template', - mock_upload_template) - - def mock_generate_receipt(self, json_request_body, headers, ur): - return TestResponse( - content= bytearray([2, 3, 5, 7]), - status_code= 200, - ); - - monkeypatch.setattr('request_api.services.cdogs_api_service.CdogsApiService._post_generate_receipt', - mock_generate_receipt) - receipt_response = client.post(f'/api/foirawrequests/{request_id}/payments/{payment_id}/receipt', data=json.dumps({ - 'requestData': {} - }), content_type='application/json') - assert receipt_response.status_code == 200 \ No newline at end of file diff --git a/historical-search-api/tests/restapi/test_foiassignees_api.py b/historical-search-api/tests/restapi/test_foiassignees_api.py deleted file mode 100644 index 4a8d5686a..000000000 --- a/historical-search-api/tests/restapi/test_foiassignees_api.py +++ /dev/null @@ -1,55 +0,0 @@ -import json -import uuid -import os -import requests -import ast - -TEST_USER_PAYLOAD = { - 'client_id': 'forms-flow-web', - 'grant_type': 'password', - 'username' : os.getenv('TEST_INTAKE_USERID'), - 'password': os.getenv('TEST_INTAKE_PASSWORD') -} - -def factory_auth_header(app, client): - url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) - x = requests.post(url, TEST_USER_PAYLOAD, verify=True).content.decode('utf-8') - return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} - -def test_ping(app, client): - response = client.get('/api/healthz') - assert response.status_code == 200 - -def test_get_foiassigneesforgeneralopen(app, client): - response = client.get('/api/foiassignees/general/open', headers=factory_auth_header(app, client), content_type='application/json') - jsondata = json.loads(response.data) - assert response.status_code == 200 and len(jsondata) >=1 - -def test_get_foiassigneesforgeneralcfr(app, client): - response = client.get('/api/foiassignees/general/callforrecords/edu', headers=factory_auth_header(app, client), content_type='application/json') - jsondata = json.loads(response.data) - assert response.status_code == 200 and len(jsondata) >=1 - -def test_get_foiassigneesforpersonalopen(app, client): - response = client.get('/api/foiassignees/personal/open', headers=factory_auth_header(app, client), content_type='application/json') - jsondata = json.loads(response.data) - assert response.status_code == 200 and len(jsondata) >=1 - -def test_get_foiassigneesforpersonalinvalidstatus(app, client): - response = client.get('/api/foiassignees/test', headers=factory_auth_header(app, client), content_type='application/json') - assert response.status_code == 404 - -def test_get_foiassigneesforgroup(app, client): - response = client.get('/api/foiassignees/group/intaketeam', headers=factory_auth_header(app, client), content_type='application/json') #invalid condition - jsondata = json.loads(response.data) - assert response.status_code == 200 and len(jsondata) >=1 - -def test_get_foiassigneesgeneralteams(app, client): - response = client.get('/api/foiassignees/processingteams/general', headers=factory_auth_header(app, client), content_type='application/json') #invalid condition - jsondata = json.loads(response.data) - assert response.status_code == 200 and len(jsondata) >=1 - -def test_get_foiassigneespersonalteams(app, client): - response = client.get('/api/foiassignees/processingteams/personal', headers=factory_auth_header(app, client), content_type='application/json') #invalid condition - jsondata = json.loads(response.data) - assert response.status_code == 200 and len(jsondata) >=1 \ No newline at end of file diff --git a/historical-search-api/tests/restapi/test_foiaudit_api.py b/historical-search-api/tests/restapi/test_foiaudit_api.py deleted file mode 100644 index 637b310c1..000000000 --- a/historical-search-api/tests/restapi/test_foiaudit_api.py +++ /dev/null @@ -1,66 +0,0 @@ -import json -import os -import uuid -import requests -import ast - -TEST_INTAKEUSER_PAYLOAD = { - 'client_id': 'forms-flow-web', - 'grant_type': 'password', - 'username' : os.getenv('TEST_INTAKE_USERID'), - 'password': os.getenv('TEST_INTAKE_PASSWORD') -} - -TEST_FLEXUSER_PAYLOAD = { - 'client_id': 'forms-flow-web', - 'grant_type': 'password', - 'username' : os.getenv('TEST_FLEX_USERID'), - 'password': os.getenv('TEST_FLEX_PASSWORD') -} - -def factory_intake_auth_header(app, client): - url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) - x = requests.post(url, TEST_INTAKEUSER_PAYLOAD, verify=True).content.decode('utf-8') - return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} - -def factory_flex_auth_header(app, client): - url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) - x = requests.post(url, TEST_FLEXUSER_PAYLOAD, verify=True).content.decode('utf-8') - return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} - -def test_ping(app, client): - response = client.get('/api/healthz') - assert response.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as f: - requestjson = json.load(f) -def test_get_foiauditfordesccase1(app, client): - response = client.post('/api/foirawrequests',data=json.dumps(requestjson), content_type='application/json') - jsondata = json.loads(response.data) - response = client.get('/api/foiaudit/rawrequest/'+str(jsondata["id"])+'/description', headers=factory_intake_auth_header(app, client), content_type='application/json') - jsondata = json.loads(response.data) - assert response.status_code == 200 and len(jsondata['audit']) >=1 - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: - generalrequestjson = json.load(y) - generalupdaterequestjson = json.load(z) - rawrequestjson = json.load(x) -def test_get_foiauditfordesccase2(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest['requeststatusid'] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - foiupdaterequest = generalupdaterequestjson - foiupdaterequest["id"] = str(foijsondata["id"]) - foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - response = client.get('/api/foiaudit/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/description', headers=factory_flex_auth_header(app, client), content_type='application/json') - jsondata = json.loads(response.data) - assert response.status_code == 200 and len(jsondata['audit']) >=1 - -def test_get_foiauditfordescinvalid(app, client): - response = client.get('/api/foiaudit/invalid/1/invalid', headers=factory_intake_auth_header(app, client), content_type='application/json') - jsondata = json.loads(response.data) - assert response.status_code == 400 and len(jsondata) >=1 diff --git a/historical-search-api/tests/restapi/test_foicfrfee_api.py b/historical-search-api/tests/restapi/test_foicfrfee_api.py deleted file mode 100644 index b3d1576ec..000000000 --- a/historical-search-api/tests/restapi/test_foicfrfee_api.py +++ /dev/null @@ -1,139 +0,0 @@ -import json -import uuid -import os -import requests -import ast - -TEST_INTAKE_USER_PAYLOAD = { - 'client_id': 'forms-flow-web', - 'grant_type': 'password', - 'username' : os.getenv('TEST_INTAKE_USERID'), - 'password': os.getenv('TEST_INTAKE_PASSWORD') -} - -TEST_MINISTRY_USER_PAYLOAD = { - 'client_id': 'forms-flow-web', - 'grant_type': 'password', - 'username' : os.getenv('TEST_MINISTRY_USERID'), - 'password': os.getenv('TEST_MINISTRY_PASSWORD') -} - -TEST_CFR_FEE_SCHEMA = { - "feedata":{ - "amountpaid":11.00, - "totalamountdue":2.00, - "estimatedlocatinghrs": 11.00, - "actuallocatinghrs": 11.00, - "estimatedproducinghrs": 11.00, - "actualproducinghrs": 11.00, - "estimatediaopreparinghrs": 11.44444433330440, - "estimatedministrypreparinghrs": 11.44444433330440, - "actualiaopreparinghrs": 11.00, - "actualministrypreparinghrs": 11.00, - "estimatedelectronicpages": 11.00, - "actualelectronicpages": 11.00, - "estimatedhardcopypages": 11.00, - "actualhardcopypages": 9 - }, - "overallsuggestions":"test" -} - -TEST_CFR_FEE_SANCTION_SCHEMA = { - "feedata":{ - "amountpaid":5.00 - }, - "status":"approved" -} - - -RAW_REQUEST_POST_URL = '/api/foirawrequests' -REQUEST_POST_URL = '/api/foirequests' -RAW_REQUEST_JSON = 'tests/samplerequestjson/rawrequest.json' -FOI_REQUEST_GENERAL_JSON = 'tests/samplerequestjson/foirequest-general.json' -FOI_REQUEST_GENERAL_UPDATE_JSON = 'tests/samplerequestjson/foirequest-general-update.json' -FOI_REQUEST_GENERAL_CFR_JSON = 'tests/samplerequestjson/foirequest-general-CFR.json' -WF_URL_BASE = '/api/foirawrequestbpm/addwfinstanceid/' -CREATE_CFR_FEE_URL = '/api/foicfrfee/ministryrequest/' -GET_CFR_FEE_URL = '/api/foicfrfee/ministryrequest/' - -CONTENT_TYPE = 'application/json' - -def factory_intake_auth_header(app, client): - url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) - x = requests.post(url, TEST_INTAKE_USER_PAYLOAD, verify=True).content.decode('utf-8') - return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} - - -def factory_ministry_auth_header(app, client): - url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) - x = requests.post(url, TEST_MINISTRY_USER_PAYLOAD, verify=True).content.decode('utf-8') - return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} - -def test_ping(app, client): - response = client.get('/api/healthz') - assert response.status_code == 200 - -with open(RAW_REQUEST_JSON) as x, open(FOI_REQUEST_GENERAL_JSON) as y, open(FOI_REQUEST_GENERAL_UPDATE_JSON) as z, open(FOI_REQUEST_GENERAL_CFR_JSON) as v: - generalcfrrequestjson = json.load(v) - generalupdaterequestjson = json.load(z) - generalrequestjson = json.load(y) - rawrequestjson = json.load(x) - - -def test_foicfrfees_create(app, client): - rawresponse = client.post(RAW_REQUEST_POST_URL,data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - jsondata = json.loads(rawresponse.data) - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post(REQUEST_POST_URL,data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put(WF_URL_BASE+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - cfrrequestposturl = REQUEST_POST_URL+'/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]) - cfrrequest = generalcfrrequestjson - cfrrequest["requeststatusid"] = 2 - foicfrrequestresponse = client.post(cfrrequestposturl,data=json.dumps(cfrrequest), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - foicfrrequestjsondata = json.loads(foicfrrequestresponse.data) - createfoicfrfeesresponse = client.post(CREATE_CFR_FEE_URL+str(foicfrrequestjsondata["ministryRequests"][0]["id"]),data=json.dumps(TEST_CFR_FEE_SCHEMA), headers=factory_ministry_auth_header(app, client), content_type=CONTENT_TYPE) - assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 - - -def test_foicfrfees_sanction(app, client): - rawresponse = client.post(RAW_REQUEST_POST_URL,data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - jsondata = json.loads(rawresponse.data) - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post(REQUEST_POST_URL,data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put(WF_URL_BASE+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - cfrrequestposturl = REQUEST_POST_URL+'/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]) - cfrrequest = generalcfrrequestjson - cfrrequest["requeststatusid"] = 2 - foicfrrequestresponse = client.post(cfrrequestposturl,data=json.dumps(cfrrequest), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - foicfrrequestjsondata = json.loads(foicfrrequestresponse.data) - createfoicfrfeesresponse = client.post(CREATE_CFR_FEE_URL+str(foicfrrequestjsondata["ministryRequests"][0]["id"]),data=json.dumps(TEST_CFR_FEE_SCHEMA), headers=factory_ministry_auth_header(app, client), content_type=CONTENT_TYPE) - sanctionfoicfrfeesresponse = client.post(CREATE_CFR_FEE_URL+str(foicfrrequestjsondata["ministryRequests"][0]["id"])+"/sanction",data=json.dumps(TEST_CFR_FEE_SANCTION_SCHEMA), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 - - -def test_foicfrfees_get(app, client): - rawresponse = client.post(RAW_REQUEST_POST_URL,data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - jsondata = json.loads(rawresponse.data) - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post(REQUEST_POST_URL,data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put(WF_URL_BASE+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - cfrrequestposturl = REQUEST_POST_URL+'/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]) - cfrrequest = generalcfrrequestjson - cfrrequest["requeststatusid"] = 2 - foicfrrequestresponse = client.post(cfrrequestposturl,data=json.dumps(cfrrequest), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - foicfrrequestjsondata = json.loads(foicfrrequestresponse.data) - createfoicfrfeesresponse = client.post(CREATE_CFR_FEE_URL+str(foicfrrequestjsondata["ministryRequests"][0]["id"]),data=json.dumps(TEST_CFR_FEE_SCHEMA), headers=factory_ministry_auth_header(app, client), content_type=CONTENT_TYPE) - getfoicfrfeeresponse = client.get(CREATE_CFR_FEE_URL+str(foicfrrequestjsondata["ministryRequests"][0]["id"]), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 diff --git a/historical-search-api/tests/restapi/test_foicomment_api.py b/historical-search-api/tests/restapi/test_foicomment_api.py deleted file mode 100644 index b0996acb8..000000000 --- a/historical-search-api/tests/restapi/test_foicomment_api.py +++ /dev/null @@ -1,168 +0,0 @@ -import json -import uuid -import os -import requests -import ast - -TEST_USER_PAYLOAD = { - 'client_id': 'forms-flow-web', - 'grant_type': 'password', - 'username' : os.getenv('TEST_INTAKE_USERID'), - 'password': os.getenv('TEST_INTAKE_PASSWORD') -} - -def factory_auth_header(app, client): - url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) - x = requests.post(url, TEST_USER_PAYLOAD, verify=True).content.decode('utf-8') - return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} - -def test_ping(app, client): - response = client.get('/api/healthz') - assert response.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as x: - rawrequestjson = json.load(x) -def test_foirawcomment(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - commentjson = { - "requestid":str(jsondata["id"]), - "comment": "test comment", - "isactive": True - } - createcommentresponse = client.post('/api/foicomment/rawrequest', data=json.dumps(commentjson), headers=factory_auth_header(app, client), content_type='application/json') - getcommentresponse = client.get('/api/foicomment/rawrequest/'+str(jsondata["id"]), headers=factory_auth_header(app, client), content_type='application/json') - assert rawresponse.status_code == 200 and createcommentresponse.status_code == 200 and getcommentresponse.status_code == 200 - - -with open('tests/samplerequestjson/rawrequest.json') as x: - rawrequestjson = json.load(x) -def test_foirawcommentdisable(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - commentjson = { - "requestid":str(jsondata["id"]), - "comment": "test comment", - "isactive": True - } - createcommentresponse = client.post('/api/foicomment/rawrequest', data=json.dumps(commentjson), headers=factory_auth_header(app, client), content_type='application/json') - comment = json.loads(createcommentresponse.data) - disablecommentresponse = client.put('/api/foicomment/rawrequest/'+str(comment["id"])+'/disable',data=json.dumps(commentjson), headers=factory_auth_header(app, client), content_type='application/json') - assert rawresponse.status_code == 200 and createcommentresponse.status_code == 200 and disablecommentresponse.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as x: - rawrequestjson = json.load(x) -def test_foirawcommentupdate(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - commentjson = { - "requestid":str(jsondata["id"]), - "comment": "test comment", - "isactive": True - } - createcommentresponse = client.post('/api/foicomment/rawrequest', data=json.dumps(commentjson), headers=factory_auth_header(app, client), content_type='application/json') - comment = json.loads(createcommentresponse.data) - updatecommentjson = { - "comment": "test comment - updated", - } - updatecommentresponse = client.put('/api/foicomment/rawrequest/'+str(comment["id"]),data=json.dumps(updatecommentjson), headers=factory_auth_header(app, client), content_type='application/json') - assert rawresponse.status_code == 200 and createcommentresponse.status_code == 200 and updatecommentresponse.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y: - generalrequestjson = json.load(y) - rawrequestjson = json.load(x) -def test_foiministrycomment(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - commentjson = { - "ministryrequestid":str(foijsondata["id"]), - "comment": "test comment", - "isactive": True - } - createcommentresponse = client.post('/api/foicomment/ministryrequest', data=json.dumps(commentjson), headers=factory_auth_header(app, client), content_type='application/json') - getcommentresponse = client.get('/api/foicomment/ministryrequest/'+str(foijsondata["id"]), headers=factory_auth_header(app, client), content_type='application/json') - assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and createcommentresponse.status_code == 200 and getcommentresponse.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y: - generalrequestjson = json.load(y) - rawrequestjson = json.load(x) -def test_foiministrycommentdisable(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - commentjson = { - "ministryrequestid":str(foijsondata["id"]), - "comment": "test comment", - "isactive": True - } - createcommentresponse = client.post('/api/foicomment/ministryrequest', data=json.dumps(commentjson), headers=factory_auth_header(app, client), content_type='application/json') - comment = json.loads(createcommentresponse.data) - childcommentjson = { - "ministryrequestid":str(foijsondata["id"]), - "comment": "test comment", - "isactive": True, - "parentcommentid": str(comment["id"]) - } - createcommentresponse2 = client.post('/api/foicomment/ministryrequest', data=json.dumps(childcommentjson), headers=factory_auth_header(app, client), content_type='application/json') - disablecommentresponse = client.put('/api/foicomment/rawrequest/'+str(comment["id"])+'/disable',data=json.dumps(commentjson), headers=factory_auth_header(app, client), content_type='application/json') - assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and createcommentresponse.status_code == 200 and createcommentresponse2.status_code == 200 and disablecommentresponse.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y: - generalrequestjson = json.load(y) - rawrequestjson = json.load(x) -def test_foiministrycommentupdate(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - commentjson = { - "ministryrequestid":str(foijsondata["id"]), - "comment": "test comment", - "isactive": True - } - createcommentresponse = client.post('/api/foicomment/ministryrequest', data=json.dumps(commentjson), headers=factory_auth_header(app, client), content_type='application/json') - comment = json.loads(createcommentresponse.data) - updatecommentjson = { - "comment": "test comment - updated", - } - updatecommentresponse = client.put('/api/foicomment/ministryrequest/'+str(comment["id"]),data=json.dumps(updatecommentjson), headers=factory_auth_header(app, client), content_type='application/json') - assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and createcommentresponse.status_code == 200 and updatecommentresponse.status_code == 200 - - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y: - generalrequestjson = json.load(y) - rawrequestjson = json.load(x) -def test_foiministrycommentmigrate(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - commentjson = { - "requestid":str(jsondata["id"]), - "comment": "test comment", - "isactive": True - } - createcommentresponse1 = client.post('/api/foicomment/rawrequest', data=json.dumps(commentjson), headers=factory_auth_header(app, client), content_type='application/json') - comment = json.loads(createcommentresponse1.data) - childcommentjson = { - "requestid":str(jsondata["id"]), - "comment": "test comment", - "isactive": True, - "parentcommentid": str(comment["id"]) - } - createcommentresponse2 = client.post('/api/foicomment/rawrequest', data=json.dumps(childcommentjson), headers=factory_auth_header(app, client), content_type='application/json') - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_auth_header(app, client), content_type='application/json') - assert rawresponse.status_code == 200 and createcommentresponse1.status_code == 200 and createcommentresponse2.status_code == 200 and foiresponse.status_code == 200 \ No newline at end of file diff --git a/historical-search-api/tests/restapi/test_foidocument_api.py b/historical-search-api/tests/restapi/test_foidocument_api.py deleted file mode 100644 index e7caa2954..000000000 --- a/historical-search-api/tests/restapi/test_foidocument_api.py +++ /dev/null @@ -1,360 +0,0 @@ -import json -import uuid -import os -import requests -import ast - -TEST_INTAKE_USER_PAYLOAD = { - 'client_id': 'forms-flow-web', - 'grant_type': 'password', - 'username' : os.getenv('TEST_INTAKE_USERID'), - 'password': os.getenv('TEST_INTAKE_PASSWORD') -} - -TEST_MINISTRY_USER_PAYLOAD = { - 'client_id': 'forms-flow-web', - 'grant_type': 'password', - 'username' : os.getenv('TEST_MINISTRY_USERID'), - 'password': os.getenv('TEST_MINISTRY_PASSWORD') -} - -def factory_intake_auth_header(app, client): - url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) - x = requests.post(url, TEST_INTAKE_USER_PAYLOAD, verify=True).content.decode('utf-8') - return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} - -def factory_ministry_auth_header(app, client): - url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) - x = requests.post(url, TEST_MINISTRY_USER_PAYLOAD, verify=True).content.decode('utf-8') - return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} - -def test_ping(app, client): - response = client.get('/api/healthz') - assert response.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z, open('tests/samplerequestjson/foirequest-ministry-general-update.json') as v: - generalministryrequestjson = json.load(v) - generalupdaterequestjson = json.load(z) - generalrequestjson = json.load(y) - rawrequestjson = json.load(x) -def test_foiministrydocument_list(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type='application/json') - foiministryrequest = generalministryrequestjson - foiministryrequest["id"] = str(foijsondata["id"]) - foiministryrequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiministryrequest["requeststatusid"] = 2 - foiministryresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(foiministryrequest), headers=factory_intake_auth_header(app, client), content_type='application/json') - foiministryrequest2 = generalministryrequestjson - foiministryrequest2["id"] = str(foijsondata["id"]) - foiministryrequest2["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiministryrequest2["documents"] = [ - { - "category": "cfr-feeassessed", - "documentpath":"/EDUC/"+str(foijsondata["ministryRequests"][0]["filenumber"])+"/cfr-review/test.docx", - "filename":"test.docx" - } - ] - foiministryrequest2["requeststatusid"] = 7 - foiministryresponse2 = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(foiministryrequest2), headers=factory_intake_auth_header(app, client), content_type='application/json') - getdocumentsresponse = client.get('/api/foidocument/ministryrequest/'+str(foijsondata["id"]), headers=factory_ministry_auth_header(app, client), content_type='application/json') - assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiministryresponse.status_code == 200 and foiministryresponse2.status_code == 200 and getdocumentsresponse.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z, open('tests/samplerequestjson/foirequest-ministry-general-update.json') as v: - generalministryrequestjson = json.load(v) - generalupdaterequestjson = json.load(z) - generalrequestjson = json.load(y) - rawrequestjson = json.load(x) -def test_foiministrydocument_rename(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type='application/json') - foiministryrequest = generalministryrequestjson - foiministryrequest["id"] = str(foijsondata["id"]) - foiministryrequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiministryrequest["requeststatusid"] = 2 - foiministryresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(foiministryrequest), headers=factory_intake_auth_header(app, client), content_type='application/json') - foiministryrequest2 = generalministryrequestjson - foiministryrequest2["id"] = str(foijsondata["id"]) - foiministryrequest2["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiministryrequest2["documents"] = [ - { - "category": "cfr-feeassessed", - "documentpath":"/EDUC/"+str(foijsondata["ministryRequests"][0]["filenumber"])+"/cfr-review/test.docx", - "filename":"test2.docx" - } - ] - foiministryrequest2["requeststatusid"] = 7 - foiministryresponse2 = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(foiministryrequest2), headers=factory_intake_auth_header(app, client), content_type='application/json') - getdocumentsresponse = client.get('/api/foidocument/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]), headers=factory_ministry_auth_header(app, client), content_type='application/json') - getdocumentsresponsejsondata = json.loads(getdocumentsresponse.data) - renamedocumentjson = { - "filename": "newname.docx" - } - renamedocumentresponse = client.post('/api/foidocument/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/documentid/'+str(getdocumentsresponsejsondata[0]['foiministrydocumentid'])+'/rename',data=json.dumps(renamedocumentjson), headers=factory_ministry_auth_header(app, client), content_type='application/json') - assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiministryresponse.status_code == 200 and foiministryresponse2.status_code == 200 and getdocumentsresponse.status_code == 200 and renamedocumentresponse.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z, open('tests/samplerequestjson/foirequest-ministry-general-update.json') as v: - generalministryrequestjson = json.load(v) - generalupdaterequestjson = json.load(z) - generalrequestjson = json.load(y) - rawrequestjson = json.load(x) -def test_foiministrydocument_replace(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type='application/json') - foiministryrequest = generalministryrequestjson - foiministryrequest["id"] = str(foijsondata["id"]) - foiministryrequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiministryrequest["requeststatusid"] = 2 - foiministryresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(foiministryrequest), headers=factory_intake_auth_header(app, client), content_type='application/json') - foiministryrequest2 = generalministryrequestjson - foiministryrequest2["id"] = str(foijsondata["id"]) - foiministryrequest2["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiministryrequest2["documents"] = [ - { - "category": "cfr-feeassessed", - "documentpath":"/EDUC/"+str(foijsondata["ministryRequests"][0]["filenumber"])+"/cfr-review/test.docx", - "filename":"test3.docx" - } - ] - foiministryrequest2["requeststatusid"] = 7 - foiministryresponse2 = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(foiministryrequest2), headers=factory_intake_auth_header(app, client), content_type='application/json') - getdocumentsresponse = client.get('/api/foidocument/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]), headers=factory_ministry_auth_header(app, client), content_type='application/json') - getdocumentsresponsejsondata = json.loads(getdocumentsresponse.data) - replacedocumentjson = { - "category": "cfr-feeassessed", - "documentpath":"/EDUC/"+str(foijsondata["ministryRequests"][0]["filenumber"])+"/cfr-review/testnew.docx", - "filename":"testnew.docx" - } - replacedocumentresponse = client.post('/api/foidocument/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/documentid/'+str(getdocumentsresponsejsondata[0]['foiministrydocumentid'])+'/replace',data=json.dumps(replacedocumentjson), headers=factory_ministry_auth_header(app, client), content_type='application/json') - assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiministryresponse.status_code == 200 and foiministryresponse2.status_code == 200 and getdocumentsresponse.status_code == 200 and replacedocumentresponse.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z, open('tests/samplerequestjson/foirequest-ministry-general-update.json') as v: - generalministryrequestjson = json.load(v) - generalupdaterequestjson = json.load(z) - generalrequestjson = json.load(y) - rawrequestjson = json.load(x) -def test_foiministrydocument_delete(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type='application/json') - foiministryrequest = generalministryrequestjson - foiministryrequest["id"] = str(foijsondata["id"]) - foiministryrequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiministryrequest["requeststatusid"] = 2 - foiministryresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(foiministryrequest), headers=factory_intake_auth_header(app, client), content_type='application/json') - foiministryrequest2 = generalministryrequestjson - foiministryrequest2["id"] = str(foijsondata["id"]) - foiministryrequest2["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiministryrequest2["documents"] = [ - { - "category": "cfr-feeassessed", - "documentpath":"/EDUC/"+str(foijsondata["ministryRequests"][0]["filenumber"])+"/cfr-review/test.docx", - "filename":"test.docx" - } - ] - foiministryrequest2["requeststatusid"] = 7 - foiministryresponse2 = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(foiministryrequest2), headers=factory_intake_auth_header(app, client), content_type='application/json') - getdocumentsresponse = client.get('/api/foidocument/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]), headers=factory_ministry_auth_header(app, client), content_type='application/json') - getdocumentsresponsejsondata = json.loads(getdocumentsresponse.data) - deletedocumentresponse = client.post('/api/foidocument/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/documentid/'+str(getdocumentsresponsejsondata[0]['foiministrydocumentid'])+'/delete', headers=factory_ministry_auth_header(app, client), content_type='application/json') - assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiministryresponse.status_code == 200 and foiministryresponse2.status_code == 200 and getdocumentsresponse.status_code == 200 and deletedocumentresponse.status_code == 200 - - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z, open('tests/samplerequestjson/foirequest-ministry-general-update.json') as v: - generalministryrequestjson = json.load(v) - generalupdaterequestjson = json.load(z) - generalrequestjson = json.load(y) - rawrequestjson = json.load(x) -def test_foiministrydocument_create(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type='application/json') - documentsschema = { - "documents": [ - { - "filename":"doc2.docx", - "documentpath": "/EDU-90909/doc2.docx", - "category": "new" - }, - { - "filename":"doc3.docx", - "documentpath": "/EDU-90909/doc3.docx", - "category": "new-doc3" - } - ] -} - foiministrydocresponse = client.post('/api/foidocument/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(documentsschema), headers=factory_intake_auth_header(app, client), content_type='application/json') - assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiministrydocresponse.status_code == 200 - - - -with open('tests/samplerequestjson/rawrequest.json') as x: - rawrequestjson = json.load(x) -def test_foirawdocument_list(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type='application/json') - documentsschema = { - "documents": [ - { - "filename":"doc2.docx", - "documentpath": "/EDU-90909/doc2.docx", - "category": "new" - }, - { - "filename":"doc3.docx", - "documentpath": "/EDU-90909/doc3.docx", - "category": "new-doc3" - } - ] -} - foirawdocresponse = client.post('/api/foidocument/rawrequest/'+str(jsondata["id"]),data=json.dumps(documentsschema), headers=factory_intake_auth_header(app, client), content_type='application/json') - getdocumentsresponse = client.get('/api/foidocument/rawrequest/'+str(jsondata["id"]), headers=factory_intake_auth_header(app, client), content_type='application/json') - assert rawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foirawdocresponse.status_code == 200 and getdocumentsresponse.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as x: - rawrequestjson = json.load(x) -def test_foirawdocument_rename(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type='application/json') - documentsschema = { - "documents": [ - { - "filename":"doc2.docx", - "documentpath": "/EDU-90909/doc2.docx", - "category": "new" - }, - { - "filename":"doc3.docx", - "documentpath": "/EDU-90909/doc3.docx", - "category": "new-doc3" - } - ] -} - foirawdocresponse = client.post('/api/foidocument/rawrequest/'+str(jsondata["id"]),data=json.dumps(documentsschema), headers=factory_intake_auth_header(app, client), content_type='application/json') - getdocumentsresponse = client.get('/api/foidocument/rawrequest/'+str(jsondata["id"]), headers=factory_intake_auth_header(app, client), content_type='application/json') - getdocumentsresponsejsondata = json.loads(getdocumentsresponse.data) - renamedocumentjson = { - "filename": "newname.docx" - } - renamedocumentresponse = client.post('/api/foidocument/rawrequest/'+str(jsondata["id"])+'/documentid/'+str(getdocumentsresponsejsondata[0]['foidocumentid'])+'/rename',data=json.dumps(renamedocumentjson), headers=factory_intake_auth_header(app, client), content_type='application/json') - assert rawresponse.status_code == 200 and foirawdocresponse.status_code == 200 and wfupdateresponse.status_code == 200 and getdocumentsresponse.status_code == 200 and renamedocumentresponse.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as x: - rawrequestjson = json.load(x) -def test_foirawdocument_replace(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type='application/json') - documentsschema = { - "documents": [ - { - "filename":"doc2.docx", - "documentpath": "/EDU-90909/doc2.docx", - "category": "new" - }, - { - "filename":"doc3.docx", - "documentpath": "/EDU-90909/doc3.docx", - "category": "new-doc3" - } - ] -} - foirawdocresponse = client.post('/api/foidocument/rawrequest/'+str(jsondata["id"]),data=json.dumps(documentsschema), headers=factory_intake_auth_header(app, client), content_type='application/json') - getdocumentsresponse = client.get('/api/foidocument/rawrequest/'+str(jsondata["id"]), headers=factory_intake_auth_header(app, client), content_type='application/json') - getdocumentsresponsejsondata = json.loads(getdocumentsresponse.data) - replacedocumentjson = { - "documentpath":"/EDUC/"+str(jsondata["id"])+"/intake/testnew.docx", - "filename":"testnew.docx" - } - replacedocumentresponse = client.post('/api/foidocument/rawrequest/'+str(jsondata["id"])+'/documentid/'+str(getdocumentsresponsejsondata[0]['foidocumentid'])+'/replace',data=json.dumps(replacedocumentjson), headers=factory_intake_auth_header(app, client), content_type='application/json') - assert rawresponse.status_code == 200 and foirawdocresponse.status_code == 200 and wfupdateresponse.status_code == 200 and getdocumentsresponse.status_code == 200 and replacedocumentresponse.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as x: - rawrequestjson = json.load(x) -def test_foirawdocument_delete(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type='application/json') - documentsschema = { - "documents": [ - { - "filename":"doc2.docx", - "documentpath": "/EDU-90909/doc2.docx", - "category": "new" - }, - { - "filename":"doc3.docx", - "documentpath": "/EDU-90909/doc3.docx", - "category": "new-doc3" - } - ] -} - foirawdocresponse = client.post('/api/foidocument/rawrequest/'+str(jsondata["id"]),data=json.dumps(documentsschema), headers=factory_intake_auth_header(app, client), content_type='application/json') - getdocumentsresponse = client.get('/api/foidocument/rawrequest/'+str(jsondata["id"]), headers=factory_intake_auth_header(app, client), content_type='application/json') - getdocumentsresponsejsondata = json.loads(getdocumentsresponse.data) - deletedocumentresponse = client.post('/api/foidocument/rawrequest/'+str(jsondata["id"])+'/documentid/'+str(getdocumentsresponsejsondata[0]['foidocumentid'])+'/delete', headers=factory_intake_auth_header(app, client), content_type='application/json') - assert rawresponse.status_code == 200 and foirawdocresponse.status_code == 200 and wfupdateresponse.status_code == 200 and getdocumentsresponse.status_code == 200 and deletedocumentresponse.status_code == 200 - - -with open('tests/samplerequestjson/rawrequest.json') as x: - rawrequestjson = json.load(x) -def test_foirawdocument_create(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type='application/json') - documentsschema = { - "documents": [ - { - "filename":"doc2.docx", - "documentpath": "/EDU-90909/doc2.docx", - "category": "new" - }, - { - "filename":"doc3.docx", - "documentpath": "/EDU-90909/doc3.docx", - "category": "new-doc3" - } - ] -} - foirawdocresponse = client.post('/api/foidocument/rawrequest/'+str(jsondata["id"]),data=json.dumps(documentsschema), headers=factory_intake_auth_header(app, client), content_type='application/json') - assert rawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foirawdocresponse.status_code == 200 diff --git a/historical-search-api/tests/restapi/test_foiextension_api.py b/historical-search-api/tests/restapi/test_foiextension_api.py deleted file mode 100644 index baa845326..000000000 --- a/historical-search-api/tests/restapi/test_foiextension_api.py +++ /dev/null @@ -1,159 +0,0 @@ -import json -import uuid -import os -import requests -import ast - -TEST_INTAKE_USER_PAYLOAD = { - 'client_id': 'forms-flow-web', - 'grant_type': 'password', - 'username' : os.getenv('TEST_INTAKE_USERID'), - 'password': os.getenv('TEST_INTAKE_PASSWORD') -} - -TEST_MINISTRY_USER_PAYLOAD = { - 'client_id': 'forms-flow-web', - 'grant_type': 'password', - 'username' : os.getenv('TEST_MINISTRY_USERID'), - 'password': os.getenv('TEST_MINISTRY_PASSWORD') -} - -NEW_EXTENSION_SCHEMA = { - "extensionreasonid": 5, - "extendedduedays": 30, - "extendedduedate": "2022-03-04" - } - -RAW_REQUEST_JSON = 'tests/samplerequestjson/rawrequest.json' -FOI_REQUEST_GENERAL_JSON = 'tests/samplerequestjson/foirequest-general.json' -FOI_REQUEST_GENERAL_UPDATE_JSON = 'tests/samplerequestjson/foirequest-general-update.json' -FOI_REQUEST_GENERAL_MINISTRY_UPDATE_JSON = 'tests/samplerequestjson/foirequest-ministry-general-update.json' -RAW_REQUEST_POST_URL = '/api/foirawrequests' -REQUEST_POST_URL = '/api/foirequests' -WF_URL_BASE = '/api/foirawrequestbpm/addwfinstanceid/' -CREATE_EXTN_BASE_URL = '/api/foiextension/foirequest/' -GET_EXTN_BASE_URL = '/api/foiextension/ministryrequest/' - -CONTENT_TYPE = 'application/json' - -def factory_intake_auth_header(app, client): - url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) - x = requests.post(url, TEST_INTAKE_USER_PAYLOAD, verify=True).content.decode('utf-8') - return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} - -def factory_ministry_auth_header(app, client): - url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) - x = requests.post(url, TEST_MINISTRY_USER_PAYLOAD, verify=True).content.decode('utf-8') - return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} - -def test_ping(app, client): - response = client.get('/api/healthz') - assert response.status_code == 200 - -with open(RAW_REQUEST_JSON) as x, open(FOI_REQUEST_GENERAL_JSON) as y, open(FOI_REQUEST_GENERAL_UPDATE_JSON) as z, open(FOI_REQUEST_GENERAL_MINISTRY_UPDATE_JSON) as v: - generalministryrequestjson = json.load(v) - generalupdaterequestjson = json.load(z) - generalrequestjson = json.load(y) - rawrequestjson = json.load(x) - -def test_foiministryextension_list(app, client): - rawresponse = client.post(RAW_REQUEST_POST_URL,data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - jsondata = json.loads(rawresponse.data) - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post(REQUEST_POST_URL,data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put(WF_URL_BASE + str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - foiministryrequest = generalministryrequestjson - foiministryrequest["id"] = str(foijsondata["id"]) - foiministryrequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiministryrequest["requeststatusid"] = 2 - requestposturl = REQUEST_POST_URL+'/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry' - foiministryresponse = client.post(requestposturl,data=json.dumps(foiministryrequest), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - foiministryrequest2 = generalministryrequestjson - foiministryrequest2["id"] = str(foijsondata["id"]) - foiministryrequest2["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiministryrequest2["documents"] = [ - { - "category": "cfr-feeassessed", - "documentpath":"/EDU/"+str(foijsondata["ministryRequests"][0]["filenumber"])+"/cfr-review/test.docx", - "filename":"test.docx" - } - ] - foiministryrequest2["requeststatusid"] = 7 - foiministryresponse2 = client.post(requestposturl,data=json.dumps(foiministryrequest2), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - ministryid = str(foijsondata["ministryRequests"][0]["id"]) - createextnresponse = client.post(CREATE_EXTN_BASE_URL+str(foijsondata["id"])+'/ministryrequest/'+ministryid,data=json.dumps(NEW_EXTENSION_SCHEMA), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - getextensionssresponse = client.get(GET_EXTN_BASE_URL+ministryid, headers=factory_ministry_auth_header(app, client), content_type=CONTENT_TYPE) - assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiministryresponse.status_code == 200 and foiministryresponse2.status_code == 200 and createextnresponse.status_code == 200 and getextensionssresponse.status_code == 200 - -def test_foiministryextension_create(app, client): - rawresponse = client.post(RAW_REQUEST_POST_URL,data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - jsondata = json.loads(rawresponse.data) - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post(REQUEST_POST_URL,data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put(WF_URL_BASE+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - - foiministryextnresponse = client.post(CREATE_EXTN_BASE_URL+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(NEW_EXTENSION_SCHEMA), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiministryextnresponse.status_code == 200 - -def test_foiministryextension_edit(app, client): - rawresponse = client.post(RAW_REQUEST_POST_URL,data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - jsondata = json.loads(rawresponse.data) - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post(REQUEST_POST_URL,data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put(WF_URL_BASE+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - - createextnresponse = client.post(CREATE_EXTN_BASE_URL+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(NEW_EXTENSION_SCHEMA), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - getextensionssresponse = client.get(GET_EXTN_BASE_URL+str(foijsondata["ministryRequests"][0]["id"]), headers=factory_ministry_auth_header(app, client), content_type=CONTENT_TYPE) - getextensionssresponsejsondata = json.loads(getextensionssresponse.data) - extensionschema = { - "extensionreasonid": 7, - "extensionstatusid": 1, - "extendedduedays": 2, - "extendedduedate": "2022-03-07" , - "decisiondate": "2022-01-20", - "approvednoofdays": 3, - "documents": [{ - "documentpath": "https://citz-foi-prod.objectstore.gov.bc.ca/dev-forms-foirequests/CITZ/CITZ-2022-236861/extension/12e4dad1-71b7-4c2d-a770-c16937ecc052.docx", - "filename": "CITZExtensionDenied.docx", - "category": "extension" - }] - } - foirequestid = str(foijsondata["id"]) - ministryid = str(foijsondata["ministryRequests"][0]["id"]) - extensionid = str(getextensionssresponsejsondata[0]['foirequestextensionid']) - foiministryextnresponse = client.post(CREATE_EXTN_BASE_URL+foirequestid+'/ministryrequest/'+ministryid+'/extension/'+extensionid+'/edit',data=json.dumps(extensionschema), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 and createextnresponse.status_code == 200 and foiministryextnresponse.status_code == 200 - - -def test_foiministryextension_delete(app, client): - rawresponse = client.post(RAW_REQUEST_POST_URL,data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - jsondata = json.loads(rawresponse.data) - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post(REQUEST_POST_URL,data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put(WF_URL_BASE+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - - createextnresponse = client.post(CREATE_EXTN_BASE_URL+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(NEW_EXTENSION_SCHEMA), headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - getextensionssresponse = client.get(GET_EXTN_BASE_URL+str(foijsondata["ministryRequests"][0]["id"]), headers=factory_ministry_auth_header(app, client), content_type=CONTENT_TYPE) - getextensionssresponsejsondata = json.loads(getextensionssresponse.data) - foirequestid = str(foijsondata["id"]) - ministryid = str(foijsondata["ministryRequests"][0]["id"]) - extensionid = str(getextensionssresponsejsondata[0]['foirequestextensionid']) - foiministryextnresponse = client.post(CREATE_EXTN_BASE_URL+foirequestid+'/ministryrequest/'+ministryid+'/extension/'+extensionid+'/delete', headers=factory_intake_auth_header(app, client), content_type=CONTENT_TYPE) - assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 and createextnresponse.status_code == 200 and foiministryextnresponse.status_code == 200 - diff --git a/historical-search-api/tests/restapi/test_foimasterdata_api.py b/historical-search-api/tests/restapi/test_foimasterdata_api.py deleted file mode 100644 index 93d49c533..000000000 --- a/historical-search-api/tests/restapi/test_foimasterdata_api.py +++ /dev/null @@ -1,58 +0,0 @@ -import json -import uuid -import jwt, os -import requests -import ast - -TEST_USER_PAYLOAD = { - 'client_id': 'forms-flow-web', - 'grant_type': 'password', - 'username' : os.getenv('TEST_INTAKE_USERID'), - 'password': os.getenv('TEST_INTAKE_PASSWORD') -} - -def factory_user_auth_header(app, client): - url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) - x = requests.post(url, TEST_USER_PAYLOAD, verify=True).content.decode('utf-8') - return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} - -def test_ping(app, client): - response = client.get('/api/healthz') - assert response.status_code == 200 - -def test_get_applicantcategories(app, client): - response = client.get('/api/foiflow/applicantcategories', headers=factory_user_auth_header(app, client), content_type='application/json') - assert response.status_code == 200 - -def test_get_programareas(app, client): - response = client.get('/api/foiflow/programareas', headers=factory_user_auth_header(app, client), content_type='application/json') - assert response.status_code == 200 - -def test_get_deliverymodes(app, client): - response = client.get('/api/foiflow/deliverymodes', headers=factory_user_auth_header(app, client), content_type='application/json') - assert response.status_code == 200 - -def test_get_receivedmodes(app, client): - response = client.get('/api/foiflow/receivedmodes', headers=factory_user_auth_header(app, client), content_type='application/json') - assert response.status_code == 200 - -def test_get_divisions(app, client): - response = client.get('/api/foiflow/divisions/edu', headers=factory_user_auth_header(app, client), content_type='application/json') - assert response.status_code == 200 - -def test_get_closereasons(app, client): - response = client.get('/api/foiflow/closereasons', headers=factory_user_auth_header(app, client), content_type='application/json') - assert response.status_code == 200 - -with open('tests/samplerequestjson/s3storagerequest.json') as f: - s3requestjson = json.load(f) -def test_post_fois3storagerequests(app, client): - response = client.post('api/foiflow/oss/authheader',headers=factory_user_auth_header(app, client),data=json.dumps(s3requestjson), content_type='application/json') - jsonresponse = json.loads(response.data) - for item in jsonresponse: - assert item['authheader'] is not None and item['filepath'] is not None - assert response.status_code == 200 and len(jsonresponse) >=1 - -def test_get_extensionreasons(app, client): - response = client.get('/api/foiflow/extensionreasons', headers=factory_user_auth_header(app, client), content_type='application/json') - assert response.status_code == 200 \ No newline at end of file diff --git a/historical-search-api/tests/restapi/test_foinotification_api.py b/historical-search-api/tests/restapi/test_foinotification_api.py deleted file mode 100644 index 2e7b6453a..000000000 --- a/historical-search-api/tests/restapi/test_foinotification_api.py +++ /dev/null @@ -1,159 +0,0 @@ -import json -import uuid -import os -import requests -import ast - -TEST_USER_PAYLOAD = { - 'client_id': 'forms-flow-web', - 'grant_type': 'password', - 'username' : os.getenv('TEST_INTAKE_USERID'), - 'password': os.getenv('TEST_INTAKE_PASSWORD') -} - -TEST_MINISTRYUSER_PAYLOAD = { - 'client_id': 'forms-flow-web', - 'grant_type': 'password', - 'username' : os.getenv('TEST_MINISTRY_USERID'), - 'password': os.getenv('TEST_MINISTRY_PASSWORD') -} - -def factory_user_auth_header(app, client): - url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) - x = requests.post(url, TEST_USER_PAYLOAD, verify=True).content.decode('utf-8') - return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} - -def factory_ministryuser_auth_header(app, client): - url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) - x = requests.post(url, TEST_MINISTRYUSER_PAYLOAD, verify=True).content.decode('utf-8') - return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} -def test_ping(app, client): - response = client.get('/api/healthz') - assert response.status_code == 200 - - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: - generalrequestjson = json.load(y) - generalupdaterequestjson = json.load(z) - rawrequestjson = json.load(x) -def test_post_get_foirequest_general_cfr_notification(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') - foiupdaterequest = generalupdaterequestjson - foiupdaterequest["id"] = str(foijsondata["id"]) - foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiupdaterequest["requeststatusid"] = 2 - foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foiministryreqresponse = client.get('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',headers=factory_ministryuser_auth_header(app, client), content_type='application/json') - foinotification = client.get('/api/foinotifications',headers=factory_ministryuser_auth_header(app, client), content_type='application/json') - assert foiministryreqresponse.status_code == 200 and foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 and foinotification.status_code == 200 - - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: - generalrequestjson = json.load(y) - generalupdaterequestjson = json.load(z) - rawrequestjson = json.load(x) -def test_dismiss_foirequest_general_cfr_notification(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - watcherjson = { - "requestid":str(jsondata["id"]), - "watchedbygroup": "Intake Team", - "watchedby": "sumathi", - "isactive": True - } - createwatcherresponse = client.post('/api/foiwatcher/rawrequest', data=json.dumps(watcherjson), headers=factory_user_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') - foiupdaterequest = generalupdaterequestjson - foiupdaterequest["id"] = str(foijsondata["id"]) - foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiupdaterequest["requeststatusid"] = 2 - foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foiministryreqresponse = client.get('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',headers=factory_ministryuser_auth_header(app, client), content_type='application/json') - foinotification = client.get('/api/foinotifications',headers=factory_ministryuser_auth_header(app, client), content_type='application/json') - foinotificationdata = json.loads(foinotification.data) - deletefoinotification = client.delete('/api/foinotifications/watcher',headers=factory_ministryuser_auth_header(app, client), content_type='application/json') - assert foiministryreqresponse.status_code == 200 and foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 and foinotification.status_code == 200 and createwatcherresponse.status_code == 200 and deletefoinotification.status_code == 200 - - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: - generalrequestjson = json.load(y) - generalupdaterequestjson = json.load(z) - rawrequestjson = json.load(x) -def test_dismissall_foirequest_general_cfr_notification(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - watcherjson = { - "requestid":str(jsondata["id"]), - "watchedbygroup": "Intake Team", - "watchedby": "sumathi", - "isactive": True - } - createwatcherresponse = client.post('/api/foiwatcher/rawrequest', data=json.dumps(watcherjson), headers=factory_user_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') - foiupdaterequest = generalupdaterequestjson - foiupdaterequest["id"] = str(foijsondata["id"]) - foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiupdaterequest["requeststatusid"] = 2 - foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foiministryreqresponse = client.get('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',headers=factory_ministryuser_auth_header(app, client), content_type='application/json') - foinotification = client.get('/api/foinotifications',headers=factory_user_auth_header(app, client), content_type='application/json') - deletefoinotification = client.delete('/api/foinotifications',headers=factory_user_auth_header(app, client), content_type='application/json') - assert foiministryreqresponse.status_code == 200 and foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 and foinotification.status_code == 200 and createwatcherresponse.status_code == 200 and deletefoinotification.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: - generalrequestjson = json.load(y) - generalupdaterequestjson = json.load(z) - rawrequestjson = json.load(x) -def test_foirequest_general_cfr_notification_reminder(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - watcherjson = { - "requestid":str(jsondata["id"]), - "watchedbygroup": "Intake Team", - "watchedby": "sumathi", - "isactive": True - } - createwatcherresponse = client.post('/api/foiwatcher/rawrequest', data=json.dumps(watcherjson), headers=factory_user_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') - foiupdaterequest = generalupdaterequestjson - foiupdaterequest["id"] = str(foijsondata["id"]) - foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiupdaterequest["requeststatusid"] = 2 - foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foiministryreqresponse = client.get('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',headers=factory_ministryuser_auth_header(app, client), content_type='application/json') - foinotification = client.get('/api/foinotifications',headers=factory_user_auth_header(app, client), content_type='application/json') - reminderfoinotification = client.post('/api/foinotifications/reminder',headers=factory_user_auth_header(app, client), content_type='application/json') - assert foiministryreqresponse.status_code == 200 and foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 and foinotification.status_code == 200 and createwatcherresponse.status_code == 200 and reminderfoinotification.status_code == 200 diff --git a/historical-search-api/tests/restapi/test_foirecord_api.py b/historical-search-api/tests/restapi/test_foirecord_api.py deleted file mode 100644 index 6d1f2f26c..000000000 --- a/historical-search-api/tests/restapi/test_foirecord_api.py +++ /dev/null @@ -1,82 +0,0 @@ -import json -import uuid -import os -import requests -import ast - -TEST_INTAKE_USER_PAYLOAD = { - 'client_id': 'forms-flow-web', - 'grant_type': 'password', - 'username' : os.getenv('TEST_INTAKE_USERID'), - 'password': os.getenv('TEST_INTAKE_PASSWORD') -} - -TEST_MINISTRY_USER_PAYLOAD = { - 'client_id': 'forms-flow-web', - 'grant_type': 'password', - 'username' : os.getenv('TEST_MINISTRY_USERID'), - 'password': os.getenv('TEST_MINISTRY_PASSWORD') -} - -def factory_intake_auth_header(app, client): - url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) - x = requests.post(url, TEST_INTAKE_USER_PAYLOAD, verify=True).content.decode('utf-8') - return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} - -def factory_ministry_auth_header(app, client): - url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) - x = requests.post(url, TEST_MINISTRY_USER_PAYLOAD, verify=True).content.decode('utf-8') - return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} - -def test_ping(app, client): - response = client.get('/api/healthz') - assert response.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z, open('tests/samplerequestjson/foirequest-ministry-general-update.json') as v: - generalministryrequestjson = json.load(v) - generalupdaterequestjson = json.load(z) - generalrequestjson = json.load(y) - rawrequestjson = json.load(x) -def test_foiministrydocument_list(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_intake_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_intake_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_intake_auth_header(app, client), content_type='application/json') - foiministryrequest = generalministryrequestjson - foiministryrequest["id"] = str(foijsondata["id"]) - foiministryrequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiministryrequest["requeststatusid"] = 2 - foiministryresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(foiministryrequest), headers=factory_intake_auth_header(app, client), content_type='application/json') - foiministryrequest2 = generalministryrequestjson - foiministryrequest2["id"] = str(foijsondata["id"]) - foiministryrequest2["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiministryrequest2["documents"] = [ - { - "category": "cfr-feeassessed", - "documentpath":"/EDUC/"+str(foijsondata["ministryRequests"][0]["filenumber"])+"/cfr-review/test.docx", - "filename":"test.docx" - } - ] - foiministryrequest2["requeststatusid"] = 7 - foiministryresponse2 = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(foiministryrequest2), headers=factory_intake_auth_header(app, client), content_type='application/json') - getdocumentsresponse = client.get('/api/foidocument/ministryrequest/'+str(foijsondata["id"]), headers=factory_ministry_auth_header(app, client), content_type='application/json') - foirecordrequest = { "records": [{ - "divisionid":1, - "s3uripath":"/s3/test/3434", - "filename": "test.pdf" - }, - { - "divisionid":1, - "s3uripath":"/s3/test/3434", - "filename": "test2.pdf" - }] - } - foirecordresponse1 = client.post('/api/foirecord/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foirecordrequest), headers=factory_ministry_auth_header(app, client), content_type='application/json') - getrecordresponse = client.get('/api/foirecord/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["id"]), headers=factory_ministry_auth_header(app, client), content_type='application/json') - assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiministryresponse.status_code == 200 and foiministryresponse2.status_code == 200 and getdocumentsresponse.status_code == 200 and foirecordresponse1.status_code == 200 and getrecordresponse.status_code == 200 - diff --git a/historical-search-api/tests/restapi/test_foirequest_api.py b/historical-search-api/tests/restapi/test_foirequest_api.py deleted file mode 100644 index da67bce25..000000000 --- a/historical-search-api/tests/restapi/test_foirequest_api.py +++ /dev/null @@ -1,425 +0,0 @@ -import json -import uuid -import jwt, os -import requests -import ast - -TEST_USER_PAYLOAD = { - 'client_id': 'forms-flow-web', - 'grant_type': 'password', - 'username' : os.getenv('TEST_INTAKE_USERID'), - 'password': os.getenv('TEST_INTAKE_PASSWORD') -} - -TEST_MINISTRYUSER_PAYLOAD = { - 'client_id': 'forms-flow-web', - 'grant_type': 'password', - 'username' : os.getenv('TEST_MINISTRY_USERID'), - 'password': os.getenv('TEST_MINISTRY_PASSWORD') -} - -def factory_user_auth_header(app, client): - url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) - x = requests.post(url, TEST_USER_PAYLOAD, verify=True).content.decode('utf-8') - return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} - -def factory_ministryuser_auth_header(app, client): - url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) - x = requests.post(url, TEST_MINISTRYUSER_PAYLOAD, verify=True).content.decode('utf-8') - return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} - -TEST_JWT_HEADER = { - "alg": "RS256", - "typ": "JWT", - "kid": "foiunittest" -} - -TEST_JWT_CLAIMS = { - "iss": os.getenv('JWT_OIDC_ISSUER'), - "sub": "3559e79c-7115-41c1-bb26-1a3dc54bbf5e", - "typ": "Bearer", - "aud": [os.getenv('JWT_OIDC_AUDIENCE')], - "firstname": "Test", - "lastname": "User", - "preferred_username": "test@idir", - "given_name": "TEST", - "family_name": "TEST", - "realm_access": { - "roles": [ - "approver_role" - ] - }, - "groups": [ - "/Flex Team" - ], - "preferred_username": "user" -} - -def factory_auth_header(jwt, claims): - """Produce JWT tokens for use in tests.""" - return {'Authorization': 'Bearer ' + jwt.encode(payload=TEST_JWT_CLAIMS,key="secret",headers=TEST_JWT_HEADER)} - -def test_ping(app, client): - response = client.get('/api/healthz') - assert response.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: - generalrequestjson = json.load(y) - generalupdaterequestjson = json.load(z) - rawrequestjson = json.load(x) -def test_post_foirequest_general(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') - foiupdaterequest = generalupdaterequestjson - foiupdaterequest["id"] = str(foijsondata["id"]) - foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiupdaterequest["cfrDueDate"] = '2020-01-02' - foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') - assert foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-personal.json') as y: - personalrequestjson = json.load(y) - rawrequestjson = json.load(x) -def test_post_foirequest_personal(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') - getrawjsondata = json.loads(getrawresponse.data) - #assert rawresponse.status_code == 200 - foirequest = personalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') - assert foiresponse.status_code == 200 and wfupdateresponse.status_code == 200 - - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: - generalrequestjson = json.load(y) - generalupdaterequestjson = json.load(z) - rawrequestjson = json.load(x) -def test_post_foirequest_general_closed(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') - foiupdaterequest = generalupdaterequestjson - foiupdaterequest["id"] = str(foijsondata["id"]) - foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiupdaterequest["requeststatusid"] = 3 - foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') - assert foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 - - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: - generalrequestjson = json.load(y) - generalupdaterequestjson = json.load(z) - rawrequestjson = json.load(x) -def test_post_foirequest_general_cfr(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') - foiupdaterequest = generalupdaterequestjson - foiupdaterequest["id"] = str(foijsondata["id"]) - foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiupdaterequest["requeststatusid"] = 2 - foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') - print('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry') - foiministryreqResponse = client.get('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',headers=factory_ministryuser_auth_header(app, client), content_type='application/json') - assert foiministryreqResponse.status_code == 200 and foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: - generalrequestjson = json.load(y) - generalupdaterequestjson = json.load(z) - rawrequestjson = json.load(x) -def test_post_foirequest_general_cfrtoopen(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') - foiupdaterequest = generalupdaterequestjson - foiupdaterequest["id"] = str(foijsondata["id"]) - foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiupdaterequest["requeststatusid"] = 2 - foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foiupdaterequest["requeststatusid"] = 1 - foireqresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') - assert foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 and foireqresponse.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: - generalrequestjson = json.load(y) - generalupdaterequestjson = json.load(z) - rawrequestjson = json.load(x) -def test_post_foirequest_general_cfrtoreview(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') - foiupdaterequest = generalupdaterequestjson - foiupdaterequest["id"] = str(foijsondata["id"]) - foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiupdaterequest["requeststatusid"] = 2 - foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foiupdaterequest["requeststatusid"] = 7 - foireqresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') - assert foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 and foireqresponse.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: - generalrequestjson = json.load(y) - generalupdaterequestjson = json.load(z) - rawrequestjson = json.load(x) -def test_post_foirequest_general_reviewtoconsult(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') - foiupdaterequest = generalupdaterequestjson - foiupdaterequest["id"] = str(foijsondata["id"]) - foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiupdaterequest["requeststatusid"] = 2 - foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foiupdaterequest["requeststatusid"] = 7 - foireqresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foiupdaterequest["requeststatusid"] = 9 - foireqresponse2 = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') - assert foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 and foireqresponse.status_code == 200 and foireqresponse2.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: - generalrequestjson = json.load(y) - generalupdaterequestjson = json.load(z) - rawrequestjson = json.load(x) -def test_post_foirequest_general_reviewtominsignoff(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') - foiupdaterequest = generalupdaterequestjson - foiupdaterequest["id"] = str(foijsondata["id"]) - foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiupdaterequest["requeststatusid"] = 2 - foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foiupdaterequest["requeststatusid"] = 7 - foireqresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foiupdaterequest["requeststatusid"] = 10 - foireqresponse2 = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') - assert foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 and foireqresponse.status_code == 200 and foireqresponse2.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: - generalrequestjson = json.load(y) - generalupdaterequestjson = json.load(z) - rawrequestjson = json.load(x) -def test_post_foirequest_general_consulttoreview(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') - foiupdaterequest = generalupdaterequestjson - foiupdaterequest["id"] = str(foijsondata["id"]) - foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiupdaterequest["requeststatusid"] = 2 - foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foiupdaterequest["requeststatusid"] = 7 - foireqresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foiupdaterequest["requeststatusid"] = 9 - foireqresponse2 = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foiupdaterequest["requeststatusid"] = 7 - foireqresponse3 = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') - assert foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 and foireqresponse.status_code == 200 and foireqresponse2.status_code == 200 and foireqresponse3.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: - generalrequestjson = json.load(y) - generalupdaterequestjson = json.load(z) - rawrequestjson = json.load(x) -def test_post_foirequest_general_cfr_assignment(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') - foiupdaterequest = generalupdaterequestjson - foiupdaterequest["id"] = str(foijsondata["id"]) - foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiupdaterequest["requeststatusid"] = 2 - foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foiministryreqResponse = client.get('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',headers=factory_ministryuser_auth_header(app, client), content_type='application/json') - foiassignrequest = { - "assignedGroup":"Intake Team", - "assignedTo":"foiintake@idir", - "assignedToFirstName":"FOI", - "assignedToLastName":"Intake", - "assignedministrygroup":"EDU Ministry Team", - "assignedministryperson":"foiedu@idir", - "assignedministrypersonFirstName":"foiedu", - "assignedministrypersonLastName":"foiedu" - } - foicfrassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(foiassignrequest), headers=factory_user_auth_header(app, client), content_type='application/json') - assert foiministryreqResponse.status_code == 200 and foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 and foiministryreqResponse.status_code == 200 and foicfrassignresponse.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: - generalrequestjson = json.load(y) - generalupdaterequestjson = json.load(z) - rawrequestjson = json.load(x) -def test_post_foirequest_general_cfr_division(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') - foiupdaterequest = generalupdaterequestjson - foiupdaterequest["id"] = str(foijsondata["id"]) - foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiupdaterequest["requeststatusid"] = 2 - foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foiministryreqResponse = client.get('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',headers=factory_ministryuser_auth_header(app, client), content_type='application/json') - foidivisionrequest = { - "assignedministrygroup":"EDU Ministry Team", - "assignedministryperson":"foiedu@idir", - "assignedministrypersonFirstName":"foiedu", - "assignedministrypersonLastName":"foiedu", - "requeststatusid": 2, - "divisions": [{"divisionid":1,"stageid":1},{"divisionid":2,"stageid":1}] - } - foicfrdivisionresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(foidivisionrequest), headers=factory_user_auth_header(app, client), content_type='application/json') - assert foiministryreqResponse.status_code == 200 and foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 and foiministryreqResponse.status_code == 200 and foicfrdivisionresponse.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: - generalrequestjson = json.load(y) - generalupdaterequestjson = json.load(z) - rawrequestjson = json.load(x) -def test_post_foirequest_general_cfr_document(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') - foiupdaterequest = generalupdaterequestjson - foiupdaterequest["id"] = str(foijsondata["id"]) - foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiupdaterequest["requeststatusid"] = 2 - foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foiministryreqResponse = client.get('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',headers=factory_ministryuser_auth_header(app, client), content_type='application/json') - foidivisionrequest = { - "assignedministrygroup":"EDU Ministry Team", - "assignedministryperson":"foiedu@idir", - "assignedministrypersonFirstName":"foiedu", - "assignedministrypersonLastName":"foiedu", - "requeststatusid": 2, - "documents": [ - { - "category": "cfr-feeassessed", - "documentpath":"/EDU/"+str(foijsondata["ministryRequests"][0]["filenumber"])+"/cfr-review/test.docx", - "filename":"test.docx" - } - ] - } - foicfrdivisionresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(foidivisionrequest), headers=factory_user_auth_header(app, client), content_type='application/json') - assert foiministryreqResponse.status_code == 200 and foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 and foiministryreqResponse.status_code == 200 and foicfrdivisionresponse.status_code == 200 - - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: - generalrequestjson = json.load(y) - generalupdaterequestjson = json.load(z) - rawrequestjson = json.load(x) -def test_post_foirequest_general_close(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_user_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_user_auth_header(app, client), content_type='application/json') - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_user_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_user_auth_header(app, client), content_type='application/json') - foiupdaterequest = generalupdaterequestjson - foiupdaterequest["id"] = str(foijsondata["id"]) - foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiupdaterequest["requeststatusid"] = 3 - foiupdaterequest["closedate"] = '2021-10-25' - foiupdaterequest["closereasonid"] = 1 - foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_user_auth_header(app, client), content_type='application/json') - assert foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 - -def test_get_foirequestqueue(app, client): - response = client.get('/api/dashboardpagination?page=1&size=10&sortingitems[]=intakeSorting&sortingitems[]=duedate&sortingorders[]=asc&sortingorders[]=asc&filters[]=firstName&filters[]=lastName&filters[]=requestType&filters[]=idNumber&filters[]=axisRequestId&filters[]=currentState&filters[]=assignedToLastName&filters[]=assignedToFirstName&additionalfilter=myRequests&userid=foiintake@idir', headers=factory_user_auth_header(app, client), content_type='application/json') - jsondata = json.loads(response.data) - assert response.status_code == 200 and len(jsondata) > 0 - -def test_get_foiministryrequestqueue(app, client): - response = client.get('/api/dashboardpagination/ministry?page=1&size=10&sortingitems[]=ministrySorting&sortingitems[]=cfrduedate&sortingorders[]=asc&sortingorders[]=asc&filters[]=applicantcategory&filters[]=requestType&filters[]=idNumber&filters[]=axisRequestId&filters[]=currentState&filters[]=assignedministrypersonLastName&filters[]=assignedministrypersonFirstName&additionalfilter=myRequests&userid=foiedu@idir', headers=factory_ministryuser_auth_header(app, client), content_type='application/json') - jsondata = json.loads(response.data) - assert response.status_code == 200 and len(jsondata) > 0 - - -def test_get_foirequestqueuewithoutheader(app, client): - response = client.get('/api/dashboardpagination?page=1&size=10&sortingitems[]=intakeSorting&sortingitems[]=duedate&sortingorders[]=asc&sortingorders[]=asc&filters[]=firstName&filters[]=lastName&filters[]=requestType&filters[]=idNumber&filters[]=axisRequestId&filters[]=currentState&filters[]=assignedToLastName&filters[]=assignedToFirstName&additionalfilter=myRequests&userid=foiintake@idir', content_type='application/json') - jsondata = json.loads(response.data) - assert response.status_code == 500 diff --git a/historical-search-api/tests/restapi/test_foisystemcomment_api.py b/historical-search-api/tests/restapi/test_foisystemcomment_api.py deleted file mode 100644 index 07be0e557..000000000 --- a/historical-search-api/tests/restapi/test_foisystemcomment_api.py +++ /dev/null @@ -1,94 +0,0 @@ -import json -import uuid -import os -import requests -import ast - -TEST_USER_PAYLOAD = { - 'client_id': 'forms-flow-web', - 'grant_type': 'password', - 'username' : os.getenv('TEST_INTAKE_USERID'), - 'password': os.getenv('TEST_INTAKE_PASSWORD') -} - -TEST_MINISTRY_USER_PAYLOAD = { - 'client_id': 'forms-flow-web', - 'grant_type': 'password', - 'username' : os.getenv('TEST_MINISTRY_USERID'), - 'password': os.getenv('TEST_MINISTRY_PASSWORD') -} - -FOI_DIVISION_BASE_PAYLOAD = { - "assignedministrygroup":"EDU Ministry Team", - "assignedministryperson":"foiedu@idir", - "assignedministrypersonFirstName":"foiedu", - "assignedministrypersonLastName":"foiedu" -} - -def factory_auth_header(app, client): - url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) - x = requests.post(url, TEST_USER_PAYLOAD, verify=True).content.decode('utf-8') - return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} - -def factory_ministry_auth_header(app, client): - url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) - x = requests.post(url, TEST_MINISTRY_USER_PAYLOAD, verify=True).content.decode('utf-8') - return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} - -def test_ping(app, client): - response = client.get('/api/healthz') - assert response.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: - generalrequestjson = json.load(y) - generalupdaterequestjson = json.load(z) - rawrequestjson = json.load(x) -def test_createrawrequest_state_comment_intakeinprogres_to_open(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - updatejson = generalupdaterequestjson - updatejson["id"] = str(jsondata["id"]) - updatejson['ispiiredacted'] = True - updatejson['requeststatusid'] = 1 - updateresponse = client.post('/api/foirawrequest/'+str(jsondata["id"]),data=json.dumps(updatejson), headers=factory_auth_header(app, client), content_type='application/json') - getcommentresponse = client.get('/api/foicomment/rawrequest/'+str(jsondata["id"]), headers=factory_auth_header(app, client), content_type='application/json') - assert rawresponse.status_code == 200 and updateresponse.status_code == 200 and getcommentresponse.status_code == 200 and len(getcommentresponse.data) >=1 - - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: - generalrequestjson = json.load(y) - generalupdaterequestjson = json.load(z) - rawrequestjson = json.load(x) -def test_createrawrequest_division_comment(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_auth_header(app, client), content_type='application/json') - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_auth_header(app, client), content_type='application/json') - foiupdaterequest = generalupdaterequestjson - foiupdaterequest["id"] = str(foijsondata["id"]) - foiupdaterequest["idNumber"] = str(foijsondata["ministryRequests"][0]["filenumber"]) - foiupdaterequest["requeststatusid"] = 2 - foiassignresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]),data=json.dumps(foiupdaterequest), headers=factory_auth_header(app, client), content_type='application/json') - foiministryreqresponse = client.get('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',headers=factory_ministry_auth_header(app, client), content_type='application/json') - - addfoidivisionrequest = FOI_DIVISION_BASE_PAYLOAD - addfoidivisionrequest["requeststatusid"] = 2 - addfoidivisionrequest["divisions"] = [{"divisionid":1,"stageid":1},{"divisionid":2,"stageid":1}] - addfoicfrdivisionresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(addfoidivisionrequest), headers=factory_ministry_auth_header(app, client), content_type='application/json') - modfoidivisionrequest = FOI_DIVISION_BASE_PAYLOAD - modfoidivisionrequest["requeststatusid"] = 2 - modfoidivisionrequest["divisions"] = [{"divisionid":1,"stageid":2},{"divisionid":2,"stageid":1}] - modfoicfrdivisionresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(modfoidivisionrequest), headers=factory_ministry_auth_header(app, client), content_type='application/json') - delfoidivisionrequest = FOI_DIVISION_BASE_PAYLOAD - delfoidivisionrequest["requeststatusid"] = 2 - delfoidivisionrequest["divisions"] = [{"divisionid":2,"stageid":1}] - delfoicfrdivisionresponse = client.post('/api/foirequests/'+str(foijsondata["id"])+'/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"])+'/ministry',data=json.dumps(delfoidivisionrequest), headers=factory_ministry_auth_header(app, client), content_type='application/json') - getcommentresponse = client.get('/api/foicomment/ministryrequest/'+str(foijsondata["ministryRequests"][0]["id"]), headers=factory_ministry_auth_header(app, client), content_type='application/json') - assert foiministryreqresponse.status_code == 200 and foiresponse.status_code == 200 and getrawresponse.status_code == 200 and wfupdateresponse.status_code == 200 and foiassignresponse.status_code == 200 and addfoicfrdivisionresponse.status_code == 200 and modfoicfrdivisionresponse.status_code == 200 and delfoicfrdivisionresponse.status_code == 200 and getcommentresponse.status_code == 200 and len(getcommentresponse.data) >=1 - \ No newline at end of file diff --git a/historical-search-api/tests/restapi/test_foiwatcher_api.py b/historical-search-api/tests/restapi/test_foiwatcher_api.py deleted file mode 100644 index 19808de17..000000000 --- a/historical-search-api/tests/restapi/test_foiwatcher_api.py +++ /dev/null @@ -1,78 +0,0 @@ -import json -import uuid -import os -import requests -import ast - -TEST_USER_PAYLOAD = { - 'client_id': 'forms-flow-web', - 'grant_type': 'password', - 'username' : os.getenv('TEST_INTAKE_USERID'), - 'password': os.getenv('TEST_INTAKE_PASSWORD') -} - -TEST_MINISTRY_USER_PAYLOAD = { - 'client_id': 'forms-flow-web', - 'grant_type': 'password', - 'username' : os.getenv('TEST_MINISTRY_USERID'), - 'password': os.getenv('TEST_MINISTRY_PASSWORD') -} - - -def factory_auth_header(app, client): - url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) - x = requests.post(url, TEST_USER_PAYLOAD, verify=True).content.decode('utf-8') - return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} - -def factory_ministry_auth_header(app, client): - url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) - x = requests.post(url, TEST_MINISTRY_USER_PAYLOAD, verify=True).content.decode('utf-8') - return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} - - -def test_ping(app, client): - response = client.get('/api/healthz') - assert response.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as x: - rawrequestjson = json.load(x) -def test_foirawwatcher(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - watcherjson = { - "requestid":str(jsondata["id"]), - "watchedbygroup": "Intake Team", - "watchedby": "sumathi", - "isactive": True - } - createwatcherresponse = client.post('/api/foiwatcher/rawrequest', data=json.dumps(watcherjson), headers=factory_auth_header(app, client), content_type='application/json') - getwatcherresponse = client.get('/api/foiwatcher/rawrequest/'+str(jsondata["id"]), headers=factory_auth_header(app, client), content_type='application/json') - disablewatcherresponse = client.put('/api/foiwatcher/rawrequest/disable/'+str(jsondata["id"]), headers=factory_auth_header(app, client), content_type='application/json') - assert rawresponse.status_code == 200 and createwatcherresponse.status_code == 200 and getwatcherresponse.status_code == 200 and disablewatcherresponse.status_code == 200 - - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y: - generalrequestjson = json.load(y) - rawrequestjson = json.load(x) -def test_foiministrywatcher(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_auth_header(app, client), content_type='application/json') - foijsondata = json.loads(foiresponse.data) - watcherjson = { - "ministryrequestid":str(foijsondata["id"]), - "watchedbygroup": "Intake Team", - "watchedby": "sumathi", - "isactive": True - } - createwatcherresponse = client.post('/api/foiwatcher/ministryrequest', data=json.dumps(watcherjson), headers=factory_ministry_auth_header(app, client), content_type='application/json') - getwatcherresponse = client.get('/api/foiwatcher/ministryrequest/'+str(foijsondata["id"]), headers=factory_ministry_auth_header(app, client), content_type='application/json') - disablewatcherresponse = client.put('/api/foiwatcher/ministryrequest/disable/'+str(jsondata["id"]), headers=factory_ministry_auth_header(app, client), content_type='application/json') - assert rawresponse.status_code == 200 and foiresponse.status_code == 200 and createwatcherresponse.status_code == 200 and getwatcherresponse.status_code == 200 and disablewatcherresponse.status_code == 200 - - - - diff --git a/historical-search-api/tests/restapi/test_rawrequest_api.py b/historical-search-api/tests/restapi/test_rawrequest_api.py deleted file mode 100644 index 77456f4f8..000000000 --- a/historical-search-api/tests/restapi/test_rawrequest_api.py +++ /dev/null @@ -1,109 +0,0 @@ -import json -import uuid -import os -import requests -import ast - -TEST_USER_PAYLOAD = { - 'client_id': 'forms-flow-web', - 'grant_type': 'password', - 'username' : os.getenv('TEST_INTAKE_USERID'), - 'password': os.getenv('TEST_INTAKE_PASSWORD') -} - -def factory_auth_header(app, client): - url = '{0}/auth/realms/{1}/protocol/openid-connect/token'.format(os.getenv('KEYCLOAK_ADMIN_HOST'),os.getenv('KEYCLOAK_ADMIN_REALM')) - x = requests.post(url, TEST_USER_PAYLOAD, verify=True).content.decode('utf-8') - return {'Authorization': 'Bearer ' + str(ast.literal_eval(x)['access_token'])} - -def test_ping(app, client): - response = client.get('/api/healthz') - assert response.status_code == 200 - -with open('tests/samplerequestjson/rawrequest.json') as f: - requestjson = json.load(f) -def test_post_foirawrequests(app, client): - response = client.post('/api/foirawrequests',data=json.dumps(requestjson), content_type='application/json') - jsondata = json.loads(response.data) - print(str(jsondata["id"])) - wfinstanceid={"wfinstanceid":str(uuid.uuid4())} - wfupdateresponse = client.put('/api/foirawrequestbpm/addwfinstanceid/'+str(jsondata["id"]),data=json.dumps(wfinstanceid), headers=factory_auth_header(app, client), content_type='application/json') - assert response.status_code == 200 and wfupdateresponse.status_code == 200 and len(jsondata) >=1 - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: - generalrequestjson = json.load(y) - generalupdaterequestjson = json.load(z) - rawrequestjson = json.load(x) -def test_post_foirawrequestspii(app, client): - response = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), content_type='application/json') - jsondata = json.loads(response.data) - updatejson = generalupdaterequestjson - updatejson["id"] = str(jsondata["id"]) - print(str(jsondata["id"])) - updatejson['ispiiredacted'] = True - updatejson['assignedTo'] = "Intake Team" - wfupdateresponse = client.post('/api/foirawrequest/'+str(jsondata["id"]),data=json.dumps(updatejson), headers=factory_auth_header(app, client), content_type='application/json') - assert response.status_code == 200 and wfupdateresponse.status_code == 200 and len(jsondata) >=1 - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: - generalrequestjson = json.load(y) - generalupdaterequestjson = json.load(z) - rawrequestjson = json.load(x) -def test_post_foirawrequestscancel(app, client): - response = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), content_type='application/json') - jsondata = json.loads(response.data) - updatejson = generalupdaterequestjson - updatejson["id"] = str(jsondata["id"]) - print(str(jsondata["id"])) - updatejson['ispiiredacted'] = True - updatejson['requeststatusid'] = 3 - updatejson['closedate'] = '2021-10-26' - updatejson['requeststatusid'] = 2 - wfupdateresponse = client.post('/api/foirawrequest/'+str(jsondata["id"]),data=json.dumps(updatejson), headers=factory_auth_header(app, client), content_type='application/json') - assert response.status_code == 200 and wfupdateresponse.status_code == 200 and len(jsondata) >=1 - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: - generalrequestjson = json.load(y) - generalupdaterequestjson = json.load(z) - rawrequestjson = json.load(x) -def test_post_foirawrequestsredirect(app, client): - response = client.post('/api/foirawrequests',data=json.dumps(requestjson), content_type='application/json') - jsondata = json.loads(response.data) - print(str(jsondata["id"])) - updatejson = generalupdaterequestjson - updatejson['ispiiredacted'] = True - updatejson['assignedTo'] = "Intake Team" - updatejson['requeststatusid'] = 4 - wfupdateresponse = client.post('/api/foirawrequest/'+str(jsondata["id"]),data=json.dumps(updatejson), headers=factory_auth_header(app, client), content_type='application/json') - assert response.status_code == 200 and wfupdateresponse.status_code == 200 and len(jsondata) >=1 - -with open('tests/samplerequestjson/rawrequest.json') as x, open('tests/samplerequestjson/foirequest-general.json') as y, open('tests/samplerequestjson/foirequest-general-update.json') as z: - generalrequestjson = json.load(y) - generalupdaterequestjson = json.load(z) - rawrequestjson = json.load(x) -def test_post_foirequest_general(app, client): - rawresponse = client.post('/api/foirawrequests',data=json.dumps(rawrequestjson), headers=factory_auth_header(app, client), content_type='application/json') - jsondata = json.loads(rawresponse.data) - getrawresponse = client.get('/api/foirawrequest/'+str(jsondata["id"]), headers=factory_auth_header(app, client), content_type='application/json') - foirequest = generalrequestjson - foirequest["id"] = str(jsondata["id"]) - foirequest["requeststatusid"] = 1 - foiresponse = client.post('/api/foirequests',data=json.dumps(foirequest), headers=factory_auth_header(app, client), content_type='application/json') - getrawresponsefields = client.get('/api/foirawrequest/'+str(jsondata["id"])+'/fields?names=ministries', headers=factory_auth_header(app, client), content_type='application/json') - assert rawresponse.status_code == 200 and getrawresponse.status_code == 200 and foiresponse.status_code == 200 and getrawresponsefields.status_code == 200 - - -def test_get_programareas(app,client): - response = client.get('api/foiflow/programareas', headers=factory_auth_header(app, client), content_type='application/json') - jsondata = json.loads(response.data) - assert response.status_code == 200 and len(jsondata) >=1 - -def test_get_applicantcategories(app,client): - response = client.get('api/foiflow/applicantcategories', headers=factory_auth_header(app, client), content_type='application/json') - jsondata = json.loads(response.data) - assert response.status_code == 200 and len(jsondata) >=1 - - - - - diff --git a/historical-search-api/tests/samplerequestjson/foirequest-extension-oipc.json b/historical-search-api/tests/samplerequestjson/foirequest-extension-oipc.json deleted file mode 100644 index e06444469..000000000 --- a/historical-search-api/tests/samplerequestjson/foirequest-extension-oipc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extensionreasonid": 5, - "extendedduedays": 30, - "extendedduedate": "2022-03-04" - -} \ No newline at end of file diff --git a/historical-search-api/tests/samplerequestjson/foirequest-general-CFR.json b/historical-search-api/tests/samplerequestjson/foirequest-general-CFR.json deleted file mode 100644 index e887f67f4..000000000 --- a/historical-search-api/tests/samplerequestjson/foirequest-general-CFR.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "additionalPersonalInfo": { - "alsoKnownAs": "", - "birthDate": "", - "childFirstName": "", - "childMiddleName": "", - "childLastName": "", - "childAlsoKnownAs": "", - "childBirthDate": "", - "anotherFirstName": "", - "anotherMiddleName": "", - "anotherLastName": "", - "anotherAlsoKnownAs": "", - "anotherBirthDate": "", - "adoptiveMotherFirstName": "", - "adoptiveMotherLastName": "", - "adoptiveFatherLastName": "", - "adoptiveFatherFirstName": "" - }, - "firstName": "Sumathi", - "lastName": "Thirumani", - "email": "sumathi.thirumani@gmail.com", - "phonePrimary": "7787009903", - "address": "4801", - "city": "Victoria", - "postal": "V8Y 2J", - "category": "Business", - "description": "test", - "selectedMinistries": [ - { - "code": "EDU", - "id": 291, - "name": "Ministry of Education and Childcare", - "isSelected": true - } - ], - "stateTransition":[ - { - "status": "Call For Records", - "version": 3 - }, - { - "status": "Open", - "version": 1 - } - ], - "requestType": "general", - "receivedMode": "Email", - "deliveryMode": "Secure File Transfer", - "receivedDate": "2021-08-01", - "receivedDateUF": "2021-08-01T00:00:00.000Z", - "requestProcessStart": "2021-08-09", - "dueDate": "2021-09-21", - "cfrDueDate":"2021-09-29", - "assignedGroup": "Intake Team", - "assignedTo": "foiintake@idir", - "assignedToFirstName": "FOI", - "assignedToLastName": "Intake", - "axisRequestId": "ABC-2099-4208", - "programareaid": 6 -} - diff --git a/historical-search-api/tests/samplerequestjson/foirequest-general-update.json b/historical-search-api/tests/samplerequestjson/foirequest-general-update.json deleted file mode 100644 index 4f97a1777..000000000 --- a/historical-search-api/tests/samplerequestjson/foirequest-general-update.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "additionalPersonalInfo":{ - "alsoKnownAs":"", - "birthDate":"" - }, - "address":"4801", - "assignedGroup":"Flex Team", - "assignedTo": "foiflex@idir", - "assignedToFirstName": "foiflex", - "assignedToLastName": "proxy", - "assignedministrygroup":"EDU Ministry Team", - "assignedministryperson":"foiedu@idir", - "assignedministrypersonFirstName":"foiedu", - "assignedministrypersonLastName":"foiedu", - "businessName":"", - "category":"Business", - "categoryid":1, - "city":"Victoria", - "currentState":"Open", - "deliveryMode":"Secure File Transfer", - "deliverymodeid":1, - "description":"test", - "dueDate":"2021-10-04", - "cfrDueDate":"2021-09-29", - "email":"sumathi.thirumani@gmail.com", - "firstName":"Sumathi", - "fromDate":"", - "lastName":"Thirumani", - "middleName":"", - "phonePrimary":"7787009903", - "postal":"V8Y 2J", - "programareaid":10, - "receivedDate":"2021 Aug, 01", - "receivedDateUF":"2021-08-01 00:00:00.000000", - "receivedMode":"Email", - "receivedmodeid":1, - "requestProcessStart":"2021-08-20", - "requestType":"general", - "requeststatusid":1, - "selectedMinistries":[ - { - "code":"LBR", - "name":"Ministry of Labour", - "selected":"true" - } - ], - "toDate":"" - } \ No newline at end of file diff --git a/historical-search-api/tests/samplerequestjson/foirequest-general.json b/historical-search-api/tests/samplerequestjson/foirequest-general.json deleted file mode 100644 index 8d5305b8f..000000000 --- a/historical-search-api/tests/samplerequestjson/foirequest-general.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "additionalPersonalInfo": { - "alsoKnownAs": "", - "birthDate": "", - "childFirstName": "", - "childMiddleName": "", - "childLastName": "", - "childAlsoKnownAs": "", - "childBirthDate": "", - "anotherFirstName": "", - "anotherMiddleName": "", - "anotherLastName": "", - "anotherAlsoKnownAs": "", - "anotherBirthDate": "", - "adoptiveMotherFirstName": "", - "adoptiveMotherLastName": "", - "adoptiveFatherLastName": "", - "adoptiveFatherFirstName": "" - }, - "firstName": "Sumathi", - "lastName": "Thirumani", - "email": "sumathi.thirumani@gmail.com", - "phonePrimary": "7787009903", - "address": "4801", - "city": "Victoria", - "postal": "V8Y 2J", - "category": "Business", - "description": "test", - "selectedMinistries": [ - { - "code": "EDU", - "id": 291, - "name": "Ministry of Education and Childcare", - "isSelected": true - } - ], - "requestType": "general", - "receivedMode": "Email", - "deliveryMode": "Secure File Transfer", - "receivedDate": "2021-08-01", - "receivedDateUF": "2021-08-01T00:00:00.000Z", - "requestProcessStart": "2021-08-09", - "dueDate": "2021-09-21", - "cfrDueDate":"2021-09-29", - "assignedGroup": "Intake Team", - "assignedTo": "foiintake@idir", - "assignedToFirstName": "FOI", - "assignedToLastName": "Intake", - "axisRequestId": "ABC-2099-4208", - "programareaid": 6 -} \ No newline at end of file diff --git a/historical-search-api/tests/samplerequestjson/foirequest-ministry-general-update.json b/historical-search-api/tests/samplerequestjson/foirequest-ministry-general-update.json deleted file mode 100644 index a62efbd15..000000000 --- a/historical-search-api/tests/samplerequestjson/foirequest-ministry-general-update.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "assignedGroup":"Intake Team", - "assignedTo":"foiintake@idir", - "assignedToFirstName":"FOI", - "assignedToLastName":"Intake", - "assignedministrygroup":"EDU Ministry Team", - "assignedministryperson":"foiedu@idir", - "assignedministrypersonFirstName":"foiedu", - "assignedministrypersonLastName":"foiedu", - "category":"Individual", - "categoryid":2, - "cfrDueDate":"2021-11-26", - "currentState":"Call For Records", - "deliveryMode":"Secure File Transfer", - "deliverymodeid":1, - "description":"test", - "divisions":[ - { - "id":0, - "divisionid":2, - "stageid":1 - } - ], - "dueDate":"2022-01-12", - "fromDate":"", - "onholdTransitionDate":null, - "programareaid":6, - "receivedDate":"2021 Nov, 01", - "receivedDateUF":"2021-11-01 00:00:00.000000", - "receivedMode":"Email", - "receivedmodeid":1, - "requestProcessStart":"2021-11-30", - "requestType":"general", - "selectedMinistries":[ - { - "code":"EDU", - "name":"Ministry of Education and Childcare", - "selected":"true" - } - ], - "toDate":"" - } \ No newline at end of file diff --git a/historical-search-api/tests/samplerequestjson/foirequest-personal.json b/historical-search-api/tests/samplerequestjson/foirequest-personal.json deleted file mode 100644 index 421a709ae..000000000 --- a/historical-search-api/tests/samplerequestjson/foirequest-personal.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "wfinstanceid":"5eb4325b-f941-11eb-ae3f-0242c0a87002", - "requestType":"personal", - "firstName":"May", - "middleName":null, - "lastName":"C", - "businessName":null, - "currentState":"Intake in Progress", - "receivedDate":"2021 08, 10", - "receivedDateUF":"2021-08-10T00:00:00.000Z", - "assignedGroup": "Intake Team", - "assignedTo": "foiintake@idir", - "assignedToFirstName": "FOI", - "assignedToLastName": "Intake", - "xgov":"No", - "idNumber":"U-0010", - "email":"may@email.com", - "phonePrimary":null, - "phoneSecondary":null, - "address":null, - "city":null, - "postal":null, - "province":null, - "country":null, - "description":"personal request with 4 ministries selected", - "fromDate":"2021-07-04T04:00:00.000Z", - "toDate":"2021-08-01T04:00:00.000Z", - "correctionalServiceNumber":"correc.Service#", - "publicServiceEmployeeNumber":"1234567890", - "topic":"Other", - "selectedMinistries":[ - { - "code":"PSA", - "name":"BC Public Service Agency", - "selected":true - }, - { - "code":"EMBC", - "name":"Emergency Management BC", - "selected":true - }, - { - "code":"EAO", - "name":"Environmental Assessment Office", - "selected":true - }, - { - "code":"JERI", - "name":"Jobs, Economic Recovery and Innovation", - "selected":true - } - ], - "additionalPersonalInfo":{ - "alsoKnownAs":null, - "requestFor":{ - "yourself":true, - "child":null, - "another":null - }, - "birthDate":"1998-04-06T04:00:00.000Z", - "childFirstName":"", - "childMiddleName":"", - "childLastName":"", - "childAlsoKnownAs":"", - "childBirthDate":"", - "anotherFirstName":"", - "anotherMiddleName":"", - "anotherLastName":"", - "anotherAlsoKnownAs":"", - "anotherBirthDate":"", - "adoptiveMotherFirstName":"", - "adoptiveMotherLastName":"", - "adoptiveFatherLastName":"", - "adoptiveFatherFirstName":"" - }, - "category":"Interest Group", - "receivedMode":"Fax", - "deliveryMode":"In Person Pick up", - "requestProcessStart":"2021-08-10", - "dueDate":"2021-09-22", - "cfrDueDate":"2021-09-29" -} \ No newline at end of file diff --git a/historical-search-api/tests/samplerequestjson/rawrequest.json b/historical-search-api/tests/samplerequestjson/rawrequest.json deleted file mode 100644 index 1eb5b7d45..000000000 --- a/historical-search-api/tests/samplerequestjson/rawrequest.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "requestData": { - "requestType": { - "requestType": "general" - }, - "ministry": { - "selectedMinistry": [], - "ministryPage": "ABCDEFGHIJK", - "defaultMinistry": { - "code": "ABCDEFGHIJKLMNOP", - "name": "ABCDEFGHIJK", - "defaulted": false, - "selected": true - } - }, - "descriptionTimeframe": { - "description": "DAN DESC", - "fromDate": "01-01-2021", - "toDate": "03-03-2021", - "correctionalServiceNumber": "ABCDEFGHIJK", - "publicServiceEmployeeNumber": null, - "topic": "ABCDEFGHI" - }, - "contactInfo": { - "firstName": "UT - UserFirstName", - "lastName": "UT - UserLastName", - "middleName": null, - "businessName": "JD Business", - "alsoKnownAs": "JD Tech", - "birthDate": "10-12-1975" - }, - "contactInfoOptions": { - "email": "john1@email.com", - "phonePrimary": null, - "phoneSecondary": "ABCDEFGHIJKLMNOPQR", - "address": null, - "city": null, - "postal": "ABCDE", - "province": null, - "country": "ABCDEF" - }, - "choose-idenity": { - "answerYes": null - }, - "selectAbout": { - "yourself": false, - "child": false, - "another": null - }, - "requestTopic": { - "value": null, - "text": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", - "ministryCode": "ABCDEFGHIJKLMNOPQRSTUVW" - }, - "adoptiveParents": { - "motherFirstName": null, - "motherLastName": null, - "fatherFirstName": "ABCDEFGHIJKLMNO", - "fatherLastName": null - }, - "Attachments": [] - } - } \ No newline at end of file diff --git a/historical-search-api/tests/samplerequestjson/s3storagerequest.json b/historical-search-api/tests/samplerequestjson/s3storagerequest.json deleted file mode 100644 index 892225890..000000000 --- a/historical-search-api/tests/samplerequestjson/s3storagerequest.json +++ /dev/null @@ -1,13 +0,0 @@ -[{ - "ministrycode":"EDU", - "requestnumber":"EDU-2021-98976", - "filestatustransition":"cfr-review", - "filename":"reviewapproval.pdf" -}, -{ - "ministrycode":"EDU", - "requestnumber":"EDU-2021-98976", - "filestatustransition":"cfr-review", - "filename":"misc.pdf" -} -] \ No newline at end of file diff --git a/historical-search-api/tests/services/test_dashboard_services.py b/historical-search-api/tests/services/test_dashboard_services.py deleted file mode 100644 index d6068166c..000000000 --- a/historical-search-api/tests/services/test_dashboard_services.py +++ /dev/null @@ -1,14 +0,0 @@ -import pytest -from request_api.services.dashboardservice import dashboardservice -import json -from request_api.utils.enums import MinistryTeamWithKeycloackGroup - -def test_get_requests_dashboard(session): - groups = ["Intake Team","Flex Team"] - queue = dashboardservice().getrequestqueuepagination(groups) - assert queue.data is not None - -def test_get_ministryrequests_dashboard(session): - groups = MinistryTeamWithKeycloackGroup.list() - queue = dashboardservice().getrequestqueuepagination(groups) - assert queue.data is not None \ No newline at end of file diff --git a/historical-search-api/tests/services/test_masterdata_services.py b/historical-search-api/tests/services/test_masterdata_services.py deleted file mode 100644 index 3733353f0..000000000 --- a/historical-search-api/tests/services/test_masterdata_services.py +++ /dev/null @@ -1,35 +0,0 @@ -from request_api.services.programareaservice import programareaservice -from request_api.services.applicantcategoryservice import applicantcategoryservice -from request_api.services.deliverymodeservice import deliverymodeservice -from request_api.services.receivedmodeservice import receivedmodeservice - -def test_get_programareas(session): - response = programareaservice().getprogramareas() - assert response - # assert the structure is correct by checking for name, description properties in each element - for item in response: - assert item['name'] and item['iaocode'] - - -def test_get_applicantcategories(session): - response = applicantcategoryservice().getapplicantcategories() - assert response - # assert the structure is correct by checking for name, description properties in each element - for item in response: - assert item['name'] and item['description'] - -def test_get_receivedmodes(session): - response = receivedmodeservice().getreceivedmodes() - assert response - # assert the structure is correct by checking for name, description properties in each element - for item in response: - assert item['name'] and item['description'] - -def test_get_deliverymodes(session): - response = deliverymodeservice().getdeliverymodes() - assert response - # assert the structure is correct by checking for name, description properties in each element - for item in response: - assert item['name'] and item['description'] - - diff --git a/historical-search-api/tests/services/test_rawrequest_services.py b/historical-search-api/tests/services/test_rawrequest_services.py deleted file mode 100644 index 770b51166..000000000 --- a/historical-search-api/tests/services/test_rawrequest_services.py +++ /dev/null @@ -1,25 +0,0 @@ -import pytest -from request_api.services.rawrequestservice import rawrequestservice -import json -import uuid - - - - -with open('tests/samplerequestjson/rawrequest.json') as f: - requestjson = json.load(f) - -def pytest_namespace(): - return {'requestidtoupdate': 0} - -def test_get_rawrequests(session): - response = rawrequestservice().getrawrequests() - assert response - requestid ='' - for item in response: - requestid = item['id'] - assert item['id'] and item['requestType'] - getresponse = rawrequestservice().getrawrequest(requestid) - assert getresponse['requestType'] - - diff --git a/historical-search-api/tests/services/test_requestextension_services.py b/historical-search-api/tests/services/test_requestextension_services.py deleted file mode 100644 index 176f78ac2..000000000 --- a/historical-search-api/tests/services/test_requestextension_services.py +++ /dev/null @@ -1,29 +0,0 @@ -import pytest -from request_api.services.extensionservice import extensionservice -import json -import uuid - - - - -with open('tests/samplerequestjson/foirequest-extension-oipc.json') as f: - requestjson = json.load(f) - -def pytest_namespace(): - return {'requestidtoupdate': 0} - -def test_create_extension(session): - requestid = 1 - ministryrequestid = 1 - response = extensionservice().createrequestextension(requestid, ministryrequestid, requestjson, userid='dviswana@idir') - requestid = response.identifier - pytest.approxrequestidtoupdate = requestid - assert response.success == True - -def test_get_request_extensions(session): - requestid = 1 - extensions = extensionservice().getrequestextensions(requestid) - for extension in extensions: - assert extension["extensionreson"] - - From 84a80f9d7822a739f893accabe970e9dfb697b9c Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 29 May 2024 09:37:27 -0700 Subject: [PATCH 03/33] completed historical request details front end and backend --- docker-compose.yml | 1 + .../src/apiManager/endpoints/config.js | 4 +- .../src/apiManager/endpoints/index.js | 5 + .../services/FOI/foiRequestServices.js | 72 +++++++- .../components/FOI/FOIAuthenticateRouting.jsx | 3 + .../components/FOI/FOIRequest/FOIRequest.js | 35 ++-- .../FOI/FOIRequest/FOIRequestHeader/index.js | 51 ++++-- .../FOI/FOIRequest/RequestDescriptionBox.js | 1 + .../FOI/FOIRequest/RequestDetails.js | 160 ++++++++++++------ .../FOI/customComponents/StateDropDown.js | 51 ++++-- .../constants/FOI/foiComponentConstants.js | 1 + forms-flow-web/src/constants/constants.js | 2 +- .../request_api/models/factRequestDetails.py | 72 ++++++-- .../request_api/resources/request.py | 76 ++++----- .../services/historicalrequestservice.py | 11 +- 15 files changed, 385 insertions(+), 160 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 20591e134..1924e3eea 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -352,6 +352,7 @@ services: - REACT_APP_DISABLE_GATHERINGRECORDS_TAB=${DISABLE_GATHERINGRECORDS_TAB} - REACT_APP_RECORD_DOWNLOAD_LIMIT=${RECORD_DOWNLOAD_LIMIT} - REACT_APP_RECORD_DOWNLOAD_SIZE_LIMIT=${RECORD_DOWNLOAD_SIZE_LIMIT} + - REACT_APP_HISTORICAL_API_URL=${HISTORICAL_API_URL} volumes: - ".:/app" - "/app/node_modules" diff --git a/forms-flow-web/src/apiManager/endpoints/config.js b/forms-flow-web/src/apiManager/endpoints/config.js index 6baadfb07..a8fad0944 100644 --- a/forms-flow-web/src/apiManager/endpoints/config.js +++ b/forms-flow-web/src/apiManager/endpoints/config.js @@ -5,4 +5,6 @@ export const FOI_BASE_API_URL = `${(window._env_ && window._env_.REACT_APP_FOI_B export const AXIS_API_URL = `${(window._env_ && window._env_.REACT_APP_AXIS_API_URL) || process.env.REACT_APP_AXIS_API_URL}`; -export const DOC_REVIEWER_BASE_API_URL = `${(window._env_ && window._env_.REACT_APP_DOCREVIEWER_BASE_API_URL) || process.env.REACT_APP_DOCREVIEWER_BASE_API_URL}`; \ No newline at end of file +export const DOC_REVIEWER_BASE_API_URL = `${(window._env_ && window._env_.REACT_APP_DOCREVIEWER_BASE_API_URL) || process.env.REACT_APP_DOCREVIEWER_BASE_API_URL}`; + +export const FOI_HISTORICAL_API_URL = `${(window._env_ && window._env_.REACT_APP_HISTORICAL_API_URL) || process.env.REACT_APP_HISTORICAL_API_URL}`; \ No newline at end of file diff --git a/forms-flow-web/src/apiManager/endpoints/index.js b/forms-flow-web/src/apiManager/endpoints/index.js index 4a03e6cc0..2804ed4ea 100644 --- a/forms-flow-web/src/apiManager/endpoints/index.js +++ b/forms-flow-web/src/apiManager/endpoints/index.js @@ -2,6 +2,7 @@ import { FOI_BASE_API_URL, AXIS_API_URL, DOC_REVIEWER_BASE_API_URL, + FOI_HISTORICAL_API_URL } from "./config"; const API = { @@ -40,6 +41,10 @@ const API = { FOI_GET_CLOSING_REASONS: `${FOI_BASE_API_URL}/api/foiflow/closereasons`, FOI_POST_OSS_HEADER: `${FOI_BASE_API_URL}/api/foiflow/oss/authheader`, + FOI_HISTORICAL_REQUEST_API: `${FOI_HISTORICAL_API_URL}/api/foihistoricalrequest`, + FOI_HISTORICAL_REQUEST_DESCRIPTION_API: `${FOI_HISTORICAL_API_URL}/api/foihistoricalrequest/descriptionhistory`, + FOI_HISTORICAL_REQUEST_EXTENSIONS_API: `${FOI_HISTORICAL_API_URL}/api/foihistoricalrequest/extensions`, + FOI_GET_PROGRAMAREADIVISIONS: `${FOI_BASE_API_URL}/api/foiadmin/divisions`, FOI_POST_PROGRAMAREADIVISION: `${FOI_BASE_API_URL}/api/foiadmin/division`, FOI_PUT_PROGRAMAREADIVISIONS: `${FOI_BASE_API_URL}/api/foiadmin/division/`, diff --git a/forms-flow-web/src/apiManager/services/FOI/foiRequestServices.js b/forms-flow-web/src/apiManager/services/FOI/foiRequestServices.js index 50475471c..b90d05842 100644 --- a/forms-flow-web/src/apiManager/services/FOI/foiRequestServices.js +++ b/forms-flow-web/src/apiManager/services/FOI/foiRequestServices.js @@ -14,7 +14,8 @@ import { setFOIRequestDescriptionHistory, setFOIMinistryRequestList, setOpenedMinistries, - setRestrictedReqTaglist + setRestrictedReqTaglist, + setRequestExtensions, } from "../../../actions/FOI/foiRequestActions"; import { fetchFOIAssignedToList, fetchFOIMinistryAssignedToList, fetchFOIProcessingTeamList } from "./foiMasterDataServices"; import { catchError, fnDone} from './foiServicesUtil'; @@ -550,4 +551,71 @@ export const deleteOIPCDetails = (requestId, ministryId, ...rest) => { catchError(error, dispatch); }); }; -} \ No newline at end of file +} + +export const fetchHistoricalRequestDetails = (requestId) => { + + const apiUrlgetRequestDetails = API.FOI_HISTORICAL_REQUEST_API + "/" + requestId; + return (dispatch) => { + httpGETRequest(apiUrlgetRequestDetails, {}, UserService.getToken()) + .then((res) => { + if (res.data) { + const foiRequest = res.data; + dispatch(clearRequestDetails({})); + dispatch(setFOIRequestDetail(foiRequest)); + dispatch(setFOILoader(false)); + } else { + dispatch(serviceActionError(res)); + dispatch(setFOILoader(false)); + throw new Error(`Error in fetching request details for request# ${requestId}`) + } + }) + .catch((error) => { + catchError(error, dispatch); + }); + }; +}; + +export const fetchFOIHistoricalRequestDescriptionList = (requestId) => { + let apiUrl = API.FOI_HISTORICAL_REQUEST_DESCRIPTION_API + "/" + requestId; + + return (dispatch) => { + httpGETRequest(apiUrl, {}, UserService.getToken()) + .then((res) => { + if (res.data) { + dispatch(setFOIRequestDescriptionHistory(res.data)); + dispatch(setFOILoader(false)); + } else { + dispatch(serviceActionError(res)); + dispatch(setFOILoader(false)); + throw new Error(`Error while fetching the request description history (request# ${requestId})`); + } + }) + .catch((error) => { + catchError(error, dispatch); + }); + }; +}; + +export const fetchHistoricalExtensions = ( + requestId, +) => { + const apiUrl = API.FOI_HISTORICAL_REQUEST_EXTENSIONS_API + "/" + requestId + + return (dispatch) => { + httpGETRequest(apiUrl, {}, UserService.getToken()) + .then((res) => { + if (res.data) { + dispatch(setRequestExtensions(res.data)); + dispatch(setFOILoader(false)); + } else { + console.log("Error in fetching attachment list", res); + dispatch(serviceActionError(res)); + } + }) + .catch((error) => { + console.log("Error in fetching attachment list", error); + dispatch(serviceActionError(error)); + }); + } +}; \ No newline at end of file diff --git a/forms-flow-web/src/components/FOI/FOIAuthenticateRouting.jsx b/forms-flow-web/src/components/FOI/FOIAuthenticateRouting.jsx index 8b3f3d346..34dfb014f 100644 --- a/forms-flow-web/src/components/FOI/FOIAuthenticateRouting.jsx +++ b/forms-flow-web/src/components/FOI/FOIAuthenticateRouting.jsx @@ -59,6 +59,9 @@ const FOIAuthenticateRouting = React.memo((props) => { + + + diff --git a/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js b/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js index 00ac839ae..e3b88fe02 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js @@ -34,7 +34,10 @@ import { fetchFOIRequestDescriptionList, fetchRequestDataFromAxis, fetchRestrictedRequestCommentTagList, - deleteOIPCDetails + deleteOIPCDetails, + fetchHistoricalRequestDetails, + fetchFOIHistoricalRequestDescriptionList, + fetchHistoricalExtensions } from "../../../apiManager/services/FOI/foiRequestServices"; import { fetchFOIRequestAttachmentsList } from "../../../apiManager/services/FOI/foiAttachmentServices"; import { fetchCFRForm } from "../../../apiManager/services/FOI/foiCFRFormServices"; @@ -130,6 +133,7 @@ const FOIRequest = React.memo(({ userDetail }) => { const { requestId, ministryId } = useParams(); const url = window.location.href; const urlIndexCreateRequest = url.indexOf(FOI_COMPONENT_CONSTANTS.ADDREQUEST); + const isHistoricalRequest = url.indexOf(FOI_COMPONENT_CONSTANTS.HISTORICAL_REQUEST) > -1; const [isAddRequest, setIsAddRequest] = useState(urlIndexCreateRequest > -1); //gets the request detail from the store let requestDetails = useSelector( @@ -251,7 +255,7 @@ const FOIRequest = React.memo(({ userDetail }) => { StateEnum.intakeinprogress.name.toLowerCase(); const [axisSyncedData, setAxisSyncedData] = useState({}); const [checkExtension, setCheckExtension] = useState(true); - let bcgovcode = getBCgovCode(ministryId, requestDetails); + let bcgovcode = isHistoricalRequest ? "" : getBCgovCode(ministryId, requestDetails); const [headerText, setHeaderText] = useState( getHeaderText({ requestDetails, ministryId, requestState }) ); @@ -292,6 +296,10 @@ const FOIRequest = React.memo(({ userDetail }) => { if (isAddRequest) { dispatch(fetchFOIAssignedToList("", "", "")); dispatch(fetchFOIProgramAreaList()); + } else if (isHistoricalRequest) { + dispatch(fetchHistoricalRequestDetails(requestId)); + dispatch(fetchFOIHistoricalRequestDescriptionList(requestId)); + dispatch(fetchHistoricalExtensions(requestId)); } else { await Promise.all([ dispatch(fetchFOIProgramAreaList()), @@ -334,7 +342,7 @@ const FOIRequest = React.memo(({ userDetail }) => { useEffect(() => { const requestDetailsValue = requestDetails; setSaveRequestObject(requestDetailsValue); - const assignedTo = getAssignedTo(requestDetails); + const assignedTo = isHistoricalRequest ? requestDetails.assignedTo : getAssignedTo(requestDetails); setAssignedToValue(assignedTo); if (Object.entries(requestDetails)?.length !== 0) { let requestStateFromId = findRequestState(requestDetails.requeststatuslabel) @@ -962,7 +970,8 @@ const FOIRequest = React.memo(({ userDetail }) => { requestState !== StateEnum.unopened.name && requestState !== StateEnum.open.name && (requestDetails?.divisions?.length > 0 || isMCFPersonal) && - DISABLE_GATHERINGRECORDS_TAB?.toLowerCase() =='false' + DISABLE_GATHERINGRECORDS_TAB?.toLowerCase() =='false' && + !isHistoricalRequest ); }; @@ -1024,6 +1033,7 @@ const FOIRequest = React.memo(({ userDetail }) => { isValidationError={isValidationError} requestType={requestDetails?.requestType} isDivisionalCoordinator={false} + isHistoricalRequest={isHistoricalRequest} /> @@ -1211,7 +1221,8 @@ const FOIRequest = React.memo(({ userDetail }) => { createSaveRequestObject={createSaveRequestObject} handlestatusudpate={handlestatusudpate} userDetail={userDetail} - disableInput={disableInput} + disableInput={disableInput || isHistoricalRequest} + isHistoricalRequest={isHistoricalRequest} isMinistry={false} isAddRequest={isAddRequest} handleOipcReviewFlagChange={handleOipcReviewFlagChange} @@ -1242,7 +1253,7 @@ const FOIRequest = React.memo(({ userDetail }) => { handleApplicantDetailsValue } createSaveRequestObject={createSaveRequestObject} - disableInput={disableInput} + disableInput={disableInput || isHistoricalRequest} userDetail={userDetail} /> {requiredRequestDetailsValues.requestType.toLowerCase() === @@ -1253,7 +1264,7 @@ const FOIRequest = React.memo(({ userDetail }) => { requestDetails.additionalPersonalInfo } createSaveRequestObject={createSaveRequestObject} - disableInput={disableInput} + disableInput={disableInput || isHistoricalRequest} userDetail={userDetail} requestType={requestDetails?.requestType} /> @@ -1262,7 +1273,7 @@ const FOIRequest = React.memo(({ userDetail }) => { requestDetails.additionalPersonalInfo } createSaveRequestObject={createSaveRequestObject} - disableInput={disableInput} + disableInput={disableInput || isHistoricalRequest} /> )} @@ -1275,7 +1286,7 @@ const FOIRequest = React.memo(({ userDetail }) => { handleContactDetailsInitialValue } handleContanctDetailsValue={handleContanctDetailsValue} - disableInput={disableInput} + disableInput={disableInput || isHistoricalRequest} handleEmailValidation={handleEmailValidation} userDetail={userDetail} /> @@ -1297,7 +1308,7 @@ const FOIRequest = React.memo(({ userDetail }) => { handleInitialRequiredRequestDescriptionValues } createSaveRequestObject={createSaveRequestObject} - disableInput={disableInput} + disableInput={disableInput || isHistoricalRequest} /> { handleRequestDetailsInitialValue } createSaveRequestObject={createSaveRequestObject} - disableInput={disableInput} + disableInput={disableInput || isHistoricalRequest} + isHistoricalRequest={isHistoricalRequest} /> {(redactedSections && Object.keys(redactedSections).length > 0 && ( @@ -1605,6 +1617,7 @@ const FOIRequest = React.memo(({ userDetail }) => { divisions={requestDetails.divisions} recordsTabSelect={tabLinksStatuses.Records.active} requestType={requestDetails?.requestType} + handleSaveRequest={handleSaveRequest} /> )} diff --git a/forms-flow-web/src/components/FOI/FOIRequest/FOIRequestHeader/index.js b/forms-flow-web/src/components/FOI/FOIRequest/FOIRequestHeader/index.js index 1e57d56e2..2a76a6055 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/FOIRequestHeader/index.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/FOIRequestHeader/index.js @@ -56,6 +56,7 @@ const FOIRequestHeader = React.memo( handleOipcReviewFlagChange, showOipcReviewFlag, isMinistry, + isHistoricalRequest, }) => { /** * Header of Review request in the UI @@ -247,23 +248,39 @@ const FOIRequestHeader = React.memo(
- } - variant="outlined" - fullWidth - required - disabled={disableHeaderInput} - error={selectedAssignedTo.toLowerCase().includes("unassigned")} - > - {menuItems} - + {!isHistoricalRequest ? + } + variant="outlined" + fullWidth + required + disabled={disableHeaderInput} + error={selectedAssignedTo.toLowerCase().includes("unassigned")} + > + {menuItems} + + : + } + variant="outlined" + fullWidth + disabled + > + {menuItems} + + }
diff --git a/forms-flow-web/src/components/FOI/FOIRequest/RequestDescriptionBox.js b/forms-flow-web/src/components/FOI/FOIRequest/RequestDescriptionBox.js index d0f8abbfa..46ff06936 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/RequestDescriptionBox.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/RequestDescriptionBox.js @@ -316,6 +316,7 @@ const RequestDescription = React.memo(({ input={} variant="outlined" fullWidth + disabled={disableInput} > {subjectCodes} diff --git a/forms-flow-web/src/components/FOI/FOIRequest/RequestDetails.js b/forms-flow-web/src/components/FOI/FOIRequest/RequestDetails.js index 43e4b4ae1..4ee9e5deb 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/RequestDetails.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/RequestDetails.js @@ -25,6 +25,7 @@ const RequestDetails = React.memo( handleRequestDetailsInitialValue, createSaveRequestObject, disableInput, + isHistoricalRequest }) => { /** * Request details box in the UI * All fields are mandatory here @@ -331,64 +332,113 @@ const RequestDetails = React.memo(
- } - variant="outlined" - fullWidth - required - disabled={disableInput || disableFieldForMinistryRequest} - error={selectedRequestType.toLowerCase().includes("select")} - > - {requestTypes} - + {!isHistoricalRequest ? + } + variant="outlined" + fullWidth + required + disabled={disableInput || disableFieldForMinistryRequest} + error={selectedRequestType.toLowerCase().includes("select")} + > + {requestTypes} + + : + } + variant="outlined" + fullWidth + required + disabled + > + + } - } - variant="outlined" - fullWidth - required - disabled={disableInput || disableFieldForMinistryRequest} - error={selectedDeliveryMode.toLowerCase().includes("select")} - > - {deliveryModes} - + {!isHistoricalRequest ? + } + variant="outlined" + fullWidth + required + disabled={disableInput || disableFieldForMinistryRequest} + error={selectedDeliveryMode.toLowerCase().includes("select")} + > + {deliveryModes} + + : + } + variant="outlined" + fullWidth + disabled + > + + }
- } - variant="outlined" - fullWidth - required - error={selectedReceivedMode.toLowerCase().includes("select")} - disabled={disableInput || - requestDetails.receivedMode?.toLowerCase() === FOI_COMPONENT_CONSTANTS.ONLINE_FORM.toLowerCase() || - requestDetails.currentState?.toLowerCase() === StateEnum.unopened.name.toLowerCase() || - disableFieldForMinistryRequest - } - > - {receivedModes} - + {!isHistoricalRequest ? + } + variant="outlined" + fullWidth + required + error={selectedReceivedMode.toLowerCase().includes("select")} + disabled={disableInput || + requestDetails.receivedMode?.toLowerCase() === FOI_COMPONENT_CONSTANTS.ONLINE_FORM.toLowerCase() || + requestDetails.currentState?.toLowerCase() === StateEnum.unopened.name.toLowerCase() || + disableFieldForMinistryRequest + } + > + {receivedModes} + + : + } + variant="outlined" + fullWidth + disabled + > + + }
diff --git a/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js b/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js index 0b8c309a6..c1f91fcf4 100644 --- a/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js +++ b/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js @@ -22,6 +22,7 @@ const StateDropDown = ({ updateStateDropDown, requestType, isDivisionalCoordinator, + isHistoricalRequest, }) => { const _isMinistryCoordinator = isMinistryCoordinator; @@ -252,22 +253,40 @@ const StateDropDown = ({ ); }); return ( - } - variant="outlined" - fullWidth - disabled={isDivisionalCoordinator} - > - {menuItems} - + !isHistoricalRequest ? + } + variant="outlined" + fullWidth + disabled={isDivisionalCoordinator} + > + {menuItems} + + : + } + variant="outlined" + fullWidth + disabled + > + + + ); }; diff --git a/forms-flow-web/src/constants/FOI/foiComponentConstants.js b/forms-flow-web/src/constants/FOI/foiComponentConstants.js index b8fbb45c8..2e3aa1b6f 100644 --- a/forms-flow-web/src/constants/FOI/foiComponentConstants.js +++ b/forms-flow-web/src/constants/FOI/foiComponentConstants.js @@ -63,6 +63,7 @@ const FOI_COMPONENT_CONSTANTS = { PROGRAM_AREA_SELECTED: "selected", ADDITIONAL_PERSONAL_INFORMATION: "additionalPersonalInfo", ADDREQUEST: "addrequest", + HISTORICAL_REQUEST: "historicalrequest", RQUESTDETAILS_INITIALVALUES: "initialValues", ASSIGNEE_GROUPS: ["Intake Team", "Flex Team", "Processing Team"], ONLINE_FORM: "Online Form", diff --git a/forms-flow-web/src/constants/constants.js b/forms-flow-web/src/constants/constants.js index 6aaf00453..bfb7ecf06 100644 --- a/forms-flow-web/src/constants/constants.js +++ b/forms-flow-web/src/constants/constants.js @@ -62,7 +62,7 @@ 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'; export const RECORD_DOWNLOAD_LIMIT = (window._env_ && window._env_.REACT_APP_RECORD_DOWNLOAD_LIMIT) || process.env.REACT_APP_RECORD_DOWNLOAD_LIMIT || 500; diff --git a/historical-search-api/request_api/models/factRequestDetails.py b/historical-search-api/request_api/models/factRequestDetails.py index 7df15f2d1..be3350b5c 100644 --- a/historical-search-api/request_api/models/factRequestDetails.py +++ b/historical-search-api/request_api/models/factRequestDetails.py @@ -2,10 +2,11 @@ from .default_method_result import DefaultMethodResult from sqlalchemy import text import logging +from dateutil.parser import parse class factRequestDetails(db.Model): - __tablename__ = 'ApplicantCategories' + __tablename__ = 'factRequestDetails' # Defining the columns foirequestid = db.Column(db.Integer, primary_key=True,autoincrement=True) @@ -14,6 +15,8 @@ def getrequestbyid(cls, requestid): request = {} try: sql = """select + rd.visualrequestfilenumber, + rd.primaryusername, rt.requesttypename, rm.receivedmodename, dm.deliverymodename, @@ -25,21 +28,25 @@ def getrequestbyid(cls, requestid): rd.description, rd.startdate, rd.closeddate, rd.receiveddate, rd.targetdate AS duedate, rd.originaltargetdate AS originalduedate, rd.subject --, rd.* - from public."factRequestDetails" rd - join public."dimRequestStatuses" rs on rs.requeststatusid = rd.requeststatusid - join public."factRequestRequesters" rr1 on rr1.requesterid = rd.requesterid and rr1.foirequestid = rd.foirequestid and rr1.activeflag = 'Y' + from public."ClosedRequestDetailsPost2018" rd + join public."dimRequestStatuses" rs on rs.requeststatusid = rd.requeststatusid + --join public."factRequestRequesters" rr1 on rr1.requesterid = rd.requesterid and rr1.foirequestid = rd.foirequestid and rr1.activeflag = 'Y' - join public."dimRequesters" r on rr1.requesterid = r.requesterid - left join public."factRequestRequesters" rr2 on rr2.requesterid = rd.onbehalfofrequesterid and rr2.foirequestid = rd.foirequestid and rr2.activeflag = 'Y' - left join public."dimRequesters" r2 on rr2.requesterid = r2.requesterid + join public."dimRequesters" r on r.requesterid = rd.requesterid + -- left join public."factRequestRequesters" rr2 on rr2.requesterid = rd.onbehalfofrequesterid and rr2.foirequestid = rd.foirequestid and rr2.activeflag = 'Y' + left join public."dimRequesters" r2 on rd.onbehalfofrequesterid = r2.requesterid LEFT JOIN "dimRequesterTypes" rqt ON rd.applicantcategoryid = rqt.requestertypeid join public."dimReceivedModes" rm on rm.receivedmodeid = rd.receivedmodeid join public."dimAddress" a on a.addressid = rd.shipaddressid join public."dimRequestTypes" rt on rt.requesttypeid = rd.requesttypeid join public."dimDeliveryModes" dm on dm.deliverymodeid = rd.deliverymodeid - where rd.visualrequestfilenumber = 'CFD-2023-30109' and rd.activeflag = 'Y'""" + where rd.visualrequestfilenumber = :requestid and rd.activeflag = 'Y'""" rs = db.session.execute(text(sql), {'requestid': requestid}) for row in rs: + request["axisRequestId"] = row['visualrequestfilenumber'] + request["currentState"] = row['requeststatusname'] + request["assignedTo"] = row['primaryusername'] + request["requeststatuslabel"] = 'closed' request["firstName"] = row['firstname'] request["lastName"] = row['lastname'] request["middleName"] = row['middlename'] @@ -54,7 +61,7 @@ def getrequestbyid(cls, requestid): request["phoneSecondary"] = row['mobile'] request["workPhonePrimary"] = row['workphone1'] request["workPhoneSecondary"] = row['workphone2'] - request["address"] = row['address'] + request["address"] = row['address1'] request["addressSecondary"] = row['address2'] request["city"] = row['city'] request["country"] = row['country'] @@ -62,11 +69,11 @@ def getrequestbyid(cls, requestid): request["province"] = row['state'] request["description"] = row['description'] request["subjectCode"] = row['subject'] - request["receivedDate"] = row['receiveddate'] - request["requestProcessStart"] = row['startdate'] - request["originalDueDate"] = row['originalduedate'] - request["dueDate"] = row['duedate'] - request["closedate"] = row['closeddate'] + request["receivedDateUF"] = row['receiveddate'].strftime('%Y-%m-%d') + request["requestProcessStart"] = row['startdate'].strftime('%Y-%m-%d') + request["originalDueDate"] = row['originalduedate'].strftime('%Y-%m-%d') + request["dueDate"] = row['duedate'].strftime('%Y-%m-%d') + request["closedate"] = row['closeddate'].strftime('%Y-%m-%d') request["requestType"] = row['requesttypename'] request["receivedMode"] = row['receivedmodename'] request["deliveryMode"] = row['deliverymodename'] @@ -79,6 +86,39 @@ def getrequestbyid(cls, requestid): raise ex finally: db.session.close() - return requestdetails + return request - \ No newline at end of file + @classmethod + def getdescriptionhistorybyid(cls, requestid): + history = [] + try: + sql = """ + with dbrequestid as (select foirequestid from public."ClosedRequestDetailsPost2018" where visualrequestfilenumber = :requestid) + SELECT + description, + modifieddate, + createddate, + modifiedbyusername + FROM public."factRequestDetails" + where foirequestid = (select foirequestid from dbrequestid) + and runcycleid in (select max(runcycleid) from public."factRequestDetails" + where foirequestid = (select foirequestid from dbrequestid) + group by description) + ORDER BY runcycleid DESC""" + rs = db.session.execute(text(sql), {'requestid': requestid}) + for row in rs: + entry = {} + entry["createdAt"] = row['modifieddate'].strftime('%Y-%m-%d') + entry["createdBy"] = row['modifiedbyusername'] + entry["description"] = row['description'] + history.append(entry) + # requestdetails["assignedToFirstName"] = row["assignedtofirstname"] + # requestdetails["assignedToLastName"] = row["assignedtolastname"] + # requestdetails["bcgovcode"] = row["bcgovcode"] + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return history + diff --git a/historical-search-api/request_api/resources/request.py b/historical-search-api/request_api/resources/request.py index 88fdc36da..01745b9db 100644 --- a/historical-search-api/request_api/resources/request.py +++ b/historical-search-api/request_api/resources/request.py @@ -59,49 +59,47 @@ class FOIRawRequest(Resource): # @auth.require def get(requestid): try : - jsondata = {} statuscode = 200 - # requestidisinteger = int(requestid) - # if requestidisinteger : - # baserequestinfo = rawrequestservice().getrawrequest(requestid) - - # assignee = baserequestinfo['assignedTo'] - # isiaorestricted = baserequestinfo['isiaorestricted'] - # # print('Request # {0} Assigned to {1} and is restricted {2} '.format(requestid,assignee,isiaorestricted)) - # if(isiaorestricted and canrestictdata(requestid,assignee,isiaorestricted,True)): - # jsondata = {'status': 401, 'message':'Restricted Request'} - # statuscode = 401 - # else: - # jsondata = json.dumps(baserequestinfo) - # statuscode = 200 + jsondata = historicalrequestservice().gethistoricalrequest(requestid) + return jsondata , statuscode + except ValueError: + return {'status': 500, 'message':INVALID_REQUEST_ID}, 500 + except BusinessException as exception: + return {'status': exception.status_code, 'message':exception.message}, 500 + -# select -# rt.requesttypename, -# rm.receivedmodename, -# dm.deliverymodename, -# rqt.requestertypename, -# r.firstname, r.lastname, r.company, r.email, r.workphone1, r.workphone2, r.mobile, r.home, -# r2.firstname, r2.lastname, -# rs.requeststatusname, -# a.address1, a.address2, a.city, a.state, a.country, a.zipcode, -# rd.description, rd.startdate, rd.closeddate, rd.receiveddate, rd.targetdate AS duedate, -# rd.subject -# --, rd.* -# from public."factRequestDetails" rd -# join public."dimRequestStatuses" rs on rs.requeststatusid = rd.requeststatusid -# join public."factRequestRequesters" rr1 on rr1.requesterid = rd.requesterid and rr1.foirequestid = rd.foirequestid and rr1.activeflag = 'Y' - -# join public."dimRequesters" r on rr1.requesterid = r.requesterid -# left join public."factRequestRequesters" rr2 on rr2.requesterid = rd.onbehalfofrequesterid and rr2.foirequestid = rd.foirequestid and rr2.activeflag = 'Y' -# left join public."dimRequesters" r2 on rr2.requesterid = r2.requesterid -# LEFT JOIN "dimRequesterTypes" rqt ON rd.applicantcategoryid = rqt.requestertypeid -# join public."dimReceivedModes" rm on rm.receivedmodeid = rd.receivedmodeid -# join public."dimAddress" a on a.addressid = rd.shipaddressid -# join public."dimRequestTypes" rt on rt.requesttypeid = rd.requesttypeid -# join public."dimDeliveryModes" dm on dm.deliverymodeid = rd.deliverymodeid -# where rd.visualrequestfilenumber = 'CFD-2023-30109' and rd.activeflag = 'Y' +@cors_preflight('GET,POST,OPTIONS') +@API.route('/foihistoricalrequest/descriptionhistory/') +class FOIRawRequest(Resource): + """Retrieve historical request details from EDW""" + @staticmethod + @TRACER.trace() + @cross_origin(origins=allowedorigins()) + # @auth.require + def get(requestid): + try : + statuscode = 200 + jsondata = historicalrequestservice().gethistoricalrequestdescriptionhistory(requestid) + return jsondata , statuscode + except ValueError: + return {'status': 500, 'message':INVALID_REQUEST_ID}, 500 + except BusinessException as exception: + return {'status': exception.status_code, 'message':exception.message}, 500 + +@cors_preflight('GET,POST,OPTIONS') +@API.route('/foihistoricalrequest/extensions/') +class FOIRawRequest(Resource): + """Retrieve historical request details from EDW""" + @staticmethod + @TRACER.trace() + @cross_origin(origins=allowedorigins()) + # @auth.require + def get(requestid): + try : + statuscode = 200 + jsondata = historicalrequestservice().gethistoricalrequestextensions(requestid) return jsondata , statuscode except ValueError: return {'status': 500, 'message':INVALID_REQUEST_ID}, 500 diff --git a/historical-search-api/request_api/services/historicalrequestservice.py b/historical-search-api/request_api/services/historicalrequestservice.py index 9b8de61a0..fb3496431 100644 --- a/historical-search-api/request_api/services/historicalrequestservice.py +++ b/historical-search-api/request_api/services/historicalrequestservice.py @@ -1,6 +1,7 @@ from os import stat -from request_api.models.FOIAssignees import FOIAssignee +from request_api.models.factRequestDetails import factRequestDetails +from request_api.models.factRequestExtensions import factRequestExtensions class historicalrequestservice: """ FOI Assignee management service @@ -10,5 +11,11 @@ class historicalrequestservice: """ def gethistoricalrequest(self, requestid): - + return factRequestDetails.getrequestbyid(requestid) + + def gethistoricalrequestdescriptionhistory(self, requestid): + return factRequestDetails.getdescriptionhistorybyid(requestid) + + def gethistoricalrequestextensions(self, requestid): + return factRequestExtensions.getextensionsbyrequestid(requestid) From ad244b6b1242b1912400a3971daca16e81fdbf86 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Wed, 29 May 2024 10:26:16 -0700 Subject: [PATCH 04/33] refining WIP requirement.txt --- .gitignore | 1 + historical-search-api/requirements.txt | 6 -- historical-search-api/sample.env | 78 +------------------------- 3 files changed, 3 insertions(+), 82 deletions(-) diff --git a/.gitignore b/.gitignore index 296498fdd..7ebc811f5 100644 --- a/.gitignore +++ b/.gitignore @@ -119,3 +119,4 @@ datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.OCR/bi datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.OCR.Tests/obj/* datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.OCR.Tests/bin/* /datamigrations/FOIMOD.CFD.ConsoleApp.DocMigration/FOIMOD.CFD.DocMigration.Utils.UnitTests/appsettings.dev.json +historical-search-api/env/* diff --git a/historical-search-api/requirements.txt b/historical-search-api/requirements.txt index ba46dc574..04dd4a883 100644 --- a/historical-search-api/requirements.txt +++ b/historical-search-api/requirements.txt @@ -15,12 +15,9 @@ SQLAlchemy==1.3.24 Werkzeug==2.3.8 alembic==1.5.8 aniso8601==9.0.1 -asyncio-nats-client==0.11.4 -asyncio-nats-streaming==0.4.0 attrs==20.3.0 bcrypt==3.2.0 blinker==1.4 -boto3==1.24.35 cachelib==0.1.1 certifi==2023.7.22 cffi==1.14.5 @@ -63,9 +60,6 @@ redis==4.1.4 flask_jwt_oidc==0.3.0 maya==0.6.1 pyjwt==2.4.0 -aws-requests-auth==0.4.3 -holidays==0.12 -Flask-SocketIO==5.3.6 Flask-Login==0.5.0 eventlet==0.35.2 uritemplate.py==3.0.2 diff --git a/historical-search-api/sample.env b/historical-search-api/sample.env index eb77dd167..02361270e 100644 --- a/historical-search-api/sample.env +++ b/historical-search-api/sample.env @@ -4,21 +4,10 @@ DATABASE_NAME=postgres DATABASE_HOST=localhost DATABASE_PORT=15432 FLASK_ENV=production -FOI_REQUESTQUEUE_REDISHOST={IP Address} -FOI_REQUESTQUEUE_REDISPORT=6379 -FOI_REQUESTQUEUE_REDISPASSWORD= -FOI_REQUESTQUEUE_REDISCHANNEL=foi-rawrequest KEYCLOAK_ADMIN_HOST= KEYCLOAK_ADMIN_REALM= -KEYCLOAK_ADMIN_CLIENT_ID=foi-lob-api -KEYCLOAK_ADMIN_CLIENT_SECRET= -KEYCLOAK_ADMIN_SRVACCOUNT=foisrcaccount -KEYCLOAK_ADMIN_SRVPASSWORD= -KEYCLOAK_ADMIN_INTAKE_GROUPID= -BPM_ENGINE_REST_URL=http://{IP Address}:8000/camunda/engine-rest -BPM_TOKEN_URL=${KEYCLOAK_URL}/auth/realms/${KEYCLOAK_URL_REALM}/protocol/openid-connect/token -BPM_CLIENT_ID=forms-flow-bpm -BPM_CLIENT_SECRET= +KEYCLOAK_URL= +KEYCLOAK_URL_REALM= JWT_OIDC_WELL_KNOWN_CONFIG=${KEYCLOAK_URL}/auth/realms/${KEYCLOAK_URL_REALM}/.well-known/openid-configuration JWT_OIDC_AUDIENCE=forms-flow-web JWT_OIDC_ISSUER=${KEYCLOAK_URL}/auth/realms/${KEYCLOAK_URL_REALM} @@ -27,75 +16,12 @@ JWT_OIDC_JWKS_URI=${KEYCLOAK_URL}/auth/realms/${KEYCLOAK_URL_REALM}/protocol/ope JWT_OIDC_JWKS_CACHE_TIMEOUT=300 CORS_ORIGIN=http://localhost:8000,http://localhost:9000,http://localhost:3000 JWT_OIDC_CACHING_ENABLED=True -TEST_INTAKE_USERID=foiintake@idir -TEST_INTAKE_PASSWORD= -TEST_FLEX_USERID=foiflex@idir -TEST_FLEX_PASSWORD= -TEST_MINISTRY_USERID=foiaed@idir -TEST_MINISTRY_PASSWORD= -CACHE_TIMEOUT=3600 -CACHE_ENABLED=Y -CACHE_REDISURL=redis://{username}:{password}@{host}:password -FOI_REQUESTQUEUE_REDISURL=redis://{username}:{password}@{host}:password -OSS_S3_FORMS_BUCKET= -OSS_S3_FORMS_ACCESS_KEY_ID= -OSS_S3_FORMS_SECRET_ACCESS_KEY= -OSS_S3_HOST= -OSS_S3_REGION= -OSS_S3_SERVICE= -OSS_S3_ENVIRONMENT=dev -FOI_WEB_PAY_URL= -PAYBC_REF_NUMBER= -PAYBC_PORTAL_URL= -PAYBC_TXN_PREFIX= -PAYBC_API_KEY= -CDOGS_ACCESS_TOKEN= -# The below setting defines the criteria of days for which the notification to be fetched. -FOI_NOTIFICATION_DAYS=14 -FOI_ADDITIONAL_HOLIDAYS=30-09-XXXX -#SocketIO push notifications -SOCKETIO_PING_INTERVAL=30 -SOCKETIO_PING_TIMEOUT=5 -#Supported options : True or False -SOCKETIO_LOG_ENABLED=false -#Supported options : REDIS or IN-MEMORY or NONE (Setting as NONE will turn off push notifications). -SOCKETIO_MESSAGE_QTYPE=REDIS -#Redis QType Settings -SOCKETIO_REDISURL=redis://{username}:{password}@{host}:password -SOCKETIO_REDIS_COMMENT_CHANNEL=foi-comment -SOCKETIO_REDIS_HEALTHCHECK_INTERVAL=10 -SOCKETIO_REDIS_CONNECT_TIMEOUT=5 -SOCKETIO_REDIS_SLEEP_TIME=3.0 -SOCKETIO_CONNECT_URL=http://{IP-ADDRESS}:15000 -SOCKETIO_RECONNECTION_DELAY=15000 -SOCKETIO_RECONNECTION_DELAY_MAX=30000 -SOCKETIO_CONNECT_NONCE=stADzpPW9zV7wA7vh9nH6fWt -#AXIS base URL -AXIS_API_URL= -SOCKETIO_CONNECT_NONCE= -#EMAIL SETTINGS -EMAIL_SERVER_IMAP=imap.gmail.com -EMAIL_SERVER_SMTP=smtp.gmail.com -EMAIL_SERVER_SMTP_PORT=587 -EMAIL_SRUSERID= -EMAIL_SRPWD= -EMAIL_SENDER_ADDRESS= -EMAIL_FOLDER_OUTBOX=[Gmail]/Sent Mail -EMAIL_FOLDER_INBOX=Inbox -EVENT_QUEUE_HOST= -EVENT_QUEUE_PORT= -EVENT_QUEUE_PASSWORD= -EVENT_QUEUE_CONVERSION_STREAMKEY='file-conversion' -EVENT_QUEUE_DEDUPE_STREAMKEY='FOIDEDUPE' - -FOI_DOCREVIEWER_BASE_API_URL=http://{IP-ADDRESS}:15500 -EVENT_QUEUE_PAGECALCULATOR_STREAM_KEY= From 8a244f79a5352abe27dcd21118e0d590ad02ff89 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Wed, 29 May 2024 10:26:54 -0700 Subject: [PATCH 05/33] Updated wsgi, removed Socket IO --- historical-search-api/request_api/__init__.py | 13 +-- .../request_api/utils/util.py | 4 +- historical-search-api/wsgi.py | 95 ++++--------------- 3 files changed, 26 insertions(+), 86 deletions(-) diff --git a/historical-search-api/request_api/__init__.py b/historical-search-api/request_api/__init__.py index 44bbbc73a..532f49cad 100644 --- a/historical-search-api/request_api/__init__.py +++ b/historical-search-api/request_api/__init__.py @@ -29,21 +29,16 @@ from request_api.auth import jwt from flask_cors import CORS import re -from flask_caching import Cache -from flask_socketio import SocketIO +#from flask_caching import Cache + import secure app = Flask(__name__) #Cache Initialization -app.config.from_object('request_api.utils.cache.Config') -cache = Cache(app) +#app.config.from_object('request_api.utils.cache.Config') +#cache = Cache(app) -SOCKETIO_PING_TIMEOUT = int(os.getenv('SOCKETIO_PING_TIMEOUT', 5)) -SOCKETIO_PING_INTERVAL = int(os.getenv('SOCKETIO_PING_INTERVAL', 25)) -SOCKETIO_LOG_ENABLED = True if os.getenv('SOCKETIO_LOG_ENABLED').lower() == "true" else False -SOCKETIO_CORS_ORIGIN= os.getenv('CORS_ORIGIN').split(",") -socketio = SocketIO(logger=SOCKETIO_LOG_ENABLED, engineio_logger=SOCKETIO_LOG_ENABLED,ping_timeout=SOCKETIO_PING_TIMEOUT,ping_interval=SOCKETIO_PING_INTERVAL,cors_allowed_origins=SOCKETIO_CORS_ORIGIN) #Setup log configure_logging() diff --git a/historical-search-api/request_api/utils/util.py b/historical-search-api/request_api/utils/util.py index eadc8c382..7715b8e08 100644 --- a/historical-search-api/request_api/utils/util.py +++ b/historical-search-api/request_api/utils/util.py @@ -28,9 +28,7 @@ from request_api.auth import jwt as _authjwt,AuthHelper import jwt import os -from request_api.utils.enums import MinistryTeamWithKeycloackGroup, ProcessingTeamWithKeycloackGroup -# from request_api.services.rawrequestservice import rawrequestservice -# from request_api.models.FOIRequestWatchers import FOIRequestWatcher + def cors_preflight(methods): diff --git a/historical-search-api/wsgi.py b/historical-search-api/wsgi.py index f910d0b70..97987e6d4 100644 --- a/historical-search-api/wsgi.py +++ b/historical-search-api/wsgi.py @@ -1,76 +1,23 @@ -from threading import Thread -import eventlet -#Monkey patch to allow for async actions (aka multiple workers) -eventlet.monkey_patch() -from distutils.log import debug +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Provides the WSGI entry point for running the application +""" +from request_api import create_app + + +# Openshift s2i expects a lower case name of application +application = create_app() # pylint: disable=invalid-name - -import os -from request_api import create_app, socketio -from flask_socketio import ConnectionRefusedError -from flask_socketio import emit -from request_api.auth import AuthHelper -from flask import g, request -from request_api.auth import AuthHelper -from request_api.exceptions import BusinessException -from flask import current_app -from request_api.utils.redissubscriber import RedisSubscriberService -import logging - -@socketio.on('connect') -def connect(message): - userid = __getauthenticateduserid(message) - if userid is not None: - current_app.logger.info('socket connection established for user: ' + userid + ' | sid: ' + request.sid) - else: - disconnect() - raise ConnectionRefusedError('unauthorized!') - - -@socketio.on('disconnect') -def disconnect(): - current_app.logger.info('socket disconnected for sid: '+request.sid) - - -def __getauthenticateduserid(message): - if message.get("userid") is not None and __isvalidnonce(message) == True: - return message.get("userid") - else: - return __validatejwt(message) - - -def __isvalidnonce(message): - if message.get("rkey") is not None and message.get("rkey") == os.getenv("SOCKETIO_CONNECT_NONCE"): - return True - return False - -def __validatejwt(message): - if "x-jwt-token" in message and message.get("x-jwt-token") is not None: - try: - return AuthHelper.getwsuserid(message.get("x-jwt-token")) - except BusinessException as exception: - print("BusinessException >> ", str(exception)) - # current_app.logger.error("%s,%s" % ('Unable to get user details', exception.message)) - except Exception as ex: - print("__validatejwt Exception >>> ", str(ex)) - return None - -@socketio.on_error() -def error_handler(e): - # current_app.logger.error("%s,%s" % ('Socket error', e.message)) - print('Socket error', str(e)) - -APP = create_app() if __name__ == "__main__": - port = int(os.environ.get('PORT', 5000)) - messagequeue = os.getenv('SOCKETIO_MESSAGE_QUEUE', 'INMEMORY') - if os.getenv("SOCKETIO_MESSAGE_QTYPE") == "REDIS": - RedisSubscriberService().register_subscription() - socketio.init_app(APP, async_mode='eventlet', - path='/api/v1/socket.io') - socketio.run(APP, port=port,host='0.0.0.0', log_output=False, use_reloader=False) - - - - - + application.run() \ No newline at end of file From fb260371c9dfca5d9f219808f25422fe5c0d9462 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Wed, 29 May 2024 10:27:55 -0700 Subject: [PATCH 06/33] WIP cleaning code base --- historical-search-api/request_api/auth.py | 3 +- .../request_api/resources/request.py | 5 --- .../request_api/services/reset.py | 3 +- .../request_api/utils/redispublisher.py | 36 ------------------- .../request_api/utils/redissubscriber.py | 31 ---------------- 5 files changed, 2 insertions(+), 76 deletions(-) delete mode 100644 historical-search-api/request_api/utils/redispublisher.py delete mode 100644 historical-search-api/request_api/utils/redissubscriber.py diff --git a/historical-search-api/request_api/auth.py b/historical-search-api/request_api/auth.py index 38bd2a9f0..be62cf285 100644 --- a/historical-search-api/request_api/auth.py +++ b/historical-search-api/request_api/auth.py @@ -19,8 +19,7 @@ from flask_jwt_oidc import JwtManager # from jose import jwt as josejwt from jose import JWTError, jwt as josejwt -from request_api.utils.enums import MinistryTeamWithKeycloackGroup, ProcessingTeamWithKeycloackGroup, IAOTeamWithKeycloackGroup -from request_api.models.FOIMinistryRequests import FOIMinistryRequest + jwt = ( JwtManager() ) # pylint: disable=invalid-name; lower case name as used by convention in most Flask apps diff --git a/historical-search-api/request_api/resources/request.py b/historical-search-api/request_api/resources/request.py index 01745b9db..0b92f7182 100644 --- a/historical-search-api/request_api/resources/request.py +++ b/historical-search-api/request_api/resources/request.py @@ -22,12 +22,7 @@ from request_api.tracer import Tracer from request_api.utils.util import cors_preflight, allowedorigins,str_to_bool,canrestictdata from request_api.exceptions import BusinessException -# from request_api.services.rawrequestservice import rawrequestservice -# from request_api.services.documentservice import documentservice -# from request_api.services.eventservice import eventservice -# from request_api.services.unopenedreportservice import unopenedreportservice from request_api.services.historicalrequestservice import historicalrequestservice -# from request_api.utils.enums import StateName import json import asyncio from jose import jwt as josejwt diff --git a/historical-search-api/request_api/services/reset.py b/historical-search-api/request_api/services/reset.py index f43804e09..c7b7c05e8 100644 --- a/historical-search-api/request_api/services/reset.py +++ b/historical-search-api/request_api/services/reset.py @@ -14,10 +14,9 @@ """Service for reset test data.""" from typing import Dict -from request_api.models import User as UserModel + from request_api.models import db from request_api.services.keycloak import KeycloakService -from request_api.utils.enums import LoginSource from request_api.utils.roles import Role diff --git a/historical-search-api/request_api/utils/redispublisher.py b/historical-search-api/request_api/utils/redispublisher.py deleted file mode 100644 index 0cb8a1ff8..000000000 --- a/historical-search-api/request_api/utils/redispublisher.py +++ /dev/null @@ -1,36 +0,0 @@ - -import os -from request_api.exceptions import BusinessException, Error - -from flask import current_app -from redis import Redis -from request_api.utils.redissubscriber import RedisSubscriberService -import logging -import redis -class RedisPublisherService: - - foirequestqueueredischannel = os.getenv('FOI_REQUESTQUEUE_REDISCHANNEL') - foicommentqueueredischannel = os.getenv('SOCKETIO_REDIS_COMMENT_CHANNEL') - - foimsgredis = redis.from_url(os.getenv('SOCKETIO_REDISURL'), socket_connect_timeout=int(os.getenv('SOCKETIO_REDIS_CONNECT_TIMEOUT')), retry_on_timeout=True, socket_keepalive=True) - - async def publishrequest(self, message): - self.publishtoredischannel(self.foirequestqueueredischannel, message) - - def publishcommment(self, message): - try: - logging.info(message) - self.publishtoredischannel(self.foicommentqueueredischannel, message) - except Exception as ex: - current_app.logger.error("%s,%s" % ('Unable to get user details', ex)) - raise ex - - def publishtoredischannel(self, channel , message): - try: - if channel == os.getenv('FOI_REQUESTQUEUE_REDISCHANNEL'): - self.foimsgredis.publish(channel, message) - if channel == os.getenv('SOCKETIO_REDIS_COMMENT_CHANNEL'): - RedisSubscriberService().foicommentredis.publish(channel, message) - except BusinessException as exception: - current_app.logger.error("%s,%s" % ('FOI request Queue REDIS Error', exception.message)) - diff --git a/historical-search-api/request_api/utils/redissubscriber.py b/historical-search-api/request_api/utils/redissubscriber.py deleted file mode 100644 index 174d9898a..000000000 --- a/historical-search-api/request_api/utils/redissubscriber.py +++ /dev/null @@ -1,31 +0,0 @@ - -import os -from request_api.exceptions import BusinessException - -from flask import current_app -import time -from request_api import socketio -import json -import redis -import logging -class RedisSubscriberService: - - foicommentredis = redis.from_url(os.getenv('SOCKETIO_REDISURL'), health_check_interval=int(os.getenv('SOCKETIO_REDIS_HEALTHCHECK_INTERVAL')), socket_connect_timeout=int(os.getenv('SOCKETIO_REDIS_CONNECT_TIMEOUT')), retry_on_timeout=True, socket_keepalive=True) - subscription = foicommentredis.pubsub(ignore_subscribe_messages=True) - - @classmethod - def register_subscription(cls): - try: - logging.warning("subscription to channel") - cls.subscription.subscribe(**{os.getenv('SOCKETIO_REDIS_COMMENT_CHANNEL'): event_handler}) - cls.subscription.run_in_thread(sleep_time=float(os.getenv('SOCKETIO_REDIS_SLEEP_TIME')), daemon=True) - except BusinessException as exception: - logging.error("%s,%s" % ('FOI request Queue REDIS Error', exception.message)) - - -def event_handler(msg): - if msg and msg.get('type') == 'message': - data = msg.get('data') - _pushnotification = json.loads(data) - socketio.emit(_pushnotification["userid"], _pushnotification) - From f24ccb4b6113dcf87ec8495a91d100d377477b69 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 29 May 2024 10:34:32 -0700 Subject: [PATCH 07/33] add missing file --- .../models/factRequestExtensions.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 historical-search-api/request_api/models/factRequestExtensions.py diff --git a/historical-search-api/request_api/models/factRequestExtensions.py b/historical-search-api/request_api/models/factRequestExtensions.py new file mode 100644 index 000000000..a68f3fe4f --- /dev/null +++ b/historical-search-api/request_api/models/factRequestExtensions.py @@ -0,0 +1,51 @@ +from .db import db, ma +from .default_method_result import DefaultMethodResult +from sqlalchemy import text +import logging +from dateutil.parser import parse + + +class factRequestExtensions(db.Model): + __tablename__ = 'factRequestExtensions' + # Defining the columns + foirequestid = db.Column(db.Integer, primary_key=True,autoincrement=True) + + @classmethod + def getextensionsbyrequestid(cls, requestid): + extensions = [] + try: + sql = """SELECT + et.extensiontypename, + re.extensiondays, + re.extendeddate, + re.approveddate, + approvedstatus, + re.createddate + FROM public."factRequestExtensions" re + join public."dimExtensionTypes" et on et.extensiontypeid = re.extensiontypeid + where foirequestid = (select foirequestid from public."ClosedRequestDetailsPost2018" where visualrequestfilenumber = :requestid) + and re.activeflag = 'Y' + and re.runcycleid in ( + select max(runcycleid) from public."factRequestExtensions" + where foirequestid = (select foirequestid from public."ClosedRequestDetailsPost2018" where visualrequestfilenumber = :requestid) + group by requestextid + ) + ORDER BY foirequestid DESC, runcycleid DESC, requestextid DESC""" + rs = db.session.execute(text(sql), {'requestid': requestid}) + for row in rs: + extension = {} + extension["approvednoofdays"] = row['extensiondays'] + extension["extendedduedays"] = row['extensiondays'] + extension["created_at"] = row['createddate'].strftime('%Y-%m-%d') + extension["extendedduedate"] = row['extendeddate'].strftime('%Y-%m-%d') + extension["extensionreson"] = row['extensiontypename'] + extension["extensionstatus"] = row['approvedstatus'] + extensions.append(extension) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return extensions + + \ No newline at end of file From b57463b21b505d0d21f3485461edc63c50cb6859 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Wed, 29 May 2024 11:22:34 -0700 Subject: [PATCH 08/33] Refining requirements.txt --- historical-search-api/requirements.txt | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/historical-search-api/requirements.txt b/historical-search-api/requirements.txt index 04dd4a883..d64f5b956 100644 --- a/historical-search-api/requirements.txt +++ b/historical-search-api/requirements.txt @@ -1,13 +1,9 @@ -Flask-Caching==1.10.1 -Flask-Mail==0.9.1 Flask-Migrate==2.7.0 Flask-Moment==0.11.0 Flask-SQLAlchemy==2.5.1 Flask-Script==2.0.5 Flask==2.2.5 Flask-OpenTracing==1.1.0 -Jinja2==3.0.3 -Mako==1.2.2 MarkupSafe==2.1.4 SQLAlchemy-Continuum==1.3.11 SQLAlchemy-Utils==0.36.8 @@ -36,10 +32,10 @@ jaro-winkler==2.0.3 jsonschema==3.2.0 marshmallow-sqlalchemy==0.23.1 marshmallow==3.0.0rc7 -minio==7.0.2 opentracing==2.4.0 protobuf==3.18.3 psycopg2-binary==2.8.6 + pycparser==2.20 pyhumps==1.6.1 pyrsistent==0.17.3 @@ -50,26 +46,12 @@ python-jose==3.2.0 pytz==2021.1 requests==2.31.0 rsa==4.7.2 -sentry-sdk==1.14.0 six==1.15.0 -threadloop==1.0.2 thrift==0.13.0 tornado==6.1 -flask-expects-json==1.5.0 -redis==4.1.4 -flask_jwt_oidc==0.3.0 -maya==0.6.1 pyjwt==2.4.0 -Flask-Login==0.5.0 -eventlet==0.35.2 -uritemplate.py==3.0.2 -urllib3==1.26.15 -ndg-httpsclient -pyopenssl -pyasn1 secure==0.3.0 -boto3==1.24.35 walrus==0.8.2 -imap-tools==0.56.0 + more-itertools==10.2.0 -e git+https://github.com/bcgov/sbc-common-components.git#egg=sbc-common-components&subdirectory=python \ No newline at end of file From c0314cc74126ed1eb88e1e61eb7000c007e73bc7 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Wed, 29 May 2024 11:22:57 -0700 Subject: [PATCH 09/33] Code clean up - wip --- historical-search-api/request_api/config.py | 23 ++++--------------- .../request_api/resources/__init__.py | 7 ++---- .../request_api/resources/request.py | 18 ++++----------- 3 files changed, 12 insertions(+), 36 deletions(-) diff --git a/historical-search-api/request_api/config.py b/historical-search-api/request_api/config.py index c8555df53..c66040f96 100644 --- a/historical-search-api/request_api/config.py +++ b/historical-search-api/request_api/config.py @@ -121,29 +121,16 @@ class _Config(): # pylint: disable=too-few-public-methods JWT_OIDC_JWKS_CACHE_TIMEOUT = 300 - # email - MAIL_FROM_ID = os.getenv('MAIL_FROM_ID') + # Fees LEGISLATIVE_TIMEZONE = 'America/Vancouver' - FOI_WEB_PAY_URL = os.getenv('FOI_WEB_PAY_URL') - FOI_FFA_URL = os.getenv('FOI_FFA_URL') - PAYBC_REF_NUMBER = os.getenv('PAYBC_REF_NUMBER') - PAYBC_PORTAL_URL = os.getenv('PAYBC_PORTAL_URL') - PAYBC_TXN_PREFIX = os.getenv('PAYBC_TXN_PREFIX', 'FOI') - PAYBC_API_KEY = os.getenv('PAYBC_API_KEY') - - PAYBC_API_BASE_URL = os.getenv('PAYBC_API_BASE_URL') - PAYBC_API_CLIENT = os.getenv('PAYBC_API_CLIENT') - PAYBC_API_SECRET = os.getenv('PAYBC_API_SECRET') + + + CONNECT_TIMEOUT = os.getenv('CONNECT_TIMEOUT', 60) - # CDOGS - CDOGS_ACCESS_TOKEN = os.getenv('CDOGS_ACCESS_TOKEN') - CDOGS_BASE_URL = os.getenv('CDOGS_BASE_URL') - CDOGS_SERVICE_CLIENT = os.getenv('CDOGS_SERVICE_CLIENT') - CDOGS_SERVICE_CLIENT_SECRET = os.getenv('CDOGS_SERVICE_CLIENT_SECRET') - CDOGS_TOKEN_URL = os.getenv('CDOGS_TOKEN_URL') + diff --git a/historical-search-api/request_api/resources/__init__.py b/historical-search-api/request_api/resources/__init__.py index f262ef261..f5a45479f 100644 --- a/historical-search-api/request_api/resources/__init__.py +++ b/historical-search-api/request_api/resources/__init__.py @@ -31,18 +31,15 @@ __all__ = ('API_BLUEPRINT') -# This will add the Authorize button to the swagger docs -#AUTHORIZATIONS = {'apikey': {'type': 'apiKey', 'in': 'header', 'name': 'Authorization'}} - API_BLUEPRINT = Blueprint('API', __name__ ) API = Api( API_BLUEPRINT, - title='FOI Request API', + title='FOI Historical Search API', version='1.0', - description='The Core API for the FOI Request System', + description='The Historical Search API for the FOI Request System', ) diff --git a/historical-search-api/request_api/resources/request.py b/historical-search-api/request_api/resources/request.py index 0b92f7182..ea50c2e82 100644 --- a/historical-search-api/request_api/resources/request.py +++ b/historical-search-api/request_api/resources/request.py @@ -16,7 +16,6 @@ from flask import g, request from flask_restx import Namespace, Resource -from flask_expects_json import expects_json from flask_cors import cross_origin from request_api.auth import auth, AuthHelper from request_api.tracer import Tracer @@ -24,19 +23,12 @@ from request_api.exceptions import BusinessException from request_api.services.historicalrequestservice import historicalrequestservice import json -import asyncio -from jose import jwt as josejwt -import holidays -from datetime import datetime, timedelta -import os -import pytz -API = Namespace('FOIRawRequests', description='Endpoints for FOI request management') +API = Namespace('FOIHistoricalSearch', description='Endpoints for FOI Historical search') TRACER = Tracer.get_instance() -with open('request_api/schemas/schemas/rawrequest.json') as f: - schema = json.load(f) + INVALID_REQUEST_ID = 'Invalid Request Id' @@ -45,7 +37,7 @@ @cors_preflight('GET,POST,OPTIONS') @API.route('/foihistoricalrequest/') -class FOIRawRequest(Resource): +class FOIHistoricalSearch(Resource): """Retrieve historical request details from EDW""" @staticmethod @@ -65,7 +57,7 @@ def get(requestid): @cors_preflight('GET,POST,OPTIONS') @API.route('/foihistoricalrequest/descriptionhistory/') -class FOIRawRequest(Resource): +class FOIHistoricalSearch(Resource): """Retrieve historical request details from EDW""" @staticmethod @@ -84,7 +76,7 @@ def get(requestid): @cors_preflight('GET,POST,OPTIONS') @API.route('/foihistoricalrequest/extensions/') -class FOIRawRequest(Resource): +class FOIHistoricalSearch(Resource): """Retrieve historical request details from EDW""" @staticmethod From ad43b99f85abe14023aa946299742d012bff66d8 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Wed, 29 May 2024 11:46:19 -0700 Subject: [PATCH 10/33] code refined - WIP --- .../utils/commons/datetimehandler.py | 2 -- historical-search-api/requirements.txt | 25 ++----------------- 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/historical-search-api/request_api/utils/commons/datetimehandler.py b/historical-search-api/request_api/utils/commons/datetimehandler.py index a465b5eef..44ae0bd4e 100644 --- a/historical-search-api/request_api/utils/commons/datetimehandler.py +++ b/historical-search-api/request_api/utils/commons/datetimehandler.py @@ -4,11 +4,9 @@ import json from datetime import datetime as datetime2 from datetime import datetime, timedelta -import holidays import maya import os from dateutil.parser import parse -from pytz import timezone class datetimehandler: """ Supports common date operations diff --git a/historical-search-api/requirements.txt b/historical-search-api/requirements.txt index d64f5b956..94b4f26b7 100644 --- a/historical-search-api/requirements.txt +++ b/historical-search-api/requirements.txt @@ -10,48 +10,27 @@ SQLAlchemy-Utils==0.36.8 SQLAlchemy==1.3.24 Werkzeug==2.3.8 alembic==1.5.8 -aniso8601==9.0.1 -attrs==20.3.0 -bcrypt==3.2.0 -blinker==1.4 -cachelib==0.1.1 -certifi==2023.7.22 -cffi==1.14.5 -chardet==4.0.0 -click==8.1.3 flask-jwt-oidc==0.3.0 flask-marshmallow==0.11.0 -flask-restplus flask_restx flask-cors==3.0.10 gunicorn==20.1.0 -idna==2.10 itsdangerous==2.0.1 jaeger-client==4.4.0 -jaro-winkler==2.0.3 jsonschema==3.2.0 marshmallow-sqlalchemy==0.23.1 marshmallow==3.0.0rc7 opentracing==2.4.0 -protobuf==3.18.3 psycopg2-binary==2.8.6 - -pycparser==2.20 -pyhumps==1.6.1 +psycopg2==2.9.9 pyrsistent==0.17.3 -python-dateutil==2.8.1 python-dotenv==0.16.0 -python-editor==1.0.4 -python-jose==3.2.0 -pytz==2021.1 requests==2.31.0 rsa==4.7.2 six==1.15.0 thrift==0.13.0 -tornado==6.1 pyjwt==2.4.0 secure==0.3.0 -walrus==0.8.2 +pyhumps==1.6.1 -more-itertools==10.2.0 -e git+https://github.com/bcgov/sbc-common-components.git#egg=sbc-common-components&subdirectory=python \ No newline at end of file From 4238e1121ae8e0eccbd0c62a4884ed8e53f102ba Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Wed, 29 May 2024 13:10:25 -0700 Subject: [PATCH 11/33] docker-compose update --- docker-compose.yml | 113 +++++---------------------------------------- sample.env | 7 +++ 2 files changed, 18 insertions(+), 102 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 1924e3eea..d6bd7bc92 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -132,9 +132,7 @@ services: context: ./historical-search-api dockerfile: dockerfile.local image: historicalsearchapi - container_name: historical_search_api - depends_on: - - foi-requests-DB + container_name: historical_search_api networks: services-network: aliases: @@ -142,113 +140,24 @@ services: ports: - 15001:5000 environment: - - DATABASE_USERNAME=${FOI_DATABASE_USERNAME} - - DATABASE_PASSWORD=${FOI_DATABASE_PASSWORD} - - DATABASE_NAME=${FOI_DATABASE_NAME} - - DATABASE_HOST=${FOI_DATABASE_HOST} - - DATABASE_PORT=${FOI_DATABASE_PORT} - - FLASK_ENV:production - - FOI_REQUESTQUEUE_REDISHOST=${WEBSOCKET_BROKER_HOST} - - FOI_REQUESTQUEUE_REDISPORT=${WEBSOCKET_BROKER_PORT} - - FOI_REQUESTQUEUE_REDISPASSWORD=${WEBSOCKET_BROKER_PASSCODE} - - FOI_REQUESTQUEUE_REDISCHANNEL=${WEBSOCKET_FOI_RAWREQUEST_TOPIC} - - KEYCLOAK_ADMIN_HOST=${KEYCLOAK_URL} - - KEYCLOAK_ADMIN_REALM=${KEYCLOAK_URL_REALM} - - KEYCLOAK_ADMIN_CLIENT_ID=foi-lob-api - - KEYCLOAK_ADMIN_CLIENT_SECRET=${KEYCLOAK_ADMIN_CLIENT_SECRET} - - KEYCLOAK_ADMIN_SRVACCOUNT=foisrcaccount - - KEYCLOAK_ADMIN_SRVPASSWORD=${KEYCLOAK_ADMIN_SRVPASSWORD} - - KEYCLOAK_ADMIN_INTAKE_GROUPID=${KEYCLOAK_ADMIN_INTAKE_GROUPID} - - BPM_ENGINE_REST_URL=${FORMSFLOW_WF_URL} - - BPM_TOKEN_URL=${KEYCLOAK_URL}/auth/realms/${KEYCLOAK_URL_REALM}/protocol/openid-connect/token - - BPM_CLIENT_ID=forms-flow-bpm - - BPM_CLIENT_SECRET=${KEYCLOAK_BPM_CLIENT_SECRET} + - DATABASE_USERNAME=${FOI_EDW_DATABASE_USERNAME} + - DATABASE_PASSWORD=${FOI_EDW_DATABASE_PASSWORD} + - DATABASE_NAME=${FOI_EDW_DATABASE_NAME} + - DATABASE_HOST=${FOI_EDW_DATABASE_HOST} + - DATABASE_PORT=${FOI_EDW_DATABASE_PORT} + - FLASK_ENV:production - JWT_OIDC_AUDIENCE=${JWT_OIDC_AUDIENCE} - CORS_ORIGIN=${CORS_ORIGIN} + - KEYCLOAK_URL=${KEYCLOAK_URL} + - KEYCLOAK_URL_REALM=${KEYCLOAK_URL_REALM} - JWT_OIDC_WELL_KNOWN_CONFIG=${KEYCLOAK_URL}/auth/realms/${KEYCLOAK_URL_REALM}/.well-known/openid-configuration - JWT_OIDC_ALGORITHMS=RS256 - JWT_OIDC_JWKS_URI=${KEYCLOAK_URL}/auth/realms/${KEYCLOAK_URL_REALM}/protocol/openid-connect/certs - JWT_OIDC_ISSUER=${KEYCLOAK_URL}/auth/realms/${KEYCLOAK_URL_REALM} - JWT_OIDC_CACHING_ENABLED=True - JWT_OIDC_JWKS_CACHE_TIMEOUT=300 - - CACHE_TIMEOUT=${CACHE_TIMEOUT} - - KC_SRC_ACC_TOKEN_EXPIRY=${KC_SRC_ACC_TOKEN_EXPIRY} - - FOI_REQUESTQUEUE_REDISURL=${FOI_REQUESTQUEUE_REDISURL} - - CACHE_ENABLED=${CACHE_ENABLED} - - CACHE_TYPE=${CACHE_TYPE} - - OSS_S3_FORMS_BUCKET=${OSS_S3_FORMS_BUCKET} - - OSS_S3_FORMS_ACCESS_KEY_ID=${OSS_S3_FORMS_ACCESS_KEY_ID} - - OSS_S3_FORMS_SECRET_ACCESS_KEY=${OSS_S3_FORMS_SECRET_ACCESS_KEY} - - OSS_S3_HOST=${OSS_S3_HOST} - - OSS_S3_REGION=${OSS_S3_REGION} - - OSS_S3_SERVICE=${OSS_S3_SERVICE} - - OSS_S3_ENVIRONMENT=${OSS_S3_ENVIRONMENT} - - OSS_S3_RECORDS_ACCESS_KEY_ID=${OSS_S3_RECORDS_ACCESS_KEY_ID} - - OSS_S3_RECORDS_SECRET_ACCESS_KEY=${OSS_S3_RECORDS_SECRET_ACCESS_KEY} - - OSS_S3_CHUNK_SIZE=${OSS_S3_CHUNK_SIZE} - - TOTAL_RECORDS_UPLOAD_LIMIT=${TOTAL_RECORDS_UPLOAD_LIMIT} - - EVENT_QUEUE_HOST=${EVENT_QUEUE_HOST} - - EVENT_QUEUE_PORT=${EVENT_QUEUE_PORT} - - EVENT_QUEUE_PASSWORD=${EVENT_QUEUE_PASSWORD} - - FOI_NOTIFICATION_DAYS=${FOI_NOTIFICATION_DAYS} - - FOI_ADDITIONAL_HOLIDAYS=${FOI_ADDITIONAL_HOLIDAYS} - - SOCKETIO_PING_INTERVAL=${SOCKETIO_PING_INTERVAL} - - SOCKETIO_PING_TIMEOUT=${SOCKETIO_PING_TIMEOUT} - - SOCKETIO_LOG_ENABLED=${SOCKETIO_LOG_ENABLED} - - SOCKETIO_MESSAGE_QTYPE=${SOCKETIO_MESSAGE_QTYPE} - - SOCKETIO_REDISURL=${SOCKETIO_REDISURL} - - SOCKETIO_REDIS_COMMENT_CHANNEL=${SOCKETIO_REDIS_COMMENT_CHANNEL} - - SOCKETIO_REDIS_HEALTHCHECK_INTERVAL=${SOCKETIO_REDIS_HEALTHCHECK_INTERVAL} - - SOCKETIO_REDIS_CONNECT_TIMEOUT=${SOCKETIO_REDIS_CONNECT_TIMEOUT} - - SOCKETIO_REDIS_SLEEP_TIME=${SOCKETIO_REDIS_SLEEP_TIME} - - SOCKETIO_CONNECT_NONCE=${SOCKETIO_CONNECT_NONCE} - - CACHE_REDISURL=${CACHE_REDISURL} - - PAYBC_REF_NUMBER=${PAYBC_REF_NUMBER} - - PAYBC_PORTAL_URL=${PAYBC_PORTAL_URL} - - PAYBC_TXN_PREFIX=${PAYBC_TXN_PREFIX} - - PAYBC_API_KEY=${PAYBC_API_KEY} - - PAYBC_API_BASE_URL=${PAYBC_API_BASE_URL} - - PAYBC_API_CLIENT=${PAYBC_API_CLIENT} - - PAYBC_API_SECRET=${PAYBC_API_SECRET} - - FOI_FFA_URL=${FOI_FFA_URL} - - EMAIL_SERVER_IMAP=${EMAIL_SERVER_IMAP} - - EMAIL_SERVER_SMTP=${EMAIL_SERVER_SMTP} - - EMAIL_SERVER_SMTP_PORT=${EMAIL_SERVER_SMTP_PORT} - - EMAIL_SRUSERID=${EMAIL_SRUSERID} - - EMAIL_SRPWD=${EMAIL_SRPWD} - - EMAIL_SENDER_ADDRESS=${EMAIL_SENDER_ADDRESS} - - EMAIL_FOLDER_OUTBOX=${EMAIL_FOLDER_OUTBOX} - - EMAIL_FOLDER_INBOX=${EMAIL_FOLDER_INBOX} - - EVENT_QUEUE_CONVERSION_STREAMKEY=${EVENT_QUEUE_CONVERSION_STREAMKEY} - - EVENT_QUEUE_CONVERSION_LARGE_FILE_STREAM_KEY=${EVENT_QUEUE_CONVERSION_LARGE_FILE_STREAM_KEY} - - EVENT_QUEUE_DEDUPE_STREAMKEY=${EVENT_QUEUE_DEDUPE_STREAMKEY} - - EVENT_QUEUE_DEDUPE_LARGE_FILE_STREAMKEY=${EVENT_QUEUE_DEDUPE_LARGE_FILE_STREAMKEY} - - FOI_DOCREVIEWER_BASE_API_URL=${FOI_DOCREVIEWER_BASE_API_URL} - - FOI_DOCREVIEWER_BASE_API_TIMEOUT=${FOI_DOCREVIEWER_BASE_API_TIMEOUT} - - PAYMENT_CONFIG=${PAYMENT_CONFIG} - - FOI_REQ_MANAGEMENT_API_URL=${FOI_REQ_MANAGEMENT_API_URL} - - FOI_RECORD_FORMATS=${FOI_RECORD_FORMATS} - - EVENT_QUEUE_PDFSTITCH_STREAMKEY=${EVENT_QUEUE_PDFSTITCH_STREAMKEY} - - EVENT_QUEUE_PDFSTITCH_LARGE_FILE_STREAMKEY=${EVENT_QUEUE_PDFSTITCH_LARGE_FILE_STREAMKEY} - - STREAM_SEPARATION_FILE_SIZE_LIMIT=${STREAM_SEPARATION_FILE_SIZE_LIMIT} - - MUTE_NOTIFICATION=${MUTE_NOTIFICATION} - - EVENT_QUEUE_PAGECALCULATOR_STREAM_KEY=${EVENT_QUEUE_PAGECALCULATOR_STREAM_KEY} - - UNOPENED_REPORT_CUTOFF_DAYS=${UNOPENED_REPORT_CUTOFF_DAYS} - - UNOPENED_REPORT_WAIT_DAYS=${UNOPENED_REPORT_WAIT_DAYS} - - UNOPENED_REPORT_JARO_CUTOFF=${UNOPENED_REPORT_JARO_CUTOFF} - - UNOPENED_REPORT_EMAIL_RECIPIENT=${UNOPENED_REPORT_EMAIL_RECIPIENT} - - AXIS_API_URL=${AXIS_API_URL} - - AXIS_SYNC_BATCHSIZE=${AXIS_SYNC_BATCHSIZE} - #- LOG_ROOT=${LOG_ROOT} - #- LOG_BASIC=${LOG_BASIC} - #- LOG_TRACING=${LOG_TRACING} - #To tune connection settings (Optional) - #- SQLALCHEMY_POOL_SIZE=${SQLALCHEMY_POOL_SIZE} - #- SQLALCHEMY_MAX_OVERFLOW=${SQLALCHEMY_MAX_OVERFLOW} - #- SQLALCHEMY_POOL_TIMEOUT=${SQLALCHEMY_POOL_TIMEOUT} - #- SQLALCHEMY_CONNECT_TIMEOUT=${SQLALCHEMY_CONNECT_TIMEOUT} - #- SQLALCHEMY_POOL_PRE_PING=${SQLALCHEMY_POOL_PRE_PING} - #- SQLALCHEMY_ECHO=${SQLALCHEMY_ECHO} + - CORS_ORIGIN=http://localhost:8000,http://localhost:9000,http://localhost:3000,http://foiflow.local:3000 + foi-requests-DB: image: postgres diff --git a/sample.env b/sample.env index 5b7017f55..3234311d0 100644 --- a/sample.env +++ b/sample.env @@ -23,6 +23,13 @@ FOI_DATABASE_NAME=postgres FOI_DATABASE_HOST=foi-requests-DB FOI_DATABASE_PORT=5432 +##EDW Database Settings - Historical search (Mandatory) +FOI_EDW_DATABASE_USERNAME=postgres +FOI_EDW_DATABASE_PASSWORD= +FOI_EDW_DATABASE_NAME=foi_edw +FOI_EDW_DATABASE_HOST= +FOI_EDW_DATABASE_PORT= + ##Database Settings - Worflow Engine (Mandatory) CAMUNDA_JDBC_USER=admin CAMUNDA_JDBC_PASSWORD= From e0cf0762fbd924a544320e4132f1535400d9d20c Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Wed, 29 May 2024 16:25:01 -0700 Subject: [PATCH 12/33] #FOIMOD-3184 Advanced Search Model Function --- .../request_api/models/factRequestDetails.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/historical-search-api/request_api/models/factRequestDetails.py b/historical-search-api/request_api/models/factRequestDetails.py index be3350b5c..b5cc6ca0d 100644 --- a/historical-search-api/request_api/models/factRequestDetails.py +++ b/historical-search-api/request_api/models/factRequestDetails.py @@ -122,3 +122,67 @@ def getdescriptionhistorybyid(cls, requestid): db.session.close() return history + @classmethod + def getadvancedsearchresults(cls, params): + searchresults = [] + try: + basequery = 'SELECT foirequestid \ + ,requesttypename \ + ,applicantname \ + ,visualrequestfilenumber \ + ,oipcno \ + ,subject \ + ,primaryusername as assignee \ + ,receiveddate \ + ,description \ + ,requeststatus \ + ,closeddate \ + ,ministry \ + ,officeid \ + FROM \ + public."ClosedRequestDetailsPost2018" WHERE ' + + filterbysearchcondition =[] + + if(params['search'] == 'requestdescription'): + for keyword in params['keywords']: + filterbysearchcondition.append("LOWER(description) like LOWER('%{0}%')".format(keyword)) + elif(params['search'] == 'applicantname'): + for keyword in params['keywords']: + filterbysearchcondition.append("LOWER(applicantname) like LOWER('%{0}%')".format(keyword)) + elif(params['search'] == 'assigneename'): + for keyword in params['keywords']: + filterbysearchcondition.append("LOWER(primaryusername) like LOWER('%{0}%')".format(keyword)) + elif(params['search'] == 'idnumber' or params['search'] == 'axisrequest_number'): + for keyword in params['keywords']: + filterbysearchcondition.append("LOWER(visualrequestfilenumber) like LOWER('%{0}%')".format(keyword)) + + conditioncount = len(filterbysearchcondition) + + for idx,searchcondition in enumerate(filterbysearchcondition): + basequery+= ' {0} '.format(searchcondition) + + if(idx!=(conditioncount-1)): + basequery+= ' AND ' + + if(conditioncount == 0): + basequery+= "LOWER(description) like LOWER('%{0}%')".format(keyword) + + basequery+= ' ORDER BY {0} {1}'.format(params['sortingitem'],params['sortingorder']) + + if params['size'] is not None: + basequery+= ' LIMIT {0}'.format(params['size']) + else: + basequery+= ' LIMIT 100' + + rs = db.session.execute(text(basequery)) + + for row in rs: + searchresults.append({"axisrequestid": row["visualrequestfilenumber"], "description": row["description"], "assignee": row["assignee"], "requeststatus": row["requeststatus"], "applicantname": row["applicantname"], "requesttypename": row["requesttypename"],"receiveddate": row["receiveddate"],"oipcno": row["oipcno"]}) + except Exception as ex: + logging.error(ex) + raise ex + finally: + db.session.close() + return searchresults + \ No newline at end of file From cf9139c88fae0f90035f1a4dd7a937bf967a8a25 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Wed, 29 May 2024 16:29:46 -0700 Subject: [PATCH 13/33] #FOIMOD-3184 API endpoint, services --- .../request_api/resources/__init__.py | 5 +- .../request_api/resources/historicalsearch.py | 71 +++++++++++++++++++ .../services/historicalrequestservice.py | 3 + 3 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 historical-search-api/request_api/resources/historicalsearch.py diff --git a/historical-search-api/request_api/resources/__init__.py b/historical-search-api/request_api/resources/__init__.py index f5a45479f..c832e7d8d 100644 --- a/historical-search-api/request_api/resources/__init__.py +++ b/historical-search-api/request_api/resources/__init__.py @@ -28,7 +28,7 @@ from .meta import API as META_API from .ops import API as OPS_API from .request import API as REQUEST_API - +from .historicalsearch import API as HISTORICAL_SEARCH_API __all__ = ('API_BLUEPRINT') @@ -44,4 +44,5 @@ -API.add_namespace(REQUEST_API ,path="/api") \ No newline at end of file +API.add_namespace(REQUEST_API ,path="/api") +API.add_namespace(HISTORICAL_SEARCH_API, path="/api") \ No newline at end of file diff --git a/historical-search-api/request_api/resources/historicalsearch.py b/historical-search-api/request_api/resources/historicalsearch.py new file mode 100644 index 000000000..44f4907f7 --- /dev/null +++ b/historical-search-api/request_api/resources/historicalsearch.py @@ -0,0 +1,71 @@ +from flask import g, request, jsonify +import flask +from flask_restx import Namespace, Resource +from flask_cors import cross_origin + +from request_api.tracer import Tracer +from request_api.utils.util import cors_preflight, allowedorigins, getrequiredmemberships +from request_api.auth import AuthHelper, auth +from request_api.tracer import Tracer +from request_api.exceptions import BusinessException +from request_api.services.historicalrequestservice import historicalrequestservice +import json + +API = Namespace('FOIHistoricalSearch', description='Endpoints for FOI Historical search') +TRACER = Tracer.get_instance() + +@cors_preflight('GET,OPTIONS') +@API.route('/advancedsearch') +class AdvancedHistoricalSearch(Resource): + """ Retrives the foi request based on the queue type. + """ + @staticmethod + @TRACER.trace() + @cross_origin(origins=allowedorigins()) + #@auth.require + @cors_preflight('GET,POST,OPTIONS') + #@auth.ismemberofgroups(getrequiredmemberships()) + def get(): + try: + + DEFAULT_SIZE = 10 + DEFAULT_SORT_ITEM = 'receiveddate' + DEFAULT_SORT_ORDER = 'desc' + + params = { + 'usertype': 'iao', #todo: need to update + 'groups': '', + 'size': flask.request.args.get('size', DEFAULT_SIZE, type=int), + 'sortingitem': flask.request.args.get('sortingitem'), + 'sortingorder': flask.request.args.get('sortingorder'), + + 'requesttype': flask.request.args.getlist('requestType[]'), + 'requestflags': flask.request.args.getlist('requestFlags[]'), + 'publicbody': flask.request.args.getlist('publicBodies[]'), + + 'daterangetype': flask.request.args.get('dateRangeType', None, type=str), + 'fromdate': flask.request.args.get('fromDate', None, type=str), + 'todate': flask.request.args.get('toDate', None, type=str), + + 'search': flask.request.args.get('search', None, type=str), + 'keywords': flask.request.args.getlist('keywords[]'), + + 'userid': flask.request.args.get('userid', None, type=str) + } + + if params['sortingitem'] is None: + params['sortingitem'] = DEFAULT_SORT_ITEM + if params['sortingorder'] is None: + params['sortingorder'] = DEFAULT_SORT_ORDER + if params['keywords'] is None: + params['keywords'] = [] + + statuscode = 200 + if (params['usertype'] == "iao" or params['usertype'] == "ministry"): + requests = historicalrequestservice().advancedsearch(params) + else: + statuscode = 401 + + return requests, statuscode + except BusinessException as exception: + return {'status': exception.status_code, 'message':exception.message}, 500 \ No newline at end of file diff --git a/historical-search-api/request_api/services/historicalrequestservice.py b/historical-search-api/request_api/services/historicalrequestservice.py index fb3496431..5b8648a55 100644 --- a/historical-search-api/request_api/services/historicalrequestservice.py +++ b/historical-search-api/request_api/services/historicalrequestservice.py @@ -18,4 +18,7 @@ def gethistoricalrequestdescriptionhistory(self, requestid): def gethistoricalrequestextensions(self, requestid): return factRequestExtensions.getextensionsbyrequestid(requestid) + + def advancedsearch(self, params={'usertype': 'iao', 'groups':None, 'page':1, 'size':10, 'sortingitems':[], 'sortingorders':[], 'requeststate':[], 'requeststatus':[], 'requesttype':[], 'requestflags':[], 'publicbody':[], 'daterangetype':None, 'fromdate':None, 'todate':None, 'search':None, 'keywords':[], 'userid':None}): + return factRequestDetails.advancedsearch(params,None) From 469e1b6b7500fb0f28d0f5b034f7c23b10eb5a80 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Thu, 30 May 2024 11:56:04 -0700 Subject: [PATCH 14/33] #FOIMOD-3184 Auth related updates --- historical-search-api/request_api/auth.py | 239 +++++++----------- .../request_api/resources/historicalsearch.py | 7 +- .../request_api/resources/request.py | 2 +- .../services/historicalrequestservice.py | 2 +- .../request_api/utils/enums.py | 101 ++++++++ .../request_api/utils/util.py | 29 +-- 6 files changed, 206 insertions(+), 174 deletions(-) create mode 100644 historical-search-api/request_api/utils/enums.py diff --git a/historical-search-api/request_api/auth.py b/historical-search-api/request_api/auth.py index be62cf285..00a870cc0 100644 --- a/historical-search-api/request_api/auth.py +++ b/historical-search-api/request_api/auth.py @@ -19,6 +19,7 @@ from flask_jwt_oidc import JwtManager # from jose import jwt as josejwt from jose import JWTError, jwt as josejwt +from request_api.utils.enums import MinistryTeamWithKeycloackGroup, ProcessingTeamWithKeycloackGroup, IAOTeamWithKeycloackGroup jwt = ( JwtManager() @@ -32,11 +33,11 @@ class Auth: def require(cls, f): """Validate the Bearer Token.""" - # @jwt.requires_auth + @jwt.requires_auth @wraps(f) def decorated(*args, **kwargs): - # g.authorization_header = request.headers.get("Authorization", None) - # g.token_info = g.jwt_oidc_token_info + g.authorization_header = request.headers.get("Authorization", None) + g.token_info = g.jwt_oidc_token_info return f(*args, **kwargs) return decorated @@ -52,43 +53,7 @@ def wrapper(*args, **kwargs): return wrapper return decorated - @classmethod - def belongstosameministry(cls,func): - @wraps(func) - def decorated(type, id, field,*args, **kwargs): - usertype = AuthHelper.getusertype() - if(usertype == "iao"): - return func(type, id, field,*args, **kwargs) - elif(usertype == "ministry"): - requestministry = FOIMinistryRequest.getrequestbyministryrequestid(id) - ministrygroups = AuthHelper.getministrygroups() - expectedministrygroup = MinistryTeamWithKeycloackGroup[requestministry['programarea.bcgovcode']].value - retval = "Unauthorized" , 401 - if(expectedministrygroup not in ministrygroups): - return retval - else: - return func(type, id, field,*args, **kwargs) - return decorated - - @classmethod - def documentbelongstosameministry(cls,func): - @wraps(func) - def decorated( ministryrequestid, *args, **kwargs): - usertype = AuthHelper.getusertype() - if(usertype == "iao"): - return func( ministryrequestid,*args, **kwargs) - elif(usertype == "ministry"): - requestministry = FOIMinistryRequest.getrequestbyministryrequestid(ministryrequestid) - ministrygroups = AuthHelper.getministrygroups() - expectedministrygroup = MinistryTeamWithKeycloackGroup[requestministry['programarea.bcgovcode']].value - retval = "Unauthorized" , 401 - if(expectedministrygroup not in ministrygroups): - return retval - else: - return func(id, *args, **kwargs) - return decorated - - + @classmethod def ismemberofgroups(cls, groups): """Check that at least one of the realm groups are in the token. @@ -101,20 +66,19 @@ def decorated(f): #@Auth.require @wraps(f) def wrapper(*args, **kwargs): - # _groups = groups.split(',') - # token = jwt.get_token_auth_header() - # unverified_claims = josejwt.get_unverified_claims(token) - # usergroups = unverified_claims['groups'] - # usergroups = [usergroup.replace('/','',1) if usergroup.startswith('/') else usergroup for usergroup in usergroups] - # exists = False - # for group in _groups: - # if group in usergroups: - # exists = True - # retval = "Unauthorized" , 401 - # if exists == True: - # return f(*args, **kwargs) - # return retval - return f(*args, **kwargs) + _groups = groups.split(',') + token = jwt.get_token_auth_header() + unverified_claims = josejwt.get_unverified_claims(token) + usergroups = unverified_claims['groups'] + usergroups = [usergroup.replace('/','',1) if usergroup.startswith('/') else usergroup for usergroup in usergroups] + exists = False + for group in _groups: + if group in usergroups: + exists = True + retval = "Unauthorized" , 401 + if exists == True: + return f(*args, **kwargs) + return retval return wrapper @@ -151,19 +115,21 @@ def wrapper(*args, **kwargs): class AuthHelper: + @classmethod + def getwsuserid(cls, token): + return cls.getuserid(token) @classmethod def getuserid(cls, token=None): try: - # if token is None: - # token = request.headers.get("Authorization", None) - # unverified_claims = josejwt.get_unverified_claims(token.partition("Bearer")[2].strip()) - # if 'identity_provider' in unverified_claims and unverified_claims['identity_provider'] == "idir": - # claim_name = 'foi_preferred_username' if "foi_preferred_username" in unverified_claims else 'preferred_username' - # claim_value = unverified_claims[claim_name].lower() - # return claim_value+'@idir' if claim_value.endswith("@idir") == False else claim_value - # return unverified_claims['preferred_username'] - return 'RICHAQI@idir' + if token is None: + token = request.headers.get("Authorization", None) + unverified_claims = josejwt.get_unverified_claims(token.partition("Bearer")[2].strip()) + if 'identity_provider' in unverified_claims and unverified_claims['identity_provider'] == "idir": + claim_name = 'foi_preferred_username' if "foi_preferred_username" in unverified_claims else 'preferred_username' + claim_value = unverified_claims[claim_name].lower() + return claim_value+'@idir' if claim_value.endswith("@idir") == False else claim_value + return unverified_claims['preferred_username'] except JWTError as exception: print("JWTError >>> ", str(exception)) except Exception as ex: @@ -171,119 +137,108 @@ def getuserid(cls, token=None): @classmethod def getusername(cls): - # token = request.headers.get("Authorization", None) - # unverified_claims = josejwt.get_unverified_claims(token.partition("Bearer")[2].strip()) - # return unverified_claims['name'] - return 'Richard Qi' + token = request.headers.get("Authorization", None) + unverified_claims = josejwt.get_unverified_claims(token.partition("Bearer")[2].strip()) + return unverified_claims['name'] @classmethod def isministrymember(cls): - # usergroups = cls.getusergroups() - # ministrygroups = list(set(usergroups).intersection(MinistryTeamWithKeycloackGroup.list())) - # if len(ministrygroups) > 0: - # return True + usergroups = cls.getusergroups() + ministrygroups = list(set(usergroups).intersection(MinistryTeamWithKeycloackGroup.list())) + if len(ministrygroups) > 0: + return True return False @classmethod def isprocesingteammember(cls): - # usergroups = cls.getusergroups() - # ministrygroups = list(set(usergroups).intersection(MinistryTeamWithKeycloackGroup.list())) - # if len(ministrygroups) > 0: - # return False - # else: - # processinggroups = list(set(usergroups).intersection(ProcessingTeamWithKeycloackGroup.list())) - # if len(processinggroups) > 0: - # return True + usergroups = cls.getusergroups() + ministrygroups = list(set(usergroups).intersection(MinistryTeamWithKeycloackGroup.list())) + if len(ministrygroups) > 0: + return False + else: + processinggroups = list(set(usergroups).intersection(ProcessingTeamWithKeycloackGroup.list())) + if len(processinggroups) > 0: + return True return False @classmethod def isiaorestrictedfilemanager(cls): #roles is an array of strings - # roles = cls.getuserroles() - # try: - # if 'IAORestrictedFilesManager' in roles: - # return True - # else: - # return False - # except ValueError: - # return False - return False + roles = cls.getuserroles() + try: + if 'IAORestrictedFilesManager' in roles: + return True + else: + return False + except ValueError: + return False @classmethod def isministryrestrictedfilemanager(cls): #roles is an array of strings - # roles = cls.getuserroles() - # try: - # if 'MinistryRestrictedFilesManager' in roles: - # return True - # else: - # return False - # except ValueError: - # return False - return False + roles = cls.getuserroles() + try: + if 'MinistryRestrictedFilesManager' in roles: + return True + else: + return False + except ValueError: + return False @classmethod def getusergroups(cls): - # token = request.headers.get("Authorization", None) - # unverified_claims = josejwt.get_unverified_claims(token.partition("Bearer")[2].strip()) - # usergroups = unverified_claims['groups'] - # usergroups = [usergroup.replace('/','',1) if usergroup.startswith('/') else usergroup for usergroup in usergroups] - # return usergroups - return ['Flex Team', 'Intake Team'] - # return ['EDU Ministry Team'] + token = request.headers.get("Authorization", None) + unverified_claims = josejwt.get_unverified_claims(token.partition("Bearer")[2].strip()) + usergroups = unverified_claims['groups'] + usergroups = [usergroup.replace('/','',1) if usergroup.startswith('/') else usergroup for usergroup in usergroups] + return usergroups @classmethod def getuserroles(cls): - # token = request.headers.get("Authorization", None) - # unverified_claims = josejwt.get_unverified_claims(token.partition("Bearer")[2].strip()) - # roles = unverified_claims['role'] - # roles = [role.replace('/','',1) if role.startswith('/') else role for role in roles] - # return roles - return [] + token = request.headers.get("Authorization", None) + unverified_claims = josejwt.get_unverified_claims(token.partition("Bearer")[2].strip()) + roles = unverified_claims['role'] + roles = [role.replace('/','',1) if role.startswith('/') else role for role in roles] + return roles @classmethod def getusertype(cls): - # usergroups = cls.getusergroups() - # ministrygroups = list(set(usergroups).intersection(MinistryTeamWithKeycloackGroup.list())) - # if len(ministrygroups) > 0: - # return "ministry" - # else: - # iaogroups = list(set(usergroups).intersection(IAOTeamWithKeycloackGroup.list())) - # if len(iaogroups) > 0: - # return "iao" - # return None - return "iao" + usergroups = cls.getusergroups() + ministrygroups = list(set(usergroups).intersection(MinistryTeamWithKeycloackGroup.list())) + if len(ministrygroups) > 0: + return "ministry" + else: + iaogroups = list(set(usergroups).intersection(IAOTeamWithKeycloackGroup.list())) + if len(iaogroups) > 0: + return "iao" + return None @classmethod def getiaotype(cls): - # usergroups = cls.getusergroups() - # _groups = set(usergroups) - # if cls.isministrymember() == False: - # processinggroups = list(_groups.intersection(ProcessingTeamWithKeycloackGroup.list())) - # if len(processinggroups) > 0: - # return "processing" - # else: - # if 'Flex Team' in _groups: - # return "flex" - # elif 'Intake Team' in _groups: - # return "intake" - # else: - # return None - # else: - # return None - return "intake" + usergroups = cls.getusergroups() + _groups = set(usergroups) + if cls.isministrymember() == False: + processinggroups = list(_groups.intersection(ProcessingTeamWithKeycloackGroup.list())) + if len(processinggroups) > 0: + return "processing" + else: + if 'Flex Team' in _groups: + return "flex" + elif 'Intake Team' in _groups: + return "intake" + else: + return None + else: + return None @classmethod def getministrygroups(cls): - # usergroups = cls.getusergroups() - # return list(set(usergroups).intersection(MinistryTeamWithKeycloackGroup.list())) - return ['EDU Ministry Team'] - # return [] - + usergroups = cls.getusergroups() + return list(set(usergroups).intersection(MinistryTeamWithKeycloackGroup.list())) @classmethod def getauthtoken(cls): return request.headers.get("Authorization", None) - + \ No newline at end of file diff --git a/historical-search-api/request_api/resources/historicalsearch.py b/historical-search-api/request_api/resources/historicalsearch.py index 44f4907f7..fdd5e752c 100644 --- a/historical-search-api/request_api/resources/historicalsearch.py +++ b/historical-search-api/request_api/resources/historicalsearch.py @@ -22,9 +22,9 @@ class AdvancedHistoricalSearch(Resource): @staticmethod @TRACER.trace() @cross_origin(origins=allowedorigins()) - #@auth.require + @auth.require @cors_preflight('GET,POST,OPTIONS') - #@auth.ismemberofgroups(getrequiredmemberships()) + @auth.ismemberofgroups(getrequiredmemberships()) def get(): try: @@ -32,6 +32,9 @@ def get(): DEFAULT_SORT_ITEM = 'receiveddate' DEFAULT_SORT_ORDER = 'desc' + print("User ID is {0}, of type {1}".format(AuthHelper.getuserid(),AuthHelper.getusertype())) + print("Is restricted file manager - {0}".format(AuthHelper.isiaorestrictedfilemanager())) + params = { 'usertype': 'iao', #todo: need to update 'groups': '', diff --git a/historical-search-api/request_api/resources/request.py b/historical-search-api/request_api/resources/request.py index ea50c2e82..8589372ac 100644 --- a/historical-search-api/request_api/resources/request.py +++ b/historical-search-api/request_api/resources/request.py @@ -19,7 +19,7 @@ from flask_cors import cross_origin from request_api.auth import auth, AuthHelper from request_api.tracer import Tracer -from request_api.utils.util import cors_preflight, allowedorigins,str_to_bool,canrestictdata +from request_api.utils.util import cors_preflight, allowedorigins from request_api.exceptions import BusinessException from request_api.services.historicalrequestservice import historicalrequestservice import json diff --git a/historical-search-api/request_api/services/historicalrequestservice.py b/historical-search-api/request_api/services/historicalrequestservice.py index 5b8648a55..f33e79c67 100644 --- a/historical-search-api/request_api/services/historicalrequestservice.py +++ b/historical-search-api/request_api/services/historicalrequestservice.py @@ -20,5 +20,5 @@ def gethistoricalrequestextensions(self, requestid): return factRequestExtensions.getextensionsbyrequestid(requestid) def advancedsearch(self, params={'usertype': 'iao', 'groups':None, 'page':1, 'size':10, 'sortingitems':[], 'sortingorders':[], 'requeststate':[], 'requeststatus':[], 'requesttype':[], 'requestflags':[], 'publicbody':[], 'daterangetype':None, 'fromdate':None, 'todate':None, 'search':None, 'keywords':[], 'userid':None}): - return factRequestDetails.advancedsearch(params,None) + return factRequestDetails.getadvancedsearchresults(params) diff --git a/historical-search-api/request_api/utils/enums.py b/historical-search-api/request_api/utils/enums.py new file mode 100644 index 000000000..15b0de3c1 --- /dev/null +++ b/historical-search-api/request_api/utils/enums.py @@ -0,0 +1,101 @@ +# Copyright © 2021 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Enum definitions.""" +from enum import Enum + + + + + +class MinistryTeamWithKeycloackGroup(Enum): + AEST = "AEST Ministry Team" + AGR = "AGR Ministry Team" + AG = "AG Ministry Team" + BRD = "BRD Ministry Team" + CAS = "CAS Ministry Team" + CITZ = "CITZ Ministry Team" + CLB = "CLB Ministry Team" + DAS = "DAS Ministry Team" + EAO = "EAO Ministry Team" + EDU = "EDU Ministry Team" + EMBC = "EMBC Ministry Team" + EMC = "EMC Ministry Team" + EMLI = "EMLI Ministry Team" + ENV = "ENV Ministry Team" + FIN = "FIN Ministry Team" + FOR = "FOR Ministry Team" + GCP = "GCP Ministry Team" + HTH = "HTH Ministry Team" + IIO = "IIO Ministry Team" + IRR = "IRR Ministry Team" + JERI = "JERI Ministry Team" + LBR = "LBR Ministry Team" + LDB = "LDB Ministry Team" + LWR = "LWR Ministry Team" + WLR = "WLR Ministry Team" + MCF = "MCF Ministry Team" + MGC = "MGC Ministry Team" + MMHA = "MMHA Ministry Team" + MUNI = "MUNI Ministry Team" + OBC = "OBC Ministry Team" + OCC = "OCC Ministry Team" + OOP = "OOP Ministry Team" + PSA = "PSA Ministry Team" + PSSG = "PSSG Ministry Team" + MSD = "MSD Ministry Team" + TACS = "TACS Ministry Team" + TIC = "TIC Ministry Team" + TRAN = "TRAN Ministry Team" + PSE = "PSE Ministry Team" + ECC = "ECC Ministry Team" + JED = "JED Ministry Team" + COR = "COR Ministry Team" + HSG = "HSG Ministry Team" + + @staticmethod + def list(): + return list(map(lambda c: c.value, MinistryTeamWithKeycloackGroup)) + +class ProcessingTeamWithKeycloackGroup(Enum): + scanningteam = "Scanning Team" + centralteam = "Central Team" + justicehealthteam = "Justice Health Team" + mcfdpersonalteam = "MCFD Personals Team" + resouceteam = "Resource Team" + socialtechteam = "Social Education" + centraleconteam = "Central and Economy Team" + resourcejusticeteam = "Resource and Justice Team" + communityhealthteam = "Community and Health Team" + childrenfamilyteam = "Children and Family Team" + childreneducationteam = "Children and Education Team" + coordinatedresponseunit = "Coordinated Response Unit" + + @staticmethod + def list(): + return list(map(lambda c: c.value, ProcessingTeamWithKeycloackGroup)) + +class IAOTeamWithKeycloackGroup(Enum): + intake = "Intake Team" + flex = "Flex Team" + + @staticmethod + def list(): + return list(map(lambda c: c.value, IAOTeamWithKeycloackGroup)) + list(map(lambda c: c.value, ProcessingTeamWithKeycloackGroup)) + +class UserGroup(Enum): + intake = "Intake Team" + flex = "Flex Team" + processing = "@processing" + ministry = "@bcgovcode Ministry Team" + diff --git a/historical-search-api/request_api/utils/util.py b/historical-search-api/request_api/utils/util.py index 7715b8e08..479e36e1c 100644 --- a/historical-search-api/request_api/utils/util.py +++ b/historical-search-api/request_api/utils/util.py @@ -26,6 +26,7 @@ from flask import request, g from sqlalchemy.sql.expression import false from request_api.auth import jwt as _authjwt,AuthHelper +from request_api.utils.enums import ProcessingTeamWithKeycloackGroup import jwt import os @@ -60,8 +61,6 @@ def snake2camelback(snake_dict: dict): def getrequiredmemberships(): membership ='' - for group in MinistryTeamWithKeycloackGroup: - membership+='{0},'.format(group.value) for procgroup in ProcessingTeamWithKeycloackGroup: membership+='{0},'.format(procgroup.value) membership+='Intake Team,Flex Team' @@ -106,34 +105,8 @@ def str_to_bool(s): else: raise ValueError # evil ValueError that doesn't tell you what the wrong value was -def canrestictdata(requestid,assignee,isrestricted,israwrequest): - _isawatcher = False - currentuser = AuthHelper.getuserid() - if israwrequest : - _isawatcher = rawrequestservice().israwrequestwatcher(requestid,currentuser) - else: - _isawatcher = FOIRequestWatcher.isaiaoministryrequestwatcher(requestid,currentuser) - - isiaorestrictedfilemanager = AuthHelper.isiaorestrictedfilemanager() - # print('Current user is {0} , is a watcher: {1} and is file manager {2} '.format(currentuser,_isawatcher,isiaorestrictedfilemanager)) - if(isrestricted and currentuser != assignee and _isawatcher == False and isiaorestrictedfilemanager == False): - return True - else: - return False -def canrestictdata_ministry(requestid,assignee,isrestricted): - - _isawatcher = False - currentuser = AuthHelper.getuserid() - _isawatcher = FOIRequestWatcher.isaministryministryrequestwatcher(requestid,currentuser) - - isministryrestrictedfilemanager = AuthHelper.isministryrestrictedfilemanager() - # print('Current user is {0}, assignee is {3}, is a watcher: {1} and is file manager {2} '.format(currentuser,_isawatcher,isministryrestrictedfilemanager,assignee)) - if(isrestricted and currentuser != assignee and _isawatcher == False and isministryrestrictedfilemanager == False): - return True - else: - return False From bfbabc4a99909b439e5eabf34689c6e459a2fe85 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Thu, 30 May 2024 12:33:10 -0700 Subject: [PATCH 15/33] #FOIMOD-3184 Restricted Request filtering by User Token --- .../request_api/models/factRequestDetails.py | 5 ++++- .../request_api/resources/historicalsearch.py | 6 +++--- .../request_api/services/historicalrequestservice.py | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/historical-search-api/request_api/models/factRequestDetails.py b/historical-search-api/request_api/models/factRequestDetails.py index b5cc6ca0d..d8a1f9bd7 100644 --- a/historical-search-api/request_api/models/factRequestDetails.py +++ b/historical-search-api/request_api/models/factRequestDetails.py @@ -123,7 +123,7 @@ def getdescriptionhistorybyid(cls, requestid): return history @classmethod - def getadvancedsearchresults(cls, params): + def getadvancedsearchresults(cls,isiaorestictedmanager:False, params): searchresults = [] try: basequery = 'SELECT foirequestid \ @@ -168,6 +168,9 @@ def getadvancedsearchresults(cls, params): if(conditioncount == 0): basequery+= "LOWER(description) like LOWER('%{0}%')".format(keyword) + if(isiaorestictedmanager == False): + basequery+= " AND requesttypename NOT LIKE '%Restricted%' AND applicantname NOT LIKE '%Restricted%'" + basequery+= ' ORDER BY {0} {1}'.format(params['sortingitem'],params['sortingorder']) if params['size'] is not None: diff --git a/historical-search-api/request_api/resources/historicalsearch.py b/historical-search-api/request_api/resources/historicalsearch.py index fdd5e752c..ef9522bcc 100644 --- a/historical-search-api/request_api/resources/historicalsearch.py +++ b/historical-search-api/request_api/resources/historicalsearch.py @@ -36,7 +36,7 @@ def get(): print("Is restricted file manager - {0}".format(AuthHelper.isiaorestrictedfilemanager())) params = { - 'usertype': 'iao', #todo: need to update + 'usertype': AuthHelper.getusertype(), 'groups': '', 'size': flask.request.args.get('size', DEFAULT_SIZE, type=int), 'sortingitem': flask.request.args.get('sortingitem'), @@ -64,8 +64,8 @@ def get(): params['keywords'] = [] statuscode = 200 - if (params['usertype'] == "iao" or params['usertype'] == "ministry"): - requests = historicalrequestservice().advancedsearch(params) + if (params['usertype'] == "iao"): + requests = historicalrequestservice().advancedsearch(AuthHelper.isiaorestrictedfilemanager(),params) else: statuscode = 401 diff --git a/historical-search-api/request_api/services/historicalrequestservice.py b/historical-search-api/request_api/services/historicalrequestservice.py index f33e79c67..58806888c 100644 --- a/historical-search-api/request_api/services/historicalrequestservice.py +++ b/historical-search-api/request_api/services/historicalrequestservice.py @@ -19,6 +19,6 @@ def gethistoricalrequestdescriptionhistory(self, requestid): def gethistoricalrequestextensions(self, requestid): return factRequestExtensions.getextensionsbyrequestid(requestid) - def advancedsearch(self, params={'usertype': 'iao', 'groups':None, 'page':1, 'size':10, 'sortingitems':[], 'sortingorders':[], 'requeststate':[], 'requeststatus':[], 'requesttype':[], 'requestflags':[], 'publicbody':[], 'daterangetype':None, 'fromdate':None, 'todate':None, 'search':None, 'keywords':[], 'userid':None}): - return factRequestDetails.getadvancedsearchresults(params) + def advancedsearch(self,isiaorestictedmanager:False, params={'usertype': 'iao', 'groups':None, 'page':1, 'size':10, 'sortingitem':[], 'sortingorder':[], 'requeststate':[], 'requeststatus':[], 'requesttype':[], 'requestflags':[], 'publicbody':[], 'daterangetype':None, 'fromdate':None, 'todate':None, 'search':None, 'keywords':[], 'userid':None}): + return factRequestDetails.getadvancedsearchresults(isiaorestictedmanager,params) From f589458787356951bd4e7bcf071f129144306f0a Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Thu, 30 May 2024 12:42:24 -0700 Subject: [PATCH 16/33] #FOIMOD-3184 Request Details Auth decor --- .../request_api/resources/historicalsearch.py | 7 ++----- .../request_api/resources/request.py | 11 +++++++---- historical-search-api/request_api/utils/util.py | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/historical-search-api/request_api/resources/historicalsearch.py b/historical-search-api/request_api/resources/historicalsearch.py index ef9522bcc..6712296e6 100644 --- a/historical-search-api/request_api/resources/historicalsearch.py +++ b/historical-search-api/request_api/resources/historicalsearch.py @@ -4,7 +4,7 @@ from flask_cors import cross_origin from request_api.tracer import Tracer -from request_api.utils.util import cors_preflight, allowedorigins, getrequiredmemberships +from request_api.utils.util import cors_preflight, allowedorigins, getIAOmemberships from request_api.auth import AuthHelper, auth from request_api.tracer import Tracer from request_api.exceptions import BusinessException @@ -24,7 +24,7 @@ class AdvancedHistoricalSearch(Resource): @cross_origin(origins=allowedorigins()) @auth.require @cors_preflight('GET,POST,OPTIONS') - @auth.ismemberofgroups(getrequiredmemberships()) + @auth.ismemberofgroups(getIAOmemberships()) def get(): try: @@ -32,9 +32,6 @@ def get(): DEFAULT_SORT_ITEM = 'receiveddate' DEFAULT_SORT_ORDER = 'desc' - print("User ID is {0}, of type {1}".format(AuthHelper.getuserid(),AuthHelper.getusertype())) - print("Is restricted file manager - {0}".format(AuthHelper.isiaorestrictedfilemanager())) - params = { 'usertype': AuthHelper.getusertype(), 'groups': '', diff --git a/historical-search-api/request_api/resources/request.py b/historical-search-api/request_api/resources/request.py index 8589372ac..70a73b6eb 100644 --- a/historical-search-api/request_api/resources/request.py +++ b/historical-search-api/request_api/resources/request.py @@ -19,7 +19,7 @@ from flask_cors import cross_origin from request_api.auth import auth, AuthHelper from request_api.tracer import Tracer -from request_api.utils.util import cors_preflight, allowedorigins +from request_api.utils.util import cors_preflight, allowedorigins,getIAOmemberships from request_api.exceptions import BusinessException from request_api.services.historicalrequestservice import historicalrequestservice import json @@ -43,7 +43,8 @@ class FOIHistoricalSearch(Resource): @staticmethod @TRACER.trace() @cross_origin(origins=allowedorigins()) - # @auth.require + @auth.require + @auth.ismemberofgroups(getIAOmemberships()) def get(requestid): try : statuscode = 200 @@ -63,7 +64,8 @@ class FOIHistoricalSearch(Resource): @staticmethod @TRACER.trace() @cross_origin(origins=allowedorigins()) - # @auth.require + @auth.require + @auth.ismemberofgroups(getIAOmemberships()) def get(requestid): try : statuscode = 200 @@ -82,7 +84,8 @@ class FOIHistoricalSearch(Resource): @staticmethod @TRACER.trace() @cross_origin(origins=allowedorigins()) - # @auth.require + @auth.require + @auth.ismemberofgroups(getIAOmemberships()) def get(requestid): try : statuscode = 200 diff --git a/historical-search-api/request_api/utils/util.py b/historical-search-api/request_api/utils/util.py index 479e36e1c..70f5baf0a 100644 --- a/historical-search-api/request_api/utils/util.py +++ b/historical-search-api/request_api/utils/util.py @@ -59,7 +59,7 @@ def snake2camelback(snake_dict: dict): """Convert the passed dictionary's keys from snake_case to camelBack case.""" return camelize(snake_dict) -def getrequiredmemberships(): +def getIAOmemberships(): membership ='' for procgroup in ProcessingTeamWithKeycloackGroup: membership+='{0},'.format(procgroup.value) From a7f0ad680ed5922e7baf6431c553f7a206139453 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Thu, 30 May 2024 13:17:36 -0700 Subject: [PATCH 17/33] #FOIMOD-3184 Restricted, Auth filter for GET historical request --- .../request_api/models/factRequestDetails.py | 20 +++++++++++-------- .../request_api/resources/request.py | 2 +- .../services/historicalrequestservice.py | 4 ++-- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/historical-search-api/request_api/models/factRequestDetails.py b/historical-search-api/request_api/models/factRequestDetails.py index d8a1f9bd7..47dcd288c 100644 --- a/historical-search-api/request_api/models/factRequestDetails.py +++ b/historical-search-api/request_api/models/factRequestDetails.py @@ -11,7 +11,7 @@ class factRequestDetails(db.Model): foirequestid = db.Column(db.Integer, primary_key=True,autoincrement=True) @classmethod - def getrequestbyid(cls, requestid): + def getrequestbyid(cls, isiaorestictedmanager:False, requestid): request = {} try: sql = """select @@ -29,18 +29,22 @@ def getrequestbyid(cls, requestid): rd.subject --, rd.* from public."ClosedRequestDetailsPost2018" rd - join public."dimRequestStatuses" rs on rs.requeststatusid = rd.requeststatusid + left join public."dimRequestStatuses" rs on rs.requeststatusid = rd.requeststatusid --join public."factRequestRequesters" rr1 on rr1.requesterid = rd.requesterid and rr1.foirequestid = rd.foirequestid and rr1.activeflag = 'Y' - join public."dimRequesters" r on r.requesterid = rd.requesterid + left join public."dimRequesters" r on r.requesterid = rd.requesterid -- left join public."factRequestRequesters" rr2 on rr2.requesterid = rd.onbehalfofrequesterid and rr2.foirequestid = rd.foirequestid and rr2.activeflag = 'Y' left join public."dimRequesters" r2 on rd.onbehalfofrequesterid = r2.requesterid LEFT JOIN "dimRequesterTypes" rqt ON rd.applicantcategoryid = rqt.requestertypeid - join public."dimReceivedModes" rm on rm.receivedmodeid = rd.receivedmodeid - join public."dimAddress" a on a.addressid = rd.shipaddressid - join public."dimRequestTypes" rt on rt.requesttypeid = rd.requesttypeid - join public."dimDeliveryModes" dm on dm.deliverymodeid = rd.deliverymodeid + left join public."dimReceivedModes" rm on rm.receivedmodeid = rd.receivedmodeid + left join public."dimAddress" a on a.addressid = rd.shipaddressid + left join public."dimRequestTypes" rt on rt.requesttypeid = rd.requesttypeid + left join public."dimDeliveryModes" dm on dm.deliverymodeid = rd.deliverymodeid where rd.visualrequestfilenumber = :requestid and rd.activeflag = 'Y'""" + + if(isiaorestictedmanager == False): + sql+= " AND rd.requesttypename NOT LIKE '%Restricted%'" + rs = db.session.execute(text(sql), {'requestid': requestid}) for row in rs: request["axisRequestId"] = row['visualrequestfilenumber'] @@ -169,7 +173,7 @@ def getadvancedsearchresults(cls,isiaorestictedmanager:False, params): basequery+= "LOWER(description) like LOWER('%{0}%')".format(keyword) if(isiaorestictedmanager == False): - basequery+= " AND requesttypename NOT LIKE '%Restricted%' AND applicantname NOT LIKE '%Restricted%'" + basequery+= " AND requesttypename NOT LIKE '%Restricted%'" basequery+= ' ORDER BY {0} {1}'.format(params['sortingitem'],params['sortingorder']) diff --git a/historical-search-api/request_api/resources/request.py b/historical-search-api/request_api/resources/request.py index 70a73b6eb..a07c7aa90 100644 --- a/historical-search-api/request_api/resources/request.py +++ b/historical-search-api/request_api/resources/request.py @@ -48,7 +48,7 @@ class FOIHistoricalSearch(Resource): def get(requestid): try : statuscode = 200 - jsondata = historicalrequestservice().gethistoricalrequest(requestid) + jsondata = historicalrequestservice().gethistoricalrequest(AuthHelper.isiaorestrictedfilemanager(),requestid) return jsondata , statuscode except ValueError: return {'status': 500, 'message':INVALID_REQUEST_ID}, 500 diff --git a/historical-search-api/request_api/services/historicalrequestservice.py b/historical-search-api/request_api/services/historicalrequestservice.py index 58806888c..f3e9504d4 100644 --- a/historical-search-api/request_api/services/historicalrequestservice.py +++ b/historical-search-api/request_api/services/historicalrequestservice.py @@ -10,8 +10,8 @@ class historicalrequestservice: """ - def gethistoricalrequest(self, requestid): - return factRequestDetails.getrequestbyid(requestid) + def gethistoricalrequest(self, isiaorestictedmanager,requestid): + return factRequestDetails.getrequestbyid(isiaorestictedmanager,requestid) def gethistoricalrequestdescriptionhistory(self, requestid): return factRequestDetails.getdescriptionhistorybyid(requestid) From 2d6099786fcae39a048e9cc18d197a87fad84868 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Tue, 4 Jun 2024 16:32:22 -0700 Subject: [PATCH 18/33] #3184 histrorical search UX WIP --- .../src/actions/FOI/foiActionConstants.js | 5 +- .../src/actions/FOI/foiRequestActions.js | 14 ++ .../src/apiManager/endpoints/index.js | 1 + .../FOI/foiHistoricalSearchServices.js | 58 ++++++ .../IAO/AdvancedSearch/ActionContext.js | 53 +++++- .../AdvancedSearch/DataGridAdvancedSearch.js | 175 +++++++++++++++++- .../IAO/AdvancedSearch/SearchComponent.js | 56 +++++- .../src/modules/FOI/foiRequestsReducer.js | 12 ++ .../request_api/models/factRequestDetails.py | 2 +- 9 files changed, 369 insertions(+), 7 deletions(-) create mode 100644 forms-flow-web/src/apiManager/services/FOI/foiHistoricalSearchServices.js diff --git a/forms-flow-web/src/actions/FOI/foiActionConstants.js b/forms-flow-web/src/actions/FOI/foiActionConstants.js index f9acfee86..f32f99cb7 100644 --- a/forms-flow-web/src/actions/FOI/foiActionConstants.js +++ b/forms-flow-web/src/actions/FOI/foiActionConstants.js @@ -9,7 +9,7 @@ const FOI_ACTION_CONSTANTS = { EVENT_QUEUE_PARAMS: "EVENT_QUEUE_PARAMS", SHOW_EVENT_QUEUE: "SHOW_EVENT_QUEUE", SHOW_ADVANCED_SEARCH: "SHOW_ADVANCED_SEARCH", - FOI_ADVANCED_SEARCH_PARAMS: "FOI_ADVANCED_SEARCH_PARAMS", + FOI_ADVANCED_SEARCH_PARAMS: "FOI_ADVANCED_SEARCH_PARAMS", IS_ASSIGNEDTOLIST_LOADING: "IS_ASSIGNEDTOLIST_LOADING", IS_ATTACHMENTLIST_LOADING: "IS_ATTACHMENTLIST_LOADING", IS_COMMENTTAGLIST_LOADING: "IS_COMMENTTAGLIST_LOADING", @@ -98,6 +98,9 @@ const FOI_ACTION_CONSTANTS = { OIPC_STATUSES: "OIPC_STATUSES", OIPC_REVIEWTYPES: "OIPC_REVIEWTYPES", OIPC_INQUIRYOUTCOMES: "OIPC_INQUIRYOUTCOMES", + + FOI_ADVANCED_SEARCH_FILTER: "FOI_ADVANCED_SEARCH_FILTER", + FOI_HISTORIC_SEARCH_PARAMS: "FOI_HISTORIC_SEARCH_PARAMS", }; export default FOI_ACTION_CONSTANTS; diff --git a/forms-flow-web/src/actions/FOI/foiRequestActions.js b/forms-flow-web/src/actions/FOI/foiRequestActions.js index 55d3aca7b..922f1c650 100644 --- a/forms-flow-web/src/actions/FOI/foiRequestActions.js +++ b/forms-flow-web/src/actions/FOI/foiRequestActions.js @@ -476,4 +476,18 @@ export const setOIPCInquiryoutcomes = (data) => (dispatch) => { type: FOI_ACTION_CONSTANTS.OIPC_INQUIRYOUTCOMES, payload: data, }); +}; + +export const setAdvancedSearchFilter = (data) => (dispatch) => { + dispatch({ + type: FOI_ACTION_CONSTANTS.FOI_ADVANCED_SEARCH_FILTER, + payload: data, + }); +}; + +export const setHistoricalSearchParams = (data) => (dispatch) => { + dispatch({ + type: FOI_ACTION_CONSTANTS.FOI_HISTORIC_SEARCH_PARAMS, + payload: data, + }); }; \ No newline at end of file diff --git a/forms-flow-web/src/apiManager/endpoints/index.js b/forms-flow-web/src/apiManager/endpoints/index.js index 2804ed4ea..5fc961285 100644 --- a/forms-flow-web/src/apiManager/endpoints/index.js +++ b/forms-flow-web/src/apiManager/endpoints/index.js @@ -44,6 +44,7 @@ const API = { FOI_HISTORICAL_REQUEST_API: `${FOI_HISTORICAL_API_URL}/api/foihistoricalrequest`, FOI_HISTORICAL_REQUEST_DESCRIPTION_API: `${FOI_HISTORICAL_API_URL}/api/foihistoricalrequest/descriptionhistory`, FOI_HISTORICAL_REQUEST_EXTENSIONS_API: `${FOI_HISTORICAL_API_URL}/api/foihistoricalrequest/extensions`, + FOI_HISTORICAL_SEARCH_API: `${FOI_HISTORICAL_API_URL}/api/advancedsearch`, FOI_GET_PROGRAMAREADIVISIONS: `${FOI_BASE_API_URL}/api/foiadmin/divisions`, FOI_POST_PROGRAMAREADIVISION: `${FOI_BASE_API_URL}/api/foiadmin/division`, diff --git a/forms-flow-web/src/apiManager/services/FOI/foiHistoricalSearchServices.js b/forms-flow-web/src/apiManager/services/FOI/foiHistoricalSearchServices.js new file mode 100644 index 000000000..9a9f19b55 --- /dev/null +++ b/forms-flow-web/src/apiManager/services/FOI/foiHistoricalSearchServices.js @@ -0,0 +1,58 @@ +import { httpGETRequest } from "../../httpRequestHandler"; +import API from "../../endpoints"; +import { catchError } from "./foiServicesUtil"; +import UserService from "../../../services/UserService"; + +export const fetchHistoricalSearchData = ({ + page = 1, + size = 10, + sort = [{ field: "receivedDate", sort: "desc" }], + search = "", + keywords = [], + requestType = [], + requestFlags = [], + dateRangeType = null, + fromDate = null, + toDate = null, + publicBodies = [], + callback, + errorCallback, + dispatch, +}) => { + let sortingItems = []; + let sortingOrders = []; + sort.forEach((item) => { + sortingItems.push(item.field); + sortingOrders.push(item.sort); + }); + + httpGETRequest( + API.FOI_HISTORICAL_SEARCH_API, + { + page: page, + size: size, + sortingitem: sortingItems, + sortingorder: sortingOrders, + search: search, + keywords: keywords, + requestType: requestType, + requestFlags: requestFlags, + dateRangeType: dateRangeType, + fromDate: fromDate, + toDate: toDate, + publicBodies: publicBodies, + }, + UserService.getToken() + ) + .then((res) => { + if (res.data) { + callback(res.data); + } else { + throw new Error(); + } + }) + .catch((error) => { + catchError(error, dispatch); + errorCallback("Error in fetching historicalsearch for IAO"); + }); +}; diff --git a/forms-flow-web/src/components/FOI/Dashboard/IAO/AdvancedSearch/ActionContext.js b/forms-flow-web/src/components/FOI/Dashboard/IAO/AdvancedSearch/ActionContext.js index 5716cbdd6..d61b19ed5 100644 --- a/forms-flow-web/src/components/FOI/Dashboard/IAO/AdvancedSearch/ActionContext.js +++ b/forms-flow-web/src/components/FOI/Dashboard/IAO/AdvancedSearch/ActionContext.js @@ -2,8 +2,9 @@ import React, { createContext, useEffect, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import { fetchFOIProgramAreaList } from "../../../../../apiManager/services/FOI/foiMasterDataServices"; import { fetchAdvancedSearchData } from "../../../../../apiManager/services/FOI/foiAdvancedSearchServices"; +import { fetchHistoricalSearchData } from "../../../../../apiManager/services/FOI/foiHistoricalSearchServices"; import { errorToast } from "../../../../../helper/FOI/helper"; -import { setAdvancedSearchParams } from "../../../../../actions/FOI/foiRequestActions"; +import { setAdvancedSearchParams, setHistoricalSearchParams } from "../../../../../actions/FOI/foiRequestActions"; export const ActionContext = createContext(); ActionContext.displayName = "AdvancedSearchContext"; export const ActionProvider = ({ children }) => { @@ -13,19 +14,36 @@ export const ActionProvider = ({ children }) => { const [searchLoading, setSearchLoading] = useState(false); const [advancedSearchComponentLoading, setAdvancedSearchComponentLoading] = useState(true); + + const [queryHistoricalData, setQueryHistoricalData] = useState(null); + const [searchHistoricalDataLoading, setHistoricalSearchLoading] = useState(false); + const [historicalSearchComponentLoading, setHistoricalSearchComponentLoading] = + useState(true); + const [searchResults, setSearchResults] = useState(null); + const [searchHistoricalSearchResults, setHistoricalSearchResults] = useState(null); + const advancedSearchParams = useSelector((state) => state.foiRequests.foiAdvancedSearchParams); + const historicSearchParams = useSelector((state) => state.foiRequests.foiHistoricalSearchParams); const handleUpdateSearchFilter = (filterData) => { dispatch(setAdvancedSearchParams(filterData)) setQueryData({ ...(queryData || {}), ...filterData }); }; + const handleUpdateHistoricSearchFilter = (filterData) => { + dispatch(setHistoricalSearchParams(filterData)) + setQueryHistoricalData({ ...(queryHistoricalData || {}), ...filterData }); + }; + const defaultSortModel = [ { field: "currentState", sort: "desc" }, { field: "receivedDateUF", sort: "desc" }, ]; + const defaultHistoricSearchSortModel = [ + { field: "receivedDate", sort: "desc" }, + ]; useEffect(() => { @@ -34,6 +52,8 @@ export const ActionProvider = ({ children }) => { useEffect(() => { if (queryData) { + + fetchAdvancedSearchData({ ...queryData, callback: (data) => { @@ -49,7 +69,26 @@ export const ActionProvider = ({ children }) => { dispatch, }); } - }, [queryData]); + + if(queryHistoricalData) + { + + fetchHistoricalSearchData({ + ...queryHistoricalData, + callback: (data) => { + setHistoricalSearchLoading(false); + setHistoricalSearchComponentLoading(false); + setHistoricalSearchResults(data); + }, + errorCallback: (error) => { + setHistoricalSearchLoading(false); + setHistoricalSearchComponentLoading(false); + errorToast(error); + }, + dispatch, + }); + } + }, [queryData,queryHistoricalData]); return ( { advancedSearchComponentLoading, setAdvancedSearchComponentLoading, advancedSearchParams, + + handleUpdateHistoricSearchFilter, + searchHistoricalDataLoading, + setHistoricalSearchLoading, + searchHistoricalSearchResults, + queryHistoricalData, + defaultHistoricSearchSortModel, + historicalSearchComponentLoading, + setHistoricalSearchComponentLoading, + historicSearchParams, }} > {children} diff --git a/forms-flow-web/src/components/FOI/Dashboard/IAO/AdvancedSearch/DataGridAdvancedSearch.js b/forms-flow-web/src/components/FOI/Dashboard/IAO/AdvancedSearch/DataGridAdvancedSearch.js index 451a4fd87..f4ace3a36 100644 --- a/forms-flow-web/src/components/FOI/Dashboard/IAO/AdvancedSearch/DataGridAdvancedSearch.js +++ b/forms-flow-web/src/components/FOI/Dashboard/IAO/AdvancedSearch/DataGridAdvancedSearch.js @@ -28,6 +28,12 @@ import { push } from "connected-react-router"; import { CustomFooter } from "../../CustomFooter" import Link from "@mui/material/Link"; +import Stack from "@mui/material/Stack"; +import { + ClickableChip, +} from "../../utils"; +import { setAdvancedSearchFilter } from "../../../../../actions/FOI/foiRequestActions"; + const DataGridAdvancedSearch = ({ userDetail }) => { const dispatch = useDispatch(); @@ -39,11 +45,30 @@ const DataGridAdvancedSearch = ({ userDetail }) => { setSearchLoading, advancedSearchComponentLoading, advancedSearchParams, + + //historic search + handleUpdateHistoricSearchFilter, + searchHistoricalSearchResults, + searchHistoricalDataLoading, + queryHistoricalData, + setHistoricalSearchLoading, + historicalSearchComponentLoading, + historicSearchParams, + + } = useContext(ActionContext); const user = useSelector((state) => state.user.userDetail); + const foiadvsearchfilter = useSelector((state) => state.foiRequests.foiadvancedsearchfilter); + const advancedFilterChange = (filter) => { + if (filter === foiadvsearchfilter) { + return; + } + + dispatch(setAdvancedSearchFilter(filter)); + }; const renderReviewRequest = (e, row) => { e.preventDefault() @@ -340,6 +365,58 @@ const DataGridAdvancedSearch = ({ userDetail }) => { // sortable: false, }, ]; + + const HistoricalSearchResultsColumns = [ + { + field: "applicantname", + headerName: "APPLICANT NAME", + headerAlign: "left", + renderCell: hyperlinkRenderCell, + cellClassName: 'foi-advanced-search-result-cell', + width: 180, + }, + { + field: "requesttype", + headerName: "REQUEST TYPE", + headerAlign: "left", + renderCell: hyperlinkRenderCell, + cellClassName: 'foi-advanced-search-result-cell', + flex: 1, + }, + { + field: "axisrequestid", + headerName: "ID NUMBER", + headerAlign: "left", + renderCell: hyperlinkRenderCell, + cellClassName: 'foi-advanced-search-result-cell', + flex: 1, + }, + { + field: "oipcno", + headerName: "OIPC no", + headerAlign: "left", + renderCell: hyperlinkRenderCell, + cellClassName: 'foi-advanced-search-result-cell', + flex: 1, + }, + { + field: "assignee", + headerName: "ASSIGNED TO", + headerAlign: "left", + renderCell: hyperlinkRenderCell, + cellClassName: 'foi-advanced-search-result-cell', + flex: 1, + }, + { + field: "receiveddate", + headerName: "RECEIVED DATE", + headerAlign: "left", + renderCell: hyperlinkRenderCell, + cellClassName: 'foi-advanced-search-result-cell', + flex: 1, + }, + + ]; const defaultTableInfo = { columns: IntakeTeamColumns, @@ -351,7 +428,18 @@ const DataGridAdvancedSearch = ({ userDetail }) => { open: "flex-open", }, }; + + const defaultHistoricalResultsTableInfo = { + columns: HistoricalSearchResultsColumns, + sort: [ + { field: "receiveddate", sort: "desc" }, + ], + stateClassName: { + open: "flex-open", + }, + }; + const getTableInfo = (userGroups) => { if (!userGroups || isIntakeTeam(userGroups)) { return defaultTableInfo; @@ -393,15 +481,28 @@ const DataGridAdvancedSearch = ({ userDetail }) => { defaultRowsState ); + const [historicrowsState, sethistoricRowsState] = useState( + Object.keys(historicSearchParams).length > 0 ? + {page: historicSearchParams.page - 1, pageSize: historicSearchParams.size} : + defaultRowsState + ); + const defaultSortModel = [ { field: "currentState", sort: "desc" }, // { field: "receivedDateUF", sort: "desc" }, ]; + + const defaultHistoricSearchSortModel = [ + { field: "receivedDate", sort: "desc" }, + // { field: "receivedDateUF", sort: "desc" }, + ]; const [sortModel, setSortModel] = useState(advancedSearchParams?.sort || defaultSortModel); + const [sortHistoricsearchSortModel, setHistoricsearchSortModel] = useState(historicSearchParams?.sort || defaultHistoricSearchSortModel); useEffect(() => { if (searchResults) { setSearchLoading(true); + // page+1 here, because initial page value is 0 for mui-data-grid handleUpdateSearchFilter({ page: rowsState.page + 1, @@ -409,10 +510,29 @@ const DataGridAdvancedSearch = ({ userDetail }) => { sort: updateSortModel(sortModel), userId: userDetail.preferred_username, }); + + + + } + + if(searchHistoricalSearchResults) + { + + setHistoricalSearchLoading(true); + + handleUpdateHistoricSearchFilter({ + page: historicrowsState.page + 1, + size: historicrowsState.pageSize, + sort: updateSortModel(sortHistoricsearchSortModel), + userId: userDetail.preferred_username, + }); + } - }, [rowsState, sortModel]); + + }, [rowsState,historicrowsState, sortModel,sortHistoricsearchSortModel]); const columnsRef = React.useRef(tableInfo?.columns || []); + const historiccolumnsRef = React.useRef(defaultHistoricalResultsTableInfo?.columns || []); if (advancedSearchComponentLoading && queryData) { return ( @@ -435,7 +555,31 @@ const DataGridAdvancedSearch = ({ userDetail }) => {

Search Results

+ + + {advancedFilterChange("foimod"); console.log(`Value of advanced search filter on advancedsearchresults is ${foiadvsearchfilter}`)}} + clicked={foiadvsearchfilter === "foimod"} + /> + { advancedFilterChange("historicalsearchresults"); console.log(`Value of advanced search filter on historicalsearchresults is ${foiadvsearchfilter}`)}} + clicked={foiadvsearchfilter === "historicalsearchresults"} + /> + + + { (foiadvsearchfilter === "foimod") ? + // FOI MOD Search results { ) } loading={searchLoading} + + /> : + // Historical Search results + row.axisrequestid} + rows={searchHistoricalSearchResults || []} + columns={HistoricalSearchResultsColumns} + rowHeight={30} + headerHeight={50} + rowCount={0} + pageSize={10} + rowsPerPageOptions={[10]} + hideFooterSelectedRowCount={true} + disableColumnMenu={true} + pagination + //paginationMode="server" + initialState={{ + pagination: historicrowsState + }} + + sortingOrder={["desc", "asc"]} + sortModel={[sortHistoricsearchSortModel[0]]} + sortingMode={"server"} + + loading={searchHistoricalDataLoading} + /> +} diff --git a/forms-flow-web/src/components/FOI/Dashboard/IAO/AdvancedSearch/SearchComponent.js b/forms-flow-web/src/components/FOI/Dashboard/IAO/AdvancedSearch/SearchComponent.js index 78b62c522..74f27ba94 100644 --- a/forms-flow-web/src/components/FOI/Dashboard/IAO/AdvancedSearch/SearchComponent.js +++ b/forms-flow-web/src/components/FOI/Dashboard/IAO/AdvancedSearch/SearchComponent.js @@ -90,6 +90,15 @@ const AdvancedSearch = ({ userDetail }) => { setAdvancedSearchComponentLoading, setSearchLoading, advancedSearchParams, + + handleUpdateHistoricSearchFilter, + searchHistoricalDataLoading, + historicalSearchComponentLoading, + setHistoricalSearchComponentLoading, + setHistoricalSearchLoading, + historicSearchParams, + defaultHistoricSearchSortModel + } = useContext(ActionContext); const programAreaList = useSelector( @@ -240,6 +249,7 @@ const AdvancedSearch = ({ userDetail }) => { .filter((value) => value); }; const handleApplySearchFilters = () => { + if (!advancedSearchComponentLoading) { setAdvancedSearchComponentLoading(true); } @@ -259,9 +269,41 @@ const AdvancedSearch = ({ userDetail }) => { size: advancedSearchParams?.size || DEFAULT_PAGE_SIZE, sort: defaultSortModel, userId: userDetail.preferred_username, - }); + }); + }; + const handleApplyHistoricSearchFilters = () =>{ + + //HISTORICAL SEARCH + if (!historicalSearchComponentLoading) { + setHistoricalSearchComponentLoading(true); + } + setHistoricalSearchLoading(true); + handleUpdateHistoricSearchFilter({ + search: searchFilterSelected, + keywords: keywordsMode ? keywords : [searchText.trim()], + requestState: getTrueKeysFromCheckboxObject(requestState), + requestType: getTrueKeysFromCheckboxObject(requestTypes), + requestFlags: getTrueKeysFromCheckboxObject(requestFlags), + requestStatus: getTrueKeysFromCheckboxObject(requestStatus), + dateRangeType: selectedDateRangeType || null, + fromDate: fromDate || null, + toDate: toDate || null, + publicBodies: selectedPublicBodies, + page: 1, + size: historicSearchParams?.size || DEFAULT_PAGE_SIZE, + sort: defaultHistoricSearchSortModel, + userId: userDetail.preferred_username, + }); + + } + + const handleSearch = () =>{ + handleApplySearchFilters(); + handleApplyHistoricSearchFilters(); + } + useEffect(() => { if (Object.keys(advancedSearchParams).length > 0) { if (!advancedSearchComponentLoading) { @@ -270,6 +312,16 @@ const AdvancedSearch = ({ userDetail }) => { setSearchLoading(true); handleUpdateSearchFilter(advancedSearchParams) } + + if (Object.keys(historicSearchParams).length > 0) { + if (!historicalSearchComponentLoading) { + setAdvancedSearchComponentLoading(true); + } + setHistoricalSearchLoading(true); + handleUpdateHistoricSearchFilter(historicSearchParams) + } + + }, []); const noSearchCriteria = () => { @@ -1005,7 +1057,7 @@ const AdvancedSearch = ({ userDetail }) => { textTransform: "none", }} variant="contained" - onClick={handleApplySearchFilters} + onClick={handleSearch} disabled={searchLoading || noSearchCriteria() || ((searchText || keywords.length>0) && !searchFilterSelected ) } disableElevation > diff --git a/forms-flow-web/src/modules/FOI/foiRequestsReducer.js b/forms-flow-web/src/modules/FOI/foiRequestsReducer.js index 4da4d7096..b8c103930 100644 --- a/forms-flow-web/src/modules/FOI/foiRequestsReducer.js +++ b/forms-flow-web/src/modules/FOI/foiRequestsReducer.js @@ -18,6 +18,7 @@ const initialState = { showAdvancedSearch: false, showEventQueue: false, foiAdvancedSearchParams: {}, + foiHistoricalSearchParams:{}, isAssignedToListLoading: true, isAttachmentListLoading: true, isCommentTagListLoading: true, @@ -153,6 +154,7 @@ const initialState = { oipcStatuses: [], oipcReviewtypes: [], oipcInquiryoutcomes: [], + foiadvancedsearchfilter:"foimod" }; const foiRequests = (state = initialState, action) => { @@ -183,6 +185,14 @@ const foiRequests = (state = initialState, action) => { ...action.payload, }, }; + case FOI_ACTION_CONSTANTS.FOI_HISTORIC_SEARCH_PARAMS: + return { + ...state, + foiHistoricalSearchParams: { + ...state.foiHistoricalSearchParams, + ...action.payload, + }, + }; case FOI_ACTION_CONSTANTS.IS_ASSIGNEDTOLIST_LOADING: return { ...state, isAssignedToListLoading: action.payload }; case FOI_ACTION_CONSTANTS.IS_ATTACHMENTLIST_LOADING: @@ -337,6 +347,8 @@ const foiRequests = (state = initialState, action) => { return { ...state, oipcReviewtypes: action.payload }; case FOI_ACTION_CONSTANTS.OIPC_INQUIRYOUTCOMES: return { ...state, oipcInquiryoutcomes: action.payload }; + case FOI_ACTION_CONSTANTS.FOI_ADVANCED_SEARCH_FILTER: + return { ...state, foiadvancedsearchfilter: action.payload }; default: return state; } diff --git a/historical-search-api/request_api/models/factRequestDetails.py b/historical-search-api/request_api/models/factRequestDetails.py index 47dcd288c..10d3baf43 100644 --- a/historical-search-api/request_api/models/factRequestDetails.py +++ b/historical-search-api/request_api/models/factRequestDetails.py @@ -185,7 +185,7 @@ def getadvancedsearchresults(cls,isiaorestictedmanager:False, params): rs = db.session.execute(text(basequery)) for row in rs: - searchresults.append({"axisrequestid": row["visualrequestfilenumber"], "description": row["description"], "assignee": row["assignee"], "requeststatus": row["requeststatus"], "applicantname": row["applicantname"], "requesttypename": row["requesttypename"],"receiveddate": row["receiveddate"],"oipcno": row["oipcno"]}) + searchresults.append({"axisrequestid": row["visualrequestfilenumber"], "description": row["description"], "assignee": row["assignee"], "requeststatus": row["requeststatus"], "applicantname": row["applicantname"], "requesttype": row["requesttypename"],"receiveddate": row["receiveddate"],"oipcno": row["oipcno"]}) except Exception as ex: logging.error(ex) raise ex From ac6f0f51d91390c06bbbbbea49c095bb1fed2954 Mon Sep 17 00:00:00 2001 From: Abin Antony Date: Mon, 10 Jun 2024 09:40:46 -0700 Subject: [PATCH 19/33] Docker Port issue fix --- historical-search-api/dockerfile | 2 +- historical-search-api/dockerfile.local | 2 +- historical-search-api/request_api/resources/apihelper.py | 2 +- historical-search-api/wsgi.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/historical-search-api/dockerfile b/historical-search-api/dockerfile index f043ae942..d2e2850c9 100644 --- a/historical-search-api/dockerfile +++ b/historical-search-api/dockerfile @@ -3,7 +3,7 @@ # Necessary to pull images from bcgov and not hit Dockerhub quotas. FROM artifacts.developer.gov.bc.ca/docker-remote/python:3.10.8-buster -EXPOSE 6402 +EXPOSE 9402 RUN set -eux; apt-get update; apt-get install -y --no-install-recommends ca-certificates curl netbase wget ; rm -rf /var/lib/apt/lists/* # Keeps Python from generating .pyc files in the container diff --git a/historical-search-api/dockerfile.local b/historical-search-api/dockerfile.local index a3f3052a8..781905107 100644 --- a/historical-search-api/dockerfile.local +++ b/historical-search-api/dockerfile.local @@ -1,6 +1,6 @@ # For more information, please refer to https://aka.ms/vscode-docker-python FROM python:3.10.8 -EXPOSE 6402 +EXPOSE 9402 # Keeps Python from generating .pyc files in the container ENV PYTHONDONTWRITEBYTECODE=1 diff --git a/historical-search-api/request_api/resources/apihelper.py b/historical-search-api/request_api/resources/apihelper.py index 73f4188e7..fcd46f684 100644 --- a/historical-search-api/request_api/resources/apihelper.py +++ b/historical-search-api/request_api/resources/apihelper.py @@ -25,5 +25,5 @@ class Api(BaseApi): @property def specs_url(self): """Return URL for endpoint.""" - scheme = 'http' if '5000' in self.base_url else 'https' + scheme = 'http' if '6000' in self.base_url else 'https' return url_for(self.endpoint('specs'), _external=True, _scheme=scheme) diff --git a/historical-search-api/wsgi.py b/historical-search-api/wsgi.py index 97987e6d4..646bc586c 100644 --- a/historical-search-api/wsgi.py +++ b/historical-search-api/wsgi.py @@ -20,4 +20,4 @@ application = create_app() # pylint: disable=invalid-name if __name__ == "__main__": - application.run() \ No newline at end of file + application.run(host='0.0.0.0',port=6000) \ No newline at end of file From f8a2d3b4f09a9043b9d1c67e1043c009429b92b5 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 5 Jul 2024 16:11:46 -0700 Subject: [PATCH 20/33] added support for oipc details --- docker-compose.yml | 2 +- .../components/FOI/FOIRequest/FOIRequest.js | 1 + .../FOI/FOIRequest/OIPCDetails/Index.jsx | 6 +- .../OIPCDetails/OIPCDetailsList.jsx | 4 +- .../FOI/FOIRequest/OIPCDetails/OIPCItem.jsx | 80 ++++++++++++++----- .../request_api/models/factRequestDetails.py | 21 ++++- 6 files changed, 88 insertions(+), 26 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index d6bd7bc92..74960d955 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -138,7 +138,7 @@ services: aliases: - backendnw ports: - - 15001:5000 + - 15001:6000 environment: - DATABASE_USERNAME=${FOI_EDW_DATABASE_USERNAME} - DATABASE_PASSWORD=${FOI_EDW_DATABASE_PASSWORD} diff --git a/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js b/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js index e3b88fe02..4b858df8f 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js @@ -1350,6 +1350,7 @@ const FOIRequest = React.memo(({ userDetail }) => { addOIPC={addOIPC} removeOIPC={removeOIPC} isMinistry={false} + isHistoricalRequest={isHistoricalRequest} /> )} diff --git a/forms-flow-web/src/components/FOI/FOIRequest/OIPCDetails/Index.jsx b/forms-flow-web/src/components/FOI/FOIRequest/OIPCDetails/Index.jsx index 438443cd8..2c2d6c411 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/OIPCDetails/Index.jsx +++ b/forms-flow-web/src/components/FOI/FOIRequest/OIPCDetails/Index.jsx @@ -10,7 +10,7 @@ import { faCirclePlus } from '@fortawesome/free-solid-svg-icons'; import OIPCDetailsMinistry from "./OIPCDetailsMinistry"; const OIPCDetails = (props) => { - const { oipcData, addOIPC, removeOIPC, updateOIPC, isMinistry } = props; + const { oipcData, addOIPC, removeOIPC, updateOIPC, isMinistry, isHistoricalRequest } = props; //Styling const useStyles = makeStyles({ @@ -35,9 +35,9 @@ const OIPCDetails = (props) => { OIPC DETAILS - +
-

Add Additional OIPC Complaint

diff --git a/forms-flow-web/src/components/FOI/FOIRequest/OIPCDetails/OIPCDetailsList.jsx b/forms-flow-web/src/components/FOI/FOIRequest/OIPCDetails/OIPCDetailsList.jsx index a4c825ce8..11b523121 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/OIPCDetails/OIPCDetailsList.jsx +++ b/forms-flow-web/src/components/FOI/FOIRequest/OIPCDetails/OIPCDetailsList.jsx @@ -3,12 +3,12 @@ import Divider from '@material-ui/core/Divider'; import './oipcdetails.scss'; const OIPCDetailsList = (props) => { - const {oipcData, removeOIPC, updateOIPC} = props; + const {oipcData, removeOIPC, updateOIPC, isHistoricalRequest} = props; const OIPCItems = oipcData?.map((oipcObj, index) => { return ( <> - + {index !== (oipcData?.length - 1) && } ); diff --git a/forms-flow-web/src/components/FOI/FOIRequest/OIPCDetails/OIPCItem.jsx b/forms-flow-web/src/components/FOI/FOIRequest/OIPCDetails/OIPCItem.jsx index f9e890fbb..10941de89 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/OIPCDetails/OIPCItem.jsx +++ b/forms-flow-web/src/components/FOI/FOIRequest/OIPCDetails/OIPCItem.jsx @@ -9,7 +9,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faTrash } from '@fortawesome/free-solid-svg-icons'; const OIPCItem = (props) => { - const {oipcObj, updateOIPC, removeOIPC} = props; + const {oipcObj, updateOIPC, removeOIPC, isHistoricalRequest} = props; //App State const oipcOutcomes = useSelector(state=> state.foiRequests.oipcOutcomes); @@ -150,7 +150,7 @@ const OIPCItem = (props) => { InputLabelProps={{ shrink: true }} error={(!oipc.outcomeid || oipc.outcomeid === 5) && oipc.oipcno === ""} required - disabled={oipc.outcomeid && oipc.outcomeid !== 5} + disabled={oipc.outcomeid && oipc.outcomeid !== 5 || isHistoricalRequest} placeholder="OIPC Number" /> @@ -166,10 +166,11 @@ const OIPCItem = (props) => { type="date" error={(!oipc.outcomeid || oipc.outcomeid === 5) && oipc.receiveddate === null || oipc.receiveddate === ""} required - disabled={oipc.outcomeid && oipc.outcomeid !== 5} + disabled={oipc.outcomeid && oipc.outcomeid !== 5 || isHistoricalRequest} /> + {!isHistoricalRequest ? { {uniqueReviewTypes(oipcReviewtypes).map((reviewtype) => { return {reviewtype.type_name} })} - + : + + } + {!isHistoricalRequest ? { return {reviewtype.reason_name} } }) : null} - + : + + } + {!isHistoricalRequest ? { onChange = {(event) => handleStatus(event.target.value)} error={(!oipc.outcomeid || oipc.outcomeid === 5) && oipc.statusid === null} required - disabled={oipc.outcomeid && oipc.outcomeid !== 5} + disabled={oipc.outcomeid && oipc.outcomeid !== 5 || isHistoricalRequest} > Select Status @@ -233,7 +256,16 @@ const OIPCItem = (props) => { {oipcStatuses.map((status) => { return {status.name} })} - + : + + } { onChange = {(event) => handleInvestiagtor(event.target.value)} value={oipc.investigator} InputLabelProps={{ shrink: true }} - disabled={oipc.outcomeid && oipc.outcomeid !== 5} + disabled={oipc.outcomeid && oipc.outcomeid !== 5 || isHistoricalRequest} placeholder="Firstname Lastname" /> + {!isHistoricalRequest ? { return {outcome.name} } })} - + : + + } { InputLabelProps={{ shrink: true }} InputProps={{inputProps: { max: formatDate(new Date())} }} type="date" - disabled={oipc.outcomeid && oipc.outcomeid !== 5} + disabled={oipc.outcomeid && oipc.outcomeid !== 5 || isHistoricalRequest} /> @@ -289,14 +331,14 @@ const OIPCItem = (props) => { handleInquiry(true)} - disabled={oipc.outcomeid && oipc.outcomeid !== 5} + disabled={oipc.outcomeid && oipc.outcomeid !== 5 || isHistoricalRequest} />} label="Yes" /> handleInquiry(false)} - disabled={oipc.outcomeid && oipc.outcomeid !== 5} + disabled={oipc.outcomeid && oipc.outcomeid !== 5 || isHistoricalRequest} />} label="No" /> @@ -306,14 +348,14 @@ const OIPCItem = (props) => { handleJudicalReview(true)} - disabled={oipc.outcomeid && oipc.outcomeid !== 5} + disabled={oipc.outcomeid && oipc.outcomeid !== 5 || isHistoricalRequest} />} label="Yes" /> handleJudicalReview(false)} - disabled={oipc.outcomeid && oipc.outcomeid !== 5} + disabled={oipc.outcomeid && oipc.outcomeid !== 5 || isHistoricalRequest} />} label="No" /> @@ -323,14 +365,14 @@ const OIPCItem = (props) => { handleSubsequentAppeal(true)} - disabled={oipc.outcomeid && oipc.outcomeid !== 5} + disabled={oipc.outcomeid && oipc.outcomeid !== 5 || isHistoricalRequest} />} label="Yes" /> handleSubsequentAppeal(false)} - disabled={oipc.outcomeid && oipc.outcomeid !== 5} + disabled={oipc.outcomeid && oipc.outcomeid !== 5 || isHistoricalRequest} />} label="No" /> @@ -350,7 +392,7 @@ const OIPCItem = (props) => { type="date" error={oipc.inquiryattributes.orderno ? (!oipc.outcomeid || oipc.outcomeid === 5) && oipc.inquiryattributes.inquirydate === null || oipc.inquiryattributes.inquirydate === "" : false} required={oipc.inquiryattributes.orderno} - disabled={oipc.outcomeid && oipc.outcomeid !== 5} + disabled={oipc.outcomeid && oipc.outcomeid !== 5 || isHistoricalRequest} /> @@ -363,7 +405,7 @@ const OIPCItem = (props) => { InputLabelProps={{ shrink: true }} error={oipc.inquiryattributes.inquirydate ? (!oipc.outcomeid || oipc.outcomeid === 5) && oipc.inquiryattributes.orderno === "" : false} required={oipc.inquiryattributes.inquirydate} - disabled={oipc.outcomeid && oipc.outcomeid !== 5} + disabled={oipc.outcomeid && oipc.outcomeid !== 5 || isHistoricalRequest} placeholder="Order Number" /> @@ -376,7 +418,7 @@ const OIPCItem = (props) => { fullWidth value={oipc.inquiryattributes.inquiryoutcome ? oipc.inquiryattributes.inquiryoutcome : -1} label="Inquiry Outcome" - disabled={oipc.outcomeid && oipc.outcomeid !== 5} + disabled={oipc.outcomeid && oipc.outcomeid !== 5 || isHistoricalRequest} > Select Inquiry Outcome diff --git a/historical-search-api/request_api/models/factRequestDetails.py b/historical-search-api/request_api/models/factRequestDetails.py index 10d3baf43..539a0738d 100644 --- a/historical-search-api/request_api/models/factRequestDetails.py +++ b/historical-search-api/request_api/models/factRequestDetails.py @@ -26,7 +26,8 @@ def getrequestbyid(cls, isiaorestictedmanager:False, requestid): rs.requeststatusname, a.address1, a.address2, a.city, a.state, a.country, a.zipcode, rd.description, rd.startdate, rd.closeddate, rd.receiveddate, rd.targetdate AS duedate, rd.originaltargetdate AS originalduedate, - rd.subject + rd.subject, + rd.oipcno, rd.reviewtype, rd.reason, rd.status, rd.portfolioofficer, rof.orderno, rof.inquirydate, rof.outcome, rof.inquirydate is not null as isinquiry --, rd.* from public."ClosedRequestDetailsPost2018" rd left join public."dimRequestStatuses" rs on rs.requeststatusid = rd.requeststatusid @@ -40,6 +41,7 @@ def getrequestbyid(cls, isiaorestictedmanager:False, requestid): left join public."dimAddress" a on a.addressid = rd.shipaddressid left join public."dimRequestTypes" rt on rt.requesttypeid = rd.requesttypeid left join public."dimDeliveryModes" dm on dm.deliverymodeid = rd.deliverymodeid + left join public."factRequestOIPCFields" rof on rof.foirequestid = rd.foirequestid and rof.activeflag = 'Y' where rd.visualrequestfilenumber = :requestid and rd.activeflag = 'Y'""" if(isiaorestictedmanager == False): @@ -82,6 +84,23 @@ def getrequestbyid(cls, isiaorestictedmanager:False, requestid): request["receivedMode"] = row['receivedmodename'] request["deliveryMode"] = row['deliverymodename'] request["subjectCode"] = row['subject'] + if row['requesttypename'] == 'Review': + request['isoipcreview'] = True + request['oipcdetails'] = [{}] + request['oipcdetails'][0]['oipcno'] = row['oipcno'] + request['oipcdetails'][0]['receiveddate'] = row['receiveddate'].strftime('%Y-%m-%d') + request['oipcdetails'][0]['closeddate'] = row['closeddate'].strftime('%Y-%m-%d') + request['oipcdetails'][0]['reviewetype'] = row['reviewtype'] + request['oipcdetails'][0]['reason'] = row['reason'] + request['oipcdetails'][0]['status'] = row['status'] + request['oipcdetails'][0]['investigator'] = row['portfolioofficer'] + request['oipcdetails'][0]['outcome'] = row['outcome'] + request['oipcdetails'][0]['isinquiry'] = row['isinquiry'] + if row['isinquiry']: + request['oipcdetails'][0]['inquiryattributes'] = {} + request['oipcdetails'][0]['inquiryattributes']['inquirydate'] = row['inquirydate'] + request['oipcdetails'][0]['inquiryattributes']['orderno'] = row['orderno'] + # requestdetails["assignedToFirstName"] = row["assignedtofirstname"] # requestdetails["assignedToLastName"] = row["assignedtolastname"] # requestdetails["bcgovcode"] = row["bcgovcode"] From 3bb9a68331bfa0b488c3ef45e7dde8aa211913e2 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 22 Jul 2024 10:28:00 -0700 Subject: [PATCH 21/33] add github cd for historical search api --- .../workflows/historical-search-api-cd.yml | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 .github/workflows/historical-search-api-cd.yml diff --git a/.github/workflows/historical-search-api-cd.yml b/.github/workflows/historical-search-api-cd.yml new file mode 100644 index 000000000..d245368db --- /dev/null +++ b/.github/workflows/historical-search-api-cd.yml @@ -0,0 +1,98 @@ +name: Historical Search API CD + + +on: + push: + branches: + - dev + - dev-marshal + - test-marshal + - dev-rook + - test-rook + - main + paths: + - "historical-search-api/**" + +defaults: + run: + shell: bash + working-directory: ./historical-search-api + +env: + APP_NAME: "historical-search-api" + +jobs: + historical-search-api-cd-by-push: + runs-on: ubuntu-20.04 + + if: github.event_name == 'push' && github.repository == 'bcgov/foi-flow' + steps: + - uses: actions/checkout@v2 + - name: Set ENV variables + id: set-variable + run: | + if [ ${{ github.ref_name }} == 'dev' ]; then + echo "For ${{ github.ref_name }} branch" + echo "TOOLS_NAME=${{secrets.OPENSHIFT4_FRONTEND_REPOSITORY}}" >> $GITHUB_ENV + echo "TAG_NAME="dev"" >> $GITHUB_ENV + echo "BRANCH_NAME="dev"" >> $GITHUB_ENV + echo "ENV_NAME="dev"" >> $GITHUB_ENV + elif [ ${{ github.ref_name }} == 'dev-marshal' ]; then + echo "For ${{ github.ref_name }} branch" + echo "TOOLS_NAME=${{secrets.OPENSHIFT4_FRONTEND_REPOSITORY}}" >> $GITHUB_ENV + echo "TAG_NAME="dev-marshal"" >> $GITHUB_ENV + echo "BRANCH_NAME="dev-marshal"" >> $GITHUB_ENV + echo "ENV_NAME="dev"" >> $GITHUB_ENV + elif [ ${{ github.ref_name }} == 'test-marshal' ]; then + echo "For ${{ github.ref_name }} branch" + echo "TOOLS_NAME=${{secrets.OPENSHIFT4_FRONTEND_REPOSITORY}}" >> $GITHUB_ENV + echo "TAG_NAME="test-marshal"" >> $GITHUB_ENV + echo "BRANCH_NAME="test-marshal"" >> $GITHUB_ENV + echo "ENV_NAME="test"" >> $GITHUB_ENV + elif [ ${{ github.ref_name }} == 'dev-rook' ]; then + echo "For ${{ github.ref_name }} branch" + echo "TOOLS_NAME=${{secrets.OPENSHIFT4_FRONTEND_REPOSITORY}}" >> $GITHUB_ENV + echo "TAG_NAME="dev-rook"" >> $GITHUB_ENV + echo "BRANCH_NAME="dev-rook"" >> $GITHUB_ENV + echo "ENV_NAME="dev"" >> $GITHUB_ENV + elif [ ${{ github.ref_name }} == 'test-rook' ]; then + echo "For ${{ github.ref_name }} branch" + echo "TOOLS_NAME=${{secrets.OPENSHIFT4_FRONTEND_REPOSITORY}}" >> $GITHUB_ENV + echo "TAG_NAME="test-rook"" >> $GITHUB_ENV + echo "BRANCH_NAME="test-rook"" >> $GITHUB_ENV + echo "ENV_NAME="test"" >> $GITHUB_ENV + elif [ ${{ github.ref_name }} == 'main' ]; then + echo "For ${{ github.ref_name }} branch" + echo "TOOLS_NAME=${{secrets.OPENSHIFT4_FRONTEND_REPOSITORY}}" >> $GITHUB_ENV + echo "TAG_NAME="test"" >> $GITHUB_ENV + echo "BRANCH_NAME="main"" >> $GITHUB_ENV + echo "ENV_NAME="test"" >> $GITHUB_ENV + else + echo "For ${{ github.ref_name }} branch" + fi + shell: bash + + - name: Login Openshift + shell: bash + run: | + oc login --server=${{secrets.OPENSHIFT4_LOGIN_REGISTRY}} --token=${{secrets.OPENSHIFT4_SA_TOKEN}} + + - name: Tools project + shell: bash + run: | + oc project ${{ env.TOOLS_NAME }}-tools + + - name: Build from ${{ env.BRANCH_NAME }} branch + shell: bash + run: | + oc patch bc/${{ env.APP_NAME }}-build -p '{"spec":{"source":{"git":{"ref":"${{ env.BRANCH_NAME }}"}}}}' + + - name: Start Build Openshift + shell: bash + run: | + oc start-build ${{ env.APP_NAME }}-build --wait + + - name: Tag+Deploy for ${{ env.TAG_NAME }} + shell: bash + run: | + oc tag ${{ env.APP_NAME }}:latest ${{ env.APP_NAME }}:${{ env.TAG_NAME }} From ae4d6806dcc5167cde4b2e1af181261862d4b421 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 24 Jul 2024 11:00:24 -0700 Subject: [PATCH 22/33] add sorting and request type filter for historical search --- .../FOI/foiHistoricalSearchServices.js | 20 ++++++----- .../AdvancedSearch/DataGridAdvancedSearch.js | 34 +++++++++++++------ .../request_api/models/factRequestDetails.py | 20 ++++++++--- .../request_api/resources/historicalsearch.py | 1 + 4 files changed, 52 insertions(+), 23 deletions(-) diff --git a/forms-flow-web/src/apiManager/services/FOI/foiHistoricalSearchServices.js b/forms-flow-web/src/apiManager/services/FOI/foiHistoricalSearchServices.js index 9a9f19b55..3e2da3ac4 100644 --- a/forms-flow-web/src/apiManager/services/FOI/foiHistoricalSearchServices.js +++ b/forms-flow-web/src/apiManager/services/FOI/foiHistoricalSearchServices.js @@ -19,20 +19,24 @@ export const fetchHistoricalSearchData = ({ errorCallback, dispatch, }) => { - let sortingItems = []; - let sortingOrders = []; - sort.forEach((item) => { - sortingItems.push(item.field); - sortingOrders.push(item.sort); - }); + // let sortingItems = []; + // let sortingOrders = []; + // sort.forEach((item) => { + // sortingItems.push(item.field); + // sortingOrders.push(item.sort); + // }); + + if (sort[0].field === 'axisrequestid') { + sort[0].field = 'visualrequestfilenumber' + } httpGETRequest( API.FOI_HISTORICAL_SEARCH_API, { page: page, size: size, - sortingitem: sortingItems, - sortingorder: sortingOrders, + sortingitem: sort[0]['field'], + sortingorder: sort[0]['sort'], search: search, keywords: keywords, requestType: requestType, diff --git a/forms-flow-web/src/components/FOI/Dashboard/IAO/AdvancedSearch/DataGridAdvancedSearch.js b/forms-flow-web/src/components/FOI/Dashboard/IAO/AdvancedSearch/DataGridAdvancedSearch.js index f4ace3a36..34f339605 100644 --- a/forms-flow-web/src/components/FOI/Dashboard/IAO/AdvancedSearch/DataGridAdvancedSearch.js +++ b/forms-flow-web/src/components/FOI/Dashboard/IAO/AdvancedSearch/DataGridAdvancedSearch.js @@ -542,6 +542,12 @@ const DataGridAdvancedSearch = ({ userDetail }) => { ); } + const test = (model) => { + if (model.length > 0) { + setHistoricsearchSortModel(model) + } + } + return ( { onSortModelChange={(model) => setSortModel(model)} getRowClassName={(params) => clsx( - `super-app-theme--${params.row.currentState - .toLowerCase() - .replace(/ +/g, "")}`, + `super-app-theme--${params.row.currentState?.toLowerCase().replace(/ +/g, "")}`, tableInfo?.stateClassName?.[ - params.row.currentState.toLowerCase().replace(/ +/g, "") + params.row.currentState?.toLowerCase().replace(/ +/g, "") ] ) } @@ -627,24 +631,32 @@ const DataGridAdvancedSearch = ({ userDetail }) => { autoHeight className="foi-data-grid" getRowId={(row) => row.axisrequestid} - rows={searchHistoricalSearchResults || []} - columns={HistoricalSearchResultsColumns} + rows={searchHistoricalSearchResults?.results || []} + columns={historiccolumnsRef?.current} rowHeight={30} headerHeight={50} - rowCount={0} - pageSize={10} - rowsPerPageOptions={[10]} + rowCount={searchHistoricalSearchResults?.count || 0} + pageSize={historicrowsState.pageSize} + // rowsPerPageOptions={[10]} hideFooterSelectedRowCount={true} disableColumnMenu={true} pagination - //paginationMode="server" + paginationMode="server" initialState={{ pagination: historicrowsState + }} + onPageChange={(newPage) => sethistoricRowsState((prev) => ({ ...prev, page: newPage }))} + onPageSizeChange={(newpageSize) => + sethistoricRowsState((prev) => ({ ...prev, pageSize: newpageSize })) + } + components={{ + Footer: ()=> }} - sortingOrder={["desc", "asc"]} sortModel={[sortHistoricsearchSortModel[0]]} sortingMode={"server"} + onSortModelChange={test} + loading={searchHistoricalDataLoading} diff --git a/historical-search-api/request_api/models/factRequestDetails.py b/historical-search-api/request_api/models/factRequestDetails.py index 539a0738d..9fa711504 100644 --- a/historical-search-api/request_api/models/factRequestDetails.py +++ b/historical-search-api/request_api/models/factRequestDetails.py @@ -148,8 +148,10 @@ def getdescriptionhistorybyid(cls, requestid): @classmethod def getadvancedsearchresults(cls,isiaorestictedmanager:False, params): searchresults = [] + count = 0 try: - basequery = 'SELECT foirequestid \ + basequery = 'SELECT count(*) OVER() AS full_count\ + ,foirequestid \ ,requesttypename \ ,applicantname \ ,visualrequestfilenumber \ @@ -180,7 +182,14 @@ def getadvancedsearchresults(cls,isiaorestictedmanager:False, params): for keyword in params['keywords']: filterbysearchcondition.append("LOWER(visualrequestfilenumber) like LOWER('%{0}%')".format(keyword)) - conditioncount = len(filterbysearchcondition) + requesttypecondition = [] + if len(params['requesttype'] + params['requestflags']) > 0: + for requesttype in (params['requesttype'] + params['requestflags']): + if (requesttype == 'oipc'): requesttype = 'review' + requesttypecondition.append("LOWER(requesttypename) like '%{0}%'".format(requesttype)) + basequery+= (' (' +' OR '.join(requesttypecondition) + ')') + + conditioncount = len(filterbysearchcondition + requesttypecondition) for idx,searchcondition in enumerate(filterbysearchcondition): basequery+= ' {0} '.format(searchcondition) @@ -188,6 +197,8 @@ def getadvancedsearchresults(cls,isiaorestictedmanager:False, params): if(idx!=(conditioncount-1)): basequery+= ' AND ' + + if(conditioncount == 0): basequery+= "LOWER(description) like LOWER('%{0}%')".format(keyword) @@ -197,7 +208,7 @@ def getadvancedsearchresults(cls,isiaorestictedmanager:False, params): basequery+= ' ORDER BY {0} {1}'.format(params['sortingitem'],params['sortingorder']) if params['size'] is not None: - basequery+= ' LIMIT {0}'.format(params['size']) + basequery+= ' LIMIT {0} OFFSET {1}'.format(params['size'], (params['page'] - 1) * params['size'] ) else: basequery+= ' LIMIT 100' @@ -205,10 +216,11 @@ def getadvancedsearchresults(cls,isiaorestictedmanager:False, params): for row in rs: searchresults.append({"axisrequestid": row["visualrequestfilenumber"], "description": row["description"], "assignee": row["assignee"], "requeststatus": row["requeststatus"], "applicantname": row["applicantname"], "requesttype": row["requesttypename"],"receiveddate": row["receiveddate"],"oipcno": row["oipcno"]}) + count = row["full_count"] except Exception as ex: logging.error(ex) raise ex finally: db.session.close() - return searchresults + return {'results': searchresults, 'count': count} \ No newline at end of file diff --git a/historical-search-api/request_api/resources/historicalsearch.py b/historical-search-api/request_api/resources/historicalsearch.py index 6712296e6..988100b75 100644 --- a/historical-search-api/request_api/resources/historicalsearch.py +++ b/historical-search-api/request_api/resources/historicalsearch.py @@ -36,6 +36,7 @@ def get(): 'usertype': AuthHelper.getusertype(), 'groups': '', 'size': flask.request.args.get('size', DEFAULT_SIZE, type=int), + 'page': flask.request.args.get('page', DEFAULT_SIZE, type=int), 'sortingitem': flask.request.args.get('sortingitem'), 'sortingorder': flask.request.args.get('sortingorder'), From ab18bd1f7448addc3500d746e9e26659073d20b6 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 24 Jul 2024 11:25:10 -0700 Subject: [PATCH 23/33] minor bug fix for historical search sorting --- .../apiManager/services/FOI/foiHistoricalSearchServices.js | 4 ---- .../request_api/models/factRequestDetails.py | 5 +++++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/forms-flow-web/src/apiManager/services/FOI/foiHistoricalSearchServices.js b/forms-flow-web/src/apiManager/services/FOI/foiHistoricalSearchServices.js index 3e2da3ac4..7e6e5a00d 100644 --- a/forms-flow-web/src/apiManager/services/FOI/foiHistoricalSearchServices.js +++ b/forms-flow-web/src/apiManager/services/FOI/foiHistoricalSearchServices.js @@ -26,10 +26,6 @@ export const fetchHistoricalSearchData = ({ // sortingOrders.push(item.sort); // }); - if (sort[0].field === 'axisrequestid') { - sort[0].field = 'visualrequestfilenumber' - } - httpGETRequest( API.FOI_HISTORICAL_SEARCH_API, { diff --git a/historical-search-api/request_api/models/factRequestDetails.py b/historical-search-api/request_api/models/factRequestDetails.py index 9fa711504..662e928bf 100644 --- a/historical-search-api/request_api/models/factRequestDetails.py +++ b/historical-search-api/request_api/models/factRequestDetails.py @@ -205,6 +205,11 @@ def getadvancedsearchresults(cls,isiaorestictedmanager:False, params): if(isiaorestictedmanager == False): basequery+= " AND requesttypename NOT LIKE '%Restricted%'" + if params['sortingitem'] == 'axisrequestid': + params['sortingitem'] = 'visualrequestfilenumber' + elif params['sortingitem'] == 'requesttype': + params['sortingitem'] = 'requesttype' + basequery+= ' ORDER BY {0} {1}'.format(params['sortingitem'],params['sortingorder']) if params['size'] is not None: From 8b4db1145cc1d4ac602663ddb96e965523e07671 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 26 Jul 2024 16:18:54 -0700 Subject: [PATCH 24/33] fix navigation from historical search to details page --- .../AdvancedSearch/DataGridAdvancedSearch.js | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/forms-flow-web/src/components/FOI/Dashboard/IAO/AdvancedSearch/DataGridAdvancedSearch.js b/forms-flow-web/src/components/FOI/Dashboard/IAO/AdvancedSearch/DataGridAdvancedSearch.js index 34f339605..b7fd088bc 100644 --- a/forms-flow-web/src/components/FOI/Dashboard/IAO/AdvancedSearch/DataGridAdvancedSearch.js +++ b/forms-flow-web/src/components/FOI/Dashboard/IAO/AdvancedSearch/DataGridAdvancedSearch.js @@ -121,6 +121,15 @@ const DataGridAdvancedSearch = ({ userDetail }) => { ) }; + const historicalRenderCell = (params) => { + let link = "./historicalrequest/" + params.row.axisrequestid + return ( + +
{params.value}
+
+ ) + }; + const ProcessingTeamColumns = [ @@ -371,7 +380,7 @@ const DataGridAdvancedSearch = ({ userDetail }) => { field: "applicantname", headerName: "APPLICANT NAME", headerAlign: "left", - renderCell: hyperlinkRenderCell, + renderCell: historicalRenderCell, cellClassName: 'foi-advanced-search-result-cell', width: 180, }, @@ -379,7 +388,7 @@ const DataGridAdvancedSearch = ({ userDetail }) => { field: "requesttype", headerName: "REQUEST TYPE", headerAlign: "left", - renderCell: hyperlinkRenderCell, + renderCell: historicalRenderCell, cellClassName: 'foi-advanced-search-result-cell', flex: 1, }, @@ -387,7 +396,7 @@ const DataGridAdvancedSearch = ({ userDetail }) => { field: "axisrequestid", headerName: "ID NUMBER", headerAlign: "left", - renderCell: hyperlinkRenderCell, + renderCell: historicalRenderCell, cellClassName: 'foi-advanced-search-result-cell', flex: 1, }, @@ -395,7 +404,7 @@ const DataGridAdvancedSearch = ({ userDetail }) => { field: "oipcno", headerName: "OIPC no", headerAlign: "left", - renderCell: hyperlinkRenderCell, + renderCell: historicalRenderCell, cellClassName: 'foi-advanced-search-result-cell', flex: 1, }, @@ -403,7 +412,7 @@ const DataGridAdvancedSearch = ({ userDetail }) => { field: "assignee", headerName: "ASSIGNED TO", headerAlign: "left", - renderCell: hyperlinkRenderCell, + renderCell: historicalRenderCell, cellClassName: 'foi-advanced-search-result-cell', flex: 1, }, @@ -411,7 +420,7 @@ const DataGridAdvancedSearch = ({ userDetail }) => { field: "receiveddate", headerName: "RECEIVED DATE", headerAlign: "left", - renderCell: hyperlinkRenderCell, + renderCell: historicalRenderCell, cellClassName: 'foi-advanced-search-result-cell', flex: 1, }, @@ -578,7 +587,7 @@ const DataGridAdvancedSearch = ({ userDetail }) => { label={"Historical AXIS Results"} color="primary" size="small" - onClick={() => { advancedFilterChange("historicalsearchresults"); console.log(`Value of advanced search filter on historicalsearchresults is ${foiadvsearchfilter}`)}} + onClick={() => { advancedFilterChange("historicalsearchresults"); console.log(`Value of advanced search filter on advancedsearchresults is ${foiadvsearchfilter}`)}} clicked={foiadvsearchfilter === "historicalsearchresults"} /> From 39b0888e343f5c5a27e06ecff96ed32b159c4501 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 29 Jul 2024 13:30:19 -0700 Subject: [PATCH 25/33] add support for downloading historical records --- .../src/apiManager/endpoints/index.js | 1 + .../services/FOI/foiRecordServices.js | 35 ++++++++- .../components/FOI/FOIRequest/FOIRequest.js | 14 +++- .../FOI/customComponents/Attachments/index.js | 78 ++++++++++--------- .../FOI/customComponents/Records/index.js | 72 +++++++++-------- .../request_api/models/factRequestDetails.py | 2 +- .../request_api/resources/foirecord.py | 20 +++++ .../request_api/services/recordservice.py | 21 +++++ .../request_api/utils/enums.py | 1 + 9 files changed, 172 insertions(+), 72 deletions(-) diff --git a/forms-flow-web/src/apiManager/endpoints/index.js b/forms-flow-web/src/apiManager/endpoints/index.js index e86f45ff8..5a2b5ba05 100644 --- a/forms-flow-web/src/apiManager/endpoints/index.js +++ b/forms-flow-web/src/apiManager/endpoints/index.js @@ -54,6 +54,7 @@ const API = { FOI_HISTORICAL_REQUEST_API: `${FOI_HISTORICAL_API_URL}/api/foihistoricalrequest`, FOI_HISTORICAL_REQUEST_DESCRIPTION_API: `${FOI_HISTORICAL_API_URL}/api/foihistoricalrequest/descriptionhistory`, FOI_HISTORICAL_REQUEST_EXTENSIONS_API: `${FOI_HISTORICAL_API_URL}/api/foihistoricalrequest/extensions`, + FOI_HISTORICAL_RECORDS_API: `${FOI_BASE_API_URL}/api/foirecord/historical/`, FOI_HISTORICAL_SEARCH_API: `${FOI_HISTORICAL_API_URL}/api/advancedsearch`, FOI_GET_PROGRAMAREADIVISIONS: `${FOI_BASE_API_URL}/api/foiadmin/divisions`, diff --git a/forms-flow-web/src/apiManager/services/FOI/foiRecordServices.js b/forms-flow-web/src/apiManager/services/FOI/foiRecordServices.js index 6a7f60629..eae91f2b9 100644 --- a/forms-flow-web/src/apiManager/services/FOI/foiRecordServices.js +++ b/forms-flow-web/src/apiManager/services/FOI/foiRecordServices.js @@ -21,6 +21,8 @@ import { setFOIPDFStitchStatusForOipcRedlineReview, setFOIPDFStitchStatusForOipcRedline, setFOIPDFStitchedRecordForOipcRedline, + setRequestAttachments, + setFOIAttachmentListLoader, } from "../../../actions/FOI/foiRequestActions"; import { fnDone } from "./foiServicesUtil"; import UserService from "../../../services/UserService"; @@ -635,4 +637,35 @@ export const fetchPDFStitchedRecordForOIPCRedlineReview = ( done(error); }); }; - } \ No newline at end of file + } + + export const fetchHistoricalRecords = (axisRequestId, ...rest) => { + const done = fnDone(rest); + let apiUrl = replaceUrl(API.FOI_HISTORICAL_RECORDS_API, "", axisRequestId); + return (dispatch) => { + dispatch(setRecordsLoader("inprogress")); + httpGETRequest(apiUrl, {}, UserService.getToken()) + .then((res) => { + if (res.data) { + if (!res.data.records) { + dispatch(setRequestAttachments(res.data)); + dispatch(setFOIAttachmentListLoader(false)); + } else { + dispatch(setRequestRecords(res.data)); + dispatch(setRecordsLoader("completed")); + } + done(null, res.data); + } else { + console.log("Error in fetching historical records", res); + dispatch(serviceActionError(res)); + dispatch(setRecordsLoader("error")); + } + }) + .catch((error) => { + console.log("Error in fetching historical records", error); + dispatch(serviceActionError(error)); + dispatch(setRecordsLoader("error")); + done(error); + }); + }; + }; \ No newline at end of file diff --git a/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js b/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js index 74950f7e3..4326794e1 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js @@ -57,6 +57,7 @@ import { fetchPDFStitchStatusForResponsePackage, fetchPDFStitchedStatusForOIPCRedlineReview, fetchPDFStitchedStatusForOIPCRedline, + fetchHistoricalRecords, } from "../../../apiManager/services/FOI/foiRecordServices"; import { makeStyles } from "@material-ui/core/styles"; import FOI_COMPONENT_CONSTANTS from "../../../constants/FOI/foiComponentConstants"; @@ -160,7 +161,7 @@ const FOIRequest = React.memo(({ userDetail, openApplicantProfileModal }) => { "fee estimate - payment receipt", "response-onhold", "fee balance outstanding - payment receipt", - ].indexOf(attachment.category.toLowerCase()) === -1 + ].indexOf(attachment.category?.toLowerCase()) === -1 ); }) ); @@ -312,6 +313,7 @@ const FOIRequest = React.memo(({ userDetail, openApplicantProfileModal }) => { dispatch(fetchHistoricalRequestDetails(requestId)); dispatch(fetchFOIHistoricalRequestDescriptionList(requestId)); dispatch(fetchHistoricalExtensions(requestId)); + dispatch(fetchHistoricalRecords(requestId)) } else { await Promise.all([ dispatch(fetchFOIProgramAreaList()), @@ -1008,13 +1010,15 @@ const FOIRequest = React.memo(({ userDetail, openApplicantProfileModal }) => { ); const showRecordsTab = () => { + if (isHistoricalRequest) { + return requestAttachments.length === 0 + } return ( requestState !== StateEnum.intakeinprogress.name && requestState !== StateEnum.unopened.name && requestState !== StateEnum.open.name && (requestDetails?.divisions?.length > 0 || isMCFPersonal) && - DISABLE_GATHERINGRECORDS_TAB?.toLowerCase() =='false' && - !isHistoricalRequest + DISABLE_GATHERINGRECORDS_TAB?.toLowerCase() =='false' ); }; @@ -1449,6 +1453,7 @@ const FOIRequest = React.memo(({ userDetail, openApplicantProfileModal }) => { iaoassignedToList={iaoassignedToList} ministryAssignedToList={ministryAssignedToList} isMinistryCoordinator={false} + isHistoricalRequest={isHistoricalRequest} /> ) : ( @@ -1660,12 +1665,13 @@ const FOIRequest = React.memo(({ userDetail, openApplicantProfileModal }) => { iaoassignedToList={iaoassignedToList} ministryAssignedToList={ministryAssignedToList} isMinistryCoordinator={false} - bcgovcode={JSON.parse(bcgovcode)} + bcgovcode={isHistoricalRequest ? "" : JSON.parse(bcgovcode)} setRecordsUploading={setRecordsUploading} divisions={requestDetails.divisions} recordsTabSelect={tabLinksStatuses.Records.active} requestType={requestDetails?.requestType} handleSaveRequest={handleSaveRequest} + isHistoricalRequest={isHistoricalRequest} /> )} 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 c8a034b9d..9d19026e8 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Attachments/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/Attachments/index.js @@ -68,7 +68,8 @@ export const AttachmentSection = ({ bcgovcode, iaoassignedToList, ministryAssignedToList, - isMinistryCoordinator + isMinistryCoordinator, + isHistoricalRequest }) => { const classes = useStyles(); const [attachments, setAttachments] = useState(attachmentsArray) @@ -103,7 +104,7 @@ export const AttachmentSection = ({ const searchAttachments = (_attachments, _filterValue, _keywordValue) => { return _attachments.filter( attachment => { - let onecategory = getCategory(attachment.category.toLowerCase()); + let onecategory = getCategory(attachment.category?.toLowerCase()); return ( (_filterValue==="ALL"?true:onecategory.tags.includes(_filterValue?.toLowerCase())) && ( onecategory.tags.join('-').includes(_keywordValue?.toLowerCase()) @@ -243,7 +244,7 @@ export const AttachmentSection = ({ }) }); } - }, 'attachments', bcgovcode); + }, isHistoricalRequest ? 'historical' : 'attachments', bcgovcode); } const downloadAllDocuments = async () => { @@ -253,7 +254,8 @@ export const AttachmentSection = ({ let failed = 0; try { for (let attachment of attachmentsForDisplay) { - const response = await getFOIS3DocumentPreSignedUrl(attachment.documentpath.split('/').slice(4).join('/'), ministryId, dispatch, null, 'attachments', bcgovcode) + const category = isHistoricalRequest ? 'historical' : 'attachments' + const response = await getFOIS3DocumentPreSignedUrl(attachment.documentpath.split('/').slice(4).join('/'), ministryId, dispatch, null, category, bcgovcode) await getFileFromS3({filepath: response.data}, (_err, res) => { if (_err) { failed++ @@ -395,6 +397,7 @@ export const AttachmentSection = ({ isMinistryCoordinator={isMinistryCoordinator} ministryId={ministryId} classes={classes} + isHistoricalRequest={isHistoricalRequest} />); } @@ -439,14 +442,14 @@ export const AttachmentSection = ({
- + } { +const Attachment = React.memo(({indexValue, attachment, handlePopupButtonClick, getFullname, isMinistryCoordinator,ministryId, isHistoricalRequest}) => { const classes = useStyles(); @@ -591,6 +594,7 @@ const Attachment = React.memo(({indexValue, attachment, handlePopupButtonClick, disabled={disabled} reclassifyIsDisabled={reclassifyIsDisabled} ministryId={ministryId} + isHistoricalRequest={isHistoricalRequest} /> @@ -650,7 +654,7 @@ const opendocumentintab =(attachment,ministryId)=> window.open(url, '_blank').focus(); } -const AttachmentPopup = React.memo(({indexValue, attachment, handlePopupButtonClick, disabled, reclassifyIsDisabled, ministryId}) => { +const AttachmentPopup = React.memo(({indexValue, attachment, handlePopupButtonClick, disabled, reclassifyIsDisabled, ministryId, isHistoricalRequest}) => { const ref = React.useRef(); const closeTooltip = () => ref.current && ref ? ref.current.close():{}; @@ -707,11 +711,11 @@ const AttachmentPopup = React.memo(({indexValue, attachment, handlePopupButtonCl ] const showReplace = (category) => { - return transitionStates.includes(category.toLowerCase()); + return transitionStates.includes(category?.toLowerCase()); } const showDelete = (category) => { - return !emailCategories.includes(category.toLowerCase()); + return !emailCategories.includes(category?.toLowerCase()); } const [popoverOpen, setPopoverOpen] = useState(false); @@ -746,14 +750,15 @@ const AttachmentPopup = React.memo(({indexValue, attachment, handlePopupButtonCl } const AddMenuItems = () => { - if (showReplace(attachment.category)) + if (showReplace(attachment.category)) { return () - else if (!showDelete(attachment.category)) + } else if (!showDelete(attachment.category)) { return null; + } return () } - const ActionsPopover = ({RestrictViewInBrowser}) => { + const ActionsPopover = ({RestrictViewInBrowser, isHistoricalRequest}) => { return ( Download
- - { - handleReclassify(); - setPopoverOpen(false); - }} - disabled={reclassifyIsDisabled} - > - Reclassify - - - { - handleRename(); - setPopoverOpen(false); - }} - > - Rename - - {attachment.category === "personal" ? ( + {!isHistoricalRequest && + { + handleReclassify(); + setPopoverOpen(false); + }} + disabled={reclassifyIsDisabled} + > + Reclassify + + } + {!isHistoricalRequest && + + { + handleRename(); + setPopoverOpen(false); + }} + > + Rename + + } + {(attachment.category === "personal" || isHistoricalRequest) ? ( "" - ) : } + ) : } ); @@ -838,7 +846,7 @@ const AttachmentPopup = React.memo(({indexValue, attachment, handlePopupButtonCl > - 0 || attachment.documentpath.toLowerCase().indexOf('.msg') > 0 ? true:false }/> + 0 || attachment.documentpath.toLowerCase().indexOf('.msg') > 0 ? true:false } isHistoricalRequest={isHistoricalRequest}/> ); }) 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 72db8a79f..159e63a3b 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/index.js @@ -226,7 +226,8 @@ export const RecordsLog = ({ setRecordsUploading, recordsTabSelect, requestType, - handleSaveRequest + handleSaveRequest, + isHistoricalRequest, }) => { const user = useSelector((state) => state.user.userDetail); const userGroups = user?.groups?.map((group) => group.slice(1)); @@ -273,8 +274,7 @@ export const RecordsLog = ({ (state) => state.foiRequests.foiRequestDetail ); - const tagList = divisions - .filter((d) => d.divisionname.toLowerCase() !== "communications") + const tagList = divisions?.filter((d) => d.divisionname.toLowerCase() !== "communications") .map((division) => { return { name: division.divisionid, @@ -917,8 +917,8 @@ export const RecordsLog = ({ ); } }, - "records", - bcgovcode + isHistoricalRequest ? "historical" : "records", + isHistoricalRequest ? undefined : bcgovcode ); }; @@ -1031,8 +1031,8 @@ export const RecordsLog = ({ }); } }, - "records", - bcgovcode + isHistoricalRequest ? "historical" : "records", + isHistoricalRequest ? undefined : bcgovcode ); }; @@ -1245,8 +1245,8 @@ export const RecordsLog = ({ ministryId, dispatch, null, - "records", - bcgovcode + isHistoricalRequest ? "historical" : "records", + isHistoricalRequest ? undefined : bcgovcode ); await getFileFromS3({ filepath: response.data }, (_err, res) => { let blob = new Blob([res.data], { type: "application/octet-stream" }); @@ -1701,6 +1701,9 @@ export const RecordsLog = ({ } const isUpdateDivisionsDisabled = () => { + if (isHistoricalRequest) { + return true; + } var count = 0; var selectedDivision = new Set(); for (let record of records) { @@ -1716,7 +1719,7 @@ export const RecordsLog = ({ } else { let int = intersection( selectedDivision, - new Set(record.attributes.divisions.map((d) => d.divisionid)) + new Set(record.attributes.divisions?.map((d) => d.divisionid)) ); if (int.size === 0) { return true; @@ -1734,7 +1737,7 @@ export const RecordsLog = ({ // setDivisionToUpdate() if (isMCFPersonal && selectedDivision.size > 0) { - if (divisions.find((d) => selectedDivision.has(d.divisionid))) { + if (divisions?.find((d) => selectedDivision.has(d.divisionid))) { return !isMinistryCoordinator; } else { return isMinistryCoordinator; @@ -1750,7 +1753,7 @@ export const RecordsLog = ({ for (let record of records) { if (record.isselected) { if ( - record.attributes.divisions.length > 1 && + record.attributes.divisions?.length > 1 && record.attributes.divisions[0].divisionid !== filterValue ) { if (filterValue < 0) { @@ -1758,7 +1761,7 @@ export const RecordsLog = ({ "invalid filter value - need to select a single division" ); } - for (var i = 1; i < record.attributes.divisions.length; i++) { + for (var i = 1; i < record.attributes.divisions?.length; i++) { if (record.attributes.divisions[i].divisionid === filterValue) { let updateRecord = records.find( (r) => @@ -1972,6 +1975,7 @@ export const RecordsLog = ({ {(isMinistryCoordinator == false && records?.length > 0 && + !isHistoricalRequest && DISABLE_REDACT_WEBLINK?.toLowerCase() == "false" && ( - {hasDocumentsToDownload && ( + {hasDocumentsToDownload && !isHistoricalRequest && ( { - (isMinistryCoordinator || (isScanningTeamMember && + (!isHistoricalRequest && (isMinistryCoordinator || (isScanningTeamMember && MinistryNeedsScanning.includes(bcgovcode.replaceAll('"', "")) && requestType === - FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL)) && ( + FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_PERSONAL))) && (
Unopened IDDate ReceivedMinistry SelectedApplicant First NameApplicant Last NamePayment StatusReceipt NumberApplication FeePotential MatchesDescription
U-00''' + str(alert['request']['requestid']) + '''''' + alert['request']['requestrawdata']['receivedDate'] + '''''' - for m in alert['request']['requestrawdata']['ministry']['selectedMinistry']: - emailhtml += (m['code'] + ' ') - emailhtml += '''''' + alert['request']['requestrawdata']['contactInfo']['firstName'] + '''''' + alert['request']['requestrawdata']['contactInfo']['lastName'] + '''''' + alert['request']['paymentstatus'] + '''''' + alert['request']['txnno'] + '''''' + alert['request']['fee'] + ''' - ''' - for m in alert['potentialmatches']['matches']: - emailhtml += ((m['requestid'] or '') + " - similarity: " + str(m['similarity']*100) + "%
") - emailhtml = emailhtml[:-4] - emailhtml += '''
''' + alert['request']['requestrawdata']['descriptionTimeframe']['description'][0:99] + '''...