def download_brief_responses(brief_id):
    brief = Brief.query.filter(
        Brief.id == brief_id
    ).first_or_404()
    brief_user_ids = [user.id for user in brief.users]
    if current_user.id not in brief_user_ids:
        return forbidden("Unauthorised to view brief or brief does not exist")
    if brief.status != 'closed':
        return forbidden("You can only download documents for closed briefs")

    response = ('', 404)
    if brief.lot.slug in ['digital-professionals', 'training', 'rfx', 'atm']:
        try:
            file = s3_download_file(
                'brief-{}-resumes.zip'.format(brief_id),
                os.path.join(brief.framework.slug, 'archives', 'brief-{}'.format(brief_id))
            )
        except botocore.exceptions.ClientError as e:
            rollbar.report_exc_info()
            not_found("Brief documents not found for brief id '{}'".format(brief_id))

        response = Response(file, mimetype='application/zip')
        response.headers['Content-Disposition'] = 'attachment; filename="brief-{}-responses.zip"'.format(brief_id)
    elif brief.lot.slug == 'digital-outcome':
        responses = BriefResponse.query.filter(
            BriefResponse.brief_id == brief_id,
            BriefResponse.withdrawn_at.is_(None)
        ).all()
        csvdata = generate_brief_responses_csv(brief, responses)
        response = Response(csvdata, mimetype='text/csv')
        response.headers['Content-Disposition'] = (
            'attachment; filename="responses-to-requirements-{}.csv"'.format(brief_id))

    return response
def decline_join_request(team_id, token):
    data = get_json_from_request()

    if 'reason' not in data or not data['reason']:
        abort('Must provide reason for decline')

    try:
        team = team_business.get_team(team_id)
    except NotFoundError as e:
        return not_found(e.message)
    except UnauthorisedError as e:
        return forbidden(e.message)

    join_request = team_business.get_join_request(token)
    if not join_request or int(join_request.data['team_id']) != team_id:
        return not_found(
            'The token is invalid, or this request has already been declined or accepted'
        )

    if ('user_id' in join_request.data
            and (team_member_service.get_team_members_by_user_id(
                team_id, [int(join_request.data['user_id'])])
                 or team_member_service.get_team_leads_by_user_id(
                     team_id, [int(join_request.data['user_id'])]))):
        abort('This user is already a member of the team')

    team_business.decline_join_request(join_request, data['reason'], team_id)

    return jsonify(success=True)
def supplier_edit(supplier_code):
    """Seller edit (role=supplier)
    ---
    tags:
      - seller edit
    definitions:
      SellerEdit:
        type: object
        properties:
            supplier:
              type: object
              properties:
                abn:
                  type: string
                code:
                  type: string
                name:
                  type: string
                data:
                  type: object
            agreementStatus:
              type: object
              properties:
                canSign:
                    type: boolean
                canUserSign:
                    type: boolean
                show:
                    type: boolean
                signed:
                    type: boolean
                startDate:
                    type: string
    parameters:
      - name: supplier_code
        in: path
        type: number
        required: true
    responses:
      200:
        description: Supplier edit info
        schema:
          $ref: '#/definitions/SellerEdit'
      403:
        description: Unauthorised to get info.

    """

    if current_user.supplier_code != supplier_code:
        return forbidden('Unauthorised to get info')

    info = seller_edit_business.get_supplier_edit_info({
        'supplier_code':
        current_user.supplier_code,
        'email_address':
        current_user.email_address
    })
    return jsonify(info), 200
