mirror of https://github.com/EasyAbp/EShop.git
committed by
GitHub
268 changed files with 12870 additions and 45 deletions
@ -0,0 +1,46 @@ |
|||
# EShop.Plugins.FlashSales |
|||
|
|||
[](https://abp.io) |
|||
[](https://www.nuget.org/packages/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared) |
|||
[](https://www.nuget.org/packages/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared) |
|||
[](https://discord.gg/S6QaezrCRq) |
|||
[](https://www.github.com/EasyAbp/EShop) |
|||
|
|||
A flash-sales plugin for EShop. |
|||
|
|||
## Installation |
|||
|
|||
1. Install the following NuGet packages. ([see how](https://github.com/EasyAbp/EasyAbpGuide/blob/master/docs/How-To.md#add-nuget-packages)) |
|||
|
|||
- EasyAbp.EShop.Orders.Plugins.FlashSales.Application (install at EasyAbp.EShop.Orders.Application location) |
|||
- EasyAbp.EShop.Products.Plugins.FlashSales.Application (install at EasyAbp.EShop.Products.Application location) |
|||
- EasyAbp.EShop.Plugins.FlashSales.Application |
|||
- EasyAbp.EShop.Plugins.FlashSales.Application.Contracts |
|||
- EasyAbp.EShop.Plugins.FlashSales.Domain |
|||
- EasyAbp.EShop.Plugins.FlashSales.Domain.Shared |
|||
- EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore |
|||
- EasyAbp.EShop.Plugins.FlashSales.HttpApi |
|||
- EasyAbp.EShop.Plugins.FlashSales.HttpApi.Client |
|||
- (Optional) EasyAbp.EShop.Plugins.FlashSales.MongoDB |
|||
- (Optional) EasyAbp.EShop.Plugins.FlashSales.Web |
|||
- (Optional) EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts (install at EasyAbp.EShop.Products.Application.Contracts location) |
|||
- (Optional) EasyAbp.EShop.Products.Plugins.FlashSales.HttpApi (install at EasyAbp.EShop.Products.HttpApi location) |
|||
- (Optional) EasyAbp.EShop.Products.Plugins.FlashSales.HttpApi.Client (install at EasyAbp.EShop.Products.HttpApi.Client location) |
|||
|
|||
2. Add `DependsOn(typeof(EShopXxxModule))` attribute to configure the module dependencies. ([see how](https://github.com/EasyAbp/EasyAbpGuide/blob/master/docs/How-To.md#add-module-dependencies)) |
|||
|
|||
3. Add `builder.ConfigureEShopPluginsFlashSales();` to the `OnModelCreating()` method in **MyProjectDbContext.cs**. |
|||
|
|||
4. Add EF Core migrations and update your database. See: [ABP document](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=MVC&DB=EF#add-database-migration). |
|||
|
|||
## Usage |
|||
|
|||
### Admins |
|||
|
|||
1. Create a published flash-sale plan. |
|||
|
|||
### Customers |
|||
|
|||
1. Use `/api/e-shop/plugins/flash-sales/flash-sale-plan/{planId}/pre-order` (POST) to pre-order. It will return an expiration time if your pre-order request succeeds. You should re-invoke this API to refresh your request before the expiration time. |
|||
2. When the flash sale starts, use `/api/e-shop/plugins/flash-sales/flash-sale-plan/{planId}/order` (POST) to create your order. If you are fast enough, it will occupy the inventory and create an order for you in the background. |
|||
3. If you are told that you have succeeded, continuous use `/api/e-shop/plugins/flash-sales/flash-sale-result/{resultId}` (GET) to query the order creation result until it succeeds or fails. |
|||
@ -0,0 +1,223 @@ |
|||
# Remove the line below if you want to inherit .editorconfig settings from higher directories |
|||
root = true |
|||
|
|||
# C# files |
|||
[*.cs] |
|||
|
|||
#### Core EditorConfig Options #### |
|||
|
|||
# Indentation and spacing |
|||
indent_size = 4 |
|||
indent_style = space |
|||
tab_width = 4 |
|||
|
|||
# New line preferences |
|||
end_of_line = crlf |
|||
insert_final_newline = false |
|||
|
|||
#### .NET Coding Conventions #### |
|||
|
|||
# Organize usings |
|||
dotnet_separate_import_directive_groups = false |
|||
dotnet_sort_system_directives_first = true |
|||
file_header_template = unset |
|||
|
|||
# this. and Me. preferences |
|||
dotnet_style_qualification_for_event = false |
|||
dotnet_style_qualification_for_field = false |
|||
dotnet_style_qualification_for_method = false |
|||
dotnet_style_qualification_for_property = false |
|||
|
|||
# Language keywords vs BCL types preferences |
|||
dotnet_style_predefined_type_for_locals_parameters_members = true |
|||
dotnet_style_predefined_type_for_member_access = true |
|||
|
|||
# Parentheses preferences |
|||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity |
|||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity |
|||
dotnet_style_parentheses_in_other_operators = never_if_unnecessary |
|||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity |
|||
|
|||
# Modifier preferences |
|||
dotnet_style_require_accessibility_modifiers = omit_if_default |
|||
|
|||
# Expression-level preferences |
|||
dotnet_style_coalesce_expression = true |
|||
dotnet_style_collection_initializer = true |
|||
dotnet_style_explicit_tuple_names = true |
|||
dotnet_style_namespace_match_folder = true |
|||
dotnet_style_null_propagation = true |
|||
dotnet_style_object_initializer = true |
|||
dotnet_style_operator_placement_when_wrapping = beginning_of_line |
|||
dotnet_style_prefer_auto_properties = true |
|||
dotnet_style_prefer_compound_assignment = true |
|||
dotnet_style_prefer_conditional_expression_over_assignment = true |
|||
dotnet_style_prefer_conditional_expression_over_return = true |
|||
dotnet_style_prefer_inferred_anonymous_type_member_names = true |
|||
dotnet_style_prefer_inferred_tuple_names = true |
|||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true |
|||
dotnet_style_prefer_simplified_boolean_expressions = true |
|||
dotnet_style_prefer_simplified_interpolation = true |
|||
|
|||
# Field preferences |
|||
dotnet_style_readonly_field = true |
|||
|
|||
# Parameter preferences |
|||
dotnet_code_quality_unused_parameters = all |
|||
|
|||
# Suppression preferences |
|||
dotnet_remove_unnecessary_suppression_exclusions = 0 |
|||
|
|||
# New line preferences |
|||
dotnet_style_allow_multiple_blank_lines_experimental = false |
|||
dotnet_style_allow_statement_immediately_after_block_experimental = false |
|||
|
|||
#### C# Coding Conventions #### |
|||
|
|||
# var preferences |
|||
csharp_style_var_elsewhere = true |
|||
csharp_style_var_for_built_in_types = true |
|||
csharp_style_var_when_type_is_apparent = true |
|||
|
|||
# Expression-bodied members |
|||
csharp_style_expression_bodied_accessors = true |
|||
csharp_style_expression_bodied_constructors = false |
|||
csharp_style_expression_bodied_indexers = true |
|||
csharp_style_expression_bodied_lambdas = true |
|||
csharp_style_expression_bodied_local_functions = false |
|||
csharp_style_expression_bodied_methods = false |
|||
csharp_style_expression_bodied_operators = false |
|||
csharp_style_expression_bodied_properties = true |
|||
|
|||
# Pattern matching preferences |
|||
csharp_style_pattern_matching_over_as_with_null_check = true |
|||
csharp_style_pattern_matching_over_is_with_cast_check = true |
|||
csharp_style_prefer_extended_property_pattern = true |
|||
csharp_style_prefer_not_pattern = true |
|||
csharp_style_prefer_pattern_matching = true |
|||
csharp_style_prefer_switch_expression = true |
|||
|
|||
# Null-checking preferences |
|||
csharp_style_conditional_delegate_call = true |
|||
csharp_style_prefer_parameter_null_checking = true |
|||
|
|||
# Modifier preferences |
|||
csharp_prefer_static_local_function = true |
|||
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async |
|||
|
|||
# Code-block preferences |
|||
csharp_prefer_braces = true |
|||
csharp_prefer_simple_using_statement = true |
|||
csharp_style_namespace_declarations = file_scoped |
|||
csharp_style_prefer_method_group_conversion = true |
|||
|
|||
# Expression-level preferences |
|||
csharp_prefer_simple_default_expression = true |
|||
csharp_style_deconstructed_variable_declaration = true |
|||
csharp_style_implicit_object_creation_when_type_is_apparent = true |
|||
csharp_style_inlined_variable_declaration = true |
|||
csharp_style_prefer_index_operator = true |
|||
csharp_style_prefer_local_over_anonymous_function = true |
|||
csharp_style_prefer_null_check_over_type_check = true |
|||
csharp_style_prefer_range_operator = true |
|||
csharp_style_prefer_tuple_swap = true |
|||
csharp_style_throw_expression = true |
|||
csharp_style_unused_value_assignment_preference = discard_variable |
|||
csharp_style_unused_value_expression_statement_preference = discard_variable |
|||
|
|||
# 'using' directive preferences |
|||
csharp_using_directive_placement = outside_namespace:suggestion |
|||
|
|||
# New line preferences |
|||
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true |
|||
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false |
|||
csharp_style_allow_embedded_statements_on_same_line_experimental = true |
|||
|
|||
#### C# Formatting Rules #### |
|||
|
|||
# New line preferences |
|||
csharp_new_line_before_catch = true |
|||
csharp_new_line_before_else = true |
|||
csharp_new_line_before_finally = true |
|||
csharp_new_line_before_members_in_anonymous_types = true |
|||
csharp_new_line_before_members_in_object_initializers = true |
|||
csharp_new_line_before_open_brace = all |
|||
csharp_new_line_between_query_expression_clauses = true |
|||
|
|||
# Indentation preferences |
|||
csharp_indent_block_contents = true |
|||
csharp_indent_braces = false |
|||
csharp_indent_case_contents = true |
|||
csharp_indent_case_contents_when_block = true |
|||
csharp_indent_labels = one_less_than_current |
|||
csharp_indent_switch_labels = true |
|||
|
|||
# Space preferences |
|||
csharp_space_after_cast = false |
|||
csharp_space_after_colon_in_inheritance_clause = true |
|||
csharp_space_after_comma = true |
|||
csharp_space_after_dot = false |
|||
csharp_space_after_keywords_in_control_flow_statements = true |
|||
csharp_space_after_semicolon_in_for_statement = true |
|||
csharp_space_around_binary_operators = before_and_after |
|||
csharp_space_around_declaration_statements = false |
|||
csharp_space_before_colon_in_inheritance_clause = true |
|||
csharp_space_before_comma = false |
|||
csharp_space_before_dot = false |
|||
csharp_space_before_open_square_brackets = false |
|||
csharp_space_before_semicolon_in_for_statement = false |
|||
csharp_space_between_empty_square_brackets = false |
|||
csharp_space_between_method_call_empty_parameter_list_parentheses = false |
|||
csharp_space_between_method_call_name_and_opening_parenthesis = false |
|||
csharp_space_between_method_call_parameter_list_parentheses = false |
|||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false |
|||
csharp_space_between_method_declaration_name_and_open_parenthesis = false |
|||
csharp_space_between_method_declaration_parameter_list_parentheses = false |
|||
csharp_space_between_parentheses = false |
|||
csharp_space_between_square_brackets = false |
|||
|
|||
# Wrapping preferences |
|||
csharp_preserve_single_line_blocks = true |
|||
csharp_preserve_single_line_statements = true |
|||
|
|||
#### Naming styles #### |
|||
|
|||
# Naming rules |
|||
|
|||
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion |
|||
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface |
|||
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i |
|||
|
|||
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion |
|||
dotnet_naming_rule.types_should_be_pascal_case.symbols = types |
|||
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case |
|||
|
|||
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion |
|||
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members |
|||
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case |
|||
|
|||
# Symbol specifications |
|||
|
|||
dotnet_naming_symbols.interface.applicable_kinds = interface |
|||
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected |
|||
dotnet_naming_symbols.interface.required_modifiers = |
|||
|
|||
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum |
|||
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected |
|||
dotnet_naming_symbols.types.required_modifiers = |
|||
|
|||
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method |
|||
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected |
|||
dotnet_naming_symbols.non_field_members.required_modifiers = |
|||
|
|||
# Naming styles |
|||
|
|||
dotnet_naming_style.pascal_case.required_prefix = |
|||
dotnet_naming_style.pascal_case.required_suffix = |
|||
dotnet_naming_style.pascal_case.word_separator = |
|||
dotnet_naming_style.pascal_case.capitalization = pascal_case |
|||
|
|||
dotnet_naming_style.begins_with_i.required_prefix = I |
|||
dotnet_naming_style.begins_with_i.required_suffix = |
|||
dotnet_naming_style.begins_with_i.word_separator = |
|||
dotnet_naming_style.begins_with_i.capitalization = pascal_case |
|||
@ -0,0 +1 @@ |
|||
{} |
|||
@ -0,0 +1,10 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,9 @@ |
|||
namespace EasyAbp.EShop.Plugins.FlashSales.MultiTenancy; |
|||
|
|||
public static class MultiTenancyConsts |
|||
{ |
|||
/* Enable/disable multi-tenancy in a single point |
|||
* to test your module with multi-tenancy. |
|||
*/ |
|||
public const bool IsEnabled = false; |
|||
} |
|||
@ -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,15 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</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.FlashSales.Application.Contracts\EasyAbp.EShop.Plugins.FlashSales.Application.Contracts.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,151 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Orders.Orders.Dtos; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
using EasyAbp.EShop.Products.ProductDetails; |
|||
using EasyAbp.EShop.Products.ProductDetails.Dtos; |
|||
using EasyAbp.EShop.Products.Products; |
|||
using EasyAbp.EShop.Products.Products.Dtos; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
using Volo.Abp.ObjectMapping; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace EasyAbp.EShop.Orders.Orders; |
|||
|
|||
public class CreateFlashSaleOrderEventHandler : IDistributedEventHandler<CreateFlashSaleOrderEto>, ITransientDependency |
|||
{ |
|||
protected INewOrderGenerator NewOrderGenerator { get; } |
|||
|
|||
protected IObjectMapper ObjectMapper { get; } |
|||
|
|||
protected IEnumerable<IOrderDiscountProvider> OrderDiscountProviders { get; } |
|||
|
|||
protected IOrderRepository OrderRepository { get; } |
|||
|
|||
protected IDistributedEventBus DistributedEventBus { get; } |
|||
|
|||
protected IProductAppService ProductAppService { get; } |
|||
|
|||
protected IProductDetailAppService ProductDetailAppService { get; } |
|||
|
|||
protected IFlashSalePlanHasher FlashSalePlanHasher { get; } |
|||
|
|||
public CreateFlashSaleOrderEventHandler( |
|||
INewOrderGenerator newOrderGenerator, |
|||
IObjectMapper objectMapper, |
|||
IEnumerable<IOrderDiscountProvider> orderDiscountProviders, |
|||
IOrderRepository orderRepository, |
|||
IDistributedEventBus distributedEventBus, |
|||
IProductAppService productAppService, |
|||
IProductDetailAppService productDetailAppService, |
|||
IFlashSalePlanHasher flashSalePlanHasher) |
|||
{ |
|||
NewOrderGenerator = newOrderGenerator; |
|||
ObjectMapper = objectMapper; |
|||
OrderDiscountProviders = orderDiscountProviders; |
|||
OrderRepository = orderRepository; |
|||
DistributedEventBus = distributedEventBus; |
|||
ProductAppService = productAppService; |
|||
ProductDetailAppService = productDetailAppService; |
|||
FlashSalePlanHasher = flashSalePlanHasher; |
|||
} |
|||
|
|||
[UnitOfWork(true)] |
|||
public virtual async Task HandleEventAsync(CreateFlashSaleOrderEto eventData) |
|||
{ |
|||
var product = await ProductAppService.GetAsync(eventData.Plan.ProductId); |
|||
var productSku = product.GetSkuById(eventData.Plan.ProductSkuId); |
|||
|
|||
if (!await ValidateHashTokenAsync(eventData.Plan, product, productSku, eventData.HashToken)) |
|||
{ |
|||
await DistributedEventBus.PublishAsync(new CreateFlashSaleOrderCompleteEto() |
|||
{ |
|||
TenantId = eventData.TenantId, |
|||
PlanId = eventData.PlanId, |
|||
OrderId = null, |
|||
UserId = eventData.UserId, |
|||
StoreId = eventData.StoreId, |
|||
PendingResultId = eventData.PendingResultId, |
|||
Success = false, |
|||
Reason = FlashSaleResultFailedReason.InvalidHashToken |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
var input = await ConvertToCreateOrderDtoAsync(eventData); |
|||
|
|||
var productDict = await GetProductDictionaryAsync(product); |
|||
|
|||
var productDetailDict = await GetProductDetailDictionaryAsync(product, productSku); |
|||
|
|||
var order = await NewOrderGenerator.GenerateAsync(eventData.UserId, input, productDict, productDetailDict); |
|||
|
|||
await OrderRepository.InsertAsync(order, autoSave: true); |
|||
|
|||
await DistributedEventBus.PublishAsync(new CreateFlashSaleOrderCompleteEto() |
|||
{ |
|||
TenantId = eventData.TenantId, |
|||
PlanId = eventData.PlanId, |
|||
OrderId = order.Id, |
|||
UserId = eventData.UserId, |
|||
StoreId = eventData.StoreId, |
|||
PendingResultId = eventData.PendingResultId, |
|||
Success = true, |
|||
Reason = null |
|||
}); |
|||
} |
|||
|
|||
protected virtual Task<Dictionary<Guid, ProductDto>> GetProductDictionaryAsync(ProductDto product) |
|||
{ |
|||
var productDict = new Dictionary<Guid, ProductDto>() |
|||
{ |
|||
{product.Id, product} |
|||
}; |
|||
|
|||
return Task.FromResult(productDict); |
|||
} |
|||
|
|||
protected virtual async Task<Dictionary<Guid, ProductDetailDto>> GetProductDetailDictionaryAsync(ProductDto product, ProductSkuDto productSku) |
|||
{ |
|||
var dict = new Dictionary<Guid, ProductDetailDto>(); |
|||
|
|||
var productDetailId = productSku.ProductDetailId ?? product.ProductDetailId; |
|||
|
|||
if (productDetailId.HasValue) |
|||
{ |
|||
dict.Add(productDetailId.Value, await ProductDetailAppService.GetAsync(productDetailId.Value)); |
|||
} |
|||
|
|||
return dict; |
|||
} |
|||
|
|||
protected virtual Task<CreateOrderDto> ConvertToCreateOrderDtoAsync(CreateFlashSaleOrderEto eventData) |
|||
{ |
|||
var input = new CreateOrderDto() |
|||
{ |
|||
StoreId = eventData.StoreId, |
|||
CustomerRemark = eventData.CustomerRemark, |
|||
OrderLines = new List<CreateOrderLineDto>() |
|||
{ |
|||
new CreateOrderLineDto() |
|||
{ |
|||
ProductId = eventData.Plan.ProductId, |
|||
ProductSkuId = eventData.Plan.ProductSkuId, |
|||
Quantity = 1 |
|||
} |
|||
} |
|||
}; |
|||
|
|||
return Task.FromResult(input); |
|||
} |
|||
|
|||
protected virtual async Task<bool> ValidateHashTokenAsync(FlashSalePlanEto plan, ProductDto product, ProductSkuDto productSku, string originHashToken) |
|||
{ |
|||
var hashToken = await FlashSalePlanHasher.HashAsync(plan.LastModificationTime, product.LastModificationTime, productSku.LastModificationTime); |
|||
|
|||
return string.Equals(hashToken, originHashToken, StringComparison.InvariantCulture); |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
using EasyAbp.EShop.Plugins.FlashSales; |
|||
using EasyAbp.EShop.Products; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.AutoMapper; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace EasyAbp.EShop.Orders.Plugins.FlashSales; |
|||
|
|||
[DependsOn( |
|||
typeof(EShopOrdersApplicationModule), |
|||
typeof(EShopProductsApplicationContractsModule), |
|||
typeof(EShopPluginsFlashSalesApplicationContractsModule) |
|||
)] |
|||
public class EShopOrdersPluginsFlashSalesApplicationModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddAutoMapperObjectMapper<EShopOrdersPluginsFlashSalesApplicationModule>(); |
|||
Configure<AbpAutoMapperOptions>(options => |
|||
{ |
|||
options.AddMaps<EShopOrdersPluginsFlashSalesApplicationModule>(validate: true); |
|||
}); |
|||
} |
|||
} |
|||
@ -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,3 @@ |
|||
{ |
|||
"role": "lib.application-contracts" |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.Ddd.Application.Contracts" Version="$(AbpVersion)" /> |
|||
<PackageReference Include="Volo.Abp.Authorization" Version="$(AbpVersion)" /> |
|||
<ProjectReference Include="..\EasyAbp.EShop.Plugins.FlashSales.Domain.Shared\EasyAbp.EShop.Plugins.FlashSales.Domain.Shared.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,15 @@ |
|||
using Volo.Abp.Application; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.Authorization; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales; |
|||
|
|||
[DependsOn( |
|||
typeof(EShopPluginsFlashSalesDomainSharedModule), |
|||
typeof(AbpDddApplicationContractsModule), |
|||
typeof(AbpAuthorizationModule) |
|||
)] |
|||
public class EShopPluginsFlashSalesApplicationContractsModule : AbpModule |
|||
{ |
|||
|
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
namespace EasyAbp.EShop.Plugins.FlashSales; |
|||
|
|||
public class EShopPluginsFlashSalesRemoteServiceConsts |
|||
{ |
|||
public const string RemoteServiceName = "EasyAbpEShopPluginsFlashSales"; |
|||
|
|||
public const string ModuleName = "easyAbpEShopPluginsFlashSales"; |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans.Dtos; |
|||
|
|||
public class CreateOrderInput : ExtensibleEntityDto |
|||
{ |
|||
public string CustomerRemark { get; set; } |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans.Dtos; |
|||
|
|||
public class FlashSaleOrderResultDto : ExtensibleEntityDto |
|||
{ |
|||
public bool IsSuccess { get; set; } |
|||
|
|||
public Guid? FlashSaleResultId { get; set; } |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
using System; |
|||
using EasyAbp.EShop.Stores.Stores; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans.Dtos; |
|||
|
|||
[Serializable] |
|||
public class FlashSalePlanCreateDto : ExtensibleEntityDto, IMultiStore |
|||
{ |
|||
public Guid StoreId { get; set; } |
|||
|
|||
public DateTime BeginTime { get; set; } |
|||
|
|||
public DateTime EndTime { get; set; } |
|||
|
|||
public Guid ProductId { get; set; } |
|||
|
|||
public Guid ProductSkuId { get; set; } |
|||
|
|||
public bool IsPublished { get; set; } |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Domain.Entities; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans.Dtos; |
|||
|
|||
[Serializable] |
|||
public class FlashSalePlanDto : ExtensibleFullAuditedEntityDto<Guid>, IHasConcurrencyStamp |
|||
{ |
|||
public Guid StoreId { get; set; } |
|||
|
|||
public DateTime BeginTime { get; set; } |
|||
|
|||
public DateTime EndTime { get; set; } |
|||
|
|||
public Guid ProductId { get; set; } |
|||
|
|||
public Guid ProductSkuId { get; set; } |
|||
|
|||
public bool IsPublished { get; set; } |
|||
|
|||
public string ConcurrencyStamp { get; set; } |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans.Dtos; |
|||
|
|||
[Serializable] |
|||
public class FlashSalePlanGetListInput : ExtensiblePagedAndSortedResultRequestDto |
|||
{ |
|||
public Guid? StoreId { get; set; } |
|||
|
|||
public Guid? ProductId { get; set; } |
|||
|
|||
public Guid? ProductSkuId { get; set; } |
|||
|
|||
public bool IncludeUnpublished { get; set; } |
|||
|
|||
public DateTime? Start { get; set; } |
|||
|
|||
public DateTime? End { get; set; } |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans.Dtos; |
|||
|
|||
public class FlashSalePlanPreOrderDto : ExtensibleEntityDto |
|||
{ |
|||
public DateTime ExpiresTime { get; set; } |
|||
|
|||
public double ExpiresInSeconds { get; set; } |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Domain.Entities; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans.Dtos; |
|||
|
|||
[Serializable] |
|||
public class FlashSalePlanUpdateDto : ExtensibleEntityDto, IHasConcurrencyStamp |
|||
{ |
|||
public DateTime BeginTime { get; set; } |
|||
|
|||
public DateTime EndTime { get; set; } |
|||
|
|||
public Guid ProductId { get; set; } |
|||
|
|||
public Guid ProductSkuId { get; set; } |
|||
|
|||
public bool IsPublished { get; set; } |
|||
|
|||
public string ConcurrencyStamp { get; set; } |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans.Dtos; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
|
|||
public interface IFlashSalePlanAppService : |
|||
ICrudAppService< |
|||
FlashSalePlanDto, |
|||
Guid, |
|||
FlashSalePlanGetListInput, |
|||
FlashSalePlanCreateDto, |
|||
FlashSalePlanUpdateDto> |
|||
{ |
|||
Task<FlashSalePlanPreOrderDto> PreOrderAsync(Guid id); |
|||
|
|||
Task<FlashSaleOrderResultDto> OrderAsync(Guid id, CreateOrderInput input); |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System; |
|||
using EasyAbp.EShop.Stores.Stores; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults.Dtos; |
|||
|
|||
public class FlashSaleResultDto : ExtensibleFullAuditedEntityDto<Guid>, IMultiStore |
|||
{ |
|||
public virtual Guid StoreId { get; set; } |
|||
|
|||
public virtual Guid PlanId { get; set; } |
|||
|
|||
public virtual FlashSaleResultStatus Status { get; set; } |
|||
|
|||
public virtual string Reason { get; set; } |
|||
|
|||
public virtual Guid UserId { get; set; } |
|||
|
|||
public virtual Guid? OrderId { get; set; } |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults.Dtos; |
|||
|
|||
public class FlashSaleResultGetListInput : ExtensiblePagedAndSortedResultRequestDto |
|||
{ |
|||
public virtual Guid? StoreId { get; set; } |
|||
|
|||
public virtual Guid? PlanId { get; set; } |
|||
|
|||
public virtual FlashSaleResultStatus? Status { get; set; } |
|||
|
|||
public virtual Guid? UserId { get; set; } |
|||
|
|||
public virtual Guid? OrderId { get; set; } |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults.Dtos; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
|
|||
public interface IFlashSaleResultAppService : |
|||
IReadOnlyAppService< |
|||
FlashSaleResultDto, |
|||
Guid, |
|||
FlashSaleResultGetListInput> |
|||
{ |
|||
|
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
using EasyAbp.EShop.Plugins.FlashSales.Localization; |
|||
using Volo.Abp.Authorization.Permissions; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.Permissions; |
|||
|
|||
public class FlashSalesPermissionDefinitionProvider : PermissionDefinitionProvider |
|||
{ |
|||
public override void Define(IPermissionDefinitionContext context) |
|||
{ |
|||
var myGroup = context.AddGroup(FlashSalesPermissions.GroupName, L("Permission:FlashSales")); |
|||
|
|||
var flashSalePlanPermission = myGroup.AddPermission(FlashSalesPermissions.FlashSalePlan.Default, L("Permission:FlashSalePlan")); |
|||
flashSalePlanPermission.AddChild(FlashSalesPermissions.FlashSalePlan.Manage, L("Permission:Manage")); |
|||
flashSalePlanPermission.AddChild(FlashSalesPermissions.FlashSalePlan.CrossStore, L("Permission:CrossStore")); |
|||
flashSalePlanPermission.AddChild(FlashSalesPermissions.FlashSalePlan.Create, L("Permission:Create")); |
|||
flashSalePlanPermission.AddChild(FlashSalesPermissions.FlashSalePlan.Update, L("Permission:Update")); |
|||
flashSalePlanPermission.AddChild(FlashSalesPermissions.FlashSalePlan.Delete, L("Permission:Delete")); |
|||
|
|||
var flashSaleResultPermission = myGroup.AddPermission(FlashSalesPermissions.FlashSaleResult.Default, L("Permission:FlashSaleResult")); |
|||
flashSaleResultPermission.AddChild(FlashSalesPermissions.FlashSaleResult.Manage, L("Permission:Manage")); |
|||
flashSaleResultPermission.AddChild(FlashSalesPermissions.FlashSaleResult.CrossStore, L("Permission:CrossStore")); |
|||
} |
|||
|
|||
private static LocalizableString L(string name) |
|||
{ |
|||
return LocalizableString.Create<FlashSalesResource>(name); |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
using Volo.Abp.Reflection; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.Permissions; |
|||
|
|||
public class FlashSalesPermissions |
|||
{ |
|||
public const string GroupName = "EasyAbp.EShop.Plugins.FlashSales"; |
|||
|
|||
public static string[] GetAll() |
|||
{ |
|||
return ReflectionHelper.GetPublicConstantsRecursively(typeof(FlashSalesPermissions)); |
|||
} |
|||
|
|||
public class FlashSalePlan |
|||
{ |
|||
public const string Default = GroupName + ".FlashSalePlan"; |
|||
public const string Manage = Default + ".Manage"; |
|||
public const string CrossStore = Default + ".CrossStore"; |
|||
public const string Update = Default + ".Update"; |
|||
public const string Create = Default + ".Create"; |
|||
public const string Delete = Default + ".Delete"; |
|||
} |
|||
|
|||
public class FlashSaleResult |
|||
{ |
|||
public const string Default = GroupName + ".FlashSaleResult"; |
|||
public const string Manage = Default + ".Manage"; |
|||
public const string CrossStore = Default + ".CrossStore"; |
|||
} |
|||
} |
|||
@ -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,3 @@ |
|||
{ |
|||
"role": "lib.application" |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.*" /> |
|||
<PackageReference Include="Volo.Abp.AutoMapper" Version="$(AbpVersion)" /> |
|||
<PackageReference Include="Volo.Abp.Caching" Version="$(AbpVersion)" /> |
|||
<PackageReference Include="Volo.Abp.Ddd.Application" Version="$(AbpVersion)" /> |
|||
<ProjectReference Include="..\..\..\..\modules\EasyAbp.EShop.Products\src\EasyAbp.EShop.Products.Application.Contracts\EasyAbp.EShop.Products.Application.Contracts.csproj" /> |
|||
<ProjectReference Include="..\..\..\..\modules\EasyAbp.EShop.Stores\src\EasyAbp.EShop.Stores.Application.Shared\EasyAbp.EShop.Stores.Application.Shared.csproj" /> |
|||
<ProjectReference Include="..\EasyAbp.EShop.Plugins.FlashSales.Application.Contracts\EasyAbp.EShop.Plugins.FlashSales.Application.Contracts.csproj" /> |
|||
<ProjectReference Include="..\EasyAbp.EShop.Plugins.FlashSales.Domain\EasyAbp.EShop.Plugins.FlashSales.Domain.csproj" /> |
|||
<ProjectReference Include="..\EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions\EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions.csproj" /> |
|||
<ProjectReference Include="..\EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts\EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,33 @@ |
|||
using EasyAbp.EShop.Products; |
|||
using EasyAbp.EShop.Products.Plugins.FlashSales; |
|||
using EasyAbp.EShop.Stores; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.Application; |
|||
using Volo.Abp.AutoMapper; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales; |
|||
|
|||
[DependsOn( |
|||
typeof(EShopProductsApplicationContractsModule), |
|||
typeof(EShopPluginsFlashSalesDomainModule), |
|||
typeof(EShopPluginsFlashSalesApplicationContractsModule), |
|||
typeof(EShopStoresApplicationSharedModule), |
|||
typeof(EShopProductsPluginsFlashSalesAbstractionsModule), |
|||
typeof(EShopProductsPluginsFlashSalesApplicationContractsModule), |
|||
typeof(AbpDddApplicationModule), |
|||
typeof(AbpAutoMapperModule), |
|||
typeof(AbpCachingModule) |
|||
)] |
|||
public class EShopPluginsFlashSalesApplicationModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddAutoMapperObjectMapper<EShopPluginsFlashSalesApplicationModule>(); |
|||
Configure<AbpAutoMapperOptions>(options => |
|||
{ |
|||
options.AddMaps<EShopPluginsFlashSalesApplicationModule>(validate: true); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.Eshop.Products.Products; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
using EasyAbp.EShop.Products.Products; |
|||
using Microsoft.Extensions.Caching.Distributed; |
|||
using Microsoft.Extensions.Logging; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
|
|||
public class CreateFlashSaleOrderFailedEventHandler : IDistributedEventHandler<CreateFlashSaleOrderCompleteEto>, ITransientDependency |
|||
{ |
|||
protected IFlashSaleInventoryManager FlashSaleInventoryManager { get; } |
|||
protected IDistributedCache DistributedCache { get; } |
|||
protected IFlashSalePlanRepository FlashSalePlanRepository { get; } |
|||
protected IProductAppService ProductAppService { get; } |
|||
protected ILogger<CreateFlashSaleOrderFailedEventHandler > Logger { get; } |
|||
|
|||
public CreateFlashSaleOrderFailedEventHandler ( |
|||
IFlashSaleInventoryManager flashSaleInventoryManager, |
|||
IDistributedCache distributedCache, |
|||
IFlashSalePlanRepository flashSalePlanRepository, |
|||
IProductAppService productAppService, |
|||
ILogger<CreateFlashSaleOrderFailedEventHandler > logger) |
|||
{ |
|||
FlashSaleInventoryManager = flashSaleInventoryManager; |
|||
DistributedCache = distributedCache; |
|||
FlashSalePlanRepository = flashSalePlanRepository; |
|||
ProductAppService = productAppService; |
|||
Logger = logger; |
|||
} |
|||
|
|||
public virtual async Task HandleEventAsync(CreateFlashSaleOrderCompleteEto eventData) |
|||
{ |
|||
if (eventData.Success) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (eventData.Reason != FlashSaleResultFailedReason.InvalidHashToken) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var plan = await FlashSalePlanRepository.GetAsync(eventData.PlanId); |
|||
var product = await ProductAppService.GetAsync(plan.ProductId); |
|||
|
|||
if (!await FlashSaleInventoryManager.TryRollBackInventoryAsync( |
|||
plan.TenantId, product.InventoryProviderName, |
|||
plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true |
|||
)) |
|||
{ |
|||
Logger.LogWarning("Try roll back inventory failed."); |
|||
return; |
|||
} |
|||
|
|||
await RemoveUserFlashSaleResultCacheAsync(plan, eventData.UserId); |
|||
} |
|||
|
|||
protected virtual Task<string> GetUserFlashSaleResultCacheKeyAsync(FlashSalePlan plan, Guid userId) |
|||
{ |
|||
return Task.FromResult(string.Format(FlashSalePlanAppService.UserFlashSaleResultCacheKeyFormat, plan.TenantId, plan.Id, userId)); |
|||
} |
|||
|
|||
protected virtual async Task RemoveUserFlashSaleResultCacheAsync(FlashSalePlan plan, Guid userId) |
|||
{ |
|||
await DistributedCache.RemoveAsync(await GetUserFlashSaleResultCacheKeyAsync(plan, userId)); |
|||
} |
|||
} |
|||
@ -0,0 +1,450 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.Eshop.Products.Products; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans.Dtos; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
using EasyAbp.EShop.Plugins.FlashSales.Permissions; |
|||
using EasyAbp.EShop.Products.Products; |
|||
using EasyAbp.EShop.Products.Products.Dtos; |
|||
using EasyAbp.EShop.Stores.Stores; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.Extensions.Caching.Distributed; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.DistributedLocking; |
|||
using Volo.Abp.Domain.Entities; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
using Volo.Abp.Timing; |
|||
using Volo.Abp.Users; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
|
|||
[Authorize] |
|||
public class FlashSalePlanAppService : |
|||
MultiStoreCrudAppService<FlashSalePlan, FlashSalePlanDto, Guid, FlashSalePlanGetListInput, FlashSalePlanCreateDto, FlashSalePlanUpdateDto>, |
|||
IFlashSalePlanAppService |
|||
{ |
|||
/// <summary>
|
|||
/// The <see cref="GetPreOrderCacheKeyAsync(FlashSalePlanCacheItem)"/> cache key format.
|
|||
/// <para>{0}: FlashSalePlan ID</para>
|
|||
/// <para>{1}: User ID</para>
|
|||
/// </summary>
|
|||
public const string PreOrderCacheKeyFormat = "eshopflashsales_{0}_{1}"; |
|||
|
|||
/// <summary>
|
|||
/// The <see cref="GetUserFlashSaleResultCacheKeyAsync(FlashSalePlanCacheItem,Guid)"/> cache key format.
|
|||
/// <para>{0}: Tenant ID</para>
|
|||
/// <para>{1}: FlashSalePlan ID</para>
|
|||
/// <para>{2}: User ID</para>
|
|||
/// </summary>
|
|||
public const string UserFlashSaleResultCacheKeyFormat = "eshopflashsales-result_{0}_{1}_{2}"; |
|||
|
|||
protected override string CrossStorePolicyName { get; set; } = FlashSalesPermissions.FlashSalePlan.CrossStore; |
|||
protected override string GetPolicyName { get; set; } |
|||
protected override string GetListPolicyName { get; set; } |
|||
protected override string CreatePolicyName { get; set; } = FlashSalesPermissions.FlashSalePlan.Create; |
|||
protected override string UpdatePolicyName { get; set; } = FlashSalesPermissions.FlashSalePlan.Update; |
|||
protected override string DeletePolicyName { get; set; } = FlashSalesPermissions.FlashSalePlan.Delete; |
|||
|
|||
protected IFlashSalePlanRepository FlashSalePlanRepository { get; } |
|||
|
|||
protected IProductAppService ProductAppService { get; } |
|||
|
|||
protected IDistributedCache<FlashSalePlanPreOrderCacheItem> PreOrderDistributedCache { get; } |
|||
|
|||
protected IDistributedCache<FlashSalePlanCacheItem, Guid> PlanDistributedCache { get; } |
|||
|
|||
protected IDistributedEventBus DistributedEventBus { get; } |
|||
|
|||
protected IFlashSaleResultRepository FlashSaleResultRepository { get; } |
|||
|
|||
protected IAbpDistributedLock DistributedLock { get; } |
|||
|
|||
protected IFlashSalePlanHasher FlashSalePlanHasher { get; } |
|||
|
|||
protected IFlashSaleInventoryManager FlashSaleInventoryManager { get; } |
|||
|
|||
protected IDistributedCache DistributedCache { get; } |
|||
|
|||
protected FlashSalesOptions Options { get; } |
|||
|
|||
public FlashSalePlanAppService( |
|||
IFlashSalePlanRepository flashSalePlanRepository, |
|||
IProductAppService productAppService, |
|||
IDistributedCache<FlashSalePlanPreOrderCacheItem> tokenDistributedCache, |
|||
IDistributedCache<FlashSalePlanCacheItem, Guid> planDistributedCache, |
|||
IDistributedEventBus distributedEventBus, |
|||
IFlashSaleResultRepository flashSaleResultRepository, |
|||
IAbpDistributedLock distributedLock, |
|||
IFlashSalePlanHasher flashSalePlanHasher, |
|||
IFlashSaleInventoryManager flashSaleInventoryManager, |
|||
IDistributedCache distributedCache, |
|||
IOptionsMonitor<FlashSalesOptions> optionsMonitor) |
|||
: base(flashSalePlanRepository) |
|||
{ |
|||
FlashSalePlanRepository = flashSalePlanRepository; |
|||
ProductAppService = productAppService; |
|||
PreOrderDistributedCache = tokenDistributedCache; |
|||
PlanDistributedCache = planDistributedCache; |
|||
DistributedEventBus = distributedEventBus; |
|||
FlashSaleResultRepository = flashSaleResultRepository; |
|||
DistributedLock = distributedLock; |
|||
FlashSalePlanHasher = flashSalePlanHasher; |
|||
FlashSaleInventoryManager = flashSaleInventoryManager; |
|||
DistributedCache = distributedCache; |
|||
Options = optionsMonitor.CurrentValue; |
|||
} |
|||
|
|||
public override async Task<FlashSalePlanDto> GetAsync(Guid id) |
|||
{ |
|||
var flashSalePlan = await GetEntityByIdAsync(id); |
|||
|
|||
await CheckMultiStorePolicyAsync(flashSalePlan.StoreId, GetPolicyName); |
|||
|
|||
if (!flashSalePlan.IsPublished) |
|||
{ |
|||
await CheckMultiStorePolicyAsync(flashSalePlan.StoreId, FlashSalesPermissions.FlashSalePlan.Manage); |
|||
} |
|||
|
|||
return await MapToGetOutputDtoAsync(flashSalePlan); |
|||
} |
|||
|
|||
public override async Task<PagedResultDto<FlashSalePlanDto>> GetListAsync(FlashSalePlanGetListInput input) |
|||
{ |
|||
await CheckMultiStorePolicyAsync(input.StoreId, GetListPolicyName); |
|||
|
|||
return await base.GetListAsync(input); |
|||
} |
|||
|
|||
public override async Task<FlashSalePlanDto> CreateAsync(FlashSalePlanCreateDto input) |
|||
{ |
|||
await CheckMultiStorePolicyAsync(input.StoreId, CreatePolicyName); |
|||
|
|||
var product = await ProductAppService.GetAsync(input.ProductId); |
|||
var productSku = product.GetSkuById(input.ProductSkuId); |
|||
|
|||
await ValidateProductAsync(input.ProductId, product, input.StoreId); |
|||
|
|||
var flashSalePlan = new FlashSalePlan( |
|||
GuidGenerator.Create(), |
|||
CurrentTenant.Id, |
|||
input.StoreId, |
|||
input.BeginTime, |
|||
input.EndTime, |
|||
product.Id, |
|||
productSku.Id, |
|||
input.IsPublished |
|||
); |
|||
|
|||
await FlashSalePlanRepository.InsertAsync(flashSalePlan, autoSave: true); |
|||
|
|||
return await MapToGetOutputDtoAsync(flashSalePlan); |
|||
} |
|||
|
|||
public override async Task<FlashSalePlanDto> UpdateAsync(Guid id, FlashSalePlanUpdateDto input) |
|||
{ |
|||
var flashSalePlan = await GetEntityByIdAsync(id); |
|||
var product = await ProductAppService.GetAsync(input.ProductId); |
|||
var productSku = product.GetSkuById(input.ProductSkuId); |
|||
|
|||
await CheckMultiStorePolicyAsync(product.StoreId, UpdatePolicyName); |
|||
|
|||
await ValidateProductAsync(input.ProductId, product, flashSalePlan.StoreId); |
|||
|
|||
if (await ExistRelatedFlashSaleResultsAsync(id) && (input.ProductId != flashSalePlan.ProductId || input.ProductSkuId != flashSalePlan.ProductSkuId)) |
|||
{ |
|||
throw new RelatedFlashSaleResultsExistException(id); |
|||
} |
|||
|
|||
flashSalePlan.SetTimeRange(input.BeginTime, input.EndTime); |
|||
flashSalePlan.SetProductSku(flashSalePlan.StoreId, product.Id, productSku.Id); |
|||
flashSalePlan.SetPublished(input.IsPublished); |
|||
|
|||
flashSalePlan.SetConcurrencyStampIfNotNull(input.ConcurrencyStamp); |
|||
|
|||
await FlashSalePlanRepository.UpdateAsync(flashSalePlan, autoSave: true); |
|||
|
|||
return await MapToGetOutputDtoAsync(flashSalePlan); |
|||
} |
|||
|
|||
public override async Task DeleteAsync(Guid id) |
|||
{ |
|||
var flashSalePlan = await GetEntityByIdAsync(id); |
|||
|
|||
await CheckMultiStorePolicyAsync(flashSalePlan.StoreId, DeletePolicyName); |
|||
|
|||
if (await ExistRelatedFlashSaleResultsAsync(id)) |
|||
{ |
|||
throw new RelatedFlashSaleResultsExistException(id); |
|||
} |
|||
|
|||
await FlashSalePlanRepository.DeleteAsync(flashSalePlan); |
|||
} |
|||
|
|||
protected override async Task<IQueryable<FlashSalePlan>> CreateFilteredQueryAsync(FlashSalePlanGetListInput input) |
|||
{ |
|||
if (input.IncludeUnpublished) |
|||
{ |
|||
await CheckMultiStorePolicyAsync(input.StoreId, FlashSalesPermissions.FlashSalePlan.Manage); |
|||
} |
|||
|
|||
return (await base.CreateFilteredQueryAsync(input)) |
|||
.WhereIf(input.StoreId.HasValue, x => x.StoreId == input.StoreId.Value) |
|||
.WhereIf(input.ProductId.HasValue, x => x.ProductId == input.ProductId.Value) |
|||
.WhereIf(input.ProductSkuId.HasValue, x => x.ProductSkuId == input.ProductSkuId.Value) |
|||
.WhereIf(!input.IncludeUnpublished, x => x.IsPublished) |
|||
.WhereIf(input.Start.HasValue, x => x.BeginTime >= input.Start.Value) |
|||
.WhereIf(input.End.HasValue, x => x.BeginTime <= input.End.Value); |
|||
} |
|||
|
|||
public virtual async Task<FlashSalePlanPreOrderDto> PreOrderAsync(Guid id) |
|||
{ |
|||
var plan = await GetFlashSalePlanCacheAsync(id); |
|||
var product = await ProductAppService.GetAsync(plan.ProductId); |
|||
var productSku = product.GetSkuById(plan.ProductSkuId); |
|||
var expiresTime = DateTimeOffset.Now.Add(Options.PreOrderExpires); |
|||
|
|||
await ValidatePreOrderAsync(plan, product, productSku); |
|||
|
|||
await SetPreOrderCacheAsync(plan, product, productSku, expiresTime); |
|||
|
|||
return new FlashSalePlanPreOrderDto { ExpiresTime = Clock.Normalize(expiresTime.LocalDateTime), ExpiresInSeconds = Options.PreOrderExpires.TotalSeconds }; |
|||
} |
|||
|
|||
public virtual async Task<FlashSaleOrderResultDto> OrderAsync(Guid id, CreateOrderInput input) |
|||
{ |
|||
var preOrderCache = await GetPreOrderCacheAsync(id); |
|||
if (preOrderCache == null) |
|||
{ |
|||
throw new BusinessException(FlashSalesErrorCodes.PreOrderExpired); |
|||
} |
|||
|
|||
var plan = await GetFlashSalePlanCacheAsync(id); |
|||
var now = Clock.Now; |
|||
if (plan.BeginTime > now) |
|||
{ |
|||
throw new BusinessException(FlashSalesErrorCodes.FlashSaleNotStarted); |
|||
} |
|||
|
|||
if (now >= plan.EndTime) |
|||
{ |
|||
throw new BusinessException(FlashSalesErrorCodes.FlashSaleIsOver); |
|||
} |
|||
|
|||
await RemovePreOrderCacheAsync(id); |
|||
|
|||
var userId = CurrentUser.GetId(); |
|||
|
|||
var lockKey = $"create-flash-sale-order-{plan.Id}-{userId}"; |
|||
|
|||
await using var handle = await DistributedLock.TryAcquireAsync(lockKey); |
|||
|
|||
if (handle == null) |
|||
{ |
|||
throw new BusinessException(FlashSalesErrorCodes.BusyToCreateFlashSaleOrder); |
|||
} |
|||
|
|||
var userFlashSaleResultCache = await GetUserFlashSaleResultCacheAsync(plan.Id); |
|||
if (!userFlashSaleResultCache.IsNullOrWhiteSpace()) |
|||
{ |
|||
throw new BusinessException(FlashSalesErrorCodes.DuplicateFlashSalesOrder); |
|||
} |
|||
|
|||
if (!await FlashSaleInventoryManager.TryReduceInventoryAsync( |
|||
plan.TenantId, preOrderCache.InventoryProviderName, |
|||
plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true)) |
|||
{ |
|||
return new FlashSaleOrderResultDto() { IsSuccess = false }; |
|||
} |
|||
|
|||
var result = await CreatePendingFlashSaleResultAsync(plan, userId, async (existsResultId) => |
|||
{ |
|||
await SetUserFlashSaleResultCacheAsync(plan.Id, existsResultId); |
|||
|
|||
await FlashSaleInventoryManager.TryRollBackInventoryAsync( |
|||
plan.TenantId, preOrderCache.InventoryProviderName, |
|||
plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true |
|||
); |
|||
}); |
|||
|
|||
await SetUserFlashSaleResultCacheAsync(plan.Id, result.Id); |
|||
|
|||
var createFlashSaleOrderEto = await PrepareCreateFlashSaleOrderEtoAsync(plan, result.Id, input, userId, now, preOrderCache.HashToken); |
|||
|
|||
await DistributedEventBus.PublishAsync(createFlashSaleOrderEto); |
|||
|
|||
return new FlashSaleOrderResultDto() { IsSuccess = true, FlashSaleResultId = result.Id }; |
|||
} |
|||
|
|||
#region PreOrderCache
|
|||
|
|||
protected virtual async Task<FlashSalePlanCacheItem> GetFlashSalePlanCacheAsync(Guid id) |
|||
{ |
|||
return await PlanDistributedCache.GetOrAddAsync(id, async () => |
|||
{ |
|||
var flashSalePlan = await FlashSalePlanRepository.GetAsync(id); |
|||
return ObjectMapper.Map<FlashSalePlan, FlashSalePlanCacheItem>(flashSalePlan); |
|||
}); |
|||
} |
|||
|
|||
protected virtual Task<string> GetPreOrderCacheKeyAsync(Guid planId) |
|||
{ |
|||
return Task.FromResult(string.Format(PreOrderCacheKeyFormat, planId, CurrentUser.Id)); |
|||
} |
|||
|
|||
protected virtual async Task<FlashSalePlanPreOrderCacheItem> GetPreOrderCacheAsync(Guid planId) |
|||
{ |
|||
return await PreOrderDistributedCache.GetAsync(await GetPreOrderCacheKeyAsync(planId)); |
|||
} |
|||
|
|||
protected virtual async Task RemovePreOrderCacheAsync(Guid planId) |
|||
{ |
|||
await PreOrderDistributedCache.RemoveAsync(await GetPreOrderCacheKeyAsync(planId)); |
|||
} |
|||
|
|||
protected virtual async Task SetPreOrderCacheAsync(FlashSalePlanCacheItem plan, ProductDto product, ProductSkuDto productSku, DateTimeOffset expirationTime) |
|||
{ |
|||
var hashToken = await FlashSalePlanHasher.HashAsync(plan.LastModificationTime, product.LastModificationTime, productSku.LastModificationTime); |
|||
|
|||
await PreOrderDistributedCache.SetAsync(await GetPreOrderCacheKeyAsync(plan.Id), new FlashSalePlanPreOrderCacheItem() |
|||
{ |
|||
HashToken = hashToken, |
|||
PlanId = plan.Id, |
|||
ProductId = product.Id, |
|||
ProductSkuId = productSku.Id, |
|||
InventoryProviderName = product.InventoryProviderName, |
|||
}, new DistributedCacheEntryOptions() |
|||
{ |
|||
AbsoluteExpiration = expirationTime |
|||
}); |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
#region UserFlashSaleResultCache
|
|||
|
|||
protected virtual Task<string> GetUserFlashSaleResultCacheKeyAsync(Guid planId) |
|||
{ |
|||
return Task.FromResult(string.Format(UserFlashSaleResultCacheKeyFormat, CurrentTenant.Id, planId, CurrentUser.GetId())); |
|||
} |
|||
|
|||
protected virtual async Task<string> GetUserFlashSaleResultCacheAsync(Guid planId) |
|||
{ |
|||
var userFlashSaleResultCacheKey = await GetUserFlashSaleResultCacheKeyAsync(planId); |
|||
return await DistributedCache.GetStringAsync(userFlashSaleResultCacheKey); |
|||
} |
|||
|
|||
protected virtual async Task SetUserFlashSaleResultCacheAsync(Guid planId, Guid resultId) |
|||
{ |
|||
var userFlashSaleResultCacheKey = await GetUserFlashSaleResultCacheKeyAsync(planId); |
|||
await DistributedCache.SetStringAsync(userFlashSaleResultCacheKey, resultId.ToString()); |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
protected virtual Task ValidatePreOrderAsync(FlashSalePlanCacheItem plan, ProductDto product, ProductSkuDto productSku) |
|||
{ |
|||
if (!product.IsPublished) |
|||
{ |
|||
throw new BusinessException(FlashSalesErrorCodes.ProductIsNotPublished); |
|||
} |
|||
|
|||
if (product.InventoryStrategy != InventoryStrategy.FlashSales) |
|||
{ |
|||
throw new UnexpectedInventoryStrategyException(InventoryStrategy.FlashSales); |
|||
} |
|||
|
|||
if (!plan.IsPublished) |
|||
{ |
|||
throw new EntityNotFoundException(typeof(FlashSalePlan), plan.Id); |
|||
} |
|||
|
|||
if (Clock.Now >= plan.EndTime) |
|||
{ |
|||
throw new BusinessException(FlashSalesErrorCodes.FlashSaleIsOver); |
|||
} |
|||
|
|||
if (productSku.Inventory < 1) |
|||
{ |
|||
throw new BusinessException(FlashSalesErrorCodes.ProductSkuInventoryExceeded); |
|||
} |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
protected virtual async Task<bool> ExistRelatedFlashSaleResultsAsync(Guid planId) |
|||
{ |
|||
return await FlashSaleResultRepository.AnyAsync(x => x.PlanId == planId); |
|||
} |
|||
|
|||
protected virtual Task ValidateProductAsync(Guid productId, ProductDto product, Guid storeId) |
|||
{ |
|||
if (product.StoreId != storeId) |
|||
{ |
|||
throw new ProductIsNotInThisStoreException(productId, storeId); |
|||
} |
|||
|
|||
if (product.InventoryStrategy != InventoryStrategy.FlashSales) |
|||
{ |
|||
throw new UnexpectedInventoryStrategyException(InventoryStrategy.FlashSales); |
|||
} |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
protected virtual Task<CreateFlashSaleOrderEto> PrepareCreateFlashSaleOrderEtoAsync( |
|||
FlashSalePlanCacheItem plan, Guid resultId, CreateOrderInput input, |
|||
Guid userId, DateTime now, string hashToken) |
|||
{ |
|||
var planEto = ObjectMapper.Map<FlashSalePlanCacheItem, FlashSalePlanEto>(plan); |
|||
planEto.TenantId = CurrentTenant.Id; |
|||
|
|||
var eto = new CreateFlashSaleOrderEto() |
|||
{ |
|||
TenantId = CurrentTenant.Id, |
|||
PlanId = plan.Id, |
|||
UserId = userId, |
|||
PendingResultId = resultId, |
|||
StoreId = plan.StoreId, |
|||
CreateTime = now, |
|||
CustomerRemark = input.CustomerRemark, |
|||
Plan = planEto, |
|||
HashToken = hashToken |
|||
}; |
|||
|
|||
foreach (var item in input.ExtraProperties) |
|||
{ |
|||
eto.ExtraProperties.Add(item.Key, item.Value); |
|||
} |
|||
|
|||
return Task.FromResult(eto); |
|||
} |
|||
|
|||
protected virtual async Task<FlashSaleResult> CreatePendingFlashSaleResultAsync(FlashSalePlanCacheItem plan, Guid userId, Func<Guid, Task> existResultPreProcess) |
|||
{ |
|||
// Prevent repeat submit
|
|||
var existsResult = await FlashSaleResultRepository.FirstOrDefaultAsync(x => |
|||
x.PlanId == plan.Id && x.UserId == userId && x.Status != FlashSaleResultStatus.Failed && x.Reason != FlashSaleResultFailedReason.InvalidHashToken); |
|||
|
|||
if (existsResult != null) |
|||
{ |
|||
await existResultPreProcess(existsResult.Id); |
|||
throw new BusinessException(FlashSalesErrorCodes.DuplicateFlashSalesOrder); |
|||
} |
|||
|
|||
var result = new FlashSaleResult( |
|||
id: GuidGenerator.Create(), |
|||
tenantId: CurrentTenant.Id, |
|||
storeId: plan.StoreId, |
|||
planId: plan.Id, |
|||
userId: userId |
|||
); |
|||
|
|||
return await FlashSaleResultRepository.InsertAsync(result, autoSave: true); |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Domain.Entities.Events; |
|||
using Volo.Abp.EventBus; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
|
|||
public class FlashSalePlanCacheInvalidator : ILocalEventHandler<EntityChangedEventData<FlashSalePlan>>, ITransientDependency |
|||
{ |
|||
protected IDistributedCache<FlashSalePlanCacheItem, Guid> DistributedCache { get; } |
|||
protected IUnitOfWorkManager UnitOfWorkManager { get; } |
|||
|
|||
public FlashSalePlanCacheInvalidator( |
|||
IDistributedCache<FlashSalePlanCacheItem, Guid> distributedCache, |
|||
IUnitOfWorkManager unitOfWorkManager) |
|||
{ |
|||
DistributedCache = distributedCache; |
|||
UnitOfWorkManager = unitOfWorkManager; |
|||
} |
|||
|
|||
public virtual async Task HandleEventAsync(EntityChangedEventData<FlashSalePlan> eventData) |
|||
{ |
|||
await DistributedCache.RemoveAsync(eventData.Entity.Id); |
|||
|
|||
UnitOfWorkManager.Current.OnCompleted(async () => |
|||
{ |
|||
await DistributedCache.RemoveAsync(eventData.Entity.Id); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans.Dtos; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
|
|||
[Serializable] |
|||
public class FlashSalePlanCacheItem : FlashSalePlanDto, IMultiTenant |
|||
{ |
|||
public Guid? TenantId { get; set; } |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
|
|||
[Serializable] |
|||
public class FlashSalePlanPreOrderCacheItem : IMultiTenant |
|||
{ |
|||
public Guid? TenantId { get; set; } |
|||
|
|||
public string HashToken { get; set; } |
|||
|
|||
public Guid PlanId { get; set; } |
|||
|
|||
public Guid ProductId { get; set; } |
|||
|
|||
public Guid ProductSkuId { get; set; } |
|||
|
|||
public string InventoryProviderName { get; set; } |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
|
|||
public class FlashSalesOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Default: 3 minutes
|
|||
/// </summary>
|
|||
public TimeSpan PreOrderExpires { get; set; } = TimeSpan.FromMinutes(3); |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System; |
|||
using Volo.Abp; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
|
|||
public class ProductIsNotInThisStoreException : BusinessException |
|||
{ |
|||
public ProductIsNotInThisStoreException(Guid productId, Guid storeId) : base(FlashSalesErrorCodes.ProductIsNotInThisStore) |
|||
{ |
|||
WithData(nameof(productId), productId); |
|||
WithData(nameof(storeId), storeId); |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using System; |
|||
using Volo.Abp; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
|
|||
public class ProductSkuIsNotFoundException : BusinessException |
|||
{ |
|||
public ProductSkuIsNotFoundException(Guid productSkuId) : base(FlashSalesErrorCodes.ProductSkuIsNotFound) |
|||
{ |
|||
WithData(nameof(productSkuId), productSkuId); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System; |
|||
using Volo.Abp; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
|
|||
[Serializable] |
|||
public class RelatedFlashSaleResultsExistException : BusinessException |
|||
{ |
|||
public RelatedFlashSaleResultsExistException(Guid planId) : base(FlashSalesErrorCodes.RelatedFlashSaleResultsExist) |
|||
{ |
|||
WithData(nameof(planId), planId); |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using EasyAbp.EShop.Products.Products; |
|||
using Volo.Abp; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
|
|||
public class UnexpectedInventoryStrategyException : BusinessException |
|||
{ |
|||
public UnexpectedInventoryStrategyException(InventoryStrategy expectedInventoryStrategy) : base(FlashSalesErrorCodes.UnexpectedInventoryStrategy) |
|||
{ |
|||
WithData(nameof(expectedInventoryStrategy), expectedInventoryStrategy); |
|||
} |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults.Dtos; |
|||
using EasyAbp.EShop.Plugins.FlashSales.Permissions; |
|||
using EasyAbp.EShop.Stores.Stores; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
|
|||
public class FlashSaleResultAppService : |
|||
MultiStoreCrudAppService<FlashSaleResult, FlashSaleResultDto, Guid, FlashSaleResultGetListInput>, |
|||
IFlashSaleResultAppService |
|||
{ |
|||
protected override string CrossStorePolicyName { get; set; } = FlashSalesPermissions.FlashSaleResult.CrossStore; |
|||
protected override string GetPolicyName { get; set; } |
|||
protected override string GetListPolicyName { get; set; } |
|||
|
|||
public FlashSaleResultAppService(IFlashSaleResultRepository flashSaleResultRepository) : base(flashSaleResultRepository) |
|||
{ |
|||
} |
|||
|
|||
public override async Task<FlashSaleResultDto> GetAsync(Guid id) |
|||
{ |
|||
var flashSaleResult = await GetEntityByIdAsync(id); |
|||
|
|||
await CheckMultiStorePolicyAsync(flashSaleResult.StoreId, GetPolicyName); |
|||
|
|||
if (flashSaleResult.UserId != CurrentUser.Id) |
|||
{ |
|||
await CheckMultiStorePolicyAsync(flashSaleResult.StoreId, FlashSalesPermissions.FlashSaleResult.Manage); |
|||
} |
|||
|
|||
return await MapToGetOutputDtoAsync(flashSaleResult); |
|||
} |
|||
|
|||
public override async Task<PagedResultDto<FlashSaleResultDto>> GetListAsync(FlashSaleResultGetListInput input) |
|||
{ |
|||
await CheckMultiStorePolicyAsync(input.StoreId, GetListPolicyName); |
|||
|
|||
return await base.GetListAsync(input); |
|||
} |
|||
|
|||
protected override async Task<IQueryable<FlashSaleResult>> CreateFilteredQueryAsync(FlashSaleResultGetListInput input) |
|||
{ |
|||
if (input.UserId != CurrentUser.Id) |
|||
{ |
|||
await CheckMultiStorePolicyAsync(input.StoreId, FlashSalesPermissions.FlashSaleResult.Manage); |
|||
} |
|||
|
|||
return (await base.CreateFilteredQueryAsync(input)) |
|||
.WhereIf(input.StoreId.HasValue, x => x.StoreId == input.StoreId.Value) |
|||
.WhereIf(input.PlanId.HasValue, x => x.PlanId == input.PlanId.Value) |
|||
.WhereIf(input.Status.HasValue, x => x.Status == input.Status.Value) |
|||
.WhereIf(input.UserId.HasValue, x => x.UserId == input.UserId.Value) |
|||
.WhereIf(input.OrderId.HasValue, x => x.OrderId == input.OrderId.Value); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using EasyAbp.EShop.Plugins.FlashSales.Localization; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales; |
|||
|
|||
public abstract class FlashSalesAppService : ApplicationService |
|||
{ |
|||
protected FlashSalesAppService() |
|||
{ |
|||
LocalizationResource = typeof(FlashSalesResource); |
|||
ObjectMapperContext = typeof(EShopPluginsFlashSalesApplicationModule); |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
using AutoMapper; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans.Dtos; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults.Dtos; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales; |
|||
|
|||
public class FlashSalesApplicationAutoMapperProfile : Profile |
|||
{ |
|||
public FlashSalesApplicationAutoMapperProfile() |
|||
{ |
|||
/* You can configure your AutoMapper mapping configuration here. |
|||
* Alternatively, you can split your mapping configurations |
|||
* into multiple profile classes for a better organization. */ |
|||
CreateMap<FlashSalePlan, FlashSalePlanDto>() |
|||
.MapExtraProperties(); |
|||
CreateMap<FlashSalePlanCreateDto, FlashSalePlan>(MemberList.Source); |
|||
CreateMap<FlashSalePlanUpdateDto, FlashSalePlan>(MemberList.Source); |
|||
CreateMap<FlashSalePlan, FlashSalePlanCacheItem>() |
|||
.MapExtraProperties(); |
|||
CreateMap<FlashSalePlanCacheItem, FlashSalePlanEto>() |
|||
.MapExtraProperties(); |
|||
|
|||
CreateMap<FlashSaleResult, FlashSaleResultDto>() |
|||
.MapExtraProperties(); |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.Eshop.Products.Products; |
|||
using EasyAbp.EShop.Products.Products.Dtos; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace EasyAbp.EShop.Products.Products; |
|||
|
|||
[ExposeServices(typeof(IFlashSaleInventoryManager), IncludeSelf = true)] |
|||
[Dependency(TryRegister = true)] |
|||
public class FlashSaleInventoryManager : IFlashSaleInventoryManager, ITransientDependency |
|||
{ |
|||
protected IFlashSaleInventoryAppService FlashSaleInventoryReducerAppService { get; } |
|||
|
|||
public FlashSaleInventoryManager(IFlashSaleInventoryAppService flashSaleInventoryReducerAppService) |
|||
{ |
|||
FlashSaleInventoryReducerAppService = flashSaleInventoryReducerAppService; |
|||
} |
|||
|
|||
public virtual async Task<bool> TryReduceInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId, |
|||
Guid productSkuId, int quantity, bool increaseSold) |
|||
{ |
|||
return await FlashSaleInventoryReducerAppService.TryReduceAsync( |
|||
new ReduceInventoryInput(tenantId, providerName, storeId, productId, productSkuId, quantity, increaseSold)); |
|||
} |
|||
|
|||
public virtual async Task<bool> TryRollBackInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId, |
|||
Guid productSkuId, int quantity, bool decreaseSold) |
|||
{ |
|||
return await FlashSaleInventoryReducerAppService.TryIncreaseAsync |
|||
(new IncreaseInventoryInput(tenantId, providerName, storeId, productId, productSkuId, quantity, decreaseSold)); |
|||
} |
|||
} |
|||
@ -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,3 @@ |
|||
{ |
|||
"role": "lib.domain-shared" |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.Validation" Version="$(AbpVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="6.0.*" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<EmbeddedResource Include="EasyAbp\EShop\Plugins\FlashSales\Localization\*.json" /> |
|||
<Content Remove="EasyAbp\EShop\Plugins\FlashSales\Localization\*.json" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\..\..\modules\EasyAbp.EShop.Products\src\EasyAbp.EShop.Products.Domain.Shared\EasyAbp.EShop.Products.Domain.Shared.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,36 @@ |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.Localization; |
|||
using EasyAbp.EShop.Plugins.FlashSales.Localization; |
|||
using Volo.Abp.Localization.ExceptionHandling; |
|||
using Volo.Abp.Validation; |
|||
using Volo.Abp.Validation.Localization; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpValidationModule) |
|||
)] |
|||
public class EShopPluginsFlashSalesDomainSharedModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<EShopPluginsFlashSalesDomainSharedModule>(); |
|||
}); |
|||
|
|||
Configure<AbpLocalizationOptions>(options => |
|||
{ |
|||
options.Resources |
|||
.Add<FlashSalesResource>("en") |
|||
.AddBaseTypes(typeof(AbpValidationResource)) |
|||
.AddVirtualJson("EasyAbp/EShop/Plugins/FlashSales/Localization"); |
|||
}); |
|||
|
|||
Configure<AbpExceptionLocalizationOptions>(options => |
|||
{ |
|||
options.MapCodeNamespace(FlashSalesErrorCodes.Namespace, typeof(FlashSalesResource)); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
using System; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.ObjectExtending; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
|
|||
public class CreateFlashSaleOrderCompleteEto : ExtensibleObject, IMultiTenant |
|||
{ |
|||
public Guid? TenantId { get; set; } |
|||
|
|||
public Guid PendingResultId { get; set; } |
|||
|
|||
public bool Success { get; set; } |
|||
|
|||
public Guid StoreId { get; set; } |
|||
|
|||
public Guid PlanId { get; set; } |
|||
|
|||
public string Reason { get; set; } |
|||
|
|||
public Guid UserId { get; set; } |
|||
|
|||
public Guid? OrderId { get; set; } |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
using System; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.ObjectExtending; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
|
|||
[Serializable] |
|||
public class CreateFlashSaleOrderEto : ExtensibleObject, IMultiTenant |
|||
{ |
|||
public Guid? TenantId { get; set; } |
|||
|
|||
public Guid StoreId { get; set; } |
|||
|
|||
public Guid PlanId { get; set; } |
|||
|
|||
public Guid UserId { get; set; } |
|||
|
|||
public Guid PendingResultId { get; set; } |
|||
|
|||
public DateTime CreateTime { get; set; } |
|||
|
|||
public string CustomerRemark { get; set; } |
|||
|
|||
public FlashSalePlanEto Plan { get; set; } |
|||
|
|||
public string HashToken { get; set; } |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
using System; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.ObjectExtending; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
|
|||
[Serializable] |
|||
public class FlashSalePlanEto : FullAuditedEntityEto<Guid>, IMultiTenant |
|||
{ |
|||
public Guid? TenantId { get; set; } |
|||
|
|||
public Guid StoreId { get; set; } |
|||
|
|||
public DateTime BeginTime { get; set; } |
|||
|
|||
public DateTime EndTime { get; set; } |
|||
|
|||
public Guid ProductId { get; set; } |
|||
|
|||
public Guid ProductSkuId { get; set; } |
|||
|
|||
public bool IsPublished { get; set; } |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
using System; |
|||
using System.Security.Cryptography; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
|
|||
public class FlashSalePlanHasher : IFlashSalePlanHasher, ITransientDependency |
|||
{ |
|||
public virtual Task<string> HashAsync(DateTime? planLastModificationTime, DateTime? productLastModificationTime, DateTime? productSkuLastModificationTime) |
|||
{ |
|||
var input = $"{planLastModificationTime?.Ticks}|{productLastModificationTime?.Ticks}|{productSkuLastModificationTime?.Ticks}"; |
|||
|
|||
return Task.FromResult(CreateMd5(input)); |
|||
} |
|||
|
|||
private static string CreateMd5(string input) |
|||
{ |
|||
using var md5 = MD5.Create(); |
|||
|
|||
var inputBytes = Encoding.UTF8.GetBytes(input); |
|||
|
|||
var sb = new StringBuilder(); |
|||
foreach (var t in md5.ComputeHash(inputBytes)) |
|||
{ |
|||
sb.Append(t.ToString("X2")); |
|||
} |
|||
return sb.ToString(); |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
|
|||
public interface IFlashSalePlanHasher |
|||
{ |
|||
Task<string> HashAsync(DateTime? planLastModificationTime, DateTime? productLastModificationTime, DateTime? productSkuLastModificationTime); |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
|
|||
public static class FlashSaleResultFailedReason |
|||
{ |
|||
public const string InsufficientInventory = "InsufficientInventory"; |
|||
|
|||
public const string InvalidHashToken = "InvalidHashToken"; |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
|
|||
public enum FlashSaleResultStatus |
|||
{ |
|||
Pending = 0, |
|||
Successful = 1, |
|||
Failed = 2 |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
namespace EasyAbp.EShop.Plugins.FlashSales; |
|||
|
|||
public static class FlashSalesErrorCodes |
|||
{ |
|||
public const string Namespace = "EasyAbp.EShop.Plugins.FlashSales"; |
|||
|
|||
public const string InvalidEndTime = $"{Namespace}:{nameof(InvalidEndTime)}"; |
|||
|
|||
public const string ProductIsNotInThisStore = $"{Namespace}:{nameof(ProductIsNotInThisStore)}"; |
|||
|
|||
public const string ProductSkuIsNotFound = $"{Namespace}:{nameof(ProductSkuIsNotFound)}"; |
|||
|
|||
public const string ProductIsNotPublished = $"{Namespace}:{nameof(ProductIsNotPublished)}"; |
|||
|
|||
public const string ProductSkuInventoryExceeded = $"{Namespace}:{nameof(ProductSkuInventoryExceeded)}"; |
|||
|
|||
public const string UnexpectedInventoryStrategy = $"{Namespace}:{nameof(UnexpectedInventoryStrategy)}"; |
|||
|
|||
public const string PreOrderExpired = $"{Namespace}:{nameof(PreOrderExpired)}"; |
|||
|
|||
public const string FlashSaleNotStarted = $"{Namespace}:{nameof(FlashSaleNotStarted)}"; |
|||
|
|||
public const string FlashSaleIsOver = $"{Namespace}:{nameof(FlashSaleIsOver)}"; |
|||
|
|||
public const string BusyToCreateFlashSaleOrder = $"{Namespace}:{nameof(BusyToCreateFlashSaleOrder)}"; |
|||
|
|||
public const string DuplicateFlashSalesOrder = $"{Namespace}:{nameof(DuplicateFlashSalesOrder)}"; |
|||
|
|||
public const string RelatedFlashSaleResultsExist = $"{Namespace}:{nameof(RelatedFlashSaleResultsExist)}"; |
|||
|
|||
public const string FlashSaleResultStatusNotPending = $"{Namespace}:{nameof(FlashSaleResultStatusNotPending)}"; |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
using System; |
|||
using Volo.Abp.ObjectExtending; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales; |
|||
|
|||
public class FullAuditedEntityEto<TPrimaryKey> : ExtensibleObject |
|||
{ |
|||
public TPrimaryKey Id { get; set; } |
|||
|
|||
public DateTime CreationTime { get; set; } |
|||
|
|||
public Guid? CreatorId { get; set; } |
|||
|
|||
public DateTime? LastModificationTime { get; set; } |
|||
|
|||
public Guid? LastModifierId { get; set; } |
|||
|
|||
public bool IsDeleted { get; set; } |
|||
|
|||
public Guid? DeleterId { get; set; } |
|||
|
|||
public DateTime? DeletionTime { get; set; } |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.Localization; |
|||
|
|||
[LocalizationResourceName("EasyAbpEShopPluginsFlashSales")] |
|||
public class FlashSalesResource |
|||
{ |
|||
|
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
{ |
|||
"culture": "en", |
|||
"texts": { |
|||
"EasyAbp.EShop.Plugins.FlashSales:InvalidEndTime": "End time must be later than start time", |
|||
"EasyAbp.EShop.Plugins.FlashSales:ProductIsNotInThisStore": "Product '{productId}' is not in this store '{storeId}'", |
|||
"EasyAbp.EShop.Plugins.FlashSales:ProductSkuIsNotFound": "ProductSku '{productSkuId}' is not found", |
|||
"EasyAbp.EShop.Plugins.FlashSales:ProductIsNotPublished": "Product is not published", |
|||
"EasyAbp.EShop.Plugins.FlashSales:ProductSkuInventoryExceeded": "ProductSku inventory exceeded", |
|||
"EasyAbp.EShop.Plugins.FlashSales:UnexpectedInventoryStrategy": "Only the specified inventory strategy {expectedInventoryStrategy} product is allowed.", |
|||
"EasyAbp.EShop.Plugins.FlashSales:PreOrderExpired": "Pre order expired", |
|||
"EasyAbp.EShop.Plugins.FlashSales:FlashSaleNotStarted": "Flash-sale not started", |
|||
"EasyAbp.EShop.Plugins.FlashSales:FlashSaleIsOver": "Flash-sale is over", |
|||
"EasyAbp.EShop.Plugins.FlashSales:BusyToCreateFlashSaleOrder": "Busy to create flash-sale order", |
|||
"EasyAbp.EShop.Plugins.FlashSales:DuplicateFlashSalesOrder": "Duplicate flash-sales order", |
|||
"EasyAbp.EShop.Plugins.FlashSales:RelatedFlashSaleResultsExist": "Related flash-sales results exist", |
|||
"EasyAbp.EShop.Plugins.FlashSales:FlashSaleResultStatusNotPending": "The status of flash-sale result has been changed", |
|||
"Menu:FlashSalesManagement": "Flash-Sales Management", |
|||
"Permission:FlashSalePlan": "FlashSale Plan", |
|||
"Permission:Manage": "Manage", |
|||
"Permission:Create": "Create", |
|||
"Permission:Update": "Update", |
|||
"Permission:Delete": "Delete", |
|||
"Menu:FlashSalePlan": "Flash-Sale Plan", |
|||
"FlashSalePlan": "Flash-Sale Plan", |
|||
"FlashSalePlanStoreId": "Store ID", |
|||
"FlashSalePlanBeginTime": "Begin Time", |
|||
"FlashSalePlanEndTime": "End Time", |
|||
"FlashSalePlanProductId": "Product ID", |
|||
"FlashSalePlanProductSkuId": "Product SKU ID", |
|||
"FlashSalePlanIsPublished": "Is Published", |
|||
"CreateFlashSalePlan": "Create", |
|||
"EditFlashSalePlan": "Edit", |
|||
"FlashSalePlanDeletionConfirmationMessage": "Are you sure to delete the flash-sale plan {0}?", |
|||
"SuccessfullyDeleted": "Successfully deleted", |
|||
"Permission:FlashSaleResult": "Flash-Sale Result", |
|||
"Menu:FlashSaleResult": "Flash-Sale Result", |
|||
"FlashSaleResult": "Flash-Sale Result", |
|||
"FlashSaleResultStoreId": "Store ID", |
|||
"FlashSaleResultPlanId": "Plan ID", |
|||
"FlashSaleResultStatus": "Status", |
|||
"FlashSaleResultReason": "Reason", |
|||
"FlashSaleResultUserId": "User ID", |
|||
"FlashSaleResultOrderId": "Order ID", |
|||
"FlashSaleResultCreationTime": "Creation Time", |
|||
"ViewFlashSaleResult": "View", |
|||
"Enum:FlashSaleResultStatus.Pending": "Pending", |
|||
"Enum:FlashSaleResultStatus.Successful": "Successful", |
|||
"Enum:FlashSaleResultStatus.Failed": "Failed", |
|||
"Enum:FlashSaleResultStatus.0": "Pending", |
|||
"Enum:FlashSaleResultStatus.1": "Successful", |
|||
"Enum:FlashSaleResultStatus.2": "Failed" |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
{ |
|||
"culture": "zh-Hans", |
|||
"texts": { |
|||
"EasyAbp.EShop.Plugins.FlashSales:InvalidEndTime": "结束时间必须晚于开始时间", |
|||
"EasyAbp.EShop.Plugins.FlashSales:ProductIsNotInThisStore": "产品'{productId}'不在此店铺'{storeId}'中", |
|||
"EasyAbp.EShop.Plugins.FlashSales:ProductSkuIsNotFound": "产品SKU'{productSkuId}'不存在", |
|||
"EasyAbp.EShop.Plugins.FlashSales:ProductIsNotPublished": "产品尚未发布", |
|||
"EasyAbp.EShop.Plugins.FlashSales:ProductSkuInventoryExceeded": "产品SKU库存不足", |
|||
"EasyAbp.EShop.Plugins.FlashSales:UnexpectedInventoryStrategy": "只允许使用指定{expectedInventoryStrategy}库存策略的产品", |
|||
"EasyAbp.EShop.Plugins.FlashSales:PreOrderExpired": "预下单已过期", |
|||
"EasyAbp.EShop.Plugins.FlashSales:FlashSaleNotStarted": "闪购尚未开始", |
|||
"EasyAbp.EShop.Plugins.FlashSales:FlashSaleIsOver": "闪购已经结束", |
|||
"EasyAbp.EShop.Plugins.FlashSales:BusyToCreateFlashSaleOrder": "闪购下单繁忙", |
|||
"EasyAbp.EShop.Plugins.FlashSales:DuplicateFlashSalesOrder": "重复闪购下单", |
|||
"EasyAbp.EShop.Plugins.FlashSales:RelatedFlashSaleResultsExist": "已存在关联的闪购结果", |
|||
"EasyAbp.EShop.Plugins.FlashSales:FlashSaleResultStatusNotPending": "闪购结果的状态已经变更", |
|||
"Menu:FlashSalesManagement": "闪购", |
|||
"Permission:FlashSalePlan": "闪购计划", |
|||
"Permission:Manage": "管理", |
|||
"Permission:Create": "新建", |
|||
"Permission:Update": "更新", |
|||
"Permission:Delete": "删除", |
|||
"Menu:FlashSalePlan": "闪购计划", |
|||
"FlashSalePlan": "闪购计划", |
|||
"FlashSalePlanStoreId": "店铺 ID", |
|||
"FlashSalePlanBeginTime": "开始时间", |
|||
"FlashSalePlanEndTime": "结束时间", |
|||
"FlashSalePlanProductId": "产品 ID", |
|||
"FlashSalePlanProductSkuId": "产品SKU ID", |
|||
"FlashSalePlanIsPublished": "发布", |
|||
"CreateFlashSalePlan": "新建", |
|||
"EditFlashSalePlan": "編輯", |
|||
"FlashSalePlanDeletionConfirmationMessage": "确认删除闪购计划 {0}?", |
|||
"SuccessfullyDeleted": "删除成功", |
|||
"Permission:FlashSaleResult": "闪购结果", |
|||
"Menu:FlashSaleResult": "闪购结果", |
|||
"FlashSaleResult": "闪购结果", |
|||
"FlashSaleResultStoreId": "店铺 ID", |
|||
"FlashSaleResultPlanId": "计划 ID", |
|||
"FlashSaleResultStatus": "状态", |
|||
"FlashSaleResultReason": "原因", |
|||
"FlashSaleResultUserId": "用户 ID", |
|||
"FlashSaleResultOrderId": "订单 ID", |
|||
"FlashSaleResultCreationTime": "创建时间", |
|||
"ViewFlashSaleResult": "查看", |
|||
"Enum:FlashSaleResultStatus.Pending": "等待中", |
|||
"Enum:FlashSaleResultStatus.Successful": "成功", |
|||
"Enum:FlashSaleResultStatus.Failed": "失败", |
|||
"Enum:FlashSaleResultStatus.0": "等待中", |
|||
"Enum:FlashSaleResultStatus.1": "成功", |
|||
"Enum:FlashSaleResultStatus.2": "失败" |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
{ |
|||
"culture": "zh-Hant", |
|||
"texts": { |
|||
"EasyAbp.EShop.Plugins.FlashSales:InvalidEndTime": "結束時間必須晚於開始時間", |
|||
"EasyAbp.EShop.Plugins.FlashSales:ProductIsNotInThisStore": "產品'{productId}'不在此店鋪'{storeId}'中", |
|||
"EasyAbp.EShop.Plugins.FlashSales:ProductSkuIsNotFound": "產品SKU'{productSkuId}'不存在", |
|||
"EasyAbp.EShop.Plugins.FlashSales:ProductIsNotPublished": "產品尚未發佈", |
|||
"EasyAbp.EShop.Plugins.FlashSales:ProductSkuInventoryExceeded": "產品SKU庫存不足", |
|||
"EasyAbp.EShop.Plugins.FlashSales:UnexpectedInventoryStrategy": "只允許使用指定的{expectedInventoryStrategy}庫存策略的產品", |
|||
"EasyAbp.EShop.Plugins.FlashSales:PreOrderExpired": "預下單已過期", |
|||
"EasyAbp.EShop.Plugins.FlashSales:FlashSaleNotStarted": "閃購尚未開始", |
|||
"EasyAbp.EShop.Plugins.FlashSales:FlashSaleIsOver": "閃購已經結束", |
|||
"EasyAbp.EShop.Plugins.FlashSales:BusyToCreateFlashSaleOrder": "閃購下單繁忙", |
|||
"EasyAbp.EShop.Plugins.FlashSales:DuplicateFlashSalesOrder": "重複閃購下單", |
|||
"EasyAbp.EShop.Plugins.FlashSales:RelatedFlashSaleResultsExist": "已存在關聯的閃購結果", |
|||
"EasyAbp.EShop.Plugins.FlashSales:FlashSaleResultStatusNotPending": "閃購結果的状态已經變更", |
|||
"Menu:FlashSalesManagement": "閃購管理", |
|||
"Permission:FlashSalePlan": "閃購計畫", |
|||
"Permission:Manage": "管理", |
|||
"Permission:Create": "新建", |
|||
"Permission:Update": "更新", |
|||
"Permission:Delete": "删除", |
|||
"Menu:FlashSalePlan": "閃購計畫", |
|||
"FlashSalePlan": "閃購計畫", |
|||
"FlashSalePlanStoreId": "店鋪ID", |
|||
"FlashSalePlanBeginTime": "開始時間", |
|||
"FlashSalePlanEndTime": "結束時間", |
|||
"FlashSalePlanProductId": "產品ID", |
|||
"FlashSalePlanProductSkuId": "產品SKU ID", |
|||
"FlashSalePlanIsPublished": "發佈", |
|||
"CreateFlashSalePlan": "新建", |
|||
"EditFlashSalePlan": "更新", |
|||
"FlashSalePlanDeletionConfirmationMessage": "確認删除閃購計畫 {0}?", |
|||
"SuccessfullyDeleted": "删除成功", |
|||
"Permission:FlashSaleResult": "閃購結果", |
|||
"Menu:FlashSaleResult": "閃購結果", |
|||
"FlashSaleResult": "閃購結果", |
|||
"FlashSaleResultStoreId": "店铺 ID", |
|||
"FlashSaleResultPlanId": "計畫 ID", |
|||
"FlashSaleResultStatus": "狀態", |
|||
"FlashSaleResultReason": "原因", |
|||
"FlashSaleResultUserId": "用戶 ID", |
|||
"FlashSaleResultOrderId": "訂單 ID", |
|||
"FlashSaleResultCreationTime": "創建時間", |
|||
"ViewFlashSaleResult": "查看", |
|||
"Enum:FlashSaleResultStatus.Pending": "等待中", |
|||
"Enum:FlashSaleResultStatus.Successful": "成功", |
|||
"Enum:FlashSaleResultStatus.Failed": "失敗", |
|||
"Enum:FlashSaleResultStatus.0": "等待中", |
|||
"Enum:FlashSaleResultStatus.1": "成功", |
|||
"Enum:FlashSaleResultStatus.2": "失敗" |
|||
} |
|||
} |
|||
@ -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,3 @@ |
|||
{ |
|||
"role": "lib.domain" |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.AutoMapper" Version="$(AbpVersion)" /> |
|||
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="$(AbpVersion)" /> |
|||
<ProjectReference Include="..\EasyAbp.EShop.Plugins.FlashSales.Domain.Shared\EasyAbp.EShop.Plugins.FlashSales.Domain.Shared.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,16 @@ |
|||
using AutoMapper; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales; |
|||
|
|||
public class EShopPluginsFlashSalesDomainAutoMapperProfile : Profile |
|||
{ |
|||
public EShopPluginsFlashSalesDomainAutoMapperProfile() |
|||
{ |
|||
/* You can configure your AutoMapper mapping configuration here. |
|||
* Alternatively, you can split your mapping configurations |
|||
* into multiple profile classes for a better organization. */ |
|||
CreateMap<FlashSalePlan, FlashSalePlanEto>() |
|||
.MapExtraProperties(); |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.AutoMapper; |
|||
using Volo.Abp.Domain; |
|||
using Volo.Abp.Domain.Entities.Events.Distributed; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpDddDomainModule), |
|||
typeof(AbpAutoMapperModule), |
|||
typeof(EShopPluginsFlashSalesDomainSharedModule) |
|||
)] |
|||
public class EShopPluginsFlashSalesDomainModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddAutoMapperObjectMapper<EShopPluginsFlashSalesDomainModule>(); |
|||
|
|||
Configure<AbpAutoMapperOptions>(options => |
|||
{ |
|||
options.AddMaps<EShopPluginsFlashSalesDomainModule>(validate: true); |
|||
}); |
|||
|
|||
Configure<AbpDistributedEntityEventOptions>(options => |
|||
{ |
|||
options.EtoMappings.Add<FlashSalePlan, FlashSalePlanEto>(typeof(EShopPluginsFlashSalesDomainModule)); |
|||
|
|||
options.AutoEventSelectors.Add<FlashSalePlan>(); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
using System; |
|||
using EasyAbp.EShop.Stores.Stores; |
|||
using Volo.Abp.Domain.Entities.Auditing; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
|
|||
public class FlashSalePlan : FullAuditedAggregateRoot<Guid>, IMultiTenant, IMultiStore |
|||
{ |
|||
public virtual Guid? TenantId { get; protected set; } |
|||
|
|||
public virtual Guid StoreId { get; protected set; } |
|||
|
|||
public virtual DateTime BeginTime { get; set; } |
|||
|
|||
public virtual DateTime EndTime { get; set; } |
|||
|
|||
public virtual Guid ProductId { get; protected set; } |
|||
|
|||
public virtual Guid ProductSkuId { get; protected set; } |
|||
|
|||
public virtual bool IsPublished { get; protected set; } |
|||
|
|||
protected FlashSalePlan() |
|||
{ |
|||
} |
|||
|
|||
public FlashSalePlan(Guid id, Guid? tenantId, Guid storeId, DateTime beginTime, DateTime endTime, Guid productId, Guid productSkuId, bool isPublished) |
|||
: base(id) |
|||
{ |
|||
TenantId = tenantId; |
|||
SetTimeRange(beginTime, endTime); |
|||
SetProductSku(storeId, productId, productSkuId); |
|||
SetPublished(isPublished); |
|||
} |
|||
|
|||
public void SetTimeRange(DateTime beginTime, DateTime endTime) |
|||
{ |
|||
if (beginTime > endTime) |
|||
{ |
|||
throw new InvalidEndTimeException(); |
|||
} |
|||
|
|||
BeginTime = beginTime; |
|||
EndTime = endTime; |
|||
} |
|||
|
|||
public void SetProductSku(Guid storeId, Guid productId, Guid productSkuId) |
|||
{ |
|||
StoreId = storeId; |
|||
ProductId = productId; |
|||
ProductSkuId = productSkuId; |
|||
} |
|||
|
|||
public void SetPublished(bool isPublished) |
|||
{ |
|||
IsPublished = isPublished; |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using System; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
|
|||
public interface IFlashSalePlanRepository : IRepository<FlashSalePlan, Guid> |
|||
{ |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using Volo.Abp; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
|
|||
public class InvalidEndTimeException : BusinessException |
|||
{ |
|||
public InvalidEndTimeException() |
|||
: base(FlashSalesErrorCodes.InvalidEndTime) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
using Volo.Abp.Guids; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
|
|||
public class CreateFlashSaleOrderCompleteEventHandler : IDistributedEventHandler<CreateFlashSaleOrderCompleteEto>, ITransientDependency |
|||
{ |
|||
protected IFlashSaleResultRepository FlashSaleResultRepository { get; } |
|||
protected IGuidGenerator GuidGenerator { get; } |
|||
|
|||
public CreateFlashSaleOrderCompleteEventHandler(IFlashSaleResultRepository flashSaleResultRepository, IGuidGenerator guidGenerator) |
|||
{ |
|||
FlashSaleResultRepository = flashSaleResultRepository; |
|||
GuidGenerator = guidGenerator; |
|||
} |
|||
|
|||
[UnitOfWork] |
|||
public virtual async Task HandleEventAsync(CreateFlashSaleOrderCompleteEto eventData) |
|||
{ |
|||
var flashSaleResult = await FlashSaleResultRepository.GetAsync(eventData.PendingResultId); |
|||
|
|||
if (eventData.Success) |
|||
{ |
|||
flashSaleResult.MarkAsSuccessful(eventData.OrderId.Value); |
|||
} |
|||
else |
|||
{ |
|||
flashSaleResult.MarkAsFailed(eventData.Reason); |
|||
} |
|||
|
|||
await FlashSaleResultRepository.UpdateAsync(flashSaleResult, autoSave: true); |
|||
} |
|||
} |
|||
@ -0,0 +1,56 @@ |
|||
using System; |
|||
using EasyAbp.EShop.Stores.Stores; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Domain.Entities.Auditing; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
|
|||
public class FlashSaleResult : FullAuditedAggregateRoot<Guid>, IMultiTenant, IMultiStore |
|||
{ |
|||
public virtual Guid? TenantId { get; protected set; } |
|||
|
|||
public virtual Guid StoreId { get; protected set; } |
|||
|
|||
public virtual Guid PlanId { get; protected set; } |
|||
|
|||
public virtual FlashSaleResultStatus Status { get; protected set; } |
|||
|
|||
public virtual string Reason { get; protected set; } |
|||
|
|||
public virtual Guid UserId { get; protected set; } |
|||
|
|||
public virtual Guid? OrderId { get; protected set; } |
|||
|
|||
protected FlashSaleResult() { } |
|||
|
|||
public FlashSaleResult(Guid id, Guid? tenantId, Guid storeId, Guid planId, Guid userId) |
|||
: base(id) |
|||
{ |
|||
TenantId = tenantId; |
|||
StoreId = storeId; |
|||
PlanId = planId; |
|||
Status = FlashSaleResultStatus.Pending; |
|||
UserId = userId; |
|||
} |
|||
|
|||
public void MarkAsSuccessful(Guid orderId) |
|||
{ |
|||
if (Status != FlashSaleResultStatus.Pending) |
|||
{ |
|||
throw new FlashSaleResultStatusNotPendingException(Id); |
|||
} |
|||
Status = FlashSaleResultStatus.Successful; |
|||
OrderId = orderId; |
|||
} |
|||
|
|||
public void MarkAsFailed(string reason) |
|||
{ |
|||
if (Status != FlashSaleResultStatus.Pending) |
|||
{ |
|||
throw new FlashSaleResultStatusNotPendingException(Id); |
|||
} |
|||
Status = FlashSaleResultStatus.Failed; |
|||
Reason = Check.NotNullOrEmpty(reason, nameof(reason)); |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using System; |
|||
using Volo.Abp; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
|
|||
public class FlashSaleResultStatusNotPendingException : BusinessException |
|||
{ |
|||
public FlashSaleResultStatusNotPendingException(Guid resultId) : base(FlashSalesErrorCodes.FlashSaleResultStatusNotPending) |
|||
{ |
|||
WithData(nameof(resultId), resultId); |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
|
|||
public interface IFlashSaleResultRepository : IRepository<FlashSaleResult, Guid> |
|||
{ |
|||
|
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
namespace EasyAbp.EShop.Plugins.FlashSales; |
|||
|
|||
public static class FlashSalesDbProperties |
|||
{ |
|||
public static string DbTablePrefix { get; set; } = "EasyAbpEShopPluginsFlashSales"; |
|||
|
|||
public static string DbSchema { get; set; } = null; |
|||
|
|||
public const string ConnectionStringName = "EasyAbpEShopPluginsFlashSales"; |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using Volo.Abp.Settings; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.Settings; |
|||
|
|||
public class FlashSalesSettingDefinitionProvider : SettingDefinitionProvider |
|||
{ |
|||
public override void Define(ISettingDefinitionContext context) |
|||
{ |
|||
/* Define module settings here. |
|||
* Use names from FlashSalesSettings class. |
|||
*/ |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
namespace EasyAbp.EShop.Plugins.FlashSales.Settings; |
|||
|
|||
public static class FlashSalesSettings |
|||
{ |
|||
public const string GroupName = "FlashSales"; |
|||
|
|||
/* Add constants for setting names. Example: |
|||
* public const string MySettingName = GroupName + ".MySettingName"; |
|||
*/ |
|||
} |
|||
@ -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,3 @@ |
|||
{ |
|||
"role": "lib.ef" |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.EntityFrameworkCore" Version="$(AbpVersion)" /> |
|||
<ProjectReference Include="..\EasyAbp.EShop.Plugins.FlashSales.Domain\EasyAbp.EShop.Plugins.FlashSales.Domain.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,23 @@ |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.EntityFrameworkCore; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore; |
|||
|
|||
[DependsOn( |
|||
typeof(EShopPluginsFlashSalesDomainModule), |
|||
typeof(AbpEntityFrameworkCoreModule) |
|||
)] |
|||
public class EShopPluginsFlashSalesEntityFrameworkCoreModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddAbpDbContext<FlashSalesDbContext>(options => |
|||
{ |
|||
options.AddRepository<FlashSalePlan, EfCoreFlashSalePlanRepository>(); |
|||
options.AddRepository<FlashSaleResult, EfCoreFlashSaleResultRepository>(); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.EntityFrameworkCore; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore; |
|||
|
|||
[ConnectionStringName(FlashSalesDbProperties.ConnectionStringName)] |
|||
public class FlashSalesDbContext : AbpDbContext<FlashSalesDbContext>, IFlashSalesDbContext |
|||
{ |
|||
public DbSet<FlashSalePlan> FlashSalePlans { get; set; } |
|||
|
|||
public DbSet<FlashSaleResult> FlashSaleResults { get; set; } |
|||
|
|||
public FlashSalesDbContext(DbContextOptions<FlashSalesDbContext> options) |
|||
: base(options) |
|||
{ |
|||
|
|||
} |
|||
|
|||
protected override void OnModelCreating(ModelBuilder builder) |
|||
{ |
|||
base.OnModelCreating(builder); |
|||
|
|||
builder.ConfigureEShopPluginsFlashSales(); |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Volo.Abp; |
|||
using Volo.Abp.EntityFrameworkCore.Modeling; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore; |
|||
|
|||
public static class FlashSalesDbContextModelCreatingExtensions |
|||
{ |
|||
public static void ConfigureEShopPluginsFlashSales( |
|||
this ModelBuilder builder) |
|||
{ |
|||
Check.NotNull(builder, nameof(builder)); |
|||
|
|||
builder.Entity<FlashSalePlan>(b => |
|||
{ |
|||
b.ToTable(FlashSalesDbProperties.DbTablePrefix + "FlashSalePlans", FlashSalesDbProperties.DbSchema); |
|||
b.ConfigureByConvention(); |
|||
|
|||
/* Configure more properties here */ |
|||
}); |
|||
|
|||
builder.Entity<FlashSaleResult>(b => |
|||
{ |
|||
b.ToTable(FlashSalesDbProperties.DbTablePrefix + "FlashSaleResults", FlashSalesDbProperties.DbSchema); |
|||
b.ConfigureByConvention(); |
|||
|
|||
/* Configure more properties here */ |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.EntityFrameworkCore; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore; |
|||
|
|||
[ConnectionStringName(FlashSalesDbProperties.ConnectionStringName)] |
|||
public interface IFlashSalesDbContext : IEfCoreDbContext |
|||
{ |
|||
DbSet<FlashSalePlan> FlashSalePlans { get; set; } |
|||
|
|||
DbSet<FlashSaleResult> FlashSaleResults { get; set; } |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore; |
|||
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; |
|||
using Volo.Abp.EntityFrameworkCore; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans |
|||
{ |
|||
public class EfCoreFlashSalePlanRepository : EfCoreRepository<IFlashSalesDbContext, FlashSalePlan, Guid>, IFlashSalePlanRepository |
|||
{ |
|||
public EfCoreFlashSalePlanRepository(IDbContextProvider<IFlashSalesDbContext> dbContextProvider) : base(dbContextProvider) |
|||
{ |
|||
} |
|||
|
|||
public override async Task<IQueryable<FlashSalePlan>> WithDetailsAsync() |
|||
{ |
|||
return (await GetQueryableAsync()).IncludeDetails(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
using System.Linq; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
|
|||
public static class FlashSalePlanEfCoreQueryableExtensions |
|||
{ |
|||
public static IQueryable<FlashSalePlan> IncludeDetails(this IQueryable<FlashSalePlan> 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.FlashSales.EntityFrameworkCore; |
|||
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; |
|||
using Volo.Abp.EntityFrameworkCore; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
|
|||
public class EfCoreFlashSaleResultRepository : EfCoreRepository<IFlashSalesDbContext, FlashSaleResult, Guid>, IFlashSaleResultRepository |
|||
{ |
|||
public EfCoreFlashSaleResultRepository(IDbContextProvider<IFlashSalesDbContext> dbContextProvider) : base(dbContextProvider) |
|||
{ |
|||
} |
|||
|
|||
public override async Task<IQueryable<FlashSaleResult>> WithDetailsAsync() |
|||
{ |
|||
return (await GetQueryableAsync()).IncludeDetails(); |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
using System.Linq; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
|
|||
public static class FlashSaleResultEfCoreQuerableExtensions |
|||
{ |
|||
public static IQueryable<FlashSaleResult> IncludeDetails(this IQueryable<FlashSaleResult> queryable, bool include = true) |
|||
{ |
|||
if (!include) |
|||
{ |
|||
return queryable; |
|||
} |
|||
|
|||
return queryable; |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue