def close_opportunity_early(user_id, brief_id): brief = brief_service.get(brief_id) if not brief: raise NotFoundError('Opportunity {} does not exist'.format(brief_id)) if not brief_service.has_permission_to_brief(user_id, brief_id): raise UnauthorisedError( 'Not authorised to close opportunity {}'.format(brief_id)) if not can_close_opportunity_early(brief): raise BriefError('Unable to close opportunity {}'.format(brief_id)) user = users.get(user_id) if not user: raise NotFoundError('User {} does not exist'.format(user_id)) brief = brief_service.close_opportunity_early(brief) create_responses_zip(brief.id) send_opportunity_closed_early_email(brief, user) try: audit_service.log_audit_event( audit_type=audit_types.close_opportunity_early, data={'briefId': brief.id}, db_object=brief, user=user.email_address) publish_tasks.brief.delay(publish_tasks.compress_brief(brief), 'closed_early', email_address=user.email_address, name=user.name) except Exception as e: rollbar.report_exc_info() return brief
def send_removed_team_member_notification_emails(team_id, user_ids): team = team_service.find(id=team_id).first() to_addresses = [] for user_id in user_ids: user = users.get(user_id) to_addresses.append(user.email_address) if len(to_addresses) == 0: return email_body = render_email_template( 'team_member_removed.md', frontend_url=current_app.config['FRONTEND_ADDRESS'], team_lead=escape_markdown(current_user.name), team_name=escape_markdown(team.name)) subject = 'You have been removed from the {} team'.format( team.name.encode('utf-8')) send_or_handle_error( to_addresses, email_body, subject, current_app.config['DM_GENERIC_NOREPLY_EMAIL'], current_app.config['DM_GENERIC_SUPPORT_NAME'], event_description_for_errors='team member removed email') audit_service.log_audit_event(audit_type=audit_types.team_member_removed, data={ 'to_address': to_addresses, 'subject': subject }, db_object=team, user='')
def update_brief_remove_user(brief_id, user): updater_json = validate_and_return_updater_request() brief = briefs.get(brief_id) if user.isdigit(): u = [users.get(int(user))] else: u = [users.get_by_email(user)] if len(u) < 1: raise ValidationError("No user found: " + user) else: brief.users.remove(u[0]) audit = AuditEvent( audit_type=AuditTypes.update_brief, user=updater_json['updated_by'], data={ 'briefId': brief.id, 'briefJson': { 'remove_user': user }, }, db_object=brief, ) db.session.add(brief) db.session.add(audit) db.session.commit() return jsonify(briefs=brief.serialize(with_users=True)), 200
def create_team(): user = users.get(current_user.id) created_teams = team_service.get_teams_for_user(user.id, 'created') completed_teams = team_service.get_teams_for_user(user.id) if len(completed_teams) == 0: if len(created_teams) == 0: team = team_service.save( Team(name='', status='created', team_members=[ TeamMember(user_id=user.id, is_team_lead=True) ])) audit_service.log_audit_event(audit_type=audit_types.create_team, data={}, db_object=team, user=current_user.email_address) publish_tasks.team.delay(publish_tasks.compress_team(team), 'created') return get_team(team.id) created_team = created_teams.pop() return get_team(created_team.id) else: team = completed_teams[0] raise TeamError( 'You can only be in one team. You\'re already a member of {}.'. format(team.name))
def test_edit_documents_only_apply_edits_with_edited_flag_set(self, agency_service, briefs, users): atm_brief = brief_service.get(1) user = user_service.get(2) agency_service.get_agency_name.return_value = 'DTA' original_attachments = atm_brief.data['attachments'] edits = { 'closingDate': '', 'title': 'test', 'summary': 'test', 'sellers': {}, 'attachments': [], 'documentsEdited': False } brief = brief_edit_business.edit_opportunity(user.id, atm_brief.id, edits) assert len(original_attachments) > 0 assert brief.data['attachments'] == original_attachments edits = { 'closingDate': '', 'title': 'test', 'summary': 'test', 'sellers': {}, 'attachments': ['ABC.PDF'], 'documentsEdited': True } brief = brief_edit_business.edit_opportunity(user.id, atm_brief.id, edits) assert brief.data['attachments'] == ['ABC.PDF']
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 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 get_people_overview(): people_overview = {} user = users.get(current_user.id) completed_teams = team_service.get_teams_for_user(user.id) people = users.get_buyer_team_members(user.agency_id) people_overview.update(users=people) organisation = agency_service.get_agency_name(user.agency_id) people_overview.update(organisation=organisation) completed_teams_count = len(completed_teams) people_overview.update(completedTeamsCount=completed_teams_count) return people_overview
def get_teams_overview(): teams_overview = {} user = users.get(current_user.id) completed_teams = team_service.get_teams_for_user(user.id) overview = team_service.get_teams_overview(user.id, user.agency_id) teams_overview.update(overview=overview) organisation = agency_service.get_agency_name(user.agency_id) teams_overview.update(organisation=organisation) completed_teams_count = len(completed_teams) teams_overview.update(completedTeamsCount=completed_teams_count) return teams_overview
def get_teams_overview(): teams_overview = {} user = users.get(current_user.id) completed_teams = team_service.get_teams_for_user(user.id) email_domain = get_email_domain(user.email_address) overview = team_service.get_teams_overview(user.id, email_domain) teams_overview.update(overview=overview) organisation = users.get_user_organisation(email_domain) teams_overview.update(organisation=organisation) completed_teams_count = len(completed_teams) teams_overview.update(completedTeamsCount=completed_teams_count) return teams_overview
def get_people_overview(): people_overview = {} user = users.get(current_user.id) completed_teams = team_service.get_teams_for_user(user.id) domain = get_email_domain(user.email_address) people = users.get_buyer_team_members(domain) people_overview.update(users=people) organisation = users.get_user_organisation(domain) people_overview.update(organisation=organisation) completed_teams_count = len(completed_teams) people_overview.update(completedTeamsCount=completed_teams_count) return people_overview
def withdraw_opportunity(user_id, brief_id, withdrawal_reason): brief = brief_service.get(brief_id) if not brief: raise NotFoundError('Opportunity {} does not exist'.format(brief_id)) if not brief_service.has_permission_to_brief(user_id, brief_id): raise UnauthorisedError( 'Not authorised to withdraw opportunity {}'.format(brief_id)) if brief.status != 'live': raise BriefError('Unable to withdraw opportunity {}'.format(brief_id)) if not withdrawal_reason: raise ValidationError( 'Withdrawal reason is required for opportunity {}'.format( brief_id)) user = users.get(user_id) if not user: raise NotFoundError('User {} does not exist'.format(user_id)) brief = brief_service.withdraw_opportunity(brief, withdrawal_reason) organisation = agency_service.get_agency_name(user.agency_id) sellers_to_contact = brief_service.get_sellers_to_notify( brief, brief_business.is_open_to_all(brief)) for email_address in sellers_to_contact: send_opportunity_withdrawn_email_to_seller(brief, email_address, organisation) send_opportunity_withdrawn_email_to_buyers(brief, user) try: audit_service.log_audit_event( audit_type=audit_types.withdraw_opportunity, data={'briefId': brief.id}, db_object=brief, user=user.email_address) publish_tasks.brief.delay(publish_tasks.compress_brief(brief), 'withdrawn', email_address=user.email_address, name=user.name) except Exception as e: rollbar.report_exc_info() return brief
def test_edit_opportunity_creates_history_record(self, agency_service, briefs, users): specialist_brief = brief_service.get(2) user = user_service.get(2) agency_service.get_agency_name.return_value = 'DTA' edits = { 'closingDate': '', 'title': 'test', 'sellers': {}, 'summary': '' } brief = brief_edit_business.edit_opportunity(user.id, specialist_brief.id, edits) history = brief_history_service.all() assert len(history) == 1 assert history[0].brief_id == brief.id assert history[0].user_id == user.id
def send_team_member_notification_emails(team_id, user_ids=None): team = team_service.find(id=team_id).first() if user_ids is None or len(user_ids) == 0: # Team members added through the create flow members = team_member_service.find(team_id=team_id, is_team_lead=False).all() else: # Team members added through the edit flow members = team_member_service.get_team_members_by_user_id( team_id, user_ids) to_addresses = [] for member in members: user = users.get(member.user_id) to_addresses.append(user.email_address) if len(to_addresses) == 0: return email_body = render_email_template( 'team_member_added.md', frontend_url=current_app.config['FRONTEND_ADDRESS'], team_lead=escape_markdown(current_user.name), team_name=escape_markdown(team.name)) subject = '{} added you as a member of {}'.format( current_user.name, team.name.encode('utf-8')) send_or_handle_error( to_addresses, email_body, subject, current_app.config['DM_GENERIC_NOREPLY_EMAIL'], current_app.config['DM_GENERIC_SUPPORT_NAME'], event_description_for_errors='team member added email') audit_service.log_audit_event(audit_type=audit_types.team_member_added, data={ 'to_address': to_addresses, 'subject': subject }, db_object=team, user='')
def send_team_lead_notification_emails(team_id, user_ids=None): team = team_service.find(id=team_id).first() if user_ids is None or len(user_ids) == 0: # Team leads added through the create flow team_leads = team_member_service.find(team_id=team_id, is_team_lead=True).all() team_leads = [ team_lead for team_lead in team_leads if team_lead.user_id != current_user.id ] else: # Team leads added through the edit flow team_leads = team_member_service.get_team_leads_by_user_id( team_id, user_ids) to_addresses = [] for team_lead in team_leads: user = users.get(team_lead.user_id) to_addresses.append(user.email_address) if len(to_addresses) == 0: return email_body = render_email_template( 'team_lead_added.md', frontend_url=current_app.config['FRONTEND_ADDRESS'], team_name=escape_markdown(team.name)) subject = 'You have been upgraded to a team lead' send_or_handle_error(to_addresses, email_body, subject, current_app.config['DM_GENERIC_NOREPLY_EMAIL'], current_app.config['DM_GENERIC_SUPPORT_NAME'], event_description_for_errors='team lead added email') audit_service.log_audit_event(audit_type=audit_types.team_lead_added, data={'to_address': to_addresses}, db_object=team, user='')
def test_edit_opportunity_creates_audit_event(self, agency_service, briefs, users): specialist_brief = brief_service.get(2) user = user_service.get(2) agency_service.get_agency_name.return_value = 'DTA' edits = { 'closingDate': '', 'title': 'test', 'sellers': {}, 'summary': '' } brief = brief_edit_business.edit_opportunity(user.id, specialist_brief.id, edits) audit_event = audit_service.find( object_id=specialist_brief.id, object_type='Brief', type=audit_types.opportunity_edited.value ).one_or_none() assert audit_event is not None
def get(user_id): """Get User --- tags: - users parameters: - name: user_id in: path type: integer required: true responses: 200: description: A User type: object schema: $ref: '#/definitions/UserDetail' """ user = users.get(user_id) user_detail = UserView(user) return jsonify(user_detail.__dict__)
def test_edit_opportunity_creates_history_record_with_original_data(self, agency_service, briefs, users): specialist_brief = brief_service.get(2) original_closed_at = copy.deepcopy(specialist_brief.closed_at) original_specialist_data = copy.deepcopy(specialist_brief.data) user = user_service.get(2) agency_service.get_agency_name.return_value = 'DTA' edits = { 'closingDate': '', 'title': 'test', 'sellers': {}, 'summary': '' } brief = brief_edit_business.edit_opportunity(user.id, specialist_brief.id, edits) history = brief_history_service.all() assert pendulum.parse(history[0].data['closed_at'], tz='utc') == original_closed_at assert history[0].data['sellers'] == original_specialist_data['sellers'] assert history[0].data['sellerSelector'] == original_specialist_data['sellerSelector'] assert history[0].data['title'] == original_specialist_data['title']
def test_only_sellers_were_edited_is_false_when_title_edited(self, agency_service, briefs, users): specialist_brief = brief_service.get(2) user = user_service.get(2) agency_service.get_agency_name.return_value = 'DTA' edits = { 'closingDate': '', 'title': 'test', 'sellers': { '1': { 'name': 'Seller 1' }, '2': { 'name': 'Seller 2' } }, 'summary': '' } brief = brief_edit_business.edit_opportunity(user.id, specialist_brief.id, edits) only_sellers_edited = brief_edit_business.only_sellers_were_edited(specialist_brief.id) assert only_sellers_edited is False
def test_only_sellers_were_edited_is_false_when_summary_edited(self, agency_service, briefs, users): specialist_brief = brief_service.get(2) user = user_service.get(2) agency_service.get_agency_name.return_value = 'DTA' edits = { 'closingDate': pendulum.now('Australia/Canberra').add(days=7).to_date_string(), 'title': '', 'sellers': { '1': { 'name': 'Seller 1' }, '2': { 'name': 'Seller 2' } }, 'summary': 'New summary' } brief = brief_edit_business.edit_opportunity(user.id, specialist_brief.id, edits) only_sellers_edited = brief_edit_business.only_sellers_were_edited(specialist_brief.id) assert only_sellers_edited is False
def edit_opportunity(user_id, brief_id, edits): brief = brief_service.get(brief_id) if not brief: raise NotFoundError('Opportunity {} does not exist'.format(brief_id)) if not brief_service.has_permission_to_brief(user_id, brief_id): raise UnauthorisedError( 'Not authorised to edit opportunity {}'.format(brief_id)) if brief.status != 'live': raise BriefError('Unable to edit opportunity {}'.format(brief_id)) user = user_service.get(user_id) if not user: raise NotFoundError('User {} does not exist'.format(user_id)) previous_data = copy.deepcopy(brief.data) previous_data['closed_at'] = brief.closed_at.to_iso8601_string( extended=True) edit_title(brief, edits['title']) edit_summary(brief, edits['summary']) edit_closing_date(brief, edits['closingDate']) if 'documentsEdited' in edits and edits['documentsEdited']: if 'attachments' in edits: edit_attachments(brief, edits['attachments']) if 'requirementsDocument' in edits and 'requirementsDocument' in brief.data: edit_requirements_document(brief, edits['requirementsDocument']) if 'responseTemplate' in edits and 'responseTemplate' in brief.data: edit_response_template(brief, edits['responseTemplate']) organisation = None sellers_to_contact = [] if (title_was_edited(brief.data['title'], previous_data['title']) or summary_was_edited(brief.data['summary'], previous_data['summary']) or closing_date_was_edited( brief.closed_at.to_iso8601_string(extended=True), previous_data['closed_at']) or documents_were_edited(brief.data.get('attachments', []), previous_data.get('attachments', [])) or documents_were_edited( brief.data.get('requirementsDocument', []), previous_data.get('requirementsDocument', [])) or documents_were_edited(brief.data.get('responseTemplate', []), previous_data.get('responseTemplate', []))): organisation = agency_service.get_agency_name(user.agency_id) # We need to find sellers to contact about the current incoming edits before sellers are edited as we're # not sending additional sellers emails about the current edits that have been made. sellers_to_contact = brief_service.get_sellers_to_notify( brief, brief_business.is_open_to_all(brief)) sellers_to_invite = {} if 'sellers' in edits and sellers_were_edited( edits['sellers'], brief.data.get('sellers', {})): sellers_to_invite = get_sellers_to_invite(brief, edits['sellers']) edit_sellers(brief, sellers_to_invite) edit_seller_selector(brief, sellers_to_invite) # strip out any data keys not whitelisted brief = brief_business.remove_keys_not_whitelisted(brief) data_to_validate = copy.deepcopy(brief.data) # only validate the sellers being added in the edit if 'sellers' in edits and len(edits['sellers'].keys()) > 0: data_to_validate['sellers'] = copy.deepcopy(edits.get('sellers', {})) validator = None if brief.lot.slug == 'rfx': validator = RFXDataValidator(data_to_validate) elif brief.lot.slug == 'training2': validator = TrainingDataValidator(data_to_validate) elif brief.lot.slug == 'atm': validator = ATMDataValidator(data_to_validate) elif brief.lot.slug == 'specialist': validator = SpecialistDataValidator(data_to_validate) if validator is None: raise ValidationError('Validator not found for {}'.format( brief.lot.slug)) errors = [] if (title_was_edited(brief.data['title'], previous_data['title']) and not validator.validate_title()): errors.append('You must add a title') if (summary_was_edited(brief.data['summary'], previous_data['summary']) and not validator.validate_summary()): message = ('You must add what the specialist will do' if brief.lot.slug == 'specialist' else 'You must add a summary of work to be done') errors.append(message) if (brief.lot.slug != 'atm' and 'sellers' in edits and sellers_were_edited( edits['sellers'], brief.data.get('sellers', {})) and not validator.validate_sellers()): message = ( 'You must select some sellers' if brief.lot.slug == 'specialist' else 'You must select at least one seller and each seller must be assessed for the chosen category' ) errors.append(message) if (closing_date_was_edited( brief.closed_at.to_iso8601_string(extended=True), previous_data['closed_at']) and not validator.validate_closed_at(minimum_days=1)): message = ( 'The closing date must be at least 1 day into the future or not more than one year long' if brief.lot.slug == 'specialist' else 'The closing date must be at least 1 day into the future') errors.append(message) if len(errors) > 0: raise ValidationError(', '.join(errors)) brief_service.save(brief, do_commit=False) edit = BriefHistory(brief_id=brief.id, user_id=user_id, data=previous_data) brief_history_service.save(edit, do_commit=False) brief_service.commit_changes() if len(sellers_to_contact) > 0 and organisation: for email_address in sellers_to_contact: send_opportunity_edited_email_to_seller(brief, email_address, organisation) for code, data in sellers_to_invite.items(): supplier = supplier_service.get_supplier_by_code(code) if supplier: if brief.lot.slug == 'rfx': send_seller_invited_to_rfx_email(brief, supplier) elif brief.lot.slug == 'specialist': send_specialist_brief_seller_invited_email(brief, supplier) elif brief.lot.slug == 'training': send_seller_invited_to_training_email(brief, supplier) send_opportunity_edited_email_to_buyers(brief, user, edit) try: audit_service.log_audit_event( audit_type=audit_types.opportunity_edited, data={'briefId': brief.id}, db_object=brief, user=user.email_address) publish_tasks.brief.delay(publish_tasks.compress_brief(brief), 'edited', email_address=user.email_address, name=user.name) except Exception as e: rollbar.report_exc_info() return brief