def update_supplier(supplier_code):
    """Update supplier (role=supplier)
    ---
    tags:
      - seller edit
    parameters:
      - name: supplier_code
        in: path
        type: number
        required: true
      - name: body
        in: body
        required: true
        schema:
            $ref: '#/definitions/SellerEdit'
    responses:
        200:
            description: Supplier updated successfully.
            schema:
                $ref: '#/definitions/SellerEdit'
        400:
            description: Bad request.
        403:
            description: Unauthorised to update supplier.
        404:
            description: Supplier not found.
        500:
            description: Unexpected error.
    """

    if current_user.supplier_code != supplier_code:
        return forbidden('Unauthorised to update supplier')

    try:
        data = get_json_from_request()
        seller_edit_business.update_supplier(
            data, {
                'supplier_code': current_user.supplier_code,
                'email_address': current_user.email_address,
                'name': current_user.name,
                'role': current_user.role
            })
    except NotFoundError as nfe:
        not_found(nfe.message)
    except DeletedError as de:
        abort(de.message)
    except ValidationError as ve:
        abort(ve.message)

    info = seller_edit_business.get_supplier_edit_info({
        'supplier_code':
        current_user.supplier_code,
        'email_address':
        current_user.email_address
    })
    return jsonify(info), 200
Example #5
0
def get_questions(brief_id):
    result = None
    try:
        result = questions_business.get_questions(current_user, brief_id)
    except NotFoundError as nfe:
        not_found(nfe.message)
    except UnauthorisedError as ue:
        return forbidden(ue.message)

    return jsonify(result), 200
def get_team(team_id):
    team = None
    try:
        team = team_business.get_team(team_id)
    except NotFoundError as e:
        return not_found(e.message)
    except UnauthorisedError as e:
        return forbidden(e.message)

    return jsonify(team)
def create_team():
    try:
        team = team_business.create_team()
    except TeamError as e:
        abort(e.message)
    except NotFoundError as e:
        return not_found(e.message)
    except UnauthorisedError as e:
        return forbidden(e.message)

    return jsonify(team)
def request_to_join(team_id):
    try:
        team_business.request_to_join(current_user.email_address, team_id,
                                      current_user.agency_id)
    except NotFoundError as e:
        return not_found(e.message)
    except UnauthorisedError as e:
        return forbidden(e.message)
    except ValidationError as e:
        abort(e.message)

    return jsonify(success=True)
Example #9
0
def publish_answer(brief_id):
    data = get_json_from_request()
    try:
        questions_business.publish_answer(current_user, brief_id, data)
    except NotFoundError as nfe:
        return not_found(nfe.message)
    except ValidationError as ve:
        return abort(ve.message)
    except UnauthorisedError as ue:
        return forbidden(ue.message)

    return jsonify(success=True), 200
def update_team(team_id):
    data = get_json_from_request()
    try:
        team = team_business.update_team(team_id, data)
    except ValidationError as e:
        return abort(e.message)
    except NotFoundError as e:
        return not_found(e.message)
    except UnauthorisedError as e:
        return forbidden(e.message)

    return jsonify(team)
def accept_agreement(supplier_code):
    """Accept agreement (role=supplier)
    ---
    tags:
      - seller edit
    parameters:
      - name: supplier_code
        in: path
        type: number
        required: true
    responses:
        200:
            description: Supplier edit info
            schema:
                $ref: '#/definitions/SellerEdit'
        400:
            description: Bad request.
        403:
            description: Unauthorised to accept agreement.
        404:
            description: Supplier not found.
        500:
            description: Unexpected error.
    """

    if current_user.supplier_code != supplier_code:
        return forbidden('Unauthorised to accept agreement')

    try:
        seller_edit_business.accept_agreement({
            'supplier_code': current_user.supplier_code,
            'email_address': current_user.email_address,
            'user_id': current_user.id
        })
    except NotFoundError as nfe:
        not_found(nfe.message)
    except DeletedError as de:
        abort(de.message)
    except ValidationError as ve:
        abort(ve.message)
    except UnauthorisedError as ue:
        abort(ue.message)

    info = seller_edit_business.get_supplier_edit_info({
        'supplier_code':
        current_user.supplier_code,
        'email_address':
        current_user.email_address
    })
    return jsonify(info), 200
def get_join_request(team_id, token):
    try:
        team = team_business.get_team(team_id)
    except NotFoundError as e:
        return not_found(e.message)
    except UnauthorisedError as e:
        return forbidden(e.message)

    join_request = team_business.get_join_request(token)
    if not join_request or int(join_request.data['team_id']) != team_id:
        return not_found(
            'The token is invalid, or this request has already been declined or accepted'
        )

    return jsonify(join_request=join_request)
