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