def briefs(app, request, users):
    with app.app_context():
        now = pendulum.now('utc')
        framework = frameworks_service.find(
            slug='digital-marketplace').one_or_none()
        atm_lot = lots_service.find(slug='atm').one_or_none()
        rfx_lot = lots_service.find(slug='rfx').one_or_none()
        specialist_lot = lots_service.find(slug='specialist').one_or_none()
        training_lot = lots_service.find(slug='training2').one_or_none()

        db.session.add(
            Brief(id=1,
                  data={},
                  framework=framework,
                  lot=atm_lot,
                  users=users,
                  published_at=now.subtract(days=2),
                  withdrawn_at=None))

        db.session.add(
            Brief(id=2,
                  data={},
                  framework=framework,
                  lot=rfx_lot,
                  users=users,
                  published_at=now.subtract(days=2),
                  withdrawn_at=None))

        db.session.add(
            Brief(id=3,
                  data={},
                  framework=framework,
                  lot=specialist_lot,
                  users=users,
                  published_at=now.subtract(days=2),
                  withdrawn_at=None))

        db.session.add(
            Brief(id=4,
                  data={},
                  framework=framework,
                  lot=training_lot,
                  users=users,
                  published_at=now.subtract(days=2),
                  withdrawn_at=None))

        db.session.commit()
        yield db.session.query(Brief).all()
    def brief(self, app, users):
        with app.app_context():
            now = pendulum.now('utc')
            framework = frameworks_service.find(
                slug='digital-marketplace').one_or_none()
            specialist_lot = lots_service.find(slug='specialist').one_or_none()

            brief = Brief(id=1,
                          data={
                              'openTo': 'selected',
                              'sellers': {
                                  '123': {
                                      'name': 'FriendFace'
                                  },
                                  '456': {
                                      'name': 'FriendFlutter'
                                  }
                              }
                          },
                          framework=framework,
                          lot=specialist_lot,
                          users=users,
                          published_at=now.subtract(days=2),
                          withdrawn_at=None)

            brief.questions_closed_at = now.add(days=3)
            brief.closed_at = now.add(days=5)
            db.session.add(brief)

            db.session.commit()
            yield db.session.query(Brief).first()
    def test_can_not_close_published_atm_opportunity_early(
            self, overview_briefs):
        lot = lots_service.find(slug='atm').one_or_none()
        brief = briefs_service.find(lot=lot, status='live').one_or_none()

        can_close = can_close_opportunity_early(brief)
        assert can_close is False
    def test_can_close_published_opportunity_early_with_single_invited_seller_that_responded(
            self, briefs, brief_responses, lot_slug):
        lot = lots_service.find(slug=lot_slug).one_or_none()
        brief = briefs_service.find(lot=lot, status='live').one_or_none()

        can_close = can_close_opportunity_early(brief)
        assert can_close is True
def create(current_user):
    lot = lots_service.find(slug='training2').one_or_none()
    framework = frameworks_service.find(
        slug='digital-marketplace').one_or_none()
    user = users.get(current_user.id)
    agency_name = ''

    email_domain = user.email_address.split('@')[1]
    agency = agency_service.find(domain=email_domain).one_or_none()
    if agency:
        agency_name = agency.name

    domain = domain_service.find(
        name='Training, Learning and Development').one_or_none()
    seller_category = None
    if domain:
        seller_category = str(domain.id)
    else:
        raise Exception('Training, Learning and Development domain not found')

    brief = briefs.create_brief(user,
                                current_user.get_team(),
                                framework,
                                lot,
                                data={
                                    'organisation': agency_name,
                                    'sellerCategory': seller_category
                                })

    audit_service.log_audit_event(audit_type=audit_types.create_brief,
                                  user=current_user.email_address,
                                  data={'briefId': brief.id},
                                  db_object=brief)

    return brief
    def test_can_not_close_published_opportunity_early_with_incorrect_seller_selector(
            self, overview_briefs, brief_data):
        lot = lots_service.find(slug=brief_data['lot_slug']).one_or_none()
        brief = briefs_service.find(lot=lot, status='live').one_or_none()
        brief.data['sellerSelector'] = brief_data['seller_selector']

        can_close = can_close_opportunity_early(brief)
        assert can_close is False
    def test_can_not_close_published_opportunity_early_with_multiple_invited_sellers(
            self, overview_briefs, lot_slug):
        lot = lots_service.find(slug=lot_slug).one_or_none()
        brief = briefs_service.find(lot=lot, status='live').one_or_none()
        brief.data['sellers'].update({'456': 'Big Corp'})

        can_close = can_close_opportunity_early(brief)
        assert can_close is False
    def briefs(self, app):
        with app.app_context():
            framework = frameworks_service.find(
                slug='digital-marketplace').one_or_none()
            digital_professional_lot = lots_service.find(
                slug='digital-professionals').one_or_none()
            specialist_lot = lots_service.find(slug='specialist').one_or_none()

            db.session.add(
                Brief(id=1,
                      data={},
                      framework=framework,
                      lot=digital_professional_lot))

            db.session.add(
                Brief(id=2, data={}, framework=framework, lot=specialist_lot))

            db.session.commit()
            yield db.session.query(Brief).all()
    def test_can_not_close_published_opportunity_early_with_multiple_seller_responses(
            self, overview_briefs, suppliers, lot_slug):
        lot = lots_service.find(slug=lot_slug).one_or_none()
        brief = briefs_service.find(lot=lot, status='live').one_or_none()

        db.session.add(
            BriefResponse(id=6, brief_id=brief.id, data={}, supplier_code=3))

        db.session.commit()

        can_close = can_close_opportunity_early(brief)
        assert can_close is False
