mirror of https://github.com/EasyAbp/EShop.git
107 changed files with 9047 additions and 54 deletions
@ -0,0 +1,3 @@ |
|||
{ |
|||
"role": "lib.domain" |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net7.0</TargetFramework> |
|||
<Nullable>enable</Nullable> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\..\..\modules\EasyAbp.EShop.Orders\src\EasyAbp.EShop.Orders.Domain\EasyAbp.EShop.Orders.Domain.csproj" /> |
|||
<ProjectReference Include="..\EasyAbp.EShop.Plugins.Promotions.Application.Contracts\EasyAbp.EShop.Plugins.Promotions.Application.Contracts.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,12 @@ |
|||
using EasyAbp.EShop.Plugins.Promotions; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace EasyAbp.EShop.Orders.Plugins.Promotions; |
|||
|
|||
[DependsOn( |
|||
typeof(EShopPluginsPromotionsApplicationContractsModule), |
|||
typeof(EShopOrdersDomainModule) |
|||
)] |
|||
public class EShopOrdersPluginsPromotionsDomainModule : AbpModule |
|||
{ |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Plugins.Promotions.Promotions; |
|||
using EasyAbp.EShop.Plugins.Promotions.Promotions.Dtos; |
|||
using EasyAbp.EShop.Orders.Orders; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace EasyAbp.EShop.Orders.Plugins.Promotions; |
|||
|
|||
public class PromotionOrderDiscountProvider : IOrderDiscountProvider, ITransientDependency |
|||
{ |
|||
public static int PromotionOrderDiscountEffectOrder { get; set; } = 5000; |
|||
|
|||
public int EffectOrder => PromotionOrderDiscountEffectOrder; |
|||
|
|||
protected IPromotionIntegrationService PromotionIntegrationService { get; } |
|||
|
|||
public PromotionOrderDiscountProvider(IPromotionIntegrationService promotionIntegrationService) |
|||
{ |
|||
PromotionIntegrationService = promotionIntegrationService; |
|||
} |
|||
|
|||
public virtual async Task DiscountAsync(OrderDiscountContext context) |
|||
{ |
|||
var dto = await PromotionIntegrationService.DiscountOrderAsync(new DiscountOrderInputDto(context)); |
|||
|
|||
if (dto.Context.Equals(context)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
context.CandidateDiscounts.Clear(); |
|||
context.CandidateDiscounts.AddRange(dto.Context.CandidateDiscounts); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
2
plugins/Promotions/src/EasyAbp.EShop.Plugins.Promotions.Application.Contracts/EasyAbp/EShop/Plugins/Promotions/PromotionsRemoteServiceConsts.cs → plugins/Promotions/src/EasyAbp.EShop.Plugins.Promotions.Application.Contracts/EasyAbp/EShop/Plugins/Promotions/EShopPluginsPromotionsRemoteServiceConsts.cs
2
plugins/Promotions/src/EasyAbp.EShop.Plugins.Promotions.Application.Contracts/EasyAbp/EShop/Plugins/Promotions/PromotionsRemoteServiceConsts.cs → plugins/Promotions/src/EasyAbp.EShop.Plugins.Promotions.Application.Contracts/EasyAbp/EShop/Plugins/Promotions/EShopPluginsPromotionsRemoteServiceConsts.cs
@ -1,6 +1,6 @@ |
|||
namespace EasyAbp.EShop.Plugins.Promotions; |
|||
|
|||
public class PromotionsRemoteServiceConsts |
|||
public class EShopPluginsPromotionsRemoteServiceConsts |
|||
{ |
|||
public const string RemoteServiceName = "EasyAbpEShopPluginsPromotions"; |
|||
|
|||
@ -0,0 +1,44 @@ |
|||
using System; |
|||
using EasyAbp.EShop.Stores.Stores; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Promotions.Dtos; |
|||
|
|||
[Serializable] |
|||
public class CreatePromotionDto : IMultiStore |
|||
{ |
|||
public Guid StoreId { get; set; } |
|||
|
|||
public string PromotionType { get; set; } |
|||
|
|||
public string UniqueName { get; set; } |
|||
|
|||
public string DisplayName { get; set; } |
|||
|
|||
public string Configurations { get; set; } |
|||
|
|||
public DateTime? FromTime { get; set; } |
|||
|
|||
public DateTime? ToTime { get; set; } |
|||
|
|||
public bool Disabled { get; set; } |
|||
|
|||
public int Priority { get; set; } |
|||
|
|||
public CreatePromotionDto() |
|||
{ |
|||
} |
|||
|
|||
public CreatePromotionDto(Guid storeId, string promotionType, string uniqueName, string displayName, |
|||
string configurations, DateTime? fromTime, DateTime? toTime, bool disabled, int priority) |
|||
{ |
|||
StoreId = storeId; |
|||
PromotionType = promotionType; |
|||
UniqueName = uniqueName; |
|||
DisplayName = displayName; |
|||
Configurations = configurations; |
|||
FromTime = fromTime; |
|||
ToTime = toTime; |
|||
Disabled = disabled; |
|||
Priority = priority; |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using System; |
|||
using EasyAbp.EShop.Orders.Orders; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Promotions.Dtos; |
|||
|
|||
[Serializable] |
|||
public class DiscountOrderInputDto |
|||
{ |
|||
public OrderDiscountContext Context { get; set; } |
|||
|
|||
public DiscountOrderInputDto() |
|||
{ |
|||
} |
|||
|
|||
public DiscountOrderInputDto(OrderDiscountContext context) |
|||
{ |
|||
Context = context; |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using System; |
|||
using EasyAbp.EShop.Orders.Orders; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Promotions.Dtos; |
|||
|
|||
[Serializable] |
|||
public class DiscountOrderOutputDto |
|||
{ |
|||
public OrderDiscountContext Context { get; set; } |
|||
|
|||
public DiscountOrderOutputDto() |
|||
{ |
|||
} |
|||
|
|||
public DiscountOrderOutputDto(OrderDiscountContext context) |
|||
{ |
|||
Context = context; |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using System; |
|||
using EasyAbp.EShop.Products.Products; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Promotions.Dtos; |
|||
|
|||
[Serializable] |
|||
public class DiscountProductInputDto |
|||
{ |
|||
public ProductDiscountContext Context { get; set; } |
|||
|
|||
public DiscountProductInputDto() |
|||
{ |
|||
} |
|||
|
|||
public DiscountProductInputDto(ProductDiscountContext context) |
|||
{ |
|||
Context = context; |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using System; |
|||
using EasyAbp.EShop.Products.Products; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Promotions.Dtos; |
|||
|
|||
[Serializable] |
|||
public class DiscountProductOutputDto |
|||
{ |
|||
public ProductDiscountContext Context { get; set; } |
|||
|
|||
public DiscountProductOutputDto() |
|||
{ |
|||
} |
|||
|
|||
public DiscountProductOutputDto(ProductDiscountContext context) |
|||
{ |
|||
Context = context; |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Promotions.Dtos; |
|||
|
|||
[Serializable] |
|||
public class PromotionDto : FullAuditedEntityDto<Guid> |
|||
{ |
|||
public Guid StoreId { get; set; } |
|||
|
|||
public string PromotionType { get; set; } |
|||
|
|||
public string UniqueName { get; set; } |
|||
|
|||
public string DisplayName { get; set; } |
|||
|
|||
public string Configurations { get; set; } |
|||
|
|||
public DateTime? FromTime { get; set; } |
|||
|
|||
public DateTime? ToTime { get; set; } |
|||
|
|||
public bool Disabled { get; set; } |
|||
|
|||
public int Priority { get; set; } |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
using System; |
|||
using System.ComponentModel; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Promotions.Dtos; |
|||
|
|||
[Serializable] |
|||
public class PromotionGetListInput : PagedAndSortedResultRequestDto |
|||
{ |
|||
public Guid? StoreId { get; set; } |
|||
|
|||
public string? PromotionType { get; set; } |
|||
|
|||
public string? UniqueName { get; set; } |
|||
|
|||
public string? DisplayName { get; set; } |
|||
|
|||
public DateTime? FromTime { get; set; } |
|||
|
|||
public DateTime? ToTime { get; set; } |
|||
|
|||
public bool? Disabled { get; set; } |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Promotions.Dtos; |
|||
|
|||
[Serializable] |
|||
public class PromotionTypeDto |
|||
{ |
|||
public string Name { get; set; } |
|||
|
|||
public string DisplayName { get; set; } |
|||
|
|||
public string ConfigurationsTemplate { get; set; } |
|||
|
|||
public PromotionTypeDto(string name, string displayName, string configurationsTemplate) |
|||
{ |
|||
Name = name; |
|||
DisplayName = displayName; |
|||
ConfigurationsTemplate = configurationsTemplate; |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
using System; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Promotions.Dtos; |
|||
|
|||
[Serializable] |
|||
public class UpdatePromotionDto |
|||
{ |
|||
public string DisplayName { get; set; } |
|||
|
|||
public string Configurations { get; set; } |
|||
|
|||
public DateTime? FromTime { get; set; } |
|||
|
|||
public DateTime? ToTime { get; set; } |
|||
|
|||
public bool Disabled { get; set; } |
|||
|
|||
public int Priority { get; set; } |
|||
|
|||
public UpdatePromotionDto() |
|||
{ |
|||
} |
|||
|
|||
public UpdatePromotionDto(string displayName, string configurations, DateTime? fromTime, DateTime? toTime, |
|||
bool disabled, int priority) |
|||
{ |
|||
DisplayName = displayName; |
|||
Configurations = configurations; |
|||
FromTime = fromTime; |
|||
ToTime = toTime; |
|||
Disabled = disabled; |
|||
Priority = priority; |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Plugins.Promotions.Promotions.Dtos; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Promotions; |
|||
|
|||
public interface IPromotionAppService : |
|||
ICrudAppService< |
|||
PromotionDto, |
|||
Guid, |
|||
PromotionGetListInput, |
|||
CreatePromotionDto, |
|||
UpdatePromotionDto> |
|||
{ |
|||
Task<ListResultDto<PromotionTypeDto>> GetPromotionTypesAsync(); |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Plugins.Promotions.Promotions.Dtos; |
|||
using Volo.Abp; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Promotions; |
|||
|
|||
[IntegrationService] |
|||
public interface IPromotionIntegrationService |
|||
{ |
|||
Task<DiscountProductOutputDto> DiscountProductAsync(DiscountProductInputDto input); |
|||
|
|||
Task<DiscountOrderOutputDto> DiscountOrderAsync(DiscountOrderInputDto input); |
|||
} |
|||
@ -0,0 +1,98 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Plugins.Promotions.Localization; |
|||
using EasyAbp.EShop.Plugins.Promotions.Options; |
|||
using EasyAbp.EShop.Plugins.Promotions.Permissions; |
|||
using EasyAbp.EShop.Plugins.Promotions.Promotions.Dtos; |
|||
using EasyAbp.EShop.Plugins.Promotions.PromotionTypes; |
|||
using EasyAbp.EShop.Stores.Stores; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Json; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Promotions; |
|||
|
|||
public class PromotionAppService : MultiStoreCrudAppService<Promotion, PromotionDto, Guid, PromotionGetListInput, |
|||
CreatePromotionDto, UpdatePromotionDto>, IPromotionAppService |
|||
{ |
|||
protected override string GetPolicyName { get; set; } = PromotionsPermissions.Promotion.Default; |
|||
protected override string GetListPolicyName { get; set; } = PromotionsPermissions.Promotion.Default; |
|||
protected override string CreatePolicyName { get; set; } = PromotionsPermissions.Promotion.Create; |
|||
protected override string UpdatePolicyName { get; set; } = PromotionsPermissions.Promotion.Update; |
|||
protected override string DeletePolicyName { get; set; } = PromotionsPermissions.Promotion.Delete; |
|||
protected override string CrossStorePolicyName { get; set; } = PromotionsPermissions.Promotion.CrossStore; |
|||
|
|||
private readonly IPromotionRepository _repository; |
|||
|
|||
protected IJsonSerializer JsonSerializer { get; } |
|||
protected PromotionManager PromotionManager { get; } |
|||
protected EShopPluginsPromotionsOptions Options { get; } |
|||
|
|||
public PromotionAppService( |
|||
IJsonSerializer jsonSerializer, |
|||
IOptions<EShopPluginsPromotionsOptions> options, |
|||
PromotionManager promotionManager, |
|||
IPromotionRepository repository) : base(repository) |
|||
{ |
|||
JsonSerializer = jsonSerializer; |
|||
PromotionManager = promotionManager; |
|||
Options = options.Value; |
|||
_repository = repository; |
|||
|
|||
LocalizationResource = typeof(PromotionsResource); |
|||
ObjectMapperContext = typeof(EShopPluginsPromotionsApplicationModule); |
|||
} |
|||
|
|||
protected override async Task<IQueryable<Promotion>> CreateFilteredQueryAsync(PromotionGetListInput input) |
|||
{ |
|||
// TODO: AbpHelper generated
|
|||
return (await base.CreateFilteredQueryAsync(input)) |
|||
.WhereIf(input.StoreId != null, x => x.StoreId == input.StoreId) |
|||
.WhereIf(!input.PromotionType.IsNullOrWhiteSpace(), x => x.PromotionType.Contains(input.PromotionType!)) |
|||
.WhereIf(!input.UniqueName.IsNullOrWhiteSpace(), x => x.UniqueName.Contains(input.UniqueName!)) |
|||
.WhereIf(!input.DisplayName.IsNullOrWhiteSpace(), x => x.DisplayName.Contains(input.DisplayName!)) |
|||
.WhereIf(input.FromTime != null, x => x.FromTime == input.FromTime) |
|||
.WhereIf(input.ToTime != null, x => x.ToTime == input.ToTime) |
|||
.WhereIf(input.Disabled != null, x => x.Disabled == input.Disabled) |
|||
; |
|||
} |
|||
|
|||
public virtual async Task<ListResultDto<PromotionTypeDto>> GetPromotionTypesAsync() |
|||
{ |
|||
var promotionTypes = Options.PromotionTypes.GetAll(); |
|||
|
|||
var dtos = new List<PromotionTypeDto>(); |
|||
|
|||
foreach (var promotionType in promotionTypes) |
|||
{ |
|||
var handler = GetPromotionHandler(promotionType); |
|||
|
|||
var configurationsTemplate = await handler.CreateConfigurationsObjectOrNullAsync(); |
|||
|
|||
dtos.Add(new PromotionTypeDto(promotionType.Name, L[promotionType.Name], |
|||
configurationsTemplate is null ? "{}" : JsonSerializer.Serialize(configurationsTemplate))); |
|||
} |
|||
|
|||
return new ListResultDto<PromotionTypeDto>(dtos); |
|||
} |
|||
|
|||
protected virtual IPromotionHandler GetPromotionHandler(PromotionTypeDefinition definition) |
|||
{ |
|||
return (IPromotionHandler)LazyServiceProvider.LazyGetRequiredService(definition.PromotionHandlerType); |
|||
} |
|||
|
|||
protected override async Task<Promotion> MapToEntityAsync(CreatePromotionDto createInput) |
|||
{ |
|||
return await PromotionManager.CreateAsync(createInput.StoreId, createInput.PromotionType, |
|||
createInput.UniqueName, createInput.DisplayName, createInput.Configurations, createInput.FromTime, |
|||
createInput.ToTime, createInput.Disabled, createInput.Priority); |
|||
} |
|||
|
|||
protected override async Task MapToEntityAsync(UpdatePromotionDto updateInput, Promotion entity) |
|||
{ |
|||
await PromotionManager.UpdateAsync(entity, updateInput.DisplayName, updateInput.Configurations, |
|||
updateInput.FromTime, updateInput.ToTime, updateInput.Disabled, updateInput.Priority); |
|||
} |
|||
} |
|||
@ -0,0 +1,82 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Plugins.Promotions.Options; |
|||
using EasyAbp.EShop.Plugins.Promotions.Promotions.Dtos; |
|||
using EasyAbp.EShop.Plugins.Promotions.PromotionTypes; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Promotions; |
|||
|
|||
public class PromotionIntegrationService : ApplicationService, IPromotionIntegrationService |
|||
{ |
|||
protected IPromotionRepository PromotionRepository { get; } |
|||
protected EShopPluginsPromotionsOptions Options { get; } |
|||
|
|||
public PromotionIntegrationService( |
|||
IPromotionRepository promotionRepository, |
|||
IOptions<EShopPluginsPromotionsOptions> options) |
|||
{ |
|||
PromotionRepository = promotionRepository; |
|||
Options = options.Value; |
|||
} |
|||
|
|||
public virtual async Task<DiscountProductOutputDto> DiscountProductAsync(DiscountProductInputDto input) |
|||
{ |
|||
var context = input.Context; |
|||
|
|||
var promotions = await GetUnexpiredPromotionsAsync(context.Product.StoreId, context.Now, true); |
|||
|
|||
foreach (var promotion in promotions.OrderByDescending(x => x.Priority)) |
|||
{ |
|||
var promotionHandler = GetPromotionHandler(promotion.PromotionType); |
|||
|
|||
await promotionHandler.HandleProductAsync(context, promotion); |
|||
} |
|||
|
|||
return new DiscountProductOutputDto(context); |
|||
} |
|||
|
|||
public virtual async Task<DiscountOrderOutputDto> DiscountOrderAsync(DiscountOrderInputDto input) |
|||
{ |
|||
var context = input.Context; |
|||
|
|||
var promotions = await GetUnexpiredPromotionsAsync(context.Order.StoreId, context.Now, false); |
|||
|
|||
foreach (var promotion in promotions.OrderByDescending(x => x.Priority)) |
|||
{ |
|||
var promotionHandler = GetPromotionHandler(promotion.PromotionType); |
|||
|
|||
await promotionHandler.HandleOrderAsync(context, promotion); |
|||
} |
|||
|
|||
return new DiscountOrderOutputDto(context); |
|||
} |
|||
|
|||
protected virtual async Task<List<Promotion>> GetUnexpiredPromotionsAsync(Guid storeId, DateTime now, |
|||
bool includesNotStarted) |
|||
{ |
|||
// skip the expired promotions.
|
|||
var queryable = (await PromotionRepository.GetQueryableAsync()).Where(x => |
|||
x.StoreId == storeId && !x.Disabled && (!x.ToTime.HasValue || now <= x.ToTime)); |
|||
|
|||
queryable.WhereIf(!includesNotStarted, x => !x.FromTime.HasValue || now >= x.FromTime); |
|||
|
|||
return await AsyncExecuter.ToListAsync(queryable); |
|||
} |
|||
|
|||
protected virtual IPromotionHandler GetPromotionHandler(string promotionType) |
|||
{ |
|||
var definition = Options.PromotionTypes.GetOrNull(promotionType); |
|||
|
|||
if (definition is null) |
|||
{ |
|||
throw new BusinessException(PromotionsErrorCodes.InvalidPromotionType); |
|||
} |
|||
|
|||
return (IPromotionHandler)LazyServiceProvider.LazyGetRequiredService(definition.PromotionHandlerType); |
|||
} |
|||
} |
|||
@ -1,5 +1,33 @@ |
|||
{ |
|||
"culture": "en", |
|||
"texts": { |
|||
"SimpleProductDiscount": "Simple product discount", |
|||
"MinQuantityOrderDiscount": "Min-quantity based order discount", |
|||
"Permission:Promotion": "Promotion", |
|||
"Permission:CrossStore": "Cross-store", |
|||
"Permission:Create": "Create", |
|||
"Permission:Update": "Update", |
|||
"Permission:Delete": "Delete", |
|||
"Menu:EasyAbpEShop": "EShop", |
|||
"Menu:PromotionManagement": "Promotion", |
|||
"Menu:Promotion": "Promotion", |
|||
"Promotion": "Promotion", |
|||
"PromotionStoreId": "Store ID", |
|||
"PromotionPromotionType": "Promotion type", |
|||
"PromotionUniqueName": "Unique name", |
|||
"PromotionDisplayName": "Display name", |
|||
"PromotionConfigurations": "Configurations", |
|||
"PromotionFromTime": "From time", |
|||
"PromotionToTime": "To time", |
|||
"PromotionDisabled": "Disabled", |
|||
"PromotionPriority": "Priority", |
|||
"CreatePromotion": "Create", |
|||
"EditPromotion": "Edit", |
|||
"PromotionDeletionConfirmationMessage": "Are you sure to delete the promotion {0}?", |
|||
"SuccessfullyDeleted": "Successfully deleted", |
|||
"TableFilter": "Filter", |
|||
"EasyAbp.EShop.Plugins.Promotions:DuplicatePromotionUniqueName": "Duplicate promotion unique name", |
|||
"EasyAbp.EShop.Plugins.Promotions:InvalidPromotionType": "Invalid promotion type", |
|||
"EasyAbp.EShop.Plugins.Promotions:InvalidPromotionConfigurations": "Invalid promotion configurations" |
|||
} |
|||
} |
|||
@ -1,5 +1,33 @@ |
|||
{ |
|||
"culture": "zh-Hans", |
|||
"texts": { |
|||
"SimpleProductDiscount": "简单商品折扣", |
|||
"MinQuantityOrderDiscount": "基于最小数量的订单折扣", |
|||
"Permission:Promotion": "优惠策略", |
|||
"Permission:CrossStore": "跨店管理", |
|||
"Permission:Create": "新建", |
|||
"Permission:Update": "更新", |
|||
"Permission:Delete": "删除", |
|||
"Menu:EasyAbpEShop": "EShop 商城", |
|||
"Menu:PromotionManagement": "优惠活动", |
|||
"Menu:Promotion": "优惠策略", |
|||
"Promotion": "优惠策略", |
|||
"PromotionStoreId": "店铺 ID", |
|||
"PromotionPromotionType": "优惠策略类型", |
|||
"PromotionUniqueName": "唯一编号", |
|||
"PromotionDisplayName": "显示名称", |
|||
"PromotionConfigurations": "配置", |
|||
"PromotionFromTime": "生效时间", |
|||
"PromotionToTime": "结束时间", |
|||
"PromotionDisabled": "停用", |
|||
"PromotionPriority": "优先度", |
|||
"CreatePromotion": "新建", |
|||
"EditPromotion": "编辑", |
|||
"PromotionDeletionConfirmationMessage": "确认删除优惠策略 {0}?", |
|||
"SuccessfullyDeleted": "删除成功", |
|||
"TableFilter": "筛选器", |
|||
"EasyAbp.EShop.Plugins.Promotions:DuplicatePromotionUniqueName": "重复的优惠策略唯一编号", |
|||
"EasyAbp.EShop.Plugins.Promotions:InvalidPromotionType": "不正确的优惠策略类型", |
|||
"EasyAbp.EShop.Plugins.Promotions:InvalidPromotionConfigurations": "不正确的优惠策略配置" |
|||
} |
|||
} |
|||
@ -1,5 +1,33 @@ |
|||
{ |
|||
"culture": "zh-Hant", |
|||
"texts": { |
|||
"SimpleProductDiscount": "簡單商品折扣", |
|||
"MinQuantityOrderDiscount": "基於最小數量的訂單折扣", |
|||
"Permission:Promotion": "優惠策略", |
|||
"Permission:CrossStore": "跨店管理", |
|||
"Permission:Create": "新建", |
|||
"Permission:Update": "更新", |
|||
"Permission:Delete": "刪除", |
|||
"Menu:EasyAbpEShop": "EShop 商城", |
|||
"Menu:PromotionManagement": "優惠活動", |
|||
"Menu:Promotion": "優惠策略", |
|||
"Promotion": "優惠策略", |
|||
"PromotionStoreId": "店鋪 ID", |
|||
"PromotionPromotionType": "優惠策略類型", |
|||
"PromotionUniqueName": "唯一編號", |
|||
"PromotionDisplayName": "顯示名稱", |
|||
"PromotionConfigurations": "配置", |
|||
"PromotionFromTime": "生效時間", |
|||
"PromotionToTime": "結束時間", |
|||
"PromotionDisabled": "停用", |
|||
"PromotionPriority": "優先度", |
|||
"CreatePromotion": "新建", |
|||
"EditPromotion": "編輯", |
|||
"PromotionDeletionConfirmationMessage": "確認刪除優惠策略 {0}?", |
|||
"SuccessfullyDeleted": "刪除成功", |
|||
"TableFilter": "篩選器", |
|||
"EasyAbp.EShop.Plugins.Promotions:DuplicatePromotionUniqueName": "重複的優惠策略唯一編號", |
|||
"EasyAbp.EShop.Plugins.Promotions:InvalidPromotionType": "不正確的優惠策略類型", |
|||
"EasyAbp.EShop.Plugins.Promotions:InvalidPromotionConfigurations": "不正確的優惠策略配置" |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
namespace EasyAbp.EShop.Plugins.Promotions; |
|||
|
|||
public static class PromotionConsts |
|||
{ |
|||
public static string PromotionDiscountName { get; set; } = "Promotion"; |
|||
|
|||
public static string PromotionEffectGroup { get; set; } = "Promotion"; |
|||
} |
|||
@ -1,13 +1,16 @@ |
|||
using Volo.Abp.Domain; |
|||
using EasyAbp.EShop.Orders; |
|||
using EasyAbp.EShop.Products; |
|||
using Volo.Abp.Domain; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpDddDomainModule), |
|||
typeof(EShopPluginsPromotionsDomainSharedModule) |
|||
typeof(EShopPluginsPromotionsDomainSharedModule), |
|||
typeof(EShopProductsDomainSharedModule), |
|||
typeof(EShopOrdersDomainSharedModule) |
|||
)] |
|||
public class EShopPluginsPromotionsDomainModule : AbpModule |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
namespace EasyAbp.EShop.Plugins.Promotions.Options; |
|||
|
|||
public class EShopPluginsPromotionsOptions |
|||
{ |
|||
public PromotionTypeConfigurations PromotionTypes { get; set; } = new(); |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using EasyAbp.EShop.Plugins.Promotions.PromotionTypes; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Options; |
|||
|
|||
public class PromotionTypeConfigurations |
|||
{ |
|||
private Dictionary<string, PromotionTypeDefinition> PromotionTypes { get; } = new(); |
|||
|
|||
public List<PromotionTypeDefinition> GetAll() |
|||
{ |
|||
return PromotionTypes.Values.ToList(); |
|||
} |
|||
|
|||
public PromotionTypeDefinition? GetOrNull(string promotionTypeName) |
|||
{ |
|||
return PromotionTypes.TryGetValue(promotionTypeName, out var type) ? type : null; |
|||
} |
|||
|
|||
public void AddOrUpdate(PromotionTypeDefinition definition) |
|||
{ |
|||
PromotionTypes[definition.Name] = definition; |
|||
} |
|||
|
|||
public void Remove(string promotionTypeName) |
|||
{ |
|||
PromotionTypes.Remove(promotionTypeName); |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System; |
|||
using System.Linq; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.PromotionTypes; |
|||
|
|||
public static class HasProductScopesExtensions |
|||
{ |
|||
public static bool IsInScope(this IHasProductScopes scopes, string productGroupName, Guid productId, |
|||
Guid productSkuId) |
|||
{ |
|||
return scopes.ProductScopes.Any(x => |
|||
x.ProductGroupName == productGroupName || x.ProductId == productId || x.ProductSkuId == productSkuId); |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.PromotionTypes; |
|||
|
|||
public interface IHasProductScopes |
|||
{ |
|||
List<ProductScopeModel> ProductScopes { get; } |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Orders.Orders; |
|||
using EasyAbp.EShop.Plugins.Promotions.Promotions; |
|||
using EasyAbp.EShop.Products.Products; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.PromotionTypes; |
|||
|
|||
public interface IPromotionHandler |
|||
{ |
|||
Task HandleProductAsync(ProductDiscountContext context, Promotion promotion); |
|||
|
|||
Task HandleOrderAsync(OrderDiscountContext context, Promotion promotion); |
|||
|
|||
Task<object?> CreateConfigurationsObjectOrNullAsync(); |
|||
|
|||
bool IsConfigurationValid(string configurations); |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.PromotionTypes.MinQuantityOrderDiscount; |
|||
|
|||
public class MinQuantityOrderDiscountConfigurations |
|||
{ |
|||
public List<MinQuantityOrderDiscountModel> Discounts { get; set; } = new(); |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
using System.Collections.Generic; |
|||
using EasyAbp.EShop.Products.Products; |
|||
using Volo.Abp; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.PromotionTypes.MinQuantityOrderDiscount; |
|||
|
|||
public class MinQuantityOrderDiscountModel : IHasProductScopes, IHasDynamicDiscountAmount |
|||
{ |
|||
public List<ProductScopeModel> ProductScopes { get; set; } = new(); |
|||
|
|||
/// <summary>
|
|||
/// This discount takes effect when the quantity of the order line equals to or greater than <see cref="MinQuantity"/>.
|
|||
/// It's important to note that this promotion type only applies to one OrderLine.
|
|||
/// The promotion will not apply if you have multiple OrderLines and the total quantity of all OrderLines meets the condition.
|
|||
/// </summary>
|
|||
public int MinQuantity { get; set; } |
|||
|
|||
public DynamicDiscountAmountModel DynamicDiscountAmount { get; set; } |
|||
|
|||
public MinQuantityOrderDiscountModel() |
|||
{ |
|||
} |
|||
|
|||
public MinQuantityOrderDiscountModel(List<ProductScopeModel> productScopes, int minQuantity, |
|||
DynamicDiscountAmountModel dynamicDiscountAmount) |
|||
{ |
|||
if (!productScopes.IsNullOrEmpty()) |
|||
{ |
|||
ProductScopes = productScopes; |
|||
} |
|||
|
|||
MinQuantity = minQuantity; |
|||
DynamicDiscountAmount = Check.NotNull(dynamicDiscountAmount, nameof(dynamicDiscountAmount)); |
|||
} |
|||
} |
|||
@ -0,0 +1,89 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Orders.Orders; |
|||
using EasyAbp.EShop.Plugins.Promotions.Promotions; |
|||
using EasyAbp.EShop.Products.Products; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Json; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.PromotionTypes.MinQuantityOrderDiscount; |
|||
|
|||
public class MinQuantityOrderDiscountPromotionHandler : PromotionHandlerBase, IScopedDependency |
|||
{ |
|||
public static string MinQuantityOrderDiscountPromotionTypeName { get; set; } = "MinQuantityOrderDiscount"; |
|||
|
|||
public MinQuantityOrderDiscountPromotionHandler(IJsonSerializer jsonSerializer) : base(jsonSerializer) |
|||
{ |
|||
} |
|||
|
|||
public override Task HandleProductAsync(ProductDiscountContext context, Promotion promotion) |
|||
{ |
|||
foreach (var discountModel in GetConfigurations<MinQuantityOrderDiscountConfigurations>(promotion).Discounts) |
|||
{ |
|||
if (!discountModel.IsInScope(context.Product.ProductGroupName, context.Product.Id, context.ProductSku.Id)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
var newDiscount = new OrderDiscountPreviewInfoModel(PromotionConsts.PromotionEffectGroup, |
|||
PromotionConsts.PromotionDiscountName, promotion.UniqueName, promotion.DisplayName, promotion.FromTime, |
|||
promotion.ToTime); |
|||
|
|||
context.OrderDiscountPreviews.Add(newDiscount); |
|||
} |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public override Task HandleOrderAsync(OrderDiscountContext context, Promotion promotion) |
|||
{ |
|||
foreach (var discountModel in GetConfigurations<MinQuantityOrderDiscountConfigurations>(promotion).Discounts) |
|||
{ |
|||
if (context.Order.Currency != discountModel.DynamicDiscountAmount.Currency) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
foreach (var orderLine in context.Order.OrderLines) |
|||
{ |
|||
if (discountModel.MinQuantity > orderLine.Quantity) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
if (!discountModel.IsInScope(orderLine.ProductGroupName, orderLine.ProductId, orderLine.ProductSkuId)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
var dynamicDiscountAmountModel = new DynamicDiscountAmountModel( |
|||
discountModel.DynamicDiscountAmount.Currency, |
|||
discountModel.DynamicDiscountAmount.DiscountAmount * orderLine.Quantity, |
|||
discountModel.DynamicDiscountAmount.DiscountRate, |
|||
discountModel.DynamicDiscountAmount.CalculatorName); |
|||
|
|||
context.CandidateDiscounts.Add(new OrderDiscountInfoModel(new List<Guid> { orderLine.Id }, |
|||
PromotionConsts.PromotionEffectGroup, PromotionConsts.PromotionDiscountName, promotion.UniqueName, |
|||
promotion.DisplayName, dynamicDiscountAmountModel)); |
|||
} |
|||
} |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public override Task<object?> CreateConfigurationsObjectOrNullAsync() |
|||
{ |
|||
var configuration = new MinQuantityOrderDiscountConfigurations(); |
|||
|
|||
configuration.Discounts.Add(new MinQuantityOrderDiscountModel(new List<ProductScopeModel> { new() }, |
|||
2, new DynamicDiscountAmountModel("USD", 0m, 0m, null))); |
|||
|
|||
return Task.FromResult<object?>(configuration); |
|||
} |
|||
|
|||
public override bool IsConfigurationValid(string configurations) |
|||
{ |
|||
return JsonSerializer.Deserialize<MinQuantityOrderDiscountConfigurations>(configurations) is not null; |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using EasyAbp.EShop.Plugins.Promotions.Options; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.PromotionTypes.MinQuantityOrderDiscount; |
|||
|
|||
public static class MinQuantityOrderDiscountPromotionTypeConfigurationsExtensions |
|||
{ |
|||
public static void AddMinQuantityOrderDiscountPromotionType(this PromotionTypeConfigurations configurations) |
|||
{ |
|||
configurations.AddOrUpdate(new PromotionTypeDefinition( |
|||
MinQuantityOrderDiscountPromotionHandler.MinQuantityOrderDiscountPromotionTypeName, |
|||
typeof(MinQuantityOrderDiscountPromotionHandler))); |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
using System; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.PromotionTypes; |
|||
|
|||
public class ProductScopeModel |
|||
{ |
|||
/// <summary>
|
|||
/// The specified product group is affected when this property is set.
|
|||
/// </summary>
|
|||
public string? ProductGroupName { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The specified product (including all SKUs) is affected when this property is set.
|
|||
/// </summary>
|
|||
public Guid? ProductId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The specified product SKU is affected when this property is set.
|
|||
/// </summary>
|
|||
public Guid? ProductSkuId { get; set; } |
|||
|
|||
public string? CustomScope { get; set; } |
|||
|
|||
public ProductScopeModel() |
|||
{ |
|||
} |
|||
|
|||
public ProductScopeModel(string? productGroupName, Guid? productId, Guid? productSkuId, string? customScope) |
|||
{ |
|||
ProductGroupName = productGroupName; |
|||
ProductId = productId; |
|||
ProductSkuId = productSkuId; |
|||
CustomScope = customScope; |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
using System; |
|||
using System.Collections.Concurrent; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Orders.Orders; |
|||
using EasyAbp.EShop.Plugins.Promotions.Promotions; |
|||
using EasyAbp.EShop.Products.Products; |
|||
using Volo.Abp.Json; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.PromotionTypes; |
|||
|
|||
public abstract class PromotionHandlerBase : IPromotionHandler |
|||
{ |
|||
protected ConcurrentDictionary<string, object?> ConfigurationCaches { get; } = new(); |
|||
|
|||
protected IJsonSerializer JsonSerializer { get; } |
|||
|
|||
public PromotionHandlerBase(IJsonSerializer jsonSerializer) |
|||
{ |
|||
JsonSerializer = jsonSerializer; |
|||
} |
|||
|
|||
public abstract Task HandleProductAsync(ProductDiscountContext context, Promotion promotion); |
|||
|
|||
public abstract Task HandleOrderAsync(OrderDiscountContext context, Promotion promotion); |
|||
|
|||
public abstract Task<object?> CreateConfigurationsObjectOrNullAsync(); |
|||
|
|||
public abstract bool IsConfigurationValid(string configurations); |
|||
|
|||
protected virtual TConfigurations GetConfigurations<TConfigurations>(Promotion promotion) |
|||
{ |
|||
if (promotion.Configurations.IsNullOrWhiteSpace()) |
|||
{ |
|||
return CreateEmptyConfigurations<TConfigurations>(); |
|||
} |
|||
|
|||
var cachedConfigurations = ConfigurationCaches.GetOrAdd(promotion.Configurations, |
|||
JsonSerializer.Deserialize<TConfigurations>(promotion.Configurations)); |
|||
|
|||
return cachedConfigurations is not null |
|||
? (TConfigurations)cachedConfigurations |
|||
: CreateEmptyConfigurations<TConfigurations>(); |
|||
} |
|||
|
|||
protected virtual TConfigurations CreateEmptyConfigurations<TConfigurations>() |
|||
{ |
|||
return JsonSerializer.Deserialize<TConfigurations>("{}"); |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
using System; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.PromotionTypes; |
|||
|
|||
public class PromotionTypeDefinition |
|||
{ |
|||
public string Name { get; } |
|||
|
|||
public Type PromotionHandlerType { get; } |
|||
|
|||
public PromotionTypeDefinition(string name, Type promotionHandlerType) |
|||
{ |
|||
Name = name; |
|||
PromotionHandlerType = promotionHandlerType; |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.PromotionTypes.SimpleProductDiscount; |
|||
|
|||
public class SimpleProductDiscountConfigurations |
|||
{ |
|||
public List<SimpleProductDiscountModel> Discounts { get; set; } = new(); |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
using System.Collections.Generic; |
|||
using EasyAbp.EShop.Products.Products; |
|||
using Volo.Abp; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.PromotionTypes.SimpleProductDiscount; |
|||
|
|||
public class SimpleProductDiscountModel : IHasProductScopes, IHasDynamicDiscountAmount |
|||
{ |
|||
public List<ProductScopeModel> ProductScopes { get; set; } = new(); |
|||
|
|||
public DynamicDiscountAmountModel DynamicDiscountAmount { get; set; } |
|||
|
|||
public SimpleProductDiscountModel() |
|||
{ |
|||
} |
|||
|
|||
public SimpleProductDiscountModel(List<ProductScopeModel> productScopes, |
|||
DynamicDiscountAmountModel dynamicDiscountAmount) |
|||
{ |
|||
if (!productScopes.IsNullOrEmpty()) |
|||
{ |
|||
ProductScopes = productScopes; |
|||
} |
|||
|
|||
DynamicDiscountAmount = Check.NotNull(dynamicDiscountAmount, nameof(dynamicDiscountAmount)); |
|||
} |
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Orders.Orders; |
|||
using EasyAbp.EShop.Plugins.Promotions.Promotions; |
|||
using EasyAbp.EShop.Products.Products; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Json; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.PromotionTypes.SimpleProductDiscount; |
|||
|
|||
public class SimpleProductDiscountPromotionHandler : PromotionHandlerBase, IScopedDependency |
|||
{ |
|||
public static string SimpleProductDiscountPromotionTypeName { get; set; } = "SimpleProductDiscount"; |
|||
|
|||
public SimpleProductDiscountPromotionHandler(IJsonSerializer jsonSerializer) : base(jsonSerializer) |
|||
{ |
|||
} |
|||
|
|||
public override Task HandleProductAsync(ProductDiscountContext context, Promotion promotion) |
|||
{ |
|||
foreach (var discountModel in GetConfigurations<SimpleProductDiscountConfigurations>(promotion).Discounts) |
|||
{ |
|||
if (context.ProductSku.Currency != discountModel.DynamicDiscountAmount.Currency) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
if (!discountModel.IsInScope(context.Product.ProductGroupName, context.Product.Id, context.ProductSku.Id)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
var discount = new CandidateProductDiscountInfoModel(PromotionConsts.PromotionEffectGroup, |
|||
PromotionConsts.PromotionDiscountName, promotion.UniqueName, promotion.DisplayName, |
|||
discountModel.DynamicDiscountAmount, promotion.FromTime, promotion.ToTime); |
|||
|
|||
context.CandidateProductDiscounts.Add(discount); |
|||
} |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public override Task HandleOrderAsync(OrderDiscountContext context, Promotion promotion) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public override Task<object?> CreateConfigurationsObjectOrNullAsync() |
|||
{ |
|||
var configuration = new SimpleProductDiscountConfigurations(); |
|||
|
|||
configuration.Discounts.Add(new SimpleProductDiscountModel(new List<ProductScopeModel> { new() }, |
|||
new DynamicDiscountAmountModel("USD", 0m, 0m, null))); |
|||
|
|||
return Task.FromResult<object?>(configuration); |
|||
} |
|||
|
|||
public override bool IsConfigurationValid(string configurations) |
|||
{ |
|||
return JsonSerializer.Deserialize<SimpleProductDiscountConfigurations>(configurations) is not null; |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using EasyAbp.EShop.Plugins.Promotions.Options; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.PromotionTypes.SimpleProductDiscount; |
|||
|
|||
public static class SimpleProductDiscountPromotionTypeConfigurationsExtensions |
|||
{ |
|||
public static void AddSimpleProductDiscountPromotionType(this PromotionTypeConfigurations configurations) |
|||
{ |
|||
configurations.AddOrUpdate(new PromotionTypeDefinition( |
|||
SimpleProductDiscountPromotionHandler.SimpleProductDiscountPromotionTypeName, |
|||
typeof(SimpleProductDiscountPromotionHandler))); |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using System; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Promotions; |
|||
|
|||
public interface IPromotionRepository : IRepository<Promotion, Guid> |
|||
{ |
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
using System; |
|||
using EasyAbp.EShop.Stores.Stores; |
|||
using Volo.Abp.Domain.Entities.Auditing; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Promotions; |
|||
|
|||
public class Promotion : FullAuditedAggregateRoot<Guid>, IMultiTenant, IMultiStore |
|||
{ |
|||
public virtual Guid? TenantId { get; protected set; } |
|||
|
|||
public virtual Guid StoreId { get; protected set; } |
|||
|
|||
/// <summary>
|
|||
/// Value of the promotion type name. It is used as the DiscountName (with EffectGroup == `Promotion`)
|
|||
/// </summary>
|
|||
/// <example>SimpleProductDiscount</example>
|
|||
public virtual string PromotionType { get; protected set; } |
|||
|
|||
/// <summary>
|
|||
/// An immutable and unique name for the promotion. It is used as the DiscountKey (with EffectGroup == `Promotion`).
|
|||
/// </summary>
|
|||
public virtual string UniqueName { get; protected set; } |
|||
|
|||
public virtual string DisplayName { get; protected set; } |
|||
|
|||
/// <summary>
|
|||
/// The promotion type-specific serialized configurations value.
|
|||
/// </summary>
|
|||
public virtual string Configurations { get; protected set; } |
|||
|
|||
public virtual DateTime? FromTime { get; protected set; } |
|||
|
|||
public virtual DateTime? ToTime { get; protected set; } |
|||
|
|||
public virtual bool Disabled { get; protected set; } |
|||
|
|||
public virtual int Priority { get; protected set; } |
|||
|
|||
protected Promotion() |
|||
{ |
|||
} |
|||
|
|||
internal Promotion(Guid id, Guid? tenantId, Guid storeId, string promotionType, string uniqueName, |
|||
string displayName, string configurations, DateTime? fromTime, DateTime? toTime, bool disabled, |
|||
int priority) : base(id) |
|||
{ |
|||
TenantId = tenantId; |
|||
StoreId = storeId; |
|||
PromotionType = promotionType; |
|||
UniqueName = uniqueName; |
|||
DisplayName = displayName; |
|||
Configurations = configurations; |
|||
FromTime = fromTime; |
|||
ToTime = toTime; |
|||
Disabled = disabled; |
|||
Priority = priority; |
|||
} |
|||
|
|||
internal void Update(string displayName, string configurations, DateTime? fromTime, DateTime? toTime, bool disabled, |
|||
int priority) |
|||
{ |
|||
DisplayName = displayName; |
|||
Configurations = configurations; |
|||
FromTime = fromTime; |
|||
ToTime = toTime; |
|||
Disabled = disabled; |
|||
Priority = priority; |
|||
} |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Plugins.Promotions.Options; |
|||
using EasyAbp.EShop.Plugins.Promotions.PromotionTypes; |
|||
using Microsoft.Extensions.Options; |
|||
using Newtonsoft.Json; |
|||
using Newtonsoft.Json.Linq; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Volo.Abp.Domain.Services; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Promotions; |
|||
|
|||
public class PromotionManager : DomainService |
|||
{ |
|||
protected IPromotionRepository Repository => LazyServiceProvider.LazyGetRequiredService<IPromotionRepository>(); |
|||
|
|||
protected EShopPluginsPromotionsOptions Options => |
|||
LazyServiceProvider.LazyGetRequiredService<IOptions<EShopPluginsPromotionsOptions>>().Value; |
|||
|
|||
[UnitOfWork] |
|||
public virtual async Task<Promotion> CreateAsync(Guid storeId, string promotionType, string uniqueName, |
|||
string displayName, string configurations, DateTime? fromTime, DateTime? toTime, bool disabled, int priority) |
|||
{ |
|||
if (await Repository.AnyAsync(x => x.StoreId == storeId && x.UniqueName == uniqueName)) |
|||
{ |
|||
throw new BusinessException(PromotionsErrorCodes.DuplicatePromotionUniqueName); |
|||
} |
|||
|
|||
return new Promotion(GuidGenerator.Create(), CurrentTenant.Id, storeId, promotionType, uniqueName, displayName, |
|||
VerifyAndMinifyConfigurations(promotionType, configurations), fromTime, toTime, disabled, priority); |
|||
} |
|||
|
|||
public virtual Task UpdateAsync(Promotion promotion, string displayName, string configurations, |
|||
DateTime? fromTime, DateTime? toTime, bool disabled, int priority) |
|||
{ |
|||
promotion.Update(displayName, configurations, fromTime, toTime, disabled, priority); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
protected virtual string VerifyAndMinifyConfigurations(string promotionType, string inputConfigurations) |
|||
{ |
|||
var promotionTypeDefinition = Options.PromotionTypes.GetOrNull(promotionType); |
|||
|
|||
if (promotionTypeDefinition is null) |
|||
{ |
|||
throw new BusinessException(PromotionsErrorCodes.InvalidPromotionType); |
|||
} |
|||
|
|||
var handler = GetPromotionHandler(promotionTypeDefinition); |
|||
|
|||
var formattedJson = JToken.Parse(inputConfigurations).ToString(Formatting.None); |
|||
|
|||
if (!handler.IsConfigurationValid(formattedJson)) |
|||
{ |
|||
throw new BusinessException(PromotionsErrorCodes.InvalidPromotionConfigurations); |
|||
} |
|||
|
|||
return formattedJson; |
|||
} |
|||
|
|||
protected virtual IPromotionHandler GetPromotionHandler(PromotionTypeDefinition definition) |
|||
{ |
|||
return (IPromotionHandler)LazyServiceProvider.LazyGetRequiredService(definition.PromotionHandlerType); |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using System.Linq; |
|||
using Microsoft.EntityFrameworkCore; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Promotions; |
|||
|
|||
public static class PromotionEfCoreQueryableExtensions |
|||
{ |
|||
public static IQueryable<Promotion> IncludeDetails(this IQueryable<Promotion> queryable, bool include = true) |
|||
{ |
|||
if (!include) |
|||
{ |
|||
return queryable; |
|||
} |
|||
|
|||
return queryable |
|||
// .Include(x => x.xxx) // TODO: AbpHelper generated
|
|||
; |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Plugins.Promotions.EntityFrameworkCore; |
|||
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; |
|||
using Volo.Abp.EntityFrameworkCore; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Promotions; |
|||
|
|||
public class PromotionRepository : EfCoreRepository<IPromotionsDbContext, Promotion, Guid>, IPromotionRepository |
|||
{ |
|||
public PromotionRepository(IDbContextProvider<IPromotionsDbContext> dbContextProvider) : base(dbContextProvider) |
|||
{ |
|||
} |
|||
|
|||
public override async Task<IQueryable<Promotion>> WithDetailsAsync() |
|||
{ |
|||
return (await GetQueryableAsync()).IncludeDetails(); |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Plugins.Promotions.Promotions.Dtos; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Promotions; |
|||
|
|||
[RemoteService(Name = EShopPluginsPromotionsRemoteServiceConsts.RemoteServiceName)] |
|||
[Route("/api/e-shop/plugins/promotions/promotion")] |
|||
public class PromotionController : PromotionsController, IPromotionAppService |
|||
{ |
|||
private readonly IPromotionAppService _service; |
|||
|
|||
public PromotionController(IPromotionAppService service) |
|||
{ |
|||
_service = service; |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("{id}")] |
|||
public virtual Task<PromotionDto> GetAsync(Guid id) |
|||
{ |
|||
return _service.GetAsync(id); |
|||
} |
|||
|
|||
[HttpGet] |
|||
public virtual Task<PagedResultDto<PromotionDto>> GetListAsync(PromotionGetListInput input) |
|||
{ |
|||
return _service.GetListAsync(input); |
|||
} |
|||
|
|||
[HttpPost] |
|||
public virtual Task<PromotionDto> CreateAsync(CreatePromotionDto input) |
|||
{ |
|||
return _service.CreateAsync(input); |
|||
} |
|||
|
|||
[HttpPut] |
|||
[Route("{id}")] |
|||
public virtual Task<PromotionDto> UpdateAsync(Guid id, UpdatePromotionDto input) |
|||
{ |
|||
return _service.UpdateAsync(id, input); |
|||
} |
|||
|
|||
[HttpDelete] |
|||
[Route("{id}")] |
|||
public virtual Task DeleteAsync(Guid id) |
|||
{ |
|||
return _service.DeleteAsync(id); |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("promotion-types")] |
|||
public virtual Task<ListResultDto<PromotionTypeDto>> GetPromotionTypesAsync() |
|||
{ |
|||
return _service.GetPromotionTypesAsync(); |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
@page |
|||
@using Microsoft.AspNetCore.Mvc.Localization |
|||
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal; |
|||
@using EasyAbp.EShop.Plugins.Promotions.Localization |
|||
@inject IHtmlLocalizer<PromotionsResource> L |
|||
@model EasyAbp.EShop.Plugins.Promotions.Web.Pages.EShop.Plugins.Promotions.Promotions.Promotion.CreateModalModel |
|||
@{ |
|||
Layout = null; |
|||
} |
|||
<abp-dynamic-form abp-model="ViewModel" data-ajaxForm="true" asp-page="CreateModal"> |
|||
<abp-modal> |
|||
<abp-modal-header title="@L["CreatePromotion"].Value"></abp-modal-header> |
|||
<abp-modal-body> |
|||
<abp-form-content /> |
|||
</abp-modal-body> |
|||
<abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer> |
|||
</abp-modal> |
|||
</abp-dynamic-form> |
|||
@ -0,0 +1,39 @@ |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Plugins.Promotions.Promotions.Dtos; |
|||
using EasyAbp.EShop.Plugins.Promotions.Web.Pages.EShop.Plugins.Promotions.Promotions.Promotion.ViewModels; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Mvc.Rendering; |
|||
using Newtonsoft.Json; |
|||
using Newtonsoft.Json.Linq; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Web.Pages.EShop.Plugins.Promotions.Promotions.Promotion; |
|||
|
|||
public class CreateModalModel : PromotionEntityPageModelBase |
|||
{ |
|||
[BindProperty(SupportsGet = true)] |
|||
public CreatePromotionViewModel ViewModel { get; set; } |
|||
|
|||
protected override async Task InternalOnGetAsync() |
|||
{ |
|||
PromotionTypes = (await Service.GetPromotionTypesAsync()).Items.ToDictionary(x => x.Name); |
|||
|
|||
PromotionTypeSelectListItems = |
|||
PromotionTypes.Values.Select(x => new SelectListItem(x.DisplayName, x.Name)).ToList(); |
|||
|
|||
var beautifiedConfigurations = JToken.Parse(PromotionTypes[ViewModel.PromotionType].ConfigurationsTemplate) |
|||
.ToString(Formatting.Indented); |
|||
|
|||
ViewModel.Configurations = beautifiedConfigurations; |
|||
} |
|||
|
|||
public virtual async Task<IActionResult> OnPostAsync() |
|||
{ |
|||
var dto = new CreatePromotionDto(ViewModel.StoreId, ViewModel.PromotionType, ViewModel.UniqueName, |
|||
ViewModel.DisplayName, ViewModel.Configurations, ViewModel.FromTime, ViewModel.ToTime, ViewModel.Disabled, |
|||
ViewModel.Priority); |
|||
|
|||
await Service.CreateAsync(dto); |
|||
return NoContent(); |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
@page |
|||
@using EasyAbp.EShop.Plugins.Promotions.Localization |
|||
@using Microsoft.AspNetCore.Mvc.Localization |
|||
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal; |
|||
@inject IHtmlLocalizer<PromotionsResource> L |
|||
@model EasyAbp.EShop.Plugins.Promotions.Web.Pages.EShop.Plugins.Promotions.Promotions.Promotion.EditModalModel |
|||
@{ |
|||
Layout = null; |
|||
} |
|||
<abp-dynamic-form abp-model="ViewModel" data-ajaxForm="true" asp-page="EditModal"> |
|||
<abp-modal> |
|||
<abp-modal-header title="@L["EditPromotion"].Value"></abp-modal-header> |
|||
<abp-modal-body> |
|||
<abp-input asp-for="Id" /> |
|||
<abp-form-content /> |
|||
</abp-modal-body> |
|||
<abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer> |
|||
</abp-modal> |
|||
</abp-dynamic-form> |
|||
@ -0,0 +1,40 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Plugins.Promotions.Promotions.Dtos; |
|||
using EasyAbp.EShop.Plugins.Promotions.Web.Pages.EShop.Plugins.Promotions.Promotions.Promotion.ViewModels; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Newtonsoft.Json; |
|||
using Newtonsoft.Json.Linq; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Web.Pages.EShop.Plugins.Promotions.Promotions.Promotion; |
|||
|
|||
public class EditModalModel : PromotionEntityPageModelBase |
|||
{ |
|||
[HiddenInput] |
|||
[BindProperty(SupportsGet = true)] |
|||
public Guid Id { get; set; } |
|||
|
|||
[BindProperty] |
|||
public EditPromotionViewModel ViewModel { get; set; } |
|||
|
|||
protected override async Task InternalOnGetAsync() |
|||
{ |
|||
var dto = await Service.GetAsync(Id); |
|||
|
|||
ViewModel = ObjectMapper.Map<PromotionDto, EditPromotionViewModel>(dto); |
|||
|
|||
var beautifiedConfigurations = JToken.Parse(ViewModel.Configurations).ToString(Formatting.Indented); |
|||
|
|||
ViewModel.Configurations = beautifiedConfigurations; |
|||
} |
|||
|
|||
public virtual async Task<IActionResult> OnPostAsync() |
|||
{ |
|||
var dto = new UpdatePromotionDto(ViewModel.DisplayName, ViewModel.Configurations, ViewModel.FromTime, |
|||
ViewModel.ToTime, ViewModel.Disabled, ViewModel.Priority); |
|||
|
|||
await Service.UpdateAsync(Id, dto); |
|||
|
|||
return NoContent(); |
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
@page |
|||
@using EasyAbp.EShop.Plugins.Promotions.Permissions |
|||
@using Microsoft.AspNetCore.Authorization |
|||
@using Microsoft.AspNetCore.Mvc.Localization |
|||
@using Volo.Abp.AspNetCore.Mvc.UI.Layout |
|||
@using EasyAbp.EShop.Plugins.Promotions.Localization |
|||
@using EasyAbp.EShop.Plugins.Promotions.Web.Menus |
|||
@model EasyAbp.EShop.Plugins.Promotions.Web.Pages.EShop.Plugins.Promotions.Promotions.Promotion.IndexModel |
|||
@inject IPageLayout PageLayout |
|||
@inject IHtmlLocalizer<PromotionsResource> L |
|||
@inject IAuthorizationService Authorization |
|||
@{ |
|||
PageLayout.Content.Title = L["Promotion"].Value; |
|||
PageLayout.Content.BreadCrumb.Add(L["Menu:Promotion"].Value); |
|||
PageLayout.Content.MenuItemName = PromotionsMenus.Promotion; |
|||
|
|||
var cardTitle = L["Promotion"].Value; |
|||
|
|||
if (Model.StoreName != null) |
|||
{ |
|||
cardTitle += $" - {Model.StoreName}"; |
|||
} |
|||
} |
|||
|
|||
@section scripts |
|||
{ |
|||
<abp-script src="/Pages/EShop/Plugins/Promotions/Promotions/Promotion/index.js"/> |
|||
} |
|||
|
|||
@section styles |
|||
{ |
|||
<abp-style src="/Pages/EShop/Plugins/Promotions/Promotions/Promotion/index.css"/> |
|||
} |
|||
|
|||
<script> |
|||
let storeId = '@Model.StoreId' |
|||
</script> |
|||
|
|||
<abp-card> |
|||
<abp-card-header> |
|||
<abp-row> |
|||
<abp-column size-md="_6"> |
|||
<abp-card-title>@cardTitle</abp-card-title> |
|||
</abp-column> |
|||
<abp-column size-md="_6" class="text-end"> |
|||
@if (await Authorization.IsGrantedAsync(PromotionsPermissions.Promotion.Create)) |
|||
{ |
|||
<abp-dropdown> |
|||
<abp-dropdown-button text="@L["CreatePromotion"].Value" icon="plus" button-type="Primary"/> |
|||
<abp-dropdown-menu> |
|||
@foreach (var promotionType in Model.PromotionTypes.Values) |
|||
{ |
|||
<abp-dropdown-item href="#" class="NewPromotionButton" promotion-type="@promotionType.Name">@promotionType.DisplayName</abp-dropdown-item> |
|||
} |
|||
</abp-dropdown-menu> |
|||
</abp-dropdown> |
|||
} |
|||
</abp-column> |
|||
</abp-row> |
|||
</abp-card-header> |
|||
<abp-card-body> |
|||
<abp-row class="mb-3"> |
|||
<a abp-collapse-id="PromotionCollapse" class="text-secondary">@L["TableFilter"] </a> |
|||
</abp-row> |
|||
<abp-collapse-body id="PromotionCollapse"> |
|||
<abp-dynamic-form abp-model="PromotionFilter" id="PromotionFilter" required-symbols="false"/> |
|||
<hr/> |
|||
</abp-collapse-body> |
|||
<abp-table striped-rows="true" id="PromotionTable" class="nowrap"/> |
|||
</abp-card-body> |
|||
</abp-card> |
|||
@ -0,0 +1,72 @@ |
|||
using System; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Stores.Stores; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Mvc.Rendering; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Web.Pages.EShop.Plugins.Promotions.Promotions.Promotion; |
|||
|
|||
public class IndexModel : PromotionEntityPageModelBase |
|||
{ |
|||
public PromotionFilterInput? PromotionFilter { get; set; } |
|||
|
|||
[BindProperty(SupportsGet = true)] |
|||
public Guid? StoreId { get; set; } |
|||
|
|||
private readonly IStoreAppService _storeAppService; |
|||
|
|||
public string? StoreName { get; set; } |
|||
|
|||
public IndexModel(IStoreAppService storeAppService) |
|||
{ |
|||
_storeAppService = storeAppService; |
|||
} |
|||
|
|||
protected override async Task InternalOnGetAsync() |
|||
{ |
|||
if (StoreId.HasValue) |
|||
{ |
|||
StoreName = (await _storeAppService.GetAsync(StoreId.Value)).Name; |
|||
} |
|||
|
|||
PromotionTypes = (await Service.GetPromotionTypesAsync()).Items.ToDictionary(x => x.Name); |
|||
|
|||
PromotionTypeSelectListItems = |
|||
PromotionTypes.Values.Select(x => new SelectListItem(x.DisplayName, x.Name)).ToList(); |
|||
} |
|||
} |
|||
|
|||
public class PromotionFilterInput |
|||
{ |
|||
[FormControlSize(AbpFormControlSize.Small)] |
|||
[Display(Name = "PromotionStoreId")] |
|||
public Guid? StoreId { get; set; } |
|||
|
|||
[SelectItems(nameof(PromotionEntityPageModelBase.PromotionTypeSelectListItems))] |
|||
[FormControlSize(AbpFormControlSize.Small)] |
|||
[Display(Name = "PromotionPromotionType")] |
|||
public string? PromotionType { get; set; } |
|||
|
|||
[FormControlSize(AbpFormControlSize.Small)] |
|||
[Display(Name = "PromotionUniqueName")] |
|||
public string? UniqueName { get; set; } |
|||
|
|||
[FormControlSize(AbpFormControlSize.Small)] |
|||
[Display(Name = "PromotionDisplayName")] |
|||
public string? DisplayName { get; set; } |
|||
|
|||
[FormControlSize(AbpFormControlSize.Small)] |
|||
[Display(Name = "PromotionFromTime")] |
|||
public DateTime? FromTime { get; set; } |
|||
|
|||
[FormControlSize(AbpFormControlSize.Small)] |
|||
[Display(Name = "PromotionToTime")] |
|||
public DateTime? ToTime { get; set; } |
|||
|
|||
[FormControlSize(AbpFormControlSize.Small)] |
|||
[Display(Name = "PromotionDisabled")] |
|||
public bool? Disabled { get; set; } |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Plugins.Promotions.Promotions; |
|||
using EasyAbp.EShop.Plugins.Promotions.Promotions.Dtos; |
|||
using Microsoft.AspNetCore.Mvc.Rendering; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Web.Pages.EShop.Plugins.Promotions.Promotions.Promotion; |
|||
|
|||
public abstract class PromotionEntityPageModelBase : PromotionsPageModel |
|||
{ |
|||
public List<SelectListItem> PromotionTypeSelectListItems { get; set; } |
|||
public Dictionary<string, PromotionTypeDto> PromotionTypes { get; set; } |
|||
|
|||
protected IPromotionAppService Service => LazyServiceProvider.LazyGetRequiredService<IPromotionAppService>(); |
|||
|
|||
public virtual async Task OnGetAsync() |
|||
{ |
|||
PromotionTypes = (await Service.GetPromotionTypesAsync()).Items.ToDictionary(x => x.Name); |
|||
|
|||
PromotionTypeSelectListItems = |
|||
PromotionTypes.Values.Select(x => new SelectListItem(x.DisplayName, x.Name)).ToList(); |
|||
|
|||
await InternalOnGetAsync(); |
|||
} |
|||
|
|||
protected abstract Task InternalOnGetAsync(); |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
using System; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Web.Pages.EShop.Plugins.Promotions.Promotions.Promotion.ViewModels; |
|||
|
|||
public class CreatePromotionViewModel |
|||
{ |
|||
[HiddenInput] |
|||
[Display(Name = "PromotionStoreId")] |
|||
public Guid StoreId { get; set; } |
|||
|
|||
[Required] |
|||
[ReadOnlyInput] |
|||
[Display(Name = "PromotionPromotionType")] |
|||
[SelectItems(nameof(PromotionEntityPageModelBase.PromotionTypeSelectListItems))] |
|||
public string PromotionType { get; set; } |
|||
|
|||
[Required] |
|||
[Display(Name = "PromotionUniqueName")] |
|||
public string UniqueName { get; set; } |
|||
|
|||
[Required] |
|||
[Display(Name = "PromotionDisplayName")] |
|||
public string DisplayName { get; set; } |
|||
|
|||
[Required] |
|||
[Display(Name = "PromotionConfigurations")] |
|||
[TextArea(Rows = 5)] |
|||
public string Configurations { get; set; } |
|||
|
|||
[Display(Name = "PromotionFromTime")] |
|||
public DateTime? FromTime { get; set; } |
|||
|
|||
[Display(Name = "PromotionToTime")] |
|||
public DateTime? ToTime { get; set; } |
|||
|
|||
[Display(Name = "PromotionDisabled")] |
|||
public bool Disabled { get; set; } |
|||
|
|||
[Display(Name = "PromotionPriority")] |
|||
public int Priority { get; set; } |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
using System; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Web.Pages.EShop.Plugins.Promotions.Promotions.Promotion.ViewModels; |
|||
|
|||
public class EditPromotionViewModel |
|||
{ |
|||
[Required] |
|||
[ReadOnlyInput] |
|||
[Display(Name = "PromotionPromotionType")] |
|||
[SelectItems(nameof(PromotionEntityPageModelBase.PromotionTypeSelectListItems))] |
|||
public string PromotionType { get; set; } |
|||
|
|||
[Required] |
|||
[Display(Name = "PromotionDisplayName")] |
|||
public string DisplayName { get; set; } |
|||
|
|||
[Required] |
|||
[Display(Name = "PromotionConfigurations")] |
|||
[TextArea(Rows = 5)] |
|||
public string Configurations { get; set; } |
|||
|
|||
[Display(Name = "PromotionFromTime")] |
|||
public DateTime? FromTime { get; set; } |
|||
|
|||
[Display(Name = "PromotionToTime")] |
|||
public DateTime? ToTime { get; set; } |
|||
|
|||
[Display(Name = "PromotionDisabled")] |
|||
public bool Disabled { get; set; } |
|||
|
|||
[Display(Name = "PromotionPriority")] |
|||
public int Priority { get; set; } |
|||
} |
|||
@ -0,0 +1,118 @@ |
|||
$(function () { |
|||
|
|||
$("#PromotionFilter :input").on('input', function () { |
|||
dataTable.ajax.reload(); |
|||
}); |
|||
|
|||
$('#PromotionFilter div').addClass('col-sm-3').parent().addClass('row'); |
|||
|
|||
var getFilter = function () { |
|||
var input = {}; |
|||
$("#PromotionFilter") |
|||
.serializeArray() |
|||
.forEach(function (data) { |
|||
if (data.value != '') { |
|||
input[abp.utils.toCamelCase(data.name.replace(/PromotionFilter./g, ''))] = data.value; |
|||
} |
|||
}) |
|||
return input; |
|||
}; |
|||
|
|||
var l = abp.localization.getResource('EasyAbpEShopPluginsPromotions'); |
|||
|
|||
var service = easyAbp.eShop.plugins.promotions.promotions.promotion; |
|||
var createModal = new abp.ModalManager(abp.appPath + 'EShop/Plugins/Promotions/Promotions/Promotion/CreateModal'); |
|||
var editModal = new abp.ModalManager(abp.appPath + 'EShop/Plugins/Promotions/Promotions/Promotion/EditModal'); |
|||
|
|||
var dataTable = $('#PromotionTable').DataTable(abp.libs.datatables.normalizeConfiguration({ |
|||
processing: true, |
|||
serverSide: true, |
|||
paging: true, |
|||
searching: false,//disable default searchbox
|
|||
autoWidth: false, |
|||
scrollCollapse: true, |
|||
order: [[0, "asc"]], |
|||
ajax: abp.libs.datatables.createAjax(service.getList, function () { |
|||
return { storeId: storeId } |
|||
}), |
|||
columnDefs: [ |
|||
{ |
|||
rowAction: { |
|||
items: |
|||
[ |
|||
{ |
|||
text: l('Edit'), |
|||
visible: abp.auth.isGranted('EasyAbp.EShop.Plugins.Promotions.Promotion.Update'), |
|||
action: function (data) { |
|||
editModal.open({id: data.record.id}); |
|||
} |
|||
}, |
|||
{ |
|||
text: l('Delete'), |
|||
visible: abp.auth.isGranted('EasyAbp.EShop.Plugins.Promotions.Promotion.Delete'), |
|||
confirmMessage: function (data) { |
|||
return l('PromotionDeletionConfirmationMessage', data.record.id); |
|||
}, |
|||
action: function (data) { |
|||
service.delete(data.record.id) |
|||
.then(function () { |
|||
abp.notify.info(l('SuccessfullyDeleted')); |
|||
dataTable.ajax.reload(); |
|||
}); |
|||
} |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
{ |
|||
title: l('PromotionStoreId'), |
|||
data: "storeId" |
|||
}, |
|||
{ |
|||
title: l('PromotionPromotionType'), |
|||
data: "promotionType" |
|||
}, |
|||
{ |
|||
title: l('PromotionUniqueName'), |
|||
data: "uniqueName" |
|||
}, |
|||
{ |
|||
title: l('PromotionDisplayName'), |
|||
data: "displayName" |
|||
}, |
|||
{ |
|||
title: l('PromotionFromTime'), |
|||
data: "fromTime" |
|||
}, |
|||
{ |
|||
title: l('PromotionToTime'), |
|||
data: "toTime" |
|||
}, |
|||
{ |
|||
title: l('PromotionDisabled'), |
|||
data: "disabled" |
|||
}, |
|||
{ |
|||
title: l('PromotionPriority'), |
|||
data: "priority" |
|||
}, |
|||
] |
|||
})); |
|||
|
|||
createModal.onResult(function () { |
|||
dataTable.ajax.reload(); |
|||
}); |
|||
|
|||
editModal.onResult(function () { |
|||
dataTable.ajax.reload(); |
|||
}); |
|||
|
|||
$('#NewPromotionButton').click(function (e) { |
|||
e.preventDefault(); |
|||
createModal.open(); |
|||
}); |
|||
$('.NewPromotionButton').click(function (e) { |
|||
e.preventDefault(); |
|||
createModal.open({"ViewModel.storeId": storeId, "ViewModel.promotionType": $(this).attr('promotion-type')}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,3 @@ |
|||
{ |
|||
"role": "lib.domain" |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net7.0</TargetFramework> |
|||
<Nullable>enable</Nullable> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\..\..\modules\EasyAbp.EShop.Products\src\EasyAbp.EShop.Products.Domain\EasyAbp.EShop.Products.Domain.csproj" /> |
|||
<ProjectReference Include="..\EasyAbp.EShop.Plugins.Promotions.Application.Contracts\EasyAbp.EShop.Plugins.Promotions.Application.Contracts.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,12 @@ |
|||
using EasyAbp.EShop.Plugins.Promotions; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace EasyAbp.EShop.Products.Plugins.Promotions; |
|||
|
|||
[DependsOn( |
|||
typeof(EShopPluginsPromotionsApplicationContractsModule), |
|||
typeof(EShopProductsDomainModule) |
|||
)] |
|||
public class EShopProductsPluginsPromotionsDomainModule : AbpModule |
|||
{ |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Plugins.Promotions.Promotions; |
|||
using EasyAbp.EShop.Plugins.Promotions.Promotions.Dtos; |
|||
using EasyAbp.EShop.Products.Products; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace EasyAbp.EShop.Products.Plugins.Promotions; |
|||
|
|||
public class PromotionProductDiscountProvider : IProductDiscountProvider, ITransientDependency |
|||
{ |
|||
public static int PromotionProductDiscountEffectOrder { get; set; } = 5000; |
|||
|
|||
public int EffectOrder => PromotionProductDiscountEffectOrder; |
|||
|
|||
protected IPromotionIntegrationService PromotionIntegrationService { get; } |
|||
|
|||
public PromotionProductDiscountProvider(IPromotionIntegrationService promotionIntegrationService) |
|||
{ |
|||
PromotionIntegrationService = promotionIntegrationService; |
|||
} |
|||
|
|||
public virtual async Task DiscountAsync(ProductDiscountContext context) |
|||
{ |
|||
var dto = await PromotionIntegrationService.DiscountProductAsync(new DiscountProductInputDto(context)); |
|||
|
|||
if (dto.Context.Equals(context)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
context.CandidateProductDiscounts.Clear(); |
|||
context.CandidateProductDiscounts.AddRange(dto.Context.CandidateProductDiscounts); |
|||
|
|||
context.OrderDiscountPreviews.Clear(); |
|||
context.OrderDiscountPreviews.AddRange(dto.Context.OrderDiscountPreviews); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,137 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Orders.Orders; |
|||
using EasyAbp.EShop.Plugins.Promotions.Promotions; |
|||
using EasyAbp.EShop.Plugins.Promotions.PromotionTypes.MinQuantityOrderDiscount; |
|||
using EasyAbp.EShop.Products.Products; |
|||
using Shouldly; |
|||
using Volo.Abp.Json; |
|||
using Xunit; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.PromotionTypes; |
|||
|
|||
public class MinQuantityOrderDiscountTests : PromotionsApplicationTestBase |
|||
{ |
|||
private IJsonSerializer JsonSerializer { get; } |
|||
private PromotionManager PromotionManager { get; } |
|||
private IPromotionRepository PromotionRepository { get; } |
|||
private IOrderDiscountResolver OrderDiscountResolver { get; } |
|||
private MinQuantityOrderDiscountPromotionHandler Handler { get; } |
|||
|
|||
public MinQuantityOrderDiscountTests() |
|||
{ |
|||
JsonSerializer = GetRequiredService<IJsonSerializer>(); |
|||
PromotionManager = GetRequiredService<PromotionManager>(); |
|||
PromotionRepository = GetRequiredService<IPromotionRepository>(); |
|||
OrderDiscountResolver = GetRequiredService<IOrderDiscountResolver>(); |
|||
Handler = GetRequiredService<MinQuantityOrderDiscountPromotionHandler>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Add_Order_Candidate_Discount_Preview() |
|||
{ |
|||
var promotion = await CreatePromotionAsync(); |
|||
|
|||
var product = new ProductEto |
|||
{ |
|||
ProductGroupName = "MyProductGroup", |
|||
ProductSkus = new List<ProductSkuEto> |
|||
{ |
|||
new() |
|||
} |
|||
}; |
|||
|
|||
var context = new ProductDiscountContext(DateTime.Now, product, product.ProductSkus.First(), 1.00m); |
|||
|
|||
await Handler.HandleProductAsync(context, promotion); |
|||
|
|||
context.OrderDiscountPreviews.Count.ShouldBe(1); |
|||
|
|||
var orderDiscount = context.OrderDiscountPreviews.First(); |
|||
orderDiscount.ShouldNotBeNull(); |
|||
orderDiscount.EffectGroup.ShouldBe(PromotionConsts.PromotionEffectGroup); |
|||
orderDiscount.DisplayName.ShouldBe("test"); |
|||
orderDiscount.FromTime.ShouldBeNull(); |
|||
orderDiscount.ToTime.ShouldBeNull(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Discount_OrderLine_If_Quantity_Is_2() |
|||
{ |
|||
var promotion = await CreatePromotionAsync(); |
|||
|
|||
var order = new Order(Guid.NewGuid(), null, PromotionsTestConsts.StoreId, Guid.NewGuid(), "USD", 2.00m, 0m, |
|||
2.00m, 2.00m, null, null); |
|||
|
|||
order.OrderLines.Add(new OrderLine(order.Id, Guid.NewGuid(), Guid.NewGuid(), null, DateTime.Now, DateTime.Now, |
|||
"MyProductGroup", "MyProductGroup", null, "Test", InventoryStrategy.NoNeed, null, null, null, "USD", 1.00m, |
|||
2.00m, 0.00m, 2.00m, 2)); |
|||
|
|||
var context = new OrderDiscountContext(DateTime.Now, order, new Dictionary<Guid, IProduct>()); |
|||
|
|||
await Handler.HandleOrderAsync(context, promotion); |
|||
|
|||
context.CandidateDiscounts.Count.ShouldBe(1); |
|||
context.CandidateDiscounts[0].EffectGroup.ShouldBe(PromotionConsts.PromotionEffectGroup); |
|||
context.CandidateDiscounts[0].Name.ShouldBe(PromotionConsts.PromotionDiscountName); |
|||
context.CandidateDiscounts[0].Key.ShouldBe(promotion.UniqueName); |
|||
context.CandidateDiscounts[0].DisplayName.ShouldBe(promotion.DisplayName); |
|||
context.CandidateDiscounts[0].AffectedOrderLineIds.Count.ShouldBe(1); |
|||
context.CandidateDiscounts[0].AffectedOrderLineIds.ShouldContain(order.OrderLines[0].Id); |
|||
|
|||
var distributionModels = await OrderDiscountResolver.ResolveAsync(order, new Dictionary<Guid, IProduct>()); |
|||
|
|||
distributionModels.Count.ShouldBe(1); |
|||
distributionModels[0].DiscountInfoModel.Name.ShouldBe(PromotionConsts.PromotionDiscountName); |
|||
distributionModels[0].DiscountInfoModel.Key.ShouldBe(promotion.UniqueName); |
|||
distributionModels[0].Distributions.Count.ShouldBe(1); |
|||
distributionModels[0].Distributions.First().Value.ShouldBe(0.02m); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Not_Discount_OrderLine_If_Quantity_Is_1() |
|||
{ |
|||
var promotion = await CreatePromotionAsync(); |
|||
|
|||
var order = new OrderEto |
|||
{ |
|||
OrderLines = new List<OrderLineEto> |
|||
{ |
|||
new() |
|||
{ |
|||
ProductGroupName = "MyProductGroup", |
|||
Currency = "USD", |
|||
UnitPrice = 1.00m, |
|||
TotalPrice = 1.00m, |
|||
TotalDiscount = 0m, |
|||
ActualTotalPrice = 1.00m, |
|||
Quantity = 1, |
|||
} |
|||
} |
|||
}; |
|||
|
|||
var context = new OrderDiscountContext(DateTime.Now, order, new Dictionary<Guid, IProduct>()); |
|||
|
|||
await Handler.HandleOrderAsync(context, promotion); |
|||
|
|||
context.CandidateDiscounts.ShouldBeEmpty(); |
|||
} |
|||
|
|||
private async Task<Promotion> CreatePromotionAsync() |
|||
{ |
|||
return await PromotionRepository.InsertAsync(await PromotionManager.CreateAsync(PromotionsTestConsts.StoreId, |
|||
MinQuantityOrderDiscountPromotionHandler.MinQuantityOrderDiscountPromotionTypeName, "test", "test", |
|||
JsonSerializer.Serialize(new MinQuantityOrderDiscountConfigurations |
|||
{ |
|||
Discounts = new List<MinQuantityOrderDiscountModel> |
|||
{ |
|||
new(new List<ProductScopeModel> |
|||
{ |
|||
new("MyProductGroup", null, null, null) |
|||
}, 2, new DynamicDiscountAmountModel("USD", 0.01m, 0m, null)) |
|||
} |
|||
}), null, null, false, 10000), true); |
|||
} |
|||
} |
|||
@ -0,0 +1,79 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Orders.Orders; |
|||
using EasyAbp.EShop.Plugins.Promotions.Promotions; |
|||
using EasyAbp.EShop.Plugins.Promotions.PromotionTypes.MinQuantityOrderDiscount; |
|||
using EasyAbp.EShop.Plugins.Promotions.PromotionTypes.SimpleProductDiscount; |
|||
using EasyAbp.EShop.Products.Products; |
|||
using Shouldly; |
|||
using Volo.Abp.Json; |
|||
using Xunit; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.PromotionTypes; |
|||
|
|||
public class SimpleProductDiscountTests : PromotionsApplicationTestBase |
|||
{ |
|||
private IJsonSerializer JsonSerializer { get; } |
|||
private PromotionManager PromotionManager { get; } |
|||
private SimpleProductDiscountPromotionHandler Handler { get; } |
|||
|
|||
public SimpleProductDiscountTests() |
|||
{ |
|||
JsonSerializer = GetRequiredService<IJsonSerializer>(); |
|||
PromotionManager = GetRequiredService<PromotionManager>(); |
|||
Handler = GetRequiredService<SimpleProductDiscountPromotionHandler>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Add_Candidate_Product_Discount() |
|||
{ |
|||
var promotion = await CreatePromotionAsync(); |
|||
|
|||
var product = new ProductEto |
|||
{ |
|||
ProductGroupName = "MyProductGroup", |
|||
ProductSkus = new List<ProductSkuEto> |
|||
{ |
|||
new() |
|||
{ |
|||
Currency = "USD", |
|||
Price = 1.00m, |
|||
} |
|||
} |
|||
}; |
|||
|
|||
var context = new ProductDiscountContext(DateTime.Now, product, product.ProductSkus.First(), 1.00m); |
|||
|
|||
await Handler.HandleProductAsync(context, promotion); |
|||
|
|||
context.CandidateProductDiscounts.Count.ShouldBe(1); |
|||
|
|||
var productDiscount = context.CandidateProductDiscounts.First(); |
|||
productDiscount.ShouldNotBeNull(); |
|||
productDiscount.EffectGroup.ShouldBe(PromotionConsts.PromotionEffectGroup); |
|||
productDiscount.DisplayName.ShouldBe("test"); |
|||
productDiscount.DynamicDiscountAmount.DiscountAmount.ShouldBe(0.01m); |
|||
productDiscount.DynamicDiscountAmount.DiscountRate.ShouldBe(0m); |
|||
productDiscount.DynamicDiscountAmount.CalculatorName.ShouldBeNull(); |
|||
productDiscount.FromTime.ShouldBeNull(); |
|||
productDiscount.ToTime.ShouldBeNull(); |
|||
} |
|||
|
|||
private async Task<Promotion> CreatePromotionAsync() |
|||
{ |
|||
return await PromotionManager.CreateAsync(Guid.NewGuid(), |
|||
MinQuantityOrderDiscountPromotionHandler.MinQuantityOrderDiscountPromotionTypeName, "test", "test", |
|||
JsonSerializer.Serialize(new SimpleProductDiscountConfigurations |
|||
{ |
|||
Discounts = new List<SimpleProductDiscountModel> |
|||
{ |
|||
new(new List<ProductScopeModel> |
|||
{ |
|||
new("MyProductGroup", null, null, null) |
|||
}, new DynamicDiscountAmountModel("USD", 0.01m, 0m, null)) |
|||
} |
|||
}), null, null, false, 10000); |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
using Shouldly; |
|||
using System.Threading.Tasks; |
|||
using Xunit; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Promotions; |
|||
|
|||
public class PromotionAppServiceTests : PromotionsApplicationTestBase |
|||
{ |
|||
private readonly IPromotionAppService _promotionAppService; |
|||
|
|||
public PromotionAppServiceTests() |
|||
{ |
|||
_promotionAppService = GetRequiredService<IPromotionAppService>(); |
|||
} |
|||
|
|||
/* |
|||
[Fact] |
|||
public async Task Test1() |
|||
{ |
|||
// Arrange
|
|||
|
|||
// Act
|
|||
|
|||
// Assert
|
|||
} |
|||
*/ |
|||
} |
|||
|
|||
@ -0,0 +1,24 @@ |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.Promotions; |
|||
|
|||
public class PromotionDomainTests : PromotionsDomainTestBase |
|||
{ |
|||
public PromotionDomainTests() |
|||
{ |
|||
} |
|||
|
|||
/* |
|||
[Fact] |
|||
public async Task Test1() |
|||
{ |
|||
// Arrange
|
|||
|
|||
// Assert
|
|||
|
|||
// Assert
|
|||
} |
|||
*/ |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Plugins.Promotions.Promotions; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Xunit; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions.EntityFrameworkCore.Promotions; |
|||
|
|||
public class PromotionRepositoryTests : PromotionsEntityFrameworkCoreTestBase |
|||
{ |
|||
private readonly IPromotionRepository _promotionRepository; |
|||
|
|||
public PromotionRepositoryTests() |
|||
{ |
|||
_promotionRepository = GetRequiredService<IPromotionRepository>(); |
|||
} |
|||
|
|||
/* |
|||
[Fact] |
|||
public async Task Test1() |
|||
{ |
|||
await WithUnitOfWorkAsync(async () => |
|||
{ |
|||
// Arrange
|
|||
|
|||
// Act
|
|||
|
|||
//Assert
|
|||
}); |
|||
} |
|||
*/ |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using System; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.Promotions; |
|||
|
|||
public static class PromotionsTestConsts |
|||
{ |
|||
public static Guid StoreId = Guid.NewGuid(); |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue