package cart import ( "context" "fmt" "log" "time" messages "git.k6n.net/mats/go-cart-actor/proto/cart" ) // mutation_remove_item.go // // Registers the RemoveItem mutation. // // Behavior: // - Removes the cart line whose local cart line Id == payload.Id // - Cascades: also removes any line whose ParentId points at a removed line // (transitively), so removing a parent removes its child sub-articles // - If no such line exists returns an error // - Releases reservations for every removed line and recalculates totals // // Notes: // - This removes only the line items; any deliveries referencing a removed // item are NOT automatically adjusted (mirrors prior logic). If future // semantics require pruning delivery.item_ids you can extend this handler. // - If multiple lines somehow shared the same Id (should not happen), all // matches are removed—data integrity relies on unique line Ids. func (c *CartMutationContext) RemoveItem(g *CartGrain, m *messages.RemoveItem) error { if m == nil { return fmt.Errorf("RemoveItem: nil payload") } targetID := uint32(m.Id) found := false for _, it := range g.Items { if it.Id == targetID { found = true break } } if !found { return fmt.Errorf("RemoveItem: item id %d not found", m.Id) } // Collect the target and, transitively, any children pointing at a removed // line. Loops until no further descendants are found (handles nesting). remove := map[uint32]bool{targetID: true} for { grew := false for _, it := range g.Items { if it.ParentId != nil && remove[*it.ParentId] && !remove[it.Id] { remove[it.Id] = true grew = true } } if !grew { break } } kept := g.Items[:0] for _, it := range g.Items { if remove[it.Id] { if it.ReservationEndTime != nil && it.ReservationEndTime.After(time.Now()) { if err := c.ReleaseItem(context.Background(), g.Id, it.Sku, it.StoreId); err != nil { log.Printf("unable to release reservation for item %d: %v", it.Id, err) } } continue } kept = append(kept, it) } g.Items = kept g.UpdateTotals() return nil }