Пример #1
0
def delete_communication(framework_slug, comm_type, filepath):
    if comm_type not in _comm_types:
        abort(404)

    framework = get_framework_or_404(data_api_client, framework_slug)

    if request.method == "POST":
        if "confirm" not in request.form:
            abort(400, "Expected 'confirm' parameter in POST request")

        communications_bucket = s3.S3(
            current_app.config['DM_COMMUNICATIONS_BUCKET'])
        full_path = _get_comm_type_root(framework_slug, comm_type) / filepath

        # do this check ourselves - deleting an object in S3 silently has no effect, forwarding this behaviour to the
        # user is a confusing thing to do
        if not communications_bucket.path_exists(str(full_path)):
            abort(404, f"{filepath} not present in S3 bucket")

        communications_bucket.delete_key(str(full_path))

        flash(
            f"{comm_type.capitalize()} ‘{filepath}’ was deleted for {framework['name']}."
        )
        return redirect(
            url_for('.manage_communications', framework_slug=framework_slug))

    return render_template(
        'confirm_communications_deletion.html',
        framework=framework,
        comm_type=comm_type,
        filepath=filepath,
    )
Пример #2
0
def manage_communications(framework_slug):
    communications_bucket = s3.S3(
        current_app.config['DM_COMMUNICATIONS_BUCKET'])
    framework = get_framework_or_404(data_api_client, framework_slug)

    # generate a dict of comm_type: seq of s3 object dicts
    comm_type_objs = {
        comm_type: tuple({
            **bucket_item,
            # annotate on to object dicts their paths relative to comm_type_root
            "rel_path":
            PurePath(bucket_item["path"]).relative_to(
                _get_comm_type_root(framework_slug, comm_type)),
        } for bucket_item in communications_bucket.list(
            str(_get_comm_type_root(framework_slug, comm_type)),
            load_timestamps=True,
        ))
        for comm_type in _comm_types
    }

    return render_template(
        'manage_communications.html',
        comm_type_objs=comm_type_objs,
        framework=framework,
    )
def list_countersigned_agreement_file(supplier_id, framework_slug):
    supplier = data_api_client.get_supplier(supplier_id)['suppliers']
    framework = data_api_client.get_framework(framework_slug)['frameworks']
    supplier_framework = data_api_client.get_supplier_framework_info(supplier_id, framework_slug)['frameworkInterest']
    if not supplier_framework['onFramework'] or supplier_framework['agreementStatus'] in (None, 'draft'):
        abort(404)
    agreements_bucket = s3.S3(
        current_app.config['DM_AGREEMENTS_BUCKET'], endpoint_url=current_app.config.get("DM_S3_ENDPOINT_URL")
    )
    countersigned_agreement_document = agreements_bucket.get_key(supplier_framework.get('countersignedPath'))

    remove_countersigned_agreement_confirm = convert_to_boolean(request.args.get('remove_countersigned_agreement'))

    countersigned_agreement = []
    if countersigned_agreement_document:
        last_modified = datetimeformat(parse_date(countersigned_agreement_document['last_modified']))
        document_name = degenerate_document_path_and_return_doc_name(supplier_framework.get('countersignedPath'))
        countersigned_agreement = [{"last_modified": last_modified, "document_name": document_name}]

    return render_template(
        "suppliers/upload_countersigned_agreement.html",
        supplier=supplier,
        framework=framework,
        countersigned_agreement=countersigned_agreement,
        remove_countersigned_agreement_confirm=remove_countersigned_agreement_confirm
    )
def upload_countersigned_agreement_file(supplier_code, framework_slug):
    agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET'])
    errors = {}

    if request.files.get('countersigned_agreement'):
        the_file = request.files['countersigned_agreement']
        if not file_is_pdf(the_file):
            errors['countersigned_agreement'] = 'not_pdf'

        if 'countersigned_agreement' not in errors.keys():
            filename = get_agreement_document_path(
                framework_slug, supplier_code,
                COUNTERSIGNED_AGREEMENT_FILENAME)
            agreements_bucket.save(filename, the_file)

            data_api_client.create_audit_event(
                audit_type=AuditTypes.upload_countersigned_agreement,
                user=current_user.email_address,
                object_type='suppliers',
                object_id=supplier_code,
                data={'upload_countersigned_agreement': filename})

            flash('countersigned_agreement', 'upload_countersigned_agreement')

    if len(errors) > 0:
        for category, message in errors.items():
            flash(category, message)

    return redirect(
        url_for('.list_countersigned_agreement_file',
                supplier_code=supplier_code,
                framework_slug=framework_slug))
def remove_countersigned_agreement_file(supplier_id, framework_slug):
    supplier_framework = data_api_client.get_supplier_framework_info(
        supplier_id, framework_slug)['frameworkInterest']
    document = supplier_framework.get('countersignedPath')
    agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET'])

    if request.method == 'GET':
        return redirect(
            url_for('.list_countersigned_agreement_file',
                    supplier_id=supplier_id,
                    framework_slug=framework_slug) +
            "?remove_countersigned_agreement=true")

    if request.method == 'POST':
        # Remove path first - as we don't want path to exist in DB with no corresponding file in S3
        # But an orphaned file in S3 wouldn't be so bad
        data_api_client.update_framework_agreement(
            supplier_framework['agreementId'],
            {"countersignedAgreementPath": None}, current_user.email_address)
        agreements_bucket.delete_key(document)

        data_api_client.create_audit_event(
            audit_type=AuditTypes.delete_countersigned_agreement,
            user=current_user.email_address,
            object_type='suppliers',
            object_id=supplier_id,
            data={'upload_countersigned_agreement': document})

    return redirect(
        url_for('.list_countersigned_agreement_file',
                supplier_id=supplier_id,
                framework_slug=framework_slug))
