package booking import ( "context" "errors" "fmt" "log/slog" "path" "time" "gorm.io/gorm" "github.com/rjNemo/rentease/internal/config" ) type Store interface { All() []*Line Search(value string) []*Line List(from, to time.Time) ([]*Line, error) CardTotal(from, to time.Time) (float64, error) Get(id int) (*Booking, error) Create(b *Booking) error Update(b *Booking) error Cancel(id int) error UpsertInvoiceDocument(doc *InvoiceDocument) error // Item methods CreateItem(i *Item) error PayItem(id int) (*Item, error) GetItem(id int) (*Item, error) UpdateItem(id int, item string, paymentMethod string, paymentStatus string, qty int, price float64) (*Item, error) } type PdfClient interface { BuildInvoice(invoice Invoice) (*GeneratedFile, error) BuildReport(report ReportData, period string, month, year int) (string, error) } type InvoiceStorage interface { StoreInvoice(ctx context.Context, objectKey string, file GeneratedFile, shareURLTTL time.Duration) (*StoredInvoiceFile, error) } type CalendarClient interface { Create(calendarID, name, description string, from, to time.Time) error } type parserClient interface { Parse(rawContent string) (*Booking, error) } type Service struct { store Store parser parserClient pdf PdfClient storage InvoiceStorage logger *slog.Logger invoiceShareURLTTL time.Duration } func NewService( logger *slog.Logger, store Store, parser parserClient, pdf PdfClient, storage InvoiceStorage, invoiceShareURLTTL time.Duration, ) (*Service, error) { svcLogger := logger if svcLogger == nil { svcLogger = slog.Default() } if invoiceShareURLTTL <= 0 { invoiceShareURLTTL = 7 * 24 * time.Hour } return &Service{ logger: svcLogger.With(slog.String("component", "booking_service")), store: store, parser: parser, pdf: pdf, storage: storage, invoiceShareURLTTL: invoiceShareURLTTL, }, nil } func (bs Service) All() []*Line { bs.logger.Info("fetching all bookings") return bs.store.All() } func (bs Service) Search(value string) []*Line { return bs.store.Search(value) } func (bs Service) Create(From time.Time, To time.Time, Name, PhoneNumber, Email, Platform string, CustomerNumber int, PlatformFees float64, externalID *string, ) *Booking { // TODO: return the error b := NewBooking(From, To, Name, PhoneNumber, Email, Platform, CustomerNumber, PlatformFees, externalID) err := bs.store.Create(b) if err != nil { bs.logger.Info("failed to create booking", slog.Any("err", err)) } return b } func (bs Service) One(id int) (*Booking, error) { b, err := bs.store.Get(id) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrBookingNotFound } return nil, err } return b, nil } // Update updates an existing booking with new data func (bs Service) Update(id int, From time.Time, To time.Time, Name string, PhoneNumber string, Email string, Platform string, CustomerNumber int, PlatformFees float64, externalID *string, ) *Booking { b := NewBooking(From, To, Name, PhoneNumber, Email, Platform, CustomerNumber, PlatformFees, externalID).WithID(id) if err := bs.store.Update(b); err != nil { bs.logger.Info("failed to create booking", slog.Any("err", err)) } return b } func (bs Service) Cancel(id int) { err := bs.store.Cancel(id) if err != nil { bs.logger.Info("failed to create booking", slog.Any("err", err)) } } func (bs Service) CreateInvoice(ctx context.Context, b *Booking, hc *config.Host) (*InvoiceDocument, error) { if bs.storage == nil { return nil, ErrInvoiceStorageNotConfigured } file, err := bs.pdf.BuildInvoice(b.ToInvoice(hc)) if err != nil { return nil, fmt.Errorf("build invoice pdf: %w", err) } objectKey := path.Join("invoices", fmt.Sprintf("%d", b.ID), file.Name) stored, err := bs.storage.StoreInvoice(ctx, objectKey, *file, bs.invoiceShareURLTTL) if err != nil { return nil, fmt.Errorf("store invoice pdf: %w", err) } doc := &InvoiceDocument{ BookingID: b.ID, ObjectKey: stored.ObjectKey, FileName: file.Name, ContentType: file.ContentType, ShareURL: stored.ShareURL, ShareURLExpiresAt: stored.ShareURLExpiresAt, } if err := bs.store.UpsertInvoiceDocument(doc); err != nil { return nil, fmt.Errorf("persist invoice document: %w", err) } return doc, nil }