Browse Source

Implement the coupon feature

pull/103/head
gdlcf88 6 years ago
parent
commit
77b813e0df
  1. 12
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/IOrderDiscountProvider.cs
  2. 11
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/OrderAppService.cs
  3. 12
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/DiscountAmountOverflowException.cs
  4. 10
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/IOrderDiscountProvider.cs
  5. 2
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/IOrderManager.cs
  6. 13
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/Order.cs
  7. 15
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderLine.cs
  8. 13
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderManager.cs
  9. 3
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/cs.json
  10. 3
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/en.json
  11. 3
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pl.json
  12. 3
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pt-BR.json
  13. 3
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/sl.json
  14. 3
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/tr.json
  15. 3
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/vi.json
  16. 3
      modules/EasyAbp.EShop.Stores/src/EasyAbp.EShop.Stores.Domain.Shared/EasyAbp/EShop/Stores/Localization/Stores/cs.json
  17. 3
      modules/EasyAbp.EShop.Stores/src/EasyAbp.EShop.Stores.Domain.Shared/EasyAbp/EShop/Stores/Localization/Stores/en.json
  18. 3
      modules/EasyAbp.EShop.Stores/src/EasyAbp.EShop.Stores.Domain.Shared/EasyAbp/EShop/Stores/Localization/Stores/pl.json
  19. 3
      modules/EasyAbp.EShop.Stores/src/EasyAbp.EShop.Stores.Domain.Shared/EasyAbp/EShop/Stores/Localization/Stores/pt-BR.json
  20. 3
      modules/EasyAbp.EShop.Stores/src/EasyAbp.EShop.Stores.Domain.Shared/EasyAbp/EShop/Stores/Localization/Stores/sl.json
  21. 3
      modules/EasyAbp.EShop.Stores/src/EasyAbp.EShop.Stores.Domain.Shared/EasyAbp/EShop/Stores/Localization/Stores/tr.json
  22. 3
      modules/EasyAbp.EShop.Stores/src/EasyAbp.EShop.Stores.Domain.Shared/EasyAbp/EShop/Stores/Localization/Stores/vi.json
  23. 2
      plugins/Coupons/src/EasyAbp.EShop.Orders.Plugins.Coupons/EasyAbp.EShop.Orders.Plugins.Coupons.csproj
  24. 100
      plugins/Coupons/src/EasyAbp.EShop.Orders.Plugins.Coupons/EasyAbp/EShop/Orders/Plugins/Coupons/Authorization/CouponOrderCreationAuthorizationHandler.cs
  25. 9
      plugins/Coupons/src/EasyAbp.EShop.Orders.Plugins.Coupons/EasyAbp/EShop/Orders/Plugins/Coupons/EShopOrdersPluginsCouponsModule.cs
  26. 12
      plugins/Coupons/src/EasyAbp.EShop.Orders.Plugins.Coupons/EasyAbp/EShop/Orders/Plugins/Coupons/OrderDiscount/CouponNotFoundOrHasExpiredException.cs
  27. 151
      plugins/Coupons/src/EasyAbp.EShop.Orders.Plugins.Coupons/EasyAbp/EShop/Orders/Plugins/Coupons/OrderDiscount/CouponOrderDiscountProvider.cs
  28. 12
      plugins/Coupons/src/EasyAbp.EShop.Orders.Plugins.Coupons/EasyAbp/EShop/Orders/Plugins/Coupons/OrderDiscount/CouponTemplateNotFoundOrUnavailableException.cs
  29. 12
      plugins/Coupons/src/EasyAbp.EShop.Orders.Plugins.Coupons/EasyAbp/EShop/Orders/Plugins/Coupons/OrderDiscount/OrderDoesNotMeetCouponUsageConditionException.cs
  30. 2
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application.Contracts/EasyAbp/EShop/Plugins/Coupons/CouponTemplates/Dtos/CouponTemplateDto.cs
  31. 27
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application.Contracts/EasyAbp/EShop/Plugins/Coupons/CouponTemplates/Dtos/CreateUpdateCouponTemplateDto.cs
  32. 11
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application.Contracts/EasyAbp/EShop/Plugins/Coupons/Coupons/CouponHasBeenOccupiedException.cs
  33. 2
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application.Contracts/EasyAbp/EShop/Plugins/Coupons/Coupons/Dtos/CreateCouponDto.cs
  34. 13
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application.Contracts/EasyAbp/EShop/Plugins/Coupons/Coupons/Dtos/GetCouponListInput.cs
  35. 10
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application.Contracts/EasyAbp/EShop/Plugins/Coupons/Coupons/Dtos/OccupyCouponInput.cs
  36. 9
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application.Contracts/EasyAbp/EShop/Plugins/Coupons/Coupons/Dtos/UpdateCouponDto.cs
  37. 9
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application.Contracts/EasyAbp/EShop/Plugins/Coupons/Coupons/ICouponAppService.cs
  38. 2
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application.Contracts/EasyAbp/EShop/Plugins/Coupons/Permissions/CouponsPermissionDefinitionProvider.cs
  39. 2
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application.Contracts/EasyAbp/EShop/Plugins/Coupons/Permissions/CouponsPermissions.cs
  40. 12
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application/EasyAbp/EShop/Plugins/Coupons/CouponTemplates/CouponTemplateAppService.cs
  41. 54
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application/EasyAbp/EShop/Plugins/Coupons/Coupons/CouponAppService.cs
  42. 3
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application/EasyAbp/EShop/Plugins/Coupons/CouponsApplicationAutoMapperProfile.cs
  43. 6
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/CouponTemplates/CouponTemplateData.cs
  44. 5
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/CouponTemplates/CouponType.cs
  45. 2
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/CouponTemplates/ICouponTemplate.cs
  46. 4
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/Coupons/CouponData.cs
  47. 2
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/Coupons/ICouponLookupService.cs
  48. 57
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/Localization/cs.json
  49. 57
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/Localization/en.json
  50. 56
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/Localization/pl-PL.json
  51. 56
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/Localization/pt-BR.json
  52. 57
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/Localization/sl.json
  53. 57
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/Localization/tr.json
  54. 56
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/Localization/vi.json
  55. 71
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/Localization/zh-Hans.json
  56. 71
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/Localization/zh-Hant.json
  57. 1
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain/EasyAbp.EShop.Plugins.Coupons.Domain.csproj
  58. 8
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain/EasyAbp/EShop/Plugins/Coupons/CouponTemplates/CouponTemplate.cs
  59. 7
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain/EasyAbp/EShop/Plugins/Coupons/Coupons/Coupon.cs
  60. 37
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain/EasyAbp/EShop/Plugins/Coupons/Coupons/CouponOrderCanceledEventHandler.cs
  61. 49
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain/EasyAbp/EShop/Plugins/Coupons/Coupons/CouponOrderCreatedEventHandler.cs
  62. 3
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain/EasyAbp/EShop/Plugins/Coupons/Coupons/ICouponRepository.cs
  63. 13
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain/EasyAbp/EShop/Plugins/Coupons/Coupons/InvalidCouponOrderIdException.cs
  64. 9
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain/EasyAbp/EShop/Plugins/Coupons/Coupons/RepositoryCouponLookupService.cs
  65. 6
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain/EasyAbp/EShop/Plugins/Coupons/EShopPluginsCouponsDomainModule.cs
  66. 7
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.EntityFrameworkCore/EasyAbp/EShop/Plugins/Coupons/CouponTemplates/CouponTemplateRepository.cs
  67. 16
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.EntityFrameworkCore/EasyAbp/EShop/Plugins/Coupons/Coupons/CouponRepository.cs
  68. 9
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.HttpApi.Client/EasyAbp/EShop/Plugins/Coupons/Coupons/HttpClientCouponLookupService.cs
  69. 13
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.HttpApi/EasyAbp/EShop/Plugins/Coupons/Coupons/CouponController.cs
  70. 5
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Web/CouponsWebAutoMapperProfile.cs
  71. 2
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Web/Pages/EShop/Plugins/Coupons/CouponTemplates/CouponTemplate/Index.cshtml
  72. 3
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Web/Pages/EShop/Plugins/Coupons/CouponTemplates/CouponTemplate/ViewModels/CreateEditCouponTemplateViewModel.cs
  73. 1
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Web/Pages/EShop/Plugins/Coupons/CouponTemplates/CouponTemplate/index.js
  74. 4
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Web/Pages/EShop/Plugins/Coupons/Coupons/Coupon/CreateModal.cshtml.cs
  75. 6
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Web/Pages/EShop/Plugins/Coupons/Coupons/Coupon/EditModal.cshtml.cs
  76. 3
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Web/Pages/EShop/Plugins/Coupons/Coupons/Coupon/ViewModels/CreateCouponViewModel.cs
  77. 11
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Web/Pages/EShop/Plugins/Coupons/Coupons/Coupon/ViewModels/EditCouponViewModel.cs
  78. 4556
      samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore.DbMigrations/Migrations/20200927094647_RemovedIsCrossProductAllowed.Designer.cs
  79. 24
      samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore.DbMigrations/Migrations/20200927094647_RemovedIsCrossProductAllowed.cs
  80. 3
      samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore.DbMigrations/Migrations/EShopSampleMigrationsDbContextModelSnapshot.cs
  81. 7
      samples/EShopSample/aspnet-core/src/EShopSample.Web/EShopSampleWebModule.cs

12
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/IOrderDiscountProvider.cs

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using EasyAbp.EShop.Products.Products.Dtos;
namespace EasyAbp.EShop.Orders.Orders
{
public interface IOrderDiscountProvider
{
Task<Order> DiscountAsync(Order order, Dictionary<Guid, ProductDto> productDict);
}
}

11
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/OrderAppService.cs

@ -8,6 +8,7 @@ using EasyAbp.EShop.Products.Products;
using EasyAbp.EShop.Products.Products.Dtos;
using EasyAbp.EShop.Stores.Stores;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Users;
@ -99,12 +100,20 @@ namespace EasyAbp.EShop.Orders.Orders
var order = await _newOrderGenerator.GenerateAsync(CurrentUser.GetId(), input, productDict);
await _orderManager.DiscountAsync(order);
await DiscountOrderAsync(order, productDict);
await Repository.InsertAsync(order, autoSave: true);
return await MapToGetOutputDtoAsync(order);
}
protected virtual async Task DiscountOrderAsync(Order order, Dictionary<Guid, ProductDto> productDict)
{
foreach (var provider in ServiceProvider.GetServices<IOrderDiscountProvider>())
{
await provider.DiscountAsync(order, productDict);
}
}
protected virtual async Task<Dictionary<Guid, ProductDto>> GetProductDictionaryAsync(
IEnumerable<Guid> productIds, Guid storeId)

12
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/DiscountAmountOverflowException.cs

@ -0,0 +1,12 @@
using Volo.Abp;
namespace EasyAbp.EShop.Orders.Orders
{
public class DiscountAmountOverflowException : BusinessException
{
public DiscountAmountOverflowException()
: base("DiscountAmountOverflow", $"The discount amount overflow.")
{
}
}
}

10
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/IOrderDiscountProvider.cs

@ -1,10 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace EasyAbp.EShop.Orders.Orders
{
public interface IOrderDiscountProvider
{
Task<Order> DiscountAsync(Order order);
}
}

2
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/IOrderManager.cs

@ -6,8 +6,6 @@ namespace EasyAbp.EShop.Orders.Orders
{
public interface IOrderManager : IDomainService
{
Task<Order> DiscountAsync(Order order);
Task<Order> CompleteAsync(Order order);
Task<Order> CancelAsync(Order order, string cancellationReason);

13
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/Order.cs

@ -165,16 +165,19 @@ namespace EasyAbp.EShop.Orders.Orders
return !(!PaymentId.HasValue || PaidTime.HasValue);
}
public decimal AddDiscountGetRealDiscountedAmount(Guid orderLineId, decimal expectedDiscountAmount)
public void AddDiscount(Guid orderLineId, decimal expectedDiscountAmount)
{
var orderLine = OrderLines.Single(x => x.Id == orderLineId);
var realDiscountedAmount = orderLine.AddDiscountGetRealDiscountedAmount(expectedDiscountAmount);
orderLine.AddDiscount(expectedDiscountAmount);
TotalDiscount += realDiscountedAmount;
ActualTotalPrice -= realDiscountedAmount;
TotalDiscount += expectedDiscountAmount;
ActualTotalPrice -= expectedDiscountAmount;
return realDiscountedAmount;
if (ActualTotalPrice < decimal.Zero)
{
throw new DiscountAmountOverflowException();
}
}
}
}

15
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderLine.cs

@ -113,16 +113,15 @@ namespace EasyAbp.EShop.Orders.Orders
RefundAmount += amount;
}
internal decimal AddDiscountGetRealDiscountedAmount(decimal expectedDiscountAmount)
internal void AddDiscount(decimal expectedDiscountAmount)
{
var realDiscountedAmount = ActualTotalPrice - expectedDiscountAmount >= decimal.Zero
? ActualTotalPrice - expectedDiscountAmount
: ActualTotalPrice;
TotalDiscount += realDiscountedAmount;
ActualTotalPrice -= realDiscountedAmount;
TotalDiscount += expectedDiscountAmount;
ActualTotalPrice -= expectedDiscountAmount;
return realDiscountedAmount;
if (ActualTotalPrice < decimal.Zero)
{
throw new DiscountAmountOverflowException();
}
}
}
}

