2025-07-14 06:26:26 -07:00
|
|
|
|
package handlers
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2025-07-17 06:35:30 -07:00
|
|
|
|
"context"
|
|
|
|
|
|
"database/sql"
|
|
|
|
|
|
"fmt"
|
2025-07-14 06:26:26 -07:00
|
|
|
|
"net/http"
|
|
|
|
|
|
"strconv"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
"code.springupsoftware.com/cmc/cmc-sales/internal/cmc/db"
|
2025-07-17 06:35:30 -07:00
|
|
|
|
"code.springupsoftware.com/cmc/cmc-sales/internal/cmc/email"
|
2025-07-14 06:26:26 -07:00
|
|
|
|
"code.springupsoftware.com/cmc/cmc-sales/internal/cmc/templates"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-07-17 06:35:30 -07:00
|
|
|
|
// Helper: returns date string or empty if zero
|
|
|
|
|
|
func formatDate(t time.Time, layout string) string {
|
|
|
|
|
|
if t.IsZero() || t.Year() == 1970 {
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
|
|
|
|
|
return t.Format(layout)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Helper: checks if a time is a valid DB value (not zero or 1970-01-01)
|
|
|
|
|
|
func isValidDBTime(t time.Time) bool {
|
|
|
|
|
|
return !t.IsZero() && t.After(time.Date(1971, 1, 1, 0, 0, 0, 0, time.UTC))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// calcExpiryInfo is a helper to calculate expiry info for a quote
|
|
|
|
|
|
func calcExpiryInfo(validUntil time.Time) (string, int, int) {
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
|
nowDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
|
|
|
|
|
validUntilDate := time.Date(validUntil.Year(), validUntil.Month(), validUntil.Day(), 0, 0, 0, 0, validUntil.Location())
|
|
|
|
|
|
daysUntil := int(validUntilDate.Sub(nowDate).Hours() / 24)
|
|
|
|
|
|
daysSince := int(nowDate.Sub(validUntilDate).Hours() / 24)
|
|
|
|
|
|
var relative string
|
|
|
|
|
|
if validUntilDate.After(nowDate) || validUntilDate.Equal(nowDate) {
|
|
|
|
|
|
switch daysUntil {
|
|
|
|
|
|
case 0:
|
|
|
|
|
|
relative = "expires today"
|
|
|
|
|
|
case 1:
|
|
|
|
|
|
relative = "expires tomorrow"
|
|
|
|
|
|
default:
|
|
|
|
|
|
relative = "expires in " + strconv.Itoa(daysUntil) + " days"
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
switch daysSince {
|
|
|
|
|
|
case 0:
|
|
|
|
|
|
relative = "expired today"
|
|
|
|
|
|
case 1:
|
|
|
|
|
|
relative = "expired yesterday"
|
|
|
|
|
|
default:
|
|
|
|
|
|
relative = "expired " + strconv.Itoa(daysSince) + " days ago"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return relative, daysUntil, daysSince
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// QuoteRow interface for all quote row types
|
|
|
|
|
|
// (We use wrapper types since sqlc structs can't be modified directly)
|
|
|
|
|
|
type QuoteRow interface {
|
|
|
|
|
|
GetID() int32
|
|
|
|
|
|
GetUsername() string
|
|
|
|
|
|
GetEnquiryID() int32
|
|
|
|
|
|
GetEnquiryRef() string
|
|
|
|
|
|
GetDateIssued() time.Time
|
|
|
|
|
|
GetValidUntil() time.Time
|
|
|
|
|
|
GetReminderType() int32
|
|
|
|
|
|
GetReminderSent() time.Time
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Wrapper types for each DB row struct
|
|
|
|
|
|
|
|
|
|
|
|
type ExpiringSoonQuoteRowWrapper struct{ db.GetExpiringSoonQuotesRow }
|
|
|
|
|
|
|
|
|
|
|
|
func (q ExpiringSoonQuoteRowWrapper) GetID() int32 { return q.DocumentID }
|
|
|
|
|
|
func (q ExpiringSoonQuoteRowWrapper) GetUsername() string { return q.Username }
|
|
|
|
|
|
func (q ExpiringSoonQuoteRowWrapper) GetEnquiryID() int32 { return q.EnquiryID }
|
|
|
|
|
|
func (q ExpiringSoonQuoteRowWrapper) GetEnquiryRef() string { return q.EnquiryRef }
|
|
|
|
|
|
func (q ExpiringSoonQuoteRowWrapper) GetDateIssued() time.Time { return q.DateIssued }
|
|
|
|
|
|
func (q ExpiringSoonQuoteRowWrapper) GetValidUntil() time.Time { return q.ValidUntil }
|
|
|
|
|
|
func (q ExpiringSoonQuoteRowWrapper) GetReminderType() int32 { return q.LatestReminderType }
|
|
|
|
|
|
func (q ExpiringSoonQuoteRowWrapper) GetReminderSent() time.Time { return q.LatestReminderSentTime }
|
|
|
|
|
|
|
|
|
|
|
|
type RecentlyExpiredQuoteRowWrapper struct{ db.GetRecentlyExpiredQuotesRow }
|
|
|
|
|
|
|
|
|
|
|
|
func (q RecentlyExpiredQuoteRowWrapper) GetID() int32 { return q.DocumentID }
|
|
|
|
|
|
func (q RecentlyExpiredQuoteRowWrapper) GetUsername() string { return q.Username }
|
|
|
|
|
|
func (q RecentlyExpiredQuoteRowWrapper) GetEnquiryID() int32 { return q.EnquiryID }
|
|
|
|
|
|
func (q RecentlyExpiredQuoteRowWrapper) GetEnquiryRef() string { return q.EnquiryRef }
|
|
|
|
|
|
func (q RecentlyExpiredQuoteRowWrapper) GetDateIssued() time.Time { return q.DateIssued }
|
|
|
|
|
|
func (q RecentlyExpiredQuoteRowWrapper) GetValidUntil() time.Time { return q.ValidUntil }
|
|
|
|
|
|
func (q RecentlyExpiredQuoteRowWrapper) GetReminderType() int32 { return q.LatestReminderType }
|
|
|
|
|
|
func (q RecentlyExpiredQuoteRowWrapper) GetReminderSent() time.Time { return q.LatestReminderSentTime }
|
|
|
|
|
|
|
|
|
|
|
|
type ExpiringSoonQuoteOnDayRowWrapper struct {
|
|
|
|
|
|
db.GetExpiringSoonQuotesOnDayRow
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (q ExpiringSoonQuoteOnDayRowWrapper) GetID() int32 { return q.DocumentID }
|
|
|
|
|
|
func (q ExpiringSoonQuoteOnDayRowWrapper) GetUsername() string { return q.Username }
|
|
|
|
|
|
func (q ExpiringSoonQuoteOnDayRowWrapper) GetEnquiryID() int32 { return q.EnquiryID }
|
|
|
|
|
|
func (q ExpiringSoonQuoteOnDayRowWrapper) GetEnquiryRef() string { return q.EnquiryRef }
|
|
|
|
|
|
func (q ExpiringSoonQuoteOnDayRowWrapper) GetDateIssued() time.Time { return q.DateIssued }
|
|
|
|
|
|
func (q ExpiringSoonQuoteOnDayRowWrapper) GetValidUntil() time.Time { return q.ValidUntil }
|
|
|
|
|
|
func (q ExpiringSoonQuoteOnDayRowWrapper) GetReminderType() int32 { return q.LatestReminderType }
|
|
|
|
|
|
func (q ExpiringSoonQuoteOnDayRowWrapper) GetReminderSent() time.Time {
|
|
|
|
|
|
return q.LatestReminderSentTime
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type RecentlyExpiredQuoteOnDayRowWrapper struct {
|
|
|
|
|
|
db.GetRecentlyExpiredQuotesOnDayRow
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (q RecentlyExpiredQuoteOnDayRowWrapper) GetID() int32 { return q.DocumentID }
|
|
|
|
|
|
func (q RecentlyExpiredQuoteOnDayRowWrapper) GetUsername() string { return q.Username }
|
|
|
|
|
|
func (q RecentlyExpiredQuoteOnDayRowWrapper) GetEnquiryID() int32 { return q.EnquiryID }
|
|
|
|
|
|
func (q RecentlyExpiredQuoteOnDayRowWrapper) GetEnquiryRef() string { return q.EnquiryRef }
|
|
|
|
|
|
func (q RecentlyExpiredQuoteOnDayRowWrapper) GetDateIssued() time.Time { return q.DateIssued }
|
|
|
|
|
|
func (q RecentlyExpiredQuoteOnDayRowWrapper) GetValidUntil() time.Time { return q.ValidUntil }
|
|
|
|
|
|
func (q RecentlyExpiredQuoteOnDayRowWrapper) GetReminderType() int32 { return q.LatestReminderType }
|
|
|
|
|
|
func (q RecentlyExpiredQuoteOnDayRowWrapper) GetReminderSent() time.Time {
|
|
|
|
|
|
return q.LatestReminderSentTime
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Helper: formats a quote row for output (generic)
|
|
|
|
|
|
func formatQuoteRow(q QuoteRow) map[string]interface{} {
|
|
|
|
|
|
relative, daysUntil, daysSince := calcExpiryInfo(q.GetValidUntil())
|
|
|
|
|
|
return map[string]interface{}{
|
|
|
|
|
|
"ID": q.GetID(),
|
|
|
|
|
|
"Username": strings.Title(q.GetUsername()),
|
|
|
|
|
|
"EnquiryID": q.GetEnquiryID(),
|
|
|
|
|
|
"EnquiryRef": q.GetEnquiryRef(),
|
|
|
|
|
|
"DateIssued": formatDate(q.GetDateIssued(), "2006-01-02"),
|
|
|
|
|
|
"ValidUntil": formatDate(q.GetValidUntil(), "2006-01-02"),
|
|
|
|
|
|
"ValidUntilRelative": relative,
|
|
|
|
|
|
"DaysUntilExpiry": daysUntil,
|
|
|
|
|
|
"DaysSinceExpiry": daysSince,
|
|
|
|
|
|
"LatestReminderSent": formatDate(q.GetReminderSent(), "2006-01-02 15:04:05"),
|
|
|
|
|
|
"LatestReminderType": reminderTypeString(int(q.GetReminderType())),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-14 06:26:26 -07:00
|
|
|
|
type QuotesHandler struct {
|
2025-07-17 06:35:30 -07:00
|
|
|
|
queries *db.Queries
|
|
|
|
|
|
tmpl *templates.TemplateManager
|
|
|
|
|
|
emailService *email.EmailService // Add email service
|
2025-07-14 06:26:26 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-17 06:35:30 -07:00
|
|
|
|
func NewQuotesHandler(queries *db.Queries, tmpl *templates.TemplateManager, emailService *email.EmailService) *QuotesHandler {
|
2025-07-14 06:26:26 -07:00
|
|
|
|
return &QuotesHandler{
|
2025-07-17 06:35:30 -07:00
|
|
|
|
queries: queries,
|
|
|
|
|
|
tmpl: tmpl,
|
|
|
|
|
|
emailService: emailService,
|
2025-07-14 06:26:26 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-17 06:35:30 -07:00
|
|
|
|
func (h *QuotesHandler) QuotesOutstandingView(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
|
// Days to look ahead and behind for expiring quotes
|
|
|
|
|
|
days := 7
|
2025-07-14 06:26:26 -07:00
|
|
|
|
|
2025-07-17 06:35:30 -07:00
|
|
|
|
// Show all quotes that are expiring in the next 7 days
|
|
|
|
|
|
expiringSoonQuotes, err := h.GetOutstandingQuotes(r, days)
|
2025-07-14 06:26:26 -07:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-17 06:35:30 -07:00
|
|
|
|
// Show all quotes that have expired in the last 60 days
|
|
|
|
|
|
recentlyExpiredQuotes, err := h.GetOutstandingQuotes(r, -60)
|
2025-07-14 06:26:26 -07:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-17 06:35:30 -07:00
|
|
|
|
data := map[string]interface{}{
|
|
|
|
|
|
"RecentlyExpiredQuotes": recentlyExpiredQuotes,
|
|
|
|
|
|
"ExpiringSoonQuotes": expiringSoonQuotes,
|
|
|
|
|
|
}
|
|
|
|
|
|
if err := h.tmpl.Render(w, "quotes/index.html", data); err != nil {
|
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetOutstandingQuotes returns outstanding quotes based on daysUntilExpiry.
|
|
|
|
|
|
func (h *QuotesHandler) GetOutstandingQuotes(r *http.Request, daysUntilExpiry int) ([]map[string]interface{}, error) {
|
|
|
|
|
|
var rows []map[string]interface{}
|
|
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
// If daysUntilExpiry is positive, get quotes expiring soon; if negative, get recently expired quotes
|
|
|
|
|
|
if daysUntilExpiry >= 0 {
|
|
|
|
|
|
quotes, err := h.queries.GetExpiringSoonQuotes(ctx, daysUntilExpiry)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
2025-07-14 06:26:26 -07:00
|
|
|
|
}
|
2025-07-17 06:35:30 -07:00
|
|
|
|
for _, q := range quotes {
|
|
|
|
|
|
rows = append(rows, formatQuoteRow(ExpiringSoonQuoteRowWrapper{q}))
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
days := -daysUntilExpiry
|
|
|
|
|
|
quotes, err := h.queries.GetRecentlyExpiredQuotes(ctx, days)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, q := range quotes {
|
|
|
|
|
|
rows = append(rows, formatQuoteRow(RecentlyExpiredQuoteRowWrapper{q}))
|
2025-07-14 06:26:26 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-07-17 06:35:30 -07:00
|
|
|
|
return rows, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetOutstandingQuotesOnDay returns quotes expiring exactly N days from today (if day >= 0), or exactly N days ago (if day < 0).
|
|
|
|
|
|
func (h *QuotesHandler) GetOutstandingQuotesOnDay(r *http.Request, day int) ([]map[string]interface{}, error) {
|
|
|
|
|
|
var rows []map[string]interface{}
|
|
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
// If day is positive, get quotes expiring on that day; if negative, get recently expired quotes on that day in the past
|
|
|
|
|
|
if day >= 0 {
|
|
|
|
|
|
quotes, err := h.queries.GetExpiringSoonQuotesOnDay(ctx, day)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, q := range quotes {
|
|
|
|
|
|
rows = append(rows, formatQuoteRow(ExpiringSoonQuoteOnDayRowWrapper{q}))
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
days := -day
|
|
|
|
|
|
quotes, err := h.queries.GetRecentlyExpiredQuotesOnDay(ctx, days)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, q := range quotes {
|
|
|
|
|
|
rows = append(rows, formatQuoteRow(RecentlyExpiredQuoteOnDayRowWrapper{q}))
|
2025-07-14 06:26:26 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-07-17 06:35:30 -07:00
|
|
|
|
return rows, nil
|
|
|
|
|
|
}
|
2025-07-14 06:26:26 -07:00
|
|
|
|
|
2025-07-17 06:35:30 -07:00
|
|
|
|
type QuoteReminderType int
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
|
FirstReminder QuoteReminderType = 1
|
|
|
|
|
|
SecondReminder QuoteReminderType = 2
|
|
|
|
|
|
ThirdReminder QuoteReminderType = 3
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
func (t QuoteReminderType) String() string {
|
|
|
|
|
|
switch t {
|
|
|
|
|
|
case FirstReminder:
|
|
|
|
|
|
return "FirstReminder"
|
|
|
|
|
|
case SecondReminder:
|
|
|
|
|
|
return "SecondReminder"
|
|
|
|
|
|
case ThirdReminder:
|
|
|
|
|
|
return "ThirdReminder"
|
|
|
|
|
|
default:
|
|
|
|
|
|
return "UnknownReminder"
|
2025-07-14 06:26:26 -07:00
|
|
|
|
}
|
2025-07-17 06:35:30 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type quoteReminderJob struct {
|
|
|
|
|
|
DayOffset int
|
|
|
|
|
|
ReminderType QuoteReminderType
|
|
|
|
|
|
Subject string
|
|
|
|
|
|
Template string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DailyQuoteExpirationCheck checks quotes for reminders and expiry notices (callable as a cron job from main)
|
|
|
|
|
|
func (h *QuotesHandler) DailyQuoteExpirationCheck() {
|
|
|
|
|
|
fmt.Println("Running DailyQuoteExpirationCheck...")
|
|
|
|
|
|
|
|
|
|
|
|
reminderJobs := []quoteReminderJob{
|
|
|
|
|
|
{7, FirstReminder, "Quote Validity Notice – Quote Ref #: ", "quotes/first_reminder.html"},
|
|
|
|
|
|
{-7, SecondReminder, "Quote Expired – 2nd Notice for Quote Ref #: ", "quotes/second_reminder.html"},
|
|
|
|
|
|
{-60, ThirdReminder, "Final Reminder – Quote Ref #: ", "quotes/final_reminder.html"},
|
2025-07-14 06:26:26 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-17 06:35:30 -07:00
|
|
|
|
for _, job := range reminderJobs {
|
|
|
|
|
|
quotes, err := h.GetOutstandingQuotesOnDay((&http.Request{}), job.DayOffset)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
fmt.Printf("Error getting quotes for day offset %d: %v\n", job.DayOffset, err)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, q := range quotes {
|
|
|
|
|
|
templateData := map[string]interface{}{
|
|
|
|
|
|
"CustomerName": "Test Customer",
|
|
|
|
|
|
"SubmissionDate": q["DateIssued"],
|
|
|
|
|
|
"QuoteRef": q["EnquiryRef"],
|
|
|
|
|
|
"SenderName": "Test Sender",
|
|
|
|
|
|
"SenderPosition": "Sales Manager",
|
|
|
|
|
|
"CompanyName": "Test Company",
|
|
|
|
|
|
}
|
|
|
|
|
|
err := h.SendQuoteReminderEmail(
|
|
|
|
|
|
context.Background(),
|
|
|
|
|
|
q["ID"].(int32),
|
|
|
|
|
|
job.ReminderType,
|
|
|
|
|
|
"test@example.com",
|
|
|
|
|
|
job.Subject+fmt.Sprint(q["ID"]),
|
|
|
|
|
|
job.Template,
|
|
|
|
|
|
templateData,
|
|
|
|
|
|
nil,
|
|
|
|
|
|
)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
fmt.Printf("Error sending %s for quote %v: %v\n", job.ReminderType.String(), q["ID"], err)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
fmt.Printf("%s sent and recorded for quote %v\n", job.ReminderType.String(), q["ID"])
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-07-14 06:26:26 -07:00
|
|
|
|
}
|
2025-07-17 06:35:30 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SendQuoteReminderEmail checks if a reminder of the given type has already been sent for the quote, sends the email if not, and records it.
|
|
|
|
|
|
func (h *QuotesHandler) SendQuoteReminderEmail(ctx context.Context, quoteID int32, reminderType QuoteReminderType, recipient string, subject string, templateName string, templateData map[string]interface{}, username *string) error {
|
|
|
|
|
|
// Check if reminder already sent
|
|
|
|
|
|
reminders, err := h.queries.GetQuoteRemindersByType(ctx, db.GetQuoteRemindersByTypeParams{
|
|
|
|
|
|
QuoteID: quoteID,
|
|
|
|
|
|
ReminderType: int32(reminderType),
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("failed to check existing reminders: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
if len(reminders) > 0 {
|
|
|
|
|
|
return fmt.Errorf("reminder of type %s already sent for quote %d", reminderType.String(), quoteID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Send the email
|
|
|
|
|
|
err = h.emailService.SendTemplateEmail(
|
|
|
|
|
|
recipient,
|
|
|
|
|
|
subject,
|
|
|
|
|
|
templateName,
|
|
|
|
|
|
templateData,
|
|
|
|
|
|
nil, nil,
|
|
|
|
|
|
)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("failed to send email: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Record the reminder
|
|
|
|
|
|
var user sql.NullString
|
|
|
|
|
|
if username != nil {
|
|
|
|
|
|
user = sql.NullString{String: *username, Valid: true}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
user = sql.NullString{Valid: false}
|
|
|
|
|
|
}
|
|
|
|
|
|
_, err = h.queries.InsertQuoteReminder(ctx, db.InsertQuoteReminderParams{
|
|
|
|
|
|
QuoteID: quoteID,
|
|
|
|
|
|
ReminderType: int32(reminderType),
|
|
|
|
|
|
DateSent: time.Now(),
|
|
|
|
|
|
Username: user,
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("failed to record reminder: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Helper: get reminder type as string
|
|
|
|
|
|
func reminderTypeString(reminderType int) string {
|
|
|
|
|
|
switch reminderType {
|
|
|
|
|
|
case 0:
|
|
|
|
|
|
return "No Reminder"
|
|
|
|
|
|
case 1:
|
|
|
|
|
|
return "First Reminder"
|
|
|
|
|
|
case 2:
|
|
|
|
|
|
return "Second Reminder"
|
|
|
|
|
|
case 3:
|
|
|
|
|
|
return "Final Reminder"
|
|
|
|
|
|
default:
|
|
|
|
|
|
return ""
|
2025-07-14 06:26:26 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|