Skip to content

Commit

Permalink
Pesticide liaison workflow (#401)
Browse files Browse the repository at this point in the history
* changed position of sbc selection in add exam popup

* added location and invigilator select in the non-sbc exams

* Invite minimal (#370)

* Fix spinner
*Fix issues when Database pod goes down

* fix quicktrans issue

* posting exam with latest body

* Phase 3 (#368)

* This commit contains working and generally finished (all questions are asked in the right order for the correct process with correct required/minLength/numeric enforecements E&OE).
Both the capture Individual and the capture Group workflows are working.  For working on wiring to the API.

* Added create individual and group exams API calls, and status call to stream files

* Clean up directory

* Added download and transfer document to BCMP

* All changes for SBC Managed, individual pesticide exams

* Fix migration file

* Test

* Fixed bug with exam inventory

* Fixed bugs with examinee email and phone

* Finished everything for individual exams

* Cleanup for everything with group exams

Co-authored-by: Scott Rumsby <scott.rumsby@gmail.com>

* Update requirements temporarily

* changed position of sbc selection in add exam popup

* added location and invigilator select in the non-sbc exams

* posting exam with latest body

* saving group exam - inprogress

* group exam posting to bcmp

* individual and group exam in the proper format

- bcmp services desired format
- added group pesticide exam type to bootstrap

* ui issues for group exam modal fixed

* fixed banner showing after refreshing once exams added

* bug fixes for saving non-pesticide exams

* modified post exam function to separate bcmp request

* emergency fix added click once to check in button

* emergency fix added click once to submit button

* emergency fix added click once to submit button and added spinners to Submit, Delete and Check-In Functions

* emergency fix added click once to submit button, remove spinner logic

* emergency fix added click once to buttons on check in modal and add appointment

* separated api call for bcmp exam posting

* request exam button api integration

* Code to put spinner in Appointments, when selecting a service (#376)

* Added spinner to let user know not to double click

* Added spinner to let user know not to double click

Co-authored-by: ozamani <ozamani9@gmail.com>

* scheduling for group exam - ui

* new parameter added for identifying pesticide exams

* Update package-lock.json to fix vulnerabilities

* Put in code to handle double clicks, turn on/off spinner

* Update the version.

* Revert package-lock.json, see if works in IE.

* ui changes for add invigilator to group exam modal

* add invigilator group exam modal submit

* exam download as pdf

* download an updated received date exam

* Mender update (#379)

* Update Raspberry Pi Image
* Update with Nightly Reboots
* Updates for Raspbian Buster Lite
* Update Python libraries
* Update web-service script
* Update RFKill to unblock wifi
* Update WebService
* Fix issue with X11
* Clean deps, update launch sequence, fix loading chromium
* Update Docker Version of Ubuntu to use new QEMU
* Update Mender Artifact Version
* More updates for Video Rendering
* Fix pkill statement
* Fix Server - Sizing issue and Image Display resizing Issue
* Update RPI Image with Reboot on Freeze
* Additional Changes to accommodate patching
* Update Documentation

* upload exam api changes

* upload exam modal changes

* $ sign if exam fee not paid but printed

* disable submit for pesticide till job id is generated

* offsite invigilators rest added

* pesticide offsite invigilators list added

* updated minio url expiry to 7 days

* email to offsite invigilators after creating exam

* return exam if uploaded

* Put in logging for debugging session errors

* receipt send status in the edit exam modal

* awaiting receipt filter

* awaiting upload filter

* UI for pesticide final step

* Use websocket transport for smartboard (#381)

* Use websocket transport for smartboard

* Make board-socket socket initialization same as Socket.vue code

* event id from bcmp job id

* exam_type variable issue fixed

* db migration for new columns

* job id issue fixed

* Update requirements.txt

* Update exam_bcmp.py

* replaced flask-restplus

* updated config file

* clear data in modal, group exam, hide add shadow invigilator

* bug fixes

* fixed exam date change issue

* bcmp base url to env

* non sbc table status correction

* await upload filter fix

* pest exam date default days corrected

* expiry date invalid fix

* bcmp to env

* bcmp manual job id breaking issue fixed

* loading for await upload

* exam not ready message persisting issue

* filter fix

* indication for upload

* loading and disable request exam btn

* event id editable for pest exams

* invigilator id edit fix

* updated minio to baseconfig

* filter fixes

* flask_marshmallow to 0.11.0

Co-authored-by: ozamani9 <ozamani9@gmail.com>
Co-authored-by: jhmccoll <32721058+jhmccoll@users.noreply.github.com>
Co-authored-by: sjrumsby <sjrumsby@users.noreply.github.com>
Co-authored-by: Scott Rumsby <scott.rumsby@gmail.com>
Co-authored-by: Karim Gillani <karim.gillani@gov.bc.ca>
Co-authored-by: ChrisDMac <33269946+ChrisDMac@users.noreply.github.com>
Co-authored-by: Chris <Chris.McIntosh@gov.bc.ca>
  • Loading branch information
8 people authored May 19, 2020
1 parent 46a0ce7 commit b9ed864
Show file tree
Hide file tree
Showing 766 changed files with 2,554 additions and 105,715 deletions.
2 changes: 2 additions & 0 deletions api/app/models/bookings/exam.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ class Exam(Base):
payee_email = db.Column(db.String(50), nullable=True)
payee_name = db.Column(db.String(50), nullable=True)
payee_phone = db.Column(db.String(50), nullable=True)
candidates_list = db.Column(db.JSON, nullable=True)
is_pesticide = db.Column(db.Integer, nullable=True, default=0)

booking = db.relationship("Booking")
exam_type = db.relationship("ExamType")
Expand Down
14 changes: 13 additions & 1 deletion api/app/resources/bookings/booking/booking_post.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,23 @@ def post(self):
db.session.add(booking)
db.session.commit()

else:
elif type(i_id) == int:

booking.invigilators.append(Invigilator.query.filter_by(invigilator_id=i_id).first_or_404())
db.session.add(booking)
db.session.commit()

elif type(i_id) == list:

if len(i_id) == 0:
db.session.add(booking)
db.session.commit()

else:
for value in i_id:
booking.invigilators.append(Invigilator.query.filter_by(invigilator_id=value).first_or_404())
db.session.add(booking)
db.session.commit()

result = self.booking_schema.dump(booking)

Expand Down
80 changes: 80 additions & 0 deletions api/app/resources/bookings/exam/exam_bcmp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
'''Copyright 2018 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 logging
import copy
import json
from flask import request, g
from flask_restx import Resource
from app.models.theq import CSR, Office
from app.models.bookings import ExamType, Invigilator
from app.schemas.bookings import ExamSchema, CandidateSchema
from qsystem import api, api_call_with_retry, db, oidc
from app.utilities.bcmp_service import BCMPService

from app.resources.bookings.exam.exam_post import ExamPost

@api.route("/exams/bcmp/", methods=["POST"])
class ExamBcmpPost(Resource):

exam_schema = ExamSchema()
bcmp_service = BCMPService()

@oidc.accept_token(require_token=True)
@api_call_with_retry
def post(self):

csr = CSR.find_by_username(g.oidc_token_info['username'])

json_data = request.get_json()

exam, warning = self.exam_schema.load(json_data)

print("json_data: ")
print(json_data)

if warning:
logging.warning("WARNING: %s", warning)
return {"message": warning}, 422

if not (exam.office_id == csr.office_id or csr.liaison_designate == 1):
return {"The Exam Office ID and CSR Office ID do not match!"}, 403

formatted_data = ExamPost.format_data(self, json_data, exam)
exam = formatted_data["exam"]

invigilator = None
if exam.invigilator_id:
invigilator = Invigilator.query.filter_by(invigilator_id=exam.invigilator_id).first()

bcmp_response = None
if json_data["ind_or_group"] == "individual":

exam_fees = json_data["fees"]

logging.info("Creating individual pesticide exam")
bcmp_response = self.bcmp_service.create_individual_exam(exam, exam_fees, invigilator, formatted_data["pesticide_office"], g.oidc_token_info)

else:

logging.info("Creating Group pesticide exam")
bcmp_response = self.bcmp_service.create_group_exam_bcmp(exam, formatted_data["candidates_list_bcmp"], invigilator, formatted_data["pesticide_office"], g.oidc_token_info)


if bcmp_response:
return {"bcmp_job_id": bcmp_response['jobId'],
"errors": {}}, 201
else:
return {"message": "create_group_exam_bcmp failed",
"error": bcmp_response}, 403
34 changes: 30 additions & 4 deletions api/app/resources/bookings/exam/exam_bulk_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,54 @@
limitations under the License.'''

import logging
import json
from flask import g
from flask_restplus import Resource
from flask_restx import Resource
from sqlalchemy import exc
from app.models.bookings import Exam
from app.schemas.bookings import ExamSchema
from app.models.theq import CSR
from app.utilities.bcmp_service import BCMPService
from qsystem import api, oidc
from qsystem import api, oidc, db


@api.route("/exams/bcmp_status/", methods=["POST"])
class ExamList(Resource):
bcmp_service = BCMPService()
exam_schema = ExamSchema()

@oidc.accept_token(require_token=True)
def post(self):
csr = CSR.find_by_username(g.oidc_token_info['username'])

try:
exams = Exam.query.filter_by(upload_received_ind=0).filter(Exam.bcmp_job_id.isnot(None))
self.bcmp_service.bulk_check_exam_status(exams)
bcmp_response = self.bcmp_service.bulk_check_exam_status(exams)

return {}, 200
job_ids = []
for job in bcmp_response["jobs"]:
if job["jobStatus"] == "RESPONSE_UPLOADED":
job_ids.append(job["jobId"])

print("job_ids to update: ")
print(job_ids)

exams_tobe_updated = None

if len(job_ids) != 0:
exams_tobe_updated = Exam.query.filter(Exam.bcmp_job_id.in_(job_ids))

for exam in exams_tobe_updated:
exam_upd, warn = self.exam_schema.load({'upload_received_ind': 1}, instance=exam, partial=True)
db.session.add(exam_upd)

try:
db.session.commit()
except:
db.session.rollback()
raise

return {"exams_updated": exams_tobe_updated}, 200

except exc.SQLAlchemyError as error:
logging.error(error, exc_info=True)
Expand Down
12 changes: 6 additions & 6 deletions api/app/resources/bookings/exam/exam_download.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
limitations under the License.'''

from flask import g, Response
from flask_restplus import Resource
from flask_restx import Resource
import io
import logging
import urllib
Expand All @@ -40,11 +40,11 @@ def get(self, exam_id):
if not (exam.office_id == csr.office_id or csr.liaison_designate == 1):
return {"The Exam Office ID and CSR Office ID do not match!"}, 403

status = self.bcmp_service.check_exam_status(exam)
print(status)
job = self.bcmp_service.check_exam_status(exam)
print(job)

if status == 'PACKAGE_GENERATED':
package_url = status["jobProperties"]["EXAM_PACKAGE_URL"]
if job['jobStatus'] == 'PACKAGE_GENERATED':
package_url = job["jobProperties"]["EXAM_PACKAGE_URL"]
req = urllib.request.Request(package_url)
response = urllib.request.urlopen(req).read()
exam_file = io.BytesIO(response)
Expand All @@ -58,7 +58,7 @@ def get(self, exam_id):
"Content-Type": "application/pdf"
})
else:
return {'message': 'API is down'}, 400
return {'message': 'Package not yet generated', 'status': job['jobStatus']}, 400
# test_url = 'http://www.pdf995.com/samples/pdf.pdf'
# req = urllib.request.Request(test_url)
# response = urllib.request.urlopen(req).read()
Expand Down
2 changes: 1 addition & 1 deletion api/app/resources/bookings/exam/exam_email_invigilator.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
limitations under the License.'''

from flask import g, request
from flask_restplus import Resource
from flask_restx import Resource
import logging
from sqlalchemy import exc
from app.models.theq import CSR
Expand Down
120 changes: 85 additions & 35 deletions api/app/resources/bookings/exam/exam_post.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,17 @@
limitations under the License.'''

import logging
import copy
import json
from flask import request, g
from flask_restx import Resource
from app.models.theq import CSR, Office
from flask_restplus import Resource
from app.models.bookings import ExamType
from app.schemas.bookings import ExamSchema
from flask_restx import Resource
from app.models.bookings import ExamType, Invigilator
from app.schemas.bookings import ExamSchema, CandidateSchema
from qsystem import api, api_call_with_retry, db, oidc
from app.utilities.bcmp_service import BCMPService


@api.route("/exams/", methods=["POST"])
class ExamPost(Resource):

Expand All @@ -33,46 +34,34 @@ class ExamPost(Resource):
@api_call_with_retry
def post(self):

is_bcmp_req = True if request.args.get('bcmp_pesticide') else False

print("is_bcmp_req: ")
print(is_bcmp_req)

csr = CSR.find_by_username(g.oidc_token_info['username'])

json_data = request.get_json()

exam, warning = self.exam_schema.load(json_data)

print("json_data: ")
print(json_data)

if warning:
logging.warning("WARNING: %s", warning)
return {"message": warning}, 422
print("+=+=+=+= NAME: %s +=+=+=+=" % exam.examinee_name)

exam_type = ExamType.query.filter_by(exam_type_id=exam.exam_type_id).first()

if not exam_type:
exam_type = ExamType.query.filter_by(pesticide_exam_ind=1, group_exam_ind=1).first()
exam.exam_type = exam_type

if exam_type.pesticide_exam_ind:
if not exam_type.group_exam_ind:
logging.info("Create BCMP exam since this is a pesticide exam")

if json_data["sbc_managed"] != "sbc":
print("Setting non-SBC shit")
pesticide_office = Office.query.filter_by(office_name="Pesticide Offsite").first()
exam.office_id = pesticide_office.office_id

if exam_type.group_exam_ind:
logging.info("Creating group pesticide exam")
bcmp_response = self.bcmp_service.create_group_exam(exam)
else:
logging.info("Creating individual pesticide exam")
bcmp_response = self.bcmp_service.create_individual_exam(exam, exam_type)

if bcmp_response:
exam.bcmp_job_id = bcmp_response['jobId']
else:
print("Do the group exam shit here")
else:
if not (exam.office_id == csr.office_id or csr.liaison_designate == 1):
return {"The Exam Office ID and CSR Office ID do not match!"}, 403

if not (exam.office_id == csr.office_id or csr.liaison_designate == 1):
return {"The Exam Office ID and CSR Office ID do not match!"}, 403

if exam.is_pesticide:
formatted_data = self.format_data(json_data, exam)
exam = formatted_data["exam"]
job = self.bcmp_service.check_exam_status(exam)
print(job)
if job and job['jobProperties'] and job['jobProperties']['JOB_ID']:
exam.event_id = job['jobProperties']['JOB_ID']

db.session.add(exam)
db.session.commit()
Expand All @@ -81,3 +70,64 @@ def post(self):

return {"exam": result.data,
"errors": result.errors}, 201



## formating data to save on bcmp
def format_data(self, json_data, exam):

candidates_list_bcmp = []

pesticide_office = None
if json_data["sbc_managed"] == "sbc":
pesticide_office = Office.query.filter_by(office_id=exam.office_id).first()
else:
pesticide_office = Office.query.filter_by(office_name="Pesticide Offsite").first()
exam.office_id = pesticide_office.office_id

if json_data["ind_or_group"] == "individual":

exam_type = ExamType.query.filter_by(exam_type_id=exam.exam_type_id).first()

if not exam_type:
exam_type = ExamType.query.filter_by(pesticide_exam_ind=1, group_exam_ind=1).first()
exam.exam_type = exam_type

else:
logging.info("For Group Exams")

exam_type = ExamType.query.filter_by(exam_type_name="Group Pesticide Exam").first()
if exam_type:
exam.exam_type_id = exam_type.exam_type_id
exam.exam_type = exam_type

if json_data["candidates"]:
candidates = json_data["candidates"]
candidates_list = []
for candidate in candidates:
candidate_temp = {}
candidate_temp["examinee_name"] = candidate["name"]
candidate_temp["examinee_email"] = candidate["email"]
candidate_temp["exam_type_id"] = candidate["exam_type_id"]
candidate_temp["fees"] = candidate["fees"]
candidate_temp["payee_ind"] = 1 if (candidate["billTo"] == "candidate") else 0
candidate_temp["receipt"] = candidate["receipt"]
candidate_temp["receipt_number"] = candidate["receipt"]
candidate_temp["payee_name"] = candidate["payeeName"]
candidate_temp["payee_email"] = candidate["payeeEmail"]
candidates_list.append(candidate_temp)
# for bcmp service
candidates_bcmp = copy.deepcopy(candidate_temp)
exam_type = ExamType.query.filter_by(exam_type_id=candidate["exam_type_id"]).first()
if exam_type.exam_type_name:
candidates_bcmp["exam_type"] = exam_type.exam_type_name
candidates_list_bcmp.append(candidates_bcmp)

exam.candidates_list = candidates_list

return {
'exam': exam,
'candidates_list_bcmp': candidates_list_bcmp,
'pesticide_office': pesticide_office,
}

12 changes: 9 additions & 3 deletions api/app/resources/bookings/exam/exam_transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
limitations under the License.'''

from flask import g
from flask_restplus import Resource
from flask_restx import Resource
import logging
from sqlalchemy import exc
from app.models.theq import CSR
Expand All @@ -37,8 +37,14 @@ def post(self, exam_id):
if not (exam.office_id == csr.office_id or csr.liaison_designate == 1):
return {"The Exam Office ID and CSR Office ID do not match!"}, 403

self.bcmp_service.send_exam_to_bcmp(exam)
return {}
bcmp_response = self.bcmp_service.send_exam_to_bcmp(exam)

if bcmp_response:
return {"bcmp": bcmp_response,
"errors": {}}, 202
else:
return {"message": "create_group_exam_bcmp failed",
"error": bcmp_response}, 403

except exc.SQLAlchemyError as error:
logging.error(error, exc_info=True)
Expand Down
Loading

0 comments on commit b9ed864

Please sign in to comment.