13
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderManager.cs

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Domain.Services;
using Volo.Abp.EventBus.Distributed;
@ -30,16 +29,6 @@ namespace EasyAbp.EShop.Orders.Orders
_unitOfWorkManager = unitOfWorkManager;
_orderRepository = orderRepository;
}
public virtual async Task<Order> DiscountAsync(Order order)
{
foreach (var provider in ServiceProvider.GetServices<IOrderDiscountProvider>())
{
await provider.DiscountAsync(order);
}
return order;
}
public virtual async Task<Order> CompleteAsync(Order order)
{

3
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/cs.json

@ -56,6 +56,9 @@
"EditProduct": "Edit",
"ProductDeletionConfirmationMessage": "Are you sure to delete the product {0}?",
"SuccessfullyDeleted": "Successfully deleted",
"CouponType.Normal": "X discount",
"CouponType.PerMeet": "X discount for every Y",
"CouponType.Custom": "Custom",
"Menu:Category": "Category",
"Category": "Category",
"CategoryUniqueName": "Unique name",

3
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/en.json

@ -57,6 +57,9 @@
"EditProduct": "Edit",
"ProductDeletionConfirmationMessage": "Are you sure to delete the product {0}?",
"SuccessfullyDeleted": "Successfully deleted",
"CouponType.Normal": "X discount",
"CouponType.PerMeet": "X discount for every Y",
"CouponType.Custom": "Custom",
"Menu:Category": "Category",
"Category": "Category",
"CategoryUniqueName": "Unique name",

3
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pl.json

@ -56,6 +56,9 @@
"EditProduct": "Edit",
"ProductDeletionConfirmationMessage": "Are you sure to delete the product {0}?",
"SuccessfullyDeleted": "Successfully deleted",
"CouponType.Normal": "X discount",
"CouponType.PerMeet": "X discount for every Y",
"CouponType.Custom": "Custom",
"Menu:Category": "Category",
"Category": "Category",
"CategoryUniqueName": "Unique name",

3
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pt-BR.json

@ -56,6 +56,9 @@
"EditProduct": "Edit",
"ProductDeletionConfirmationMessage": "Are you sure to delete the product {0}?",
"SuccessfullyDeleted": "Successfully deleted",
"CouponType.Normal": "X discount",
"CouponType.PerMeet": "X discount for every Y",
"CouponType.Custom": "Custom",
"Menu:Category": "Category",
"Category": "Category",
"CategoryUniqueName": "Unique name",

3
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/sl.json

@ -57,6 +57,9 @@
"EditProduct": "Edit",
"ProductDeletionConfirmationMessage": "Are you sure to delete the product {0}?",
"SuccessfullyDeleted": "Successfully deleted",
"CouponType.Normal": "X discount",
"CouponType.PerMeet": "X discount for every Y",
"CouponType.Custom": "Custom",
"Menu:Category": "Category",
"Category": "Category",
"CategoryUniqueName": "Unique name",

3
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/tr.json

@ -57,6 +57,9 @@
"EditProduct": "Edit",
"ProductDeletionConfirmationMessage": "Are you sure to delete the product {0}?",
"SuccessfullyDeleted": "Successfully deleted",
"CouponType.Normal": "X discount",
"CouponType.PerMeet": "X discount for every Y",
"CouponType.Custom": "Custom",
"Menu:Category": "Category",
"Category": "Category",
"CategoryUniqueName": "Unique name",

3
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/vi.json

@ -56,6 +56,9 @@
"EditProduct": "Edit",
"ProductDeletionConfirmationMessage": "Are you sure to delete the product {0}?",
"SuccessfullyDeleted": "Successfully deleted",
"CouponType.Normal": "X discount",
"CouponType.PerMeet": "X discount for every Y",
"CouponType.Custom": "Custom",
"Menu:Category": "Category",
"Category": "Category",
"CategoryUniqueName": "Unique name",

3
modules/EasyAbp.EShop.Stores/src/EasyAbp.EShop.Stores.Domain.Shared/EasyAbp/EShop/Stores/Localization/Stores/cs.json

@ -10,6 +10,9 @@
"EditStore": "Edit",
"StoreDeletionConfirmationMessage": "Are you sure to delete the store {0}?",
"SuccessfullyDeleted": "Successfully deleted",
"CouponType.Normal": "X discount",
"CouponType.PerMeet": "X discount for every Y",
"CouponType.Custom": "Custom",
"StoreOwner": "Store owner",
"Menu:StoreOwner": "Store owner",
"StoreOwnerUserName": "Username",

3
modules/EasyAbp.EShop.Stores/src/EasyAbp.EShop.Stores.Domain.Shared/EasyAbp/EShop/Stores/Localization/Stores/en.json

@ -11,6 +11,9 @@
"EditStore": "Edit",
"StoreDeletionConfirmationMessage": "Are you sure to delete the store {0}?",
"SuccessfullyDeleted": "Successfully deleted",
"CouponType.Normal": "X discount",
"CouponType.PerMeet": "X discount for every Y",
"CouponType.Custom": "Custom",
"StoreOwner": "Store owner",
"Menu:StoreOwner": "Store owner",
"StoreOwnerUserName": "Username",

3
modules/EasyAbp.EShop.Stores/src/EasyAbp.EShop.Stores.Domain.Shared/EasyAbp/EShop/Stores/Localization/Stores/pl.json

@ -10,6 +10,9 @@
"EditStore": "Edit",
"StoreDeletionConfirmationMessage": "Are you sure to delete the store {0}?",
"SuccessfullyDeleted": "Successfully deleted",
"CouponType.Normal": "X discount",
"CouponType.PerMeet": "X discount for every Y",
"CouponType.Custom": "Custom",
"StoreOwner": "Store owner",
"Menu:StoreOwner": "Store owner",
"StoreOwnerUserName": "Username",

3
modules/EasyAbp.EShop.Stores/src/EasyAbp.EShop.Stores.Domain.Shared/EasyAbp/EShop/Stores/Localization/Stores/pt-BR.json

@ -10,6 +10,9 @@
"EditStore": "Edit",
"StoreDeletionConfirmationMessage": "Are you sure to delete the store {0}?",
"SuccessfullyDeleted": "Successfully deleted",
"CouponType.Normal": "X discount",
"CouponType.PerMeet": "X discount for every Y",
"CouponType.Custom": "Custom",
"StoreOwner": "Store owner",
"Menu:StoreOwner": "Store owner",
"StoreOwnerUserName": "Username",

3
modules/EasyAbp.EShop.Stores/src/EasyAbp.EShop.Stores.Domain.Shared/EasyAbp/EShop/Stores/Localization/Stores/sl.json

@ -11,6 +11,9 @@
"EditStore": "Edit",
"StoreDeletionConfirmationMessage": "Are you sure to delete the store {0}?",
"SuccessfullyDeleted": "Successfully deleted",
"CouponType.Normal": "X discount",
"CouponType.PerMeet": "X discount for every Y",
"CouponType.Custom": "Custom",
"StoreOwner": "Store owner",
"Menu:StoreOwner": "Store owner",
"StoreOwnerUserName": "Username",

3
modules/EasyAbp.EShop.Stores/src/EasyAbp.EShop.Stores.Domain.Shared/EasyAbp/EShop/Stores/Localization/Stores/tr.json

@ -11,6 +11,9 @@
"EditStore": "Edit",
"StoreDeletionConfirmationMessage": "Are you sure to delete the store {0}?",
"SuccessfullyDeleted": "Successfully deleted",
"CouponType.Normal": "X discount",
"CouponType.PerMeet": "X discount for every Y",
"CouponType.Custom": "Custom",
"StoreOwner": "Store owner",
"Menu:StoreOwner": "Store owner",
"StoreOwnerUserName": "Username",

3
modules/EasyAbp.EShop.Stores/src/EasyAbp.EShop.Stores.Domain.Shared/EasyAbp/EShop/Stores/Localization/Stores/vi.json

@ -10,6 +10,9 @@
"EditStore": "Edit",
"StoreDeletionConfirmationMessage": "Are you sure to delete the store {0}?",
"SuccessfullyDeleted": "Successfully deleted",
"CouponType.Normal": "X discount",
"CouponType.PerMeet": "X discount for every Y",
"CouponType.Custom": "Custom",
"StoreOwner": "Store owner",
"Menu:StoreOwner": "Store owner",
"StoreOwnerUserName": "Username",

2
plugins/Coupons/src/EasyAbp.EShop.Orders.Plugins.Coupons/EasyAbp.EShop.Orders.Plugins.Coupons.csproj

@ -9,7 +9,7 @@
<ItemGroup>
<ProjectReference Include="..\..\..\..\modules\EasyAbp.EShop.Orders\src\EasyAbp.EShop.Orders.Application\EasyAbp.EShop.Orders.Application.csproj" />
<ProjectReference Include="..\EasyAbp.EShop.Plugins.Coupons.Domain.Shared\EasyAbp.EShop.Plugins.Coupons.Domain.Shared.csproj" />
<ProjectReference Include="..\EasyAbp.EShop.Plugins.Coupons.Application.Contracts\EasyAbp.EShop.Plugins.Coupons.Application.Contracts.csproj" />
</ItemGroup>
</Project>

100
plugins/Coupons/src/EasyAbp.EShop.Orders.Plugins.Coupons/EasyAbp/EShop/Orders/Plugins/Coupons/Authorization/CouponOrderCreationAuthorizationHandler.cs

@ -1,100 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EasyAbp.EShop.Orders.Orders;
using EasyAbp.EShop.Orders.Orders.Dtos;
using EasyAbp.EShop.Plugins.Coupons;
using EasyAbp.EShop.Plugins.Coupons.Coupons;
using EasyAbp.EShop.Plugins.Coupons.CouponTemplates;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Data;
using Volo.Abp.Timing;
namespace EasyAbp.EShop.Orders.Plugins.Coupons.Authorization
{
public class CouponOrderCreationAuthorizationHandler : OrderCreationAuthorizationHandler
{
private readonly IClock _clock;
private readonly ICouponLookupService _couponLookupService;
private readonly ICouponTemplateLookupService _couponTemplateLookupService;
public CouponOrderCreationAuthorizationHandler(
IClock clock,
ICouponLookupService couponLookupService,
ICouponTemplateLookupService couponTemplateLookupService)
{
_clock = clock;
_couponLookupService = couponLookupService;
_couponTemplateLookupService = couponTemplateLookupService;
}
protected override async Task HandleOrderCreationAsync(AuthorizationHandlerContext context,
OrderOperationAuthorizationRequirement requirement, OrderCreationResource resource)
{
if (!Guid.TryParse(resource.Input.GetProperty<string>(CouponsConsts.OrderCouponIdPropertyName),
out var couponId))
{
return;
}
var now = _clock.Now;
var coupon = await _couponLookupService.FindByIdAsync(couponId);
if (coupon == null || coupon.ExpirationTime < now)
{
context.Fail();
return;
}
var couponTemplate = await _couponTemplateLookupService.FindByIdAsync(coupon.CouponTemplateId);
if (couponTemplate == null ||
!IsInUsableTime(couponTemplate, now) ||
!IsOrderInScope(couponTemplate, resource))
{
context.Fail();
return;
}
}
protected virtual bool IsOrderInScope(CouponTemplateData couponTemplate, OrderCreationResource resource)
{
if (couponTemplate.IsUnscoped)
{
return true;
}
var expectedOrderLines = new List<CreateOrderLineDto>();
foreach (var scope in couponTemplate.Scopes)
{
expectedOrderLines.AddRange(resource.Input.OrderLines
.WhereIf(scope.ProductGroupName != null,
x => resource.ProductDictionary[x.ProductId].ProductGroupName == scope.ProductGroupName)
.WhereIf(scope.ProductId.HasValue, x => x.ProductId == scope.ProductId)
.WhereIf(scope.ProductSkuId.HasValue, x => x.ProductSkuId == scope.ProductSkuId));
}
if (couponTemplate.IsCrossProductAllowed)
{
return expectedOrderLines.Sum(x => GetOrderLineProductPrice(x, resource) * x.Quantity) >=
couponTemplate.ConditionAmount;
}
return expectedOrderLines.Any(orderLine =>
GetOrderLineProductPrice(orderLine, resource) >= couponTemplate.ConditionAmount);
}
protected virtual decimal GetOrderLineProductPrice(CreateOrderLineDto createOrderLineDto, OrderCreationResource resource)
{
return resource.ProductDictionary[createOrderLineDto.ProductId].GetSkuById(createOrderLineDto.ProductSkuId).Price;
}
protected virtual bool IsInUsableTime(ICouponTemplate couponTemplate, DateTime now)
{
return couponTemplate.UsableBeginTime <= now && couponTemplate.UsableEndTime > now;
}
}
}

9
plugins/Coupons/src/EasyAbp.EShop.Orders.Plugins.Coupons/EasyAbp/EShop/Orders/Plugins/Coupons/EShopOrdersPluginsCouponsModule.cs

@ -1,23 +1,18 @@
using EasyAbp.EShop.Orders.Plugins.Coupons.Authorization;
using EasyAbp.EShop.Orders.Plugins.Coupons.ObjectExtending;
using EasyAbp.EShop.Orders.Plugins.Coupons.ObjectExtending;
using EasyAbp.EShop.Plugins.Coupons;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
namespace EasyAbp.EShop.Orders.Plugins.Coupons
{
[DependsOn(
typeof(EShopOrdersDomainModule),
typeof(EShopPluginsCouponsDomainSharedModule)
typeof(EShopPluginsCouponsApplicationContractsModule)
)]
public class EShopOrdersPluginsCouponsModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
EShopOrdersPluginsCouponsObjectExtensions.Configure();
context.Services.AddSingleton<IAuthorizationHandler, CouponOrderCreationAuthorizationHandler>();
}
}
}

