diff --git a/politeiawww/cmsnotifications.go b/politeiawww/cmsnotifications.go index 4b187641e..3d5d43d21 100644 --- a/politeiawww/cmsnotifications.go +++ b/politeiawww/cmsnotifications.go @@ -11,74 +11,120 @@ import ( "github.com/decred/politeia/politeiawww/user" ) -// Seconds Minutes Hours Days Months DayOfWeek -const emailSchedule = "0 0 12 5 * *" // Check at 12:00 PM on 5th day every month +const ( + // Seconds Minutes Hours Days Months DayOfWeek + firstEmailSchedule = "0 0 12 1 * *" // Check at 12:00 PM on 1st day every month + secondEmailSchedule = "0 0 12 4 * *" // Check at 12:00 PM on 4th day every month + thirdEmailSchedule = "0 0 12 7 * *" // Check at 12:00 PM on 7th day every month + + firstEmailCheck = 1 + secondEmailCheck = 2 + thirdEmailCheck = 3 +) func (p *politeiawww) checkInvoiceNotifications() { log.Infof("Starting cron for invoice email checking") // Launch invoice notification cron job - err := p.cron.AddFunc(emailSchedule, func() { - log.Infof("Running invoice email notification cron") - currentMonth := time.Now().Month() - currentYear := time.Now().Year() - // Check all CMS users - err := p.db.AllUsers(func(user *user.User) { - log.Tracef("Checking user: %v", user.Username) - if user.Admin { - return - } + err := p.cron.AddFunc(firstEmailSchedule, func() { + log.Infof("Running first invoice email notification cron") + p.invoiceNotification(firstEmailCheck) + }) + if err != nil { + log.Errorf("Error running first invoice notification cron: %v", err) + } + err = p.cron.AddFunc(secondEmailSchedule, func() { + log.Infof("Running second invoice email notification cron") + p.invoiceNotification(secondEmailCheck) + }) + if err != nil { + log.Errorf("Error running second invoice notification cron: %v", err) + } + err = p.cron.AddFunc(thirdEmailSchedule, func() { + log.Infof("Running third invoice email notification cron") + p.invoiceNotification(thirdEmailCheck) + }) + if err != nil { + log.Errorf("Error running third invoice notification cron: %v", err) + } +} - cmsUser, err := p.getCMSUserByID(user.ID.String()) - if err != nil { - log.Errorf("Error retrieving user invoices email: %v %v", err, - user.Email) - return - } +func (p *politeiawww) invoiceNotification(emailCheckVersion int) { + currentMonth := time.Now().Month() + currentYear := time.Now().Year() + // Check all CMS users + err := p.db.AllUsers(func(user *user.User) { + log.Tracef("Checking user: %v", user.Username) + if user.Admin { + return + } - // Skip if user isn't a direct or supervisor contractor. - if cmsUser.ContractorType != cms.ContractorTypeDirect && - cmsUser.ContractorType != cms.ContractorTypeSupervisor { - return - } + cmsUser, err := p.getCMSUserByID(user.ID.String()) + if err != nil { + log.Errorf("Error retrieving user invoices email: %v %v", err, + user.Email) + return + } - // If HashedPassword not set to anything that means the user has - // not completed registration. - if len(user.HashedPassword) == 0 { - return - } - invoiceFound := false - userInvoices, err := p.cmsDB.InvoicesByUserID(user.ID.String()) - if err != nil { - log.Errorf("Error retrieving user invoices email: %v %v", err, - user.Email) - return + // Skip if user isn't a direct or supervisor contractor. + if cmsUser.ContractorType != cms.ContractorTypeDirect && + cmsUser.ContractorType != cms.ContractorTypeSupervisor { + return + } + + // If HashedPassword not set to anything that means the user has + // not completed registration. + if len(user.HashedPassword) == 0 { + return + } + invoiceFound := false + userInvoices, err := p.cmsDB.InvoicesByUserID(user.ID.String()) + if err != nil { + log.Errorf("Error retrieving user invoices email: %v %v", err, + user.Email) + return + } + for _, inv := range userInvoices { + // Check to see if invoices match last month + current year OR + // if it's currently January and the user has not submitted an + // invoice for December of the previous year. + if (inv.Month == uint(currentMonth-1) && + inv.Year == uint(currentYear)) || + (currentMonth == 1 && inv.Month == 12 && + inv.Year == uint(currentYear-1)) { + invoiceFound = true + break } - for _, inv := range userInvoices { - // Check to see if invoices match last month + current year OR - // if it's currently January and the user has not submitted an - // invoice for December of the previous year. - if (inv.Month == uint(currentMonth-1) && - inv.Year == uint(currentYear)) || - (currentMonth == 1 && inv.Month == 12 && - inv.Year == uint(currentYear-1)) { - invoiceFound = true - break + } + log.Tracef("Checked user: %v sending email? %v", user.Username, + !invoiceFound) + if !invoiceFound { + switch emailCheckVersion { + case firstEmailCheck: + err = p.emailInvoiceNotifications(user.Email, user.Username, + "Monthly Invoice Reminder", + invoiceFirstNotificationTmpl) + if err != nil { + log.Errorf("Error sending first email: %v %v", err, user.Email) } - } - log.Tracef("Checked user: %v sending email? %v", user.Username, - !invoiceFound) - if !invoiceFound { - err = p.emailInvoiceNotSent(user.Email, user.Username) + case secondEmailCheck: + err = p.emailInvoiceNotifications(user.Email, user.Username, + "Awaiting Monthly Invoice", + invoiceSecondNotificationTmpl) if err != nil { - log.Errorf("Error sending email: %v %v", err, user.Email) + log.Errorf("Error sending second email: %v %v", err, user.Email) + } + + case thirdEmailCheck: + err = p.emailInvoiceNotifications(user.Email, user.Username, + "Final Invoice Notice", + invoiceFinalNotificationTmpl) + if err != nil { + log.Errorf("Error sending second email: %v %v", err, user.Email) } } - }) - if err != nil { - log.Errorf("Error querying for AllUsers: %v", err) } }) if err != nil { - log.Errorf("Error running invoice notification cron: %v", err) + log.Errorf("Error querying for AllUsers: %v", err) } } diff --git a/politeiawww/email.go b/politeiawww/email.go index fe8b92625..b928bdabb 100644 --- a/politeiawww/email.go +++ b/politeiawww/email.go @@ -270,19 +270,17 @@ func (p *politeiawww) emailInvoiceStatusUpdate(invoiceToken, userEmail string) e return p.mail.SendTo(subject, body, recipients) } -// emailInvoiceNotSent sends a invoice not sent email notification to the -// provided email address. -func (p *politeiawww) emailInvoiceNotSent(email, username string) error { +// emailInvoiceNotifications emails users that have not yet submitted an +// invoice for the given month/year +func (p *politeiawww) emailInvoiceNotifications(email, username, subject string, tmpl *template.Template) error { // Set the date to the first day of the previous month. newDate := time.Date(time.Now().Year(), time.Now().Month()-1, 1, 0, 0, 0, 0, time.UTC) - tplData := invoiceNotSent{ + tplData := invoiceNotification{ Username: username, Month: newDate.Month().String(), Year: newDate.Year(), } - - subject := "Awaiting Monthly Invoice" - body, err := createBody(invoiceNotSentTmpl, tplData) + body, err := createBody(tmpl, &tplData) if err != nil { return err } diff --git a/politeiawww/templates.go b/politeiawww/templates.go index 2c760c25c..d3d14ddcc 100644 --- a/politeiawww/templates.go +++ b/politeiawww/templates.go @@ -4,7 +4,9 @@ package main -import "text/template" +import ( + "text/template" +) // User email verify - Send verification link to new user type userEmailVerify struct { @@ -151,6 +153,45 @@ If you do not recognize this, please ignore this email. var userDCCApprovedTmpl = template.Must( template.New("userDCCApproved").Parse(userDCCApprovedText)) +var invoiceFirstNotificationTmpl = template.Must( + template.New("first_invoice_notification").Parse(invoiceFirstText)) +var invoiceSecondNotificationTmpl = template.Must( + template.New("second_invoice_notification").Parse(invoiceSecondText)) +var invoiceFinalNotificationTmpl = template.Must( + template.New("final_invoice_notification").Parse(invoiceFinalText)) + +type invoiceNotification struct { + Username string + Month string + Year int +} + +const invoiceFirstText = ` +{{.Username}}, + +Please submit your invoice for {{.Month}} {{.Year}}. + +Regards, +Contractor Management System +` + +const invoiceSecondText = ` +{{.Username}}, + +You have not yet submitted an invoice for {{.Month}} {{.Year}}. + +Regards, +Contractor Management System` + +const invoiceFinalText = ` +{{.Username}}, + +You have not yet submitted an invoice for {{.Month}} {{.Year}}. This is the final warning you will receive, if you delay further, you may not be included in this month's payout. + +Regards, +Contractor Management System +` + // DCC submitted - Send to admins type dccSubmitted struct { Link string // DCC gui link @@ -202,25 +243,6 @@ Contractor Management System var invoiceStatusUpdateTmpl = template.Must( template.New("invoiceStatusUpdate").Parse(invoiceStatusUpdateText)) -// Invoice not sent - Send to users that did not send monthly invoice yet -type invoiceNotSent struct { - Username string // User username - Month string // Current month - Year int // Current year -} - -const invoiceNotSentText = ` -{{.Username}}, - -You have not yet submitted an invoice for {{.Month}} {{.Year}}. Please do so as soon as possible, so your invoice may be reviewed and paid out in a timely manner. - -Regards, -Contractor Management System -` - -var invoiceNotSentTmpl = template.Must( - template.New("invoiceNotSent").Parse(invoiceNotSentText)) - // Invoice new comment - Send to invoice owner const invoiceNewCommentText = ` An administrator has submitted a new comment to your invoice, please login to cms.decred.org to view the message.