示例#10
0
 def has_responded(self):
     if self.user_role == 'supplier':
         brief_response_count = brief_responses_service.find(
             supplier_code=self.supplier_code,
             brief_id=self.brief.id,
             withdrawn_at=None).count()
         lot = lots_service.find(slug='specialist').one_or_none()
         if self.brief.lot_id == lot.id:
             return brief_response_count >= int(
                 self.brief.data.get('numberOfSuppliers', 0))
         elif brief_response_count > 0:
             return True
     return False
示例#11
0
def create_atm_brief():
    """Create ATM brief (role=buyer)
    ---
    tags:
        - brief
    definitions:
        ATMBriefCreated:
            type: object
            properties:
                id:
                    type: number
                lot:
                    type: string
                status:
                    type: string
                author:
                    type: string
    responses:
        200:
            description: Brief created successfully.
            schema:
                $ref: '#/definitions/ATMBriefCreated'
        400:
            description: Bad request.
        403:
            description: Unauthorised to create ATM brief.
        500:
            description: Unexpected error.
    """
    try:
        lot = lots_service.find(slug='atm').one_or_none()
        framework = frameworks_service.find(slug='digital-marketplace').one_or_none()
        user = users.get(current_user.id)
        brief = briefs.create_brief(user, framework, lot)
    except Exception as e:
        rollbar.report_exc_info()
        return jsonify(message=e.message), 400

    try:
        audit_service.log_audit_event(
            audit_type=AuditTypes.create_brief,
            user=current_user.email_address,
            data={
                'briefId': brief.id
            },
            db_object=brief)
    except Exception as e:
        rollbar.report_exc_info()

    return jsonify(brief.serialize(with_users=False))
示例#12
0
 def has_responded(self, submitted_only=True):
     if self.user_role == 'supplier':
         responses = brief_responses_service.get_brief_responses(
             self.brief.id,
             self.supplier_code,
             submitted_only=submitted_only)
         brief_response_count = len(responses)
         lot = lots_service.find(slug='specialist').one_or_none()
         if self.brief.lot_id == lot.id:
             return brief_response_count >= int(
                 self.brief.data.get('numberOfSuppliers', 0))
         elif brief_response_count > 0:
             return True
     return False
