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