flamingo.me/flamingo-commerce/v3@v3.11.0/cart/interfaces/controller/cartcontroller.go (about)

     1  package controller
     2  
     3  import (
     4  	"context"
     5  	"encoding/gob"
     6  	"strconv"
     7  
     8  	"go.opencensus.io/trace"
     9  
    10  	"flamingo.me/flamingo-commerce/v3/cart/domain/decorator"
    11  	"flamingo.me/flamingo-commerce/v3/cart/domain/validation"
    12  
    13  	"flamingo.me/flamingo-commerce/v3/cart/application"
    14  	productDomain "flamingo.me/flamingo-commerce/v3/product/domain"
    15  	"flamingo.me/flamingo/v3/framework/flamingo"
    16  	"flamingo.me/flamingo/v3/framework/web"
    17  )
    18  
    19  type (
    20  	// CartViewData is used for cart views/templates
    21  	CartViewData struct {
    22  		DecoratedCart         decorator.DecoratedCart
    23  		CartValidationResult  validation.Result
    24  		AddToCartProductsData []productDomain.BasicProductData
    25  		CartRestrictionError  application.RestrictionError
    26  	}
    27  
    28  	// CartViewController for carts
    29  	CartViewController struct {
    30  		responder *web.Responder
    31  
    32  		applicationCartService         *application.CartService
    33  		applicationCartReceiverService *application.CartReceiverService
    34  		router                         *web.Router
    35  		logger                         flamingo.Logger
    36  
    37  		showEmptyCartPageIfNoItems bool
    38  		adjustItemsToRestrictedQty bool
    39  	}
    40  
    41  	// CartViewActionData for rendering results
    42  	CartViewActionData struct {
    43  		AddToCartProductsData []productDomain.BasicProductData
    44  	}
    45  )
    46  
    47  func init() {
    48  	gob.Register(CartViewActionData{})
    49  }
    50  
    51  // Inject dependencies
    52  func (cc *CartViewController) Inject(
    53  	responder *web.Responder,
    54  
    55  	applicationCartService *application.CartService,
    56  	applicationCartReceiverService *application.CartReceiverService,
    57  	router *web.Router,
    58  	logger flamingo.Logger,
    59  	config *struct {
    60  		ShowEmptyCartPageIfNoItems bool `inject:"config:commerce.cart.showEmptyCartPageIfNoItems,optional"`
    61  		AdjustItemsToRestrictedQty bool `inject:"config:commerce.cart.adjustItemsToRestrictedQty,optional"`
    62  	},
    63  ) {
    64  	cc.responder = responder
    65  	cc.applicationCartService = applicationCartService
    66  	cc.applicationCartReceiverService = applicationCartReceiverService
    67  	cc.router = router
    68  	cc.logger = logger.WithField(flamingo.LogKeyCategory, "cartcontroller").WithField(flamingo.LogKeyModule, "cart")
    69  
    70  	if config != nil {
    71  		cc.showEmptyCartPageIfNoItems = config.ShowEmptyCartPageIfNoItems
    72  		cc.adjustItemsToRestrictedQty = config.AdjustItemsToRestrictedQty
    73  	}
    74  }
    75  
    76  // ViewAction the DecoratedCart View ( / cart)
    77  func (cc *CartViewController) ViewAction(ctx context.Context, r *web.Request) web.Result {
    78  	ctx, span := trace.StartSpan(ctx, "cart/CartViewController/ViewAction")
    79  	defer span.End()
    80  
    81  	if cc.adjustItemsToRestrictedQty {
    82  		err := cc.adjustItemsToRestrictedQtyAndAddToSession(ctx, r)
    83  		if err != nil {
    84  			cc.logger.WithContext(ctx).Warn("cart.cartcontroller.viewaction: Error %v", err)
    85  		}
    86  	}
    87  
    88  	decoratedCart, err := cc.applicationCartReceiverService.ViewDecoratedCart(ctx, r.Session())
    89  	if err != nil {
    90  		cc.logger.WithContext(ctx).Warn("cart.cartcontroller.viewaction: Error %v", err)
    91  		return cc.responder.Render("checkout/carterror", nil).SetNoCache()
    92  	}
    93  
    94  	if cc.showEmptyCartPageIfNoItems && decoratedCart.Cart.ItemCount() == 0 {
    95  		return cc.responder.Render("checkout/emptycart", nil).SetNoCache()
    96  	}
    97  
    98  	cartViewData := CartViewData{
    99  		DecoratedCart:        *decoratedCart,
   100  		CartValidationResult: cc.applicationCartService.ValidateCart(ctx, r.Session(), decoratedCart),
   101  	}
   102  
   103  	flashes := r.Session().Flashes("cart.view.data")
   104  	if len(flashes) > 0 {
   105  		if cartViewActionData, ok := flashes[0].(CartViewActionData); ok {
   106  			cartViewData.AddToCartProductsData = cartViewActionData.AddToCartProductsData
   107  		}
   108  	}
   109  	restrictionFlashes := r.Session().Flashes("cart.view.error.restriction")
   110  	if len(restrictionFlashes) > 0 {
   111  		if restrictionError, ok := restrictionFlashes[0].(application.RestrictionError); ok {
   112  			cartViewData.CartRestrictionError = restrictionError
   113  		}
   114  	}
   115  
   116  	return cc.responder.Render("checkout/cart", cartViewData).SetNoCache()
   117  }
   118  
   119  // AddAndViewAction the DecoratedCart View ( / cart)
   120  func (cc *CartViewController) AddAndViewAction(ctx context.Context, r *web.Request) web.Result {
   121  	ctx, span := trace.StartSpan(ctx, "cart/CartViewController/AddAndViewAction")
   122  	defer span.End()
   123  
   124  	variantMarketplaceCode := r.Params["variantMarketplaceCode"]
   125  
   126  	qty, ok := r.Params["qty"]
   127  	if !ok {
   128  		qty = "1"
   129  	}
   130  
   131  	qtyInt, _ := strconv.Atoi(qty)
   132  	deliveryCode := r.Params["deliveryCode"]
   133  
   134  	addRequest := cc.applicationCartService.BuildAddRequest(ctx, r.Params["marketplaceCode"], variantMarketplaceCode, qtyInt, nil)
   135  
   136  	product, err := cc.applicationCartService.AddProduct(ctx, r.Session(), deliveryCode, addRequest)
   137  	if notAllowedErr, ok := err.(*validation.AddToCartNotAllowed); ok {
   138  		if notAllowedErr.RedirectHandlerName != "" {
   139  			return cc.responder.RouteRedirect(notAllowedErr.RedirectHandlerName, notAllowedErr.RedirectParams)
   140  		}
   141  	}
   142  	if err != nil {
   143  		cc.logger.WithContext(ctx).Warn("cart.cartcontroller.addandviewaction: Error %v", err)
   144  		return cc.responder.Render("checkout/carterror", nil)
   145  	}
   146  
   147  	r.Session().AddFlash(CartViewActionData{
   148  		AddToCartProductsData: []productDomain.BasicProductData{product.BaseData()},
   149  	}, "cart.view.data")
   150  	return cc.responder.RouteRedirect("cart.view", nil)
   151  }
   152  
   153  // UpdateQtyAndViewAction the DecoratedCart View ( / cart)
   154  func (cc *CartViewController) UpdateQtyAndViewAction(ctx context.Context, r *web.Request) web.Result {
   155  	ctx, span := trace.StartSpan(ctx, "cart/CartViewController/UpdateQtyAndViewAction")
   156  	defer span.End()
   157  
   158  	id, ok := r.Params["id"]
   159  	if !ok {
   160  		cc.logger.WithContext(ctx).Warn("cart.cartcontroller.UpdateQtyAndViewAction: param id not found")
   161  		return cc.responder.RouteRedirect("cart.view", nil)
   162  	}
   163  
   164  	qty, ok := r.Params["qty"]
   165  	if !ok {
   166  		qty = "1"
   167  	}
   168  
   169  	qtyInt, err := strconv.Atoi(qty)
   170  	if err != nil {
   171  		qtyInt = 1
   172  	}
   173  	deliveryCode := r.Params["deliveryCode"]
   174  
   175  	err = cc.applicationCartService.UpdateItemQty(ctx, r.Session(), id, deliveryCode, qtyInt)
   176  	if err != nil {
   177  		cc.logger.WithContext(ctx).Warn("cart.cartcontroller.UpdateAndViewAction: Error %v", err)
   178  
   179  		if e, ok := err.(*application.RestrictionError); ok {
   180  			r.Session().AddFlash(e, "cart.view.error.restriction")
   181  		}
   182  	}
   183  
   184  	return cc.responder.RouteRedirect("cart.view", nil)
   185  }
   186  
   187  // DeleteAndViewAction the DecoratedCart View ( / cart)
   188  func (cc *CartViewController) DeleteAndViewAction(ctx context.Context, r *web.Request) web.Result {
   189  	ctx, span := trace.StartSpan(ctx, "cart/CartViewController/DeleteAndViewAction")
   190  	defer span.End()
   191  
   192  	id, ok := r.Params["id"]
   193  	if !ok {
   194  		cc.logger.WithContext(ctx).Warn("cart.cartcontroller.deleteaction: param id not found")
   195  		return cc.responder.RouteRedirect("cart.view", nil)
   196  	}
   197  	deliveryCode := r.Params["deliveryCode"]
   198  	err := cc.applicationCartService.DeleteItem(ctx, r.Session(), id, deliveryCode)
   199  	if err != nil {
   200  		cc.logger.WithContext(ctx).Warn("cart.cartcontroller.deleteaction: Error %v", err)
   201  	}
   202  
   203  	return cc.responder.RouteRedirect("cart.view", nil)
   204  }
   205  
   206  // DeleteAllAndViewAction empties the cart and shows it
   207  func (cc *CartViewController) DeleteAllAndViewAction(ctx context.Context, r *web.Request) web.Result {
   208  	ctx, span := trace.StartSpan(ctx, "cart/CartViewController/DeleteAllAndViewAction")
   209  	defer span.End()
   210  
   211  	err := cc.applicationCartService.DeleteAllItems(ctx, r.Session())
   212  	if err != nil {
   213  		cc.logger.WithContext(ctx).Warn("cart.cartcontroller.deleteaction: Error %v", err)
   214  	}
   215  
   216  	return cc.responder.RouteRedirect("cart.view", nil)
   217  }
   218  
   219  // CleanAndViewAction empties the cart and shows it
   220  func (cc *CartViewController) CleanAndViewAction(ctx context.Context, r *web.Request) web.Result {
   221  	ctx, span := trace.StartSpan(ctx, "cart/CartViewController/CleanAndViewAction")
   222  	defer span.End()
   223  
   224  	err := cc.applicationCartService.Clean(ctx, r.Session())
   225  	if err != nil {
   226  		cc.logger.WithContext(ctx).Warn("cart.cartcontroller.deleteaction: Error %v", err)
   227  	}
   228  
   229  	return cc.responder.RouteRedirect("cart.view", nil)
   230  }
   231  
   232  // CleanDeliveryAndViewAction empties a single delivery and shows it
   233  func (cc *CartViewController) CleanDeliveryAndViewAction(ctx context.Context, r *web.Request) web.Result {
   234  	ctx, span := trace.StartSpan(ctx, "cart/CartViewController/CleanDeliveryAndViewAction")
   235  	defer span.End()
   236  
   237  	deliveryCode := r.Params["deliveryCode"]
   238  	_, err := cc.applicationCartService.DeleteDelivery(ctx, r.Session(), deliveryCode)
   239  	if err != nil {
   240  		cc.logger.WithContext(ctx).Warn("cart.cartcontroller.deleteaction: Error %v", err)
   241  	}
   242  
   243  	return cc.responder.RouteRedirect("cart.view", nil)
   244  }
   245  
   246  // adjustItemsToRestrictedQtyAndAddToSession checks the items of the cart against their qty restrictions and adds adjustments to the session
   247  func (cc *CartViewController) adjustItemsToRestrictedQtyAndAddToSession(ctx context.Context, r *web.Request) error {
   248  	ctx, span := trace.StartSpan(ctx, "cart/CartViewController/adjustItemsToRestrictedQtyAndAddToSession")
   249  	defer span.End()
   250  
   251  	adjustments, err := cc.applicationCartService.AdjustItemsToRestrictedQty(ctx, r.Session())
   252  	if err != nil {
   253  		return err
   254  	}
   255  
   256  	cc.addAdjustmentsToSession(adjustments, r)
   257  
   258  	return nil
   259  }
   260  
   261  func (cc *CartViewController) addAdjustmentsToSession(adjustments application.QtyAdjustmentResults, r *web.Request) {
   262  	var storedAdjustments application.QtyAdjustmentResults
   263  	var ok bool
   264  
   265  	if sessionStoredAdjustments, found := r.Session().Load("cart.view.quantity.adjustments"); found {
   266  		if storedAdjustments, ok = sessionStoredAdjustments.(application.QtyAdjustmentResults); !ok {
   267  			storedAdjustments = application.QtyAdjustmentResults{}
   268  		}
   269  	} else {
   270  		storedAdjustments = application.QtyAdjustmentResults{}
   271  	}
   272  
   273  	for _, adjustment := range adjustments {
   274  		if i, contains := cc.containsAdjustment(storedAdjustments, adjustment); contains {
   275  			storedAdjustments[i] = adjustment
   276  		} else {
   277  			storedAdjustments = append(storedAdjustments, adjustment)
   278  		}
   279  	}
   280  
   281  	r.Session().Store("cart.view.quantity.adjustments", storedAdjustments)
   282  }
   283  
   284  func (cc *CartViewController) containsAdjustment(adjustments application.QtyAdjustmentResults, adjustment application.QtyAdjustmentResult) (index int, contains bool) {
   285  	for i, a := range adjustments {
   286  		if a.OriginalItem.ID == adjustment.OriginalItem.ID && a.DeliveryCode == adjustment.DeliveryCode {
   287  			return i, true
   288  		}
   289  	}
   290  
   291  	return -1, false
   292  }