Skip to content

Commit c6077c2

Browse files
authored
#396 Admin - view coupon usage history (#398)
1 parent 53d3fa9 commit c6077c2

File tree

21 files changed

+2899
-124
lines changed

21 files changed

+2899
-124
lines changed

src/Modules/SimplCommerce.Module.Core/Views/HomeAdmin/Index.cshtml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,11 @@
169169
<script simpl-append-version="true" src="~/modules/contacts/admin/contacts/contact.js"></script>
170170

171171
<script simpl-append-version="true" src="~/modules/pricing/admin/pricing.module.js"></script>
172-
<script simpl-append-version="true" src="~/modules/pricing/admin/cartrule/cartrule-service.js"></script>
173-
<script simpl-append-version="true" src="~/modules/pricing/admin/cartrule/cartrule-list.js"></script>
174-
<script simpl-append-version="true" src="~/modules/pricing/admin/cartrule/cartrule-form.js"></script>
172+
<script simpl-append-version="true" src="~/modules/pricing/admin/cart-rule/cart-rule-service.js"></script>
173+
<script simpl-append-version="true" src="~/modules/pricing/admin/cart-rule/cart-rule-list.js"></script>
174+
<script simpl-append-version="true" src="~/modules/pricing/admin/cart-rule/cart-rule-form.js"></script>
175+
<script simpl-append-version="true" src="~/modules/pricing/admin/cart-rule-usage/cart-rule-usage-service.js"></script>
176+
<script simpl-append-version="true" src="~/modules/pricing/admin/cart-rule-usage/cart-rule-usage-list.js"></script>
175177

176178
<script simpl-append-version="true" src="~/modules/tax/admin/tax.module.js"></script>
177179
<script simpl-append-version="true" src="~/modules/tax/admin/tax-class/tax-class-service.js"></script>
@@ -272,7 +274,8 @@
272274
<li class="dropdown">
273275
<a href="javascript:void(0)" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">@Localizer["Promotions"] <span class="caret"></span></a>
274276
<ul class="dropdown-menu">
275-
<li><a ui-sref="cartrule">@Localizer["Cart Price Rules"]</a></li>
277+
<li><a ui-sref="cart-rules">@Localizer["Cart Price Rules"]</a></li>
278+
<li><a ui-sref="cart-rule-usages">@Localizer["Cart Price Rule Usages"]</a></li>
276279
</ul>
277280
</li>
278281
<li class="dropdown">

src/Modules/SimplCommerce.Module.Orders/Services/OrderService.cs

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,10 @@ public async Task<Result<Order>> CreateOrder(User user, string paymentMethod, st
108108
return Result.Fail<Order>($"Cart of user {user.Id} cannot be found");
109109
}
110110

111-
var applyDiscountResult = await ApplyDiscountIfAny(user, cart);
112-
if (!applyDiscountResult.Succeeded)
111+
var checkingDiscountResult = await CheckForDiscountIfAny(user, cart);
112+
if (!checkingDiscountResult.Succeeded)
113113
{
114-
return Result.Fail<Order>(applyDiscountResult.ErrorMessage);
114+
return Result.Fail<Order>(checkingDiscountResult.ErrorMessage);
115115
}
116116

117117
var validateShippingMethodResult = await ValidateShippingMethod(shippingMethodName, shippingAddress, cart);
@@ -178,7 +178,7 @@ public async Task<Result<Order>> CreateOrder(User user, string paymentMethod, st
178178
orderItem.ProductPrice = orderItem.ProductPrice - orderItem.TaxAmount;
179179
}
180180