12
plugins/Coupons/src/EasyAbp.EShop.Orders.Plugins.Coupons/EasyAbp/EShop/Orders/Plugins/Coupons/OrderDiscount/CouponNotFoundOrHasExpiredException.cs

@ -0,0 +1,12 @@
using Volo.Abp;
namespace EasyAbp.EShop.Orders.Plugins.Coupons.OrderDiscount
{
public class CouponNotFoundOrHasExpiredException : BusinessException
{
public CouponNotFoundOrHasExpiredException() : base("CouponNotFoundOrHasExpired",
"Coupon not found or has expired.")
{
}
}
}

151
plugins/Coupons/src/EasyAbp.EShop.Orders.Plugins.Coupons/EasyAbp/EShop/Orders/Plugins/Coupons/OrderDiscount/CouponOrderDiscountProvider.cs

@ -1,31 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EasyAbp.EShop.Orders.Orders;
using EasyAbp.EShop.Plugins.Coupons;
using EasyAbp.EShop.Plugins.Coupons.Coupons;
using EasyAbp.EShop.Plugins.Coupons.Coupons.Dtos;
using EasyAbp.EShop.Plugins.Coupons.CouponTemplates;
using EasyAbp.EShop.Products.Products.Dtos;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Timing;
namespace EasyAbp.EShop.Orders.Plugins.Coupons.OrderDiscount
{
public class CouponOrderDiscountProvider : IOrderDiscountProvider, ITransientDependency
{
private readonly IOrderRepository _orderRepository;
private readonly IClock _clock;
private readonly ICouponAppService _couponAppService;
private readonly ICouponLookupService _couponLookupService;
private readonly ICouponTemplateLookupService _couponTemplateLookupService;
public CouponOrderDiscountProvider(
IOrderRepository orderRepository,
IClock clock,
ICouponAppService couponAppService,
ICouponLookupService couponLookupService,
ICouponTemplateLookupService couponTemplateLookupService)
{
_orderRepository = orderRepository;
_clock = clock;
_couponAppService = couponAppService;
_couponLookupService = couponLookupService;
_couponTemplateLookupService = couponTemplateLookupService;
}
public virtual async Task<Order> DiscountAsync(Order order)
public virtual async Task<Order> DiscountAsync(Order order, Dictionary<Guid, ProductDto> productDict)
{
if (!Guid.TryParse(order.GetProperty<string>(CouponsConsts.OrderCouponIdPropertyName),
out var couponId))
@ -33,20 +41,143 @@ namespace EasyAbp.EShop.Orders.Plugins.Coupons.OrderDiscount
return order;
}
var now = _clock.Now;
var coupon = await _couponLookupService.FindByIdAsync(couponId);
var couponTemplate = await _couponTemplateLookupService.FindByIdAsync(coupon.CouponTemplateId);
if (coupon == null || coupon.ExpirationTime < now)
{
throw new CouponNotFoundOrHasExpiredException();
}
// Todo: consume the coupon!!!
if (coupon.OrderId.HasValue)
{
throw new CouponHasBeenOccupiedException();
}
// Todo: get discounted order lines
var discountedAmount = order.AddDiscountGetRealDiscountedAmount(couponTemplate.DiscountAmount);
var couponTemplate = await _couponTemplateLookupService.FindByIdAsync(coupon.CouponTemplateId);
order.SetProperty(CouponsConsts.OrderCouponDiscountAmountPropertyName, discountedAmount);
if (couponTemplate == null ||
!IsInUsableTime(couponTemplate, now))
{
throw new CouponTemplateNotFoundOrUnavailableException();
}
var orderLinesInScope = GetOrderLinesInScope(couponTemplate, order, productDict);
await _orderRepository.UpdateAsync(order, true);
DiscountOrderLines(couponTemplate, order, orderLinesInScope);
await _couponAppService.OccupyAsync(coupon.Id, new OccupyCouponInput {OrderId = order.Id});
return order;
}
protected virtual void DiscountOrderLines(CouponTemplateData couponTemplate, Order order,
List<OrderLine> orderLinesInScope)
{
// Todo: support Custom coupon.
if (couponTemplate.CouponType == CouponType.Custom)
{
throw new NotSupportedException();
}
var totalOrderLineActualTotalPrice = orderLinesInScope.Sum(x => x.ActualTotalPrice);
var totalDiscountedAmount = couponTemplate.CouponType == CouponType.PerMeet
? couponTemplate.DiscountAmount *
Math.Floor(totalOrderLineActualTotalPrice / couponTemplate.ConditionAmount)
: couponTemplate.DiscountAmount;
if (totalDiscountedAmount > totalOrderLineActualTotalPrice)
{
totalDiscountedAmount = totalOrderLineActualTotalPrice;
}
var remainingDiscountedAmount = totalDiscountedAmount;
// Todo: https://github.com/EasyAbp/EShop/issues/104
const int accuracy = 2;
foreach (var orderLine in orderLinesInScope)
{
var maxDiscountAmount =
Math.Round(orderLine.ActualTotalPrice / totalOrderLineActualTotalPrice * totalDiscountedAmount,
accuracy, MidpointRounding.ToZero);
var discountAmount = maxDiscountAmount > orderLine.ActualTotalPrice
? orderLine.ActualTotalPrice
: maxDiscountAmount;
order.AddDiscount(orderLine.Id, discountAmount);
remainingDiscountedAmount -= discountAmount;
}
foreach (var orderLine in orderLinesInScope.OrderByDescending(x => x.ActualTotalPrice))
{
if (remainingDiscountedAmount == decimal.Zero)
{
break;
}
var discountAmount = remainingDiscountedAmount > orderLine.ActualTotalPrice
? orderLine.ActualTotalPrice
: remainingDiscountedAmount;
order.AddDiscount(orderLine.Id, discountAmount);
remainingDiscountedAmount -= discountAmount;
}
if (remainingDiscountedAmount != decimal.Zero)
{
throw new ApplicationException();
}
order.SetProperty(CouponsConsts.OrderCouponDiscountAmountPropertyName, totalDiscountedAmount);
}
protected virtual List<OrderLine> GetOrderLinesInScope(CouponTemplateData couponTemplate, Order order,
Dictionary<Guid, ProductDto> productDict)
{
if (couponTemplate.IsUnscoped)
{
return order.OrderLines;
}
var expectedOrderLines = new List<OrderLine>();
foreach (var scope in couponTemplate.Scopes.Where(scope => scope.StoreId == order.StoreId))
{
expectedOrderLines.AddRange(order.OrderLines
.WhereIf(scope.ProductGroupName != null,
x => productDict[x.ProductId].ProductGroupName == scope.ProductGroupName)
.WhereIf(scope.ProductId.HasValue, x => x.ProductId == scope.ProductId)
.WhereIf(scope.ProductSkuId.HasValue, x => x.ProductSkuId == scope.ProductSkuId));
}
if (expectedOrderLines.IsNullOrEmpty())
{
throw new OrderDoesNotMeetCouponUsageConditionException();
}
if (expectedOrderLines.Sum(x => GetOrderLineProductPrice(x, productDict) * x.Quantity) <
couponTemplate.ConditionAmount)
{
throw new OrderDoesNotMeetCouponUsageConditionException();
}
return expectedOrderLines;
}
protected virtual decimal GetOrderLineProductPrice(OrderLine orderLine, Dictionary<Guid, ProductDto> productDict)
{
return productDict[orderLine.ProductId].GetSkuById(orderLine.ProductSkuId).Price;
}
protected virtual bool IsInUsableTime(ICouponTemplate couponTemplate, DateTime now)
{
return couponTemplate.UsableBeginTime <= now && couponTemplate.UsableEndTime > now;
}
}
}

12
plugins/Coupons/src/EasyAbp.EShop.Orders.Plugins.Coupons/EasyAbp/EShop/Orders/Plugins/Coupons/OrderDiscount/CouponTemplateNotFoundOrUnavailableException.cs

@ -0,0 +1,12 @@
using Volo.Abp;
namespace EasyAbp.EShop.Orders.Plugins.Coupons.OrderDiscount
{
public class CouponTemplateNotFoundOrUnavailableException : BusinessException
{
public CouponTemplateNotFoundOrUnavailableException() : base("CouponTemplateNotFoundOrUnavailable",
"Coupon template not found or unavailable.")
{
}
}
}

12
plugins/Coupons/src/EasyAbp.EShop.Orders.Plugins.Coupons/EasyAbp/EShop/Orders/Plugins/Coupons/OrderDiscount/OrderDoesNotMeetCouponUsageConditionException.cs

@ -0,0 +1,12 @@
using Volo.Abp;
namespace EasyAbp.EShop.Orders.Plugins.Coupons.OrderDiscount
{
public class OrderDoesNotMeetCouponUsageConditionException : BusinessException
{
public OrderDoesNotMeetCouponUsageConditionException() : base("OrderDoesNotMeetCouponUsageCondition",
"Order does not meet the coupon usage condition.")
{
}
}
}

2
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application.Contracts/EasyAbp/EShop/Plugins/Coupons/CouponTemplates/Dtos/CouponTemplateDto.cs

@ -27,8 +27,6 @@ namespace EasyAbp.EShop.Plugins.Coupons.CouponTemplates.Dtos
public decimal DiscountAmount { get; set; }
public bool IsCrossProductAllowed { get; set; }
public bool IsUnscoped { get; set; }
public List<CouponTemplateScopeDto> Scopes { get; set; }

27
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application.Contracts/EasyAbp/EShop/Plugins/Coupons/CouponTemplates/Dtos/CreateUpdateCouponTemplateDto.cs

@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
namespace EasyAbp.EShop.Plugins.Coupons.CouponTemplates.Dtos
{
[Serializable]
public class CreateUpdateCouponTemplateDto
public class CreateUpdateCouponTemplateDto : IValidatableObject
{
public Guid? StoreId { get; set; }
@ -26,10 +28,27 @@ namespace EasyAbp.EShop.Plugins.Coupons.CouponTemplates.Dtos
public decimal DiscountAmount { get; set; }
public bool IsCrossProductAllowed { get; set; }
public bool IsUnscoped { get; set; }
public List<CreateUpdateCouponTemplateScopeDto> Scopes { get; set; }
public List<CreateUpdateCouponTemplateScopeDto> Scopes { get; set; } = new List<CreateUpdateCouponTemplateScopeDto>();
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (DiscountAmount > ConditionAmount)
{
yield return new ValidationResult(
"DiscountAmount should not be greater than ConditionAmount!",
new[] { nameof(DiscountAmount) }
);
}
if (CouponType == CouponType.PerMeet && ConditionAmount == decimal.Zero)
{
yield return new ValidationResult(
"ConditionAmount should be greater than zero if the CouponType is PerMeet!",
new[] { nameof(ConditionAmount) }
);
}
}
}
}

11
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application.Contracts/EasyAbp/EShop/Plugins/Coupons/Coupons/CouponHasBeenOccupiedException.cs

@ -0,0 +1,11 @@
using Volo.Abp;
namespace EasyAbp.EShop.Plugins.Coupons.Coupons
{
public class CouponHasBeenOccupiedException : BusinessException
{
public CouponHasBeenOccupiedException() : base("CouponHasBeenOccupied", "Coupon has been occupied.")
{
}
}
}

2
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application.Contracts/EasyAbp/EShop/Plugins/Coupons/Coupons/Dtos/CreateUpdateCouponDto.cs → plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application.Contracts/EasyAbp/EShop/Plugins/Coupons/Coupons/Dtos/CreateCouponDto.cs