def view_signed_agreement(supplier_id, framework_slug):
    # not properly validating this - all we do is pass it through
    next_status = request.args.get("next_status")

    supplier = data_api_client.get_supplier(supplier_id)['suppliers']
    framework = data_api_client.get_framework(framework_slug)['frameworks']
    if not framework.get('frameworkAgreementVersion'):
        abort(404)
    supplier_framework = data_api_client.get_supplier_framework_info(
        supplier_id, framework_slug)['frameworkInterest']
    if not supplier_framework.get('agreementReturned'):
        abort(404)

    lot_names = []
    if framework["status"] in ("live", "expired"):
        # If the framework is live or expired we don't need to filter drafts, we only care about successful services
        service_iterator = data_api_client.find_services_iter(
            supplier_id=supplier_id, framework=framework_slug)
        for service in service_iterator:
            if service['lotName'] not in lot_names:
                lot_names.append(service['lotName'])
    else:
        # If the framework has not yet become live we need to filter out unsuccessful services
        service_iterator = data_api_client.find_draft_services_iter(
            supplier_id=supplier_id, framework=framework_slug)
        for service in service_iterator:
            if service["status"] == "submitted" and service[
                    'lotName'] not in lot_names:
                lot_names.append(service['lotName'])

    agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET'])

    if framework_slug in OLD_COUNTERSIGNING_FLOW_FRAMEWORKS:
        is_e_signature_flow = False
        # Fetch path to supplier's signature page
        path = supplier_framework['agreementPath']
        template = "suppliers/view_signed_agreement.html"
    else:
        is_e_signature_flow = True
        # Fetch path to combined countersigned agreement, if available
        path = supplier_framework.get('countersignedPath')
        template = "suppliers/view_esignature_agreement.html"

    url = get_signed_url(agreements_bucket, path,
                         current_app.config['DM_ASSETS_URL']) if path else ""
    agreement_ext = get_extension(path) if path else ""

    if not url:
        current_app.logger.info(f'No agreement file found for {path}')
    return render_template(
        template,
        company_details=get_company_details_from_supplier(supplier),
        supplier=supplier,
        framework=framework,
        supplier_framework=supplier_framework,
        lot_names=list(sorted(lot_names)),
        agreement_url=url,
        agreement_ext=agreement_ext,
        next_status=next_status,
        is_e_signature_flow=is_e_signature_flow)
Пример #7
0
def download_supplier_file(framework_slug, filepath):
    uploader = s3.S3(current_app.config['DM_COMMUNICATIONS_BUCKET'])
    url = get_signed_document_url(
        uploader, "{}/communications/{}".format(framework_slug, filepath))
    if not url:
        abort(404)

    return redirect(url)
def signature_upload(framework_slug):
    framework = get_framework(data_api_client, framework_slug)
    return_supplier_framework_info_if_on_framework_or_abort(
        data_api_client, framework_slug)
    agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET'])
    signature_page = get_most_recently_uploaded_agreement_file_or_none(
        agreements_bucket, framework_slug)
    upload_error = None

    if request.method == 'POST':
        # No file chosen for upload and file already exists on s3 so can use existing and progress
        if not request.files['signature_page'].filename and signature_page:
            return redirect(
                url_for(".contract_review", framework_slug=framework_slug))

        if not file_is_image(
                request.files['signature_page']) and not file_is_pdf(
                    request.files['signature_page']):
            upload_error = "The file must be a PDF, JPG or PNG"
        elif not file_is_less_than_5mb(request.files['signature_page']):
            upload_error = "The file must be less than 5MB"
        elif file_is_empty(request.files['signature_page']):
            upload_error = "The file must not be empty"

        if not upload_error:
            upload_path = get_agreement_document_path(
                framework_slug, current_user.supplier_code, '{}{}'.format(
                    SIGNED_AGREEMENT_PREFIX,
                    get_extension(request.files['signature_page'].filename)))
            agreements_bucket.save(upload_path,
                                   request.files['signature_page'],
                                   acl='private')

            session['signature_page'] = request.files[
                'signature_page'].filename

            data_api_client.create_audit_event(
                audit_type=AuditTypes.upload_signed_agreement,
                user=current_user.email_address,
                object_type="suppliers",
                object_id=current_user.supplier_code,
                data={
                    "upload_signed_agreement":
                    request.files['signature_page'].filename,
                    "upload_path": upload_path
                })

            return redirect(
                url_for(".contract_review", framework_slug=framework_slug))

    status_code = 400 if upload_error else 200
    return render_template_with_csrf(
        "frameworks/signature_upload.html",
        status_code=status_code,
        framework=framework,
        signature_page=signature_page,
        upload_error=upload_error,
    )
