Beispiel #1
0
    def put(self, mine_guid, variance_guid):
        parser = CustomReqparser()
        # Arguments required by MineDocument
        parser.add_argument('document_name', type=str, required=True)
        parser.add_argument('document_manager_guid', type=str, required=True)
        parser.add_argument('variance_document_category_code', type=str, required=True)

        variance = Variance.find_by_variance_guid(variance_guid)

        if not variance:
            raise NotFound('Unable to fetch variance.')

        data = parser.parse_args()
        document_name = data.get('document_name')
        document_manager_guid = data.get('document_manager_guid')

        # Register new file upload
        mine_doc = MineDocument(mine_guid=mine_guid,
                                document_manager_guid=document_manager_guid,
                                document_name=document_name)

        if not mine_doc:
            raise BadRequest('Unable to register uploaded file as document')

        # Associate Variance & MineDocument to create Variance Document
        # Add fields specific to Variance Documents
        mine_doc.save()
        variance_doc = VarianceDocumentXref(
            mine_document_guid=mine_doc.mine_document_guid,
            variance_id=variance.variance_id,
            variance_document_category_code=data.get('variance_document_category_code'))

        variance.documents.append(variance_doc)
        variance.save()
        return variance
class NOWApplicationImportResource(Resource, UserMixin):
    parser = CustomReqparser()
    parser.add_argument('mine_guid',
                        type=str,
                        help='guid of the mine.',
                        required=True)

    @requires_role_edit_permit
    @api.expect(parser)
    def post(self, application_guid):
        data = self.parser.parse_args()
        mine_guid = data.get('mine_guid')
        mine = Mine.find_by_mine_guid(mine_guid)
        if not mine:
            raise NotFound('Mine not found')

        now_application_identity = NOWApplicationIdentity.query.filter_by(
            now_application_guid=application_guid).first()
        if not now_application_identity:
            raise NotFound('No identity record for this application guid.')

        application = transmogrify_now(now_application_identity)
        application.mine_guid = mine_guid
        application.now_application_guid = application_guid
        application.save()

        return {'now_application_guid': str(application.now_application_guid)}
Beispiel #3
0
class PartyOrgBookEntityListResource(Resource, UserMixin):
    parser = CustomReqparser()
    parser.add_argument(
        'credential_id',
        type=int,
        help=
        'The latest credential ID of the OrgBook entity to associate the party with.',
        required=True)

    @api.expect(parser)
    @api.doc(description='Create a Party OrgBook Entity.')
    @requires_role_edit_party
    @api.marshal_with(PARTY_ORGBOOK_ENTITY, code=201)
    def post(self, party_guid):
        party = Party.find_by_party_guid(party_guid)
        if party is None:
            raise NotFound('Party not found.')

        if PartyOrgBookEntity.find_by_party_guid(party_guid) is not None:
            raise BadRequest(
                'This party is already associated with an OrgBook entity.')

        data = PartyOrgBookEntityListResource.parser.parse_args()
        credential_id = data.get('credential_id')

        if PartyOrgBookEntity.find_by_credential_id(credential_id) is not None:
            raise BadRequest(
                'An OrgBook entity with the provided credential ID already exists.'
            )

        resp = OrgBookService.get_credential(credential_id)
        if resp.status_code != requests.codes.ok:
            raise BadGateway(
                f'OrgBook API responded with {resp.status_code}: {resp.reason}'
            )

        try:
            credential = json.loads(resp.text)
            registration_id = credential['topic']['source_id']
            registration_status = not (credential['inactive'])
            registration_date = credential['effective_date']
            name_id = credential['names'][0]['id']
            name_text = credential['names'][0]['text']
        except:
            raise BadGateway('OrgBook API responded with unexpected data.')

        party_orgbook_entity = PartyOrgBookEntity.create(
            registration_id, registration_status, registration_date, name_id,
            name_text, credential_id, party_guid)
        if not party_orgbook_entity:
            raise InternalServerError(
                'Failed to create the Party OrgBook Entity.')

        party_orgbook_entity.save()

        party.party_name = name_text
        party.save()

        return party_orgbook_entity, 201
class NOWApplicationDocumentGenerateResource(Resource, UserMixin):
    parser = CustomReqparser()
    parser.add_argument('now_application_guid',
                        type=str,
                        location='json',
                        required=True)
    parser.add_argument('template_data',
                        type=dict,
                        location='json',
                        required=True)

    @api.doc(
        description=
        'Generates the specified document for the NoW using the provided template data and issues a one-time token that is used to download the document.',
        params={
            'document_type_code':
            'The code indicating the type of document to generate.'
        })
    @api.marshal_with(NOW_DOCUMENT_DOWNLOAD_TOKEN_MODEL, code=200)
    @requires_role_edit_permit
    def post(self, document_type_code):
        document_type = NOWApplicationDocumentType.query.get(
            document_type_code)
        if not document_type:
            raise NotFound('Document type not found')

        if not document_type.document_template:
            raise BadRequest(f'Cannot generate a {document_type.description}')

        data = self.parser.parse_args()
        template_data = data['template_data']

        ##ENFORCE READ-ONLY CONTEXT DATA
        enforced_data = [
            x for x in document_type.document_template._form_spec_with_context(
                data['now_application_guid']) if x.get('read-only', False)
        ]
        for enforced_item in enforced_data:
            if template_data.get(
                    enforced_item['id']) != enforced_item['context-value']:
                current_app.logger.debug(
                    f'OVERWRITING ENFORCED key={enforced_item["id"]}, value={template_data.get(enforced_item["id"])} -> {enforced_item["context-value"]}'
                )
            template_data[enforced_item['id']] = enforced_item['context-value']

        token = uuid.uuid4()
        # For now, we don't have a "proper" means of authorizing communication between our microservices, so this temporary solution
        # has been put in place to authorize with the document manager (pass the authorization headers into the token and re-use them
        # later). A ticket (MDS-2744) to set something else up as been created.
        cache.set(
            NOW_DOCUMENT_DOWNLOAD_TOKEN(token), {
                'document_type_code': document_type_code,
                'now_application_guid': data['now_application_guid'],
                'template_data': template_data,
                'username': User().get_user_username(),
                'authorization_header': request.headers['Authorization']
            }, TIMEOUT_5_MINUTES)

        return {'token': token}
    def put(self, mine_guid, mine_party_appt_guid):
        parser = CustomReqparser()
        # Arguments required by MineDocument
        parser.add_argument('document_name', type=str, required=True)
        parser.add_argument('document_manager_guid', type=str, required=True)

        mine_party_appt = MinePartyAppointment.find_by_mine_party_appt_guid(
            mine_party_appt_guid)

        if not mine_party_appt:
            raise NotFound('Mine Party Appointment.')

        data = parser.parse_args()
        document_name = data.get('document_name')
        document_manager_guid = data.get('document_manager_guid')

        # Register new file upload
        mine_doc = MineDocument(mine_guid=mine_guid,
                                document_manager_guid=document_manager_guid,
                                document_name=document_name)

        if not mine_doc:
            raise BadRequest('Unable to register uploaded file as document')

        mine_doc.save()
        mine_party_appt.documents.append(mine_doc)
        mine_party_appt.save()
        return mine_party_appt
    def put(self, mine_guid, variance_guid):
        parser = CustomReqparser()
        # Arguments required by MineDocument
        parser.add_argument('document_name', type=str, required=True)
        parser.add_argument('document_manager_guid', type=str, required=True)

        variance = Variance.find_by_variance_guid(variance_guid)

        if not variance:
            raise NotFound('Unable to fetch variance.')

        data = parser.parse_args()
        document_name = data.get('document_name')
        document_manager_guid = data.get('document_manager_guid')

        # Register new file upload
        mine_doc = MineDocument(mine_guid=mine_guid,
                                document_manager_guid=document_manager_guid,
                                document_name=document_name)

        if not mine_doc:
            raise BadRequest('Unable to register uploaded file as document')

        variance.mine_documents.append(mine_doc)
        variance.save()
        return variance
