def notify_paid_invoice_slack_admin(invoice): invoice = clean_instance(invoice, Invoice) if invoice.legacy_id or not invoice.paid: # ignore legacy invoices return project_url = '{}/projects/{}/'.format(TUNGA_URL, invoice.project.id) person_url = '{}/network/{}/'.format(TUNGA_URL, invoice.user.username) invoice_url = '{}/api/invoices/{}/download/?format=pdf'.format(TUNGA_URL, invoice.id) slack_msg = ':tada: A {} of *EUR {}* has been {} *<{}|{}>* for <{}|{}> | <{}|Download Invoice>'.format( invoice.type == INVOICE_TYPE_SALE and 'payment' or 'payout', invoice.amount, invoice.type == INVOICE_TYPE_SALE and 'made by' or 'sent to', person_url, invoice.user.display_name.encode('utf-8'), project_url, invoice.full_title, invoice_url ) slack_utils.send_incoming_webhook( SLACK_STAFF_INCOMING_WEBHOOK, { slack_utils.KEY_TEXT: slack_msg, slack_utils.KEY_CHANNEL: SLACK_STAFF_PAYMENTS_CHANNEL } )
def notify_user_profile_updated_slack(instance, edited=None): instance = clean_instance(instance, UserProfile) if instance.user.type != USER_TYPE_DEVELOPER: return profile_url = '{}/developer/{}'.format(TUNGA_URL, instance.user.username) slack_msg = "{}'s profile has been updated | <{}|Review on Tunga>".format( instance.user.display_name, profile_url) attachments = [{ slack_utils.KEY_TITLE: instance.user.display_name, slack_utils.KEY_TITLE_LINK: profile_url, slack_utils.KEY_TEXT: '*Name:* {}\n' '*Location:* {}\n' '*Skills*: {}\n' '*Verified:* {}'.format(instance.user.display_name, instance.location, str(instance.skills), instance.user.verified and 'True' or 'False'), slack_utils.KEY_MRKDWN_IN: [slack_utils.KEY_TEXT], slack_utils.KEY_COLOR: instance.user.verified and SLACK_ATTACHMENT_COLOR_GREEN or SLACK_ATTACHMENT_COLOR_RED }] slack_utils.send_incoming_webhook( SLACK_STAFF_INCOMING_WEBHOOK, { slack_utils.KEY_TEXT: slack_msg, slack_utils.KEY_ATTACHMENTS: attachments, slack_utils.KEY_CHANNEL: SLACK_STAFF_PROFILES_CHANNEL })
def notify_paid_invoice_email_dev(invoice): invoice = clean_instance(invoice, Invoice) if invoice.legacy_id or invoice.type != INVOICE_TYPE_PURCHASE or not invoice.paid: # ignore legacy invoices and only notify about developer invoices return to = [invoice.user.email] merge_vars = [ mandrill_utils.create_merge_var(MANDRILL_VAR_FIRST_NAME, invoice.user.first_name), mandrill_utils.create_merge_var('payout_title', invoice.full_title), ] pdf_file_contents = base64.b64encode(invoice.pdf) attachments = [ dict(content=pdf_file_contents, name='Invoice - {}.pdf'.format(invoice.full_title), type='application/pdf') ] mandrill_utils.send_email('87-payout-made', to, merge_vars=merge_vars, attachments=attachments)
def notify_new_contact_request_email(contact_request): contact_request = clean_instance(contact_request, ContactRequest) if contact_request.body: merge_vars = [ mandrill_utils.create_merge_var('full_name', contact_request.fullname), mandrill_utils.create_merge_var('email', contact_request.email), mandrill_utils.create_merge_var('message', contact_request.body), ] mandrill_utils.send_email('73_Platform-guest-emails', TUNGA_CONTACT_REQUEST_EMAIL_RECIPIENTS, merge_vars=merge_vars) else: subject = "New {} Request".format(contact_request.item and 'Offer' or 'Contact') msg_suffix = 'wants to know more about Tunga.' if contact_request.item: item_name = contact_request.get_item_display() subject = '%s (%s)' % (subject, item_name) msg_suffix = 'requested for "%s"' % item_name to = TUNGA_CONTACT_REQUEST_EMAIL_RECIPIENTS ctx = { 'email': contact_request.email, 'message': '%s %s ' % (contact_request.email, msg_suffix) } if send_mail(subject, 'tunga/email/contact_request_message', to, ctx): contact_request.email_sent_at = datetime.datetime.utcnow() contact_request.save()
def notify_interest_poll_email(interest_poll, reminder=False): interest_poll = clean_instance(interest_poll, InterestPoll) to = [interest_poll.user.email] poll_url = '{}/poll/{}/{}'.format(TUNGA_URL, interest_poll.id, interest_poll.token) merge_vars = [ mandrill_utils.create_merge_var(MANDRILL_VAR_FIRST_NAME, interest_poll.user.first_name), mandrill_utils.create_merge_var('opportunity_title', interest_poll.project.title), mandrill_utils.create_merge_var('skills', str(interest_poll.project.skills)), mandrill_utils.create_merge_var('description', interest_poll.project.description), mandrill_utils.create_merge_var('scope', interest_poll.project.get_expected_duration_display()), mandrill_utils.create_merge_var('yes_url', '{}?status=interested'.format(poll_url)), mandrill_utils.create_merge_var('no_url', '{}?status=uninterested'.format(poll_url)), ] mandrill_response = mandrill_utils.send_email( reminder and '90-availability-for-project-reminder' or '89-availability-for-project', to, merge_vars=merge_vars ) if mandrill_response: if reminder: interest_poll.reminded_at = datetime.datetime.utcnow() else: interest_poll.sent_at = datetime.datetime.utcnow() interest_poll.save() mandrill_utils.log_emails.delay(mandrill_response, to)
def notify_progress_report_deadline_missed_slack_admin(instance): instance = clean_instance(instance, ProgressReport) task_url = '{}/work/{}'.format(TUNGA_URL, instance.event.task.id) slack_msg = "`Alert (!):` Follow up on missed deadline for \"{}\" | <{}|View on Tunga>".format( instance.event.task.summary, task_url ) attachments = [ { slack_utils.KEY_TITLE: instance.event.task.summary, slack_utils.KEY_TITLE_LINK: task_url, slack_utils.KEY_TEXT: 'A deadline has been missed on the "{}" {}\n' '*Was the client informed before hand?:* {}\n' 'Please contact the stakeholders.'.format( instance.event.task.summary, instance.event.task.is_task and 'task' or 'project', instance.deadline_miss_communicated and 'Yes' or 'No' ), slack_utils.KEY_MRKDWN_IN: [slack_utils.KEY_TEXT], slack_utils.KEY_COLOR: SLACK_ATTACHMENT_COLOR_TUNGA }, create_task_stakeholders_attachment_slack(instance.event.task, show_title=False) ] slack_utils.send_incoming_webhook( SLACK_STAFF_INCOMING_WEBHOOK, { slack_utils.KEY_TEXT: slack_msg, slack_utils.KEY_ATTACHMENTS: attachments, slack_utils.KEY_CHANNEL: SLACK_STAFF_UPDATES_CHANNEL } )
def notify_progress_report_stuck_slack_admin(instance): instance = clean_instance(instance, ProgressReport) task_url = '{}/work/{}/event/{}'.format(TUNGA_URL, instance.event.task.id, instance.event.id) slack_msg = "`Alert (!):` The status for the \"{}\" {} has been classified as stuck | <{}|View on Tunga>".format( instance.event.task.summary, instance.event.task.is_task and 'task' or 'project', task_url ) attachments = [ { slack_utils.KEY_TITLE: instance.event.task.summary, slack_utils.KEY_TITLE_LINK: task_url, slack_utils.KEY_TEXT: 'Please contact all stakeholders.', slack_utils.KEY_MRKDWN_IN: [slack_utils.KEY_TEXT], slack_utils.KEY_COLOR: SLACK_ATTACHMENT_COLOR_TUNGA }, create_task_stakeholders_attachment_slack(instance.event.task, show_title=False) ] slack_utils.send_incoming_webhook( SLACK_STAFF_INCOMING_WEBHOOK, { slack_utils.KEY_TEXT: slack_msg, slack_utils.KEY_ATTACHMENTS: attachments, slack_utils.KEY_CHANNEL: SLACK_STAFF_UPDATES_CHANNEL } )
def notify_new_task_community_email(instance): instance = clean_instance(instance, Task) # Notify Devs or PMs community_receivers = None if instance.is_developer_ready: # Notify developers if instance.approved and instance.visibility in [VISIBILITY_DEVELOPER, VISIBILITY_MY_TEAM]: community_receivers = get_suggested_community_receivers(instance, user_type=USER_TYPE_DEVELOPER) elif instance.is_project and not instance.pm: community_receivers = get_suggested_community_receivers(instance, user_type=USER_TYPE_PROJECT_MANAGER) if instance.is_project and instance.pm: community_receivers = [instance.pm] subject = "New {} created by {}".format( instance.scope == TASK_SCOPE_TASK and 'task' or 'project', instance.user.first_name ) if community_receivers: to = [community_receivers[0].email] bcc = None if len(community_receivers) > 1: bcc = [user.email for user in community_receivers[1:]] if community_receivers[1:] else None ctx = { 'owner': instance.owner or instance.user, 'task': instance, 'task_url': '{}/work/{}/'.format(TUNGA_URL, instance.id) } send_mail(subject, 'tunga/email/new_task', to, ctx, bcc=bcc, **dict(deal_ids=[instance.hubspot_deal_id]))
def notify_progress_report_stuck_email_pm(instance): instance = clean_instance(instance, ProgressReport) subject = "You guys are stuck, but help is underway." pm = instance.event.task.pm if not pm: return to = [pm.email] ctx = { 'owner': instance.event.task.owner or instance.event.task.user, 'reporter': instance.user, 'pm': pm, 'event': instance.event, 'report': instance, 'update_url': '{}/work/{}/event/{}/'.format(TUNGA_URL, instance.event.task.id, instance.event.id) } send_mail(subject, 'tunga/email/report_stuck_pm', to, ctx, **dict(deal_ids=[instance.event.task.hubspot_deal_id]))
def notify_payment_link_client_email(instance): instance = clean_instance(instance, Task) to = [instance.user.email] if instance.owner and instance.owner.email != instance.user.email: to.append(instance.owner.email) task_url = '{}/task/{}/'.format(TUNGA_URL, instance.id) payment_link = '{}pay/'.format(task_url) owner = instance.owner or instance.user merge_vars = [ mandrill_utils.create_merge_var(MANDRILL_VAR_FIRST_NAME, owner.first_name), mandrill_utils.create_merge_var('payment_title', instance.summary), mandrill_utils.create_merge_var('payment_link', payment_link), ] mandrill_response = mandrill_utils.send_email('70-payment-link-ready', to, merge_vars=merge_vars) if mandrill_response: instance.payment_link_sent = True instance.payment_link_sent_at = datetime.datetime.utcnow() instance.save() mandrill_utils.log_emails.delay(mandrill_response, to, deal_ids=[instance.hubspot_deal_id])
def notify_new_task_admin_email(instance, new_user=False, completed=False, call_scheduled=False): instance = clean_instance(instance, Task) completed_phrase_subject = '' completed_phrase_body = '' if call_scheduled: completed_phrase_subject = 'availability window shared' completed_phrase_body = 'shared an availability window' elif completed: completed_phrase_subject = 'details completed' completed_phrase_body = 'completed the details' subject = "New{} {} {} by {}{}".format( (completed or call_scheduled) and ' wizard' or '', instance.scope == TASK_SCOPE_TASK and 'task' or 'project', completed_phrase_subject or 'created', instance.user.first_name, new_user and ' (New user)' or '' ) to = TUNGA_STAFF_LOW_LEVEL_UPDATE_EMAIL_RECIPIENTS # Notified via Slack so limit receiving admins ctx = { 'owner': instance.owner or instance.user, 'task': instance, 'task_url': '{}/task/{}/'.format(TUNGA_URL, instance.id), 'completed_phrase': completed_phrase_body, } send_mail(subject, 'tunga/email/new_task', to, ctx, **dict(deal_ids=[instance.hubspot_deal_id]))
def notify_parties_of_low_rating_email(instance): instance = clean_instance(instance, ProgressReport) is_client_report = instance.event.type in [LEGACY_PROGRESS_EVENT_TYPE_CLIENT, LEGACY_PROGRESS_EVENT_TYPE_CLIENT_MID_SPRINT] if is_client_report: subject = "Work Rating For {}".format(instance.event.task.summary) ctx = { 'owner': instance.event.task.owner or instance.event.task.user, 'event': instance, 'update_url': '{}/work/{}/event/{}/'.format(TUNGA_URL, instance.event.task.id, instance.event.id) } # send to client if instance.task.owner: to = [instance.event.task.owner.email] email_template = 'low_rating_client' send_mail( subject, 'tunga/email/{}'.format(email_template), to, ctx, **dict(deal_ids=[instance.event.task.hubspot_deal_id]) ) # send to pm if instance.event.task.pm: to = [instance.event.task.pm.email] email_template = 'low_rating_pm' send_mail( subject, 'tunga/email/{}'.format(email_template), to, ctx, **dict(deal_ids=[instance.event.task.hubspot_deal_id]) ) # send to user if instance.event.task.user: to = [instance.event.task.user.email] email_template = 'low_rating_user' send_mail( subject, 'tunga/email/{}'.format(email_template), to, ctx, **dict(deal_ids=[instance.event.task.hubspot_deal_id]) )
def trigger_schedule_call_automation(user): user = clean_instance(user, get_user_model()) mailchimp_utils.add_email_to_automation_queue( email_address=user.email, workflow_id=MAILCHIMP_NEW_USER_AUTOMATION_WORKFLOW_ID, email_id=MAILCHIMP_NEW_USER_AUTOMATION_EMAIL_ID )
def notify_progress_report_wont_meet_deadline_slack_admin(instance): instance = clean_instance(instance, ProgressReport) task_url = '{}/work/{}/event/{}'.format(TUNGA_URL, instance.event.task.id, instance.event.id) slack_msg = "`Alert (!):` {} doesn't expect to meet the deadline | <{}|View on Tunga>".format( instance.event.type in [LEGACY_PROGRESS_EVENT_TYPE_PM, LEGACY_PROGRESS_EVENT_TYPE_MILESTONE_INTERNAL] and 'PM' or 'Developer', task_url ) attachments = [ { slack_utils.KEY_TITLE: instance.event.task.summary, slack_utils.KEY_TITLE_LINK: task_url, slack_utils.KEY_TEXT: 'The {} on the \"{}\" {} has indicated that they might not meet the coming deadline.\n' 'Please contact all stakeholders.'.format( instance.event.type in [LEGACY_PROGRESS_EVENT_TYPE_PM, LEGACY_PROGRESS_EVENT_TYPE_MILESTONE_INTERNAL] and 'PM' or 'Developer', instance.event.task.summary, instance.event.task.is_task and 'task' or 'project' ), slack_utils.KEY_MRKDWN_IN: [slack_utils.KEY_TEXT], slack_utils.KEY_COLOR: SLACK_ATTACHMENT_COLOR_TUNGA }, create_task_stakeholders_attachment_slack(instance.event.task, show_title=False) ] slack_utils.send_incoming_webhook( SLACK_STAFF_INCOMING_WEBHOOK, { slack_utils.KEY_TEXT: slack_msg, slack_utils.KEY_ATTACHMENTS: attachments, slack_utils.KEY_CHANNEL: SLACK_STAFF_UPDATES_CHANNEL } )
def notify_new_progress_report_slack(progress_report, updated=False): progress_report = clean_instance(progress_report, ProgressReport) is_pm_report = progress_report.event.type in [PROGRESS_EVENT_PM, PROGRESS_EVENT_INTERNAL] or \ (progress_report.event.type == PROGRESS_EVENT_MILESTONE and progress_report.user.is_project_manager) is_client_report = progress_report.event.type == PROGRESS_EVENT_CLIENT or \ ( progress_report.event.type == PROGRESS_EVENT_MILESTONE and progress_report.user.is_project_owner) is_pm_or_client_report = is_pm_report or is_client_report is_dev_report = not is_pm_or_client_report # All reports go to Tunga #updates Slack slack_msg, attachments = create_progress_report_slack_message( progress_report, updated=updated) slack_utils.send_incoming_webhook( SLACK_STAFF_INCOMING_WEBHOOK, { slack_utils.KEY_TEXT: slack_msg, slack_utils.KEY_CHANNEL: SLACK_STAFF_UPDATES_CHANNEL, slack_utils.KEY_ATTACHMENTS: attachments }) if is_dev_report: # Re-create report for clients # TODO: Respect client's settings slack_msg, attachments = create_progress_report_slack_message( progress_report, updated=updated, to_client=True) slack_utils.send_project_message(progress_report.event.project, message=slack_msg, attachments=attachments)
def notify_new_message_developers(instance): instance = clean_instance(instance, Message) if instance.channel.type == CHANNEL_TYPE_DEVELOPER and (instance.user.is_staff or instance.user.is_superuser) and \ not instance.channel.messages.filter(user__is_staff=False, user__is_superuser=False).count(): recipients = get_user_model().objects.filter(type=USER_TYPE_DEVELOPER) if recipients: to = [recipients[0]] bcc = recipients[1:] if len(recipients) > 1 else None if to and isinstance(to, (list, tuple)): subject = "Developer Notification: {}".format( instance.channel.subject or instance.sender.short_name) ctx = { 'sender': instance.sender.short_name, 'subject': instance.channel.subject, 'channel': instance.channel, 'message': instance, 'message_url': '%s/conversation/%s/' % (TUNGA_URL, instance.channel_id) } send_mail(subject, 'tunga/email/new_message', to, ctx, bcc=bcc)
def notify_missed_progress_event_slack(progress_event): progress_event = clean_instance(progress_event, ProgressEvent) if progress_event.project.archived or progress_event.status != "missed" or not progress_event.last_reminder_at or progress_event.missed_notification_at: return participants = progress_event.participants if not participants: # No one to report or project is now closed return target_user = None if participants and len(participants) == 1: target_user = participants[0] project_url = '{}/projects/{}'.format(TUNGA_URL, progress_event.project.id) slack_msg = "`Alert (!):` {} {} for \"{}\" | <{}|View on Tunga>".format( target_user and '{} missed a'.format(target_user.short_name) or 'Missed', (progress_event.type == PROGRESS_EVENT_CLIENT and 'progress survey') or (progress_event.type == PROGRESS_EVENT_MILESTONE and 'milestone report') or 'progress report', progress_event.project.title, project_url ) attachments = [ { slack_utils.KEY_TITLE: progress_event.project.title, slack_utils.KEY_TITLE_LINK: project_url, slack_utils.KEY_TEXT: '*Due Date:* {}\n\n{}'.format( progress_event.due_at.strftime("%d %b, %Y"), '\n\n'.join( [ '*Name:* {}\n' '*Email:* {}{}'.format( user.display_name.encode('utf-8'), user.email, not user.is_project_owner and user.profile and user.profile.phone_number and '\n*Phone Number:* {}'.format(user.profile.phone_number) or '') for user in participants ] ) ), slack_utils.KEY_MRKDWN_IN: [slack_utils.KEY_TEXT], slack_utils.KEY_COLOR: SLACK_ATTACHMENT_COLOR_TUNGA } ] slack_utils.send_incoming_webhook( SLACK_STAFF_INCOMING_WEBHOOK, { slack_utils.KEY_TEXT: slack_msg, slack_utils.KEY_ATTACHMENTS: attachments, slack_utils.KEY_CHANNEL: SLACK_STAFF_MISSED_UPDATES_CHANNEL } ) # Save notification time progress_event.missed_notification_at = datetime.datetime.now() progress_event.save()
def notify_new_invite_request_slack(invite_request): invite_request = clean_instance(invite_request, InviteRequest) slack_msg = "<!channel> {} wants to join Tunga".format(invite_request.name) attachments = [{ slack_utils.KEY_TITLE: invite_request.name, slack_utils.KEY_TITLE_LINK: invite_request.cv_url, slack_utils.KEY_TEXT: '*Name:* {}\n*Email:* {}\n*Country*: {}\n<{}|Download CV>'.format( invite_request.name, invite_request.email, invite_request.country.name, invite_request.cv_url), slack_utils.KEY_MRKDWN_IN: [slack_utils.KEY_TEXT], slack_utils.KEY_COLOR: SLACK_ATTACHMENT_COLOR_GREEN, }, { slack_utils.KEY_TITLE: 'Motivation', slack_utils.KEY_TEXT: invite_request.motivation, slack_utils.KEY_MRKDWN_IN: [slack_utils.KEY_TEXT], slack_utils.KEY_COLOR: SLACK_ATTACHMENT_COLOR_BLUE, }] slack_utils.send_incoming_webhook( SLACK_STAFF_INCOMING_WEBHOOK, { slack_utils.KEY_TEXT: slack_msg, slack_utils.KEY_ATTACHMENTS: attachments, slack_utils.KEY_CHANNEL: SLACK_STAFF_PROFILES_CHANNEL })
def notify_new_task_application_slack(instance): instance = clean_instance(instance, Application) if not slack_utils.is_task_notification_enabled(instance.task, slugs.EVENT_APPLICATION): return application_url = '%s/work/%s/applications/' % (TUNGA_URL, instance.task_id) slack_msg = "New application from %s" % instance.user.short_name attachments = [{ slack_utils.KEY_TITLE: instance.task.summary, slack_utils.KEY_TITLE_LINK: application_url, slack_utils.KEY_TEXT: '%s%s%s%s\n\n<%s|View details on Tunga>' % (truncatewords(convert_to_text(instance.pitch), 100), instance.hours_needed and '\n*Workload:* {} hrs'.format(instance.hours_needed) or '', instance.deliver_at and '\n*Delivery Date:* {}'.format( instance.deliver_at.strftime("%d %b, %Y at %H:%M GMT")) or '', instance.remarks and '\n*Remarks:* {}'.format( truncatewords(convert_to_text(instance.remarks), 100)) or '', application_url), slack_utils.KEY_MRKDWN_IN: [slack_utils.KEY_TEXT], slack_utils.KEY_COLOR: SLACK_ATTACHMENT_COLOR_TUNGA }] slack_utils.send_integration_message(instance.task, message=slack_msg, attachments=attachments)
def update_task_submit_milestone(task): task = clean_instance(task, Task) if task.deadline: task_period = (task.deadline - task.created_at).days if task.parent: # task is part of a bigger project draft_submission_date = task.deadline else: # standalone task needs a milestone before the deadline days_before = (task.pay > 150 and task_period >= 7) and 2 or 1 draft_submission_date = task.deadline - datetime.timedelta( days=days_before) draft_defaults = { 'due_at': draft_submission_date, 'title': 'Final draft' } ProgressEvent.objects.update_or_create(task=task, type=PROGRESS_EVENT_TYPE_SUBMIT, defaults=draft_defaults) submit_defaults = {'due_at': task.deadline, 'title': 'Submit work'} ProgressEvent.objects.update_or_create( task=task, type=PROGRESS_EVENT_TYPE_COMPLETE, defaults=submit_defaults)
def notify_estimate_approved_client_email(instance, estimate_type='estimate'): instance = clean_instance(instance, estimate_type == 'quote' and Quote or Estimate) if instance.status != STATUS_APPROVED: return subject = "{} submitted {}".format( instance.user.first_name, estimate_type == 'estimate' and 'an estimate' or 'a quote' ) to = [instance.task.user.email] if instance.task.owner: to.append(instance.task.owner.email) ctx = { 'owner': instance.user, 'estimate': instance, 'task': instance.task, 'estimate_url': '{}/work/{}/{}/{}'.format(TUNGA_URL, instance.task.id, estimate_type, instance.id), 'actor': instance.user, 'target': instance.task.owner or instance.task.user, 'verb': 'submitted', 'noun': estimate_type } if instance.task.source == TASK_SOURCE_NEW_USER and not instance.task.user.is_confirmed: url_prefix = '{}/reset-password/confirm/{}/{}?new_user=true&next='.format( TUNGA_URL, instance.user.uid, instance.user.generate_reset_token() ) ctx['estimate_url'] = '{}{}'.format(url_prefix, ctx['estimate_url']) if send_mail( subject, 'tunga/email/estimate_status', to, ctx, **dict(deal_ids=[instance.task.hubspot_deal_id]) ): instance.reviewer_email_at = datetime.datetime.utcnow() instance.save()
def notify_missed_progress_event_slack(progress_event): progress_event = clean_instance(progress_event, ProgressEvent) if progress_event.project.archived or progress_event.status != "missed" or not progress_event.last_reminder_at or progress_event.missed_notification_at: return participants = progress_event.participants if not participants: # No one to report or project is now closed return target_user = None if participants and len(participants) == 1: target_user = participants[0] project_url = '{}/projects/{}'.format(TUNGA_URL, progress_event.project.id) slack_msg = "`Alert (!):` {} {} for \"{}\" | <{}|View on Tunga>".format( target_user and '{} missed a'.format(target_user.short_name) or 'Missed', (progress_event.type == PROGRESS_EVENT_CLIENT and 'progress survey') or (progress_event.type == PROGRESS_EVENT_MILESTONE and 'milestone report') or 'progress report', progress_event.project.title, project_url ) attachments = [ { slack_utils.KEY_TITLE: progress_event.project.title, slack_utils.KEY_TITLE_LINK: project_url, slack_utils.KEY_TEXT: '*Due Date:* {}\n\n{}'.format( progress_event.due_at.strftime("%d %b, %Y"), '\n\n'.join( [ '*Name:* {}\n' '*Email:* {}{}'.format( user.display_name.encode('utf-8'), user.email, not user.is_project_owner and user.profile and user.profile.phone_number and '\n*Phone Number:* {}'.format(user.profile.phone_number) or '') for user in participants ] ) ), slack_utils.KEY_MRKDWN_IN: [slack_utils.KEY_TEXT], slack_utils.KEY_COLOR: SLACK_ATTACHMENT_COLOR_TUNGA } ] slack_utils.send_incoming_webhook( SLACK_STAFF_INCOMING_WEBHOOK, { slack_utils.KEY_TEXT: slack_msg, slack_utils.KEY_ATTACHMENTS: attachments, slack_utils.KEY_CHANNEL: SLACK_STAFF_MISSED_UPDATES_CHANNEL } ) # Save notification time progress_event.missed_notification_at = datetime.datetime.now() progress_event.save()
def notify_progress_report_stuck_email_admin(instance): instance = clean_instance(instance, ProgressReport) subject = "{} has been classified as stuck ".format( instance.event.task.is_task and 'Task' or 'Project') to = TUNGA_STAFF_LOW_LEVEL_UPDATE_EMAIL_RECIPIENTS ctx = { 'owner': instance.event.task.owner or instance.event.task.user, 'reporter': instance.user, 'pm': instance.event.task.pm, 'event': instance.event, 'report': instance, 'developers': instance.event.task.active_participants, 'update_url': '{}/work/{}/event/{}/'.format(TUNGA_URL, instance.event.task.id, instance.event.id) } send_mail(subject, 'tunga/email/report_stuck_admin', to, ctx, **dict(deal_ids=[instance.event.task.hubspot_deal_id]))
def notify_progress_report_wont_meet_deadline_email_pm(instance): instance = clean_instance(instance, ProgressReport) subject = "`Alert (!):` {} doesn't expect to meet the deadline".format( instance.event.type in [LEGACY_PROGRESS_EVENT_TYPE_PM, LEGACY_PROGRESS_EVENT_TYPE_MILESTONE_INTERNAL] and 'PM' or 'Developer' ) pm = instance.event.task.pm if not pm: return to = [pm.email] ctx = { 'owner': instance.event.task.owner or instance.event.task.user, 'reporter': instance.user, 'pm': pm, 'event': instance.event, 'report': instance, 'update_url': '{}/work/{}/event/{}/'.format(TUNGA_URL, instance.event.task.id, instance.event.id) } send_mail( subject, 'tunga/email/wont_meet_deadline_pm', to, ctx, **dict(deal_ids=[instance.event.task.hubspot_deal_id]) )
def notify_progress_report_client_not_satisfied_email_pm(instance): instance = clean_instance(instance, ProgressReport) subject = "Alert (!): Client dissatisfied" pm = instance.event.task.pm if not pm: return to = [pm.email] ctx = { 'owner': instance.event.task.owner or instance.event.task.user, 'reporter': instance.user, 'pm': pm, 'event': instance.event, 'report': instance, 'update_url': '{}/work/{}/event/{}/'.format(TUNGA_URL, instance.event.task.id, instance.event.id) } send_mail(subject, 'tunga/email/client_not_satisfied_pm', to, ctx, **dict(deal_ids=[instance.event.task.hubspot_deal_id]))
def notify_progress_report_wont_meet_deadline_email_pm(instance): instance = clean_instance(instance, ProgressReport) subject = "`Alert (!):` {} doesn't expect to meet the deadline".format( instance.event.type == PROGRESS_EVENT_TYPE_PM and 'PM' or 'Developer') pm = instance.event.task.pm if not pm: return to = [pm.email] ctx = { 'owner': instance.event.task.owner or instance.event.task.user, 'reporter': instance.user, 'pm': pm, 'event': instance.event, 'report': instance, 'update_url': '{}/work/{}/event/{}/'.format(TUNGA_URL, instance.event.task.id, instance.event.id) } send_mail(subject, 'tunga/email/wont_meet_deadline_pm', to, ctx, **dict(deal_ids=[instance.event.task.hubspot_deal_id]))
def notify_progress_report_client_not_satisfied_email_admin(instance): instance = clean_instance(instance, ProgressReport) subject = "Alert (!): Client dissatisfied" to = TUNGA_STAFF_LOW_LEVEL_UPDATE_EMAIL_RECIPIENTS ctx = { 'owner': instance.event.task.owner or instance.event.task.user, 'reporter': instance.user, 'pm': instance.event.task.pm, 'event': instance.event, 'report': instance, 'developers': instance.event.task.active_participants, 'update_url': '{}/work/{}/event/{}/'.format(TUNGA_URL, instance.event.task.id, instance.event.id) } send_mail(subject, 'tunga/email/client_not_satisfied_admin', to, ctx, **dict(deal_ids=[instance.event.task.hubspot_deal_id]))
def notify_progress_report_client_not_satisfied_email_client(instance): instance = clean_instance(instance, ProgressReport) subject = "Following up on {} quality".format( instance.event.task.is_task and 'task' or 'project') to = [instance.event.task.user.email] if instance.event.task.owner: to.append(instance.event.task.owner.email) ctx = { 'owner': instance.event.task.owner or instance.event.task.user, 'reporter': instance.user, 'event': instance.event, 'report': instance, 'update_url': '{}/work/{}/event/{}/'.format(TUNGA_URL, instance.event.task.id, instance.event.id) } send_mail(subject, 'tunga/email/client_not_satisfied_client', to, ctx, **dict(deal_ids=[instance.event.task.hubspot_deal_id]))
def notify_progress_report_deadline_missed_email_client(instance): instance = clean_instance(instance, ProgressReport) subject = "Following up on missed deadline" to = [instance.event.task.user.email] if instance.event.task.owner: to.append(instance.event.task.owner.email) ctx = { 'owner': instance.event.task.owner or instance.event.task.user, 'reporter': instance.user, 'event': instance.event, 'report': instance, 'update_url': '{}/work/{}/event/{}/'.format(TUNGA_URL, instance.event.task.id, instance.event.id) } send_mail(subject, 'tunga/email/deadline_missed_client', to, ctx, **dict(deal_ids=[instance.event.task.hubspot_deal_id]))
def notify_progress_report_behind_schedule_by_algo_email_pm(instance): instance = clean_instance(instance, ProgressReport) subject = "Alert (!): it appears you're behind schedule" pm = instance.event.task.pm if not pm: return to = [pm.email] ctx = { 'owner': instance.event.task.owner or instance.event.task.user, 'reporter': instance.user, 'pm': pm, 'event': instance.event, 'report': instance, 'update_url': '{}/work/{}/event/{}/'.format(TUNGA_URL, instance.event.task.id, instance.event.id) } send_mail(subject, 'tunga/email/behind_schedule_by_algo_pm', to, ctx, **dict(deal_ids=[instance.event.task.hubspot_deal_id]))
def notify_new_task_admin_email(instance, new_user=False, completed=False, call_scheduled=False): instance = clean_instance(instance, Task) completed_phrase_subject = '' completed_phrase_body = '' if call_scheduled: completed_phrase_subject = 'availability window shared' completed_phrase_body = 'shared an availability window' elif completed: completed_phrase_subject = 'details completed' completed_phrase_body = 'completed the details' subject = "New{} {} {} by {}{}".format( (completed or call_scheduled) and ' wizard' or '', instance.scope == TASK_SCOPE_TASK and 'task' or 'project', completed_phrase_subject or 'created', instance.user.first_name, new_user and ' (New user)' or '') to = TUNGA_STAFF_LOW_LEVEL_UPDATE_EMAIL_RECIPIENTS # Notified via Slack so limit receiving admins ctx = { 'owner': instance.owner or instance.user, 'task': instance, 'task_url': '{}/task/{}/'.format(TUNGA_URL, instance.id), 'completed_phrase': completed_phrase_body, } send_mail(subject, 'tunga/email/new_task', to, ctx, **dict(deal_ids=[instance.hubspot_deal_id]))
def notify_new_task_client_drip_one(instance, template='welcome'): instance = clean_instance(instance, Task) if instance.source != TASK_SOURCE_NEW_USER: # Only target wizard users return False to = [instance.user.email] if instance.owner: to.append(instance.owner.email) task_url = '{}/task/{}/'.format(TUNGA_URL, instance.id) task_edit_url = '{}/task/{}/edit/complete-task/'.format( TUNGA_URL, instance.id) task_call_url = '{}/task/{}/edit/call/'.format(TUNGA_URL, instance.id) browse_url = '{}/people/filter/developers'.format(TUNGA_URL) if not instance.user.is_confirmed: url_prefix = '{}/reset-password/confirm/{}/{}?new_user=true&next='.format( TUNGA_URL, instance.user.uid, instance.user.generate_reset_token()) task_url = '{}{}'.format(url_prefix, task_url) task_edit_url = '{}{}'.format(url_prefix, task_edit_url) task_call_url = '{}{}'.format(url_prefix, task_call_url) browse_url = '{}{}'.format(url_prefix, browse_url) merge_vars = [ mandrill_utils.create_merge_var(MANDRILL_VAR_FIRST_NAME, instance.user.first_name), mandrill_utils.create_merge_var('task_url', task_edit_url), mandrill_utils.create_merge_var('call_url', task_call_url), mandrill_utils.create_merge_var('browse_url', browse_url) ] template_code = None if template == 'welcome': if instance.schedule_call_start: template_code = '01-b-welcome-call-scheduled' merge_vars.extend([ mandrill_utils.create_merge_var( 'date', instance.schedule_call_start.strftime("%d %b, %Y")), mandrill_utils.create_merge_var( 'time', instance.schedule_call_start.strftime("%I:%M%p")), ]) else: template_code = '01-welcome-new' elif template == 'hiring': template_code = '02-hiring' if template_code: mandrill_response = mandrill_utils.send_email(template_code, to, merge_vars=merge_vars) if mandrill_response: instance.last_drip_mail = template instance.last_drip_mail_at = datetime.datetime.utcnow() instance.save() mandrill_utils.log_emails.delay( mandrill_response, to, deal_ids=[instance.hubspot_deal_id])
def notify_progress_report_wont_meet_deadline_email_dev(instance): instance = clean_instance(instance, ProgressReport) subject = "`Alert (!):` {} doesn't expect to meet the deadline".format( instance.event.type in [ LEGACY_PROGRESS_EVENT_TYPE_PM, LEGACY_PROGRESS_EVENT_TYPE_MILESTONE_INTERNAL ] and 'PM' or 'Developer') to = [instance.user.email] ctx = { 'owner': instance.event.task.owner or instance.event.task.user, 'reporter': instance.user, 'event': instance.event, 'report': instance, 'update_url': '{}/work/{}/event/{}/'.format(TUNGA_URL, instance.event.task.id, instance.event.id) } send_mail(subject, 'tunga/email/wont_meet_deadline_dev', to, ctx, **dict(deal_ids=[instance.event.task.hubspot_deal_id]))
def notify_payment_link_client_email(instance): instance = clean_instance(instance, Task) to = [instance.user.email] if instance.owner and instance.owner.email != instance.user.email: to.append(instance.owner.email) task_url = '{}/task/{}/'.format(TUNGA_URL, instance.id) payment_link = '{}pay/'.format(task_url) owner = instance.owner or instance.user merge_vars = [ mandrill_utils.create_merge_var(MANDRILL_VAR_FIRST_NAME, owner.first_name), mandrill_utils.create_merge_var('payment_title', instance.summary), mandrill_utils.create_merge_var('payment_link', payment_link), ] mandrill_response = mandrill_utils.send_email('70-payment-link-ready', to, merge_vars=merge_vars) if mandrill_response: instance.payment_link_sent = True instance.payment_link_sent_at = datetime.datetime.utcnow() instance.save() mandrill_utils.log_emails.delay(mandrill_response, to, deal_ids=[instance.hubspot_deal_id])
def notify_new_comment_slack(instance): instance = clean_instance(instance, Comment) if ContentType.objects.get_for_model( Task) != ContentType.objects.get_for_model( instance.content_object): return task = instance.content_object if not slack_utils.is_task_notification_enabled(task, slugs.EVENT_COMMUNICATION): return task_url = '{}/work/{}/'.format(TUNGA_URL, task.id) slack_msg = '{} | <{}|View on Tunga>'.format(instance.text_body, task_url) extras = dict(author_name=instance.user.display_name) try: if instance.user.avatar_url: extras['author_icon'] = instance.user.avatar_url except: pass slack_utils.send_integration_message(instance.content_object, message=slack_msg, **extras)
def notify_progress_report_wont_meet_deadline_email_admin(instance): instance = clean_instance(instance, ProgressReport) subject = "`Alert (!):` {} doesn't expect to meet the deadline".format( instance.event.type in [ LEGACY_PROGRESS_EVENT_TYPE_PM, LEGACY_PROGRESS_EVENT_TYPE_MILESTONE_INTERNAL ] and 'PM' or 'Developer') to = TUNGA_STAFF_LOW_LEVEL_UPDATE_EMAIL_RECIPIENTS ctx = { 'owner': instance.event.task.owner or instance.event.task.user, 'reporter': instance.user, 'pm': instance.event.task.pm, 'event': instance.event, 'report': instance, 'developers': instance.event.task.active_participants, 'update_url': '{}/work/{}/event/{}/'.format(TUNGA_URL, instance.event.task.id, instance.event.id) } send_mail(subject, 'tunga/email/wont_meet_deadline_admin', to, ctx, **dict(deal_ids=[instance.event.task.hubspot_deal_id]))
def notify_progress_report_client_not_satisfied_slack_admin(instance): instance = clean_instance(instance, ProgressReport) task_url = '{}/work/{}/event/{}'.format(TUNGA_URL, instance.event.task.id, instance.event.id) slack_msg = "`Alert (!):` Client dissatisfied | <{}|View on Tunga>".format(task_url) attachments = [ { slack_utils.KEY_TITLE: instance.event.task.summary, slack_utils.KEY_TITLE_LINK: task_url, slack_utils.KEY_TEXT: 'The project owner of \"{}\" {} is unsatisfied with the deliverable.\n ' 'Please contact all stakeholders.'.format( instance.event.task.summary, instance.event.task.is_task and 'task' or 'project' ), slack_utils.KEY_MRKDWN_IN: [slack_utils.KEY_TEXT], slack_utils.KEY_COLOR: SLACK_ATTACHMENT_COLOR_TUNGA }, create_task_stakeholders_attachment_slack(instance.event.task, show_title=False) ] slack_utils.send_incoming_webhook( SLACK_STAFF_INCOMING_WEBHOOK, { slack_utils.KEY_TEXT: slack_msg, slack_utils.KEY_ATTACHMENTS: attachments, slack_utils.KEY_CHANNEL: SLACK_STAFF_UPDATES_CHANNEL } )
def send_developer_invited_email(instance, resend=False): instance = clean_instance(instance, DeveloperInvitation) subject = "You have been invited to become a Tunga {}".format( instance.get_type_display().lower()) to = [instance.email] ctx = { 'invite': instance, 'invite_url': '%s/signup/invite/%s/' % ( TUNGA_URL, instance.invitation_key, ) } if send_mail(subject, 'tunga/email/user_invitation', to, ctx): if resend: instance.used = False instance.resent = True instance.resent_at = datetime.datetime.utcnow() else: instance.invitation_sent_at = datetime.datetime.utcnow() instance.save() if not resend: notify_user_has_been_invited_to_developer_slack(instance)
def send_developer_application_received_email(instance): instance = clean_instance(instance, DeveloperApplication) subject = "%s Your application to become a Tunga developer has been received" % EMAIL_SUBJECT_PREFIX to = [instance.email] ctx = {'application': instance} send_mail(subject, 'tunga/email/email_developer_application_received', to, ctx)
def notify_paid_invoice_email_dev(invoice): invoice = clean_instance(invoice, Invoice) if invoice.legacy_id or invoice.type != INVOICE_TYPE_PURCHASE or not invoice.paid: # ignore legacy invoices and only notify about developer invoices return to = [invoice.user.email] merge_vars = [ mandrill_utils.create_merge_var(MANDRILL_VAR_FIRST_NAME, invoice.user.first_name), mandrill_utils.create_merge_var('payout_title', invoice.full_title), ] pdf_file_contents = base64.b64encode(invoice.pdf) attachments = [ dict( content=pdf_file_contents, name='Invoice - {}.pdf'.format(invoice.full_title), type='application/pdf' ) ] mandrill_utils.send_email('87-payout-made', to, merge_vars=merge_vars, attachments=attachments)
def notify_missed_progress_event_slack(instance): instance = clean_instance(instance, ProgressEvent) is_client_report = instance.type in [LEGACY_PROGRESS_EVENT_TYPE_CLIENT, LEGACY_PROGRESS_EVENT_TYPE_CLIENT_MID_SPRINT] if instance.task.archived or instance.status != "missed" or not instance.last_reminder_at: return participants = instance.participants if not participants or instance.task.closed: # No one to report or task is now closed return target_user = None if participants and len(participants) == 1: target_user = participants[0] task_url = '{}/work/{}'.format(TUNGA_URL, instance.task.id) slack_msg = "`Alert (!):` {} {} for \"{}\" | <{}|View on Tunga>".format( target_user and '{} missed a'.format(target_user.short_name) or 'Missed', is_client_report and 'weekly survey' or 'progress report', instance.task.summary, task_url ) attachments = [ { slack_utils.KEY_TITLE: instance.task.summary, slack_utils.KEY_TITLE_LINK: task_url, slack_utils.KEY_TEXT: '\n\n'.join( [ '*Due Date:* {}\n\n' '*Name:* {}\n' '*Email:* {}{}'.format( instance.due_at.strftime("%d %b, %Y"), user.display_name.encode('utf-8'), user.email, user.profile and user.profile.phone_number and '\n*Phone Number:* {}'.format( user.profile.phone_number) or '' ) for user in participants ] ), slack_utils.KEY_MRKDWN_IN: [slack_utils.KEY_TEXT], slack_utils.KEY_COLOR: SLACK_ATTACHMENT_COLOR_TUNGA } ] slack_utils.send_incoming_webhook( SLACK_STAFF_INCOMING_WEBHOOK, { slack_utils.KEY_TEXT: slack_msg, slack_utils.KEY_ATTACHMENTS: attachments, slack_utils.KEY_CHANNEL: SLACK_STAFF_MISSED_UPDATES_CHANNEL } ) # Save notification time instance.missed_notification_at = datetime.datetime.now() instance.save()
def trigger_progress_report_actionable_events(instance): # Trigger actionable event notifications instance = clean_instance(instance, ProgressReport) is_pm_report = instance.event.type in [LEGACY_PROGRESS_EVENT_TYPE_PM, LEGACY_PROGRESS_EVENT_TYPE_MILESTONE_INTERNAL] is_client_report = instance.event.type in [LEGACY_PROGRESS_EVENT_TYPE_CLIENT, LEGACY_PROGRESS_EVENT_TYPE_CLIENT_MID_SPRINT] is_pm_or_client_report = is_pm_report or is_client_report is_dev_report = not is_pm_or_client_report task = instance.event.task has_pm = instance.event.task.pm # Deadline wasn't met if instance.last_deadline_met is not None and not instance.last_deadline_met: if is_pm_report or is_dev_report: notify_progress_report_deadline_missed_admin(instance) notify_progress_report_deadline_missed_client(instance) if has_pm: notify_progress_report_deadline_missed_pm(instance) if is_dev_report: notify_progress_report_deadline_missed_dev(instance) # More than 20% difference between time passed and accomplished if task.deadline: if is_dev_report and instance.started_at and task.deadline > instance.started_at: right_now = datetime.datetime.utcnow() spent_percentage = ((right_now - instance.started_at)/(task.deadline - right_now))*100 if ((instance.percentage or 0) + 20) < spent_percentage: notify_progress_report_behind_schedule_by_algo_admin(instance) if has_pm: notify_progress_report_behind_schedule_by_algo_pm(instance) notify_progress_report_behind_schedule_by_algo_dev(instance) # Client not satisfied with deliverable if instance.deliverable_satisfaction is not None and not instance.deliverable_satisfaction: if is_client_report: notify_progress_report_client_not_satisfied_admin(instance) notify_progress_report_client_not_satisfied_client(instance) if has_pm: notify_progress_report_client_not_satisfied_pm(instance) notify_progress_report_client_not_satisfied_dev(instance) # Stuck and/ or not progressing if instance.status in [LEGACY_PROGRESS_REPORT_STATUS_STUCK, LEGACY_PROGRESS_REPORT_STATUS_BEHIND_AND_STUCK]: if is_pm_report or is_dev_report: notify_progress_report_stuck_admin(instance) if has_pm: notify_progress_report_stuck_pm(instance) if is_dev_report: notify_progress_report_stuck_dev(instance)
def distribute_multi_task_payment(multi_task_key): multi_task_key = clean_instance(multi_task_key, MultiTaskPaymentKey) if not multi_task_key.paid: return # Distribute connected tasks for task in multi_task_key.tasks.all(): distribute_task_payment_payoneer(task)
def sync_exact_invoices(task, invoice_types=('client', 'tunga', 'developer'), developers=None): task = clean_instance(task, Task) invoice = task.invoice client = task.owner or task.user admin_emails = ['*****@*****.**', '*****@*****.**', '*****@*****.**'] if 'client' in invoice_types and task.paid and client.type == USER_TYPE_PROJECT_OWNER \ and client.email not in admin_emails: # Only sync paid invoices whose project owner is not a Tunga admin invoice_file_client = HTML( string=process_invoices(task.id, invoice_types=['client'], user_id=client.id, is_admin=False), encoding='utf-8' ).write_pdf() exact_utils.upload_invoice( task, client, 'client', invoice_file_client, float(invoice.amount.get('total_invoice_client', 0)), vat_location=invoice.vat_location_client ) participation_shares = task.get_participation_shares() for share_info in participation_shares: participant = share_info['participant'] dev = participant.user if participant.status != STATUS_ACCEPTED or share_info['share'] <= 0: continue if developers and dev.id not in developers: continue if ParticipantPayment.objects.filter(participant=participant): amount_details = invoice.get_amount_details(share=share_info['share']) if 'tunga' in invoice_types: invoice_file_tunga = HTML( string=process_invoices( task.id, invoice_types=['tunga'], user_id=dev.id, developer_ids=[dev.id], is_admin=False ), encoding='utf-8' ).write_pdf() exact_utils.upload_invoice( task, dev, 'tunga', invoice_file_tunga, float(amount_details.get('total_invoice_tunga', 0)) ) if 'developer' in invoice_types and invoice.version == 1: # Developer (tunga invoicing dev) invoices are only part of the old invoice scheme invoice_file_dev = HTML( string=process_invoices( task.id, invoice_types=['developer'], user_id=dev.id, developer_ids=[dev.id], is_admin=False ), encoding='utf-8' ).write_pdf() exact_utils.upload_invoice( task, dev, 'developer', invoice_file_dev, float(amount_details.get('total_invoice_developer', 0)) )
def notify_estimate_status_email(instance, estimate_type='estimate', target_admins=False): instance = clean_instance(instance, estimate_type == 'quote' and Quote or Estimate) if instance.status == STATUS_INITIAL: return actor = None target = None action_verb = VERB_MAP_STATUS_CHANGE.get(instance.status, None) recipients = None if instance.status in [STATUS_SUBMITTED]: actor = instance.user recipients = TUNGA_STAFF_UPDATE_EMAIL_RECIPIENTS elif instance.status in [STATUS_APPROVED, STATUS_DECLINED]: actor = instance.moderated_by target = instance.user recipients = [instance.user.email] elif instance.status in [STATUS_ACCEPTED, STATUS_REJECTED]: actor = instance.reviewed_by if target_admins: recipients = TUNGA_STAFF_UPDATE_EMAIL_RECIPIENTS else: target = instance.user recipients = [instance.user.email] # Notify staff in a separate email notify_estimate_status_email.delay(instance.id, estimate_type=estimate_type, target_admins=True) subject = "{} {} {}".format( actor.first_name, action_verb, estimate_type == 'estimate' and 'an estimate' or 'a quote' ) to = recipients ctx = { 'owner': instance.user, 'estimate': instance, 'task': instance.task, 'estimate_url': '{}/work/{}/{}/{}'.format(TUNGA_URL, instance.task.id, estimate_type, instance.id), 'actor': actor, 'target': target, 'verb': action_verb, 'noun': estimate_type } if send_mail( subject, 'tunga/email/estimate_status', to, ctx, **dict(deal_ids=[instance.task.hubspot_deal_id]) ): if instance.status == STATUS_SUBMITTED: instance.moderator_email_at = datetime.datetime.utcnow() instance.save() if instance.status in [STATUS_ACCEPTED, STATUS_REJECTED]: instance.reviewed_email_at = datetime.datetime.utcnow() instance.save() if instance.status == STATUS_APPROVED: notify_estimate_approved_client_email(instance, estimate_type=estimate_type)
def notify_invoice_email(invoice, updated=False): invoice = clean_instance(invoice, Invoice) if invoice.type == INVOICE_TYPE_SALE: if updated: notify_updated_invoice_email_client(invoice) else: notify_new_invoice_email_client(invoice) elif invoice.type == INVOICE_TYPE_PURCHASE and not updated: notify_new_invoice_email_dev(invoice)
def notify_new_project_slack_admin(project): project = clean_instance(project, Project) summary, attachments = create_project_slack_message(project) slack_utils.send_incoming_webhook(SLACK_STAFF_INCOMING_WEBHOOK, { slack_utils.KEY_TEXT: summary, slack_utils.KEY_CHANNEL: SLACK_STAFF_LEADS_CHANNEL, slack_utils.KEY_ATTACHMENTS: attachments })
def send_new_user_joined_email(instance): instance = clean_instance(instance, get_user_model()) subject = "{} joined Tunga".format(instance.display_name) to = TUNGA_STAFF_UPDATE_EMAIL_RECIPIENTS ctx = { 'user': instance, 'user_url': '%s/people/%s/' % (TUNGA_URL, instance.username) } send_mail(subject, 'tunga/email/new_user', to, ctx)
def notify_new_task_community_slack(instance): instance = clean_instance(instance, Task) # Notify Devs or PMs via Slack if (not instance.is_developer_ready) or (instance.approved and instance.visibility == VISIBILITY_DEVELOPER): slack_msg = create_task_slack_msg( instance, channel=instance.is_developer_ready and SLACK_DEVELOPER_UPDATES_CHANNEL or SLACK_PMS_UPDATES_CHANNEL ) slack_utils.send_incoming_webhook(SLACK_DEVELOPER_INCOMING_WEBHOOK, slack_msg)
def notify_new_task_invoice_admin_slack(instance): instance = clean_instance(instance, TaskInvoice) task_url = '{}/work/{}/'.format(TUNGA_URL, instance.task.id) owner = instance.task.owner or instance.task.user client_url = '{}/people/{}/'.format(TUNGA_URL, owner.username) invoice_url = '{}/api/task/{}/download/invoice/?format=pdf'.format(TUNGA_URL, instance.task.id) slack_msg = '{} generated an invoice'.format( instance.user.display_name.encode('utf-8') ) attachments = [ { slack_utils.KEY_TITLE: instance.task.summary, slack_utils.KEY_TITLE_LINK: task_url, slack_utils.KEY_TEXT: 'Client: <{}|{}>\nFee: {}\nPayment Method: {}\n<{}|Download invoice>'.format( client_url, owner.display_name.encode('utf-8'), instance.display_fee().encode('utf-8'), instance.get_payment_method_display(), invoice_url ), slack_utils.KEY_MRKDWN_IN: [slack_utils.KEY_TEXT], slack_utils.KEY_COLOR: SLACK_ATTACHMENT_COLOR_BLUE }, ] if not instance.task.payment_approved: task_approval_url = '{}edit/payment-approval/'.format(task_url) if instance.payment_method == PAYMENT_METHOD_BANK: attachments.append({ slack_utils.KEY_TITLE: 'Review and approve payment.', slack_utils.KEY_TITLE_LINK: task_approval_url, slack_utils.KEY_TEXT: "Payment will be completed via bank transfer.\n " "However, developer payments won't be distributed until the payment" " is reviewed and approved.", slack_utils.KEY_MRKDWN_IN: [slack_utils.KEY_TEXT], slack_utils.KEY_COLOR: SLACK_ATTACHMENT_COLOR_GREEN }) else: attachments.append({ slack_utils.KEY_TITLE: 'Review and approve payment.', slack_utils.KEY_TITLE_LINK: task_approval_url, slack_utils.KEY_TEXT: "The client won't be able to pay until the payment is approved.", slack_utils.KEY_MRKDWN_IN: [slack_utils.KEY_TEXT], slack_utils.KEY_COLOR: SLACK_ATTACHMENT_COLOR_RED }) slack_utils.send_incoming_webhook( SLACK_STAFF_INCOMING_WEBHOOK, { slack_utils.KEY_TEXT: slack_msg, slack_utils.KEY_ATTACHMENTS: attachments, slack_utils.KEY_CHANNEL: SLACK_STAFF_PAYMENTS_CHANNEL } )
def notify_new_task_client_drip_one(instance, template='welcome'): instance = clean_instance(instance, Task) if instance.source != TASK_SOURCE_NEW_USER: # Only target wizard users return False to = [instance.user.email] if instance.owner: to.append(instance.owner.email) task_url = '{}/task/{}/'.format(TUNGA_URL, instance.id) task_edit_url = '{}/task/{}/edit/complete-task/'.format(TUNGA_URL, instance.id) task_call_url = '{}/task/{}/edit/call/'.format(TUNGA_URL, instance.id) browse_url = '{}/people/filter/developers'.format(TUNGA_URL) if not instance.user.is_confirmed: url_prefix = '{}/reset-password/confirm/{}/{}?new_user=true&next='.format( TUNGA_URL, instance.user.uid, instance.user.generate_reset_token() ) task_url = '{}{}'.format(url_prefix, task_url) task_edit_url = '{}{}'.format(url_prefix, task_edit_url) task_call_url = '{}{}'.format(url_prefix, task_call_url) browse_url = '{}{}'.format(url_prefix, browse_url) merge_vars = [ mandrill_utils.create_merge_var(MANDRILL_VAR_FIRST_NAME, instance.user.first_name), mandrill_utils.create_merge_var('task_url', task_edit_url), mandrill_utils.create_merge_var('call_url', task_call_url), mandrill_utils.create_merge_var('browse_url', browse_url) ] template_code = None if template == 'welcome': if instance.schedule_call_start: template_code = '01-b-welcome-call-scheduled' merge_vars.extend( [ mandrill_utils.create_merge_var('date', instance.schedule_call_start.strftime("%d %b, %Y")), mandrill_utils.create_merge_var('time', instance.schedule_call_start.strftime("%I:%M%p")), ] ) else: template_code = '01-welcome-new' elif template == 'hiring': template_code = '02-hiring' if template_code: mandrill_response = mandrill_utils.send_email(template_code, to, merge_vars=merge_vars) if mandrill_response: instance.last_drip_mail = template instance.last_drip_mail_at = datetime.datetime.utcnow() instance.save() mandrill_utils.log_emails.delay(mandrill_response, to, deal_ids=[instance.hubspot_deal_id])
def update_task_pm_updates(task): task = clean_instance(task, Task) target_task = task if task.parent: # for sub-tasks, create all pm updates on the project target_task = task.parent if target_task.archived or target_task.closed or target_task.is_task or not target_task.pm: # only request pm updates for project which are approved and not closed return if target_task.update_interval and target_task.update_interval_units: periodic_start_date = ProgressEvent.objects.filter( Q(task=target_task) | Q(task__parent=target_task), type=LEGACY_PROGRESS_EVENT_TYPE_PM ).aggregate(latest_date=Max('due_at'))['latest_date'] now = datetime.datetime.utcnow() if periodic_start_date and periodic_start_date > now: return if not periodic_start_date: periodic_start_date = datetime.datetime.utcnow() if periodic_start_date: last_update_at = clean_update_datetime(periodic_start_date, target_task) while True: last_update_day = last_update_at.weekday() next_update_at = last_update_at if last_update_day < 3: # Last was before Thursday so schedule for Thursday next_update_at += relativedelta(days=3 - last_update_day) else: # Last was on after Thursday so schedule for Monday next_update_at += relativedelta(days=7 - last_update_day) if next_update_at >= now: future_by_18_hours = now + relativedelta(hours=18) if next_update_at <= future_by_18_hours and ( not target_task.deadline or next_update_at < target_task.deadline): num_updates_within_on_same_day = ProgressEvent.objects.filter( task=target_task, type=LEGACY_PROGRESS_EVENT_TYPE_PM, due_at__contains=next_update_at.date() ).count() if num_updates_within_on_same_day == 0: # Schedule at most one pm update for any day ProgressEvent.objects.update_or_create( task=target_task, type=LEGACY_PROGRESS_EVENT_TYPE_PM, due_at=next_update_at, defaults={'title': 'PM Report'} ) break else: last_update_at = next_update_at
def send_new_user_password_email(instance): instance = clean_instance(instance, get_user_model()) subject = "You have been invited to become a Tunga {}".format( instance.get_type_display().lower() ) to = [instance.email] ctx = { 'invite': instance, 'invite_url': '{}/password/{}/{}'.format(TUNGA_URL, instance.uid, instance.generate_reset_token()) } send_mail(subject, 'tunga/email/user_invitation_password', to, ctx)
def update_multi_tasks(multi_task_key, distribute=False): multi_task_key = clean_instance(multi_task_key, MultiTaskPaymentKey) if multi_task_key.distribute_only: connected_tasks = multi_task_key.distribute_tasks connected_tasks.filter(paid=True).update( btc_price=multi_task_key.btc_price, withhold_tunga_fee_distribute=multi_task_key.withhold_tunga_fee, btc_paid=multi_task_key.paid, btc_paid_at=multi_task_key.paid_at ) else: connected_tasks = multi_task_key.tasks connected_tasks.filter(paid=False).update( payment_method=multi_task_key.payment_method, btc_price=multi_task_key.btc_price, withhold_tunga_fee=multi_task_key.withhold_tunga_fee, paid=multi_task_key.paid, paid_at=multi_task_key.paid_at, processing=multi_task_key.processing, processing_at=multi_task_key.processing_at ) # Generate invoices for all connected tasks for task in connected_tasks.all(): if multi_task_key.distribute_only: if task.paid and multi_task_key.paid: # Coinbase waits for 6 confirmations, so not safe to distribute yet # distribute_task_payment.delay(task.id) pass return # Save Invoice if not task.btc_address or not bitcoin_utils.is_valid_btc_address(task.btc_address): address = coinbase_utils.get_new_address(coinbase_utils.get_api_client()) task.btc_address = address task.save() TaskInvoice.objects.create( task=task, user=multi_task_key.user, title=task.title, fee=task.pay, client=task.owner or task.user, # developer=developer, payment_method=multi_task_key.payment_method, btc_price=multi_task_key.btc_price, btc_address=task.btc_address, withhold_tunga_fee=multi_task_key.withhold_tunga_fee ) if distribute and multi_task_key.paid: distribute_task_payment_payoneer.delay(task.id)
def make_payout(invoice): invoice = clean_instance(invoice, Invoice) if invoice.legacy_id or invoice.type != INVOICE_TYPE_PURCHASE or invoice.status != STATUS_APPROVED or invoice.paid: # Only payout non-legacy non-paid approved purchase invoices return payoneer_client = payoneer_utils.get_client( PAYONEER_USERNAME, PAYONEER_PASSWORD, PAYONEER_PARTNER_ID ) balance = payoneer_client.get_balance() if Decimal(20) <= invoice.amount <= balance.get('accountbalance', 0): # Payments must be more than EUR 20 and less than the balance payment, created = Payment.objects.get_or_create( invoice=invoice, defaults=dict( amount=invoice.amount, payment_method=PAYMENT_METHOD_PAYONEER ) ) if created or (payment and payment.status == STATUS_RETRY): if payment.status == STATUS_RETRY: payment.status = STATUS_INITIATED payment.save() transaction = payoneer_client.make_payment( PAYONEER_PARTNER_ID, 'invoice{}'.format(invoice.id), invoice.user.id, invoice.amount, invoice.full_title ) if transaction.get('status', None) == '000': paid_at = datetime.datetime.utcnow() # Update invoice invoice.paid = True invoice.paid_at = paid_at invoice.save() payment.invoice = invoice payment.amount = invoice.amount payment.payment_method = PAYMENT_METHOD_PAYONEER payment.currency = invoice.currency or CURRENCY_EUR payment.ref = transaction.get('paymentid', None) payment.paid_at = paid_at payment.created_by = invoice.created_by payment.save() notify_paid_invoice.delay(invoice) else: payment.status = STATUS_FAILED payment.save()