Skip to content

Commit a7b16fd

Browse files
authored
added product comparison module (#180)
1 parent eeef0b3 commit a7b16fd

31 files changed

+2225
-86
lines changed

SimplCommerce.sln

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio 15
4-
VisualStudioVersion = 15.0.26403.3
4+
VisualStudioVersion = 15.0.26228.4
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C9BFDDC4-5671-47A3-B57D-197C2A51FA8A}"
77
EndProject
@@ -50,6 +50,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimplCommerce.Module.News",
5050
EndProject
5151
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimplCommerce.Module.Cms.Tests", "test\SimplCommerce.Module.Cms.Tests\SimplCommerce.Module.Cms.Tests.csproj", "{483C5E8D-99A4-410D-8DB3-2F695B05BF40}"
5252
EndProject
53+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimplCommerce.Module.ProductComparison", "src\Modules\SimplCommerce.Module.ProductComparison\SimplCommerce.Module.ProductComparison.csproj", "{3BA470E8-1A8A-4E36-A1AE-669838816770}"
54+
EndProject
5355
Global
5456
GlobalSection(SolutionConfigurationPlatforms) = preSolution
5557
Debug|Any CPU = Debug|Any CPU
@@ -252,6 +254,18 @@ Global
252254
{483C5E8D-99A4-410D-8DB3-2F695B05BF40}.Release|x64.Build.0 = Release|Any CPU
253255
{483C5E8D-99A4-410D-8DB3-2F695B05BF40}.Release|x86.ActiveCfg = Release|Any CPU
254256
{483C5E8D-99A4-410D-8DB3-2F695B05BF40}.Release|x86.Build.0 = Release|Any CPU
257+
{3BA470E8-1A8A-4E36-A1AE-669838816770}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
258+
{3BA470E8-1A8A-4E36-A1AE-669838816770}.Debug|Any CPU.Build.0 = Debug|Any CPU
259+
{3BA470E8-1A8A-4E36-A1AE-669838816770}.Debug|x64.ActiveCfg = Debug|Any CPU
260+
{3BA470E8-1A8A-4E36-A1AE-669838816770}.Debug|x64.Build.0 = Debug|Any CPU
261+
{3BA470E8-1A8A-4E36-A1AE-669838816770}.Debug|x86.ActiveCfg = Debug|Any CPU
262+
{3BA470E8-1A8A-4E36-A1AE-669838816770}.Debug|x86.Build.0 = Debug|Any CPU
263+
{3BA470E8-1A8A-4E36-A1AE-669838816770}.Release|Any CPU.ActiveCfg = Release|Any CPU
264+
{3BA470E8-1A8A-4E36-A1AE-669838816770}.Release|Any CPU.Build.0 = Release|Any CPU
265+
{3BA470E8-1A8A-4E36-A1AE-669838816770}.Release|x64.ActiveCfg = Release|Any CPU
266+
{3BA470E8-1A8A-4E36-A1AE-669838816770}.Release|x64.Build.0 = Release|Any CPU
267+
{3BA470E8-1A8A-4E36-A1AE-669838816770}.Release|x86.ActiveCfg = Release|Any CPU
268+
{3BA470E8-1A8A-4E36-A1AE-669838816770}.Release|x86.Build.0 = Release|Any CPU
255269
EndGlobalSection
256270
GlobalSection(SolutionProperties) = preSolution
257271
HideSolutionNode = FALSE
@@ -275,5 +289,6 @@ Global
275289
{52D24DE2-8661-4BBF-AC7B-B36BB1A655BA} = {D9FD9ABA-AE5E-4427-AA6B-6285BE2E212D}
276290
{4FE8CC2E-3DF9-4AE7-A04E-4293FABEE221} = {7EFA2FA7-32DD-4047-B021-50E77A83D714}
277291
{483C5E8D-99A4-410D-8DB3-2F695B05BF40} = {D9FD9ABA-AE5E-4427-AA6B-6285BE2E212D}
292+
{3BA470E8-1A8A-4E36-A1AE-669838816770} = {7EFA2FA7-32DD-4047-B021-50E77A83D714}
278293
EndGlobalSection
279294
EndGlobal

src/Modules/SimplCommerce.Module.Catalog/Components/ProductWidgetViewComponent.cs

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Linq;
22
using Microsoft.AspNetCore.Mvc;
3+
using Microsoft.EntityFrameworkCore;
34
using Newtonsoft.Json;
45
using SimplCommerce.Infrastructure.Data;
56
using SimplCommerce.Module.Catalog.Models;
@@ -41,26 +42,10 @@ public IViewComponentResult Invoke(WidgetInstanceViewModel widgetInstance)
4142
}
4243