def update_service(service_id, section_id, question_slug=None):
    service = data_api_client.get_service(service_id)
    if service is None:
        abort(404)
    service = service['services']

    # we don't actually need the framework here; using this to 404 if framework for the service is not live
    # TODO remove `expired` from below. It's a temporary fix to allow access to DOS2 as it's expired.
    get_framework_or_404(data_api_client,
                         service['frameworkSlug'],
                         allowed_statuses=['live', 'expired'])

    content = content_loader.get_manifest(
        service['frameworkSlug'],
        'edit_service_as_admin',
    ).filter(service, inplace_allowed=True)
    section = content.get_section(section_id)
    if question_slug is not None:
        # Overwrite section with single question section for 'question per page' editing.
        section = section.get_question_as_section(question_slug)
    if section is None or not section.editable:
        abort(404)

    errors = None
    posted_data = section.get_data(request.form)

    uploaded_documents, document_errors = upload_service_documents(
        s3.S3(current_app.config['DM_S3_DOCUMENT_BUCKET']), 'documents',
        current_app.config['DM_ASSETS_URL'], service, request.files, section)

    if document_errors:
        errors = section.get_error_messages(document_errors)
    else:
        posted_data.update(uploaded_documents)

    if not errors and section.has_changes_to_save(service, posted_data):
        try:
            data_api_client.update_service(
                service_id,
                posted_data,
                current_user.email_address,
                user_role='admin',
            )
        except HTTPError as e:
            errors = section.get_error_messages(e.message)

    if errors:
        return render_template(
            "edit_section.html",
            section=section,
            service_data=section.unformat_data(dict(service, **posted_data)),
            service_id=service_id,
            errors=govuk_errors(errors),
        ), 400

    return redirect(url_for(".view_service", service_id=service_id))
def upload_countersigned_agreement_file(supplier_id, framework_slug):
    supplier_framework = data_api_client.get_supplier_framework_info(
        supplier_id, framework_slug)['frameworkInterest']
    if not supplier_framework['onFramework'] or supplier_framework[
            'agreementStatus'] in (None, 'draft'):
        abort(404)
    agreement_id = supplier_framework['agreementId']
    agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET'])
    errors = {}

    if request.files.get('countersigned_agreement'):
        the_file = request.files['countersigned_agreement']
        if not file_is_pdf(the_file):
            errors['countersigned_agreement'] = 'not_pdf'
            flash(COUNTERSIGNED_AGREEMENT_NOT_PDF_MESSAGE)

        if 'countersigned_agreement' not in errors.keys():
            supplier_name = supplier_framework.get(
                'declaration', {}).get('nameOfOrganisation')
            if not supplier_name:
                supplier_name = data_api_client.get_supplier(
                    supplier_id)['suppliers']['name']
            if supplier_framework['agreementStatus'] not in [
                    'approved', 'countersigned'
            ]:
                data_api_client.approve_agreement_for_countersignature(
                    agreement_id, current_user.email_address, current_user.id)

            path = generate_timestamped_document_upload_path(
                framework_slug, supplier_id, 'agreements',
                COUNTERPART_FILENAME)
            download_filename = generate_download_filename(
                supplier_id, COUNTERPART_FILENAME, supplier_name)
            agreements_bucket.save(path,
                                   the_file,
                                   acl='bucket-owner-full-control',
                                   move_prefix=None,
                                   download_filename=download_filename)

            data_api_client.update_framework_agreement(
                agreement_id, {"countersignedAgreementPath": path},
                current_user.email_address)

            data_api_client.create_audit_event(
                audit_type=AuditTypes.upload_countersigned_agreement,
                user=current_user.email_address,
                object_type='suppliers',
                object_id=supplier_id,
                data={'upload_countersigned_agreement': path})

            flash(UPLOAD_COUNTERSIGNED_AGREEMENT_MESSAGE)

    return redirect(
        url_for('.list_countersigned_agreement_file',
                supplier_id=supplier_id,
                framework_slug=framework_slug))
def service_submission_document(framework_slug, supplier_id, document_name):
    if current_user.supplier_id != supplier_id:
        abort(404)

    uploader = s3.S3(current_app.config['DM_SUBMISSIONS_BUCKET'])
    s3_url = get_signed_document_url(uploader,
                                     "{}/submissions/{}/{}".format(framework_slug, supplier_id, document_name))
    if not s3_url:
        abort(404)

    return redirect(s3_url)
Пример #12
0
def download_supplier_user_research_report(framework_slug):

    reports_bucket = s3.S3(current_app.config['DM_REPORTS_BUCKET'])
    path = "{framework_slug}/reports/user-research-suppliers-on-{framework_slug}.csv"
    url = get_signed_url(reports_bucket,
                         path.format(framework_slug=framework_slug),
                         current_app.config['DM_ASSETS_URL'])
    if not url:
        abort(404)

    return redirect(url)
def download_agreement_file(supplier_id, framework_slug, document_name):
    supplier_framework = data_api_client.get_supplier_framework_info(supplier_id, framework_slug)['frameworkInterest']
    if supplier_framework is None or not supplier_framework.get("declaration"):
        abort(404)

    agreements_bucket = s3.S3(
        current_app.config['DM_AGREEMENTS_BUCKET'], endpoint_url=current_app.config.get("DM_S3_ENDPOINT_URL")
    )
    path = get_document_path(framework_slug, supplier_id, 'agreements', document_name)
    url = get_signed_url(agreements_bucket, path, current_app.config['DM_ASSETS_URL'])
    if not url:
        abort(404)

    return redirect(url)
Пример #14
0
def download_communication(framework_slug, comm_type, filepath):
    if comm_type not in _comm_types:
        abort(404)

    # ensure this is a real framework
    get_framework_or_404(data_api_client, framework_slug)

    bucket = s3.S3(current_app.config['DM_COMMUNICATIONS_BUCKET'])
    full_path = _get_comm_type_root(framework_slug, comm_type) / filepath
    url = get_signed_url(bucket, str(full_path),
                         current_app.config["DM_ASSETS_URL"])
    if not url:
        abort(404)

    return redirect(url)
