Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
def update_evidence(evidence_id):
    evidence = evidence_service.get_evidence_by_id(evidence_id)
    if not evidence or current_user.supplier_code != evidence.supplier_code:
        not_found("No evidence for id '%s' found" % (evidence_id))

    if evidence.status != 'draft':
        abort('Only draft submissions can be edited')

    data = get_json_from_request()

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

    if 'maxDailyRate' in data:
        try:
            data['maxDailyRate'] = int(data['maxDailyRate'])
        except ValueError as e:
            data['maxDailyRate'] = 0

    # Validate the evidence request data
    errors = EvidenceDataValidator(data,
                                   evidence=evidence).validate(publish=publish)
    if len(errors) > 0:
        abort(', '.join(errors))

    if publish:
        evidence.submit()
        if current_app.config['JIRA_FEATURES']:
            create_evidence_assessment_in_jira.delay(evidence_id)
        try:
            send_evidence_assessment_requested_notification(
                evidence.domain_id, current_user.email_address)
        except Exception as e:
            current_app.logger.warn(
                'Failed to send requested assessment email for evidence id: {}, {}'
                .format(evidence_id, e))

    evidence.data = data
    evidence_service.save_evidence(evidence)

    try:
        publish_tasks.evidence.delay(publish_tasks.compress_evidence(evidence),
                                     'updated',
                                     name=current_user.name,
                                     domain=evidence.domain.name,
                                     supplier_code=current_user.supplier_code)

        if publish:
            publish_tasks.evidence.delay(
                publish_tasks.compress_evidence(evidence),
                'submitted',
                name=current_user.name,
                domain=evidence.domain.name,
                supplier_code=current_user.supplier_code)
    except Exception as e:
        pass

    return jsonify(evidence.serialize())
Ejemplo n.º 3
0
def check_csrf_token():
    if request.method in ('POST', 'PATCH', 'PUT', 'DELETE'):
        new_csrf_valid = check_valid_csrf()

        if not (new_csrf_valid):
            rollbar.report_message(
                'csrf.invalid_token: Aborting request check_csrf_token()',
                'error', request)
            abort('Invalid CSRF token. Please try again.')
def withdraw_brief_response(brief_response_id):
    """Withdraw brief responses (role=supplier)
    ---
    tags:
      - "Brief Response"
    security:
      - basicAuth: []
    parameters:
      - name: brief_response_id
        in: path
        type: number
        required: true
    responses:
      200:
        description: Successfully withdrawn a candidate
        schema:
          id: BriefResponse
      400:
        description: brief_response_id not found
    """
    brief_response = (brief_responses_service.find(
        id=brief_response_id,
        supplier_code=current_user.supplier_code).one_or_none())

    if brief_response:
        if brief_response.withdrawn_at is None:
            brief_response.withdrawn_at = utcnow()
            brief_responses_service.save(brief_response)

            try:
                audit = AuditEvent(
                    audit_type=audit_types.update_brief_response,
                    user=current_user.email_address,
                    data={'briefResponseId': brief_response.id},
                    db_object=brief_response)
                audit_service.save(audit)
            except Exception as e:
                extra_data = {
                    'audit_type': audit_types.update_brief_response,
                    'briefResponseId': brief_response.id
                }
                rollbar.report_exc_info(extra_data=extra_data)

            publish_tasks.brief_response.delay(
                publish_tasks.compress_brief_response(brief_response),
                'withdrawn',
                user=current_user.email_address)
        else:
            abort(
                'Brief response with brief_response_id "{}" is already withdrawn'
                .format(brief_response_id))
    else:
        not_found(
            'Cannot find brief response with brief_response_id :{} and supplier_code: {}'
            .format(brief_response_id, current_user.supplier_code))

    return jsonify(briefResponses=brief_response.serialize()), 200
Ejemplo n.º 5
0
def request_access():
    data = get_json_from_request()

    try:
        team_business.request_access(data)
    except ValidationError as e:
        abort(e.message)

    return jsonify(success=True)
