mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-06-03 20:13:00 +00:00
201 lines
5.5 KiB
Go
201 lines
5.5 KiB
Go
package bookings
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"bookra/apps/backend/internal/db"
|
|
"bookra/apps/backend/internal/domain"
|
|
"bookra/apps/backend/internal/notifications"
|
|
)
|
|
|
|
var (
|
|
ErrInvalidReschedule = errors.New("invalid reschedule request")
|
|
ErrBookingCancelled = errors.New("booking already cancelled")
|
|
ErrUnauthorized = errors.New("unauthorized access")
|
|
)
|
|
|
|
type CustomerService struct {
|
|
repo db.Repository
|
|
notifier CustomerNotifier
|
|
}
|
|
|
|
type CustomerNotifier interface {
|
|
SendBookingReschedule(ctx context.Context, data notifications.BookingEmailData) error
|
|
SendBookingCancellation(ctx context.Context, data notifications.BookingEmailData) error
|
|
}
|
|
|
|
func NewCustomerService(repo db.Repository, notifier CustomerNotifier) *CustomerService {
|
|
if notifier == nil {
|
|
notifier = &customerNoopNotifier{}
|
|
}
|
|
return &CustomerService{repo: repo, notifier: notifier}
|
|
}
|
|
|
|
type customerNoopNotifier struct{}
|
|
|
|
func (n *customerNoopNotifier) SendBookingReschedule(ctx context.Context, data notifications.BookingEmailData) error {
|
|
return nil
|
|
}
|
|
|
|
func (n *customerNoopNotifier) SendBookingCancellation(ctx context.Context, data notifications.BookingEmailData) error {
|
|
return nil
|
|
}
|
|
|
|
// GetBookingByReference returns booking details for customer management link
|
|
func (s *CustomerService) GetBookingByReference(ctx context.Context, reference string, token string) (domain.CustomerBookingView, error) {
|
|
booking, err := s.repo.GetBookingByReference(ctx, reference)
|
|
if err != nil {
|
|
return domain.CustomerBookingView{}, ErrBookingNotFound
|
|
}
|
|
|
|
// Get tenant details for business name
|
|
tenant, err := s.repo.GetTenantByID(ctx, booking.TenantID)
|
|
if err != nil {
|
|
return domain.CustomerBookingView{}, err
|
|
}
|
|
|
|
return domain.CustomerBookingView{
|
|
Reference: booking.Reference,
|
|
CustomerName: booking.CustomerName,
|
|
CustomerEmail: booking.CustomerEmail,
|
|
Service: "Service", // Would get from service ID in full implementation
|
|
BusinessName: tenant.Name,
|
|
StartsAt: booking.StartsAt,
|
|
EndsAt: booking.EndsAt,
|
|
Location: "Location", // Would get from location ID in full implementation
|
|
Status: booking.Status,
|
|
}, nil
|
|
}
|
|
|
|
// RescheduleBooking allows customers to reschedule their booking
|
|
func (s *CustomerService) RescheduleBooking(ctx context.Context, reference string, req domain.RescheduleBookingRequest, token string) error {
|
|
booking, err := s.repo.GetBookingByReference(ctx, reference)
|
|
if err != nil {
|
|
return ErrBookingNotFound
|
|
}
|
|
|
|
if booking.Status == "cancelled" {
|
|
return ErrBookingCancelled
|
|
}
|
|
|
|
newStartsAt, err := time.Parse(time.RFC3339, req.NewStartsAt)
|
|
if err != nil {
|
|
return ErrInvalidReschedule
|
|
}
|
|
|
|
newEndsAt, err := time.Parse(time.RFC3339, req.NewEndsAt)
|
|
if err != nil {
|
|
return ErrInvalidReschedule
|
|
}
|
|
|
|
if !newEndsAt.After(newStartsAt) {
|
|
return ErrInvalidReschedule
|
|
}
|
|
|
|
if newStartsAt.Before(time.Now().UTC()) {
|
|
return ErrInvalidReschedule
|
|
}
|
|
|
|
if err := s.repo.RescheduleBooking(ctx, booking.ID, newStartsAt, newEndsAt); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Send reschedule email (async)
|
|
go s.sendRescheduleEmail(booking, newStartsAt, newEndsAt)
|
|
|
|
return nil
|
|
}
|
|
|
|
// CancelBooking allows customers to cancel their booking
|
|
func (s *CustomerService) CancelBooking(ctx context.Context, reference string, token string) error {
|
|
booking, err := s.repo.GetBookingByReference(ctx, reference)
|
|
if err != nil {
|
|
return ErrBookingNotFound
|
|
}
|
|
|
|
if booking.Status == "cancelled" {
|
|
return ErrBookingCancelled
|
|
}
|
|
|
|
if err := s.repo.UpdateBookingStatus(ctx, booking.ID, "cancelled"); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Send cancellation email (async)
|
|
go s.sendCancellationEmail(booking)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *CustomerService) sendRescheduleEmail(booking db.BookingRecord, newStartsAt, newEndsAt time.Time) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
tenant, err := s.repo.GetTenantByID(ctx, booking.TenantID)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
brand, _ := s.repo.GetBrandProfile(ctx, tenant.ID)
|
|
brandColor := brand.PrimaryColor
|
|
if brandColor == "" {
|
|
brandColor = "#a65c3e"
|
|
}
|
|
|
|
managementURL := "https://bookra.eu/manage/" + booking.Reference + "?token=" + booking.Reference
|
|
|
|
emailData := notifications.BookingEmailData{
|
|
TenantName: tenant.Name,
|
|
TenantSlug: tenant.Slug,
|
|
BrandColor: brandColor,
|
|
CustomerName: booking.CustomerName,
|
|
CustomerEmail: booking.CustomerEmail,
|
|
Service: "Service",
|
|
Location: "Location",
|
|
Reference: booking.Reference,
|
|
StartsAt: newStartsAt,
|
|
EndsAt: newEndsAt,
|
|
Timezone: tenant.Timezone,
|
|
Locale: tenant.Locale,
|
|
ManagementURL: managementURL,
|
|
}
|
|
|
|
s.notifier.SendBookingReschedule(ctx, emailData)
|
|
}
|
|
|
|
func (s *CustomerService) sendCancellationEmail(booking db.BookingRecord) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
tenant, err := s.repo.GetTenantByID(ctx, booking.TenantID)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
brand, _ := s.repo.GetBrandProfile(ctx, tenant.ID)
|
|
brandColor := brand.PrimaryColor
|
|
if brandColor == "" {
|
|
brandColor = "#a65c3e"
|
|
}
|
|
|
|
emailData := notifications.BookingEmailData{
|
|
TenantName: tenant.Name,
|
|
TenantSlug: tenant.Slug,
|
|
BrandColor: brandColor,
|
|
CustomerName: booking.CustomerName,
|
|
CustomerEmail: booking.CustomerEmail,
|
|
Service: "Service",
|
|
Location: "Location",
|
|
Reference: booking.Reference,
|
|
StartsAt: booking.StartsAt,
|
|
EndsAt: booking.EndsAt,
|
|
Timezone: tenant.Timezone,
|
|
Locale: tenant.Locale,
|
|
ManagementURL: "",
|
|
}
|
|
|
|
s.notifier.SendBookingCancellation(ctx, emailData)
|
|
}
|