def download_brief_response_file(brief_id, supplier_code, slug):
    brief = Brief.query.filter(
        Brief.id == brief_id
    ).first_or_404()
    brief_user_ids = [user.id for user in brief.users]
    if hasattr(current_user, 'role') and (current_user.role == 'buyer' and current_user.id in brief_user_ids) \
            or (current_user.role == 'supplier' and current_user.supplier_code == supplier_code):
        file = s3_download_file(slug, os.path.join(brief.framework.slug, 'documents',
                                                   'brief-' + str(brief_id),
                                                   'supplier-' + str(supplier_code)))

        mimetype = mimetypes.guess_type(slug)[0] or 'binary/octet-stream'
        return Response(file, mimetype=mimetype)
    else:
        return forbidden("Unauthorised to view brief or brief does not exist")
def notify_auth_rep(supplier_code):
    """Notify auth rep (role=supplier)
    ---
    tags:
      - seller edit
    parameters:
      - name: supplier_code
        in: path
        type: number
        required: true
    responses:
        200:
            description: Supplier edit info
            schema:
                $ref: '#/definitions/SellerEdit'
        400:
            description: Bad request.
        403:
            description: Unauthorised to notify authorised representative.
        404:
            description: Supplier not found.
        500:
            description: Unexpected error.
    """

    if current_user.supplier_code != supplier_code:
        return forbidden('Unauthorised to notify authorised representative')

    try:
        seller_edit_business.notify_auth_rep({
            'supplier_code':
            current_user.supplier_code,
            'email_address':
            current_user.email_address
        })
    except NotFoundError as nfe:
        not_found(nfe.message)
    except DeletedError as de:
        abort(de.message)

    info = seller_edit_business.get_supplier_edit_info({
        'supplier_code':
        current_user.supplier_code,
        'email_address':
        current_user.email_address
    })
    return jsonify(info), 200
def decline_agreement(supplier_code):
    """Decline agreement (role=supplier)
    ---
    tags:
      - seller edit
    parameters:
      - name: supplier_code
        in: path
        type: number
        required: true
    responses:
        200:
            description: Agreement declined.
        400:
            description: Bad request.
        403:
            description: Unauthorised to decline agreement.
        404:
            description: Supplier not found.
        500:
            description: Unexpected error.
    """

    if current_user.supplier_code != supplier_code:
        return forbidden('Unauthorised to decline agreement')

    try:
        seller_edit_business.decline_agreement({
            'supplier_code':
            current_user.supplier_code,
            'email_address':
            current_user.email_address
        })
    except NotFoundError as nfe:
        not_found(nfe.message)
    except DeletedError as de:
        abort(de.message)
    except UnauthorisedError as ue:
        abort(ue.message)

    return Response(status=200)
def upload_brief_rfx_attachment_file(brief_id, slug):
    """Add brief attachments (role=buyer)
    ---
    tags:
        - brief
    parameters:
      - name: brief_id
        in: path
        type: number
        required: true
      - name: slug
        in: path
        type: string
        required: true
      - name: file
        in: body
        required: true
    responses:
        200:
            description: Attachment uploaded successfully.
        403:
            description: Unauthorised to update brief.
        404:
            description: Brief not found.
        500:
            description: Unexpected error.
    """
    brief = briefs.get(brief_id)

    if not brief:
        not_found("Invalid brief id '{}'".format(brief_id))

    brief_user_ids = [user.id for user in brief.users]
    if current_user.id not in brief_user_ids:
        return forbidden('Unauthorised to update brief')

    return jsonify({"filename": s3_upload_file_from_request(request, slug,
                                                            os.path.join(brief.framework.slug, 'attachments',
                                                                         'brief-' + str(brief_id)))
                    })