Beispiel #7
0
class NOWApplicationDocumentGenerateResource(Resource, UserMixin):
    parser = CustomReqparser()
    parser.add_argument('now_application_guid',
                        type=str,
                        location='json',
                        required=True)
    parser.add_argument('template_data',
                        type=dict,
                        location='json',
                        required=True)

    @api.doc(
        description=
        'Generates the specified document for the NoW using the provided template data and issues a one-time token that is used to download the document.',
        params={
            'document_type_code':
            'The code indicating the type of document to generate.'
        })
    @api.marshal_with(NOW_DOCUMENT_DOWNLOAD_TOKEN_MODEL, code=200)
    @requires_role_edit_permit
    def post(self, document_type_code):
        document_type = NOWApplicationDocumentType.query.get(
            document_type_code)
        if not document_type:
            raise NotFound('Document type not found')

        if not document_type.document_template:
            raise BadRequest(f'Cannot generate a {document_type.description}')

        # TODO: Generate document using the provided data.
        data = self.parser.parse_args()
        template_data = data['template_data']

        ##ENFORCE READ-ONLY CONTEXT DATA
        enforced_data = [
            x for x in document_type.document_template._form_spec_with_context(
                data['now_application_guid']) if x.get('read-only', False)
        ]
        for enforced_item in enforced_data:
            if template_data.get(
                    enforced_item['id']) != enforced_item['context-value']:
                current_app.logger.debug(
                    f'OVERWRITING ENFORCED key={enforced_item["id"]}, value={template_data.get(enforced_item["id"])} -> {enforced_item["context-value"]}'
                )
            template_data[enforced_item['id']] = enforced_item['context-value']

        token = uuid.uuid4()
        cache.set(
            NOW_DOCUMENT_DOWNLOAD_TOKEN(token), {
                'document_type_code': document_type_code,
                'now_application_guid': data['now_application_guid'],
                'template_data': template_data
            }, TIMEOUT_5_MINUTES)

        return {'token': token}
Beispiel #8
0
class NOWApplicationImportResource(Resource, UserMixin):
    parser = CustomReqparser()
    parser.add_argument('mine_guid',
                        type=str,
                        help='guid of the mine.',
                        required=True)
    parser.add_argument('longitude',
                        type=lambda x: Decimal(x) if x else None,
                        help='Longitude point for the Notice of Work.',
                        location='json')
    parser.add_argument('latitude',
                        type=lambda x: Decimal(x) if x else None,
                        help='Latitude point for the Notice of Work.',
                        location='json')

    @requires_role_edit_permit
    @api.expect(parser)
    def post(self, application_guid):
        data = self.parser.parse_args()
        mine_guid = data.get('mine_guid')
        latitude = data.get('latitude')
        longitude = data.get('longitude')
        mine = Mine.find_by_mine_guid(mine_guid)
        if not mine:
            raise NotFound('Mine not found')

        now_application_identity = NOWApplicationIdentity.query.filter_by(
            now_application_guid=application_guid).first()
        if not now_application_identity:
            raise NotFound('No identity record for this application guid.')

        application = transmogrify_now(now_application_identity)
        application.mine_guid = mine_guid
        application.latitude = latitude
        application.longitude = longitude
        application.now_application_guid = application_guid

        # This is a first pass but by no means exhaustive solution to preventing the now application from being saved more than once.
        # In the event of multiple requests being fired simultaneously this can still sometimes fail.
        db.session.refresh(now_application_identity)
        if now_application_identity.now_application_id is not None:
            raise BadRequest('This record has already been imported.')
        application.save()
        db.session.refresh(now_application_identity)
        NROSNOWStatusService.nros_now_status_update(
            now_application_identity.now_number,
            now_application_identity.now_application.status.description,
            now_application_identity.now_application.status_updated_date.
            strftime("%Y-%m-%dT%H:%M:%S"))

        return {'now_application_guid': str(application.now_application_guid)}
Beispiel #9
0
class MineReportCommentResource(Resource, UserMixin):
    parser = CustomReqparser()
    parser.add_argument('report_comment', type=str, location='json')
    parser.add_argument('comment_visibility_ind',
                        type=inputs.boolean,
                        location='json')

    @api.expect(MINE_REPORT_COMMENT_MODEL)
    @api.doc(description='update a comment')
    @api.marshal_with(MINE_REPORT_COMMENT_MODEL, code=201)
    @requires_role_edit_report
    def put(self, mine_guid, mine_report_guid, mine_report_comment_guid=None):

        data = self.parser.parse_args()
        comment = MineReportComment.find_by_guid(mine_report_comment_guid)
        if not comment:
            raise NotFound(
                'Mine report comment with guid "{mine_report_comment_guid}" not found.'
            )

        current_app.logger.info(f'Updating {comment} with {data}')
        for key, value in data.items():
            setattr(comment, key, value)

        comment.save()

        return comment, 201

    @api.doc(
        description='Delete a mine report comment by guid',
        params={'mine_report_comment_guid': 'guid of the comment to delete.'})
    @requires_role_mine_admin
    def delete(self, mine_guid, mine_report_guid, mine_report_comment_guid):
        if mine_report_comment_guid is None:
            return self.create_error_payload(
                404, 'Must provide a mine report comment guid.'), 404

        comment = MineReportComment.find_by_guid(mine_report_comment_guid)
        if not comment:
            raise NotFound(
                'Mine report comment with guid "{mine_report_comment_guid}" not found.'
            )

        comment.deleted_ind = True
        current_app.logger.info(f'Deleting {comment}')

        comment.save()

        return ('', 204)