181-
var discountedItem = applyDiscountResult.DiscountedProducts.FirstOrDefault(x => x.Id == cartItem.ProductId);
181+
var discountedItem = checkingDiscountResult.DiscountedProducts.FirstOrDefault(x => x.Id == cartItem.ProductId);
182182
if(discountedItem != null)
183183
{
184184
orderItem.DiscountAmount = discountedItem.DiscountAmount;
@@ -189,14 +189,14 @@ public async Task<Result<Order>> CreateOrder(User user, string paymentMethod, st
189189
}
190190

191191
order.OrderStatus = orderStatus;
192-
order.CouponCode = applyDiscountResult.CouponCode;
192+
order.CouponCode = checkingDiscountResult.CouponCode;
193193
order.CouponRuleName = cart.CouponRuleName;
194-
order.DiscountAmount = applyDiscountResult.DiscountAmount;
194+
order.DiscountAmount = checkingDiscountResult.DiscountAmount;
195195
order.ShippingAmount = shippingMethod.Price;
196196
order.ShippingMethod = shippingMethod.Name;
197197
order.TaxAmount = order.OrderItems.Sum(x => x.TaxAmount);
198198
order.SubTotal = order.OrderItems.Sum(x => x.ProductPrice * x.Quantity);
199-
order.SubTotalWithDiscount = order.SubTotal - applyDiscountResult.DiscountAmount;
199+
order.SubTotalWithDiscount = order.SubTotal - checkingDiscountResult.DiscountAmount;
200200
order.OrderTotal = order.SubTotal + order.TaxAmount + order.ShippingAmount - order.DiscountAmount;
201201
_orderRepository.Add(order);
202202

@@ -241,7 +241,14 @@ public async Task<Result<Order>> CreateOrder(User user, string paymentMethod, st
241241
_orderRepository.Add(subOrder);
242242
}
243243

244-
_orderRepository.SaveChanges();
244+
using (var transaction = _orderRepository.BeginTransaction())
245+
{
246+
_orderRepository.SaveChanges();
247+
_couponService.AddCouponUsage(user.Id, order.Id, checkingDiscountResult);
248+
_orderRepository.SaveChanges();
249+
transaction.Commit();
250+
}
251+
245252
// await _orderEmailService.SendEmailToUser(user, order);
246253
return Result.Ok(order);
247254
}
@@ -288,7 +295,7 @@ public async Task<decimal> GetTax(long cartOwnerUserId, long countryId, long sta
288295
return taxAmount;
289296
}
290297

