Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VCST-2303: add changeCartCurrency mutation #24

Merged
merged 12 commits into from
Dec 20, 2024
94 changes: 54 additions & 40 deletions src/VirtoCommerce.XCart.Core/CartAggregate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,73 +180,84 @@ public virtual async Task<CartAggregate> AddConfiguredItemAsync(NewCartItem newC

EnsureCartExists();

if (newCartItem.CartProduct != null)
if (newCartItem.CartProduct == null)
{
CartProducts[newCartItem.CartProduct.Id] = newCartItem.CartProduct;
return this;
}

newConfiguredItem.Id = null;
newConfiguredItem.SelectedForCheckout = IsSelectedForCheckout;
newConfiguredItem.Quantity = newCartItem.Quantity;
newConfiguredItem.Note = newCartItem.Comment;
CartProducts[newCartItem.CartProduct.Id] = newCartItem.CartProduct;

Cart.Items.Add(newConfiguredItem);
newConfiguredItem.Id = null;
newConfiguredItem.SelectedForCheckout = IsSelectedForCheckout;
newConfiguredItem.Quantity = newCartItem.Quantity;
newConfiguredItem.Note = newCartItem.Comment;

if (newCartItem.DynamicProperties != null)
{
await UpdateCartItemDynamicProperties(newConfiguredItem, newCartItem.DynamicProperties);
}
Cart.Items.Add(newConfiguredItem);

await SetItemFulfillmentCenterAsync(newConfiguredItem, newCartItem.CartProduct);
await UpdateVendor(newConfiguredItem, newCartItem.CartProduct);
if (newCartItem.DynamicProperties != null)
{
await UpdateCartItemDynamicProperties(newConfiguredItem, newCartItem.DynamicProperties);
}

await SetItemFulfillmentCenterAsync(newConfiguredItem, newCartItem.CartProduct);
await UpdateVendor(newConfiguredItem, newCartItem.CartProduct);

return this;
}

public virtual async Task<CartAggregate> AddItemAsync(NewCartItem newCartItem)
{
EnsureCartExists();

ArgumentNullException.ThrowIfNull(newCartItem);

EnsureCartExists();

var validationResult = await AbstractTypeFactory<NewCartItemValidator>.TryCreateInstance().ValidateAsync(newCartItem, options => options.IncludeRuleSets(ValidationRuleSet));
if (!validationResult.IsValid)
{
OperationValidationErrors.AddRange(validationResult.Errors);
}
else if (newCartItem.CartProduct != null)
{
if (newCartItem.IsWishlist && newCartItem.CartProduct.Price == null)

if (!newCartItem.IgnoreValidationErrors)
{
newCartItem.CartProduct.Price = new ProductPrice(Currency);
return this;
}
}

if (newCartItem.CartProduct == null)
{
return this;
}

var lineItem = _mapper.Map<LineItem>(newCartItem.CartProduct);
if (newCartItem.IsWishlist && newCartItem.CartProduct.Price == null)
{
newCartItem.CartProduct.Price = new ProductPrice(Currency);
}

lineItem.SelectedForCheckout = IsSelectedForCheckout;
lineItem.Quantity = newCartItem.Quantity;
var lineItem = _mapper.Map<LineItem>(newCartItem.CartProduct);

if (newCartItem.Price != null)
{
lineItem.ListPrice = newCartItem.Price.Value;
lineItem.SalePrice = newCartItem.Price.Value;
}
else
{
SetLineItemTierPrice(newCartItem.CartProduct.Price, newCartItem.Quantity, lineItem);
}
lineItem.Currency ??= Currency.Code;
lineItem.SelectedForCheckout = newCartItem.IsSelectedForCheckout ?? IsSelectedForCheckout;
lineItem.Quantity = newCartItem.Quantity;

if (!string.IsNullOrEmpty(newCartItem.Comment))
{
lineItem.Note = newCartItem.Comment;
}
if (newCartItem.Price != null)
{
lineItem.ListPrice = newCartItem.Price.Value;
lineItem.SalePrice = newCartItem.Price.Value;
}
else
{
SetLineItemTierPrice(newCartItem.CartProduct.Price, newCartItem.Quantity, lineItem);
}

CartProducts[newCartItem.CartProduct.Id] = newCartItem.CartProduct;
await SetItemFulfillmentCenterAsync(lineItem, newCartItem.CartProduct);
await UpdateVendor(lineItem, newCartItem.CartProduct);
await InnerAddLineItemAsync(lineItem, newCartItem.CartProduct, newCartItem.DynamicProperties);
if (!string.IsNullOrEmpty(newCartItem.Comment))
{
lineItem.Note = newCartItem.Comment;
}

CartProducts[newCartItem.CartProduct.Id] = newCartItem.CartProduct;
await SetItemFulfillmentCenterAsync(lineItem, newCartItem.CartProduct);
await UpdateVendor(lineItem, newCartItem.CartProduct);
await InnerAddLineItemAsync(lineItem, newCartItem.CartProduct, newCartItem.DynamicProperties);

return this;
}

