package abono
import (
"html/template"
"strings"
"time"
)
// The keys of a Status, for the app to translate. Tones are status-pill's.
const (
StatusActive = "abono.status.active"
StatusRenews = "abono.status.renews"
StatusCancelling = "abono.status.cancelling"
StatusFailing = "abono.status.failing"
StatusEnded = "abono.status.ended"
StatusComp = "abono.status.comp"
StatusCompUntil = "abono.status.comp_until"
)
// Status is the one line a holding reads as: a message key, a tone, and
// the date the key's sentence mentions, when it has one.
type Status struct {
Key string
Tone string // positive, warning, negative or neutral
Date *time.Time
}
// Describe is what to say of h at now. It applies the same holding rule as
// Holdings, at the now it is given, so a stale Holding cannot say "active"
// of access that has ended.
//
// active grants, no date known
// renews active or trialing, with a period end, not cancelling: Date is the period end
// cancelling cancel_at_period_end and granting: Date is the period end
// failing past_due and granting: Date is the day access stops, 30 days after the
// first unpaid period started (UnpaidSince, else PeriodStart; after the
// period end when neither is known), or the period end if that is
// sooner and it is cancelling
// ended does not grant
// comp a comp with no end
// comp_until a comp with an end: Date is ends_at
//
// A past_due subscription that is also cancelling reads as failing: the
// payment problem is what the reader can act on, and the date it gives is
// the one access really stops on.
func Describe(h Holding, now time.Time) Status {
granting, _ := h.evaluate(now)
if !granting {
return Status{Key: StatusEnded, Tone: "neutral"}
}
if h.Source == SourceComp {
if h.Until == nil {
return Status{Key: StatusComp, Tone: "positive"}
}
return Status{Key: StatusCompUntil, Tone: "positive", Date: copyTime(h.Until)}
}
switch {
case h.Status == "past_due":
// Granting means there is a stop date to give (ruling R30).
stops, ok := pastDueStops(h.UnpaidSince, h.PeriodStart, h.Until, h.CancelAtPeriodEnd)
if !ok {
return Status{Key: StatusEnded, Tone: "neutral"}
}
return Status{Key: StatusFailing, Tone: "warning", Date: &stops}
case h.CancelAtPeriodEnd:
return Status{Key: StatusCancelling, Tone: "warning", Date: copyTime(h.Until)}
case h.Until != nil:
return Status{Key: StatusRenews, Tone: "positive", Date: copyTime(h.Until)}
}
return Status{Key: StatusActive, Tone: "positive"}
}
func copyTime(t *time.Time) *time.Time {
if t == nil {
return nil
}
c := *t
return &c
}
// Describe is Describe(h, now), as a method so a template can call it:
// {{.Holding.Describe .Now}}.
func (h Holding) Describe(now time.Time) Status { return Describe(h, now) }
// Sentence is the status's words as markup, for a template: text is the
// key's catalog text with its {date} still in it, and dateText is what to
// show for the date (empty shows the date as year-month-day). The date
// goes in a