Beispiel #10
0
    def put(self, mine_guid, mine_incident_guid, mine_document_guid):
        parser = CustomReqparser()
        parser.add_argument('filename', type=str, required=True)
        parser.add_argument('document_manager_guid', type=str, required=True)
        parser.add_argument('mine_incident_document_type',
                            type=str,
                            required=True)

        mine_incident = MineIncident.find_by_mine_incident_guid(
            mine_incident_guid)
        mine = Mine.find_by_mine_guid(mine_guid)

        if not mine_incident:
            raise NotFound('Mine incident not found')
        if not mine:
            raise NotFound('Mine not found.')

        data = parser.parse_args()
        document_manager_guid = data.get('document_manager_guid')
        file_name = data.get('filename')
        mine_incident_document_type = data.get('mine_incident_document_type')

        mine_doc = MineDocument(mine_guid=mine.mine_guid,
                                document_name=file_name,
                                document_manager_guid=document_manager_guid)

        if not mine_doc:
            raise BadRequest('Unable to register uploaded file as document')

        mine_doc.save()
        mine_incident_doc = MineIncidentDocumentXref(
            mine_document_guid=mine_doc.mine_document_guid,
            mine_incident_id=mine_incident.mine_incident_id,
            mine_incident_document_type_code=mine_incident_document_type
            if mine_incident_document_type else 'INI')

        mine_incident.documents.append(mine_incident_doc)
        mine_incident.save()

        return mine_incident
Beispiel #11
0
class MineCommentListResource(Resource, UserMixin):
    parser = CustomReqparser()
    parser.add_argument('mine_comment', type=str, location='json')

    @api.doc(description='Retrive a list of comments for a mine')
    @api.marshal_with(MINE_COMMENT_MODEL, envelope='records', code=200)
    @requires_role_view_all
    def get(self, mine_guid):

        mine = Mine.find_by_mine_guid(mine_guid)

        if not mine:
            raise NotFound('Mine not found')

        current_app.logger.info(f'Retrieving comments for {mine}')

        comments = [comment.__dict__ for comment in mine.comments]

        return comments, 200

    @api.expect(MINE_COMMENT_MODEL)
    @api.doc(description='creates a new comment for the Mine')
    @api.marshal_with(MINE_COMMENT_MODEL, code=201)
    @requires_role_mine_edit
    def post(self, mine_guid):

        mine = Mine.find_by_mine_guid(mine_guid)

        if not mine:
            raise NotFound('Mine not found')

        data = self.parser.parse_args()

        if not data['mine_comment']:
            raise BadRequest('Empty comment')

        mine_comment = MineComment.create(
            mine=mine,
            mine_comment=data['mine_comment'],
        )

        current_app.logger.info(f'Creating comment {mine_comment}')

        mine_comment.save()

        return mine_comment, 201
Beispiel #12
0
class MineReportCommentListResource(Resource, UserMixin):
    parser = CustomReqparser()
    parser.add_argument('report_comment', type=str, location='json')
    parser.add_argument('comment_visibility_ind',
                        type=inputs.boolean,
                        location='json')

    @api.doc(
        description='retrive a list of comments for all report submissions')
    @api.marshal_with(MINE_REPORT_COMMENT_MODEL, envelope='records', code=200)
    @requires_role_view_all
    def get(self, mine_guid, mine_report_guid):

        mine_report = MineReport.find_by_mine_report_guid(mine_report_guid)

        if not mine_report:
            raise NotFound('Mine report not found')

        mine_report_submissions = mine_report.mine_report_submissions

        if not mine_report_submissions:
            raise NotFound('No mine report submissions for this report')

        current_app.logger.info(f'Retrieving comments for {mine_report}')

        comments = [
            comment.__dict__ for submission in mine_report_submissions[:-1]
            for comment in submission.comments
        ]
        for comment in comments:
            comment['from_latest_submission'] = False
        latest_comments = [
            comment.__dict__ for submission in mine_report_submissions[-1:]
            for comment in submission.comments
        ]
        for comment in latest_comments:
            comment['from_latest_submission'] = True

        return comments + latest_comments, 200

    @api.expect(MINE_REPORT_COMMENT_MODEL)
    @api.doc(description='creates a new comment for the report submission')
    @api.marshal_with(MINE_REPORT_COMMENT_MODEL, code=201)
    @requires_role_edit_report
    def post(self, mine_guid, mine_report_guid):

        mine_report_submission = MineReportSubmission.find_latest_by_mine_report_guid(
            mine_report_guid)

        if not mine_report_submission:
            raise NotFound('Mine report submission not found')
            # TODO: Do we want to create a submission if it doesn't exist?

        data = self.parser.parse_args()

        if not data['report_comment']:
            raise BadRequest('Empty comment')

        mine_report_comment_guid = uuid.uuid4()

        mine_report_comment = MineReportComment.create(
            mine_report_submission,
            mine_report_comment_guid=mine_report_comment_guid,
            report_comment=data['report_comment'],
            comment_visibility_ind=data['comment_visibility_ind'],
        )

        current_app.logger.info(f'Creating comment {mine_report_comment}')

        mine_report_comment.save()

        return mine_report_comment, 201