示例#13
0
def overview_briefs(app, users):
    now = pendulum.now('utc')
    framework = frameworks_service.find(
        slug='digital-marketplace').one_or_none()
    atm_lot = lots_service.find(slug='atm').one_or_none()
    rfx_lot = lots_service.find(slug='rfx').one_or_none()
    specialist_lot = lots_service.find(slug='specialist').one_or_none()
    training_lot = lots_service.find(slug='training2').one_or_none()

    with app.app_context():
        db.session.add(
            Brief(id=5,
                  data={},
                  framework=framework,
                  lot=specialist_lot,
                  users=users,
                  published_at=None,
                  withdrawn_at=None))

        published_atm = Brief(id=6,
                              data={},
                              framework=framework,
                              lot=atm_lot,
                              users=users,
                              published_at=now.subtract(days=2),
                              withdrawn_at=None)

        published_atm.questions_closed_at = now.add(days=3)
        published_atm.closed_at = now.add(days=5)
        db.session.add(published_atm)

        published_rfx_open_to_one = Brief(id=7,
                                          data={
                                              'sellerSelector': 'oneSeller',
                                              'sellers': {
                                                  '2': {
                                                      'name': 'FriendFace'
                                                  }
                                              }
                                          },
                                          framework=framework,
                                          lot=rfx_lot,
                                          users=users,
                                          published_at=now.subtract(days=2),
                                          withdrawn_at=None)

        published_rfx_open_to_one.questions_closed_at = now.add(days=3)
        published_rfx_open_to_one.closed_at = now.add(days=5)
        db.session.add(published_rfx_open_to_one)

        published_training_open_to_one = Brief(
            id=8,
            data={
                'sellerSelector': 'oneSeller',
                'sellers': {
                    '2': {
                        'name': 'FriendFace'
                    }
                }
            },
            framework=framework,
            lot=training_lot,
            users=users,
            published_at=now.subtract(days=2),
            withdrawn_at=None)

        published_training_open_to_one.questions_closed_at = now.add(days=3)
        published_training_open_to_one.closed_at = now.add(days=5)
        db.session.add(published_training_open_to_one)

        published_specialist_open_to_some = Brief(
            id=9,
            data={
                'numberOfSuppliers': '3',
                'sellerSelector': 'someSellers',
                'sellers': {
                    '2': {
                        'name': 'FriendFace'
                    }
                }
            },
            framework=framework,
            lot=specialist_lot,
            users=users,
            published_at=now.subtract(days=2),
            withdrawn_at=None)

        published_specialist_open_to_some.questions_closed_at = now.add(days=3)
        published_specialist_open_to_some.closed_at = now.add(days=5)
        db.session.add(published_specialist_open_to_some)

        closed_specialist = Brief(id=10,
                                  data={},
                                  framework=framework,
                                  lot=specialist_lot,
                                  users=users,
                                  created_at=now.subtract(days=3),
                                  published_at=now.subtract(days=3),
                                  withdrawn_at=None)

        closed_specialist.questions_closed_at = now.subtract(days=2)
        closed_specialist.closed_at = now.subtract(days=1)
        db.session.add(closed_specialist)

        withdrawn_specialist = Brief(id=11,
                                     data={},
                                     framework=framework,
                                     lot=specialist_lot,
                                     users=users,
                                     created_at=now.subtract(days=2),
                                     published_at=now.subtract(days=3),
                                     withdrawn_at=None)

        withdrawn_specialist.questions_closed_at = now.add(days=3)
        withdrawn_specialist.closed_at = now.add(days=5)
        withdrawn_specialist.withdrawn_at = now
        db.session.add(withdrawn_specialist)

        db.session.commit()
        yield db.session.query(Brief).all()