@ -2,7 +2,7 @@ using System;
namespace EasyAbp.EShop.Plugins.Coupons.Coupons.Dtos
{
[Serializable]
public class CreateUpdateCouponDto
public class CreateCouponDto
{
public Guid CouponTemplateId { get; set; }

13
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application.Contracts/EasyAbp/EShop/Plugins/Coupons/Coupons/Dtos/GetCouponListInput.cs

@ -0,0 +1,13 @@
using System;
using Volo.Abp.Application.Dtos;
namespace EasyAbp.EShop.Plugins.Coupons.Coupons.Dtos
{
[Serializable]
public class GetCouponListInput : PagedAndSortedResultRequestDto
{
public bool AvailableOnly { get; set; }
public Guid? UserId { get; set; }
}
}

10
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application.Contracts/EasyAbp/EShop/Plugins/Coupons/Coupons/Dtos/OccupyCouponInput.cs

@ -0,0 +1,10 @@
using System;
namespace EasyAbp.EShop.Plugins.Coupons.Coupons.Dtos
{
[Serializable]
public class OccupyCouponInput
{
public Guid OrderId { get; set; }
}
}

9
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application.Contracts/EasyAbp/EShop/Plugins/Coupons/Coupons/Dtos/UpdateCouponDto.cs

@ -0,0 +1,9 @@
using System;
namespace EasyAbp.EShop.Plugins.Coupons.Coupons.Dtos
{
[Serializable]
public class UpdateCouponDto
{
public DateTime? ExpirationTime { get; set; }
}
}

9
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application.Contracts/EasyAbp/EShop/Plugins/Coupons/Coupons/ICouponAppService.cs

@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using EasyAbp.EShop.Plugins.Coupons.Coupons.Dtos;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
@ -9,10 +10,10 @@ namespace EasyAbp.EShop.Plugins.Coupons.Coupons
ICrudAppService<
CouponDto,
Guid,
PagedAndSortedResultRequestDto,
CreateUpdateCouponDto,
CreateUpdateCouponDto>
GetCouponListInput,
CreateCouponDto,
UpdateCouponDto>
{
Task<CouponDto> OccupyAsync(Guid id, OccupyCouponInput input);
}
}

2
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application.Contracts/EasyAbp/EShop/Plugins/Coupons/Permissions/CouponsPermissionDefinitionProvider.cs

@ -16,6 +16,8 @@ namespace EasyAbp.EShop.Plugins.Coupons.Permissions
couponTemplatePermission.AddChild(CouponsPermissions.CouponTemplate.Delete, L("Permission:Delete"));
var couponPermission = myGroup.AddPermission(CouponsPermissions.Coupon.Default, L("Permission:Coupon"));
couponPermission.AddChild(CouponsPermissions.Coupon.Use, L("Permission:Use"));
couponPermission.AddChild(CouponsPermissions.Coupon.Manage, L("Permission:Manage"));
couponPermission.AddChild(CouponsPermissions.Coupon.Create, L("Permission:Create"));
couponPermission.AddChild(CouponsPermissions.Coupon.Update, L("Permission:Update"));
couponPermission.AddChild(CouponsPermissions.Coupon.Delete, L("Permission:Delete"));

2
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application.Contracts/EasyAbp/EShop/Plugins/Coupons/Permissions/CouponsPermissions.cs

@ -22,6 +22,8 @@ namespace EasyAbp.EShop.Plugins.Coupons.Permissions
public class Coupon
{
public const string Default = GroupName + ".Coupon";
public const string Use = Default + ".Use";
public const string Manage = Default + ".Manage";
public const string Update = Default + ".Update";
public const string Create = Default + ".Create";
public const string Delete = Default + ".Delete";

12
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application/EasyAbp/EShop/Plugins/Coupons/CouponTemplates/CouponTemplateAppService.cs

@ -26,6 +26,11 @@ namespace EasyAbp.EShop.Plugins.Coupons.CouponTemplates
_repository = repository;
}
protected override IQueryable<CouponTemplate> CreateFilteredQuery(PagedAndSortedResultRequestDto input)
{
return _repository.WithDetails();
}
public override async Task<CouponTemplateDto> CreateAsync(CreateUpdateCouponTemplateDto input)
{
await CheckCreatePolicyAsync();
@ -59,11 +64,8 @@ namespace EasyAbp.EShop.Plugins.Coupons.CouponTemplates
inputScoreDto.ProductGroupName, inputScoreDto.ProductId, inputScoreDto.ProductSkuId));
}
foreach (var scope in entity.Scopes.Where(scope => !ExistScope(input.Scopes, scope)))
{
entity.Scopes.Remove(scope);
}
entity.Scopes.RemoveAll(scope => !ExistScope(input.Scopes, scope));
await Repository.UpdateAsync(entity, autoSave: true);
return await MapToGetOutputDtoAsync(entity);

54
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application/EasyAbp/EShop/Plugins/Coupons/Coupons/CouponAppService.cs

@ -1,14 +1,17 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using EasyAbp.EShop.Plugins.Coupons.Permissions;
using EasyAbp.EShop.Plugins.Coupons.Coupons.Dtos;
using EasyAbp.EShop.Plugins.Coupons.CouponTemplates;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Users;
namespace EasyAbp.EShop.Plugins.Coupons.Coupons
{
public class CouponAppService : CrudAppService<Coupon, CouponDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateCouponDto, CreateUpdateCouponDto>,
public class CouponAppService : CrudAppService<Coupon, CouponDto, Guid, GetCouponListInput, CreateCouponDto, UpdateCouponDto>,
ICouponAppService
{
protected override string GetPolicyName { get; set; } = CouponsPermissions.Coupon.Default;
@ -28,7 +31,37 @@ namespace EasyAbp.EShop.Plugins.Coupons.Coupons
_repository = repository;
}
public override async Task<CouponDto> CreateAsync(CreateUpdateCouponDto input)
protected override IQueryable<Coupon> CreateFilteredQuery(GetCouponListInput input)
{
return input.AvailableOnly ? _repository.GetAvailableCouponQueryable(Clock) : _repository.AsQueryable()
.WhereIf(input.UserId.HasValue, x => x.UserId == input.UserId.Value);
}
public override async Task<CouponDto> GetAsync(Guid id)
{
await CheckGetPolicyAsync();
var entity = await GetEntityByIdAsync(id);
if (entity.UserId != CurrentUser.GetId())
{
await AuthorizationService.CheckAsync(CouponsPermissions.Coupon.Manage);
}
return await MapToGetOutputDtoAsync(entity);
}
public override async Task<PagedResultDto<CouponDto>> GetListAsync(GetCouponListInput input)
{
if (input.UserId != CurrentUser.GetId())
{
await AuthorizationService.CheckAsync(CouponsPermissions.Coupon.Manage);
}
return await base.GetListAsync(input);
}
public override async Task<CouponDto> CreateAsync(CreateCouponDto input)
{
await CheckCreatePolicyAsync();
@ -44,5 +77,22 @@ namespace EasyAbp.EShop.Plugins.Coupons.Coupons
return await MapToGetOutputDtoAsync(entity);
}
[Authorize(CouponsPermissions.Coupon.Use)]
public virtual async Task<CouponDto> OccupyAsync(Guid id, OccupyCouponInput input)
{
var coupon = await GetEntityByIdAsync(id);
if (coupon.OrderId.HasValue)
{
throw new CouponHasBeenOccupiedException();
}
coupon.SetOrderId(input.OrderId);
await _repository.UpdateAsync(coupon, true);
return ObjectMapper.Map<Coupon, CouponDto>(coupon);
}
}
}

3
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Application/EasyAbp/EShop/Plugins/Coupons/CouponsApplicationAutoMapperProfile.cs

@ -22,7 +22,8 @@ namespace EasyAbp.EShop.Plugins.Coupons
CreateMap<CreateUpdateCouponTemplateScopeDto, CouponTemplateScope>(MemberList.Source);
CreateMap<Coupon, CouponDto>();
CreateMap<CreateUpdateCouponDto, Coupon>(MemberList.Source);
CreateMap<CreateCouponDto, Coupon>(MemberList.Source);
CreateMap<UpdateCouponDto, Coupon>(MemberList.Source);
}
}
}

6
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/CouponTemplates/CouponTemplateData.cs