def get_brief(brief_id):
    """Get brief
    ---
    tags:
        - brief
    parameters:
      - name: brief_id
        in: path
        type: number
        required: true
    responses:
        200:
            description: Brief retrieved successfully.
            schema:
                type: object
                properties:
                    brief:
                        type: object
                    brief_response_count:
                        type: number
                    invited_seller_count:
                        type: number
                    can_respond:
                        type: boolean
                    open_to_all:
                        type: boolean
                    is_brief_owner:
                        type: boolean
                    is_buyer:
                        type: boolean
                    has_responded:
                        type: boolean
                    has_chosen_brief_category:
                        type: boolean
                    is_assessed_for_category:
                        type: boolean
                    is_assessed_in_any_category:
                        type: boolean
                    is_approved_seller:
                        type: boolean
                    is_awaiting_application_assessment:
                        type: boolean
                    is_awaiting_domain_assessment:
                        type: boolean
                    has_been_assessed_for_brief:
                        type: boolean
                    open_to_category:
                        type: boolean
                    is_applicant:
                        type: boolean
                    is_recruiter:
                        type: boolean
                    domains:
                        type: array
                        items:
                            type: object
        403:
            description: Unauthorised to view brief.
        404:
            description: Brief not found.
        500:
            description: Unexpected error.
    """
    brief = briefs.find(id=brief_id).one_or_none()
    if not brief:
        not_found("No brief for id '%s' found" % (brief_id))

    user_role = current_user.role if hasattr(current_user, 'role') else None
    invited_sellers = brief.data['sellers'] if 'sellers' in brief.data else {}

    is_buyer = False
    is_brief_owner = False
    brief_user_ids = [user.id for user in brief.users]
    if user_role == 'buyer':
        is_buyer = True
        if current_user.id in brief_user_ids:
            is_brief_owner = True

    if brief.status == 'draft' and not is_brief_owner:
        return forbidden("Unauthorised to view brief")

    brief_response_count = len(brief_responses_service.get_brief_responses(brief_id, None))
    invited_seller_count = len(invited_sellers)
    open_to_all = brief.lot.slug == 'atm' and brief.data.get('openTo', '') == 'all'
    open_to_category = brief.lot.slug == 'atm' and brief.data.get('openTo', '') == 'category'
    is_applicant = user_role == 'applicant'

    # gather facts about the user's status against this brief
    user_status = BriefUserStatus(brief, current_user)
    has_chosen_brief_category = user_status.has_chosen_brief_category()
    is_assessed_for_category = user_status.is_assessed_for_category()
    is_assessed_in_any_category = user_status.is_assessed_in_any_category()
    is_awaiting_application_assessment = user_status.is_awaiting_application_assessment()
    is_awaiting_domain_assessment = user_status.is_awaiting_domain_assessment()
    has_been_assessed_for_brief = user_status.has_been_assessed_for_brief()
    is_recruiter_only = user_status.is_recruiter_only()
    is_approved_seller = user_status.is_approved_seller()
    can_respond = user_status.can_respond()
    has_responded = user_status.has_responded()

    # remove private data for non brief owners
    brief.data['contactEmail'] = ''
    brief.data['users'] = None
    if not is_buyer:
        if 'sellers' in brief.data:
            brief.data['sellers'] = {}
        brief.responses_zip_filesize = None
        brief.data['contactNumber'] = ''
        if not can_respond:
            brief.data['proposalType'] = []
            brief.data['evaluationType'] = []
            brief.data['responseTemplate'] = []
            brief.data['requirementsDocument'] = []
            brief.data['industryBriefing'] = ''
            brief.data['backgroundInformation'] = ''
            brief.data['outcome'] = ''
            brief.data['endUsers'] = ''
            brief.data['workAlreadyDone'] = ''
            brief.data['timeframeConstraints'] = ''
            brief.data['attachments'] = []
    else:
        brief.data['contactEmail'] = [user.email_address for user in brief.users][0]
        if not is_brief_owner:
            if 'sellers' in brief.data:
                brief.data['sellers'] = {}
            brief.data['industryBriefing'] = ''
            brief.data['contactNumber'] = ''

    domains = []
    for domain in domain_service.all():
        domains.append({
            'id': str(domain.id),
            'name': domain.name
        })

    return jsonify(brief=brief.serialize(with_users=False, with_author=is_brief_owner),
                   brief_response_count=brief_response_count,
                   invited_seller_count=invited_seller_count,
                   can_respond=can_respond,
                   has_chosen_brief_category=has_chosen_brief_category,
                   is_assessed_for_category=is_assessed_for_category,
                   is_assessed_in_any_category=is_assessed_in_any_category,
                   is_approved_seller=is_approved_seller,
                   is_awaiting_application_assessment=is_awaiting_application_assessment,
                   is_awaiting_domain_assessment=is_awaiting_domain_assessment,
                   has_been_assessed_for_brief=has_been_assessed_for_brief,
                   open_to_all=open_to_all,
                   open_to_category=open_to_category,
                   is_brief_owner=is_brief_owner,
                   is_buyer=is_buyer,
                   is_applicant=is_applicant,
                   is_recruiter_only=is_recruiter_only,
                   has_responded=has_responded,
                   domains=domains)