Пример #15
0
def download_agreement_file(supplier_id, framework_slug, document_name):
    supplier_framework = data_api_client.get_supplier_framework_info(supplier_id, framework_slug)['frameworkInterest']
    if not supplier_framework.get('declaration'):
        abort(404)
    agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET'])
    prefix = get_agreement_document_path(framework_slug, supplier_id, document_name)
    agreement_documents = agreements_bucket.list(prefix=prefix)
    if not len(agreement_documents):
        abort(404)
    path = agreement_documents[-1]['path']
    url = get_signed_url(agreements_bucket, path, current_app.config['DM_ASSETS_URL'])
    if not url:
        abort(404)

    return redirect(url)
Пример #16
0
def manage_communications(framework_slug):
    communications_bucket = s3.S3(current_app.config['DM_COMMUNICATIONS_BUCKET'])
    framework = data_api_client.get_framework(framework_slug)['frameworks']

    itt_pack = next(iter(communications_bucket.list(_get_itt_pack_path(framework_slug))), None)
    clarification = next(iter(communications_bucket.list(_get_path(framework_slug, 'updates/clarifications'))), None)
    communication = next(iter(communications_bucket.list(_get_path(framework_slug, 'updates/communications'))), None)

    return render_template(
        'manage_communications.html',
        itt_pack=itt_pack,
        clarification=clarification,
        communication=communication,
        framework=framework
    )
Пример #17
0
def download_supplier_user_list_report(framework_slug, report_type):
    reports_bucket = s3.S3(current_app.config['DM_REPORTS_BUCKET'])

    if report_type == 'official':
        path = f"{framework_slug}/reports/official-details-for-suppliers-{framework_slug}.csv"
    elif report_type == 'accounts':
        path = f"{framework_slug}/reports/all-email-accounts-for-suppliers-{framework_slug}.csv"
    else:
        abort(404)

    url = get_signed_url(reports_bucket, path,
                         current_app.config['DM_ASSETS_URL'])
    if not url:
        abort(404)

    return redirect(url)
Пример #18
0
def download_agreement_file(framework_slug, document_name):
    supplier_framework_info = get_supplier_framework_info(
        data_api_client, framework_slug)
    if supplier_framework_info is None or not supplier_framework_info.get(
            "declaration"):
        abort(404)

    agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET'])
    path = get_document_path(framework_slug, current_user.supplier_id,
                             'agreements', document_name)
    url = get_signed_url(agreements_bucket, path,
                         current_app.config['DM_ASSETS_URL'])
    if not url:
        abort(404)

    return redirect(url)
Пример #19
0
def upload_communication(framework_slug):
    communications_bucket = s3.S3(
        current_app.config['DM_COMMUNICATIONS_BUCKET'],
        endpoint_url=current_app.config.get("DM_S3_ENDPOINT_URL"))
    errors = {}

    if request.files.get('communication'):
        the_file = request.files['communication']
        if not (file_is_open_document_format(the_file)
                or file_is_csv(the_file)):
            errors['communication'] = 'not_open_document_format_or_csv'
            flash(
                'Communication file is not an open document format or a CSV.',
                'error')

        if 'communication' not in errors.keys():
            path = "{}/communications/updates/communications/{}".format(
                framework_slug, the_file.filename)
            communications_bucket.save(path,
                                       the_file,
                                       acl='bucket-owner-full-control',
                                       download_filename=the_file.filename)
            flash('New communication was uploaded.')

    if request.files.get('clarification'):
        the_file = request.files['clarification']
        if not file_is_pdf(the_file):
            errors['clarification'] = 'not_pdf'
            flash('Clarification file is not a PDF.', 'error')

        if 'clarification' not in errors.keys():
            path = "{}/communications/updates/clarifications/{}".format(
                framework_slug, the_file.filename)
            communications_bucket.save(path,
                                       the_file,
                                       acl='bucket-owner-full-control',
                                       download_filename=the_file.filename)
            flash('New clarification was uploaded.')

    return redirect(
        url_for('.manage_communications', framework_slug=framework_slug))
Пример #20
0
def framework_updates(framework_slug,
                      error_message=None,
                      default_textbox_value=None):
    framework = get_framework(data_api_client, framework_slug)
    supplier_framework_info = get_supplier_framework_info(
        data_api_client, framework_slug)

    current_app.logger.info(
        "{framework_slug}-updates.viewed: user_id {user_id} supplier_id {supplier_id}",
        extra={
            'framework_slug': framework_slug,
            'user_id': current_user.id,
            'supplier_id': current_user.supplier_id
        })

    communications_bucket = s3.S3(
        current_app.config['DM_COMMUNICATIONS_BUCKET'])
    file_list = communications_bucket.list(
        '{}/communications/updates/'.format(framework_slug),
        load_timestamps=True)
    files = {
        'communications': [],
        'clarifications': [],
    }
    for file in file_list:
        path_parts = file['path'].split('/')
        file['path'] = '/'.join(path_parts[2:])
        files[path_parts[3]].append(file)

    return render_template(
        "frameworks/updates.html",
        framework=framework,
        clarification_question_name=CLARIFICATION_QUESTION_NAME,
        clarification_question_value=default_textbox_value,
        error_message=error_message,
        files=files,
        dates=content_loader.get_message(framework_slug, 'dates'),
        agreement_countersigned=bool(
            supplier_framework_info
            and supplier_framework_info['countersignedPath']),
    ), 200 if not error_message else 400
