Files
MealQuest/internal/shopping/chat.go
T
dev_agentandClaude Opus 5.5 45c4f8a803
CI / ci (push) Failing after 5m48s
CI / ci (pull_request) Successful in 6m58s
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>
2026-10-09 15:42:16 -04:00

77 lines
2.4 KiB
Go

package shopping
import (
"context"
"database/sql"
"fmt"
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/chat"
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/db"
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/household"
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/jobs"
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/store"
)
// SendToChat posts the Supply run of the week starting weekStart to Google
// Chat (chat.PostSupplyRun): each dinner with its items still needed, and a
// link to the Supply run. It records sent_to_chat_at and returns the list.
//
// A week is sent once: a Supply run already sent is returned as it is,
// without posting again, unless resend is true. A week with no Supply run
// is ErrListNotFound and one frozen into the Supply archive ErrFrozen. A
// post that fails returns chat.PostSupplyRun's error and records nothing.
func (s *Service) SendToChat(ctx context.Context, weekStart store.LocalDate, resend bool) (List, error) {
s.sending.Lock()
defer s.sending.Unlock()
l, err := s.Get(ctx, weekStart)
if err != nil {
return List{}, err
}
if l.Status != StatusCurrent {
return List{}, ErrFrozen
}
if l.SentToChatAt != nil && !resend {
return l, nil
}
name, err := household.WeekLabel(weekStart)
if err != nil {
return List{}, fmt.Errorf("shopping: send to Google Chat: %w", err)
}
if l.Title != nil {
name = *l.Title
}
groups := make([]chat.SupplyRunGroup, len(l.Groups))
for i, g := range l.Groups {
groups[i].Dinner = g.DinnerName
for _, it := range g.Items {
if !it.Needed {
continue
}
item := chat.SupplyRunItem{Name: it.Name}
if it.Qty != nil {
item.Qty = *it.Qty
}
groups[i].Items = append(groups[i].Items, item)
}
}
if err := s.chat.PostSupplyRun(ctx, weekStart, name, groups); err != nil {
return List{}, err
}
// The post is made: record it even if the client has gone.
write, cancel := jobs.FinalWriteContext(ctx)
defer cancel()
now := store.Now().String()
n, err := db.New(s.st.Writer).MarkShoppingListSentToChat(write, db.MarkShoppingListSentToChatParams{
SentToChatAt: sql.NullString{String: now, Valid: true},
UpdatedAt: now,
ID: l.ID,
})
if err != nil {
return List{}, fmt.Errorf("shopping: record sent to Google Chat: %w", err)
}
if n != 1 {
return List{}, ErrFrozen
}
return s.Get(write, weekStart)
}