mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-10-28 15:51:15 +00:00
feat(activity): per-tenant audit feed in notification dropdown
activity_events records booking lifecycle transitions with the actor behind each change — staff (dashboard), customer (self-service), or system (auto-promotion). The notification dropdown leads with the real feed instead of derived recent-booking guesses. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
e5cb9bd543
commit
d86cba61e3
18 files changed
+634
-28
No files matched your search
@@ -1365,6 +1365,16 @@ func (s *Service) CancelBooking(ctx context.Context, principal domain.Principal,
|
||||
return err
|
||||
}
|
||||
_ = s.repo.CancelPendingReminderJobsForBooking(ctx, booking.ID)
|
||||
_ = s.repo.AppendActivityEvent(ctx, db.ActivityEventParams{
|
||||
TenantID: membership.Tenant.ID,
|
||||
ActorKind: "staff",
|
||||
ActorSubject: principal.Subject,
|
||||
ActorName: actorDisplayName(principal),
|
||||
Action: "booking.cancelled",
|
||||
EntityType: "booking",
|
||||
EntityID: &booking.ID,
|
||||
Summary: booking.CustomerName + " — " + s.bookingLabel(ctx, booking),
|
||||
})
|
||||
|
||||
go s.sendBookingEmail(booking, false)
|
||||
|
||||
@@ -1395,10 +1405,46 @@ func (s *Service) PromoteBooking(ctx context.Context, principal domain.Principal
|
||||
}
|
||||
return err
|
||||
}
|
||||
_ = s.repo.AppendActivityEvent(ctx, db.ActivityEventParams{
|
||||
TenantID: membership.Tenant.ID,
|
||||
ActorKind: "staff",
|
||||
ActorSubject: principal.Subject,
|
||||
ActorName: actorDisplayName(principal),
|
||||
Action: "booking.promoted",
|
||||
EntityType: "booking",
|
||||
EntityID: &promoted.ID,
|
||||
Summary: promoted.CustomerName + " — " + s.bookingLabel(ctx, promoted),
|
||||
})
|
||||
go s.sendBookingEmail(promoted, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
// bookingLabel resolves the display label for a booking — service name for
|
||||
// appointments, session title for classes.
|
||||
func (s *Service) bookingLabel(ctx context.Context, booking db.BookingRecord) string {
|
||||
if booking.ServiceID != nil {
|
||||
if service, err := s.repo.GetServiceByID(ctx, *booking.ServiceID); err == nil {
|
||||
return service.Name
|
||||
}
|
||||
}
|
||||
if booking.ClassSessionID != nil {
|
||||
if session, err := s.repo.GetClassSessionByID(ctx, *booking.ClassSessionID); err == nil {
|
||||
return session.Title
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func actorDisplayName(principal domain.Principal) string {
|
||||
if principal.Name != "" {
|
||||
return principal.Name
|
||||
}
|
||||
if principal.Email != "" {
|
||||
return principal.Email
|
||||
}
|
||||
return principal.Subject
|
||||
}
|
||||
|
||||
// sendBookingEmail builds the shared payload and fires a confirmation
|
||||
// (promoted=true) or cancellation notification. Failures are logged-and-done:
|
||||
// email delivery must not fail the cancellation itself.
|
||||
@@ -1419,17 +1465,8 @@ func (s *Service) sendBookingEmail(booking db.BookingRecord, promoted bool) {
|
||||
brandColor = "#a65c3e"
|
||||
}
|
||||
|
||||
var serviceName, locationName string
|
||||
if booking.ServiceID != nil {
|
||||
if service, err := s.repo.GetServiceByID(ctx, *booking.ServiceID); err == nil {
|
||||
serviceName = service.Name
|
||||
}
|
||||
}
|
||||
if booking.ClassSessionID != nil {
|
||||
if session, err := s.repo.GetClassSessionByID(ctx, *booking.ClassSessionID); err == nil {
|
||||
serviceName = session.Title
|
||||
}
|
||||
}
|
||||
var locationName string
|
||||
serviceName := s.bookingLabel(ctx, booking)
|
||||
if booking.LocationID != nil {
|
||||
if location, err := s.repo.GetLocationByID(ctx, *booking.LocationID); err == nil {
|
||||
locationName = location.Name
|
||||
|
||||
Reference in new issue
Block a user