Beispiel #13
0
class MinePartyApptResource(Resource, UserMixin, ErrorMixin):
    parser = CustomReqparser()
    parser.add_argument('mine_guid', type=str, help='guid of the mine.')
    parser.add_argument('party_guid', type=str, help='guid of the party.')
    parser.add_argument('mine_party_appt_type_code',
                        type=str,
                        help='code for the type of appt.',
                        store_missing=False)
    parser.add_argument('related_guid', type=str, store_missing=False)
    parser.add_argument('start_date',
                        type=lambda x: datetime.strptime(x, '%Y-%m-%d')
                        if x else None,
                        store_missing=False)
    parser.add_argument('end_date',
                        type=lambda x: datetime.strptime(x, '%Y-%m-%d')
                        if x else None,
                        store_missing=False)

    @api.doc(
        params={'mine_party_appt_guid': 'mine party appointment serial id'})
    @requires_role_view_all
    def get(self, mine_party_appt_guid=None):
        relationships = request.args.get('relationships')
        relationships = relationships.split(',') if relationships else []
        if mine_party_appt_guid:
            mpa = MinePartyAppointment.find_by_mine_party_appt_guid(
                mine_party_appt_guid)
            if not mpa:
                raise NotFound('Mine Party Appointment not found')
            result = mpa.json(relationships=relationships)
        else:
            mine_guid = request.args.get('mine_guid')
            party_guid = request.args.get('party_guid')
            types = request.args.getlist('types')  #list
            mpas = MinePartyAppointment.find_by(
                mine_guid=mine_guid,
                party_guid=party_guid,
                mine_party_appt_type_codes=types)
            result = [x.json(relationships=relationships) for x in mpas]
        return result

    @api.doc(
        params={'mine_party_appt_guid': 'mine party appointment serial id'})
    @requires_role_mine_edit
    def post(self, mine_party_appt_guid=None):
        if mine_party_appt_guid:
            raise BadRequest('unexpected mine party appointment guid')
        data = self.parser.parse_args()

        new_mpa = MinePartyAppointment(
            mine_guid=data.get('mine_guid'),
            party_guid=data.get('party_guid'),
            mine_party_appt_type_code=data.get('mine_party_appt_type_code'),
            start_date=data.get('start_date'),
            end_date=data.get('end_date'),
            processed_by=self.get_user_info())

        if new_mpa.mine_party_appt_type_code == "EOR":
            new_mpa.assign_related_guid(data.get('related_guid'))
            if not new_mpa.mine_tailings_storage_facility_guid:
                raise AssertionError(
                    'mine_tailings_storage_facility_guid must be provided for Engineer of Record'
                )
            #TODO move db foreign key constraint when services get separated
            pass

        if new_mpa.mine_party_appt_type_code == "PMT":
            new_mpa.assign_related_guid(data.get('related_guid'))
            if not new_mpa.permit_guid:
                raise AssertionError(
                    'permit_guid must be provided for Permittee')
            #TODO move db foreign key constraint when services get separated
            pass
        try:
            new_mpa.save()
        except alch_exceptions.IntegrityError as e:
            if "daterange_excl" in str(e):
                mpa_type_name = MinePartyAppointmentType.find_by_mine_party_appt_type_code(
                    data.get('mine_party_appt_type_code')).description
                raise BadRequest(
                    f'Date ranges for {mpa_type_name} must not overlap')
        return new_mpa.json()

    @api.doc(
        params={
            'mine_party_appt_guid':
            'mine party appointment guid, this endpoint only respects form data keys: start_date and end_date, and related_guid'
        })
    @requires_role_mine_edit
    def put(self, mine_party_appt_guid=None):
        if not mine_party_appt_guid:
            raise BadRequest('missing mine party appointment guid')

        data = self.parser.parse_args()
        mpa = MinePartyAppointment.find_by_mine_party_appt_guid(
            mine_party_appt_guid)
        if not mpa:
            raise NotFound('mine party appointment not found')

        for key, value in data.items():
            if key in ['party_guid', 'mine_guid']:
                continue
            elif key == "related_guid":
                mpa.assign_related_guid(data.get('related_guid'))
            else:
                setattr(mpa, key, value)
        try:
            mpa.save()
        except alch_exceptions.IntegrityError as e:
            if "daterange_excl" in str(e):
                mpa_type_name = mpa.mine_party_appt_type.description
                raise BadRequest(
                    f'Date ranges for {mpa_type_name} must not overlap.')

        return mpa.json()

    @api.doc(params={
        'mine_party_appt_guid':
        'mine party appointment guid to be deleted'
    })
    @requires_role_mine_edit
    def delete(self, mine_party_appt_guid=None):
        if not mine_party_appt_guid:
            raise BadRequest('Expected mine party appointment guid.')

        data = self.parser.parse_args()
        mpa = MinePartyAppointment.find_by_mine_party_appt_guid(
            mine_party_appt_guid)
        if not mpa:
            raise NotFound('Mine party appointment not found.')

        mpa.deleted_ind = True
        mpa.save()

        return ('', 204)
Beispiel #14
0
class MineVarianceResource(Resource, UserMixin, ErrorMixin):
    parser = CustomReqparser()
    parser.add_argument(
        'compliance_article_id',
        type=int,
        store_missing=False,
        help='ID representing the MA or HSRCM code to which this variance relates.')
    parser.add_argument(
        'received_date',
        store_missing=False,
        help='The date on which the variance application was received.')
    parser.add_argument(
        'variance_application_status_code',
        type=str,
        store_missing=False,
        help='A 3-character code indicating the status type of the variance. Default: REV')
    parser.add_argument(
        'inspector_party_guid',
        type=str,
        store_missing=False,
        help='GUID of the party who inspected the mine during the variance application process.')
    parser.add_argument(
        'note',
        type=str,
        store_missing=False,
        help='A note to include on the variance. Limited to 300 characters.')
    parser.add_argument(
        'parties_notified_ind',
        type=bool,
        store_missing=False,
        help='Indicates if the relevant parties have been notified of variance request and decision.')
    parser.add_argument(
        'issue_date', store_missing=False, help='The date on which the variance was issued.')
    parser.add_argument(
        'expiry_date', store_missing=False, help='The date on which the variance expires.')

    @api.doc(
        description='Get a single variance.',
        params={
            'mine_guid': 'GUID of the mine to which the variance is associated',
            'variance_guid': 'GUID of the variance to fetch'
        })
    @requires_any_of([VIEW_ALL, MINESPACE_PROPONENT])
    @api.marshal_with(VARIANCE_MODEL, code=200)
    def get(self, mine_guid, variance_guid):
        variance = Variance.find_by_mine_guid_and_variance_guid(
            mine_guid, variance_guid)

        if variance is None:
            raise NotFound('Unable to fetch variance')

        return variance

    @api.doc(
        description='Update a variance.',
        params={
            'mine_guid': 'GUID of the mine to which the variance is associated',
            'variance_guid': 'GUID of the variance to update'
        })
    @requires_any_of([EDIT_VARIANCE, MINESPACE_PROPONENT])
    @api.marshal_with(VARIANCE_MODEL, code=200)
    def put(self, mine_guid, variance_guid):
        variance = Variance.find_by_mine_guid_and_variance_guid(
            mine_guid, variance_guid)
        if variance is None:
            raise NotFound('Unable to fetch variance')

        data = self.parser.parse_args()

        inspector_party_guid = data.get('inspector_party_guid')
        if inspector_party_guid:
            inspector = Party.find_by_party_guid(inspector_party_guid)
            if not inspector:
                raise BadRequest('Unable to find new inspector.')
            if not 'INS' in inspector.business_roles_codes:
                raise BadRequest('Party is not an inspector.')

            variance.inspector_party_guid = inspector_party_guid

        for key, value in data.items():
            if key in ['inspector_party_guid']:
                continue
            setattr(variance, key, value)

        # A manual check to prevent a stack trace dump on a foreign key /
        # constraint error because global error handling doesn't currently work
        # with these errors
        Variance.validate_status_with_other_values(
            status=variance.variance_application_status_code,
            issue=variance.issue_date,
            expiry=variance.expiry_date,
            inspector=variance.inspector_party_guid)

        variance.save()
        return variance