Expand All @@ -270,7 +281,9 @@ await AddItemAsync(new NewCartItem(item.ProductId, item.Quantity)
DynamicProperties = item.DynamicProperties,
Price = item.Price,
IsWishlist = item.IsWishlist,
IsSelectedForCheckout = item.IsSelectedForCheckout,
CartProduct = product,
IgnoreValidationErrors = item.IgnoreValidationErrors,
});
}
else
Expand Down Expand Up @@ -1150,6 +1163,7 @@ public virtual async Task<CartAggregate> UpdateConfiguredLineItemPrice(IList<Lin
foreach (var configurationLineItem in configuredItems)
{
var contaner = AbstractTypeFactory<ConfiguredLineItemContainer>.TryCreateInstance();
contaner.Currency = Currency;

if (CartProducts.TryGetValue(configurationLineItem.ProductId, out var configurableProduct))
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using VirtoCommerce.XCart.Core.Commands.BaseCommands;

namespace VirtoCommerce.XCart.Core.Commands
{
public class ChangeCartCurrencyCommand : CartCommand
{
public string NewCurrencyCode { get; set; }
}
}
4 changes: 4 additions & 0 deletions src/VirtoCommerce.XCart.Core/Models/NewCartItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,9 @@ public NewCartItem(string productId, int quantity)
public IList<DynamicPropertyValue> DynamicProperties { get; set; }

public bool IsWishlist { get; set; }

public bool? IsSelectedForCheckout { get; set; }

public bool IgnoreValidationErrors { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using GraphQL.Types;

namespace VirtoCommerce.XCart.Core.Schemas
{
public class InputChangeCartCurrencyType : InputCartBaseType
{
public InputChangeCartCurrencyType()
{
Field<NonNullGraphType<StringGraphType>>("newCurrencyCode", "Second cart currency");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using VirtoCommerce.CartModule.Core.Model;
using VirtoCommerce.Platform.Core.Common;
using VirtoCommerce.Xapi.Core.Models;
using VirtoCommerce.XCart.Core;
using VirtoCommerce.XCart.Core.Commands;
using VirtoCommerce.XCart.Core.Commands.BaseCommands;
using VirtoCommerce.XCart.Core.Models;
using VirtoCommerce.XCart.Core.Services;

namespace VirtoCommerce.XCart.Data.Commands
{
public class ChangeCartCurrencyCommandHandler : CartCommandHandler<ChangeCartCurrencyCommand>
{
private readonly ICartProductService _cartProductService;

public ChangeCartCurrencyCommandHandler(
ICartAggregateRepository cartAggregateRepository,
ICartProductService cartProductService)
: base(cartAggregateRepository)
{
_cartProductService = cartProductService;
}

public override async Task<CartAggregate> Handle(ChangeCartCurrencyCommand request, CancellationToken cancellationToken)
{
// get (or create) both carts
var currentCurrencyCartAggregate = await GetOrCreateCartFromCommandAsync(request)
?? throw new OperationCanceledException("Cart not found");

var newCurrencyCartRequest = new ChangeCartCurrencyCommand
{
StoreId = request.StoreId ?? currentCurrencyCartAggregate.Cart.StoreId,
CartName = request.CartName ?? currentCurrencyCartAggregate.Cart.Name,
CartType = request.CartType ?? currentCurrencyCartAggregate.Cart.Type,
UserId = request.UserId ?? currentCurrencyCartAggregate.Cart.CustomerId,
OrganizationId = request.OrganizationId ?? currentCurrencyCartAggregate.Cart.OrganizationId,
CultureName = request.CultureName ?? currentCurrencyCartAggregate.Cart.LanguageCode,
CurrencyCode = request.NewCurrencyCode,
};

var newCurrencyCartAggregate = await GetOrCreateCartFromCommandAsync(newCurrencyCartRequest);

// clear (old) cart items and add items from the currency cart
newCurrencyCartAggregate.Cart.Items.Clear();

await CopyItems(currentCurrencyCartAggregate, newCurrencyCartAggregate);

await CartRepository.SaveAsync(newCurrencyCartAggregate);
return newCurrencyCartAggregate;
}

protected virtual async Task CopyItems(CartAggregate currentCurrencyCartAggregate, CartAggregate newCurrencyCartAggregate)
{
var ordinaryItems = currentCurrencyCartAggregate.LineItems
.Where(x => !x.IsConfigured)
.ToArray();

if (ordinaryItems.Length > 0)
{
var newCartItems = ordinaryItems
.Select(x => new NewCartItem(x.ProductId, x.Quantity)
{
IgnoreValidationErrors = true,
Comment = x.Note,
IsSelectedForCheckout = x.SelectedForCheckout,
DynamicProperties = x.DynamicProperties.SelectMany(x => x.Values.Select(y => new DynamicPropertyValue()
{
Name = x.Name,
Value = y.Value,
Locale = y.Locale,
})).ToArray(),
})
.ToArray();

await newCurrencyCartAggregate.AddItemsAsync(newCartItems);
}

// copy configured items
var configuredItems = currentCurrencyCartAggregate.LineItems
.Where(x => x.IsConfigured)
.ToArray();

await CopyConfiguredItems(newCurrencyCartAggregate, configuredItems);
}

protected virtual async Task CopyConfiguredItems(CartAggregate newCurrencyCartAggregate, IList<LineItem> configuredItems)
{
if (configuredItems.Count == 0)
{
return;
}

var configProductsIds = configuredItems
.Where(x => !x.ConfigurationItems.IsNullOrEmpty())
.SelectMany(x => x.ConfigurationItems.Select(x => x.ProductId))
.Distinct()
.ToList();

configProductsIds.AddRange(configuredItems.Select(x => x.ProductId));

var configProducts = await _cartProductService.GetCartProductsByIdsAsync(newCurrencyCartAggregate, configProductsIds);

foreach (var configurationLineItem in configuredItems)
{
var contaner = AbstractTypeFactory<ConfiguredLineItemContainer>.TryCreateInstance();
contaner.Currency = newCurrencyCartAggregate.Currency;
contaner.Store = newCurrencyCartAggregate.Store;

contaner.ConfigurableProduct = configProducts.FirstOrDefault(x => x.Product.Id == configurationLineItem.ProductId);

foreach (var configurationItem in configurationLineItem.ConfigurationItems ?? [])
{
var product = configProducts.FirstOrDefault(x => x.Product.Id == configurationItem.ProductId);
if (product != null)
{
contaner.AddItem(product, configurationItem.Quantity, configurationItem.SectionId);
}
}

var expItem = contaner.CreateConfiguredLineItem(configurationLineItem.Quantity);

await newCurrencyCartAggregate.AddConfiguredItemAsync(new NewCartItem(configurationLineItem.ProductId, configurationLineItem.Quantity)
{
CartProduct = contaner.ConfigurableProduct,
IgnoreValidationErrors = true,
Comment = configurationLineItem.Note,
IsSelectedForCheckout = configurationLineItem.SelectedForCheckout,
DynamicProperties = configurationLineItem.DynamicProperties.SelectMany(x => x.Values.Select(y => new DynamicPropertyValue()
{
Name = x.Name,
Value = y.Value,
Locale = y.Locale,
})).ToArray(),
}, expItem.Item);
}
}
}
}
19 changes: 19 additions & 0 deletions src/VirtoCommerce.XCart.Data/Schemas/PurchaseSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,25 @@ public void Build(ISchema schema)

schema.Mutation.AddField(margeCartField);

var changeCartCurrency = FieldBuilder.Create<CartAggregate, CartAggregate>(GraphTypeExtenstionHelper.GetActualType<CartType>())
.Name("changeCartCurrency")
.Argument(GraphTypeExtenstionHelper.GetActualComplexType<NonNullGraphType<InputChangeCartCurrencyType>>(), SchemaConstants.CommandName)
.ResolveSynchronizedAsync(CartPrefix, "userId", _distributedLockService, async context =>
{
var cartCommand = context.GetCartCommand<ChangeCartCurrencyCommand>();

await CheckAuthByCartCommandAsync(context, cartCommand);

//We need to add cartAggregate to the context to be able use it on nested cart types resolvers (e.g for currency)
var cartAggregate = await _mediator.Send(cartCommand);

//store cart aggregate in the user context for future usage in the graph types resolvers
context.SetExpandedObjectGraph(cartAggregate);
return cartAggregate;
}).FieldType;

schema.Mutation.AddField(changeCartCurrency);

/// <example>
/// This is an example JSON request for a mutation
/// {
Expand Down
Loading