4344
model.Products = query
45+
.Include(x => x.ThumbnailImage)
4446
.OrderByDescending(x => x.CreatedOn)
4547
.Take(model.Setting.NumberOfProducts)
46-
.Select(x => new ProductThumbnail
47-
{
48-
Id = x.Id,
49-
Name = x.Name,
50-
SeoTitle = x.SeoTitle,
51-
Price = x.Price,
52-
OldPrice = x.OldPrice,
53-
SpecialPrice = x.SpecialPrice,
54-
SpecialPriceStart = x.SpecialPriceStart,
55-
SpecialPriceEnd = x.SpecialPriceEnd,
56-
StockQuantity = x.StockQuantity,
57-
IsAllowToOrder = x.IsAllowToOrder,
58-
IsCallForPricing = x.IsCallForPricing,
59-
ThumbnailImage = x.ThumbnailImage,
60-
NumberVariation = x.ProductLinks.Count,
61-
ReviewsCount = x.ReviewsCount,
62-
RatingAverage = x.RatingAverage
63-
}).ToList();
48+
.Select(x => ProductThumbnail.FromProduct(x)).ToList();
6449

6550
foreach (var product in model.Products)
6651
{

src/Modules/SimplCommerce.Module.Catalog/Controllers/BrandController.cs

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -85,24 +85,7 @@ public IActionResult BrandDetail(long id, SearchOption searchOption)
8585
query = AppySort(searchOption, query);
8686

8787
var products = query
88-
.Select(x => new ProductThumbnail
89-
{
90-
Id = x.Id,
91-
Name = x.Name,
92-
SeoTitle = x.SeoTitle,
93-
Price = x.Price,
94-
OldPrice = x.OldPrice,
95-
SpecialPrice = x.SpecialPrice,
96-
SpecialPriceStart = x.SpecialPriceStart,
97-
SpecialPriceEnd = x.SpecialPriceEnd,
98-
StockQuantity = x.StockQuantity,
99-
IsAllowToOrder = x.IsAllowToOrder,
100-
IsCallForPricing = x.IsCallForPricing,
101-
ThumbnailImage = x.ThumbnailImage,
102-
NumberVariation = x.ProductLinks.Count,
103-
ReviewsCount = x.ReviewsCount,
104-
RatingAverage = x.RatingAverage
105-
})
88+
.Select(x => ProductThumbnail.FromProduct(x))
10689
.Skip(offset)
10790
.Take(_pageSize)
10891
.ToList();

src/Modules/SimplCommerce.Module.Catalog/Controllers/CategoryController.cs

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -94,24 +94,7 @@ public IActionResult CategoryDetail(long id, SearchOption searchOption)
9494
query = AppySort(searchOption, query);
9595

9696
var products = query
97-
.Select(x => new ProductThumbnail
98-
{
99-
Id = x.Id,
100-
Name = x.Name,
101-
SeoTitle = x.SeoTitle,
102-
Price = x.Price,
103-
OldPrice = x.OldPrice,
104-
SpecialPrice = x.SpecialPrice,
105-
SpecialPriceStart = x.SpecialPriceStart,
106-
SpecialPriceEnd = x.SpecialPriceEnd,
107-
StockQuantity = x.StockQuantity,
108-
IsAllowToOrder = x.IsAllowToOrder,
109-
IsCallForPricing = x.IsCallForPricing,
110-
ThumbnailImage = x.ThumbnailImage,
111-
NumberVariation = x.ProductLinks.Count,
112-
ReviewsCount = x.ReviewsCount,
113-
RatingAverage = x.RatingAverage
114-
})
97+
.Select(x => ProductThumbnail.FromProduct(x))
11598
.Skip(offset)
11699
.Take(_pageSize)
117100
.ToList();

src/Modules/SimplCommerce.Module.Catalog/ViewModels/ProductThumbnail.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,29 @@ public class ProductThumbnail
3939
public double? RatingAverage { get; set; }
4040

4141
public CalculatedProductPrice CalculatedProductPrice { get; set; }
42+
43+
public static ProductThumbnail FromProduct(Product product)
44+
{
45+
var productThumbnail = new ProductThumbnail
46+
{
47+
Id = product.Id,
48+
Name = product.Name,
49+
SeoTitle = product.SeoTitle,
50+
Price = product.Price,
51+
OldPrice = product.OldPrice,
52+
SpecialPrice = product.SpecialPrice,
53+
SpecialPriceStart = product.SpecialPriceStart,
54+
SpecialPriceEnd = product.SpecialPriceEnd,
55+
StockQuantity = product.StockQuantity,
56+
IsAllowToOrder = product.IsAllowToOrder,
57+
IsCallForPricing = product.IsCallForPricing,
58+
ThumbnailImage = product.ThumbnailImage,
59+
NumberVariation = product.ProductLinks.Count,
60+
ReviewsCount = product.ReviewsCount,
61+
RatingAverage = product.RatingAverage
62+
};
63+
64+
return productThumbnail;
65+
}
4266
}
4367
}