@ -5,6 +5,10 @@ namespace EasyAbp.EShop.Plugins.Coupons.CouponTemplates
{
public class CouponTemplateData : ICouponTemplate, IHasCouponTemplateScopes<CouponTemplateScopeData>
{
public Guid Id { get; set; }
public Guid? TenantId { get; set; }
public Guid? StoreId { get; set; }
public CouponType CouponType { get; set; }
@ -25,8 +29,6 @@ namespace EasyAbp.EShop.Plugins.Coupons.CouponTemplates
public decimal DiscountAmount { get; set; }
public bool IsCrossProductAllowed { get; set; }
public bool IsUnscoped { get; set; }
public List<CouponTemplateScopeData> Scopes { get; set; }

5
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/CouponTemplates/CouponType.cs

@ -1,5 +1,8 @@
namespace EasyAbp.EShop.Plugins.Coupons.CouponTemplates
using System;
namespace EasyAbp.EShop.Plugins.Coupons.CouponTemplates
{
[Flags]
public enum CouponType
{
/// <summary>

2
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/CouponTemplates/ICouponTemplate.cs

@ -29,8 +29,6 @@ namespace EasyAbp.EShop.Plugins.Coupons.CouponTemplates
decimal DiscountAmount { get; }
bool IsCrossProductAllowed { get; }
bool IsUnscoped { get; }
}
}

4
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/Coupons/CouponData.cs

@ -6,6 +6,10 @@ namespace EasyAbp.EShop.Plugins.Coupons.Coupons
{
public class CouponData : ICoupon
{
public Guid Id { get; set; }
public Guid? TenantId { get; set; }
public Guid CouponTemplateId { get; set; }
public Guid UserId { get; set; }

2
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/Coupons/ICouponLookupService.cs

@ -6,6 +6,6 @@ namespace EasyAbp.EShop.Plugins.Coupons.Coupons
{
public interface ICouponLookupService
{
Task<ICoupon> FindByIdAsync(Guid id, CancellationToken cancellationToken = default);
Task<CouponData> FindByIdAsync(Guid id, CancellationToken cancellationToken = default);
}
}

57
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/Localization/cs.json

@ -3,39 +3,42 @@
"texts": {
"ManageYourProfile": "Spravovat profil",
"SamplePageMessage": "Ukázková stránka pro modul Coupons",
"Permission:CouponTemplate": "CouponTemplate",
"Menu:CouponManagement": "Coupons",
"Permission:CouponTemplate": "Coupon template",
"Permission:Create": "Create",
"Permission:Update": "Update",
"Permission:Delete": "Delete",
"Menu:CouponTemplate": "MenuCouponTemplate",
"CouponTemplate": "CouponTemplate",
"CouponTemplateStoreId": "CouponTemplateStoreId",
"CouponTemplateCouponType": "CouponTemplateCouponType",
"CouponTemplateUniqueName": "CouponTemplateUniqueName",
"CouponTemplateDisplayName": "CouponTemplateDisplayName",
"CouponTemplateDescription": "CouponTemplateDescription",
"CouponTemplateUsableDuration": "CouponTemplateUsableDuration",
"CouponTemplateUsableBeginTime": "CouponTemplateUsableBeginTime",
"CouponTemplateUsableEndTime": "CouponTemplateUsableEndTime",
"CouponTemplateConditionAmount": "CouponTemplateConditionAmount",
"CouponTemplateDiscountAmount": "CouponTemplateDiscountAmount",
"CouponTemplateIsCrossProductAllowed": "CouponTemplateIsCrossProductAllowed",
"CouponTemplateIsUnscoped": "CouponTemplateIsUnscoped",
"CreateCouponTemplate": "CreateCouponTemplate",
"EditCouponTemplate": "EditCouponTemplate",
"CouponTemplateDeletionConfirmationMessage": "Are you sure to delete the couponTemplate {0}?",
"Menu:CouponTemplate": "Coupon template",
"CouponTemplate": "Coupon template",
"CouponTemplateStoreId": "Store ID",
"CouponTemplateCouponType": "Coupon type",
"CouponTemplateUniqueName": "Unique name",
"CouponTemplateDisplayName": "Display name",
"CouponTemplateDescription": "Description",
"CouponTemplateUsableDuration": "Usable duration",
"CouponTemplateUsableBeginTime": "Usable begin time",
"CouponTemplateUsableEndTime": "Usable end time",
"CouponTemplateConditionAmount": "Condition amount",
"CouponTemplateDiscountAmount": "Discount amount",
"CouponTemplateIsUnscoped": "Unscoped",
"CreateCouponTemplate": "New",
"EditCouponTemplate": "Edit",
"CouponTemplateDeletionConfirmationMessage": "Are you sure to delete the coupon template {0}?",
"SuccessfullyDeleted": "Successfully deleted",
"CouponType.Normal": "X discount",
"CouponType.PerMeet": "X discount for every Y",
"CouponType.Custom": "Custom",
"Permission:Coupon": "Coupon",
"Menu:Coupon": "MenuCoupon",
"Menu:Coupon": "Coupon",
"Coupon": "Coupon",
"CouponCouponTemplateId": "CouponCouponTemplateId",
"CouponUserId": "CouponUserId",
"CouponOrderId": "CouponOrderId",
"CouponExpirationTime": "CouponExpirationTime",
"CouponUsedTime": "CouponUsedTime",
"CouponDiscountedAmount": "CouponDiscountedAmount",
"CreateCoupon": "CreateCoupon",
"EditCoupon": "EditCoupon",
"CouponCouponTemplateId": "Coupon template ID",
"CouponUserId": "User ID",
"CouponOrderId": "Order ID",
"CouponExpirationTime": "Expiration time",
"CouponUsedTime": "Used time",
"CouponDiscountedAmount": "Discounted amount",
"CreateCoupon": "New",
"EditCoupon": "Edit",
"CouponDeletionConfirmationMessage": "Are you sure to delete the coupon {0}?"
}
}

57
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/Localization/en.json

@ -3,39 +3,42 @@
"texts": {
"ManageYourProfile": "Manage your profile",
"SamplePageMessage": "A sample page for the Coupons module",
"Permission:CouponTemplate": "CouponTemplate",
"Menu:CouponManagement": "Coupons",
"Permission:CouponTemplate": "Coupon template",
"Permission:Create": "Create",
"Permission:Update": "Update",
"Permission:Delete": "Delete",
"Menu:CouponTemplate": "MenuCouponTemplate",
"CouponTemplate": "CouponTemplate",
"CouponTemplateStoreId": "CouponTemplateStoreId",
"CouponTemplateCouponType": "CouponTemplateCouponType",
"CouponTemplateUniqueName": "CouponTemplateUniqueName",
"CouponTemplateDisplayName": "CouponTemplateDisplayName",
"CouponTemplateDescription": "CouponTemplateDescription",
"CouponTemplateUsableDuration": "CouponTemplateUsableDuration",
"CouponTemplateUsableBeginTime": "CouponTemplateUsableBeginTime",
"CouponTemplateUsableEndTime": "CouponTemplateUsableEndTime",
"CouponTemplateConditionAmount": "CouponTemplateConditionAmount",
"CouponTemplateDiscountAmount": "CouponTemplateDiscountAmount",
"CouponTemplateIsCrossProductAllowed": "CouponTemplateIsCrossProductAllowed",
"CouponTemplateIsUnscoped": "CouponTemplateIsUnscoped",
"CreateCouponTemplate": "CreateCouponTemplate",
"EditCouponTemplate": "EditCouponTemplate",
"CouponTemplateDeletionConfirmationMessage": "Are you sure to delete the couponTemplate {0}?",
"Menu:CouponTemplate": "Coupon template",
"CouponTemplate": "Coupon template",
"CouponTemplateStoreId": "Store ID",
"CouponTemplateCouponType": "Coupon type",
"CouponTemplateUniqueName": "Unique name",
"CouponTemplateDisplayName": "Display name",
"CouponTemplateDescription": "Description",
"CouponTemplateUsableDuration": "Usable duration",
"CouponTemplateUsableBeginTime": "Usable begin time",
"CouponTemplateUsableEndTime": "Usable end time",
"CouponTemplateConditionAmount": "Condition amount",
"CouponTemplateDiscountAmount": "Discount amount",
"CouponTemplateIsUnscoped": "Unscoped",
"CreateCouponTemplate": "New",
"EditCouponTemplate": "Edit",
"CouponTemplateDeletionConfirmationMessage": "Are you sure to delete the coupon template {0}?",
"SuccessfullyDeleted": "Successfully deleted",
"CouponType.Normal": "X discount",
"CouponType.PerMeet": "X discount for every Y",
"CouponType.Custom": "Custom",
"Permission:Coupon": "Coupon",
"Menu:Coupon": "MenuCoupon",
"Menu:Coupon": "Coupon",
"Coupon": "Coupon",
"CouponCouponTemplateId": "CouponCouponTemplateId",
"CouponUserId": "CouponUserId",
"CouponOrderId": "CouponOrderId",
"CouponExpirationTime": "CouponExpirationTime",
"CouponUsedTime": "CouponUsedTime",
"CouponDiscountedAmount": "CouponDiscountedAmount",
"CreateCoupon": "CreateCoupon",
"EditCoupon": "EditCoupon",
"CouponCouponTemplateId": "Coupon template ID",
"CouponUserId": "User ID",
"CouponOrderId": "Order ID",
"CouponExpirationTime": "Expiration time",
"CouponUsedTime": "Used time",
"CouponDiscountedAmount": "Discounted amount",
"CreateCoupon": "New",
"EditCoupon": "Edit",
"CouponDeletionConfirmationMessage": "Are you sure to delete the coupon {0}?"
}
}

56
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/Localization/pl-PL.json

@ -1,39 +1,41 @@
{
"culture": "pl-PL",
"texts": {
"Permission:CouponTemplate": "CouponTemplate",
"Permission:CouponTemplate": "Coupon template",
"Permission:Create": "Create",
"Permission:Update": "Update",
"Permission:Delete": "Delete",
"Menu:CouponTemplate": "MenuCouponTemplate",
"CouponTemplate": "CouponTemplate",
"CouponTemplateStoreId": "CouponTemplateStoreId",
"CouponTemplateCouponType": "CouponTemplateCouponType",
"CouponTemplateUniqueName": "CouponTemplateUniqueName",
"CouponTemplateDisplayName": "CouponTemplateDisplayName",
"CouponTemplateDescription": "CouponTemplateDescription",
"CouponTemplateUsableDuration": "CouponTemplateUsableDuration",
"CouponTemplateUsableBeginTime": "CouponTemplateUsableBeginTime",
"CouponTemplateUsableEndTime": "CouponTemplateUsableEndTime",
"CouponTemplateConditionAmount": "CouponTemplateConditionAmount",
"CouponTemplateDiscountAmount": "CouponTemplateDiscountAmount",
"CouponTemplateIsCrossProductAllowed": "CouponTemplateIsCrossProductAllowed",
"CouponTemplateIsUnscoped": "CouponTemplateIsUnscoped",
"CreateCouponTemplate": "CreateCouponTemplate",
"EditCouponTemplate": "EditCouponTemplate",
"CouponTemplateDeletionConfirmationMessage": "Are you sure to delete the couponTemplate {0}?",
"Menu:CouponTemplate": "Coupon template",
"CouponTemplate": "Coupon template",
"CouponTemplateStoreId": "Store ID",
"CouponTemplateCouponType": "Coupon type",
"CouponTemplateUniqueName": "Unique name",
"CouponTemplateDisplayName": "Display name",
"CouponTemplateDescription": "Description",
"CouponTemplateUsableDuration": "Usable duration",
"CouponTemplateUsableBeginTime": "Usable begin time",
"CouponTemplateUsableEndTime": "Usable end time",
"CouponTemplateConditionAmount": "Condition amount",
"CouponTemplateDiscountAmount": "Discount amount",
"CouponTemplateIsUnscoped": "Unscoped",
"CreateCouponTemplate": "New",
"EditCouponTemplate": "Edit",
"CouponTemplateDeletionConfirmationMessage": "Are you sure to delete the coupon template {0}?",
"SuccessfullyDeleted": "Successfully deleted",
"CouponType.Normal": "X discount",
"CouponType.PerMeet": "X discount for every Y",
"CouponType.Custom": "Custom",
"Permission:Coupon": "Coupon",
"Menu:Coupon": "MenuCoupon",
"Menu:Coupon": "Coupon",
"Coupon": "Coupon",
"CouponCouponTemplateId": "CouponCouponTemplateId",
"CouponUserId": "CouponUserId",
"CouponOrderId": "CouponOrderId",
"CouponExpirationTime": "CouponExpirationTime",
"CouponUsedTime": "CouponUsedTime",
"CouponDiscountedAmount": "CouponDiscountedAmount",
"CreateCoupon": "CreateCoupon",
"EditCoupon": "EditCoupon",
"CouponCouponTemplateId": "Coupon template ID",
"CouponUserId": "User ID",
"CouponOrderId": "Order ID",
"CouponExpirationTime": "Expiration time",
"CouponUsedTime": "Used time",
"CouponDiscountedAmount": "Discounted amount",
"CreateCoupon": "New",
"EditCoupon": "Edit",
"CouponDeletionConfirmationMessage": "Are you sure to delete the coupon {0}?"
}
}

56
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/Localization/pt-BR.json

@ -1,39 +1,41 @@
{
"culture": "pt-BR",
"texts": {
"Permission:CouponTemplate": "CouponTemplate",
"Permission:CouponTemplate": "Coupon template",
"Permission:Create": "Create",
"Permission:Update": "Update",
"Permission:Delete": "Delete",
"Menu:CouponTemplate": "MenuCouponTemplate",
"CouponTemplate": "CouponTemplate",
"CouponTemplateStoreId": "CouponTemplateStoreId",
"CouponTemplateCouponType": "CouponTemplateCouponType",
"CouponTemplateUniqueName": "CouponTemplateUniqueName",
"CouponTemplateDisplayName": "CouponTemplateDisplayName",
"CouponTemplateDescription": "CouponTemplateDescription",
"CouponTemplateUsableDuration": "CouponTemplateUsableDuration",
"CouponTemplateUsableBeginTime": "CouponTemplateUsableBeginTime",
"CouponTemplateUsableEndTime": "CouponTemplateUsableEndTime",
"CouponTemplateConditionAmount": "CouponTemplateConditionAmount",
"CouponTemplateDiscountAmount": "CouponTemplateDiscountAmount",
"CouponTemplateIsCrossProductAllowed": "CouponTemplateIsCrossProductAllowed",
"CouponTemplateIsUnscoped": "CouponTemplateIsUnscoped",
"CreateCouponTemplate": "CreateCouponTemplate",
"EditCouponTemplate": "EditCouponTemplate",
"CouponTemplateDeletionConfirmationMessage": "Are you sure to delete the couponTemplate {0}?",
"Menu:CouponTemplate": "Coupon template",
"CouponTemplate": "Coupon template",
"CouponTemplateStoreId": "Store ID",
"CouponTemplateCouponType": "Coupon type",
"CouponTemplateUniqueName": "Unique name",
"CouponTemplateDisplayName": "Display name",
"CouponTemplateDescription": "Description",
"CouponTemplateUsableDuration": "Usable duration",
"CouponTemplateUsableBeginTime": "Usable begin time",
"CouponTemplateUsableEndTime": "Usable end time",
"CouponTemplateConditionAmount": "Condition amount",
"CouponTemplateDiscountAmount": "Discount amount",
"CouponTemplateIsUnscoped": "Unscoped",
"CreateCouponTemplate": "New",
"EditCouponTemplate": "Edit",
"CouponTemplateDeletionConfirmationMessage": "Are you sure to delete the coupon template {0}?",
"SuccessfullyDeleted": "Successfully deleted",
"CouponType.Normal": "X discount",
"CouponType.PerMeet": "X discount for every Y",
"CouponType.Custom": "Custom",
"Permission:Coupon": "Coupon",
"Menu:Coupon": "MenuCoupon",
"Menu:Coupon": "Coupon",
"Coupon": "Coupon",
"CouponCouponTemplateId": "CouponCouponTemplateId",
"CouponUserId": "CouponUserId",
"CouponOrderId": "CouponOrderId",
"CouponExpirationTime": "CouponExpirationTime",
"CouponUsedTime": "CouponUsedTime",
"CouponDiscountedAmount": "CouponDiscountedAmount",
"CreateCoupon": "CreateCoupon",
"EditCoupon": "EditCoupon",
"CouponCouponTemplateId": "Coupon template ID",
"CouponUserId": "User ID",
"CouponOrderId": "Order ID",
"CouponExpirationTime": "Expiration time",
"CouponUsedTime": "Used time",
"CouponDiscountedAmount": "Discounted amount",
"CreateCoupon": "New",
"EditCoupon": "Edit",
"CouponDeletionConfirmationMessage": "Are you sure to delete the coupon {0}?"
}
}

57
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/Localization/sl.json

@ -2,39 +2,42 @@
"culture": "sl",
"texts": {
"ManageYourProfile": "Upravljajte svojim profilom",
"Permission:CouponTemplate": "CouponTemplate",
"Menu:CouponManagement": "Coupons",
"Permission:CouponTemplate": "Coupon template",
"Permission:Create": "Create",
"Permission:Update": "Update",
"Permission:Delete": "Delete",
"Menu:CouponTemplate": "MenuCouponTemplate",
"CouponTemplate": "CouponTemplate",
"CouponTemplateStoreId": "CouponTemplateStoreId",
"CouponTemplateCouponType": "CouponTemplateCouponType",
"CouponTemplateUniqueName": "CouponTemplateUniqueName",
"CouponTemplateDisplayName": "CouponTemplateDisplayName",
"CouponTemplateDescription": "CouponTemplateDescription",
"CouponTemplateUsableDuration": "CouponTemplateUsableDuration",
"CouponTemplateUsableBeginTime": "CouponTemplateUsableBeginTime",
"CouponTemplateUsableEndTime": "CouponTemplateUsableEndTime",
"CouponTemplateConditionAmount": "CouponTemplateConditionAmount",
"CouponTemplateDiscountAmount": "CouponTemplateDiscountAmount",
"CouponTemplateIsCrossProductAllowed": "CouponTemplateIsCrossProductAllowed",
"CouponTemplateIsUnscoped": "CouponTemplateIsUnscoped",
"CreateCouponTemplate": "CreateCouponTemplate",
"EditCouponTemplate": "EditCouponTemplate",
"CouponTemplateDeletionConfirmationMessage": "Are you sure to delete the couponTemplate {0}?",
"Menu:CouponTemplate": "Coupon template",
"CouponTemplate": "Coupon template",
"CouponTemplateStoreId": "Store ID",
"CouponTemplateCouponType": "Coupon type",
"CouponTemplateUniqueName": "Unique name",
"CouponTemplateDisplayName": "Display name",
"CouponTemplateDescription": "Description",
"CouponTemplateUsableDuration": "Usable duration",
"CouponTemplateUsableBeginTime": "Usable begin time",
"CouponTemplateUsableEndTime": "Usable end time",
"CouponTemplateConditionAmount": "Condition amount",
"CouponTemplateDiscountAmount": "Discount amount",
"CouponTemplateIsUnscoped": "Unscoped",
"CreateCouponTemplate": "New",
"EditCouponTemplate": "Edit",
"CouponTemplateDeletionConfirmationMessage": "Are you sure to delete the coupon template {0}?",
"SuccessfullyDeleted": "Successfully deleted",
"CouponType.Normal": "X discount",
"CouponType.PerMeet": "X discount for every Y",
"CouponType.Custom": "Custom",
"Permission:Coupon": "Coupon",
"Menu:Coupon": "MenuCoupon",
"Menu:Coupon": "Coupon",
"Coupon": "Coupon",
"CouponCouponTemplateId": "CouponCouponTemplateId",
"CouponUserId": "CouponUserId",
"CouponOrderId": "CouponOrderId",
"CouponExpirationTime": "CouponExpirationTime",
"CouponUsedTime": "CouponUsedTime",
"CouponDiscountedAmount": "CouponDiscountedAmount",
"CreateCoupon": "CreateCoupon",
"EditCoupon": "EditCoupon",
"CouponCouponTemplateId": "Coupon template ID",
"CouponUserId": "User ID",
"CouponOrderId": "Order ID",
"CouponExpirationTime": "Expiration time",
"CouponUsedTime": "Used time",
"CouponDiscountedAmount": "Discounted amount",
"CreateCoupon": "New",
"EditCoupon": "Edit",
"CouponDeletionConfirmationMessage": "Are you sure to delete the coupon {0}?"
}
}

57
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/Localization/tr.json

@ -3,39 +3,42 @@
"texts": {
"ManageYourProfile": "Profil yönetimi",
"SamplePageMessage": "Coupons modulünden örnek bir sayfa",
"Permission:CouponTemplate": "CouponTemplate",
"Menu:CouponManagement": "Coupons",
"Permission:CouponTemplate": "Coupon template",
"Permission:Create": "Create",
"Permission:Update": "Update",
"Permission:Delete": "Delete",
"Menu:CouponTemplate": "MenuCouponTemplate",
"CouponTemplate": "CouponTemplate",
"CouponTemplateStoreId": "CouponTemplateStoreId",
"CouponTemplateCouponType": "CouponTemplateCouponType",
"CouponTemplateUniqueName": "CouponTemplateUniqueName",
"CouponTemplateDisplayName": "CouponTemplateDisplayName",
"CouponTemplateDescription": "CouponTemplateDescription",
"CouponTemplateUsableDuration": "CouponTemplateUsableDuration",
"CouponTemplateUsableBeginTime": "CouponTemplateUsableBeginTime",
"CouponTemplateUsableEndTime": "CouponTemplateUsableEndTime",
"CouponTemplateConditionAmount": "CouponTemplateConditionAmount",
"CouponTemplateDiscountAmount": "CouponTemplateDiscountAmount",
"CouponTemplateIsCrossProductAllowed": "CouponTemplateIsCrossProductAllowed",
"CouponTemplateIsUnscoped": "CouponTemplateIsUnscoped",
"CreateCouponTemplate": "CreateCouponTemplate",
"EditCouponTemplate": "EditCouponTemplate",
"CouponTemplateDeletionConfirmationMessage": "Are you sure to delete the couponTemplate {0}?",
"Menu:CouponTemplate": "Coupon template",
"CouponTemplate": "Coupon template",
"CouponTemplateStoreId": "Store ID",
"CouponTemplateCouponType": "Coupon type",
"CouponTemplateUniqueName": "Unique name",
"CouponTemplateDisplayName": "Display name",
"CouponTemplateDescription": "Description",
"CouponTemplateUsableDuration": "Usable duration",
"CouponTemplateUsableBeginTime": "Usable begin time",
"CouponTemplateUsableEndTime": "Usable end time",
"CouponTemplateConditionAmount": "Condition amount",
"CouponTemplateDiscountAmount": "Discount amount",
"CouponTemplateIsUnscoped": "Unscoped",
"CreateCouponTemplate": "New",
"EditCouponTemplate": "Edit",
"CouponTemplateDeletionConfirmationMessage": "Are you sure to delete the coupon template {0}?",
"SuccessfullyDeleted": "Successfully deleted",
"CouponType.Normal": "X discount",
"CouponType.PerMeet": "X discount for every Y",
"CouponType.Custom": "Custom",
"Permission:Coupon": "Coupon",
"Menu:Coupon": "MenuCoupon",
"Menu:Coupon": "Coupon",
"Coupon": "Coupon",
"CouponCouponTemplateId": "CouponCouponTemplateId",
"CouponUserId": "CouponUserId",
"CouponOrderId": "CouponOrderId",
"CouponExpirationTime": "CouponExpirationTime",
"CouponUsedTime": "CouponUsedTime",
"CouponDiscountedAmount": "CouponDiscountedAmount",
"CreateCoupon": "CreateCoupon",
"EditCoupon": "EditCoupon",
"CouponCouponTemplateId": "Coupon template ID",
"CouponUserId": "User ID",
"CouponOrderId": "Order ID",
"CouponExpirationTime": "Expiration time",
"CouponUsedTime": "Used time",
"CouponDiscountedAmount": "Discounted amount",
"CreateCoupon": "New",
"EditCoupon": "Edit",
"CouponDeletionConfirmationMessage": "Are you sure to delete the coupon {0}?"
}
}

