-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
#1090: Notification to the consumer when the product is back in stock #1091
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -33,6 +33,11 @@ public class WorkContext : IWorkContext | |||||||||||||
_configuration = configuration; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
public string GetCurrentHostName() | ||||||||||||||
{ | ||||||||||||||
return _httpContext.Request.Host.Value; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
Comment on lines
+36
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
public async Task<User> GetCurrentUser() | ||||||||||||||
{ | ||||||||||||||
if (_currentUser != null) | ||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -20,17 +20,21 @@ public class StockApiController : Controller | |||||||||||
{ | ||||||||||||
private readonly IRepository<Stock> _stockRepository; | ||||||||||||
private readonly IStockService _stockService; | ||||||||||||
private readonly IStockSubscriptionService _stockSubscriptionService; | ||||||||||||
private readonly IWorkContext _workContext; | ||||||||||||
private readonly IRepository<Warehouse> _warehouseRepository; | ||||||||||||
private readonly IRepository<StockHistory> _stockHistoryRepository; | ||||||||||||
private readonly IRepository<BackInStockSubscription> _backInStockSubscriptionRepository; | ||||||||||||
|
||||||||||||
public StockApiController(IRepository<Stock> stockRepository, IStockService stockService, IWorkContext workContext, IRepository<Warehouse> warehouseRepository, IRepository<StockHistory> stockHistoryRepository) | ||||||||||||
public StockApiController(IRepository<Stock> stockRepository, IStockService stockService, IWorkContext workContext, IRepository<Warehouse> warehouseRepository, IRepository<StockHistory> stockHistoryRepository, IRepository<BackInStockSubscription> backInStockSubscriptionRepository, IStockSubscriptionService stockSubscriptionService) | ||||||||||||
{ | ||||||||||||
_stockRepository = stockRepository; | ||||||||||||
_stockService = stockService; | ||||||||||||
_workContext = workContext; | ||||||||||||
_warehouseRepository = warehouseRepository; | ||||||||||||
_stockHistoryRepository = stockHistoryRepository; | ||||||||||||
_backInStockSubscriptionRepository = backInStockSubscriptionRepository; | ||||||||||||
_stockSubscriptionService = stockSubscriptionService; | ||||||||||||
} | ||||||||||||
|
||||||||||||
[HttpPost("grid")] | ||||||||||||
|
@@ -135,5 +139,21 @@ public async Task<IActionResult> GetStockHistory(int warehouseId, int productId) | |||||||||||
|
||||||||||||
return Ok(stockHistory); | ||||||||||||
} | ||||||||||||
|
||||||||||||
[AllowAnonymous] | ||||||||||||
[HttpPost("back-in-stock")] | ||||||||||||
public async Task<IActionResult> BackInStockSubscribe(long productId, string customerEmail) | ||||||||||||
{ | ||||||||||||
if (await _backInStockSubscriptionRepository.Query() | ||||||||||||
.Where(o => o.ProductId == productId && o.CustomerEmail == customerEmail) | ||||||||||||
.AnyAsync()) | ||||||||||||
Comment on lines
+147
to
+149
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
{ | ||||||||||||
return Conflict(); | ||||||||||||
} | ||||||||||||
|
||||||||||||
await _stockSubscriptionService.BackInStockSubscribeAsync(productId, customerEmail); | ||||||||||||
|
||||||||||||
return Ok(); | ||||||||||||
} | ||||||||||||
} | ||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
@using SimplCommerce.Module.Catalog.Models | ||
@using SimplCommerce.Module.Core.Extensions | ||
@using SimplCommerce.Module.Core.Services | ||
|
||
@inject IWorkContext _workContext | ||
@inject IMediaService _mediaService | ||
|
||
@{ | ||
Layout = null; | ||
|
||
var hostName = _workContext.GetCurrentHostName(); | ||
var thumbnailUrl = _mediaService.GetThumbnailUrl(Model.ThumbnailImage); | ||
} | ||
|
||
@model SimplCommerce.Module.Catalog.Models.Product | ||
|
||
<div style="border: 1px solid #ebebeb; width: 50%; padding: 16px 24px; margin: 0 auto;"> | ||
Hi, <b>@Model.Name</b> in now available. | ||
Get in now before it out of stock again. | ||
<br /> | ||
<br /> | ||
<div> | ||
<img src="https://@hostName@thumbnailUrl" /> | ||
<h2>@Model.Name</h2> | ||
<h4 style="color:red;">$1000</h4> | ||
</div> | ||
<a href="https://@hostName/@Model.Slug"> | ||
View product | ||
</a> | ||
</div> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using MediatR; | ||
|
||
namespace SimplCommerce.Module.Inventory.Event | ||
{ | ||
public class BackInStock : INotification | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ProductBackInStock might more meaningful |
||
{ | ||
public long ProductId { get; set; } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,26 @@ | ||||||||||||||
using System; | ||||||||||||||
using System.Collections.Generic; | ||||||||||||||
using System.Linq; | ||||||||||||||
using System.Text; | ||||||||||||||
using System.Threading; | ||||||||||||||
using System.Threading.Tasks; | ||||||||||||||
using MediatR; | ||||||||||||||
using SimplCommerce.Module.Inventory.Services; | ||||||||||||||
|
||||||||||||||
namespace SimplCommerce.Module.Inventory.Event | ||||||||||||||
{ | ||||||||||||||
public class BackInStockSendEmailHandler : INotificationHandler<BackInStock> | ||||||||||||||
{ | ||||||||||||||
public readonly IStockSubscriptionService _stockSubscriptionService; | ||||||||||||||
|
||||||||||||||
public BackInStockSendEmailHandler(IStockSubscriptionService stockSubscriptionService) | ||||||||||||||
{ | ||||||||||||||
_stockSubscriptionService = stockSubscriptionService; | ||||||||||||||
} | ||||||||||||||
Comment on lines
+16
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
|
||||||||||||||
public async Task Handle(BackInStock notification, CancellationToken cancellationToken) | ||||||||||||||
{ | ||||||||||||||
await _stockSubscriptionService.BackInStockSendNotificationsAsync(notification.ProductId); | ||||||||||||||
} | ||||||||||||||
Comment on lines
+21
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
} | ||||||||||||||
} |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,15 @@ | ||||||||
using System; | ||||||||
using System.Collections.Generic; | ||||||||
using System.Linq; | ||||||||
using System.Text; | ||||||||
using System.Threading.Tasks; | ||||||||
using SimplCommerce.Infrastructure.Models; | ||||||||
|
||||||||
namespace SimplCommerce.Module.Inventory.Models | ||||||||
{ | ||||||||
public class BackInStockSubscription : EntityBase | ||||||||
{ | ||||||||
public long ProductId { get; set; } | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
public string CustomerEmail { get; set; } | ||||||||
} | ||||||||
} |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -4,6 +4,10 @@ | |||
using SimplCommerce.Infrastructure.Modules; | ||||
using SimplCommerce.Module.Inventory.Services; | ||||
using SimplCommerce.Infrastructure; | ||||
using MediatR; | ||||
using SimplCommerce.Module.Catalog.Events; | ||||
using SimplCommerce.Module.Core.Events; | ||||
using SimplCommerce.Module.Inventory.Event; | ||||
|
||||
namespace SimplCommerce.Module.Inventory | ||||
{ | ||||
|
@@ -12,6 +16,9 @@ public class ModuleInitializer : IModuleInitializer | |||
public void ConfigureServices(IServiceCollection serviceCollection) | ||||
{ | ||||
serviceCollection.AddTransient<IStockService, StockService>(); | ||||
serviceCollection.AddTransient<IStockSubscriptionService, StockSubscriptionService>(); | ||||
serviceCollection.AddTransient<INotificationHandler<BackInStock>, BackInStockSendEmailHandler>(); | ||||
|
||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||
|
||||
GlobalConfiguration.RegisterAngularModule("simplAdmin.inventory"); | ||||
} | ||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace SimplCommerce.Module.Inventory.Services | ||
{ | ||
public interface IStockSubscriptionService | ||
{ | ||
Task BackInStockSubscribeAsync(long productId, string customerEmail); | ||
|
||
Task BackInStockSendNotificationsAsync(long productId); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -1,9 +1,11 @@ | ||||
using System; | ||||
using System.Linq; | ||||
using System.Threading.Tasks; | ||||
using MediatR; | ||||
using Microsoft.EntityFrameworkCore; | ||||
using SimplCommerce.Infrastructure.Data; | ||||
using SimplCommerce.Module.Catalog.Models; | ||||
using SimplCommerce.Module.Inventory.Event; | ||||
using SimplCommerce.Module.Inventory.Models; | ||||
|
||||
namespace SimplCommerce.Module.Inventory.Services | ||||
|
@@ -13,12 +15,14 @@ public class StockService : IStockService | |||
private readonly IRepository<Stock> _stockRepository; | ||||
private readonly IRepository<Product> _productRepository; | ||||
private readonly IRepository<StockHistory> _stockHistoryRepository; | ||||
private readonly IMediator _mediator; | ||||
|
||||
public StockService(IRepository<Stock> stockRepository, IRepository<Product> productRepository, IRepository<StockHistory> stockHistoryRepository) | ||||
public StockService(IRepository<Stock> stockRepository, IRepository<Product> productRepository, IRepository<StockHistory> stockHistoryRepository, IMediator mediator) | ||||
{ | ||||
_stockRepository = stockRepository; | ||||
_productRepository = productRepository; | ||||
_stockHistoryRepository = stockHistoryRepository; | ||||
_mediator = mediator; | ||||
} | ||||
|
||||
public async Task AddAllProduct(Warehouse warehouse) | ||||
|
@@ -44,6 +48,8 @@ public async Task UpdateStock(StockUpdateRequest stockUpdateRequest) | |||
var product = await _productRepository.Query().FirstOrDefaultAsync(x => x.Id == stockUpdateRequest.ProductId); | ||||
var stock = await _stockRepository.Query().FirstOrDefaultAsync(x => x.ProductId == stockUpdateRequest.ProductId && x.WarehouseId == stockUpdateRequest.WarehouseId); | ||||
|
||||
var prevStockQuantity = product.StockQuantity; | ||||
|
||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||
stock.Quantity = stock.Quantity + stockUpdateRequest.AdjustedQuantity; | ||||
product.StockQuantity = product.StockQuantity + stockUpdateRequest.AdjustedQuantity; | ||||
var stockHistory = new StockHistory | ||||
|
@@ -58,6 +64,11 @@ public async Task UpdateStock(StockUpdateRequest stockUpdateRequest) | |||
|
||||
_stockHistoryRepository.Add(stockHistory); | ||||
await _stockHistoryRepository.SaveChangesAsync(); | ||||
|
||||
if (prevStockQuantity <= 0 && product.StockQuantity > 0) | ||||
{ | ||||
await _mediator.Publish(new BackInStock { ProductId = product.Id }); | ||||
} | ||||
} | ||||
} | ||||
} |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,70 @@ | ||||||||
using System; | ||||||||
using System.Collections.Generic; | ||||||||
using System.Linq; | ||||||||
using System.Text; | ||||||||
using System.Threading.Tasks; | ||||||||
using Microsoft.AspNetCore.Mvc.Routing; | ||||||||
using Microsoft.EntityFrameworkCore; | ||||||||
using SimplCommerce.Infrastructure.Data; | ||||||||
using SimplCommerce.Infrastructure.Web; | ||||||||
using SimplCommerce.Module.Catalog.Models; | ||||||||
using SimplCommerce.Module.Core.Models; | ||||||||
using SimplCommerce.Module.Core.Services; | ||||||||
using SimplCommerce.Module.Inventory.Models; | ||||||||
|
||||||||
namespace SimplCommerce.Module.Inventory.Services | ||||||||
{ | ||||||||
public class StockSubscriptionService : IStockSubscriptionService | ||||||||
{ | ||||||||
private readonly IRepository<BackInStockSubscription> _backInStockSubscriptionRepository; | ||||||||
private readonly IRepository<Product> _productRepository; | ||||||||
private readonly IEmailSender _emailSender; | ||||||||
private readonly IRazorViewRenderer _viewRender; | ||||||||
|
||||||||
public StockSubscriptionService(IRepository<BackInStockSubscription> backInStockSubscriptionRepository, IEmailSender emailSender, IRazorViewRenderer viewRender, IRepository<Product> productRepository) | ||||||||
{ | ||||||||
_backInStockSubscriptionRepository = backInStockSubscriptionRepository; | ||||||||
_emailSender = emailSender; | ||||||||
_viewRender = viewRender; | ||||||||
_productRepository = productRepository; | ||||||||
} | ||||||||
|
||||||||
public async Task BackInStockSendNotificationsAsync(long productId) | ||||||||
{ | ||||||||
var subscriptions = await _backInStockSubscriptionRepository | ||||||||
.Query() | ||||||||
.Where(o => o.ProductId == productId) | ||||||||
.ToListAsync(); | ||||||||
|
||||||||
var product = await _productRepository | ||||||||
.Query() | ||||||||
.Where(o => o.Id == productId) | ||||||||
.Include(o => o.ThumbnailImage) | ||||||||
.FirstOrDefaultAsync(); | ||||||||
|
||||||||
var emailBody = await _viewRender.RenderViewToStringAsync("/Areas/Inventory/Views/EmailTemplates/BackInStockEmail.cshtml", product); | ||||||||
var emailSubject = $"Back in stock"; | ||||||||
|
||||||||
foreach (var subscription in subscriptions) | ||||||||
{ | ||||||||
await _emailSender.SendEmailAsync(subscription.CustomerEmail, emailSubject, emailBody, true); | ||||||||
|
||||||||
_backInStockSubscriptionRepository.Remove(subscription); | ||||||||
} | ||||||||
|
||||||||
await _backInStockSubscriptionRepository.SaveChangesAsync(); | ||||||||
} | ||||||||
|
||||||||
public async Task BackInStockSubscribeAsync(long productId, string customerEmail) | ||||||||
{ | ||||||||
var subscription = new BackInStockSubscription | ||||||||
{ | ||||||||
ProductId = productId, | ||||||||
CustomerEmail = customerEmail | ||||||||
}; | ||||||||
|
||||||||
_backInStockSubscriptionRepository.Add(subscription); | ||||||||
await _backInStockSubscriptionRepository.SaveChangesAsync(); | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
} | ||||||||
} | ||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.