Skip to content
This repository has been archived by the owner on Jun 13, 2023. It is now read-only.

Commit

Permalink
VDS-790: Load product parts of the configurable product with him (#56)
Browse files Browse the repository at this point in the history
* Generate API client for DemoSolutionFeaturesModule

* Regenerate DemoSolutionFeatures API client

Right

* Add API client and service to IDemoCatalog

* Load product parts of configurable product at product loading

* Fix code smells

* Fix null reference exception

* Take into account PR notes

* Change ProductIsBuyableSpecification for configurable product case

* Remove usings. Use const instead of static props

* Fix error with ProductIsBuyableSpecification for configurable products

* Fix error. Missing price for Unavailable product

* Attempt to fix anonnymous cart merge

* Add unit tests for ProductIsBuyableSpecification

* Fix code smells

* Add comments according to AAA unit tests pattern

* Revert "Attempt to fix anonnymous cart merge"

This reverts commit 1328380.

* Fix merge issues

Co-authored-by: Aleksandr Vishniakov <av@virtoway.com>
Co-authored-by: Dmitry Pushnitsa <d.pushnitsa@gmail.com>
  • Loading branch information
3 people authored Feb 4, 2021
1 parent 795f154 commit 7be9cc0
Show file tree
Hide file tree
Showing 20 changed files with 2,617 additions and 210 deletions.
19 changes: 18 additions & 1 deletion VirtoCommerce.Storefront.Model/Cart/Demo/ConfiguredGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,23 @@ public ConfiguredGroup(int quantity, Currency currency, string productId)

#endregion Taxation

public ICollection<ProductPart> Parts { get; set; } = new List<ProductPart>();
public override object Clone()
{
var result = (ConfiguredGroup) base.Clone();

result.ListPrice = ListPrice?.Clone() as Money;
result.SalePrice = SalePrice?.Clone() as Money;
result.ListPriceWithTax = ListPriceWithTax?.Clone() as Money;
result.SalePriceWithTax = SalePriceWithTax?.Clone() as Money;
result.PlacedPrice = PlacedPrice?.Clone() as Money;
result.PlacedPriceWithTax = PlacedPriceWithTax?.Clone() as Money;
result.ExtendedPrice = ExtendedPrice?.Clone() as Money;
result.ExtendedPriceWithTax = ExtendedPriceWithTax?.Clone() as Money;
result.TaxTotal = TaxTotal?.Clone() as Money;

result.Items = new List<LineItem>();

return result;
}
}
}
9 changes: 9 additions & 0 deletions VirtoCommerce.Storefront.Model/Catalog/Demo/Product.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Collections.Generic;

namespace VirtoCommerce.Storefront.Model.Catalog
{
public partial class Product
{
public ICollection<ProductPart> Parts { get; set; } = new List<ProductPart>();
}
}
11 changes: 11 additions & 0 deletions VirtoCommerce.Storefront.Model/Catalog/Demo/ProductTypes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace VirtoCommerce.Storefront.Model.Catalog.Demo
{
public static class ProductTypes
{
public const string Configurable = "Configurable";

public const string Physical = "Physical";

public const string Digital = "Digital";
}
}
12 changes: 11 additions & 1 deletion VirtoCommerce.Storefront.Model/Catalog/ProductPart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,22 @@ public ProductPart()
Items = Array.Empty<Product>();
}

public string Id { get; set; }

public string Name { get; set; }

public Image Image { get; set; }

public Product[] Items { get; set; }
public string Description { get; set; }

public string SelectedItemId { get; set; }

public int MinQuantity { get; set; }

public int MaxQuantity { get; set; }

public bool IsRequired { get; set; }