56
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/Localization/vi.json

@ -1,39 +1,41 @@
{
"culture": "vi",
"texts": {
"Permission:CouponTemplate": "CouponTemplate",
"Permission:CouponTemplate": "Coupon template",
"Permission:Create": "Create",
"Permission:Update": "Update",
"Permission:Delete": "Delete",
"Menu:CouponTemplate": "MenuCouponTemplate",
"CouponTemplate": "CouponTemplate",
"CouponTemplateStoreId": "CouponTemplateStoreId",
"CouponTemplateCouponType": "CouponTemplateCouponType",
"CouponTemplateUniqueName": "CouponTemplateUniqueName",
"CouponTemplateDisplayName": "CouponTemplateDisplayName",
"CouponTemplateDescription": "CouponTemplateDescription",
"CouponTemplateUsableDuration": "CouponTemplateUsableDuration",
"CouponTemplateUsableBeginTime": "CouponTemplateUsableBeginTime",
"CouponTemplateUsableEndTime": "CouponTemplateUsableEndTime",
"CouponTemplateConditionAmount": "CouponTemplateConditionAmount",
"CouponTemplateDiscountAmount": "CouponTemplateDiscountAmount",
"CouponTemplateIsCrossProductAllowed": "CouponTemplateIsCrossProductAllowed",
"CouponTemplateIsUnscoped": "CouponTemplateIsUnscoped",
"CreateCouponTemplate": "CreateCouponTemplate",
"EditCouponTemplate": "EditCouponTemplate",
"CouponTemplateDeletionConfirmationMessage": "Are you sure to delete the couponTemplate {0}?",
"Menu:CouponTemplate": "Coupon template",
"CouponTemplate": "Coupon template",
"CouponTemplateStoreId": "Store ID",
"CouponTemplateCouponType": "Coupon type",
"CouponTemplateUniqueName": "Unique name",
"CouponTemplateDisplayName": "Display name",
"CouponTemplateDescription": "Description",
"CouponTemplateUsableDuration": "Usable duration",
"CouponTemplateUsableBeginTime": "Usable begin time",
"CouponTemplateUsableEndTime": "Usable end time",
"CouponTemplateConditionAmount": "Condition amount",
"CouponTemplateDiscountAmount": "Discount amount",
"CouponTemplateIsUnscoped": "Unscoped",
"CreateCouponTemplate": "New",
"EditCouponTemplate": "Edit",
"CouponTemplateDeletionConfirmationMessage": "Are you sure to delete the coupon template {0}?",
"SuccessfullyDeleted": "Successfully deleted",
"CouponType.Normal": "X discount",
"CouponType.PerMeet": "X discount for every Y",
"CouponType.Custom": "Custom",
"Permission:Coupon": "Coupon",
"Menu:Coupon": "MenuCoupon",
"Menu:Coupon": "Coupon",
"Coupon": "Coupon",
"CouponCouponTemplateId": "CouponCouponTemplateId",
"CouponUserId": "CouponUserId",
"CouponOrderId": "CouponOrderId",
"CouponExpirationTime": "CouponExpirationTime",
"CouponUsedTime": "CouponUsedTime",
"CouponDiscountedAmount": "CouponDiscountedAmount",
"CreateCoupon": "CreateCoupon",
"EditCoupon": "EditCoupon",
"CouponCouponTemplateId": "Coupon template ID",
"CouponUserId": "User ID",
"CouponOrderId": "Order ID",
"CouponExpirationTime": "Expiration time",
"CouponUsedTime": "Used time",
"CouponDiscountedAmount": "Discounted amount",
"CreateCoupon": "New",
"EditCoupon": "Edit",
"CouponDeletionConfirmationMessage": "Are you sure to delete the coupon {0}?"
}
}

71
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/Localization/zh-Hans.json

@ -2,39 +2,42 @@
"culture": "zh-Hans",
"texts": {
"ManageYourProfile": "管理个人资料",
"Permission:CouponTemplate": "CouponTemplate",
"Permission:Create": "Create",
"Permission:Update": "Update",
"Permission:Delete": "Delete",
"Menu:CouponTemplate": "MenuCouponTemplate",
"CouponTemplate": "CouponTemplate",
"CouponTemplateStoreId": "CouponTemplateStoreId",
"CouponTemplateCouponType": "CouponTemplateCouponType",
"CouponTemplateUniqueName": "CouponTemplateUniqueName",
"CouponTemplateDisplayName": "CouponTemplateDisplayName",
"CouponTemplateDescription": "CouponTemplateDescription",
"CouponTemplateUsableDuration": "CouponTemplateUsableDuration",
"CouponTemplateUsableBeginTime": "CouponTemplateUsableBeginTime",
"CouponTemplateUsableEndTime": "CouponTemplateUsableEndTime",
"CouponTemplateConditionAmount": "CouponTemplateConditionAmount",
"CouponTemplateDiscountAmount": "CouponTemplateDiscountAmount",
"CouponTemplateIsCrossProductAllowed": "CouponTemplateIsCrossProductAllowed",
"CouponTemplateIsUnscoped": "CouponTemplateIsUnscoped",
"CreateCouponTemplate": "CreateCouponTemplate",
"EditCouponTemplate": "EditCouponTemplate",
"CouponTemplateDeletionConfirmationMessage": "Are you sure to delete the couponTemplate {0}?",
"SuccessfullyDeleted": "Successfully deleted",
"Permission:Coupon": "Coupon",
"Menu:Coupon": "MenuCoupon",
"Coupon": "Coupon",
"CouponCouponTemplateId": "CouponCouponTemplateId",
"CouponUserId": "CouponUserId",
"CouponOrderId": "CouponOrderId",
"CouponExpirationTime": "CouponExpirationTime",
"CouponUsedTime": "CouponUsedTime",
"CouponDiscountedAmount": "CouponDiscountedAmount",
"CreateCoupon": "CreateCoupon",
"EditCoupon": "EditCoupon",
"CouponDeletionConfirmationMessage": "Are you sure to delete the coupon {0}?"
"Menu:CouponManagement": "优惠券",
"Permission:CouponTemplate": "优惠券模板",
"Permission:Create": "新建",
"Permission:Update": "更新",
"Permission:Delete": "删除",
"Menu:CouponTemplate": "优惠券模板",
"CouponTemplate": "优惠券模板",
"CouponTemplateStoreId": "店铺 ID",
"CouponTemplateCouponType": "类型",
"CouponTemplateUniqueName": "编号",
"CouponTemplateDisplayName": "名称",
"CouponTemplateDescription": "描述",
"CouponTemplateUsableDuration": "领取后有效时长",
"CouponTemplateUsableBeginTime": "可用开始时间",
"CouponTemplateUsableEndTime": "可用截止时间",
"CouponTemplateConditionAmount": "条件金额",
"CouponTemplateDiscountAmount": "减免金额",
"CouponTemplateIsUnscoped": "不限使用范围",
"CreateCouponTemplate": "新建",
"EditCouponTemplate": "编辑",
"CouponTemplateDeletionConfirmationMessage": "确认删除优惠券模板 {0}?",
"SuccessfullyDeleted": "删除成功",
"CouponType.Normal": "代金券(满 X 减 Y)",
"CouponType.PerMeet": "满减券(每满 X 减 Y)",
"CouponType.Custom": "自定义",
"Permission:Coupon": "优惠券",
"Menu:Coupon": "优惠券",
"Coupon": "优惠券",
"CouponCouponTemplateId": "优惠券模板 ID",
"CouponUserId": "用户 ID",
"CouponOrderId": "订单 ID",
"CouponExpirationTime": "有效期至",
"CouponUsedTime": "使用时间",
"CouponDiscountedAmount": "实际抵扣金额",
"CreateCoupon": "新建",
"EditCoupon": "编辑",
"CouponDeletionConfirmationMessage": "确认删除优惠券 {0}?"
}
}

71
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/Localization/zh-Hant.json

