From b604060bc721ecbc296ed16f26fc8bc2458c3200 Mon Sep 17 00:00:00 2001 From: Matt Lott <mattlott@live.com> Date: Tue, 10 May 2016 14:40:41 -0700 Subject: [PATCH] Add call tasks to inbound leads after 3 days --- .../mongodb/queries/maybeStudentTeacher.js | 2 +- scripts/followupCloseIoLeads.js | 241 +++++++++++++++++- 2 files changed, 240 insertions(+), 3 deletions(-) diff --git a/scripts/analytics/mongodb/queries/maybeStudentTeacher.js b/scripts/analytics/mongodb/queries/maybeStudentTeacher.js index 444598527..debde0cf2 100644 --- a/scripts/analytics/mongodb/queries/maybeStudentTeacher.js +++ b/scripts/analytics/mongodb/queries/maybeStudentTeacher.js @@ -9,7 +9,7 @@ // Classroom owner, teacher role (GOOD) // Classroom member, no role // Classroom member, teacher role -// Classroom member, studnet role (GOOD) +// Classroom member, student role (GOOD) 'use strict'; const scriptStartTime = new Date(); diff --git a/scripts/followupCloseIoLeads.js b/scripts/followupCloseIoLeads.js index cdad03c1a..b6abbebe8 100644 --- a/scripts/followupCloseIoLeads.js +++ b/scripts/followupCloseIoLeads.js @@ -11,6 +11,7 @@ if (process.argv.length !== 6) { // TODO: 2nd follow up email activity does not handle paged activity results // TODO: sendMail copied from updateCloseIoLeads.js // TODO: template values copied from updateCloseIoLeads.js +// TODO: status change is not related to specific lead contacts const createTeacherEmailTemplatesAuto1 = ['tmpl_i5bQ2dOlMdZTvZil21bhTx44JYoojPbFkciJ0F560mn', 'tmpl_CEZ9PuE1y4PRvlYiKB5kRbZAQcTIucxDvSeqvtQW57G']; const demoRequestEmailTemplatesAuto1 = ['tmpl_s7BZiydyCHOMMeXAcqRZzqn0fOtk0yOFlXSZ412MSGm', 'tmpl_cGb6m4ssDvqjvYd8UaG6cacvtSXkZY3vj9b9lSmdQrf']; @@ -31,7 +32,9 @@ earliestDate.setUTCDate(earliestDate.getUTCDate() - 10); // ** Main program async.series([ - sendSecondFollowupMails + sendSecondFollowupMails, + addCallTasks +// TODO: Cancel call tasks ], (err, results) => { if (err) console.error(err); @@ -69,6 +72,14 @@ function isCreateTeacherTemplateAuto1(template) { return createTeacherEmailTemplatesAuto1.indexOf(template) >= 0; } +function isDemoRequestTemplateAuto2(template) { + return demoRequestEmailTemplatesAuto2.indexOf(template) >= 0; +} + +function isCreateTeacherTemplateAuto2(template) { + return createTeacherEmailTemplatesAuto2.indexOf(template) >= 0; +} + function log(str) { console.log(new Date().toISOString() + " " + str); } @@ -325,7 +336,9 @@ function sendSecondFollowupMails(done) { if (error) return done(error); try { const results = JSON.parse(body); - console.log(`sendSecondFollowupMails total num leads ${results.total_results} has_more=${results.has_more}`); + if (skip === 0) { + console.log(`sendSecondFollowupMails total num leads ${results.total_results} has_more=${results.has_more}`); + } has_more = results.has_more; const tasks = []; for (const lead of results.data) { @@ -357,3 +370,227 @@ function sendSecondFollowupMails(done) { nextPage(0); }); } + +function createAddCallTaskFn(userApiKeyMap, latestDate, lead, email) { + // Check for activity since second auto mail and status update + // Add call task + // TODO: Very similar function to createSendFollowupMailFn + const auto1Statuses = ["Auto Attempt 1", "New US Schools Auto Attempt 1"]; + const auto2Statuses = ["Auto Attempt 2", "New US Schools Auto Attempt 2"]; + return (done) => { + // console.log("DEBUG: addCallTask", lead.id); + + // Skip leads with tasks + const url = `https://${closeIoApiKey}:X@app.close.io/api/v1/task/?lead_id=${lead.id}`; + request.get(url, (error, response, body) => { + if (error) { + console.log(error); + return done(); + } + try { + const results = JSON.parse(body); + if (results.total_results > 0) { + // console.log(`DEBUG: ${lead.id} has ${results.total_results} tasks`); + return done(); + } + } + catch (err) { + return done(err); + } + + // Find all lead activities + const url = `https://${closeIoApiKey}:X@app.close.io/api/v1/activity/?lead_id=${lead.id}`; + request.get(url, (error, response, body) => { + if (error) { + console.log(error); + return done(); + } + try { + const results = JSON.parse(body); + if (results.has_more) { + console.log(`ERROR: ${lead.id} has more activities than returned!`); + return done(); + } + + // Find second auto mail and status change + let sentSecondCreateTeacherEmail = false; + let sentSecondDemoRequestEmail = false; + let secondMailActivity; + let statusUpdateActivity; + let contactReplyMail; + for (const activity of results.data) { + if (activity._type === 'Email' && activity.to[0] === email) { + if (isCreateTeacherTemplateAuto2(activity.template_id)) { + if (sentSecondCreateTeacherEmail || sentSecondDemoRequestEmail) { + console.log(`ERROR: ${lead.id} ${email} sent multiple auto2 emails!? ${sentSecondCreateTeacherEmail} ${sentSecondDemoRequestEmail}`); + return done(); + } + sentSecondCreateTeacherEmail = true; + secondMailActivity = activity; + } + else if (isDemoRequestTemplateAuto2(activity.template_id)) { + if (sentSecondCreateTeacherEmail || sentSecondDemoRequestEmail) { + console.log(`ERROR: ${lead.id} ${email} sent multiple auto2 emails!? ${sentSecondCreateTeacherEmail} ${sentSecondDemoRequestEmail}`); + return done(); + } + sentSecondDemoRequestEmail = true; + secondMailActivity = activity; + } + } + else if (activity._type === 'LeadStatusChange' && auto1Statuses.indexOf(activity.old_status_label) >= 0 + && auto2Statuses.indexOf(activity.new_status_label) >= 0) { + statusUpdateActivity = activity; + } + } + + if (!secondMailActivity) { + // console.log(`DEBUG: No auto2 mail sent for ${lead.id} ${email}`); + return done(); + } + if (!statusUpdateActivity) { + console.log(`ERROR: No status update for ${lead.id} ${email}`); + return done(); + } + if (new Date(secondMailActivity.date_created) > latestDate) { + // console.log(`DEBUG: Second auto mail too recent ${secondMailActivity.date_created} ${lead.id}`); + return done(); + } + + if (sentSecondCreateTeacherEmail && sentSecondDemoRequestEmail) { + console.log(`ERROR: ${lead.id} ${email} sent multiple auto2 emails!? ${sentSecondCreateTeacherEmail} ${sentSecondDemoRequestEmail}`); + return done(); + } + // console.log(secondMailActivity); + + // Find activity since second auto mail and status update + // Skip email to a different contact's email + // Skip note about different contact + let recentActivity; + for (const activity of results.data) { + if (activity.id === secondMailActivity.id) continue; + if (activity.id === statusUpdateActivity.id) continue; + if (new Date(secondMailActivity.date_created) > new Date(activity.date_created)) continue; + if (new Date(statusUpdateActivity.date_created) > new Date(activity.date_created)) continue; + if (activity._type === 'Note' && activity.note + && activity.note.indexOf('demo_email') >= 0 && activity.note.indexOf(email) < 0) { + // console.log(`DEBUG: Skipping ${lead.id} ${email} auto import note for different contact`); + // console.log(activity.note); + continue; + } + recentActivity = activity; + break; + } + + // Create call task + if (!recentActivity) { + console.log(`DEBUG: adding call task for ${lead.id} ${email}`); + const postData = { + _type: "lead", + lead_id: lead.id, + assigned_to: secondMailActivity.user_id, + text: `Call ${email}`, + is_complete: false + }; + const options = { + uri: `https://${closeIoApiKey}:X@app.close.io/api/v1/task/`, + body: JSON.stringify(postData) + }; + request.post(options, (error, response, body) => { + if (error) return done(error); + const result = JSON.parse(body); + if (result.errors || result['field-errors']) { + const errorMessage = `Create call task POST error for ${email} ${lead.id}`; + console.error(errorMessage); + // console.error(body); + // console.error(postData); + return done(errorMessage); + } + return done(); + }); + } + else { + // console.log(`DEBUG: Found recent activity after auto2 mail for ${lead.id} ${email}`); + // console.log(recentActivity); + return done(); + } + } + catch (err) { + console.log(err); + console.log(body); + return done(); + } + }); + }); + }; +} + +function addCallTasks(done) { + // Find all leads with auto 2 status, created since earliestDate + // TODO: Very similar function to sendSecondFollowupMails + // console.log("DEBUG: addCallTasks"); + const userApiKeyMap = {}; + let createGetUserFn = (apiKey) => { + return (done) => { + const url = `https://${apiKey}:X@app.close.io/api/v1/me/`; + request.get(url, (error, response, body) => { + if (error) return done(); + const results = JSON.parse(body); + userApiKeyMap[results.id] = apiKey; + return done(); + }); + }; + } + const tasks = []; + for (const apiKey of closeIoMailApiKeys) { + tasks.push(createGetUserFn(apiKey)); + } + async.parallel(tasks, (err, results) => { + if (err) console.log(err); + const latestDate = new Date(); + latestDate.setUTCDate(latestDate.getUTCDate() - 3); + const query = `date_created > ${earliestDate.toISOString().substring(0, 19)} (lead_status:"Auto Attempt 2" or lead_status:"New US Schools Auto Attempt 2")"`; + const limit = 100; + const nextPage = (skip) => { + let has_more = false; + const url = `https://${closeIoApiKey}:X@app.close.io/api/v1/lead/?_skip=${skip}&_limit=${limit}&query=${encodeURIComponent(query)}/`; + request.get(url, (error, response, body) => { + if (error) return done(error); + try { + const results = JSON.parse(body); + if (skip === 0) { + console.log(`addCallTasks total num leads ${results.total_results} has_more=${results.has_more}`); + } + has_more = results.has_more; + const tasks = []; + for (const lead of results.data) { + // console.log(`${lead.id}\t${lead.status_label}\t${lead.name}`); + // if (lead.id !== 'lead_foo') continue; + const existingContacts = lead.contacts || []; + for (const contact of existingContacts) { + if (contact.emails && contact.emails.length > 0) { + if (contact.phones && contact.phones.length > 0) { + tasks.push(createAddCallTaskFn(userApiKeyMap, latestDate, lead, contact.emails[0].email.toLowerCase())); + } + } + else { + console.log(`ERROR: lead ${lead.id} contact has non-1 emails`); + } + } + // if (tasks.length > 10) break; + } + async.series(tasks, (err, results) => { + if (err) return done(err); + if (has_more) { + return nextPage(skip + limit); + } + return done(err); + }); + } + catch (err) { + return done(err); + } + }); + }; + nextPage(0); + }); +}