public Product[] Items { get; set; }
}
}
10 changes: 10 additions & 0 deletions VirtoCommerce.Storefront.Model/Catalog/ProductTotal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ namespace VirtoCommerce.Storefront.Model.Catalog
{
public class ProductTotal
{
public ProductTotal(Currency currency)
{
Total = new Money(currency);
TotalWithTax = new Money(currency);
SubTotal = new Money(currency);
SubTotalWithTax = new Money(currency);
DiscountTotal = new Money(currency);
DiscountTotalWithTax = new Money(currency);
TaxTotal = new Money(currency);
}
public Money Total { get; set; }
public Money TotalWithTax { get; set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,5 @@ namespace VirtoCommerce.Storefront.Model.Catalog.Services
public interface IDemoCatalogService: ICatalogService
{
Task<ProductPart[]> GetProductPartsAsync(string productId);

ProductPart TryGetProductPartByCategoryId(string categoryId);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Linq;
using VirtoCommerce.Storefront.Model.Common.Specifications;
using VirtoCommerce.Storefront.Model.Catalog.Demo;

namespace VirtoCommerce.Storefront.Model.Catalog
{
Expand All @@ -10,7 +12,8 @@ public virtual bool IsSatisfiedBy(Product product)
if (product == null)
throw new ArgumentNullException(nameof(product));

return product.IsActive && product.IsBuyable && product.Price.ListPrice.Amount > 0;
return ( product.IsActive && product.IsBuyable && product.Price.ListPrice.Amount > 0 && product.ProductType != ProductTypes.Configurable )
|| ( product.ProductType == ProductTypes.Configurable && product.Parts.Any() && product.Parts.All(x => x.Items.Any()) );
}

}
Expand Down
2 changes: 0 additions & 2 deletions VirtoCommerce.Storefront.Model/Order/Demo/ConfiguredGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,5 @@ public ConfiguredGroup(int quantity, Currency currency, string productId)
public Money TaxTotal { get; set; }

#endregion Taxation

public ICollection<ProductPart> Parts { get; set; } = new List<ProductPart>();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,5 @@ namespace VirtoCommerce.Storefront.Model.Order.Services
public interface IDemoCustomerOrderService
{
Task LoadProductsAsync(params CustomerOrder[] orders);

void SelectConfiguredProductParts(params CustomerOrder[] orders);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
using System;
using Xunit;
using Bogus;
using VirtoCommerce.Storefront.Model.Catalog;
using VirtoCommerce.Storefront.Model.Catalog.Demo;
using VirtoCommerce.Storefront.Model.Common;
using VirtoCommerce.Storefront.Model;

namespace VirtoCommerce.Storefront.Tests.Catalog.Specifications
{
public class ProductIsBuyableSpecificationTests
{
const string CURRENCY_CODE = "USD";
static readonly Currency Usd = new Currency(Language.InvariantLanguage, CURRENCY_CODE);
static readonly Randomizer Rand = new Randomizer();
static readonly Faker Faker = new Faker();

[Fact]
public void IsSatisfiedBy_NotConfigurable_True()
{
// arrange
var product = new Product()
{
ProductType = ProductTypes.Physical,
IsActive = true,
IsBuyable = true,
Price = new ProductPrice(Usd)
{
ListPrice = new Money(Rand.Decimal(1), Usd)
}
};

var spec = new ProductIsBuyableSpecification();

// act
var result = spec.IsSatisfiedBy(product);

// assert
Assert.True(result);
}

[Theory]
[InlineData(true, false, 1, int.MaxValue)]
[InlineData(false, true, 1, int.MaxValue)]
[InlineData(false, false, 1, int.MaxValue)]
[InlineData(false, false, 0, 0)]
[InlineData(true, true, 0, 0)]
public void IsSatisfiedBy_NotConfigurable_False(bool isActive, bool isBuyable, int priceMin, int priceMax)
{
// arrange
var product = new Product()
{
ProductType = ProductTypes.Physical,
IsActive = isActive,
IsBuyable = isBuyable,
Price = new ProductPrice(Usd)
{
ListPrice = new Money(Rand.Decimal(priceMin, priceMax), Usd)
}
};

var spec = new ProductIsBuyableSpecification();

// act
var result = spec.IsSatisfiedBy(product);

// assert
Assert.False(result);
}

[Fact]
public void IsSatisfiedBy_Configurable_True()
{
// arrange
var partItemFaker = new Faker<Product>()
.RuleFor(p => p.ProductType, f => ProductTypes.Physical);

var partFaker = new Faker<ProductPart>()
.RuleFor(p => p.Items, f => partItemFaker.Generate(Rand.Int(1, 5)).ToArray());

var productFaker = new Faker<Product>()
.RuleFor(p => p.ProductType, f => ProductTypes.Configurable)
.RuleFor(p => p.Parts, f => partFaker.Generate(Rand.Int(1, 5)).ToArray());

var product = productFaker.Generate();

var spec = new ProductIsBuyableSpecification();

// act
var result = spec.IsSatisfiedBy(product);

// assert
Assert.True(result);
}

[Fact]
public void IsSatisfiedBy_ConfigurablePartsIsEmpty_False()
{
// arrange
var partItemFaker = new Faker<Product>()
.RuleFor(p => p.ProductType, f => ProductTypes.Physical);

var partFaker = new Faker<ProductPart>()
.RuleFor(p => p.Items, f => partItemFaker.Generate(Rand.Int(1, 5)).ToArray());

var productFaker = new Faker<Product>()
.RuleFor(p => p.ProductType, f => ProductTypes.Configurable)
.RuleFor(p => p.Parts, f => partFaker.Generate(Rand.Int(1, 5)).ToArray());

var product = productFaker.Generate();

product.Parts = Array.Empty<ProductPart>();

var spec = new ProductIsBuyableSpecification();

// act
var result = spec.IsSatisfiedBy(product);

// assert
Assert.False(result);
}

[Fact]
public void IsSatisfiedBy_ConfigurableAnyPartItemsIsEmpty_False()
{
// arrange
var partItemFaker = new Faker<Product>()
.RuleFor(p => p.ProductType, f => ProductTypes.Physical);

var partFaker = new Faker<ProductPart>()
.RuleFor(p => p.Items, f => partItemFaker.Generate(Rand.Int(1, 5)).ToArray());

var productFaker = new Faker<Product>()
.RuleFor(p => p.ProductType, f => ProductTypes.Configurable)
.RuleFor(p => p.Parts, f => partFaker.Generate(Rand.Int(1, 5)).ToArray());

var product = productFaker.Generate();

var part = Faker.PickRandom(product.Parts);

part.Items= Array.Empty<Product>();

var spec = new ProductIsBuyableSpecification();

// act
var result = spec.IsSatisfiedBy(product);

// assert
Assert.False(result);
}
}
}

Loading

0 comments on commit 7be9cc0

Please sign in to comment.