def update_brief(brief_id):
    """Update RFX brief (role=buyer)
    ---
    tags:
        - brief
    definitions:
        RFXBrief:
            type: object
            properties:
                title:
                    type: string
                organisation:
                    type: string
                location:
                    type: array
                    items:
                        type: string
                summary:
                    type: string
                industryBriefing:
                    type: string
                sellerCategory:
                    type: string
                sellers:
                    type: object
                attachments:
                    type: array
                    items:
                        type: string
                requirementsDocument:
                    type: array
                    items:
                        type: string
                responseTemplate:
                    type: array
                    items:
                        type: string
                evaluationType:
                    type: array
                    items:
                        type: string
                proposalType:
                    type: array
                    items:
                        type: string
                evaluationCriteria:
                    type: array
                    items:
                        type: object
                includeWeightings:
                    type: boolean
                closedAt:
                    type: string
                contactNumber:
                    type: string
                startDate:
                    type: string
                contractLength:
                    type: string
                contractExtensions:
                    type: string
                budgetRange:
                    type: string
                workingArrangements:
                    type: string
                securityClearance:
                    type: string
    parameters:
      - name: brief_id
        in: path
        type: number
        required: true
      - name: body
        in: body
        required: true
        schema:
            $ref: '#/definitions/RFXBrief'
    responses:
        200:
            description: Brief updated successfully.
            schema:
                $ref: '#/definitions/RFXBrief'
        400:
            description: Bad request.
        403:
            description: Unauthorised to update RFX brief.
        404:
            description: Brief not found.
        500:
            description: Unexpected error.
    """
    brief = briefs.get(brief_id)

    if not brief:
        not_found("Invalid brief id '{}'".format(brief_id))

    if brief.status != 'draft':
        abort('Cannot edit a {} brief'.format(brief.status))

    if brief.lot.slug not in ['rfx', 'atm']:
        abort('Brief lot not supported for editing')

    if current_user.role == 'buyer':
        brief_user_ids = [user.id for user in brief.users]
        if current_user.id not in brief_user_ids:
            return forbidden('Unauthorised to update brief')

    data = get_json_from_request()

    publish = False
    if 'publish' in data and data['publish']:
        del data['publish']
        publish = True

    if brief.lot.slug == 'rfx':
        # validate the RFX JSON request data
        errors = RFXDataValidator(data).validate(publish=publish)
        if len(errors) > 0:
            abort(', '.join(errors))

    if brief.lot.slug == 'atm':
        # validate the ATM JSON request data
        errors = ATMDataValidator(data).validate(publish=publish)
        if len(errors) > 0:
            abort(', '.join(errors))

    if brief.lot.slug == 'rfx' and 'evaluationType' in data:
        if 'Written proposal' not in data['evaluationType']:
            data['proposalType'] = []
        if 'Response template' not in data['evaluationType']:
            data['responseTemplate'] = []

    if brief.lot.slug == 'rfx' and 'sellers' in data and len(data['sellers']) > 0:
        data['sellerSelector'] = 'someSellers' if len(data['sellers']) > 1 else 'oneSeller'

    data['areaOfExpertise'] = ''
    if brief.lot.slug == 'atm' and 'openTo' in data:
        if data['openTo'] == 'all':
            data['sellerSelector'] = 'allSellers'
            data['sellerCategory'] = ''
        elif data['openTo'] == 'category':
            data['sellerSelector'] = 'someSellers'
            brief_domain = (
                domain_service.get_by_name_or_id(int(data['sellerCategory'])) if data['sellerCategory'] else None
            )
            if brief_domain:
                data['areaOfExpertise'] = brief_domain.name

    previous_status = brief.status
    if publish:
        brief.publish(closed_at=data['closedAt'])
        if 'sellers' in brief.data and data['sellerSelector'] != 'allSellers':
            for seller_code, seller in brief.data['sellers'].iteritems():
                supplier = suppliers.get_supplier_by_code(seller_code)
                if brief.lot.slug == 'rfx':
                    send_seller_invited_to_rfx_email(brief, supplier)
        try:
            brief_url_external = '{}/2/digital-marketplace/opportunities/{}'.format(
                current_app.config['FRONTEND_ADDRESS'],
                brief_id
            )
            _notify_team_brief_published(
                brief.data['title'],
                brief.data['organisation'],
                current_user.name,
                current_user.email_address,
                brief_url_external
            )
        except Exception as e:
            pass

    brief.data = data
    briefs.save_brief(brief)

    if publish:
        brief_url_external = '{}/2/digital-marketplace/opportunities/{}'.format(
            current_app.config['FRONTEND_ADDRESS'],
            brief_id
        )
        publish_tasks.brief.delay(
            publish_tasks.compress_brief(brief),
            'published',
            previous_status=previous_status,
            name=current_user.name,
            email_address=current_user.email_address,
            url=brief_url_external
        )
    try:
        audit_service.log_audit_event(
            audit_type=AuditTypes.update_brief,
            user=current_user.email_address,
            data={
                'briefId': brief.id,
                'briefData': brief.data
            },
            db_object=brief)
    except Exception as e:
        rollbar.report_exc_info()

    return jsonify(brief.serialize(with_users=False))
