mirror of
https://github.com/rjNemo/rentease.git
synced 2026-06-06 02:36:49 +00:00
Some checks failed
CI / checks (push) Has been cancelled
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.
104 lines
2.7 KiB
Go
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
|
|
}
|