Beispiel #15
0
class AuthorizationResource(Resource, UserMixin):
    parser = CustomReqparser()
    parser.add_argument('application_guid',
                        type=str,
                        help='',
                        store_missing=False)
    parser.add_argument('otl_guid', type=str, help='', store_missing=False)

    ONE_TIME_PASSWORD_TIMEOUT_SECONDS = Config.ONE_TIME_PASSWORD_TIMEOUT_SECONDS

    @api.doc(description='authorization endpoint is reachable')
    def get(self):
        return "OK", 200

    @api.doc(
        description=
        'generates and sends OTL to an email address associated with the application'
    )
    def post(self):
        data = AuthorizationResource.parser.parse_args()
        application_guid = data.get('application_guid')
        application = Application.find_by_guid(application_guid)
        otl_guid = uuid.uuid4()

        if application is None:
            raise NotFound(
                'No application was found matching the provided reference number'
            )

        html_content = f"""
        <table width="100%" style="font-size:12.0pt; color:#595959 " >
            <tr>
                <td>
                    You have requested access to the Dormant Sites Reclamation Program site to view information about an application (see Reference Number above).
                    <br/>
                    <br/>
                    Use the button below to access your application information and submit payment requests.
                    <br/>
                    This button can only be used once and access expires after four hours. If you need to access the application again, request another link on the website.
                </td> 
            </tr>   
            <tr>
                <td>
                <br/>
                    <table style="margin-left: auto; margin-right: auto;">
                        <tr>
                            <td style="border-radius: 2px;" bgcolor="#003366" >
                                <a href="{ONE_TIME_LINK_FRONTEND_URL(otl_guid)}" target="_blank" style="padding: 8px 12px; border: 1px solid #003366;border-radius: 2px;font-size: 14px; color: #ffffff;text-decoration: none;font-weight:bold;display: inline-block;">
                                    View Application             
                                </a>
                            </td>
                        </tr>
                    </table>
                <br/>
                </td>
            </tr>
		</table>"""

        with EmailService() as es:
            es.send_email_to_applicant(
                application,
                f"Dormant Site Reclamation Program – Access Request",
                html_content)

        current_app.logger.debug(f"This is a OTL: {otl_guid}")
        cache.set(
            str(otl_guid),
            application_guid,
            timeout=AuthorizationResource.ONE_TIME_PASSWORD_TIMEOUT_SECONDS)

        return "OK", 200

    @api.doc(description='generates and returns OTP')
    def put(self):
        otp_guid = None
        issued_time_utc = None
        timeout = AuthorizationResource.ONE_TIME_PASSWORD_TIMEOUT_SECONDS

        data = AuthorizationResource.parser.parse_args()
        otl_guid = data.get('otl_guid')
        app_guid = cache.get(otl_guid)

        current_app.logger.info(f'this is app_guid: {app_guid}')

        if otl_guid and app_guid:
            cache.delete(otl_guid)
            current_app.logger.info(f"OTL_GUID_VALUE: {cache.get(otl_guid)}")
            otp_guid = uuid.uuid4()
            issued_time_utc = datetime.now(timezone.utc)
            cache.set(str(otp_guid), app_guid, timeout=timeout)
        else:
            abort(401)

        return jsonify({
            "OTP":
            otp_guid,
            "issued_time_utc":
            issued_time_utc.strftime("%d %b %Y %H:%M:%S %z"),
            "timeout_seconds":
            AuthorizationResource.ONE_TIME_PASSWORD_TIMEOUT_SECONDS,
            "application_guid":
            app_guid
        })
Beispiel #16
0
class MineVarianceListResource(Resource, UserMixin):
    parser = CustomReqparser()
    parser.add_argument('compliance_article_id',
                        type=int,
                        store_missing=False,
                        help='ID representing the MA or HSRCM code to which this variance relates.',
                        required=True)
    parser.add_argument('received_date',
                        store_missing=False,
                        help='The date on which the variance application was received.',
                        required=True)
    parser.add_argument(
        'variance_application_status_code',
        type=str,
        store_missing=False,
        help='A 3-character code indicating the status type of the variance. Default: REV')
    parser.add_argument('applicant_guid',
                        type=str,
                        store_missing=False,
                        help='GUID of the party on behalf of which the application was made.')
    parser.add_argument(
        'inspector_party_guid',
        type=str,
        store_missing=False,
        help='GUID of the party who inspected the mine during the variance application process.')
    parser.add_argument('note',
                        type=str,
                        store_missing=False,
                        help='A note to include on the variance. Limited to 300 characters.')
    parser.add_argument(
        'parties_notified_ind',
        type=bool,
        store_missing=False,
        help='Indicates if the relevant parties have been notified of variance request and decision.'
    )
    parser.add_argument('issue_date',
                        store_missing=False,
                        help='The date on which the variance was issued.')
    parser.add_argument('expiry_date',
                        store_missing=False,
                        help='The date on which the variance expires.')

    @api.doc(description='Get a list of all variances for a given mine.',
             params={'mine_guid': 'guid of the mine for which to fetch variances'})
    @requires_any_of([VIEW_ALL, MINESPACE_PROPONENT])
    @api.marshal_with(VARIANCE_MODEL, code=200, envelope='records')
    def get(self, mine_guid):
        variances = Variance.find_by_mine_guid(mine_guid)

        if variances is None:
            raise BadRequest(
                'Unable to fetch variances. Confirm you\'ve provided a valid mine_guid')

        if len(variances) == 0:
            mine = Mine.find_by_mine_guid(mine_guid)
            if mine is None:
                raise NotFound('Mine')

        return variances

    @api.doc(description='Create a new variance for a given mine.',
             params={'mine_guid': 'guid of the mine with which to associate the variances'})
    @api.expect(parser)
    @requires_any_of([EDIT_VARIANCE, MINESPACE_PROPONENT])
    @api.marshal_with(VARIANCE_MODEL, code=200)
    def post(self, mine_guid):
        data = self.parser.parse_args()
        compliance_article_id = data['compliance_article_id']
        received_date = data['received_date']
        inspector_party_guid = data.get('inspector_party_guid')

        mine = Mine.find_by_mine_guid(mine_guid)
        if mine is None:
            raise NotFound('Mine')

        # A manual check to prevent a stack trace dump on a foreign key /
        # constraint error because global error handling doesn't currently work
        # with these errors
        variance_application_status_code = data.get('variance_application_status_code') or 'REV'
        issue_date = data.get('issue_date')
        expiry_date = data.get('expiry_date')
        Variance.validate_status_with_other_values(status=variance_application_status_code,
                                                   issue=issue_date,
                                                   expiry=expiry_date,
                                                   inspector=inspector_party_guid)

        variance = Variance.create(
            compliance_article_id=compliance_article_id,
            mine_guid=mine_guid,
            received_date=received_date,
            # Optional fields
            variance_application_status_code=variance_application_status_code,
            applicant_guid=data.get('applicant_guid'),
            inspector_party_guid=inspector_party_guid,
            note=data.get('note'),
            parties_notified_ind=data.get('parties_notified_ind'),
            issue_date=issue_date,
            expiry_date=expiry_date)

        if not variance:
            raise BadRequest('Error: Failed to create variance')

        variance.save()
        return variance
