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 }