def delete_brief(brief_id):
    """Delete brief (role=buyer)
    ---
    tags:
        - brief
    definitions:
        DeleteBrief:
            type: object
            properties:
                message:
                    type: string
    parameters:
      - name: brief_id
        in: path
        type: number
        required: true
    responses:
        200:
            description: Brief deleted successfully.
            schema:
                $ref: '#/definitions/DeleteBrief'
        400:
            description: Bad request. Brief status must be 'draft'.
        403:
            description: Unauthorised to delete brief.
        404:
            description: brief_id not found.
        500:
            description: Unexpected error.
    """
    brief = briefs.get(brief_id)

    if not brief:
        not_found("Invalid brief id '{}'".format(brief_id))

    if current_user.role == 'buyer':
        brief_user_ids = [user.id for user in brief.users]
        if current_user.id not in brief_user_ids:
            return forbidden('Unauthorised to delete brief')

    if brief.status != 'draft':
        abort('Cannot delete a {} brief'.format(brief.status))

    audit = AuditEvent(
        audit_type=AuditTypes.delete_brief,
        user=current_user.email_address,
        data={
            'briefId': brief_id
        },
        db_object=None
    )

    try:
        deleted_brief = publish_tasks.compress_brief(brief)
        audit_service.save(audit)
        briefs.delete(brief)
        publish_tasks.brief.delay(
            deleted_brief,
            'deleted',
            user=current_user.email_address
        )
    except Exception as e:
        extra_data = {'audit_type': AuditTypes.delete_brief, 'briefId': brief.id, 'exception': e.message}
        rollbar.report_exc_info(extra_data=extra_data)

    return jsonify(message='Brief {} deleted'.format(brief_id)), 200
