Skip to content

Commit

Permalink
[cms] Add more invoice email notifications (#1353)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexlyp committed May 21, 2021
1 parent 22f8e7c commit 7ade635
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 81 deletions.
154 changes: 100 additions & 54 deletions politeiawww/cmsnotifications.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
12 changes: 5 additions & 7 deletions politeiawww/email.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
62 changes: 42 additions & 20 deletions politeiawww/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

package main

import "text/template"
import (
"text/template"
)

// User email verify - Send verification link to new user
type userEmailVerify struct {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit 7ade635

Please sign in to comment.