def withdraw_brief_response(brief_response_id):
    """Withdraw brief responses (role=supplier)
    ---
    tags:
      - "Brief Response"
    security:
      - basicAuth: []
    parameters:
      - name: brief_response_id
        in: path
        type: number
        required: true
    responses:
      200:
        description: Successfully withdrawn a candidate
        schema:
          id: BriefResponse
      400:
        description: brief_response_id not found
    """
    brief_response = (brief_responses_service
                      .find(id=brief_response_id,
                            supplier_code=current_user.supplier_code)
                      .one_or_none())

    if brief_response:
        if brief_response.withdrawn_at is None:
            brief_response.withdrawn_at = utcnow()
            brief_responses_service.save(brief_response)

            try:
                audit = AuditEvent(
                    audit_type=audit_types.update_brief_response,
                    user=current_user.email_address,
                    data={
                        'briefResponseId': brief_response.id
                    },
                    db_object=brief_response
                )
                audit_service.save(audit)
            except Exception as e:
                extra_data = {'audit_type': audit_types.update_brief_response, 'briefResponseId': brief_response.id}
                rollbar.report_exc_info(extra_data=extra_data)

            publish_tasks.brief_response.delay(
                publish_tasks.compress_brief_response(brief_response),
                'withdrawn',
                user=current_user.email_address
            )
        else:
            abort('Brief response with brief_response_id "{}" is already withdrawn'.format(brief_response_id))
    else:
        not_found('Cannot find brief response with brief_response_id :{} and supplier_code: {}'
                  .format(brief_response_id, current_user.supplier_code))

    return jsonify(briefResponses=brief_response.serialize()), 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
Ejemplo n.º 8
0
    def update_ceiling_price(self,
                             ceiling_id,
                             new_ceiling,
                             set_current_price_to_ceiling=False):
        """We only validate against a SINGLE (most recently updated) service_price.
        See the query in prices_service.get_prices() for further details on how prices are ordered.

        If multiple prices are related to the same ceiling_price, they will all be updated.

        In the case where the user has requested to update the current price to match the new
        ceiling price, these two operations are done in separate transactions. There is no
        requirement to ensure they are done atomically.

        The following risks are accepted:
        (1) Concurrent updates to a service-price or a ceiling-price are not considered.
        (2) Failure in any database interaction may result in partial state changes. For example, if the
        current_price fails to update, the ceiling price will have been changed but no audit record
        would be saved.

        :param set_current_price_to_ceiling: whether (or not) to set the current price to match the new ceiling price
        """
        ceiling_price = self.get(ceiling_id)
        if not ceiling_price:
            abort('Ceiling price with id {} does not exist'.format(ceiling_id))

        # Check if the new ceiling price is less than the current price
        supplier_prices = self.prices_service.get_prices(
            ceiling_price.supplier_code, ceiling_price.service_type_id,
            ceiling_price.sub_service_id,
            pendulum.today(current_app.config['DEADLINES_TZ_NAME']).date(),
            ceiling_price.region_id)
        if supplier_prices:
            current_price = supplier_prices[0]['price']
            current_price = current_price.translate(None, ',')
            if new_ceiling < float(current_price.strip(' "')):
                abort('Ceiling price cannot be lower than ${} (current price)'.
                      format(current_price))

        old_ceiling = ceiling_price.price
        ceiling_price.price = new_ceiling
        db.session.commit()

        # if there is a current_price, and it needs to match the ceiling, update it
        if set_current_price_to_ceiling:
            price_id = supplier_prices[0]['id'] if supplier_prices else None
            self._match_price_to_ceiling(price_id, ceiling_price)

        self.audit.create(audit_type=AuditTypes.update_ceiling_price,
                          user=current_user.id,
                          data={
                              "old": old_ceiling,
                              "new": new_ceiling
                          },
                          db_object=ceiling_price)
