mirror of https://github.com/EasyAbp/EShop.git
26 changed files with 4991 additions and 17 deletions
@ -0,0 +1,15 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netcoreapp3.1</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<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" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,101 @@ |
|||||
|
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) |
||||
|
{ |
||||
|
var couponId = resource.Input.GetProperty<Guid?>(CouponsConsts.OrderCouponIdPropertyName); |
||||
|
|
||||
|
if (!couponId.HasValue) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var now = _clock.Now; |
||||
|
|
||||
|
var coupon = await _couponLookupService.FindByIdAsync(couponId.Value); |
||||
|
|
||||
|
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(ICouponTemplate 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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
using EasyAbp.EShop.Orders.Plugins.Coupons.Authorization; |
||||
|
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) |
||||
|
)] |
||||
|
public class EShopOrdersPluginsCouponsModule : AbpModule |
||||
|
{ |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddSingleton<IAuthorizationHandler, CouponOrderCreationAuthorizationHandler>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
using System; |
||||
|
using EasyAbp.EShop.Orders.Orders; |
||||
|
using EasyAbp.EShop.Orders.Orders.Dtos; |
||||
|
using EasyAbp.EShop.Plugins.Coupons; |
||||
|
using Volo.Abp.ObjectExtending; |
||||
|
using Volo.Abp.Threading; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Orders.Plugins.Coupons.ObjectExtending |
||||
|
{ |
||||
|
public static class EShopOrdersPluginsCouponsObjectExtensions |
||||
|
{ |
||||
|
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); |
||||
|
|
||||
|
public static void Configure() |
||||
|
{ |
||||
|
OneTimeRunner.Run(() => |
||||
|
{ |
||||
|
/* You can configure extension properties to entities or other object types |
||||
|
* defined in the depended modules. |
||||
|
* |
||||
|
* If you are using EF Core and want to map the entity extension properties to new |
||||
|
* table fields in the database, then configure them in the EShopSampleEfCoreEntityExtensionMappings |
||||
|
* |
||||
|
* Example: |
||||
|
* |
||||
|
* ObjectExtensionManager.Instance |
||||
|
* .AddOrUpdateProperty<IdentityRole, string>("Title"); |
||||
|
* |
||||
|
* See the documentation for more: |
||||
|
* https://docs.abp.io/en/abp/latest/Object-Extensions
|
||||
|
*/ |
||||
|
|
||||
|
ObjectExtensionManager.Instance |
||||
|
.AddOrUpdate( |
||||
|
new[] |
||||
|
{ |
||||
|
typeof(Order), |
||||
|
typeof(CreateOrderDto) |
||||
|
}, |
||||
|
config => |
||||
|
{ |
||||
|
config.AddOrUpdateProperty<Guid?>(CouponsConsts.OrderCouponIdPropertyName); |
||||
|
} |
||||
|
); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using EasyAbp.EShop.Orders.Orders; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Orders.Plugins.Coupons.OrderDiscount |
||||
|
{ |
||||
|
public class CouponOrderDiscountProvider : IOrderDiscountProvider, ITransientDependency |
||||
|
{ |
||||
|
public async Task<Order> DiscountAsync(Order order, Dictionary<string, object> inputExtraProperties) |
||||
|
{ |
||||
|
throw new System.NotImplementedException(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using JetBrains.Annotations; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Plugins.Coupons.CouponTemplates |
||||
|
{ |
||||
|
public interface ICouponTemplate |
||||
|
{ |
||||
|
Guid? TenantId { get; } |
||||
|
|
||||
|
Guid? StoreId { get; } |
||||
|
|
||||
|
CouponType CouponType { get; } |
||||
|
|
||||
|
[CanBeNull] |
||||
|
string UniqueName { get; } |
||||
|
|
||||
|
[NotNull] |
||||
|
string DisplayName { get; } |
||||
|
|
||||
|
[CanBeNull] |
||||
|
string Description { get; } |
||||
|
|
||||
|
TimeSpan? UsableDuration { get; } |
||||
|
|
||||
|
DateTime? UsableBeginTime { get; } |
||||
|
|
||||
|
DateTime? UsableEndTime { get; } |
||||
|
|
||||
|
decimal ConditionAmount { get; } |
||||
|
|
||||
|
decimal DiscountAmount { get; } |
||||
|
|
||||
|
bool IsCrossProductAllowed { get; } |
||||
|
|
||||
|
bool IsUnscoped { get; } |
||||
|
|
||||
|
IEnumerable<ICouponTemplateScope> Scopes { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using System; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Plugins.Coupons.CouponTemplates |
||||
|
{ |
||||
|
public interface ICouponTemplateLookupService |
||||
|
{ |
||||
|
Task<ICouponTemplate> FindByIdAsync(Guid id, CancellationToken cancellationToken = default); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
using System; |
||||
|
using EasyAbp.EShop.Stores.Stores; |
||||
|
using JetBrains.Annotations; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Plugins.Coupons.CouponTemplates |
||||
|
{ |
||||
|
public interface ICouponTemplateScope : IMultiStore |
||||
|
{ |
||||
|
[CanBeNull] |
||||
|
string ProductGroupName { get; } |
||||
|
|
||||
|
Guid? ProductId { get; } |
||||
|
|
||||
|
Guid? ProductSkuId { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Plugins.Coupons.Coupons |
||||
|
{ |
||||
|
public interface ICoupon |
||||
|
{ |
||||
|
Guid CouponTemplateId { get; } |
||||
|
|
||||
|
Guid UserId { get; } |
||||
|
|
||||
|
Guid? OrderId { get; } |
||||
|
|
||||
|
DateTime? ExpirationTime { get; } |
||||
|
|
||||
|
DateTime? UsedTime { get; } |
||||
|
|
||||
|
decimal? DiscountedAmount { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using System; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Plugins.Coupons.Coupons |
||||
|
{ |
||||
|
public interface ICouponLookupService |
||||
|
{ |
||||
|
Task<ICoupon> FindByIdAsync(Guid id, CancellationToken cancellationToken = default); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace EasyAbp.EShop.Plugins.Coupons |
||||
|
{ |
||||
|
public static class CouponsConsts |
||||
|
{ |
||||
|
public const string OrderCouponIdPropertyName = "CouponId"; |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,89 @@ |
|||||
|
using Microsoft.EntityFrameworkCore.Migrations; |
||||
|
|
||||
|
namespace EShopSample.Migrations |
||||
|
{ |
||||
|
public partial class ConfiguredCouponTemplateScope : Migration |
||||
|
{ |
||||
|
protected override void Up(MigrationBuilder migrationBuilder) |
||||
|
{ |
||||
|
migrationBuilder.DropForeignKey( |
||||
|
name: "FK_CouponTemplateScope_EasyAbpEShopPluginsCouponsCouponTemplates_CouponTemplateId", |
||||
|
table: "CouponTemplateScope"); |
||||
|
|
||||
|
migrationBuilder.DropPrimaryKey( |
||||
|
name: "PK_CouponTemplateScope", |
||||
|
table: "CouponTemplateScope"); |
||||
|
|
||||
|
migrationBuilder.RenameTable( |
||||
|
name: "CouponTemplateScope", |
||||
|
newName: "EasyAbpEShopPluginsCouponsCouponTemplateScopes"); |
||||
|
|
||||
|
migrationBuilder.RenameIndex( |
||||
|
name: "IX_CouponTemplateScope_CouponTemplateId", |
||||
|
table: "EasyAbpEShopPluginsCouponsCouponTemplateScopes", |
||||
|
newName: "IX_EasyAbpEShopPluginsCouponsCouponTemplateScopes_CouponTemplateId"); |
||||
|
|
||||
|
migrationBuilder.AlterColumn<bool>( |
||||
|
name: "IsDeleted", |
||||
|
table: "EasyAbpEShopPluginsCouponsCouponTemplateScopes", |
||||
|
nullable: false, |
||||
|
defaultValue: false, |
||||
|
oldClrType: typeof(bool), |
||||
|
oldType: "bit"); |
||||
|
|
||||
|
migrationBuilder.AddPrimaryKey( |
||||
|
name: "PK_EasyAbpEShopPluginsCouponsCouponTemplateScopes", |
||||
|
table: "EasyAbpEShopPluginsCouponsCouponTemplateScopes", |
||||
|
column: "Id"); |
||||
|
|
||||
|
migrationBuilder.AddForeignKey( |
||||
|
name: "FK_EasyAbpEShopPluginsCouponsCouponTemplateScopes_EasyAbpEShopPluginsCouponsCouponTemplates_CouponTemplateId", |
||||
|
table: "EasyAbpEShopPluginsCouponsCouponTemplateScopes", |
||||
|
column: "CouponTemplateId", |
||||
|
principalTable: "EasyAbpEShopPluginsCouponsCouponTemplates", |
||||
|
principalColumn: "Id", |
||||
|
onDelete: ReferentialAction.Restrict); |
||||
|
} |
||||
|
|
||||
|
protected override void Down(MigrationBuilder migrationBuilder) |
||||
|
{ |
||||
|
migrationBuilder.DropForeignKey( |
||||
|
name: "FK_EasyAbpEShopPluginsCouponsCouponTemplateScopes_EasyAbpEShopPluginsCouponsCouponTemplates_CouponTemplateId", |
||||
|
table: "EasyAbpEShopPluginsCouponsCouponTemplateScopes"); |
||||
|
|
||||
|
migrationBuilder.DropPrimaryKey( |
||||
|
name: "PK_EasyAbpEShopPluginsCouponsCouponTemplateScopes", |
||||
|
table: "EasyAbpEShopPluginsCouponsCouponTemplateScopes"); |
||||
|
|
||||
|
migrationBuilder.RenameTable( |
||||
|
name: "EasyAbpEShopPluginsCouponsCouponTemplateScopes", |
||||
|
newName: "CouponTemplateScope"); |
||||
|
|
||||
|
migrationBuilder.RenameIndex( |
||||
|
name: "IX_EasyAbpEShopPluginsCouponsCouponTemplateScopes_CouponTemplateId", |
||||
|
table: "CouponTemplateScope", |
||||
|
newName: "IX_CouponTemplateScope_CouponTemplateId"); |
||||
|
|
||||
|
migrationBuilder.AlterColumn<bool>( |
||||
|
name: "IsDeleted", |
||||
|
table: "CouponTemplateScope", |
||||
|
type: "bit", |
||||
|
nullable: false, |
||||
|
oldClrType: typeof(bool), |
||||
|
oldDefaultValue: false); |
||||
|
|
||||
|
migrationBuilder.AddPrimaryKey( |
||||
|
name: "PK_CouponTemplateScope", |
||||
|
table: "CouponTemplateScope", |
||||
|
column: "Id"); |
||||
|
|
||||
|
migrationBuilder.AddForeignKey( |
||||
|
name: "FK_CouponTemplateScope_EasyAbpEShopPluginsCouponsCouponTemplates_CouponTemplateId", |
||||
|
table: "CouponTemplateScope", |
||||
|
column: "CouponTemplateId", |
||||
|
principalTable: "EasyAbpEShopPluginsCouponsCouponTemplates", |
||||
|
principalColumn: "Id", |
||||
|
onDelete: ReferentialAction.Restrict); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue