Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[cms] Add more invoice email notifications #1353

Merged
merged 1 commit into from
May 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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