Пример #21
0
def list_countersigned_agreement_file(supplier_id, framework_slug):
    supplier = data_api_client.get_supplier(supplier_id)['suppliers']
    framework = data_api_client.get_framework(framework_slug)['frameworks']
    agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET'])
    path = get_agreement_document_path(framework_slug, supplier_id, COUNTERSIGNED_AGREEMENT_FILENAME)
    countersigned_agreement_document = agreements_bucket.get_key(path)
    if countersigned_agreement_document:
        countersigned_agreement = countersigned_agreement_document
        countersigned_agreement['last_modified'] = datetimeformat(parse_date(
            countersigned_agreement['last_modified']))
        countersigned_agreement = [countersigned_agreement]
    else:
        countersigned_agreement = []

    return render_template(
        "suppliers/upload_countersigned_agreement.html",
        supplier=supplier,
        framework=framework,
        countersigned_agreement=countersigned_agreement,
        countersigned_agreement_filename=COUNTERSIGNED_AGREEMENT_FILENAME
    )
def remove_countersigned_agreement_file(supplier_code, framework_slug):
    agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET'])
    document = get_agreement_document_path(framework_slug, supplier_code,
                                           COUNTERSIGNED_AGREEMENT_FILENAME)

    if request.method == 'GET':
        flash('countersigned_agreement', 'remove_countersigned_agreement')

    if request.method == 'POST':
        agreements_bucket.delete_key(document)

        data_api_client.create_audit_event(
            audit_type=AuditTypes.delete_countersigned_agreement,
            user=current_user.email_address,
            object_type='suppliers',
            object_id=supplier_code,
            data={'upload_countersigned_agreement': document})

    return redirect(
        url_for('.list_countersigned_agreement_file',
                supplier_code=supplier_code,
                framework_slug=framework_slug))
Пример #23
0
def download_dos_outcomes():
    # get the slug for the latest DOS framework iteration
    framework_slug = (sorted(
        filter(
            lambda fw: (fw["family"] == "digital-outcomes-and-specialists" and
                        fw["status"] == "live"),
            data_api_client.find_frameworks()["frameworks"]),
        key=lambda fw: fw["frameworkLiveAtUTC"],
        reverse=True,
    )[0]["slug"])

    reports_bucket = s3.S3(
        current_app.config["DM_REPORTS_BUCKET"],
        endpoint_url=current_app.config.get("DM_S3_ENDPOINT_URL"))
    url = get_signed_url(reports_bucket,
                         f"{framework_slug}/reports/opportunity-data.csv",
                         current_app.config["DM_ASSETS_URL"])

    if not url:
        abort(404)

    return redirect(url)
Пример #24
0
def upload_communication(framework_slug):
    communications_bucket = s3.S3(current_app.config['DM_COMMUNICATIONS_BUCKET'])
    errors = {}

    if request.files.get('communication'):
        the_file = request.files['communication']
        if not (file_is_pdf(the_file) or file_is_csv(the_file)):
            errors['communication'] = 'not_pdf_or_csv'

        if 'communication' not in list(errors.keys()):
            filename = _get_path(framework_slug, 'updates/communications') + '/' + the_file.filename
            communications_bucket.save(filename, the_file)
            flash('communication', 'upload_communication')

    if request.files.get('clarification'):
        the_file = request.files['clarification']
        if not file_is_pdf(the_file):
            errors['clarification'] = 'not_pdf'

        if 'clarification' not in list(errors.keys()):
            filename = _get_path(framework_slug, 'updates/clarifications') + '/' + the_file.filename
            communications_bucket.save(filename, the_file)
            flash('clarification', 'upload_communication')

    if request.files.get('itt_pack'):
        the_file = request.files['itt_pack']
        if not file_is_zip(the_file):
            errors['itt_pack'] = 'not_zip'

        if 'itt_pack' not in list(errors.keys()):
            filename = _get_itt_pack_path(framework_slug)
            communications_bucket.save(filename, the_file)
            flash('itt_pack', 'upload_communication')

    if len(errors) > 0:
        for category, message in list(errors.items()):
            flash(category, message)
    return redirect(url_for('.manage_communications', framework_slug=framework_slug))