Ejemplo n.º 9
0
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)
Ejemplo n.º 10
0
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)
Ejemplo n.º 11
0
def find_team_members():
    keywords = request.args.get('keywords') or ''
    exclude = request.args.get('exclude') or ''

    if keywords:
        results = team_business.search_team_members(current_user,
                                                    current_user.agency_id,
                                                    keywords=keywords,
                                                    exclude=exclude.split(','))

        return jsonify(users=results), 200
    else:
        abort('You must provide a keywords param.')
def check_csrf_token():
    if request.method in ('POST', 'PATCH', 'PUT', 'DELETE'):
        '''
        Only check CSRF tokens if there is no valid API key in the request. The API key comes via a header which will
        not be forwarded by browsers automatically in authenticated requests, so the presence of a valid API key in the
        request proves authenticity like a CSRF token.
        '''
        api_key = get_api_key_from_request(request)
        if not api_key or not api_key_service.get_key(api_key):
            new_csrf_valid = check_valid_csrf()

            if not (new_csrf_valid):
                rollbar.report_message(
                    'csrf.invalid_token: Aborting request check_csrf_token()',
                    'error', request)
                abort('Invalid CSRF token. Please try again.')
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 get_brief_response(brief_response_id):
    """Get brief response (role=supplier)
    ---
    tags:
      - "Brief Response"
    security:
      - basicAuth: []
    parameters:
      - name: brief_response_id
        in: path
        type: number
        required: true
    definitions:
      BriefResponse:
        type: object
        properties:
          id:
            type: number
          data:
            type: object
          brief_id:
            type: number
          supplier_code:
            type: number
    responses:
      200:
        description: A brief response on id
        schema:
          id: BriefResponse
      404:
        description: brief_response_id not found
    """

    brief_response = brief_responses_service.find(
        id=brief_response_id,
        supplier_code=current_user.supplier_code).one_or_none()

    if brief_response:
        if brief_response.withdrawn_at is not None:
            abort('Brief response {} is withdrawn'.format(brief_response_id))
    else:
        not_found(
            'Cannot find brief response with brief_response_id :{} and supplier_code: {}'
            .format(brief_response_id, current_user.supplier_code))

    return jsonify(brief_response.serialize())
def get_brief_response(brief_response_id):
    """Get brief response (role=supplier)
    ---
    tags:
      - "Brief Response"
    security:
      - basicAuth: []
    parameters:
      - name: brief_response_id
        in: path
        type: number
        required: true
    definitions:
      BriefResponse:
        type: object
        properties:
          id:
            type: number
          data:
            type: object
          brief_id:
            type: number
          supplier_code:
            type: number
    responses:
      200:
        description: A brief response on id
        schema:
          id: BriefResponse
      404:
        description: brief_response_id not found
    """

    brief_response = brief_responses_service.find(id=brief_response_id,
                                                  supplier_code=current_user.supplier_code).one_or_none()

    if brief_response:
        if brief_response.withdrawn_at is not None:
            abort('Brief response {} is withdrawn'.format(brief_response_id))
    else:
        not_found('Cannot find brief response with brief_response_id :{} and supplier_code: {}'
                  .format(brief_response_id, current_user.supplier_code))

    return jsonify(brief_response.serialize())
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)
Ejemplo n.º 17
0
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)
Ejemplo n.º 18
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 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
Ejemplo n.º 20
0
def upsert(key):
    """Upsert a key value (role=admin)
    ---
    tags:
      - key_value
    security:
      - basicAuth: []
    parameters:
      - name: key
        in: path
        type: string
        required: true
      - name: data
        in: body
        required: true
        schema:
          $ref: '#/definitions/KeyValueUpsert'
    definitions:
          KeyValueUpsert:
            properties:
              data:
                type: object
    responses:
      200:
        description: A key value
        type: object
        schema:
          $ref: '#/definitions/KeyValue'
    """
    try:
        json_payload = get_json_from_request()
        data = json_payload.get('data')
        saved = key_values_service.upsert(key, data)
        return jsonify(saved), 200

    except Exception as error:
        return abort(error.message)
