mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-06-03 20:13:00 +00:00
cleanup
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user