Beispiel #17
0
class MineReportListResource(Resource, UserMixin):
    parser = CustomReqparser()

    # required
    parser.add_argument('submission_year',
                        type=str,
                        location='json',
                        required=True)
    parser.add_argument('mine_report_definition_guid',
                        type=str,
                        location='json',
                        required=True)
    parser.add_argument('due_date',
                        location='json',
                        type=lambda x: datetime.strptime(x, '%Y-%m-%d')
                        if x else None)

    parser.add_argument('permit_guid', type=str, location='json')
    parser.add_argument('mine_report_submission_status',
                        type=str,
                        location='json')
    parser.add_argument('received_date',
                        location='json',
                        type=lambda x: datetime.strptime(x, '%Y-%m-%d')
                        if x else None)
    parser.add_argument('mine_report_submissions', type=list, location='json')

    @api.marshal_with(MINE_REPORT_MODEL, envelope='records', code=200)
    @api.doc(description='returns the reports for a given mine.')
    @requires_any_of([VIEW_ALL, MINESPACE_PROPONENT])
    def get(self, mine_guid):
        mrd_category = request.args.get('mine_report_definition_category')
        if mrd_category:
            mine_reports = MineReport.find_by_mine_guid_and_category(
                mine_guid, mrd_category)
        else:
            mine_reports = MineReport.find_by_mine_guid(mine_guid)

        return mine_reports

    @api.doc(description='creates a new report for the mine')
    @api.marshal_with(MINE_REPORT_MODEL, code=201)
    @requires_any_of([EDIT_REPORT, MINESPACE_PROPONENT])
    def post(self, mine_guid):
        mine = Mine.find_by_mine_guid(mine_guid)
        if not mine:
            raise NotFound('Mine not found')

        data = self.parser.parse_args()

        mine_report_definition = MineReportDefinition.find_by_mine_report_definition_guid(
            data['mine_report_definition_guid'])
        permit = Permit.find_by_permit_guid_or_no(data['permit_guid'])
        if mine_report_definition is None:
            raise BadRequest('A report must be selected from the list.')

        if permit and permit.mine_guid != mine.mine_guid:
            raise BadRequest(
                'The permit must be associated with the selected mine.')
        mine_report = MineReport.create(
            mine_report_definition_id=mine_report_definition.
            mine_report_definition_id,
            mine_guid=mine.mine_guid,
            due_date=data.get('due_date'),
            received_date=data['received_date'],
            submission_year=data['submission_year'],
            permit_id=permit.permit_id if permit else None)

        submissions = data.get('mine_report_submissions')
        if submissions:
            submission = submissions[-1]
            if len(submission.get('documents')) > 0:
                submission_status = data.get(
                    'mine_report_submission_status') if data.get(
                        'mine_report_submission_status') else 'NRQ'
                report_submission = MineReportSubmission(
                    mine_report_submission_status_code=submission_status,
                    submission_date=datetime.utcnow())
                for submission_doc in submission.get('documents'):
                    mine_doc = MineDocument(
                        mine_guid=mine.mine_guid,
                        document_name=submission_doc['document_name'],
                        document_manager_guid=submission_doc[
                            'document_manager_guid'])

                    if not mine_doc:
                        raise BadRequest(
                            'Unable to register uploaded file as document')

                    mine_doc.save()

                    report_submission.documents.append(mine_doc)

                mine_report.mine_report_submissions.append(report_submission)
        try:
            mine_report.save()
        except Exception as e:
            raise InternalServerError(f'Error when saving: {e}')

        return mine_report, 201
Beispiel #18
0
class PartyResource(Resource, UserMixin, ErrorMixin):
    parser = CustomReqparser()
    parser.add_argument(
        'first_name',
        type=str,
        help='First name of the party, if the party is a person.',
        store_missing=False)
    parser.add_argument(
        'party_name',
        type=str,
        help=
        'Last name of the party (Person), or the Organization name (Organization).',
        store_missing=False)
    parser.add_argument('phone_no',
                        type=str,
                        help='The phone number of the party. Ex: 123-123-1234',
                        store_missing=False)
    parser.add_argument('phone_ext',
                        type=str,
                        help='The extension of the phone number. Ex: 1234',
                        store_missing=False)
    parser.add_argument('email',
                        type=str,
                        help='The email of the party.',
                        store_missing=False)
    parser.add_argument('email',
                        type=str,
                        help='The email of the party.',
                        store_missing=False)
    parser.add_argument('party_type_code',
                        type=str,
                        help='The type of the party. Ex: PER',
                        store_missing=False)
    parser.add_argument('suite_no',
                        type=str,
                        store_missing=False,
                        help='The suite number of the party address. Ex: 123')
    parser.add_argument(
        'address_line_1',
        type=str,
        store_missing=False,
        help='The first address line of the party address. Ex: 1234 Foo Road')
    parser.add_argument(
        'address_line_2',
        type=str,
        store_missing=False,
        help='The second address line of the party address. Ex: 1234 Foo Road')
    parser.add_argument(
        'city',
        type=str,
        store_missing=False,
        help='The city where the party is located. Ex: FooTown')
    parser.add_argument(
        'sub_division_code',
        type=str,
        store_missing=False,
        help='The region code where the party is located. Ex: BC')
    parser.add_argument(
        'post_code',
        type=str,
        store_missing=False,
        help='The postal code of the party address. Ex: A0B1C2')
    parser.add_argument(
        'job_title',
        type=str,
        store_missing=False,
        help='The job title of the party. Ex "Chief of Inspections"')
    parser.add_argument('postnominal_letters',
                        type=str,
                        store_missing=False,
                        help='Suffixes for a party name. Ex "BSc, PhD"')
    parser.add_argument(
        'idir_username',
        type=str,
        store_missing=False,
        help='The IDIR username of the party. Ex "IDIR\JSMITH"')

    PARTY_LIST_RESULT_LIMIT = 25

    @api.doc(description='Fetch a party by guid',
             params={
                 'party_guid': 'guid of the party to fetch',
             })
    @requires_role_view_all
    @api.marshal_with(PARTY, code=200)
    def get(self, party_guid):
        party = Party.find_by_party_guid(party_guid)
        if not party:
            raise NotFound('Party not found')

        return party

    @api.expect(parser)
    @api.doc(description='Update a party by guid',
             params={'party_guid': 'guid of the party to update.'})
    @requires_role_edit_party
    @api.marshal_with(PARTY, code=200)
    def put(self, party_guid):
        data = PartyResource.parser.parse_args()
        existing_party = Party.find_by_party_guid(party_guid)
        if not existing_party:
            raise NotFound('Party not found.')

        current_app.logger.info(f'Updating {existing_party} with {data}')
        for key, value in data.items():
            if key in ['party_type_code']:
                continue  # non-editable fields from put
            setattr(existing_party, key, value)

        # We are now allowing parties to be created without an address
        if (data.get('suite_no') or data.get('address_line_1')
                or data.get('address_line_2') or data.get('city')
                or data.get('sub_division_code') or data.get('post_code')
            ):  # and check that we are changing the address
            if len(existing_party.address) == 0:
                address = Address.create()
                existing_party.address.append(address)

            for key, value in data.items():
                setattr(existing_party.address[0], key, value)

        existing_party.save()

        return existing_party

    @api.doc(description='Delete a party by guid',
             params={'party_guid': 'guid of the party to delete.'})
    @requires_role_mine_admin
    def delete(self, party_guid):
        if party_guid is None:
            return self.create_error_payload(404,
                                             'Must provide a party guid.'), 404
        try:
            party = Party.find_by_party_guid(party_guid)
        except DBAPIError:
            return self.create_error_payload(422, 'Invalid Party guid'), 422
        if party is None:
            return self.create_error_payload(
                404, 'Party guid with "{party_guid}" not found.'), 404

        mine_party_appts = MinePartyAppointment.find_by_party_guid(party_guid)
        for mine_party_appt in mine_party_appts:
            mine_party_appt.deleted_ind = True
            mine_party_appt.save()

        party.deleted_ind = True
        party.save()
        return ('', 204)