Ejemplo n.º 21
0
def upsert(key):
    """Upsert a key value (role=admin)
    ---
    tags:
      - key_value
    security:
      - basicAuth: []
    parameters:
      - name: key
        in: path
        type: string
        required: true
      - name: data
        in: body
        required: true
        schema:
          $ref: '#/definitions/KeyValueUpsert'
    definitions:
          KeyValueUpsert:
            properties:
              data:
                type: object
    responses:
      200:
        description: A key value
        type: object
        schema:
          $ref: '#/definitions/KeyValue'
    """
    try:
        json_payload = get_json_from_request()
        data = json_payload.get('data')
        saved = key_values_service.upsert(key, data)
        return jsonify(saved), 200

    except Exception as error:
        return abort(error.message)
Ejemplo n.º 22
0
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
Ejemplo n.º 23
0
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
Ejemplo n.º 24
0
def update():
    """Update prices (role=supplier)
    ---
    tags:
      - prices
    security:
      - basicAuth: []
    consumes:
      - application/json
    parameters:
      - name: body
        in: body
        required: true
        schema:
          id: PriceUpdates
          required:
            - prices
          properties:
            prices:
              type: array
              items:
                type: object
                required:
                  - id
                  - price
                  - startDate
                properties:
                  id:
                    type: integer
                  price:
                    type: number
                    minimum: 1
                  startDate:
                    type: string
                  endDate:
                    type: string
    responses:
      200:
        description: An updated price
        schema:
          properties:
            prices:
              type: array
              items:
                $ref: '#/definitions/SupplierPrice'
    """
    json_data = request.get_json()
    updated_prices = json_data.get('prices')
    results = []

    for p in updated_prices:
        existing_price = prices.get(p['id'])

        if existing_price is None:
            abort('Invalid price id: {}'.format(p['id']))

        if existing_price.supplier_code != current_user.supplier_code:
            abort('Supplier {} unauthorized to update price {}'.format(
                current_user.supplier_code, existing_price.id))

        start_date = p.get('startDate')
        end_date = p.get('endDate', '')
        price = p.get('price')
        date_from = parse_date(start_date)

        if end_date:
            date_to = parse_date(end_date)
        else:
            date_to = pendulum.Date.create(2050, 1, 1)

        if not date_from.is_future():
            abort('startDate must be in the future: {}'.format(date_from))

        if date_to < date_from:
            abort('endDate must be after startDate: {}'.format(date_to))

        if price > existing_price.service_type_price_ceiling.price:
            abort('price must be less than capPrice: {}'.format(price))

        existing_price.date_to = date_from.subtract(days=1)
        new_price = prices.add_price(existing_price, date_from, date_to, price)
        trailing_price = prices.add_price(new_price, date_to.add(days=1),
                                          pendulum.Date.create(2050, 1, 1), existing_price.price)\
            if end_date else None

        results.append([
            x for x in [existing_price, new_price, trailing_price]
            if x is not None
        ])

    send_price_change_email(results)

    return jsonify(prices=results)
