diff --git a/internal/service/booking/sync.go b/internal/service/booking/sync.go index e99ca42..ba018b8 100644 --- a/internal/service/booking/sync.go +++ b/internal/service/booking/sync.go @@ -1,6 +1,9 @@ package booking import ( + "fmt" + "strings" + "github.com/rjNemo/rentease/internal/config" ) @@ -14,14 +17,47 @@ func (bs Service) ParseFromAPI(rawContent string) (*Booking, error) { b = bs.Create(b.From, b.To, b.Name, b.PhoneNumber, b.Email, string(b.Platform), b.CustomerNumber, b.PlatformFees, b.ExternalID) hostItems := config.NewHost().Items + createdItems := make([]Item, 0, len(items)) for _, itm := range items { - hostItem, ok := hostItems[itm.Item] - if !ok { + itemName := strings.TrimSpace(itm.Item) + hostItem, ok := findHostItem(hostItems, itemName) + if ok { + for _, created := range bs.CreateItem(b.ID, hostItem, itm.Quantity, itm.Price, itm.PaymentMethod, b.CustomerNumber, string(b.Platform)) { + createdItems = append(createdItems, *created) + } continue } - bs.CreateItem(b.ID, hostItem, itm.Quantity, itm.Price, itm.PaymentMethod, b.CustomerNumber, string(b.Platform)) + fallbackItem := &Item{ + BookingID: b.ID, + Item: itemName, + Quantity: itm.Quantity, + Price: itm.Price, + PaymentMethod: itm.PaymentMethod, + PaymentStatus: "Pending", + } + + if err := bs.store.CreateItem(fallbackItem); err != nil { + return nil, fmt.Errorf("failed to create item %q for booking %d: %w", itemName, b.ID, err) + } + createdItems = append(createdItems, *fallbackItem) } + b.Items = createdItems + return b, nil } + +func findHostItem(hostItems map[string]config.HostItem, itemName string) (config.HostItem, bool) { + if hostItem, ok := hostItems[itemName]; ok { + return hostItem, true + } + + for key, hostItem := range hostItems { + if strings.EqualFold(key, itemName) { + return hostItem, true + } + } + + return config.HostItem{}, false +} diff --git a/internal/service/booking/sync_test.go b/internal/service/booking/sync_test.go new file mode 100644 index 0000000..2f93c34 --- /dev/null +++ b/internal/service/booking/sync_test.go @@ -0,0 +1,183 @@ +package booking + +import ( + "errors" + "log/slog" + "testing" + "time" + + "github.com/rjNemo/rentease/internal/config" +) + +func TestParseFromAPI_CreatesItemsForBookingSync(t *testing.T) { + store := newStubStore() + parser := stubParser{ + booking: Booking{ + From: time.Date(2025, time.January, 1, 0, 0, 0, 0, time.UTC), + To: time.Date(2025, time.January, 4, 0, 0, 0, 0, time.UTC), + Name: "Jane Doe", + PhoneNumber: "123456", + Email: "jane@example.com", + Platform: config.Platform("Booking"), + CustomerNumber: 2, + PlatformFees: 15.0, + Items: []Item{{ + Item: " T3 ", + Quantity: 3, + Price: 80.0, + PaymentMethod: "Card", + }}, + }, + } + + svc, err := NewService(slog.Default(), store, parser, noopPDF{}) + if err != nil { + t.Fatalf("unexpected error creating service: %v", err) + } + + booking, err := svc.ParseFromAPI("raw booking content") + if err != nil { + t.Fatalf("unexpected error parsing booking: %v", err) + } + + if len(store.items) != 2 { + t.Fatalf("expected 2 items to be created (booking item + taxes), got %d", len(store.items)) + } + + if store.items[0].Item != "T3" { + t.Fatalf("expected base item to be trimmed to host item name, got %q", store.items[0].Item) + } + + if store.items[1].Item != "Taxes" { + t.Fatalf("expected taxes item to be created, got %q", store.items[1].Item) + } + + if len(booking.Items) != 2 { + t.Fatalf("expected booking to include created items, got %d", len(booking.Items)) + } +} + +func TestParseFromAPI_CreatesFallbackItemWhenUnknown(t *testing.T) { + store := newStubStore() + parser := stubParser{ + booking: Booking{ + From: time.Date(2025, time.February, 1, 0, 0, 0, 0, time.UTC), + To: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC), + Name: "Jane Doe", + PhoneNumber: "123456", + Email: "jane@example.com", + Platform: config.Platform("Booking"), + Items: []Item{{ + Item: "New Suite", + Quantity: 1, + Price: 120.0, + PaymentMethod: "Card", + }}, + }, + } + + svc, err := NewService(slog.Default(), store, parser, noopPDF{}) + if err != nil { + t.Fatalf("unexpected error creating service: %v", err) + } + + if _, err := svc.ParseFromAPI("raw booking content"); err != nil { + t.Fatalf("unexpected error parsing booking: %v", err) + } + + if len(store.items) != 1 { + t.Fatalf("expected fallback item to be created, got %d items", len(store.items)) + } + + if store.items[0].Item != "New Suite" { + t.Fatalf("expected fallback item name to match parsed value, got %q", store.items[0].Item) + } +} + +type stubParser struct { + booking Booking + err error +} + +func (p stubParser) Parse(rawContent string) (*Booking, error) { + if p.err != nil { + return nil, p.err + } + + cp := p.booking + return &cp, nil +} + +type stubStore struct { + bookings []*Booking + items []*Item +} + +func newStubStore() *stubStore { + return &stubStore{ + bookings: make([]*Booking, 0), + items: make([]*Item, 0), + } +} + +func (s *stubStore) All() []*Line { + return nil +} + +func (s *stubStore) Search(value string) []*Line { + return nil +} + +func (s *stubStore) List(from, to time.Time) ([]*Line, error) { + return nil, nil +} + +func (s *stubStore) CardTotal(from, to time.Time) (float64, error) { + return 0, nil +} + +func (s *stubStore) Get(id int) (*Booking, error) { + return nil, errors.New("not implemented") +} + +func (s *stubStore) Create(b *Booking) error { + b.ID = len(s.bookings) + 1 + s.bookings = append(s.bookings, b) + return nil +} + +func (s *stubStore) Update(b *Booking) error { + return nil +} + +func (s *stubStore) Cancel(id int) error { + return nil +} + +func (s *stubStore) CreateItem(i *Item) error { + i.ID = len(s.items) + 1 + s.items = append(s.items, i) + return nil +} + +func (s *stubStore) PayItem(id int) (*Item, error) { + return nil, nil +} + +func (s *stubStore) GetItem(id int) (*Item, error) { + return nil, nil +} + +func (s *stubStore) UpdateItem(id int, item string, paymentMethod string, paymentStatus string, qty int, price float64) (*Item, error) { + return nil, nil +} + +type noopPDF struct{} + +func (noopPDF) BuildInvoice(invoice Invoice) (string, error) { + return "", nil +} + +func (noopPDF) BuildReport(report ReportData, period string, month, year int) (string, error) { + return "", nil +}