Wire plan_ready, list_ready, new_suggestions and Send to Google Chat
Connect the features to the notification outbox so real events notify,
each stored by the new notify.EmitTx in the transaction of the change it
is about (Wake after commit):
- plan_ready:{weekStart} (/plan) when a draft job stores the AI's draft;
a redraft shares the key and a manual swap never emits. The title reads
"Next week's plan is ready" for the coming week.
- list_ready:{weekStart} (/plan/shopping) when a week's Supply run is
first created, by generate or after Push to Calendar.
- new_suggestions:{batchId} (/discover) when a scheduled or manual batch
ends done with cards; replenish batches never notify.
- plan.Service.EmitDayOfReminder(date) emits day_of_reminder:{date} for
the Feature 20 scheduler.
Add POST /api/v1/shopping/{weekStart}/send-chat: shopping.SendToChat posts
the dinners with needed items and a link through chat.PostSupplyRun,
records sent_to_chat_at, and is idempotent per week unless
{"resend": true}. The Supply run's Send to Google Chat button calls it
("Send again" once sent) and shows failures in an ErrorBanner; the unused
Unavailable placeholder is removed.
Task: f0cd9d50-9c11-41be-a945-7bae9f5f28a4
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ package chat
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/household"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/notify"
|
||||
@@ -34,13 +35,16 @@ var actions = map[notify.EventType]string{
|
||||
// button.
|
||||
var carded = map[notify.EventType]bool{notify.PlanReady: true, notify.ListReady: true}
|
||||
|
||||
// PlanReady is the plan_ready event of the week starting on week, naming
|
||||
// its dinners in plan order.
|
||||
func PlanReady(week store.LocalDate, dinners []string) notify.Event {
|
||||
// PlanReady is the plan_ready event of the week starting on week, drafted
|
||||
// in the week starting on thisWeek, naming its dinners in plan order. Its
|
||||
// title names the week as seen from thisWeek: "Next week's plan is ready",
|
||||
// "This week's plan is ready", or "The plan for the week of Oct 26 is
|
||||
// ready" for any other week.
|
||||
func PlanReady(week, thisWeek store.LocalDate, dinners []string) notify.Event {
|
||||
return notify.Event{
|
||||
Type: notify.PlanReady,
|
||||
Key: string(notify.PlanReady) + ":" + week.String(),
|
||||
Title: "This week's plan is ready",
|
||||
Title: planTitle(week, thisWeek),
|
||||
Body: nameList(dinners),
|
||||
URL: "/plan",
|
||||
Week: week,
|
||||
@@ -100,6 +104,72 @@ func RotationReview(dinnerID, name string, week store.LocalDate) notify.Event {
|
||||
}
|
||||
}
|
||||
|
||||
// SupplyRunItem is a Supply run item still needed, as sent to Google Chat;
|
||||
// Qty is empty when the recipe gives none.
|
||||
type SupplyRunItem struct {
|
||||
Name string
|
||||
Qty string
|
||||
}
|
||||
|
||||
// SupplyRunGroup is one dinner of a Supply run sent to Google Chat, with
|
||||
// its items still needed.
|
||||
type SupplyRunGroup struct {
|
||||
Dinner string
|
||||
Items []SupplyRunItem
|
||||
}
|
||||
|
||||
// SupplyRunText is the Google Chat message of the Supply run named name
|
||||
// (its title, or its week label until it has one): each dinner with items
|
||||
// still needed in bold, its items one per line, and the call to action.
|
||||
//
|
||||
// Supply run · Week 42 · Oct 12 – 18
|
||||
//
|
||||
// *Tacos*
|
||||
// • ground beef — 1 lb
|
||||
// • tortillas
|
||||
//
|
||||
// Open the Supply run →
|
||||
//
|
||||
// A Supply run with nothing still needed says so.
|
||||
func SupplyRunText(name string, groups []SupplyRunGroup) string {
|
||||
var b strings.Builder
|
||||
b.WriteString("Supply run · " + name)
|
||||
listed := false
|
||||
for _, g := range groups {
|
||||
if len(g.Items) == 0 {
|
||||
continue
|
||||
}
|
||||
listed = true
|
||||
b.WriteString("\n\n*" + g.Dinner + "*")
|
||||
for _, it := range g.Items {
|
||||
b.WriteString("\n• " + it.Name)
|
||||
if it.Qty != "" {
|
||||
b.WriteString(" — " + it.Qty)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !listed {
|
||||
b.WriteString("\n\nNothing left to buy.")
|
||||
}
|
||||
b.WriteString("\n\n" + actions[notify.ListReady] + " →")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// planTitle is the plan_ready title of the week starting on week, seen
|
||||
// from the week starting on thisWeek.
|
||||
func planTitle(week, thisWeek store.LocalDate) string {
|
||||
y, m, d := thisWeek.Date()
|
||||
next := store.LocalDateOf(time.Date(y, m, d+7, 0, 0, 0, 0, time.UTC))
|
||||
switch week {
|
||||
case thisWeek:
|
||||
return "This week's plan is ready"
|
||||
case next:
|
||||
return "Next week's plan is ready"
|
||||
}
|
||||
_, m, d = week.Date()
|
||||
return fmt.Sprintf("The plan for the week of %s %d is ready", m.String()[:3], d)
|
||||
}
|
||||
|
||||
// Text is the Google Chat message of p: its title, its body after a colon
|
||||
// and the event's call to action with an arrow.
|
||||
func Text(p notify.Payload) string {
|
||||
|
||||
Reference in New Issue
Block a user