Пример #25
0
def contract_review(framework_slug, agreement_id):
    framework = get_framework(data_api_client,
                              framework_slug,
                              allowed_statuses=['standstill', 'live'])
    # if there's no frameworkAgreementVersion key it means we're pre-G-Cloud 8 and shouldn't be using this route
    if not framework.get('frameworkAgreementVersion'):
        abort(404)
    supplier_framework = return_supplier_framework_info_if_on_framework_or_abort(
        data_api_client, framework_slug)
    agreement = data_api_client.get_framework_agreement(
        agreement_id)['agreement']
    check_agreement_is_related_to_supplier_framework_or_abort(
        agreement, supplier_framework)

    # if framework agreement doesn't have a name or a role or the agreement file, then 404
    if not (agreement.get('signedAgreementDetails')
            and agreement['signedAgreementDetails'].get('signerName')
            and agreement['signedAgreementDetails'].get('signerRole')
            and agreement.get('signedAgreementPath')):
        abort(404)

    agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET'])
    signature_page = agreements_bucket.get_key(
        agreement['signedAgreementPath'])

    form = ContractReviewForm()
    form_errors = None

    if request.method == 'POST':
        if form.validate_on_submit():
            data_api_client.sign_framework_agreement(
                agreement_id, current_user.email_address,
                {'uploaderUserId': current_user.id})

            try:
                email_body = render_template(
                    'emails/framework_agreement_with_framework_version_returned.html',
                    framework_name=framework['name'],
                    framework_slug=framework['slug'],
                    framework_live_date=content_loader.get_message(
                        framework_slug,
                        'dates')['framework_live_date'],  # noqa
                )

                send_email(
                    returned_agreement_email_recipients(supplier_framework),
                    email_body,
                    current_app.config['DM_MANDRILL_API_KEY'],
                    'Your {} signature page has been received'.format(
                        framework['name']),
                    current_app.config["DM_GENERIC_NOREPLY_EMAIL"],
                    current_app.config["FRAMEWORK_AGREEMENT_RETURNED_NAME"],
                    ['{}-framework-agreement'.format(framework_slug)],
                )
            except MandrillException as e:
                current_app.logger.error(
                    "Framework agreement email failed to send. "
                    "error {error} supplier_id {supplier_id} email_hash {email_hash}",
                    extra={
                        'error': six.text_type(e),
                        'supplier_id': current_user.supplier_id,
                        'email_hash': hash_email(current_user.email_address)
                    })
                abort(503, "Framework agreement email failed to send")

            session.pop('signature_page', None)

            flash(
                'Your framework agreement has been returned to the Crown Commercial Service to be countersigned.',
                'success')

            if feature.is_active('CONTRACT_VARIATION'):
                # Redirect to contract variation if it has not been signed
                if (framework.get('variations')
                        and not supplier_framework['agreedVariations']):
                    variation_slug = list(framework['variations'].keys())[0]
                    return redirect(
                        url_for('.view_contract_variation',
                                framework_slug=framework_slug,
                                variation_slug=variation_slug))

            return redirect(
                url_for(".framework_dashboard", framework_slug=framework_slug))

        else:
            form_errors = [{
                'question': form['authorisation'].label.text,
                'input_name': 'authorisation'
            }]

    form.authorisation.description = u"I have the authority to return this agreement on behalf of {}.".format(
        supplier_framework['declaration']['nameOfOrganisation'])

    return render_template(
        "frameworks/contract_review.html",
        agreement=agreement,
        form=form,
        form_errors=form_errors,
        framework=framework,
        signature_page=signature_page,
        supplier_framework=supplier_framework,
    ), 400 if form_errors else 200
Пример #26
0
def signature_upload(framework_slug, agreement_id):
    framework = get_framework(data_api_client,
                              framework_slug,
                              allowed_statuses=['standstill', 'live'])
    # if there's no frameworkAgreementVersion key it means we're pre-G-Cloud 8 and shouldn't be using this route
    if not framework.get('frameworkAgreementVersion'):
        abort(404)
    supplier_framework = return_supplier_framework_info_if_on_framework_or_abort(
        data_api_client, framework_slug)
    agreement = data_api_client.get_framework_agreement(
        agreement_id)['agreement']
    check_agreement_is_related_to_supplier_framework_or_abort(
        agreement, supplier_framework)

    agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET'])
    signature_page = agreements_bucket.get_key(
        agreement.get('signedAgreementPath'))
    upload_error = None

    if request.method == 'POST':
        # No file chosen for upload and file already exists on s3 so can use existing and progress
        if not request.files['signature_page'].filename and signature_page:
            return redirect(
                url_for(".contract_review",
                        framework_slug=framework_slug,
                        agreement_id=agreement_id))

        if not file_is_image(
                request.files['signature_page']) and not file_is_pdf(
                    request.files['signature_page']):
            upload_error = "The file must be a PDF, JPG or PNG"
        elif not file_is_less_than_5mb(request.files['signature_page']):
            upload_error = "The file must be less than 5MB"
        elif file_is_empty(request.files['signature_page']):
            upload_error = "The file must not be empty"

        if not upload_error:
            extension = get_extension(request.files['signature_page'].filename)
            upload_path = generate_timestamped_document_upload_path(
                framework_slug, current_user.supplier_id, 'agreements',
                '{}{}'.format(SIGNED_AGREEMENT_PREFIX, extension))
            agreements_bucket.save(
                upload_path,
                request.files['signature_page'],
                acl='private',
                download_filename='{}-{}-{}{}'.format(
                    sanitise_supplier_name(current_user.supplier_name),
                    current_user.supplier_id, SIGNED_SIGNATURE_PAGE_PREFIX,
                    extension),
                disposition_type=
                'inline'  # Embeddeding PDFs in admin pages requires 'inline' and not 'attachment'
            )

            data_api_client.update_framework_agreement(
                agreement_id, {"signedAgreementPath": upload_path},
                current_user.email_address)

            session['signature_page'] = request.files[
                'signature_page'].filename

            return redirect(
                url_for(".contract_review",
                        framework_slug=framework_slug,
                        agreement_id=agreement_id))

    return render_template(
        "frameworks/signature_upload.html",
        agreement=agreement,
        framework=framework,
        signature_page=signature_page,
        upload_error=upload_error,
    ), 400 if upload_error else 200