Beispiel #19
0
class PartyListResource(Resource, UserMixin ):
    parser = CustomReqparser()
    parser.add_argument(
        'party_name',
        type=str,
        help='Last name of the party (Person), or the Organization name (Organization).',
        required=True)
    parser.add_argument('party_type_code',
                        type=str,
                        help='Party type. Person (PER) or Organization (ORG).',
                        required=True)
    parser.add_argument('name_search', type=str, help='Name or partial name of the party.')
    parser.add_argument('phone_no',
                        type=str,
                        help='The phone number of the party. Ex: 123-123-1234',
                        required=True)
    parser.add_argument('last_name',
                        type=str,
                        help='Last name of the party, if the party is a person.')
    parser.add_argument('first_name',
                        type=str,
                        help='First name of the party, if the party is a person.')
    parser.add_argument('phone_ext', type=str, help='The extension of the phone number. Ex: 1234')
    parser.add_argument('email', type=str, help='The email of the party.')
    parser.add_argument('suite_no',
                        type=str,
                        store_missing=False,
                        help='The suite number of the party address. Ex: 123')
    parser.add_argument('address_line_1',
                        type=str,
                        store_missing=False,
                        help='The first address line of the party address. Ex: 1234 Foo Road')
    parser.add_argument('address_line_2',
                        type=str,
                        store_missing=False,
                        help='The second address line of the party address. Ex: 1234 Foo Road')
    parser.add_argument('city',
                        type=str,
                        store_missing=False,
                        help='The city where the party is located. Ex: FooTown')
    parser.add_argument('sub_division_code',
                        type=str,
                        store_missing=False,
                        help='The region code where the party is located. Ex: BC')
    parser.add_argument('post_code',
                        type=str,
                        store_missing=False,
                        help='The postal code of the party address. Ex: A0B1C2')

    PARTY_LIST_RESULT_LIMIT = 25

    @api.doc(description='Fetch a paginated list of parties.',
             params={
                 'first_name': 'First name of party or contact',
                 'party_name': 'Last name or party name of person or organisation',
                 'phone_no': 'phone number',
                 'email': 'email of person or organisation',
                 'type': 'A person (PER) or organisation (ORG)',
                 'role': 'A comma separated list of roles to be filtered by',
                 'sort_field': 'enum[party_name] Default: party_name',
                 'sort_dir': 'enum[asc, desc] Default: asc',
                 'business_role': 'A business role or roles to filter on'
             })
    @requires_any_of([VIEW_ALL, MINESPACE_PROPONENT])
    @api.marshal_with(PAGINATED_PARTY_LIST, code=200)
    def get(self):
        paginated_parties, pagination_details = self.apply_filter_and_search(request.args)
        if not paginated_parties:
            raise BadRequest('Unable to fetch parties')

        return {
            'records': paginated_parties.all(),
            'current_page': pagination_details.page_number,
            'total_pages': pagination_details.num_pages,
            'items_per_page': pagination_details.page_size,
            'total': pagination_details.total_results,
        }

    @api.expect(parser)
    @api.doc(description='Create a party.')
    @requires_role_edit_party
    @api.marshal_with(PARTY, code=200)
    def post(self, party_guid=None):
        if party_guid:
            raise BadRequest('Unexpected party id in Url.')
        data = PartyListResource.parser.parse_args()

        party = Party.create(
            data.get('party_name'),
            data.get('phone_no'),
            data.get('party_type_code'),
            # Nullable fields
            email=data.get('email'),
            first_name=data.get('first_name'),
            phone_ext=data.get('phone_ext'))

        if not party:
            raise InternalServerError('Error: Failed to create party')

        # If no address data is provided do not create an address.
        if (data.get('suite_no') or data.get('address_line_1') or data.get('address_line_2')
                or data.get('city') or data.get('sub_division_code') or data.get('post_code')):
            address = Address.create(suite_no=data.get('suite_no'),
                                     address_line_1=data.get('address_line_1'),
                                     address_line_2=data.get('address_line_2'),
                                     city=data.get('city'),
                                     sub_division_code=data.get('sub_division_code'),
                                     post_code=data.get('post_code'))
            party.address.append(address)

        party.save()
        return party

    def apply_filter_and_search(self, args):
        sort_models = {
            'party_name': 'Party',
        }

        # Handle ListView request
        items_per_page = args.get('per_page', 25)
        if items_per_page == 'all':
            items_per_page = None
        else:
            items_per_page = int(items_per_page)

        page = args.get('page', 1, type=int)
        # parse the filter terms
        first_name_filter_term = args.get('first_name', None, type=str)
        last_name_filter_term = args.get('last_name', None, type=str)
        party_name_filter_term = args.get('party_name', None, type=str)
        name_search_filter_term = args.get('name_search', None, type=str)
        type_filter_term = args.get('type', None, type=str)
        role_filter_term = args.get('role', None, type=str)
        email_filter_term = args.get('email', None, type=str)
        phone_filter_term = args.get('phone_no', None, type=str)
        sort_field = args.get('sort_field', 'party_name', type=str)
        sort_dir = args.get('sort_dir', 'asc', type=str)
        business_roles = args.getlist('business_role', None)
        sort_model = sort_models.get(sort_field)

        conditions = [Party.deleted_ind == False]
        if first_name_filter_term:
            conditions.append(Party.first_name.ilike('%{}%'.format(first_name_filter_term)))
        if last_name_filter_term:
            conditions.append(Party.party_name.ilike('%{}%'.format(last_name_filter_term)))
        if party_name_filter_term:
            conditions.append(Party.party_name.ilike('%{}%'.format(party_name_filter_term)))
        if email_filter_term:
            conditions.append(Party.email.ilike('%{}%'.format(email_filter_term)))
        if type_filter_term:
            conditions.append(Party.party_type_code.like(type_filter_term))
        if phone_filter_term:
            conditions.append(Party.phone_no.ilike('%{}%'.format(phone_filter_term)))
        if role_filter_term == "NONE":
            conditions.append(Party.mine_party_appt == None)
        if name_search_filter_term:
            name_search_conditions = []
            for name_part in name_search_filter_term.strip().split(" "):
                name_search_conditions.append(Party.first_name.ilike('%{}%'.format(name_part)))
                name_search_conditions.append(Party.party_name.ilike('%{}%'.format(name_part)))
            conditions.append(or_(*name_search_conditions))
        contact_query = Party.query.filter(and_(*conditions))
        if role_filter_term and not role_filter_term == "NONE":
            role_filter = MinePartyAppointment.mine_party_appt_type_code.like(role_filter_term)
            role_query = Party.query.join(MinePartyAppointment).filter(role_filter)
            contact_query = contact_query.intersect(role_query) if contact_query else role_query
        if business_roles and len(business_roles) > 0:
            business_role_filter = PartyBusinessRoleAppointment.party_business_role_code.in_(
                business_roles)
            business_role_query = Party.query.join(PartyBusinessRoleAppointment).filter(
                business_role_filter)
            contact_query = contact_query.intersect(
                business_role_query) if contact_query else business_role_query

        # Apply sorting
        if sort_model and sort_field and sort_dir:
            sort_criteria = [{'model': sort_model, 'field': sort_field, 'direction': sort_dir}]
            contact_query = apply_sort(contact_query, sort_criteria)
        return apply_pagination(contact_query, page, items_per_page)