示例#14
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
    def briefs(self, app, users, suppliers):
        framework = frameworks_service.find(slug='digital-marketplace').one_or_none()
        atm_lot = lots_service.find(slug='atm').one_or_none()
        specialist_lot = lots_service.find(slug='specialist').one_or_none()
        rfx_lot = lots_service.find(slug='rfx').one_or_none()
        now = pendulum.now('utc')

        with app.app_context():
            atm_brief = Brief(
                id=1,
                data={
                    'title': 'ATM title',
                    'closedAt': pendulum.today(tz='Australia/Sydney').add(days=14).format('%Y-%m-%d'),
                    'organisation': 'ABC',
                    'summary': 'My ATM summary',
                    'location': [
                        'New South Wales'
                    ],
                    'sellerCategory': '',
                    'openTo': 'all',
                    'requestMoreInfo': 'yes',
                    'evaluationType': [
                        'References',
                        'Case study',
                    ],
                    'attachments': [
                        'TEST3.pdf'
                    ],
                    'industryBriefing': 'TEST',
                    'startDate': 'ASAP',
                    'includeWeightings': True,
                    'evaluationCriteria': [
                        {
                            'criteria': 'TEST',
                            'weighting': '55'
                        },
                        {
                            'criteria': 'TEST 2',
                            'weighting': '45'
                        }
                    ],
                    'contactNumber': '0263635544',
                    'timeframeConstraints': 'TEST',
                    'backgroundInformation': 'TEST',
                    'outcome': 'TEST',
                    'endUsers': 'TEST',
                    'workAlreadyDone': 'TEST'
                },
                framework=framework,
                lot=atm_lot,
                users=users,
                published_at=now,
                withdrawn_at=None
            )

            atm_brief.questions_closed_at = now.add(days=3)
            atm_brief.closed_at = now.add(days=5)
            db.session.add(atm_brief)

            specialist_brief = Brief(
                id=2,
                data={
                    'areaOfExpertise': 'Software engineering and Development',
                    'attachments': [],
                    'budgetRange': '',
                    'closedAt': pendulum.today(tz='Australia/Sydney').add(days=14).format('%Y-%m-%d'),
                    'contactNumber': '0123456789',
                    'contractExtensions': '',
                    'contractLength': '1 year',
                    'comprehensiveTerms': True,
                    'essentialRequirements': [
                        {
                            'criteria': 'TEST',
                            'weighting': '55'
                        },
                        {
                            'criteria': 'TEST 2',
                            'weighting': '45'
                        }
                    ],
                    'evaluationType': [
                        'Responses to selection criteria',
                        'Résumés'.decode('utf-8')
                    ],
                    'includeWeightingsEssential': False,
                    'includeWeightingsNiceToHave': False,
                    'internalReference': '',
                    'location': [
                        'Australian Capital Territory'
                    ],
                    'maxRate': '123',
                    'niceToHaveRequirements': [
                        {
                            'criteria': 'Code review',
                            'weighting': '0'
                        }
                    ],
                    'numberOfSuppliers': '3',
                    'openTo': 'selected',
                    'organisation': 'Digital Transformation Agency',
                    'preferredFormatForRates': 'dailyRate',
                    'securityClearance': 'noneRequired',
                    'securityClearanceCurrent': '',
                    'securityClearanceObtain': '',
                    'securityClearanceOther': '',
                    'sellers': {
                        '1': {
                            'name': 'Seller 1'
                        }
                    },
                    'sellerCategory': '6',
                    'sellerSelector': 'oneSeller',
                    'startDate': pendulum.today(tz='Australia/Sydney').add(days=14).format('%Y-%m-%d'),
                    'summary': 'My specialist summary',
                    'title': 'Specialist title'
                },
                framework=framework,
                lot=specialist_lot,
                users=users,
                published_at=now,
                withdrawn_at=None
            )

            specialist_brief.questions_closed_at = now.add(days=3)
            specialist_brief.closed_at = now.add(days=5)
            db.session.add(specialist_brief)

            rfx_brief = Brief(
                id=3,
                data={
                    'title': 'TEST',
                    'closedAt': pendulum.today(tz='Australia/Sydney').add(days=14).format('%Y-%m-%d'),
                    'organisation': 'ABC',
                    'summary': 'TEST',
                    'workingArrangements': 'TEST',
                    'location': [
                        'New South Wales'
                    ],
                    'sellerCategory': '1',
                    'sellers': {
                        '1': {
                            'name': 'Seller 1'
                        }
                    },
                    'evaluationType': [
                        'Response template',
                        'Written proposal'
                    ],
                    'proposalType': [
                        'Breakdown of costs'
                    ],
                    'requirementsDocument': [
                        'TEST.pdf'
                    ],
                    'responseTemplate': [
                        'TEST2.pdf'
                    ],
                    'startDate': 'ASAP',
                    'contractLength': 'TEST',
                    'includeWeightings': True,
                    'essentialRequirements': [
                        {
                            'criteria': 'TEST',
                            'weighting': '55'
                        },
                        {
                            'criteria': 'TEST 2',
                            'weighting': '45'
                        }
                    ],
                    'niceToHaveRequirements': [],
                    'contactNumber': '0263635544'
                },
                framework=framework,
                lot=specialist_lot,
                users=users,
                published_at=now,
                withdrawn_at=None
            )

            rfx_brief.questions_closed_at = now.add(days=3)
            rfx_brief.closed_at = now.add(days=5)
            db.session.add(rfx_brief)

            yield db.session.query(Brief).all()