def get_brief_overview(brief_id):
    """Overview (role=buyer)
    ---
    tags:
        - brief
    definitions:
        BriefOverview:
            type: object
            properties:
                sections:
                    type: array
                    items:
                        $ref: '#/definitions/BriefOverviewSections'
                title:
                    type: string
        BriefOverviewSections:
            type: array
            items:
                $ref: '#/definitions/BriefOverviewSection'
        BriefOverviewSection:
            type: object
            properties:
                links:
                    type: array
                    items:
                        $ref: '#/definitions/BriefOverviewSectionLinks'
                title:
                    type: string
        BriefOverviewSectionLinks:
            type: array
            items:
                $ref: '#/definitions/BriefOverviewSectionLink'
        BriefOverviewSectionLink:
            type: object
            properties:
                complete:
                    type: boolean
                path:
                    type: string
                    nullable: true
                text:
                    type: string
    responses:
        200:
            description: Data for the Overview page
            schema:
                $ref: '#/definitions/BriefOverview'
        400:
            description: Lot not supported.
        403:
            description: Unauthorised to view brief.
        404:
            description: brief_id not found
    """
    brief = briefs.get(brief_id)

    if not brief:
        not_found("Invalid brief id '{}'".format(brief_id))

    if current_user.role == 'buyer':
        brief_user_ids = [user.id for user in brief.users]
        if current_user.id not in brief_user_ids:
            return forbidden('Unauthorised to view brief')

    if not (brief.lot.slug == 'digital-professionals' or
            brief.lot.slug == 'training'):
        abort('Lot {} is not supported'.format(brief.lot.slug))

    sections = brief_overview_service.get_sections(brief)

    return jsonify(sections=sections, status=brief.status, title=brief.data['title']), 200
def get_brief_responses(brief_id):
    """All brief responses (role=supplier,buyer)
    ---
    tags:
      - brief
    security:
      - basicAuth: []
    parameters:
      - name: brief_id
        in: path
        type: number
        required: true
    definitions:
      BriefResponses:
        properties:
          briefResponses:
            type: array
            items:
              id: BriefResponse
    responses:
      200:
        description: A list of brief responses
        schema:
          id: BriefResponses
      404:
        description: brief_id not found
    """
    brief = briefs.get(brief_id)
    if not brief:
        not_found("Invalid brief id '{}'".format(brief_id))

    if current_user.role == 'buyer':
        brief_user_ids = [user.id for user in brief.users]
        if current_user.id not in brief_user_ids:
            return forbidden("Unauthorised to view brief or brief does not exist")

    supplier_code = getattr(current_user, 'supplier_code', None)
    if current_user.role == 'supplier':
        validation_result = supplier_business.get_supplier_messages(supplier_code, True)
        if len(validation_result.errors) > 0:
            abort(validation_result.errors)
        # strip data from seller view
        if 'sellers' in brief.data:
            brief.data['sellers'] = {}
        if brief.responses_zip_filesize:
            brief.responses_zip_filesize = None
        if 'industryBriefing' in brief.data:
            brief.data['industryBriefing'] = ''
        if 'attachments' in brief.data:
            brief.data['attachments'] = []
        if 'backgroundInformation' in brief.data:
            brief.data['backgroundInformation'] = ''
        if 'outcome' in brief.data:
            brief.data['outcome'] = ''
        if 'endUsers' in brief.data:
            brief.data['endUsers'] = ''
        if 'workAlreadyDone' in brief.data:
            brief.data['workAlreadyDone'] = ''
        if 'timeframeConstraints' in brief.data:
            brief.data['timeframeConstraints'] = ''
        if 'contactNumber' in brief.data:
            brief.data['contactNumber'] = ''

    if current_user.role == 'buyer' and brief.status != 'closed':
        brief_responses = []
    else:
        brief_responses = brief_responses_service.get_brief_responses(brief_id, supplier_code)

    return jsonify(brief=brief.serialize(with_users=False, with_author=False),
                   briefResponses=brief_responses)