291-
private async Task<CouponValidationResult> ApplyDiscountIfAny(User user, Cart cart)
298+
private async Task<CouponValidationResult> CheckForDiscountIfAny(User user, Cart cart)
292299
{
293300
if (string.IsNullOrWhiteSpace(cart.CouponCode))
294301
{
@@ -301,17 +308,6 @@ private async Task<CouponValidationResult> ApplyDiscountIfAny(User user, Cart ca
301308
};
302309

303310
var couponValidationResult = await _couponService.Validate(cart.CouponCode, cartInfoForCoupon);
304-
if (couponValidationResult.Succeeded)
305-
{
306-
foreach (var item in couponValidationResult.DiscountedProducts)
307-
{
308-
for (var i = 0; i < item.Quantity; i++)
309-
{
310-
_couponService.AddCouponUsage(user.Id, couponValidationResult.CouponId);
311-
}
312-
}
313-
}
314-
315311
return couponValidationResult;
316312
}
317313

src/Modules/SimplCommerce.Module.Pricing/Controllers/CartRuleApiController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
namespace SimplCommerce.Module.Pricing.Controllers
1212
{
1313
[Authorize(Roles = "admin")]
14-
[Route("api/cartrules")]
14+
[Route("api/cart-rules")]
1515
public class CartRuleApiController : Controller
1616
{
1717
private readonly IRepository<CartRule> _cartRuleRepository;
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using System;
2+
using System.Linq;
3+
using Microsoft.AspNetCore.Authorization;
4+
using Microsoft.AspNetCore.Mvc;
5+
using SimplCommerce.Infrastructure.Data;
6+
using SimplCommerce.Infrastructure.Web.SmartTable;
7+
using SimplCommerce.Module.Pricing.Models;
8+
9+
namespace SimplCommerce.Module.Pricing.Controllers
10+
{
11+
[Authorize(Roles = "admin")]
12+
[Route("api/cart-rule-usages")]
13+
public class CartRuleUsageApiController : Controller
14+
{
15+
private readonly IRepository<CartRuleUsage> _cartRuleUsageRepository;
16+
17+
public CartRuleUsageApiController(IRepository<CartRuleUsage> cartRuleUsageRepository)
18+
{
19+
_cartRuleUsageRepository = cartRuleUsageRepository;
20+
}
21+
22+
[HttpPost("grid")]
23+
public IActionResult List([FromBody] SmartTableParam param)
24+
{
25+
IQueryable<CartRuleUsage> query = _cartRuleUsageRepository
26+
.Query();
27+
28+
if (param.Search.PredicateObject != null)
29+
{
30+
dynamic search = param.Search.PredicateObject;
31+
if (search.RuleName != null)
32+
{
33+
string ruleName = search.RuleName;
34+
query = query.Where(x => x.CartRule.Name.Contains(ruleName));
35+
}
36+
37+
if (search.CouponCode != null)
38+
{
39+
string couponCode = search.CouponCode;
40+
query = query.Where(x => x.Coupon.Code.Contains(couponCode));
41+
}
42+
43+
if (search.FullName != null)
44+
{
45+
string fullName = search.FullName;
46+
query = query.Where(x => x.User.FullName.Contains(fullName));
47+
}
48+
49+
if (search.CreatedOn != null)
50+
{
51+
if (search.CreatedOn.before != null)
52+
{
53+
DateTimeOffset before = search.CreatedOn.before;
54+
query = query.Where(x => x.CreatedOn <= before);
55+
}
56+
57+
if (search.CreatedOn.after != null)
58+
{
59+
DateTimeOffset after = search.CreatedOn.after;
60+
query = query.Where(x => x.CreatedOn >= after);
61+
}
62+
}
63+
}
64+
65+
var cartRuleUsages = query.ToSmartTableResult(
66+
param,
67+
x => new
68+
{
69+
x.Id,
70+
x.CartRuleId,
71+
CartRuleName = x.CartRule.Name,
72+
x.UserId,
73+
x.User.FullName,
74+
CouponCode = x.Coupon.Code,
75+
x.OrderId,
76+
x.CreatedOn
77+
});
78+
79+
return Json(cartRuleUsages);
80+
}
81+
}
82+
}

src/Modules/SimplCommerce.Module.Pricing/Models/CartRuleUsage.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,25 @@ namespace SimplCommerce.Module.Pricing.Models
66
{
77
public class CartRuleUsage : EntityBase
88
{
9+
public CartRuleUsage()
10+
{
11+
CreatedOn = DateTimeOffset.Now;
12+
}
13+
914
public long CartRuleId { get; set; }
1015

1116
public CartRule CartRule { get; set; }
1217

18+
public long? CouponId { get; set; }
19+
20+
public Coupon Coupon { get; set; }
21+
1322
public long UserId { get; set; }
1423

1524
public User User { get; set; }
1625

1726
public long OrderId { get; set; }
1827

19-
public DateTimeOffset UsedOn { get; set; }
28+
public DateTimeOffset CreatedOn { get; set; }
2029
}
2130
}

src/Modules/SimplCommerce.Module.Pricing/Models/CouponUsage.cs

Lines changed: 0 additions & 24 deletions
This file was deleted.

src/Modules/SimplCommerce.Module.Pricing/Services/CouponService.cs

Lines changed: 54 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ namespace SimplCommerce.Module.Pricing.Services
1313
public class CouponService : ICouponService
1414
{
1515
private readonly IRepository<Coupon> _couponRepository;
16-
private readonly IRepository<CouponUsage> _couponUsageRepository;
16+
private readonly IRepository<CartRuleUsage> _cartRuleUsageRepository;
1717
private readonly IRepository<Product> _productRepository;
1818
private readonly IWorkContext _workContext;
1919

20-
public CouponService(IRepository<Coupon> couponRepository, IRepository<CouponUsage> couponUsageRepository, IRepository<Product> productRespository, IWorkContext workContext)
20+
public CouponService(IRepository<Coupon> couponRepository, IRepository<CartRuleUsage> cartRuleUsageRepository, IRepository<Product> productRespository, IWorkContext workContext)
2121
{
2222
_couponRepository = couponRepository;
23-
_couponUsageRepository = couponUsageRepository;
23+
_cartRuleUsageRepository = cartRuleUsageRepository;
2424
_productRepository = productRespository;
2525
_workContext = workContext;
2626
}
@@ -51,15 +51,15 @@ public async Task<CouponValidationResult> Validate(string couponCode, CartInfoFo
5151
return validationResult;
5252
}
5353

54-
var couponUsageCount = _couponUsageRepository.Query().Count(x => x.CouponId == coupon.Id);
54+
var couponUsageCount = _cartRuleUsageRepository.Query().Count(x => x.CouponId == coupon.Id);
5555
if(coupon.CartRule.UsageLimitPerCoupon.HasValue && couponUsageCount >= coupon.CartRule.UsageLimitPerCoupon)
5656
{
5757
validationResult.ErrorMessage = $"The coupon {couponCode} is all used.";
5858
return validationResult;
5959
}
6060

6161
var currentCustomer = await _workContext.GetCurrentUser();
62-
var couponUsageByCustomerCount = _couponUsageRepository.Query().Count(x => x.CouponId == coupon.Id && x.UserId == currentCustomer.Id);
62+
var couponUsageByCustomerCount = _cartRuleUsageRepository.Query().Count(x => x.CouponId == coupon.Id && x.UserId == currentCustomer.Id);
6363
if (coupon.CartRule.UsageLimitPerCustomer.HasValue && couponUsageByCustomerCount >= coupon.CartRule.UsageLimitPerCustomer)
6464
{
6565
validationResult.ErrorMessage = $"You can use the coupon {couponCode} only {coupon.CartRule.UsageLimitPerCustomer} times";
@@ -116,27 +116,27 @@ public async Task<CouponValidationResult> Validate(string couponCode, CartInfoFo
116116
return validationResult;
117117
}
118118

119+
validationResult.Succeeded = true;
120+
validationResult.CouponId = coupon.Id;
121+
validationResult.CouponCode = coupon.Code;
122+
validationResult.CouponRuleName = coupon.CartRule.Name;
123+
validationResult.CartRule = coupon.CartRule;
124+
119125
switch (coupon.CartRule.RuleToApply)
120126
{
121127
case "cart_fixed":
122-
validationResult.Succeeded = true;
123-
validationResult.CouponId = coupon.Id;
124-
validationResult.CouponCode = coupon.Code;
125-
validationResult.CouponRuleName = coupon.CartRule.Name;
126128
validationResult.DiscountAmount = coupon.CartRule.DiscountAmount;
127129
return validationResult;
130+
128131
case "by_percent":
129-
validationResult.Succeeded = true;
130-
validationResult.CouponId = coupon.Id;
131-
validationResult.CouponCode = coupon.Code;
132-
validationResult.CouponRuleName = coupon.CartRule.Name;
133132
foreach(var item in validationResult.DiscountedProducts)
134133
{
135134
item.DiscountAmount = (item.Price * coupon.CartRule.DiscountAmount / 100) * item.Quantity;
136135
}
137136

138137
validationResult.DiscountAmount = validationResult.DiscountedProducts.Sum(x => x.DiscountAmount);
139138
return validationResult;
139+
140140
default:
141141
throw new InvalidOperationException($"{coupon.CartRule.RuleToApply} is not supported");
142142
}
@@ -165,15 +165,50 @@ private IList<DiscountableProduct> GetDiscountableProduct(IList<CartRuleProduct>
165165
return discountedProducts;
166166
}
167167

168-
public void AddCouponUsage(long userId, long couponId)
168+
public void AddCouponUsage(long userId, long orderId, CouponValidationResult couponValidationResult)
169169
{
170-
var couponUsage = new CouponUsage
170+
if (!couponValidationResult.Succeeded || couponValidationResult.CartRule == null)
171+
{
172+
return;
173+
}
174+
175+
CartRuleUsage couponUsage;
176+
switch (couponValidationResult.CartRule.RuleToApply)
171177
{
172-
CouponId = couponId,
173-
UserId = userId
174-
};
178+
case "cart_fixed":
179+
couponUsage = new CartRuleUsage
180+
{
181+
UserId = userId,
182+
OrderId = orderId,
183+
CouponId = couponValidationResult.CouponId,
184+
CartRuleId = couponValidationResult.CartRule.Id
185+
};
186+
187+
_cartRuleUsageRepository.Add(couponUsage);
188+
break;
175189

176-
_couponUsageRepository.Add(couponUsage);
190+
case "by_percent":
191+
foreach (var item in couponValidationResult.DiscountedProducts)
192+
{
193+
for (var i = 0; i < item.Quantity; i++)
194+
{
195+
couponUsage = new CartRuleUsage
196+
{
197+
UserId = userId,
198+
OrderId = orderId,
199+
CouponId = couponValidationResult.CouponId,
200+
CartRuleId = couponValidationResult.CartRule.Id
201+
};
202+
203+
_cartRuleUsageRepository.Add(couponUsage);
204+
}
205+
}
206+
207+
break;
208+
209+
default:
210+
throw new InvalidOperationException($"{couponValidationResult.CartRule.RuleToApply} is not supported");
211+
}
177212
}
178213
}
179214
}

src/Modules/SimplCommerce.Module.Pricing/Services/CouponValidationResult.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using SimplCommerce.Module.Pricing.Models;
2+
using System.Collections.Generic;
23

34
namespace SimplCommerce.Module.Pricing.Services
45
{
@@ -16,6 +17,8 @@ public class CouponValidationResult
1617

1718
public long CouponId { get; set; }
1819

20+
public CartRule CartRule { get; set; }
21+
1922
public IList<DiscountedProduct> DiscountedProducts { get; set; } = new List<DiscountedProduct>();
2023
}
2124
}

0 commit comments

Comments
 (0)