src/Modules/SimplCommerce.Module.Catalog/Views/Product/ProductDetail.cshtml

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,18 @@
101101
<button type="button" class="quantity-button" name="add" value="+">+</button>
102102
</div>
103103
<button type="button" disabled="@(!variant.IsAllowToOrder || variant.StockQuantity <= 0)" class="btn btn-lg btn-add-cart">@Localizer["Add to cart"]</button>
104+
<div>
105+
<ul class="list-inline add-to-wishlist">
106+
@*<li class="wishlist-in">
107+
<i class="fa fa-heart"></i>
108+
<a href="#">Add to Wishlist</a>
109+
</li>*@
110+
<li class="compare-in">
111+
<i class="fa fa-exchange"></i>
112+
<a href="#" class="add-to-comparison">Add to Compare</a>
113+
</li>
114+
</ul>
115+
</div>
104116
</form>
105117
</div>
106118
</div>
@@ -141,22 +153,21 @@
141153
<button type="button" class="quantity-button" name="add" value="+">+</button>
142154
</div>
143155
<button type="button" disabled="@(!Model.IsAllowToOrder || Model.StockQuantity <= 0)" class="btn btn-lg btn-add-cart">@Localizer["Add to cart"]</button>
156+
<div>
157+
<ul class="list-inline add-to-wishlist">
158+
@*<li class="wishlist-in">
159+
<i class="fa fa-heart"></i>
160+
<a href="#">Add to Wishlist</a>
161+
</li>*@
162+
<li class="compare-in">
163+
<i class="fa fa-exchange"></i>
164+
<a href="#" class="add-to-comparison">Add to Compare</a>
165+
</li>
166+
</ul>
167+
</div>
144168
</form>
145169
</div>
146170
}
147-
148-
<div>
149-
<ul class="list-inline add-to-wishlist">
150-
<li class="wishlist-in">
151-
<i class="fa fa-heart"></i>
152-
<a href="#">Add to Wishlist</a>
153-
</li>
154-
<li class="compare-in">
155-
<i class="fa fa-exchange"></i>
156-
<a href="#">Add to Compare</a>
157-
</li>
158-
</ul>
159-
</div>
160171
</div>
161172
</div>
162173
@if (!string.IsNullOrWhiteSpace(Model.Description))