def _can_do_brief_response(brief_id):
    try:
        brief = Brief.query.get(brief_id)
    except DataError:
        brief = None

    if brief is None:
        abort("Invalid brief ID '{}'".format(brief_id))

    if brief.status != 'live':
        abort("Brief must be live")

    if brief.framework.status != 'live':
        abort("Brief framework must be live")

    if not hasattr(current_user, 'role') or current_user.role != 'supplier':
        forbidden("Only supplier role users can respond to briefs")

    try:
        supplier = Supplier.query.filter(
            Supplier.code == current_user.supplier_code
        ).first()
    except DataError:
        supplier = None

    if not supplier:
        forbidden("Invalid supplier Code '{}'".format(current_user.supplier_code))

    validation_result = SupplierValidator(supplier).validate_all()
    if len(validation_result.errors) > 0:
        abort(validation_result.errors)

    def domain(email):
        return email.split('@')[-1]

    current_user_domain = domain(current_user.email_address) \
        if domain(current_user.email_address) not in current_app.config.get('GENERIC_EMAIL_DOMAINS') \
        else None

    rfx_lot = lots_service.find(slug='rfx').one_or_none()
    rfx_lot_id = rfx_lot.id if rfx_lot else None

    atm_lot = lots_service.find(slug='atm').one_or_none()
    atm_lot_id = atm_lot.id if atm_lot else None

    is_selected = False
    seller_selector = brief.data.get('sellerSelector', '')
    open_to = brief.data.get('openTo', '')
    brief_category = brief.data.get('sellerCategory', '')
    brief_domain = domain_service.get_by_name_or_id(int(brief_category)) if brief_category else None

    if brief.lot_id == rfx_lot_id:
        if str(current_user.supplier_code) in brief.data['sellers'].keys():
            is_selected = True

    elif brief.lot_id == atm_lot_id:
        if seller_selector == 'allSellers' and len(supplier.assessed_domains) > 0:
            is_selected = True
        elif seller_selector == 'someSellers' and open_to == 'category' and brief_domain and\
                                brief_domain.name in supplier.assessed_domains:
            is_selected = True

    else:
        if not seller_selector or seller_selector == 'allSellers':
            is_selected = True
        elif seller_selector == 'someSellers':
            seller_domain_list = [domain(x).lower() for x in brief.data['sellerEmailList']]
            if current_user.email_address in brief.data['sellerEmailList'] \
               or (current_user_domain and current_user_domain.lower() in seller_domain_list):
                is_selected = True
        elif seller_selector == 'oneSeller':
            if current_user.email_address.lower() == brief.data['sellerEmail'].lower() \
               or (current_user_domain and current_user_domain.lower() == domain(brief.data['sellerEmail'].lower())):
                is_selected = True
    if not is_selected:
        forbidden("Supplier not selected for this brief")

    if (len(supplier.frameworks) == 0 or
            'digital-marketplace' != supplier.frameworks[0].framework.slug):

        abort("Supplier does not have Digital Marketplace framework")

    if len(supplier.assessed_domains) == 0:
        abort("Supplier does not have at least one assessed domain")
    else:
        training_lot = lots_service.find(slug='training').one_or_none()
        if brief.lot_id == training_lot.id:
            if 'Training, Learning and Development' not in supplier.assessed_domains:
                abort("Supplier needs to be assessed in 'Training, Learning and Development'")

    lot = lots_service.first(slug='digital-professionals')
    if brief.lot_id == lot.id:
        # Check the supplier can respond to the category
        brief_category = brief.data.get('areaOfExpertise', None)
        if brief_category and brief_category not in supplier.assessed_domains:
            abort("Supplier needs to be assessed in '{}'".format(brief_category))
        # Check if there are more than 3 brief response already from this supplier when professional aka specialists
        brief_response_count = brief_responses_service.find(supplier_code=supplier.code,
                                                            brief_id=brief.id,
                                                            withdrawn_at=None).count()
        if (brief_response_count > 2):  # TODO magic number
            abort("There are already 3 brief responses for supplier '{}'".format(supplier.code))
    else:
        # Check if brief response already exists from this supplier when outcome for all other types
        if brief_responses_service.find(supplier_code=supplier.code,
                                        brief_id=brief.id,
                                        withdrawn_at=None).one_or_none():
            abort("Brief response already exists for supplier '{}'".format(supplier.code))

    return supplier, brief