flamingo.me/flamingo-commerce/v3@v3.11.0/cart/domain/decorator/cartDecorator.go (about)

     1  package decorator
     2  
     3  import (
     4  	"context"
     5  	"sort"
     6  
     7  	"go.opencensus.io/trace"
     8  
     9  	cartDomain "flamingo.me/flamingo-commerce/v3/cart/domain/cart"
    10  
    11  	"flamingo.me/flamingo/v3/framework/flamingo"
    12  
    13  	priceDomain "flamingo.me/flamingo-commerce/v3/price/domain"
    14  	"flamingo.me/flamingo-commerce/v3/product/domain"
    15  )
    16  
    17  type (
    18  	// DecoratedCartFactory - Factory to be injected: If you need to create a new Decorator then get the factory injected and use the factory
    19  	DecoratedCartFactory struct {
    20  		productService domain.ProductService
    21  		logger         flamingo.Logger
    22  	}
    23  
    24  	// DecoratedCart Decorates Access To a Cart
    25  	DecoratedCart struct {
    26  		Cart                cartDomain.Cart
    27  		DecoratedDeliveries []DecoratedDelivery
    28  		Ctx                 context.Context `json:"-"`
    29  		Logger              flamingo.Logger `json:"-"`
    30  	}
    31  
    32  	// DecoratedDelivery Decorates a CartItem with its Product
    33  	DecoratedDelivery struct {
    34  		Delivery       cartDomain.Delivery
    35  		DecoratedItems []DecoratedCartItem
    36  		logger         flamingo.Logger
    37  	}
    38  
    39  	// DecoratedCartItem Decorates a CartItem with its Product
    40  	DecoratedCartItem struct {
    41  		Item    cartDomain.Item
    42  		Product domain.BasicProduct
    43  		logger  flamingo.Logger
    44  	}
    45  
    46  	// GroupedDecoratedCartItem - value object used for grouping (generated on the fly)
    47  	GroupedDecoratedCartItem struct {
    48  		DecoratedItems []DecoratedCartItem
    49  		Group          string
    50  	}
    51  
    52  	cartCtxKey     struct{}
    53  	deliveryCtxKey struct{}
    54  )
    55  
    56  // Inject dependencies
    57  func (df *DecoratedCartFactory) Inject(
    58  	productService domain.ProductService,
    59  	logger flamingo.Logger,
    60  ) {
    61  	df.productService = productService
    62  	df.logger = logger
    63  }
    64  
    65  // Create Factory method to get Decorated Cart
    66  func (df *DecoratedCartFactory) Create(ctx context.Context, cart cartDomain.Cart) *DecoratedCart {
    67  	ctx, span := trace.StartSpan(ctx, "cart/DecoratedCartFactory/Create")
    68  	defer span.End()
    69  
    70  	decoratedCart := DecoratedCart{Cart: cart, Logger: df.logger}
    71  
    72  	contextWithCart := context.WithValue(ctx, cartCtxKey{}, cart)
    73  
    74  	for _, d := range cart.Deliveries {
    75  		contextWithDeliveryCode := ContextWithDeliveryCode(contextWithCart, d.DeliveryInfo.Code)
    76  
    77  		decoratedCart.DecoratedDeliveries = append(decoratedCart.DecoratedDeliveries, DecoratedDelivery{
    78  			Delivery:       d,
    79  			DecoratedItems: df.CreateDecorateCartItems(contextWithDeliveryCode, d.Cartitems),
    80  			logger:         df.logger,
    81  		})
    82  	}
    83  
    84  	decoratedCart.Ctx = ctx
    85  
    86  	return &decoratedCart
    87  }
    88  
    89  // CreateDecorateCartItems Factory method to get Decorated Cart
    90  func (df *DecoratedCartFactory) CreateDecorateCartItems(ctx context.Context, items []cartDomain.Item) []DecoratedCartItem {
    91  	ctx, span := trace.StartSpan(ctx, "cart/DecoratedCartFactory/CreateDecorateCartItems")
    92  	defer span.End()
    93  
    94  	var decoratedItems []DecoratedCartItem
    95  
    96  	for _, cartItem := range items {
    97  		decoratedItem := df.decorateCartItem(ctx, cartItem)
    98  		decoratedItems = append(decoratedItems, decoratedItem)
    99  	}
   100  
   101  	return decoratedItems
   102  }
   103  
   104  // decorateCartItem factory method
   105  func (df *DecoratedCartFactory) decorateCartItem(ctx context.Context, cartItem cartDomain.Item) DecoratedCartItem {
   106  	ctx, span := trace.StartSpan(ctx, "cart/DecoratedCartFactory/decorateCartItem")
   107  	defer span.End()
   108  
   109  	decoratedItem := DecoratedCartItem{Item: cartItem, logger: df.logger}
   110  
   111  	product, err := df.productService.Get(ctx, cartItem.MarketplaceCode)
   112  	if err != nil {
   113  		df.logger.WithContext(ctx).Debug("cart.decorator - no product for item: ", err)
   114  
   115  		if product == nil {
   116  			// To avoid errors if consumers want to access the product data
   117  			product = domain.SimpleProduct{
   118  				BasicProductData: domain.BasicProductData{
   119  					Title: cartItem.ProductName + "[outdated]",
   120  				},
   121  			}
   122  
   123  			decoratedItem.Product = product
   124  		}
   125  
   126  		return decoratedItem
   127  	}
   128  
   129  	if product.Type() == domain.TypeConfigurable {
   130  		if configurable, ok := product.(domain.ConfigurableProduct); ok {
   131  			configurableWithVariant, err := configurable.GetConfigurableWithActiveVariant(cartItem.VariantMarketPlaceCode)
   132  			if err != nil {
   133  				product = domain.SimpleProduct{
   134  					BasicProductData: domain.BasicProductData{
   135  						Title: cartItem.ProductName + "[outdated]",
   136  					},
   137  				}
   138  			} else {
   139  				product = configurableWithVariant
   140  			}
   141  		}
   142  	}
   143  
   144  	if product.Type() == domain.TypeBundle {
   145  		if bundle, ok := product.(domain.BundleProduct); ok {
   146  			bundleWithActiveChoices, err := bundle.GetBundleProductWithActiveChoices(cartItem.BundleConfig)
   147  			if err != nil {
   148  				product = domain.SimpleProduct{
   149  					BasicProductData: domain.BasicProductData{
   150  						Title: cartItem.ProductName + "[outdated]",
   151  					},
   152  				}
   153  			} else {
   154  				product = bundleWithActiveChoices
   155  			}
   156  		}
   157  	}
   158  
   159  	decoratedItem.Product = product
   160  
   161  	return decoratedItem
   162  }
   163  
   164  // IsConfigurable - checks if current CartItem is a Configurable Product
   165  func (dci DecoratedCartItem) IsConfigurable() bool {
   166  	if dci.Product == nil {
   167  		return false
   168  	}
   169  	return dci.Product.Type() == domain.TypeConfigurableWithActiveVariant
   170  }
   171  
   172  // GetVariant getter
   173  func (dci DecoratedCartItem) GetVariant() (*domain.Variant, error) {
   174  	return dci.Product.(domain.ConfigurableProductWithActiveVariant).Variant(dci.Item.VariantMarketPlaceCode)
   175  }
   176  
   177  // GetDisplayTitle getter
   178  func (dci DecoratedCartItem) GetDisplayTitle() string {
   179  	if dci.IsConfigurable() {
   180  		variant, e := dci.GetVariant()
   181  		if e != nil {
   182  			return "Error Getting Variant"
   183  		}
   184  		return variant.Title
   185  	}
   186  	return dci.Product.BaseData().Title
   187  }
   188  
   189  // GetDisplayMarketplaceCode getter
   190  func (dci DecoratedCartItem) GetDisplayMarketplaceCode() string {
   191  	if dci.IsConfigurable() {
   192  		variant, e := dci.GetVariant()
   193  		if e != nil {
   194  			return "Error Getting Variant"
   195  		}
   196  		return variant.MarketPlaceCode
   197  	}
   198  	return dci.Product.BaseData().MarketPlaceCode
   199  }
   200  
   201  // GetVariantsVariationAttributes getter
   202  func (dci DecoratedCartItem) GetVariantsVariationAttributes() domain.Attributes {
   203  	attributes := domain.Attributes{}
   204  	if dci.IsConfigurable() {
   205  		variant, _ := dci.GetVariant()
   206  
   207  		for _, attributeName := range dci.Product.(domain.ConfigurableProductWithActiveVariant).VariantVariationAttributes {
   208  			attributes[attributeName] = variant.BaseData().Attributes[attributeName]
   209  		}
   210  	}
   211  	return attributes
   212  }
   213  
   214  // GetVariantsVariationAttributeCodes getter
   215  func (dci DecoratedCartItem) GetVariantsVariationAttributeCodes() []string {
   216  	if dci.Product.Type() == domain.TypeConfigurableWithActiveVariant {
   217  		return dci.Product.(domain.ConfigurableProductWithActiveVariant).VariantVariationAttributes
   218  	}
   219  	return nil
   220  }
   221  
   222  // GetChargesToPay getter
   223  func (dci DecoratedCartItem) GetChargesToPay(wishedToPaySum *domain.WishedToPay) priceDomain.Charges {
   224  	priceToPayForItem := dci.Item.RowPriceGrossWithDiscount
   225  	return dci.Product.SaleableData().GetLoyaltyChargeSplit(&priceToPayForItem, wishedToPaySum, dci.Item.Qty)
   226  }
   227  
   228  // GetGroupedBy legacy function
   229  // deprecated: only here to support the old structure of accesing DecoratedItems in the Decorated Cart
   230  // Use instead:
   231  //
   232  //	or iterate over DecoratedCart.DecoratedDelivery
   233  func (dc DecoratedCart) GetGroupedBy(group string, sortGroup bool, params ...string) []*GroupedDecoratedCartItem {
   234  
   235  	if dc.Logger != nil {
   236  		dc.Logger.Warn("DEPRECATED: DecoratedCart.GetGroupedBy()")
   237  	}
   238  	if len(dc.DecoratedDeliveries) != 1 {
   239  		return nil
   240  	}
   241  	return dc.DecoratedDeliveries[0].GetGroupedBy(group, sortGroup, params...)
   242  }
   243  
   244  // GetAllDecoratedItems getter
   245  func (dc DecoratedCart) GetAllDecoratedItems() []DecoratedCartItem {
   246  	var allItems []DecoratedCartItem
   247  	for _, dd := range dc.DecoratedDeliveries {
   248  		allItems = append(allItems, dd.DecoratedItems...)
   249  	}
   250  	return allItems
   251  }
   252  
   253  // GetDecoratedDeliveryByCode getter
   254  func (dc DecoratedCart) GetDecoratedDeliveryByCode(deliveryCode string) (*DecoratedDelivery, bool) {
   255  	for _, dd := range dc.DecoratedDeliveries {
   256  		if dd.Delivery.DeliveryInfo.Code == deliveryCode {
   257  			return &dd, true
   258  		}
   259  
   260  	}
   261  	return nil, false
   262  }
   263  
   264  // GetDecoratedDeliveryByCodeWithoutBool - used inside a template, therefor we need the method with a single return param
   265  func (dc DecoratedCart) GetDecoratedDeliveryByCodeWithoutBool(deliveryCode string) *DecoratedDelivery {
   266  	decoratedDelivery, _ := dc.GetDecoratedDeliveryByCode(deliveryCode)
   267  	return decoratedDelivery
   268  }
   269  
   270  // GetGroupedBy getter
   271  func (dc DecoratedDelivery) GetGroupedBy(group string, sortGroup bool, params ...string) []*GroupedDecoratedCartItem {
   272  	groupedItemsCollection := make(map[string]*GroupedDecoratedCartItem)
   273  	var groupedItemsCollectionKeys []string
   274  
   275  	var groupKey string
   276  	for _, item := range dc.DecoratedItems {
   277  		switch group {
   278  		case "retailer_code":
   279  			groupKey = item.Product.BaseData().RetailerCode
   280  		default:
   281  			groupKey = "default"
   282  		}
   283  		if _, ok := groupedItemsCollection[groupKey]; !ok {
   284  			groupedItemsCollection[groupKey] = &GroupedDecoratedCartItem{
   285  				Group: groupKey,
   286  			}
   287  			groupedItemsCollectionKeys = append(groupedItemsCollectionKeys, groupKey)
   288  		}
   289  
   290  		if groupedItemsEntry, ok := groupedItemsCollection[groupKey]; ok {
   291  			groupedItemsEntry.DecoratedItems = append(groupedItemsEntry.DecoratedItems, item)
   292  		}
   293  	}
   294  
   295  	// sort before return
   296  	if sortGroup {
   297  		direction := ""
   298  		if len(params) > 0 {
   299  			direction = params[0]
   300  		}
   301  
   302  		if direction == "DESC" {
   303  			sort.Sort(sort.Reverse(sort.StringSlice(groupedItemsCollectionKeys)))
   304  		} else {
   305  			sort.Strings(groupedItemsCollectionKeys)
   306  		}
   307  	}
   308  
   309  	var groupedItemsCollectionSorted []*GroupedDecoratedCartItem
   310  	for _, keyName := range groupedItemsCollectionKeys {
   311  		if groupedItemsEntry, ok := groupedItemsCollection[keyName]; ok {
   312  			groupedItemsCollectionSorted = append(groupedItemsCollectionSorted, groupedItemsEntry)
   313  		}
   314  	}
   315  	return groupedItemsCollectionSorted
   316  }
   317  
   318  // GetDecoratedCartItemByID getter
   319  func (dc DecoratedDelivery) GetDecoratedCartItemByID(ID string) *DecoratedCartItem {
   320  	for _, decoratedItem := range dc.DecoratedItems {
   321  		if decoratedItem.Item.ID == ID {
   322  			return &decoratedItem
   323  		}
   324  	}
   325  	return nil
   326  }
   327  
   328  func CartFromDecoratedCartFactoryContext(ctx context.Context) *cartDomain.Cart {
   329  	ctx, span := trace.StartSpan(ctx, "cart/CartFromDecoratedCartFactoryContext")
   330  	defer span.End()
   331  
   332  	if cart, ok := ctx.Value(cartCtxKey{}).(cartDomain.Cart); ok {
   333  		return &cart
   334  	}
   335  
   336  	return nil
   337  }
   338  
   339  func DeliveryCodeFromDecoratedCartFactoryContext(ctx context.Context) string {
   340  	ctx, span := trace.StartSpan(ctx, "cart/DeliveryCodeFromDecoratedCartFactoryContext")
   341  	defer span.End()
   342  
   343  	if deliveryCode, ok := ctx.Value(deliveryCtxKey{}).(string); ok {
   344  		return deliveryCode
   345  	}
   346  
   347  	return ""
   348  }
   349  
   350  func ContextWithDeliveryCode(ctx context.Context, deliveryCode string) context.Context {
   351  	if _, ok := ctx.Value(deliveryCtxKey{}).(string); !ok {
   352  		return context.WithValue(ctx, deliveryCtxKey{}, deliveryCode)
   353  	}
   354  
   355  	return ctx
   356  }