def get_question(current_user, brief_id, question_id): brief = briefs.find(id=brief_id).one_or_none() if not brief: raise NotFoundError("Invalid brief id '{}'".format(brief_id)) if not briefs.has_permission_to_brief(current_user.id, brief.id): raise UnauthorisedError('Unauthorised to publish answer') question = brief_question_service.find(id=question_id, brief_id=brief.id).one_or_none() question_result = None if question: question_result = {"id": question.id, "data": question.data} return { "question": question_result, "brief": { "title": brief.data.get('title'), "id": brief.id, "lot": brief.lot.slug, "questionsCloseAt": brief.questions_closed_at, "closedAt": brief.closed_at, "internalReference": brief.data.get('internalReference'), "isOpenToAll": brief_business.is_open_to_all(brief), "status": brief.status } }
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 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_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 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 get_answers(brief_id): brief = briefs.find(id=brief_id).one_or_none() if not brief: raise NotFoundError("Invalid brief id '{}'".format(brief_id)) answers = brief_clarification_question_service.get_answers(brief_id) return { "answers": answers, "brief": { "title": brief.data.get('title'), "id": brief.id, "closedAt": brief.closed_at, "internalReference": brief.data.get('internalReference') }, "questionCount": get_counts(brief_id, answers=answers) }
def get_questions(current_user, brief_id): brief = briefs.find(id=brief_id).one_or_none() if not brief: raise NotFoundError("Invalid brief id '{}'".format(brief_id)) if not briefs.has_permission_to_brief(current_user.id, brief.id): raise UnauthorisedError('Unauthorised to publish answer') questions = brief_question_service.get_questions(brief_id) return { "questions": questions, "brief": { "title": brief.data.get('title'), "id": brief.id, "closedAt": brief.closed_at, "internalReference": brief.data.get('internalReference') }, "questionCount": get_counts(brief_id, questions=questions) }
def create_responses_zip(brief_id): brief = briefs.find(id=brief_id).one_or_none() if not brief: raise CreateResponsesZipException( 'Failed to load brief for id {}'.format(brief_id)) responses = brief_responses_service.get_responses_to_zip( brief_id, brief.lot.slug) if not responses: raise CreateResponsesZipException( 'There were no respones for brief id {}'.format(brief_id)) if brief.lot.slug not in [ 'digital-professionals', 'training', 'rfx', 'training2', 'atm', 'specialist' ]: raise CreateResponsesZipException( 'Brief id {} is not a compatible lot'.format(brief_id)) print 'Generating zip for brief id: {}'.format(brief_id) BUCKET_NAME = getenv('S3_BUCKET_NAME') s3 = boto3.resource('s3', region_name=getenv('AWS_REGION'), aws_access_key_id=getenv('AWS_ACCESS_KEY_ID'), aws_secret_access_key=getenv('AWS_SECRET_ACCESS_KEY'), endpoint_url=getenv('AWS_S3_URL')) bucket = s3.Bucket(BUCKET_NAME) files = [] attachments = brief_responses_service.get_all_attachments(brief_id) for attachment in attachments: if attachment['file_name'].startswith( 'digital-marketplace') and '/' in attachment['file_name']: key = attachment['file_name'] zip_file_name = attachment['file_name'].split('/')[-1] else: key = 'digital-marketplace/documents/brief-{}/supplier-{}/{}'.format( brief_id, attachment['supplier_code'], attachment['file_name']) zip_file_name = attachment['file_name'] files.append({ 'key': key, 'zip_name': 'opportunity-{}-documents/{}/{}'.format( brief_id, secure_filename(attachment['supplier_name']), zip_file_name) }) with tempfile.TemporaryFile() as archive: with zipfile.ZipFile(archive, mode='w', compression=zipfile.ZIP_DEFLATED) as zf: for file in files: s3file = file['key'] with BytesIO() as s3_stream: try: bucket.download_fileobj(s3file, s3_stream) zf.writestr(file['zip_name'], s3_stream.getvalue()) except botocore.exceptions.ClientError as e: raise CreateResponsesZipException( 'The file "{}" failed to download'.format(s3file)) finally: s3_stream.close() csvdata = generate_brief_responses_csv(brief, responses) csv_file_name = ( 'opportunity-{}-raw.csv'.format(brief_id) if brief.lot.slug == 'digital-professionals' else 'responses-to-requirements-{}.csv'.format(brief_id)) zf.writestr(csv_file_name, csvdata.encode('utf-8')) if brief.lot.slug == 'digital-professionals': compliance_check_template = template_env.get_template( 'compliance-check.html') compliance_check_html = render_template( compliance_check_template, brief=brief, responses=responses) zf.writestr('compliance-check-{}.html'.format(brief_id), compliance_check_html.encode('utf-8')) candidates = prepare_specialist_responses(brief, responses) response_criteria_template = template_env.get_template( 'response-criteria.html') response_criteria_html = render_template( response_criteria_template, brief=brief, candidates=candidates) zf.writestr('responses-{}.html'.format(brief_id), response_criteria_html.encode('utf-8')) elif brief.lot.slug == 'specialist': compliance_check_template = template_env.get_template( 'compliance-check-specialist.html') supplier_labour_hire = {} for response in responses: labour_hire = [] for state, state_value in response.supplier.data.get( 'labourHire', {}).iteritems(): if state_value.get( 'licenceNumber') and state_value.get('expiry'): state_value['state'] = state.upper() state_value['expiry'] = (pendulum.parse( state_value['expiry']).format( 'DD MMMM YYYY', formatter='alternative')) labour_hire.append(state_value) supplier_labour_hire[response.supplier.code] = labour_hire compliance_check_html = render_template( compliance_check_template, brief=brief, supplier_labour_hire=supplier_labour_hire, responses=responses) zf.writestr('Compliance check ({}).html'.format(brief_id), compliance_check_html.encode('utf-8')) response_criteria_template = template_env.get_template( 'response-criteria-specialist.html') candidates = [] for response in responses: data = response.data candidates.append({ 'essential_requirement_responses': data.get('essentialRequirements', {}), 'nice_to_have_requirement_responses': data.get('niceToHaveRequirements', {}), 'name': '{} {}'.format(data.get('specialistGivenNames', ''), data.get('specialistSurname', '')), 'seller': response.supplier.name }) response_criteria_html = render_template( response_criteria_template, brief=brief, candidates=candidates, essential_requirements=brief.data.get( 'essentialRequirements', {}), nice_to_have_requirements=brief.data.get( 'niceToHaveRequirements', {})) zf.writestr('Responses ({}).html'.format(brief_id), response_criteria_html.encode('utf-8')) archive.seek(0) try: brief.responses_zip_filesize = len(archive.read()) archive.seek(0) db.session.add(brief) db.session.commit() except Exception as e: raise CreateResponsesZipException(str(e)) try: bucket.upload_fileobj( archive, 'digital-marketplace/archives/brief-{}/brief-{}-resumes.zip'. format(brief_id, brief_id)) except botocore.exceptions.ClientError as e: raise CreateResponsesZipException( 'The responses archive for brief id "{}" failed to upload'. format(brief_id))
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())
def test_can_not_close_opportunity_early_with_incorrect_status( self, overview_briefs, status): brief = briefs_service.find(status=status).first() can_close = can_close_opportunity_early(brief) assert can_close is False
def get_brief(brief_id): """Get brief --- tags: - brief parameters: - name: brief_id in: path type: number required: true responses: 200: description: Brief retrieved successfully. schema: type: object properties: brief: type: object brief_response_count: type: number invited_seller_count: type: number can_respond: type: boolean open_to_all: type: boolean is_brief_owner: type: boolean is_buyer: type: boolean has_responded: type: boolean has_chosen_brief_category: type: boolean is_assessed_for_category: type: boolean is_assessed_in_any_category: type: boolean is_approved_seller: type: boolean is_awaiting_application_assessment: type: boolean is_awaiting_domain_assessment: type: boolean has_been_assessed_for_brief: type: boolean open_to_category: type: boolean is_applicant: type: boolean is_recruiter: type: boolean domains: type: array items: type: object 403: description: Unauthorised to view brief. 404: description: Brief not found. 500: description: Unexpected error. """ brief = briefs.find(id=brief_id).one_or_none() if not brief: not_found("No brief for id '%s' found" % (brief_id)) user_role = current_user.role if hasattr(current_user, 'role') else None invited_sellers = brief.data['sellers'] if 'sellers' in brief.data else {} is_buyer = False is_brief_owner = False brief_user_ids = [user.id for user in brief.users] if user_role == 'buyer': is_buyer = True if current_user.id in brief_user_ids: is_brief_owner = True if brief.status == 'draft' and not is_brief_owner: return forbidden("Unauthorised to view brief") brief_response_count = len(brief_responses_service.get_brief_responses(brief_id, None)) invited_seller_count = len(invited_sellers) open_to_all = brief.lot.slug == 'atm' and brief.data.get('openTo', '') == 'all' open_to_category = brief.lot.slug == 'atm' and brief.data.get('openTo', '') == 'category' is_applicant = user_role == 'applicant' # gather facts about the user's status against this brief user_status = BriefUserStatus(brief, current_user) has_chosen_brief_category = user_status.has_chosen_brief_category() is_assessed_for_category = user_status.is_assessed_for_category() is_assessed_in_any_category = user_status.is_assessed_in_any_category() is_awaiting_application_assessment = user_status.is_awaiting_application_assessment() is_awaiting_domain_assessment = user_status.is_awaiting_domain_assessment() has_been_assessed_for_brief = user_status.has_been_assessed_for_brief() is_recruiter_only = user_status.is_recruiter_only() is_approved_seller = user_status.is_approved_seller() can_respond = user_status.can_respond() has_responded = user_status.has_responded() # remove private data for non brief owners brief.data['contactEmail'] = '' brief.data['users'] = None if not is_buyer: if 'sellers' in brief.data: brief.data['sellers'] = {} brief.responses_zip_filesize = None brief.data['contactNumber'] = '' if not can_respond: brief.data['proposalType'] = [] brief.data['evaluationType'] = [] brief.data['responseTemplate'] = [] brief.data['requirementsDocument'] = [] brief.data['industryBriefing'] = '' brief.data['backgroundInformation'] = '' brief.data['outcome'] = '' brief.data['endUsers'] = '' brief.data['workAlreadyDone'] = '' brief.data['timeframeConstraints'] = '' brief.data['attachments'] = [] else: brief.data['contactEmail'] = [user.email_address for user in brief.users][0] if not is_brief_owner: if 'sellers' in brief.data: brief.data['sellers'] = {} brief.data['industryBriefing'] = '' brief.data['contactNumber'] = '' domains = [] for domain in domain_service.all(): domains.append({ 'id': str(domain.id), 'name': domain.name }) return jsonify(brief=brief.serialize(with_users=False, with_author=is_brief_owner), brief_response_count=brief_response_count, invited_seller_count=invited_seller_count, can_respond=can_respond, has_chosen_brief_category=has_chosen_brief_category, is_assessed_for_category=is_assessed_for_category, is_assessed_in_any_category=is_assessed_in_any_category, is_approved_seller=is_approved_seller, is_awaiting_application_assessment=is_awaiting_application_assessment, is_awaiting_domain_assessment=is_awaiting_domain_assessment, has_been_assessed_for_brief=has_been_assessed_for_brief, open_to_all=open_to_all, open_to_category=open_to_category, is_brief_owner=is_brief_owner, is_buyer=is_buyer, is_applicant=is_applicant, is_recruiter_only=is_recruiter_only, has_responded=has_responded, domains=domains)