@ -2,39 +2,42 @@
"culture": "zh-Hant",
"texts": {
"ManageYourProfile": "管理個人資料",
"Permission:CouponTemplate": "CouponTemplate",
"Permission:Create": "Create",
"Permission:Update": "Update",
"Permission:Delete": "Delete",
"Menu:CouponTemplate": "MenuCouponTemplate",
"CouponTemplate": "CouponTemplate",
"CouponTemplateStoreId": "CouponTemplateStoreId",
"CouponTemplateCouponType": "CouponTemplateCouponType",
"CouponTemplateUniqueName": "CouponTemplateUniqueName",
"CouponTemplateDisplayName": "CouponTemplateDisplayName",
"CouponTemplateDescription": "CouponTemplateDescription",
"CouponTemplateUsableDuration": "CouponTemplateUsableDuration",
"CouponTemplateUsableBeginTime": "CouponTemplateUsableBeginTime",
"CouponTemplateUsableEndTime": "CouponTemplateUsableEndTime",
"CouponTemplateConditionAmount": "CouponTemplateConditionAmount",
"CouponTemplateDiscountAmount": "CouponTemplateDiscountAmount",
"CouponTemplateIsCrossProductAllowed": "CouponTemplateIsCrossProductAllowed",
"CouponTemplateIsUnscoped": "CouponTemplateIsUnscoped",
"CreateCouponTemplate": "CreateCouponTemplate",
"EditCouponTemplate": "EditCouponTemplate",
"CouponTemplateDeletionConfirmationMessage": "Are you sure to delete the couponTemplate {0}?",
"SuccessfullyDeleted": "Successfully deleted",
"Permission:Coupon": "Coupon",
"Menu:Coupon": "MenuCoupon",
"Coupon": "Coupon",
"CouponCouponTemplateId": "CouponCouponTemplateId",
"CouponUserId": "CouponUserId",
"CouponOrderId": "CouponOrderId",
"CouponExpirationTime": "CouponExpirationTime",
"CouponUsedTime": "CouponUsedTime",
"CouponDiscountedAmount": "CouponDiscountedAmount",
"CreateCoupon": "CreateCoupon",
"EditCoupon": "EditCoupon",
"CouponDeletionConfirmationMessage": "Are you sure to delete the coupon {0}?"
"Menu:CouponManagement": "優惠券",
"Permission:CouponTemplate": "優惠券模板",
"Permission:Create": "新建",
"Permission:Update": "更新",
"Permission:Delete": "刪除",
"Menu:CouponTemplate": "優惠券模板",
"CouponTemplate": "優惠券模板",
"CouponTemplateStoreId": "店鋪 ID",
"CouponTemplateCouponType": "類型",
"CouponTemplateUniqueName": "編號",
"CouponTemplateDisplayName": "名稱",
"CouponTemplateDescription": "描述",
"CouponTemplateUsableDuration": "領取後有效時長",
"CouponTemplateUsableBeginTime": "可用開始時間",
"CouponTemplateUsableEndTime": "可用截止時間",
"CouponTemplateConditionAmount": "條件金額",
"CouponTemplateDiscountAmount": "減免金額",
"CouponTemplateIsUnscoped": "不限使用範圍",
"CreateCouponTemplate": "新建",
"EditCouponTemplate": "編輯",
"CouponTemplateDeletionConfirmationMessage": "確認刪除優惠券模板 {0}?",
"SuccessfullyDeleted": "刪除成功",
"CouponType.Normal": "代金券(滿 X 減 Y)",
"CouponType.PerMeet": "滿減券(每滿 X 減 Y)",
"CouponType.Custom": "自定義",
"Permission:Coupon": "優惠券",
"Menu:Coupon": "優惠券",
"Coupon": "優惠券",
"CouponCouponTemplateId": "優惠券模板 ID",
"CouponUserId": "用戶 ID",
"CouponOrderId": "訂單 ID",
"CouponExpirationTime": "有效期至",
"CouponUsedTime": "使用時間",
"CouponDiscountedAmount": "實際抵扣金額",
"CreateCoupon": "新建",
"EditCoupon": "編輯",
"CouponDeletionConfirmationMessage": "確認刪除優惠券 {0}?"
}
}

1
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain/EasyAbp.EShop.Plugins.Coupons.Domain.csproj

@ -10,6 +10,7 @@
<ItemGroup>
<PackageReference Include="Volo.Abp.AutoMapper" Version="3.1.2" />
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="3.1.2" />
<ProjectReference Include="..\..\..\..\modules\EasyAbp.EShop.Orders\src\EasyAbp.EShop.Orders.Domain.Shared\EasyAbp.EShop.Orders.Domain.Shared.csproj" />
<ProjectReference Include="..\EasyAbp.EShop.Plugins.Coupons.Domain.Shared\EasyAbp.EShop.Plugins.Coupons.Domain.Shared.csproj" />
</ItemGroup>

8
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain/EasyAbp/EShop/Plugins/Coupons/CouponTemplates/CouponTemplate.cs

@ -43,12 +43,6 @@ namespace EasyAbp.EShop.Plugins.Coupons.CouponTemplates
public virtual decimal ConditionAmount { get; protected set; }
public virtual decimal DiscountAmount { get; protected set; }
/// <summary>
/// If it is set to true, the coupon can be used when the total price of all the order lines in the order
/// with products in scope is greater than or equal to ConditionAmount.
/// </summary>
public virtual bool IsCrossProductAllowed { get; protected set; }
/// <summary>
/// The coupon can be used for any product of any store if this property is set to true.
@ -75,7 +69,6 @@ namespace EasyAbp.EShop.Plugins.Coupons.CouponTemplates
DateTime? usableEndTime,
decimal conditionAmount,
decimal discountAmount,
bool isCrossProductAllowed,
bool isUnscoped,
List<CouponTemplateScope> scopes
) : base(id)
@ -91,7 +84,6 @@ namespace EasyAbp.EShop.Plugins.Coupons.CouponTemplates
UsableEndTime = usableEndTime;
ConditionAmount = conditionAmount;
DiscountAmount = discountAmount;
IsCrossProductAllowed = isCrossProductAllowed;
IsUnscoped = isUnscoped;
Scopes = scopes ?? new List<CouponTemplateScope>();
}

7
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain/EasyAbp/EShop/Plugins/Coupons/Coupons/Coupon.cs

@ -39,12 +39,17 @@ namespace EasyAbp.EShop.Plugins.Coupons.Coupons
ExpirationTime = expirationTime;
}
public void SetOrderId(Guid? orderId)
{
OrderId = orderId;
}
public void SetExpirationTime(DateTime? expirationTime)
{
ExpirationTime = expirationTime;
}
public void Use(DateTime? usedTime, decimal? discountedAmount)
public void SetUsed(DateTime? usedTime, decimal? discountedAmount)
{
UsedTime = usedTime;
DiscountedAmount = discountedAmount;

37
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain/EasyAbp/EShop/Plugins/Coupons/Coupons/CouponOrderCanceledEventHandler.cs

@ -0,0 +1,37 @@
using System;
using System.Threading.Tasks;
using EasyAbp.EShop.Orders.Orders;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Uow;
namespace EasyAbp.EShop.Plugins.Coupons.Coupons
{
public class CouponOrderCanceledEventHandler : IDistributedEventHandler<OrderCanceledEto>, ITransientDependency
{
private readonly ICouponRepository _couponRepository;
public CouponOrderCanceledEventHandler(ICouponRepository couponRepository)
{
_couponRepository = couponRepository;
}
[UnitOfWork(true)]
public virtual async Task HandleEventAsync(OrderCanceledEto eventData)
{
if (!Guid.TryParse(eventData.Order.GetProperty<string>(CouponsConsts.OrderCouponIdPropertyName),
out var couponId))
{
return;
}
var coupon = await _couponRepository.GetAsync(couponId);
coupon.SetOrderId(null);
coupon.SetUsed(null, null);
await _couponRepository.UpdateAsync(coupon, true);
}
}
}

49
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain/EasyAbp/EShop/Plugins/Coupons/Coupons/CouponOrderCreatedEventHandler.cs

@ -0,0 +1,49 @@
using System;
using System.Threading.Tasks;
using EasyAbp.EShop.Orders.Orders;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Timing;
using Volo.Abp.Uow;
namespace EasyAbp.EShop.Plugins.Coupons.Coupons
{
public class CouponOrderCreatedEventHandler : IDistributedEventHandler<EntityCreatedEto<OrderEto>>, ITransientDependency
{
private readonly IClock _clock;
private readonly ICouponRepository _couponRepository;
public CouponOrderCreatedEventHandler(
IClock clock,
ICouponRepository couponRepository)
{
_clock = clock;
_couponRepository = couponRepository;
}
[UnitOfWork(true)]
public virtual async Task HandleEventAsync(EntityCreatedEto<OrderEto> eventData)
{
if (!Guid.TryParse(eventData.Entity.GetProperty<string>(CouponsConsts.OrderCouponIdPropertyName),
out var couponId))
{
return;
}
var discountAmount = eventData.Entity.GetProperty<decimal>(CouponsConsts.OrderCouponDiscountAmountPropertyName);
var coupon = await _couponRepository.GetAsync(couponId);
if (coupon.OrderId != eventData.Entity.Id)
{
throw new InvalidCouponOrderIdException(eventData.Entity.Id, coupon.OrderId);
}
coupon.SetUsed(_clock.Now, discountAmount);
await _couponRepository.UpdateAsync(coupon, true);
}
}
}

3
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain/EasyAbp/EShop/Plugins/Coupons/Coupons/ICouponRepository.cs

@ -1,9 +1,12 @@
using System;
using System.Linq;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Timing;
namespace EasyAbp.EShop.Plugins.Coupons.Coupons
{
public interface ICouponRepository : IRepository<Coupon, Guid>
{
IQueryable<Coupon> GetAvailableCouponQueryable(IClock clock);
}
}

13
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain/EasyAbp/EShop/Plugins/Coupons/Coupons/InvalidCouponOrderIdException.cs

@ -0,0 +1,13 @@
using System;
using Volo.Abp;
namespace EasyAbp.EShop.Plugins.Coupons.Coupons
{
public class InvalidCouponOrderIdException : BusinessException
{
public InvalidCouponOrderIdException(Guid expectedOrderId, Guid? actualOrderId) : base("InvalidCouponOrderId",
$"The expected order ID is {expectedOrderId}, but the actual order ID is {actualOrderId}.")
{
}
}
}

9
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain/EasyAbp/EShop/Plugins/Coupons/Coupons/RepositoryCouponLookupService.cs

@ -2,22 +2,27 @@
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.ObjectMapping;
namespace EasyAbp.EShop.Plugins.Coupons.Coupons
{
public class RepositoryCouponLookupService : ICouponLookupService, ITransientDependency
{
private readonly IObjectMapper _objectMapper;
private readonly ICouponRepository _couponRepository;
public RepositoryCouponLookupService(
IObjectMapper objectMapper,
ICouponRepository couponRepository)
{
_objectMapper = objectMapper;
_couponRepository = couponRepository;
}
public virtual async Task<ICoupon> FindByIdAsync(Guid id, CancellationToken cancellationToken = default)
public virtual async Task<CouponData> FindByIdAsync(Guid id, CancellationToken cancellationToken = default)
{
return await _couponRepository.FindAsync(id, true, cancellationToken);
return _objectMapper.Map<Coupon, CouponData>(
await _couponRepository.FindAsync(id, true, cancellationToken));
}
}
}

6
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain/EasyAbp/EShop/Plugins/Coupons/EShopPluginsCouponsDomainModule.cs

@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using EasyAbp.EShop.Orders;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AutoMapper;
using Volo.Abp.Domain;
using Volo.Abp.Modularity;
@ -8,7 +9,8 @@ namespace EasyAbp.EShop.Plugins.Coupons
[DependsOn(
typeof(AbpAutoMapperModule),
typeof(AbpDddDomainModule),
typeof(EShopPluginsCouponsDomainSharedModule)
typeof(EShopPluginsCouponsDomainSharedModule),
typeof(EShopOrdersDomainSharedModule)
)]
public class EShopPluginsCouponsDomainModule : AbpModule
{

7
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.EntityFrameworkCore/EasyAbp/EShop/Plugins/Coupons/CouponTemplates/CouponTemplateRepository.cs

@ -1,5 +1,7 @@
using System;
using System.Linq;
using EasyAbp.EShop.Plugins.Coupons.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
@ -10,5 +12,10 @@ namespace EasyAbp.EShop.Plugins.Coupons.CouponTemplates
public CouponTemplateRepository(IDbContextProvider<ICouponsDbContext> dbContextProvider) : base(dbContextProvider)
{
}
public override IQueryable<CouponTemplate> WithDetails()
{
return base.WithDetails().Include(x => x.Scopes);
}
}
}

16
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.EntityFrameworkCore/EasyAbp/EShop/Plugins/Coupons/Coupons/CouponRepository.cs