Пример #27
0
def upload_framework_agreement(framework_slug):
    """
    This is the route used to upload agreements for pre-G-Cloud 8 frameworks
    """
    framework = get_framework(data_api_client,
                              framework_slug,
                              allowed_statuses=['standstill', 'live'])
    # if there's a frameworkAgreementVersion key it means we're on G-Cloud 8 or higher and shouldn't be using this route
    if framework.get('frameworkAgreementVersion'):
        abort(404)

    supplier_framework = return_supplier_framework_info_if_on_framework_or_abort(
        data_api_client, framework_slug)

    upload_error = None
    if not file_is_less_than_5mb(request.files['agreement']):
        upload_error = "Document must be less than 5MB"
    elif file_is_empty(request.files['agreement']):
        upload_error = "Document must not be empty"

    if upload_error is not None:
        return render_template(
            "frameworks/agreement.html",
            framework=framework,
            supplier_framework=supplier_framework,
            upload_error=upload_error,
            agreement_filename=AGREEMENT_FILENAME,
        ), 400

    agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET'])
    extension = get_extension(request.files['agreement'].filename)

    path = generate_timestamped_document_upload_path(
        framework_slug, current_user.supplier_id, 'agreements',
        '{}{}'.format(SIGNED_AGREEMENT_PREFIX, extension))
    agreements_bucket.save(
        path,
        request.files['agreement'],
        acl='private',
        download_filename='{}-{}-{}{}'.format(
            sanitise_supplier_name(current_user.supplier_name),
            current_user.supplier_id, SIGNED_AGREEMENT_PREFIX, extension))

    agreement_id = data_api_client.create_framework_agreement(
        current_user.supplier_id, framework_slug,
        current_user.email_address)['agreement']['id']
    data_api_client.update_framework_agreement(agreement_id,
                                               {"signedAgreementPath": path},
                                               current_user.email_address)
    data_api_client.sign_framework_agreement(
        agreement_id, current_user.email_address,
        {"uploaderUserId": current_user.id})

    try:
        email_body = render_template(
            'emails/framework_agreement_uploaded.html',
            framework_name=framework['name'],
            supplier_name=current_user.supplier_name,
            supplier_id=current_user.supplier_id,
            user_name=current_user.name)
        send_email(
            current_app.config['DM_FRAMEWORK_AGREEMENTS_EMAIL'],
            email_body,
            current_app.config['DM_MANDRILL_API_KEY'],
            '{} framework agreement'.format(framework['name']),
            current_app.config["DM_GENERIC_NOREPLY_EMAIL"],
            '{} Supplier'.format(framework['name']),
            ['{}-framework-agreement'.format(framework_slug)],
            reply_to=current_user.email_address,
        )
    except MandrillException as e:
        current_app.logger.error(
            "Framework agreement email failed to send. "
            "error {error} supplier_id {supplier_id} email_hash {email_hash}",
            extra={
                'error': six.text_type(e),
                'supplier_id': current_user.supplier_id,
                'email_hash': hash_email(current_user.email_address)
            })
        abort(503, "Framework agreement email failed to send")

    return redirect(
        url_for('.framework_agreement', framework_slug=framework_slug))
Пример #28
0
def framework_dashboard(framework_slug):
    framework = get_framework(data_api_client, framework_slug)
    if request.method == 'POST':
        register_interest_in_framework(data_api_client, framework_slug)
        supplier_users = data_api_client.find_users(
            supplier_id=current_user.supplier_id)

        try:
            email_body = render_template(
                'emails/{}_application_started.html'.format(framework_slug))
            send_email([
                user['emailAddress']
                for user in supplier_users['users'] if user['active']
            ], email_body, current_app.config['DM_MANDRILL_API_KEY'],
                       'You started a {} application'.format(
                           framework['name']),
                       current_app.config['CLARIFICATION_EMAIL_FROM'],
                       current_app.config['CLARIFICATION_EMAIL_NAME'],
                       ['{}-application-started'.format(framework_slug)])
        except MandrillException as e:
            current_app.logger.error(
                "Application started email failed to send: {error}, supplier_id: {supplier_id}",
                extra={
                    'error': six.text_type(e),
                    'supplier_id': current_user.supplier_id
                })

    drafts, complete_drafts = get_drafts(data_api_client, framework_slug)

    supplier_framework_info = get_supplier_framework_info(
        data_api_client, framework_slug)
    declaration_status = get_declaration_status_from_info(
        supplier_framework_info)
    supplier_is_on_framework = get_supplier_on_framework_from_info(
        supplier_framework_info)

    # Do not show a framework dashboard for earlier G-Cloud iterations
    if declaration_status == 'unstarted' and framework['status'] == 'live':
        abort(404)

    application_made = supplier_is_on_framework or (
        len(complete_drafts) > 0 and declaration_status == 'complete')
    lots_with_completed_drafts = [
        lot for lot in framework['lots']
        if count_drafts_by_lot(complete_drafts, lot['slug'])
    ]

    first_page = content_loader.get_manifest(
        framework_slug, 'declaration').get_next_editable_section_id()
    framework_dates = content_loader.get_message(framework_slug, 'dates')
    framework_urls = content_loader.get_message(framework_slug, 'urls')

    # filenames
    result_letter_filename = RESULT_LETTER_FILENAME

    countersigned_agreement_file = None
    if supplier_framework_info and supplier_framework_info['countersignedPath']:
        countersigned_agreement_file = degenerate_document_path_and_return_doc_name(
            supplier_framework_info['countersignedPath'])

    signed_agreement_document_name = None
    if supplier_is_on_framework and supplier_framework_info[
            'agreementReturned']:
        signed_agreement_document_name = degenerate_document_path_and_return_doc_name(
            supplier_framework_info['agreementPath'])

    key_list = s3.S3(current_app.config['DM_COMMUNICATIONS_BUCKET']).list(
        framework_slug, load_timestamps=True)
    key_list.reverse()

    base_communications_files = {
        "invitation": {
            "path": "communications/",
            "filename": "{}-invitation.pdf".format(framework_slug),
        },
        "proposed_agreement": {
            "path":
            "communications/",
            "filename":
            "{}-proposed-framework-agreement.pdf".format(framework_slug),
        },
        "final_agreement": {
            "path": "communications/",
            "filename":
            "{}-final-framework-agreement.pdf".format(framework_slug),
        },
        "proposed_call_off": {
            "path": "communications/",
            "filename": "{}-proposed-call-off.pdf".format(framework_slug),
        },
        "final_call_off": {
            "path": "communications/",
            "filename": "{}-final-call-off.pdf".format(framework_slug),
        },
        "reporting_template": {
            "path": "communications/",
            "filename": "{}-reporting-template.xls".format(framework_slug),
        },
        "supplier_updates": {
            "path": "communications/updates/",
        },
    }
    # now we annotate these with last_modified information which also tells us whether the file exists
    communications_files = {
        label: dict(
            d,
            last_modified=get_last_modified_from_first_matching_file(
                key_list,
                framework_slug,
                d["path"] + d.get("filename", ""),
            ),
        )
        for label, d in six.iteritems(base_communications_files)
    }

    return render_template(
        "frameworks/dashboard.html",
        application_made=application_made,
        communications_files=communications_files,
        completed_lots=tuple(
            dict(lot,
                 complete_count=count_drafts_by_lot(complete_drafts,
                                                    lot['slug']))
            for lot in lots_with_completed_drafts),
        countersigned_agreement_file=countersigned_agreement_file,
        counts={
            "draft": len(drafts),
            "complete": len(complete_drafts)
        },
        declaration_status=declaration_status,
        signed_agreement_document_name=signed_agreement_document_name,
        first_page_of_declaration=first_page,
        framework=framework,
        framework_dates=framework_dates,
        framework_urls=framework_urls,
        result_letter_filename=result_letter_filename,
        supplier_framework=supplier_framework_info,
        supplier_is_on_framework=supplier_is_on_framework,
    ), 200