Ejemplo n.º 25
0
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))
Ejemplo n.º 26
0
def create_evidence(domain_id, brief_id=None):
    """Create evidence (role=supplier)
    ---
    tags:
        - evidence
    definitions:
        EvidenceCreated:
            type: object
            properties:
                id:
                    type: number
                domain_id:
                    type: number
                supplier_code:
                    type: number
    responses:
        200:
            description: Evidence created successfully.
            schema:
                $ref: '#/definitions/EvidenceCreated'
        400:
            description: Bad request.
        403:
            description: Unauthorised to create evidence.
        500:
            description: Unexpected error.
    """
    domain = domain_service.get_by_name_or_id(domain_id, show_legacy=False)
    if not domain:
        abort('Unknown domain id')

    supplier = suppliers.get_supplier_by_code(current_user.supplier_code)
    if supplier.data.get('recruiter', '') == 'yes':
        abort('Assessment can\'t be started against a recruiter only supplier')

    existing_evidence = evidence_service.get_latest_evidence_for_supplier_and_domain(
        domain_id, current_user.supplier_code)
    if domain.name in supplier.assessed_domains:
        abort('This supplier is already assessed for this domain')

    open_assessment = assessments.get_open_assessments(
        domain_id=domain_id, supplier_code=current_user.supplier_code)
    if open_assessment or (existing_evidence and existing_evidence.status
                           in ['draft', 'submitted']):
        abort(
            'This supplier already has a draft assessment or is awaiting assessment for this domain'
        )

    if brief_id:
        brief = briefs.find(id=brief_id).one_or_none()
        if not brief or brief.status != 'live':
            abort('Brief id does not exist or is not open for responses')

    try:
        data = {}

        if existing_evidence and existing_evidence.status == 'rejected':
            data = existing_evidence.data.copy()
        else:
            # does this supplier already have a max price for this domain set? if so, pre-populate
            current_max_price = suppliers.get_supplier_max_price_for_domain(
                current_user.supplier_code, domain.name)
            if current_max_price:
                data['maxDailyRate'] = int(current_max_price)

        evidence = evidence_service.create_evidence(domain_id,
                                                    current_user.supplier_code,
                                                    current_user.id,
                                                    brief_id=brief_id,
                                                    data=data)

    except Exception as e:
        rollbar.report_exc_info()
        abort(e.message)

    publish_tasks.evidence.delay(publish_tasks.compress_evidence(evidence),
                                 'created',
                                 name=current_user.name,
                                 domain=domain.name,
                                 supplier_code=current_user.supplier_code)

    return jsonify(evidence.serialize())
Ejemplo n.º 27
0
def get_evidence_feedback(evidence_id):
    evidence = evidence_service.get_evidence_by_id(evidence_id)
    if not evidence or current_user.supplier_code != evidence.supplier_code:
        not_found("No evidence for id '%s' found" % (evidence_id))
    if not evidence.status == 'rejected':
        abort('Only rejected submissions can contain feedback')
    evidence_assessment = evidence_assessment_service.get_assessment_for_rejected_evidence(
        evidence_id)
    if not evidence_assessment:
        abort('Failed to get the evidence assessment')

    try:
        domain_criteria = DomainCriteria(domain_id=evidence.domain.id,
                                         rate=evidence.data.get(
                                             'maxDailyRate', None))
        criteria_needed = domain_criteria.get_criteria_needed()
    except Exception as e:
        abort(str(e))

    criteria_from_domain = {}
    domain_criteria = domain_criteria_service.get_criteria_by_domain_id(
        evidence.domain.id)
    for criteria in domain_criteria:
        criteria_from_domain[str(criteria.id)] = criteria.name

    criteria = {}
    failed_criteria = evidence_assessment.data.get('failed_criteria', {})
    vfm = evidence_assessment.data.get('vfm', None)
    for criteria_id, criteria_response in evidence.get_criteria_responses(
    ).iteritems():
        has_feedback = True if criteria_id in failed_criteria.keys() else False
        criteria[criteria_id] = {
            "response":
            criteria_response,
            "name":
            criteria_from_domain[criteria_id]
            if criteria_id in criteria_from_domain else '',
            "has_feedback":
            has_feedback,
            "assessment":
            failed_criteria[criteria_id] if has_feedback else {}
        }

    current_evidence = evidence_service.get_latest_evidence_for_supplier_and_domain(
        evidence.domain.id, current_user.supplier_code)

    data = {
        'domain_id':
        evidence.domain.id,
        'domain_name':
        evidence.domain.name,
        'criteria':
        criteria,
        'criteria_needed':
        criteria_needed,
        'current_evidence_id':
        current_evidence.id if current_evidence.status == 'draft' else None,
        'vfm':
        vfm
    }

    return jsonify(data)
Ejemplo n.º 28
0
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
Ejemplo n.º 29
0
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)