def decline_join_request(team_id, token): data = get_json_from_request() if 'reason' not in data or not data['reason']: abort('Must provide reason for decline') try: team = team_business.get_team(team_id) except NotFoundError as e: return not_found(e.message) except UnauthorisedError as e: return forbidden(e.message) join_request = team_business.get_join_request(token) if not join_request or int(join_request.data['team_id']) != team_id: return not_found( 'The token is invalid, or this request has already been declined or accepted' ) if ('user_id' in join_request.data and (team_member_service.get_team_members_by_user_id( team_id, [int(join_request.data['user_id'])]) or team_member_service.get_team_leads_by_user_id( team_id, [int(join_request.data['user_id'])]))): abort('This user is already a member of the team') team_business.decline_join_request(join_request, data['reason'], team_id) return jsonify(success=True)
def 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())
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
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
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)
def create_team(): try: team = team_business.create_team() except TeamError as e: abort(e.message) except NotFoundError as e: return not_found(e.message) except UnauthorisedError as e: return forbidden(e.message) return jsonify(team)
def request_to_join(team_id): try: team_business.request_to_join(current_user.email_address, team_id, current_user.agency_id) except NotFoundError as e: return not_found(e.message) except UnauthorisedError as e: return forbidden(e.message) except ValidationError as e: abort(e.message) return jsonify(success=True)
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)
def update_team(team_id): data = get_json_from_request() try: team = team_business.update_team(team_id, data) except ValidationError as e: return abort(e.message) except NotFoundError as e: return not_found(e.message) except UnauthorisedError as e: return forbidden(e.message) return jsonify(team)
def 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
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)
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)
def get_brief_overview(brief_id): """Overview (role=buyer) --- tags: - brief definitions: BriefOverview: type: object properties: sections: type: array items: $ref: '#/definitions/BriefOverviewSections' title: type: string BriefOverviewSections: type: array items: $ref: '#/definitions/BriefOverviewSection' BriefOverviewSection: type: object properties: links: type: array items: $ref: '#/definitions/BriefOverviewSectionLinks' title: type: string BriefOverviewSectionLinks: type: array items: $ref: '#/definitions/BriefOverviewSectionLink' BriefOverviewSectionLink: type: object properties: complete: type: boolean path: type: string nullable: true text: type: string responses: 200: description: Data for the Overview page schema: $ref: '#/definitions/BriefOverview' 400: description: Lot not supported. 403: description: Unauthorised to view brief. 404: description: brief_id not found """ brief = briefs.get(brief_id) if not brief: not_found("Invalid brief id '{}'".format(brief_id)) if current_user.role == 'buyer': brief_user_ids = [user.id for user in brief.users] if current_user.id not in brief_user_ids: return forbidden('Unauthorised to view brief') if not (brief.lot.slug == 'digital-professionals' or brief.lot.slug == 'training'): abort('Lot {} is not supported'.format(brief.lot.slug)) sections = brief_overview_service.get_sections(brief) return jsonify(sections=sections, status=brief.status, title=brief.data['title']), 200
def _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 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)
def update_brief(brief_id): """Update RFX brief (role=buyer) --- tags: - brief definitions: RFXBrief: type: object properties: title: type: string organisation: type: string location: type: array items: type: string summary: type: string industryBriefing: type: string sellerCategory: type: string sellers: type: object attachments: type: array items: type: string requirementsDocument: type: array items: type: string responseTemplate: type: array items: type: string evaluationType: type: array items: type: string proposalType: type: array items: type: string evaluationCriteria: type: array items: type: object includeWeightings: type: boolean closedAt: type: string contactNumber: type: string startDate: type: string contractLength: type: string contractExtensions: type: string budgetRange: type: string workingArrangements: type: string securityClearance: type: string parameters: - name: brief_id in: path type: number required: true - name: body in: body required: true schema: $ref: '#/definitions/RFXBrief' responses: 200: description: Brief updated successfully. schema: $ref: '#/definitions/RFXBrief' 400: description: Bad request. 403: description: Unauthorised to update RFX brief. 404: description: Brief not found. 500: description: Unexpected error. """ brief = briefs.get(brief_id) if not brief: not_found("Invalid brief id '{}'".format(brief_id)) if brief.status != 'draft': abort('Cannot edit a {} brief'.format(brief.status)) if brief.lot.slug not in ['rfx', 'atm']: abort('Brief lot not supported for editing') if current_user.role == 'buyer': brief_user_ids = [user.id for user in brief.users] if current_user.id not in brief_user_ids: return forbidden('Unauthorised to update brief') data = get_json_from_request() publish = False if 'publish' in data and data['publish']: del data['publish'] publish = True if brief.lot.slug == 'rfx': # validate the RFX JSON request data errors = RFXDataValidator(data).validate(publish=publish) if len(errors) > 0: abort(', '.join(errors)) if brief.lot.slug == 'atm': # validate the ATM JSON request data errors = ATMDataValidator(data).validate(publish=publish) if len(errors) > 0: abort(', '.join(errors)) if brief.lot.slug == 'rfx' and 'evaluationType' in data: if 'Written proposal' not in data['evaluationType']: data['proposalType'] = [] if 'Response template' not in data['evaluationType']: data['responseTemplate'] = [] if brief.lot.slug == 'rfx' and 'sellers' in data and len(data['sellers']) > 0: data['sellerSelector'] = 'someSellers' if len(data['sellers']) > 1 else 'oneSeller' data['areaOfExpertise'] = '' if brief.lot.slug == 'atm' and 'openTo' in data: if data['openTo'] == 'all': data['sellerSelector'] = 'allSellers' data['sellerCategory'] = '' elif data['openTo'] == 'category': data['sellerSelector'] = 'someSellers' brief_domain = ( domain_service.get_by_name_or_id(int(data['sellerCategory'])) if data['sellerCategory'] else None ) if brief_domain: data['areaOfExpertise'] = brief_domain.name previous_status = brief.status if publish: brief.publish(closed_at=data['closedAt']) if 'sellers' in brief.data and data['sellerSelector'] != 'allSellers': for seller_code, seller in brief.data['sellers'].iteritems(): supplier = suppliers.get_supplier_by_code(seller_code) if brief.lot.slug == 'rfx': send_seller_invited_to_rfx_email(brief, supplier) try: brief_url_external = '{}/2/digital-marketplace/opportunities/{}'.format( current_app.config['FRONTEND_ADDRESS'], brief_id ) _notify_team_brief_published( brief.data['title'], brief.data['organisation'], current_user.name, current_user.email_address, brief_url_external ) except Exception as e: pass brief.data = data briefs.save_brief(brief) if publish: brief_url_external = '{}/2/digital-marketplace/opportunities/{}'.format( current_app.config['FRONTEND_ADDRESS'], brief_id ) publish_tasks.brief.delay( publish_tasks.compress_brief(brief), 'published', previous_status=previous_status, name=current_user.name, email_address=current_user.email_address, url=brief_url_external ) try: audit_service.log_audit_event( audit_type=AuditTypes.update_brief, user=current_user.email_address, data={ 'briefId': brief.id, 'briefData': brief.data }, db_object=brief) except Exception as e: rollbar.report_exc_info() return jsonify(brief.serialize(with_users=False))
def 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 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)
def delete_brief(brief_id): """Delete brief (role=buyer) --- tags: - brief definitions: DeleteBrief: type: object properties: message: type: string parameters: - name: brief_id in: path type: number required: true responses: 200: description: Brief deleted successfully. schema: $ref: '#/definitions/DeleteBrief' 400: description: Bad request. Brief status must be 'draft'. 403: description: Unauthorised to delete brief. 404: description: brief_id not found. 500: description: Unexpected error. """ brief = briefs.get(brief_id) if not brief: not_found("Invalid brief id '{}'".format(brief_id)) if current_user.role == 'buyer': brief_user_ids = [user.id for user in brief.users] if current_user.id not in brief_user_ids: return forbidden('Unauthorised to delete brief') if brief.status != 'draft': abort('Cannot delete a {} brief'.format(brief.status)) audit = AuditEvent( audit_type=AuditTypes.delete_brief, user=current_user.email_address, data={ 'briefId': brief_id }, db_object=None ) try: deleted_brief = publish_tasks.compress_brief(brief) audit_service.save(audit) briefs.delete(brief) publish_tasks.brief.delay( deleted_brief, 'deleted', user=current_user.email_address ) except Exception as e: extra_data = {'audit_type': AuditTypes.delete_brief, 'briefId': brief.id, 'exception': e.message} rollbar.report_exc_info(extra_data=extra_data) return jsonify(message='Brief {} deleted'.format(brief_id)), 200
def get_brief_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)