rentease/internal/service/booking/sync.go
Ruidy be5b707fa0
Some checks failed
CI / checks (push) Has been cancelled
feat(booking): improve item sync and add tests (#51)
Enhance the booking sync logic to trim and match item names more
robustly,
falling back to creating a generic item when no host item is found. Add
unit tests for item creation and fallback behavior in booking sync.
2025-12-01 15:06:22 +01:00

104 lines
2.7 KiB
Go

package booking
import (
"fmt"
"strings"
"github.com/rjNemo/rentease/internal/config"
)
func (bs Service) ParseFromAPI(rawContent string) (*Booking, error) {
b, err := bs.parser.Parse(rawContent)
if err != nil {
return nil, err
}
items := b.Items
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 {
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
}
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 itemName == "" {
return config.HostItem{}, false
}
// Exact match on key
if hostItem, ok := hostItems[itemName]; ok {
return hostItem, true
}
// Case-insensitive match on key
for key, hostItem := range hostItems {
if strings.EqualFold(key, itemName) {
return hostItem, true
}
}
// Token match (handles verbose names containing the configured item, e.g., "One-Bedroom House (T2 - ...)")
tokens := tokenizeItemName(itemName)
for key, hostItem := range hostItems {
lKey := strings.ToLower(key)
if tokens[lKey] || tokens[strings.ToLower(hostItem.Name)] {
return hostItem, true
}
}
// Substring match as last resort (avoids dropping partially matching items)
lower := strings.ToLower(itemName)
for key, hostItem := range hostItems {
lKey := strings.ToLower(key)
lName := strings.ToLower(hostItem.Name)
if strings.Contains(lower, lKey) || strings.Contains(lower, lName) {
return hostItem, true
}
}
return config.HostItem{}, false
}
func tokenizeItemName(itemName string) map[string]bool {
tokens := strings.FieldsFunc(itemName, func(r rune) bool {
return r == ' ' || r == '-' || r == '_' || r == '(' || r == ')' || r == ',' || r == '/' || r == '.'
})
result := make(map[string]bool, len(tokens))
for _, t := range tokens {
if t == "" {
continue
}
result[strings.ToLower(t)] = true
}
return result
}