Beispiel #20
0
class MineReportResource(Resource, UserMixin):
    parser = CustomReqparser()
    parser.add_argument('due_date',
                        type=str,
                        location='json',
                        store_missing=False)
    parser.add_argument('received_date',
                        location='json',
                        store_missing=False,
                        type=lambda x: datetime.strptime(x, '%Y-%m-%d')
                        if x else None)
    parser.add_argument('submission_year',
                        type=str,
                        location='json',
                        store_missing=False)
    parser.add_argument('mine_report_submission_status',
                        type=str,
                        location='json')
    parser.add_argument('mine_report_submissions',
                        type=list,
                        location='json',
                        store_missing=False)

    @api.marshal_with(MINE_REPORT_MODEL, code=200)
    @requires_any_of([VIEW_ALL, MINESPACE_PROPONENT])
    def get(self, mine_guid, mine_report_guid):
        mine_report = MineReport.find_by_mine_report_guid(mine_report_guid)
        if not mine_report:
            raise NotFound("Mine Report not found")
        return mine_report

    @api.expect(parser)
    @api.marshal_with(MINE_REPORT_MODEL, code=200)
    @requires_any_of([EDIT_REPORT, MINESPACE_PROPONENT])
    def put(self, mine_guid, mine_report_guid):
        mine = Mine.find_by_mine_guid(mine_guid)
        mine_report = MineReport.find_by_mine_report_guid(mine_report_guid)
        if not mine_report or str(mine_report.mine_guid) != mine_guid:
            raise NotFound("Mine Report not found")

        data = self.parser.parse_args()

        if 'due_date' in data:
            mine_report.due_date = data.get('due_date')

        if 'received_date' in data:
            mine_report.received_date = data['received_date']

        if 'submission_year' in data:
            mine_report.submission_year = data['submission_year']

        if data.get('mine_report_submission_status') is not None:
            mine_report_submission_status = data.get(
                'mine_report_submission_status')
        else:
            mine_report_submission_status = 'NRQ'

        report_submissions = data.get('mine_report_submissions')
        submission_iterator = iter(report_submissions)
        new_submission = next(
            (x for x in submission_iterator
             if x.get('mine_report_submission_guid') is None), None)
        if new_submission is not None:
            new_report_submission = MineReportSubmission(
                submission_date=datetime.now(),
                mine_report_submission_status_code=mine_report_submission_status
            )
            # Copy the current list of documents for the report submission
            last_submission_docs = mine_report.mine_report_submissions[
                -1].documents.copy() if len(
                    mine_report.mine_report_submissions) > 0 else []

            # Gets the difference between the set of documents in the new submission and the last submission
            new_docs = [
                x for x in new_submission.get('documents') if not any(
                    str(doc.document_manager_guid) ==
                    x['document_manager_guid'] for doc in last_submission_docs)
            ]
            # Get the documents that were on the last submission but not part of the new submission
            removed_docs = [
                x for x in last_submission_docs
                if not any(doc['document_manager_guid'] == str(
                    x.document_manager_guid)
                           for doc in new_submission.get('documents'))
            ]
            # Remove the deleted documents from the existing set.
            for doc in removed_docs:
                last_submission_docs.remove(doc)

            if len(last_submission_docs) > 0:
                new_report_submission.documents.extend(last_submission_docs)

            for doc in new_docs:
                mine_doc = MineDocument(
                    mine_guid=mine.mine_guid,
                    document_name=doc['document_name'],
                    document_manager_guid=doc['document_manager_guid'])

                if not mine_doc:
                    raise BadRequest(
                        'Unable to register uploaded file as document')

                mine_doc.save()

                new_report_submission.documents.append(mine_doc)

            mine_report.mine_report_submissions.append(new_report_submission)

        # if the status has changed, update the status of the last submission
        elif (
                len(mine_report.mine_report_submissions) > 0
        ) and mine_report_submission_status != mine_report.mine_report_submissions[
                -1].mine_report_submission_status_code:
            mine_report.mine_report_submissions[
                -1].mine_report_submission_status_code = mine_report_submission_status

        try:
            mine_report.save()
        except Exception as e:
            raise InternalServerError(f'Error when saving: {e}')
        return mine_report

    @requires_role_edit_report
    @api.response(204, 'Successfully deleted.')
    def delete(self, mine_guid, mine_report_guid):
        mine_report = MineReport.find_by_mine_report_guid(mine_report_guid)
        if not mine_report or str(mine_report.mine_guid) != mine_guid:
            raise NotFound("Mine Report not found")

        mine_report.deleted_ind = True
        mine_report.save()
        return None, 204