src/Modules/SimplCommerce.Module.Core/wwwroot/site.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,3 +689,7 @@ li.nav-item-group, li.nav-item-group a {
689689
.rating-xs {
690690
font-size: 1.2em;
691691
}
692+
693+
.product-comparison-result-remove {
694+
padding-top: 10px;
695+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System.Threading.Tasks;
4+
using Microsoft.AspNetCore.Identity;
5+
using Microsoft.AspNetCore.Mvc;
6+
using Microsoft.EntityFrameworkCore;
7+
using SimplCommerce.Infrastructure.Data;
8+
using SimplCommerce.Module.Catalog.Models;
9+
using SimplCommerce.Module.Core.Extensions;
10+
using SimplCommerce.Module.Core.Models;
11+
using SimplCommerce.Module.Core.Services;
12+
using SimplCommerce.Module.ProductComparison.Models;
13+
using SimplCommerce.Module.ProductComparison.Services;
14+
using SimplCommerce.Module.ProductComparison.ViewModels;
15+
using SimplCommerce.Module.Catalog.Services;
16+
17+
namespace SimplCommerce.Module.ProductComparison.Controllers
18+
{
19+
public class ComparingProductController : Controller
20+
{
21+
private readonly IRepository<ComparingProduct> _comparingProductRepository;
22+
private readonly IComparingProductService _comparingProductService;
23+
private readonly IProductPricingService _productPricingService;
24+
private readonly IMediaService _mediaService;
25+
private readonly IWorkContext _workContext;
26+
27+
public ComparingProductController(
28+
UserManager<User> userManager,
29+
IRepository<ComparingProduct> comparingProductRepository,
30+
IComparingProductService comparingProductService,
31+
IProductPricingService productPricingService,
32+
IMediaService mediaService,
33+
IWorkContext workContext)
34+
{
35+
_comparingProductRepository = comparingProductRepository;
36+
_comparingProductService = comparingProductService;
37+
_productPricingService = productPricingService;
38+
_mediaService = mediaService;
39+
_workContext = workContext;
40+
}
41+
42+
[HttpPost]
43+
public async Task<IActionResult> AddToComparison([FromBody] AddToComparisonModel model)
44+
{
45+
var currentUser = await _workContext.GetCurrentUser();
46+
var returnModel = new AddToComparisonResult();
47+
48+
try
49+
{
50+
_comparingProductService.AddToComparison(currentUser.Id, model.ProductId);
51+
returnModel.Message = "The product has been added to comparison items";
52+
}
53+
catch (TooManyComparingProductException ex)
54+
{
55+
returnModel.Message = $"Can not add to comparison items. Can only comparison {ex.MaxNumComparingProduct} items";
56+
}
57+
58+
var comparingProducts = _comparingProductRepository.Query()
59+
.Include(x => x.Product).ThenInclude(x => x.ThumbnailImage)
60+
.Where(x => x.UserId == currentUser.Id)
61+
.Select(x => new ComparingProductVm()
62+
{
63+
ProductName = x.Product.Name,
64+
ProductImage = _mediaService.GetThumbnailUrl(x.Product.ThumbnailImage),
65+
CalculatedProductPrice = _productPricingService.CalculateProductPrice(x.Product),
66+
ProductId = x.ProductId
67+
}
68+
).ToList();
69+
70+
returnModel.ProductComparisons = comparingProducts;
71+
72+
return PartialView("AddToComparisonResult", returnModel);
73+
}
74+
75+
[HttpDelete]
76+
public async Task<IActionResult> Remove(long id)
77+
{
78+
var currentUser = await _workContext.GetCurrentUser();
79+
var productComparison = _comparingProductRepository.Query().FirstOrDefault(x => x.UserId == currentUser.Id && x.ProductId == id);
80+
81+
if (productComparison == null)
82+
{
83+
return new NotFoundResult();
84+
}
85+
86+
_comparingProductRepository.Remove(productComparison);
87+
_comparingProductRepository.SaveChange();
88+
89+
return Ok();
90+
}
91+
92+
[HttpGet("compare-products")]
93+
public async Task<IActionResult> Index()
94+
{
95+
var currentUser = await _workContext.GetCurrentUser();
96+
var comparingProducts = _comparingProductRepository.Query()
97+
.Include(x => x.Product).ThenInclude(p => p.ThumbnailImage)
98+
.Include(x => x.Product).ThenInclude(p => p.AttributeValues).ThenInclude(a => a.Attribute)
99+
.Where(x => x.UserId == currentUser.Id).ToList();
100+
101+
var allAttributes = new List<ProductAttribute>();
102+
foreach (var item in comparingProducts)
103+
{
104+
allAttributes.AddRange(item.Product.AttributeValues.Select(x => x.Attribute));
105+
}
106+
107+
var model = new ProductComparisonVm();
108+
model.Attributes = allAttributes.Distinct().Select(x => new AttributeVm { AttributeId = x.Id, Name = x.Name }).ToList();
109+
model.Products = comparingProducts.Select(x => new ComparingProductVm
110+
{
111+
ProductName = x.Product.Name,
112+
ProductImage = _mediaService.GetThumbnailUrl(x.Product.ThumbnailImage),
113+
CalculatedProductPrice = _productPricingService.CalculateProductPrice(x.Product),
114+
ProductId = x.ProductId,
115+
AttributeValues = x.Product.AttributeValues.Select(a => new AttributeValueVm { AttributeId = a.AttributeId, Value = a.Value }).ToList()
116+
}).ToList();
117+
118+
return View(model);
119+
}
120+
}
121+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using MediatR;
2+
using SimplCommerce.Module.Core.Events;
3+
using SimplCommerce.Module.Core.Extensions;
4+
using SimplCommerce.Module.ProductComparison.Services;
5+
6+
namespace SimplCommerce.Module.ProductComparison.Events
7+
{
8+
public class UserSignedInHandler : INotificationHandler<UserSignedIn>
9+
{
10+
private readonly IWorkContext _workContext;
11+
private readonly IComparingProductService _comparingService;
12+
13+
public UserSignedInHandler(IWorkContext workContext, IComparingProductService comparingService)
14+
{
15+
_workContext = workContext;
16+
_comparingService = comparingService;
17+
}
18+
19+
public void Handle(UserSignedIn user)
20+
{
21+
var guestUser = _workContext.GetCurrentUser().Result;
22+
_comparingService.MigrateComparingProduct(guestUser.Id, user.UserId);
23+
}
24+
}
25+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System;
2+
using SimplCommerce.Infrastructure.Models;
3+
using SimplCommerce.Module.Catalog.Models;
4+
using SimplCommerce.Module.Core.Models;
5+
6+
namespace SimplCommerce.Module.ProductComparison.Models
7+
{
8+
public class ComparingProduct : EntityBase
9+
{
10+
public DateTimeOffset CreatedOn { get; set; }
11+
12+
public long UserId { get; set; }
13+
14+
public User User { get; set; }
15+
16+
public long ProductId { get; set; }
17+
18+
public Product Product { get; set; }
19+
}
20+
}

0 commit comments

Comments
 (0)