def get_bucket_name(stage):
    return 'digitalmarketplace-agreements-{0}-{0}'.format(stage)


if __name__ == '__main__':
    arguments = docopt(__doc__)

    data_api_url = get_api_endpoint_from_stage(arguments['<stage>'], 'api')
    client = DataAPIClient(data_api_url,
                           get_auth_token('api', arguments['<stage>']))

    FRAMEWORKS = ['g-cloud-7', 'g-cloud-8', 'digital-outcomes-and-specialists']
    BUCKET_NAME = get_bucket_name(arguments['<stage>'])
    BUCKET = s3.S3(BUCKET_NAME)
    print("STARTED AT {}".format(time.strftime('%X %x %Z')))
    for framework_slug in FRAMEWORKS:
        # Get all supplier frameworks who have returned their agreement
        supplier_frameworks = client.find_framework_suppliers(
            framework_slug=framework_slug,
            agreement_returned=True)['supplierFrameworks']

        for supplier_framework in supplier_frameworks:
            print("======================")
            print("Supplier ID: {}, Agreement ID: {}".format(
                supplier_framework['supplierId'],
                supplier_framework['agreementId']))

            # Get their framework agreement
            framework_agreement = client.get_framework_agreement(
def contract_review(framework_slug):
    framework = get_framework(data_api_client, framework_slug)
    supplier_framework = return_supplier_framework_info_if_on_framework_or_abort(
        data_api_client, framework_slug)
    agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET'])
    signature_page = get_most_recently_uploaded_agreement_file_or_none(
        agreements_bucket, framework_slug)

    # if supplier_framework doesn't have a name or a role or the agreement file, then 404
    if not (supplier_framework['agreementDetails']
            and supplier_framework['agreementDetails'].get('signerName')
            and supplier_framework['agreementDetails'].get('signerRole')
            and signature_page):
        abort(404)

    form = ContractReviewForm(request.form)
    form_errors = None

    if request.method == 'POST':
        if form.validate():
            data_api_client.register_framework_agreement_returned(
                current_user.supplier_code, framework_slug,
                current_user.email_address, current_user.id)

            email_recipients = [
                supplier_framework['declaration']['primaryContactEmail']
            ]
            if supplier_framework['declaration']['primaryContactEmail'].lower(
            ) != current_user.email_address.lower():
                email_recipients.append(current_user.email_address)

            try:
                email_body = render_template(
                    'emails/framework_agreement_with_framework_version_returned.html',
                    framework_name=framework['name'],
                    framework_slug=framework['slug'],
                    framework_live_date=content_loader.get_message(
                        framework_slug,
                        'dates')['framework_live_date'],  # noqa
                )

                send_email(
                    email_recipients,
                    email_body,
                    'Your {} signature page has been received'.format(
                        framework['name']),
                    current_app.config["DM_GENERIC_NOREPLY_EMAIL"],
                    current_app.config["FRAMEWORK_AGREEMENT_RETURNED_NAME"],
                    ['{}-framework-agreement'.format(framework_slug)],
                )
            except EmailError as e:
                rollbar.report_exc_info()
                current_app.logger.error(
                    "Framework agreement email failed to send. "
                    "error {error} supplier_code {supplier_code} email_hash {email_hash}",
                    extra={
                        'error': six.text_type(e),
                        'supplier_code': current_user.supplier_code,
                        'email_hash': hash_email(current_user.email_address)
                    })
                abort(503, "Framework agreement email failed to send")

            session.pop('signature_page', None)

            flash(
                'Your framework agreement has been returned to the Crown Commercial Service to be countersigned.',
                'success')

            return redirect(
                url_for(".framework_dashboard", framework_slug=framework_slug))

        else:
            form_errors = [{
                'question': form['authorisation'].label.text,
                'input_name': 'authorisation'
            }]

    form.authorisation.description = u"I have the authority to return this agreement on behalf of {}.".format(
        supplier_framework['declaration']['nameOfOrganisation'])

    status_code = 400 if form_errors else 200
    return render_template_with_csrf(
        "frameworks/contract_review.html",
        status_code=status_code,
        form=form,
        form_errors=form_errors,
        framework=framework,
        signature_page=signature_page,
        supplier_framework=supplier_framework,
    )