mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-10-28 15:51:15 +00:00
POST /v1/internal/jobs/reports/dispatch emails each tenant owner a trailing-window digest (bookings, revenue, per-service and per-staff breakdown) localized to the tenant locale; zero-activity tenants are skipped. Metrics cores extracted to tenant-scoped variants so the job needs no principal. PATCH /v1/catalog/class-sessions/:id sets a session's seat count with a confirmed-bookings floor — shrinking below it needs allowOverCapacity, matching the waitlist-promotion convention. Session rows gain an inline capacity editor with an overbook confirm state. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
122 lines
3.7 KiB
Go
122 lines
3.7 KiB
Go
package reports
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"bookra/apps/backend/internal/bookings"
|
|
"bookra/apps/backend/internal/db"
|
|
"bookra/apps/backend/internal/notifications"
|
|
)
|
|
|
|
func newTestService(repo db.Repository) *Service {
|
|
return NewService(repo, bookings.NewService(repo, nil, "http://localhost:3000"), "http://localhost:3000", "[email protected]")
|
|
}
|
|
|
|
func TestDispatchDigestsSendsLocalizedSummary(t *testing.T) {
|
|
repo := db.NewMemoryRepository()
|
|
svc := newTestService(repo)
|
|
ctx := context.Background()
|
|
tenantID := "5d6b3551-0a3e-4b86-bdf0-e9df20a47148"
|
|
serviceID := "d5d76a61-3d49-467c-8dd4-bf61ee754e39"
|
|
|
|
_, err := repo.CreateBooking(ctx, db.CreateBookingParams{
|
|
TenantID: tenantID,
|
|
ServiceID: &serviceID,
|
|
BookingMode: "appointment",
|
|
CustomerName: "Klára",
|
|
CustomerEmail: "[email protected]",
|
|
StartsAt: time.Now().UTC().Add(-26 * time.Hour),
|
|
EndsAt: time.Now().UTC().Add(-25 * time.Hour),
|
|
Status: "confirmed",
|
|
Reference: "BK-DIG-1",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("seed booking: %v", err)
|
|
}
|
|
|
|
var sent []notifications.EmailMessage
|
|
capture := func(_ context.Context, msg notifications.EmailMessage) error {
|
|
sent = append(sent, msg)
|
|
return nil
|
|
}
|
|
|
|
response, err := svc.DispatchDigests(ctx, 7, tenantID, capture)
|
|
if err != nil {
|
|
t.Fatalf("dispatch: %v", err)
|
|
}
|
|
if response.TenantCount != 1 || response.SentCount != 1 {
|
|
t.Fatalf("expected 1 tenant / 1 sent, got %+v", response)
|
|
}
|
|
if len(sent) != 1 {
|
|
t.Fatalf("expected 1 email, got %d", len(sent))
|
|
}
|
|
msg := sent[0]
|
|
if msg.To != "[email protected]" || msg.From != "[email protected]" {
|
|
t.Fatalf("bad addressing: %+v", msg)
|
|
}
|
|
// Demo tenant locale is cs — subject should be the Czech digest.
|
|
if !strings.Contains(msg.Subject, "přehled") {
|
|
t.Fatalf("expected Czech subject, got %q", msg.Subject)
|
|
}
|
|
if !strings.Contains(msg.Text, "Rezervace celkem") || !strings.Contains(msg.Text, "1200 Kč") {
|
|
t.Fatalf("digest body missing totals: %q", msg.Text)
|
|
}
|
|
if !strings.Contains(msg.Text, "Signature treatment") {
|
|
t.Fatalf("digest body missing service line: %q", msg.Text)
|
|
}
|
|
}
|
|
|
|
func TestDispatchDigestsSkipsZeroActivityTenant(t *testing.T) {
|
|
// NewMemoryRepository seeds the tenant, staff, and services but no
|
|
// bookings — the trailing window is empty, so the digest must skip.
|
|
repo := db.NewMemoryRepository()
|
|
svc := newTestService(repo)
|
|
calls := 0
|
|
response, err := svc.DispatchDigests(context.Background(), 7, "", func(context.Context, notifications.EmailMessage) error {
|
|
calls++
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("dispatch: %v", err)
|
|
}
|
|
if response.TenantCount != 1 || response.SkippedCount != 1 || calls != 0 {
|
|
t.Fatalf("expected 1 skipped tenant and no sends, got %+v (calls=%d)", response, calls)
|
|
}
|
|
}
|
|
|
|
func TestDispatchDigestsCountsSendFailures(t *testing.T) {
|
|
repo := db.NewMemoryRepository()
|
|
svc := newTestService(repo)
|
|
ctx := context.Background()
|
|
tenantID := "5d6b3551-0a3e-4b86-bdf0-e9df20a47148"
|
|
serviceID := "d5d76a61-3d49-467c-8dd4-bf61ee754e39"
|
|
|
|
if _, err := repo.CreateBooking(ctx, db.CreateBookingParams{
|
|
TenantID: tenantID,
|
|
ServiceID: &serviceID,
|
|
BookingMode: "appointment",
|
|
CustomerName: "Klára",
|
|
CustomerEmail: "[email protected]",
|
|
StartsAt: time.Now().UTC().Add(-2 * time.Hour),
|
|
EndsAt: time.Now().UTC().Add(-time.Hour),
|
|
Status: "confirmed",
|
|
Reference: "BK-DIG-2",
|
|
}); err != nil {
|
|
t.Fatalf("seed booking: %v", err)
|
|
}
|
|
|
|
response, err := svc.DispatchDigests(ctx, 7, tenantID, func(context.Context, notifications.EmailMessage) error {
|
|
return errors.New("smtp down")
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("dispatch: %v", err)
|
|
}
|
|
if response.FailedCount != 1 || response.SentCount != 0 {
|
|
t.Fatalf("expected 1 failed send, got %+v", response)
|
|
}
|
|
}
|