Skip to content

Commit

Permalink
#1090: Notification to the consumer when the product is back in stock (
Browse files Browse the repository at this point in the history
  • Loading branch information
s04v committed Mar 10, 2024
1 parent 484cb93 commit 4565b57
Show file tree
Hide file tree
Showing 15 changed files with 314 additions and 2 deletions.
Expand Up @@ -199,6 +199,31 @@
<div class="out-of-stock">
<span class="label label-danger">@Localizer["Out of stock"]</span>
</div>

<div class="back-in-stock-subscribe">
<form action="api/stocks/back-in-stock">
<div>
@if (SignInManager.IsSignedIn(User))
{
<p>Subscribe and we'll notify you when the product is back in stock.</p>
<div class="d-flex">
<input type="hidden" name="productId" value="@Model.Id" />
<input type="hidden" class="form-control mr-2" name="customerEmail" value="@User.Claims.First(c => c.Type == "email").Value"/>
<button type="button" class="btn btn-primary btn-back-in-stock-subscribe" id="subscribeBackInStock">Subscribe</button>
</div>
}
else
{
<p>Leave your email and we'll notify you when the product is in stock</p>
<div class="d-flex">
<input type="hidden" name="productId" value="@Model.Id" />
<input type="text" class="form-control mr-2" name="customerEmail" />
<button type="button" class="btn btn-primary btn-back-in-stock-subscribe" id="subscribeBackInStock">Send</button>
</div>
}
</div>
</form>
</div>
}
<div class="add-to-cart">
<form class="inline">
Expand Down
18 changes: 18 additions & 0 deletions src/Modules/SimplCommerce.Module.Catalog/wwwroot/product-detail.js
Expand Up @@ -71,4 +71,22 @@ $(document).ready(function () {
});
}
});

$("#subscribeBackInStock").on('click', function (e) {
e.preventDefault();
var $form = $(this).closest("form"),
productId = $(this).closest("form").find('input[name=productId]').val(),
customerEmail = $(this).closest("form").find('input[name=customerEmail]').val();

var that = this;
$.post($form.attr('action'), $form.serializeArray())
.done(function (result) {
$(that).closest('.back-in-stock-subscribe').html('<b>Thank you. We\'ll notify you.</b>');
})
.fail(function (result) {
if (result.status === 409) {
$(that).closest('.back-in-stock-subscribe').html('<b>You\'ve already subscribed.</b>');
}
});
});
});
Expand Up @@ -5,6 +5,8 @@ namespace SimplCommerce.Module.Core.Extensions
{
public interface IWorkContext
{
string GetCurrentHostName();

Task<User> GetCurrentUser();
}
}
Expand Up @@ -33,6 +33,11 @@ public class WorkContext : IWorkContext
_configuration = configuration;
}

public string GetCurrentHostName()
{
return _httpContext.Request.Host.Value;
}

public async Task<User> GetCurrentUser()
{
if (_currentUser != null)
Expand Down
Expand Up @@ -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")]
Expand Down Expand Up @@ -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())
{
return Conflict();
}

await _stockSubscriptionService.BackInStockSubscribeAsync(productId, customerEmail);

return Ok();
}
}
}
@@ -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>
14 changes: 14 additions & 0 deletions src/Modules/SimplCommerce.Module.Inventory/Event/BackInStock.cs
@@ -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
{
public long ProductId { get; set; }
}
}
@@ -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;
}

public async Task Handle(BackInStock notification, CancellationToken cancellationToken)
{
await _stockSubscriptionService.BackInStockSendNotificationsAsync(notification.ProductId);
}
}
}
@@ -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; }
public string CustomerEmail { get; set; }
}
}
Expand Up @@ -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
{
Expand All @@ -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>();


GlobalConfiguration.RegisterAngularModule("simplAdmin.inventory");
}
Expand Down
@@ -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);
}
}
@@ -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
Expand All @@ -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)
Expand All @@ -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;

stock.Quantity = stock.Quantity + stockUpdateRequest.AdjustedQuantity;
product.StockQuantity = product.StockQuantity + stockUpdateRequest.AdjustedQuantity;
var stockHistory = new StockHistory
Expand All @@ -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 });
}
}
}
}
@@ -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();
}
}
}

0 comments on commit 4565b57

Please sign in to comment.