@ -1,7 +1,9 @@
using System;
using System.Linq;
using EasyAbp.EShop.Plugins.Coupons.EntityFrameworkCore;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.Timing;
namespace EasyAbp.EShop.Plugins.Coupons.Coupons
{
@ -10,5 +12,19 @@ namespace EasyAbp.EShop.Plugins.Coupons.Coupons
public CouponRepository(IDbContextProvider<ICouponsDbContext> dbContextProvider) : base(dbContextProvider)
{
}
public virtual IQueryable<Coupon> GetAvailableCouponQueryable(IClock clock)
{
var now = clock.Now;
return DbSet
.Where(x => x.ExpirationTime > now)
.Join(
DbContext.CouponTemplates.Where(x => x.UsableBeginTime <= now),
coupon => coupon.CouponTemplateId,
couponTemplate => couponTemplate.Id,
(coupon, couponTemplate) => coupon
);
}
}
}

9
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.HttpApi.Client/EasyAbp/EShop/Plugins/Coupons/Coupons/HttpClientCouponLookupService.cs

@ -1,24 +1,29 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using EasyAbp.EShop.Plugins.Coupons.Coupons.Dtos;
using Volo.Abp.DependencyInjection;
using Volo.Abp.ObjectMapping;
namespace EasyAbp.EShop.Plugins.Coupons.Coupons
{
[Dependency(TryRegister = true)]
public class HttpClientCouponLookupService : ICouponLookupService, ITransientDependency
{
private readonly IObjectMapper _objectMapper;
private readonly ICouponAppService _couponAppService;
public HttpClientCouponLookupService(
IObjectMapper objectMapper,
ICouponAppService couponAppService)
{
_objectMapper = objectMapper;
_couponAppService = couponAppService;
}
public virtual async Task<ICoupon> FindByIdAsync(Guid id, CancellationToken cancellationToken = default)
public virtual async Task<CouponData> FindByIdAsync(Guid id, CancellationToken cancellationToken = default)
{
return await _couponAppService.GetAsync(id);
return _objectMapper.Map<CouponDto, CouponData>(await _couponAppService.GetAsync(id));
}
}
}

13
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.HttpApi/EasyAbp/EShop/Plugins/Coupons/Coupons/CouponController.cs

@ -20,14 +20,14 @@ namespace EasyAbp.EShop.Plugins.Coupons.Coupons
}
[HttpPost]
public virtual Task<CouponDto> CreateAsync(CreateUpdateCouponDto input)
public virtual Task<CouponDto> CreateAsync(CreateCouponDto input)
{
return _service.CreateAsync(input);
}
[HttpPut]
[Route("{id}")]
public virtual Task<CouponDto> UpdateAsync(Guid id, CreateUpdateCouponDto input)
public virtual Task<CouponDto> UpdateAsync(Guid id, UpdateCouponDto input)
{
return _service.UpdateAsync(id, input);
}
@ -39,6 +39,13 @@ namespace EasyAbp.EShop.Plugins.Coupons.Coupons
return _service.DeleteAsync(id);
}
[HttpPost]
[Route("{id}/occupy")]
public Task<CouponDto> OccupyAsync(Guid id, OccupyCouponInput input)
{
return _service.OccupyAsync(id, input);
}
[HttpGet]
[Route("{id}")]
public virtual Task<CouponDto> GetAsync(Guid id)
@ -47,7 +54,7 @@ namespace EasyAbp.EShop.Plugins.Coupons.Coupons
}
[HttpGet]
public virtual Task<PagedResultDto<CouponDto>> GetListAsync(PagedAndSortedResultRequestDto input)
public virtual Task<PagedResultDto<CouponDto>> GetListAsync(GetCouponListInput input)
{
return _service.GetListAsync(input);
}

5
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Web/CouponsWebAutoMapperProfile.cs

@ -17,8 +17,9 @@ namespace EasyAbp.EShop.Plugins.Coupons.Web
CreateMap<CouponTemplateDto, CreateEditCouponTemplateViewModel>();
CreateMap<CreateEditCouponTemplateViewModel, CreateUpdateCouponTemplateDto>()
.Ignore(x => x.Scopes);
CreateMap<CouponDto, CreateEditCouponViewModel>();
CreateMap<CreateEditCouponViewModel, CreateUpdateCouponDto>();
CreateMap<CouponDto, EditCouponViewModel>();
CreateMap<CreateCouponViewModel, CreateCouponDto>();
CreateMap<EditCouponViewModel, UpdateCouponDto>();
}
}
}

2
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Web/Pages/EShop/Plugins/Coupons/CouponTemplates/CouponTemplate/Index.cshtml

@ -57,9 +57,7 @@
<th>@L["CouponTemplateUsableEndTime"]</th>
<th>@L["CouponTemplateConditionAmount"]</th>
<th>@L["CouponTemplateDiscountAmount"]</th>
<th>@L["CouponTemplateIsCrossProductAllowed"]</th>
<th>@L["CouponTemplateIsUnscoped"]</th>
<th>@L["CouponTemplateScopes"]</th>
</tr>
</thead>
</abp-table>

3
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Web/Pages/EShop/Plugins/Coupons/CouponTemplates/CouponTemplate/ViewModels/CreateEditCouponTemplateViewModel.cs

@ -37,9 +37,6 @@ namespace EasyAbp.EShop.Plugins.Coupons.Web.Pages.EShop.Plugins.Coupons.CouponTe
[Display(Name = "CouponTemplateDiscountAmount")]
public decimal DiscountAmount { get; set; }
[Display(Name = "CouponTemplateIsCrossProductAllowed")]
public bool IsCrossProductAllowed { get; set; }
[Display(Name = "CouponTemplateIsUnscoped")]
public bool IsUnscoped { get; set; }
}

1
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Web/Pages/EShop/Plugins/Coupons/CouponTemplates/CouponTemplate/index.js

@ -54,7 +54,6 @@ $(function () {
{ data: "usableEndTime" },
{ data: "conditionAmount" },
{ data: "discountAmount" },
{ data: "isCrossProductAllowed" },
{ data: "isUnscoped" },
]
}));

4
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Web/Pages/EShop/Plugins/Coupons/Coupons/Coupon/CreateModal.cshtml.cs

@ -9,7 +9,7 @@ namespace EasyAbp.EShop.Plugins.Coupons.Web.Pages.EShop.Plugins.Coupons.Coupons.
public class CreateModalModel : CouponsPageModel
{
[BindProperty]
public CreateEditCouponViewModel ViewModel { get; set; }
public CreateCouponViewModel ViewModel { get; set; }
private readonly ICouponAppService _service;
@ -20,7 +20,7 @@ namespace EasyAbp.EShop.Plugins.Coupons.Web.Pages.EShop.Plugins.Coupons.Coupons.
public virtual async Task<IActionResult> OnPostAsync()
{
var dto = ObjectMapper.Map<CreateEditCouponViewModel, CreateUpdateCouponDto>(ViewModel);
var dto = ObjectMapper.Map<CreateCouponViewModel, CreateCouponDto>(ViewModel);
await _service.CreateAsync(dto);
return NoContent();
}

6
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Web/Pages/EShop/Plugins/Coupons/Coupons/Coupon/EditModal.cshtml.cs

@ -14,7 +14,7 @@ namespace EasyAbp.EShop.Plugins.Coupons.Web.Pages.EShop.Plugins.Coupons.Coupons.
public Guid Id { get; set; }
[BindProperty]
public CreateEditCouponViewModel ViewModel { get; set; }
public EditCouponViewModel ViewModel { get; set; }
private readonly ICouponAppService _service;
@ -26,12 +26,12 @@ namespace EasyAbp.EShop.Plugins.Coupons.Web.Pages.EShop.Plugins.Coupons.Coupons.
public virtual async Task OnGetAsync()
{
var dto = await _service.GetAsync(Id);
ViewModel = ObjectMapper.Map<CouponDto, CreateEditCouponViewModel>(dto);
ViewModel = ObjectMapper.Map<CouponDto, EditCouponViewModel>(dto);
}
public virtual async Task<IActionResult> OnPostAsync()
{
var dto = ObjectMapper.Map<CreateEditCouponViewModel, CreateUpdateCouponDto>(ViewModel);
var dto = ObjectMapper.Map<EditCouponViewModel, UpdateCouponDto>(ViewModel);
await _service.UpdateAsync(Id, dto);
return NoContent();
}

3
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Web/Pages/EShop/Plugins/Coupons/Coupons/Coupon/ViewModels/CreateEditCouponViewModel.cs → plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Web/Pages/EShop/Plugins/Coupons/Coupons/Coupon/ViewModels/CreateCouponViewModel.cs

@ -1,10 +1,9 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace EasyAbp.EShop.Plugins.Coupons.Web.Pages.EShop.Plugins.Coupons.Coupons.Coupon.ViewModels
{
public class CreateEditCouponViewModel
public class CreateCouponViewModel
{
[Display(Name = "CouponCouponTemplateId")]
public Guid CouponTemplateId { get; set; }

11
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Web/Pages/EShop/Plugins/Coupons/Coupons/Coupon/ViewModels/EditCouponViewModel.cs

@ -0,0 +1,11 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace EasyAbp.EShop.Plugins.Coupons.Web.Pages.EShop.Plugins.Coupons.Coupons.Coupon.ViewModels
{
public class EditCouponViewModel
{
[Display(Name = "CouponExpirationTime")]
public DateTime? ExpirationTime { get; set; }
}
}

4556
samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore.DbMigrations/Migrations/20200927094647_RemovedIsCrossProductAllowed.Designer.cs

File diff suppressed because it is too large

24
samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore.DbMigrations/Migrations/20200927094647_RemovedIsCrossProductAllowed.cs

@ -0,0 +1,24 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace EShopSample.Migrations
{
public partial class RemovedIsCrossProductAllowed : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsCrossProductAllowed",
table: "EasyAbpEShopPluginsCouponsCouponTemplates");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "IsCrossProductAllowed",
table: "EasyAbpEShopPluginsCouponsCouponTemplates",
type: "bit",
nullable: false,
defaultValue: false);
}
}
}

3
samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore.DbMigrations/Migrations/EShopSampleMigrationsDbContextModelSnapshot.cs

@ -783,9 +783,6 @@ namespace EShopSample.Migrations
.HasColumnName("ExtraProperties")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsCrossProductAllowed")
.HasColumnType("bit");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnName("IsDeleted")

7
samples/EShopSample/aspnet-core/src/EShopSample.Web/EShopSampleWebModule.cs

@ -9,6 +9,7 @@ using EasyAbp.EShop.Payments;
using EasyAbp.EShop.Payments.Web;
using EasyAbp.EShop.Plugins.Baskets;
using EasyAbp.EShop.Plugins.Baskets.Web;
using EasyAbp.EShop.Plugins.Coupons;
using EasyAbp.EShop.Plugins.Coupons.Web;
using EasyAbp.EShop.Products;
using EasyAbp.EShop.Products.Web;
@ -189,6 +190,12 @@ namespace EShopSample.Web
options.FileSets.ReplaceEmbeddedByPhysical<EShopPluginsBasketsApplicationContractsModule>(Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}plugins{Path.DirectorySeparatorChar}Baskets{Path.DirectorySeparatorChar}src{Path.DirectorySeparatorChar}EasyAbp.EShop.Plugins.Baskets.Application.Contracts"));
options.FileSets.ReplaceEmbeddedByPhysical<EShopPluginsBasketsApplicationModule>(Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}plugins{Path.DirectorySeparatorChar}Baskets{Path.DirectorySeparatorChar}src{Path.DirectorySeparatorChar}EasyAbp.EShop.Plugins.Baskets.Application"));
options.FileSets.ReplaceEmbeddedByPhysical<EShopPluginsBasketsWebModule>(Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}plugins{Path.DirectorySeparatorChar}Baskets{Path.DirectorySeparatorChar}src{Path.DirectorySeparatorChar}EasyAbp.EShop.Plugins.Baskets.Web"));
options.FileSets.ReplaceEmbeddedByPhysical<EShopPluginsCouponsDomainSharedModule>(Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}plugins{Path.DirectorySeparatorChar}Coupons{Path.DirectorySeparatorChar}src{Path.DirectorySeparatorChar}EasyAbp.EShop.Plugins.Coupons.Domain.Shared"));
options.FileSets.ReplaceEmbeddedByPhysical<EShopPluginsCouponsDomainModule>(Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}plugins{Path.DirectorySeparatorChar}Coupons{Path.DirectorySeparatorChar}src{Path.DirectorySeparatorChar}EasyAbp.EShop.Plugins.Coupons.Domain"));
options.FileSets.ReplaceEmbeddedByPhysical<EShopPluginsCouponsApplicationContractsModule>(Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}plugins{Path.DirectorySeparatorChar}Coupons{Path.DirectorySeparatorChar}src{Path.DirectorySeparatorChar}EasyAbp.EShop.Plugins.Coupons.Application.Contracts"));
options.FileSets.ReplaceEmbeddedByPhysical<EShopPluginsCouponsApplicationModule>(Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}plugins{Path.DirectorySeparatorChar}Coupons{Path.DirectorySeparatorChar}src{Path.DirectorySeparatorChar}EasyAbp.EShop.Plugins.Coupons.Application"));
options.FileSets.ReplaceEmbeddedByPhysical<EShopPluginsCouponsWebModule>(Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}plugins{Path.DirectorySeparatorChar}Coupons{Path.DirectorySeparatorChar}src{Path.DirectorySeparatorChar}EasyAbp.EShop.Plugins.Coupons.Web"));
});
}
}

Loading…
Cancel
Save