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
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
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))
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
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()
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()