Browse Source

Merge pull request #184 from JadynWong/jadyn/FlashSales

Add flash sales plugin
pull/193/head
Super 4 years ago
committed by GitHub
parent
commit
677120c525
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 229
      EShop.sln
  2. 46
      docs/plugins/flash-sales/README.md
  3. 1
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp.EShop.Products.Domain.csproj
  4. 41
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/DefaultProductInventoryProvider.cs
  5. 2
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductInventoryProviderResolver.cs
  6. 8
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductInventoryProviderResolver.cs
  7. 223
      plugins/FlashSales/.editorconfig
  8. 1
      plugins/FlashSales/host/EasyAbp.EShop.Plugins.FlashSales.Host.Shared/EasyAbp.EShop.Plugins.FlashSales.Host.Shared.abppkg.json
  9. 10
      plugins/FlashSales/host/EasyAbp.EShop.Plugins.FlashSales.Host.Shared/EasyAbp.EShop.Plugins.FlashSales.Host.Shared.csproj
  10. 9
      plugins/FlashSales/host/EasyAbp.EShop.Plugins.FlashSales.Host.Shared/EasyAbp/EShop/Plugins/FlashSales/MultiTenancy/MultiTenancyConsts.cs
  11. 3
      plugins/FlashSales/host/EasyAbp.EShop.Plugins.FlashSales.Host.Shared/FodyWeavers.xml
  12. 30
      plugins/FlashSales/host/EasyAbp.EShop.Plugins.FlashSales.Host.Shared/FodyWeavers.xsd
  13. 15
      plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.csproj
  14. 151
      plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Orders/Orders/CreateFlashSaleOrderEventHandler.cs
  15. 24
      plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Orders/Plugins/FlashSales/EShopOrdersPluginsFlashSalesApplicationModule.cs
  16. 3
      plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/FodyWeavers.xml
  17. 30
      plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/FodyWeavers.xsd
  18. 3
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts.abppkg.json
  19. 16
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts.csproj
  20. 15
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesApplicationContractsModule.cs
  21. 8
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesRemoteServiceConsts.cs
  22. 8
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/Dtos/CreateOrderInput.cs
  23. 11
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/Dtos/FlashSaleOrderResultDto.cs
  24. 21
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/Dtos/FlashSalePlanCreateDto.cs
  25. 23
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/Dtos/FlashSalePlanDto.cs
  26. 20
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/Dtos/FlashSalePlanGetListInput.cs
  27. 11
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/Dtos/FlashSalePlanPreOrderDto.cs
  28. 21
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/Dtos/FlashSalePlanUpdateDto.cs
  29. 19
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/IFlashSalePlanAppService.cs
  30. 20
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/Dtos/FlashSaleResultDto.cs
  31. 17
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/Dtos/FlashSaleResultGetListInput.cs
  32. 14
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/IFlashSaleResultAppService.cs
  33. 29
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/Permissions/FlashSalesPermissionDefinitionProvider.cs
  34. 30
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/Permissions/FlashSalesPermissions.cs
  35. 3
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/FodyWeavers.xml
  36. 30
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/FodyWeavers.xsd
  37. 3
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp.EShop.Plugins.FlashSales.Application.abppkg.json
  38. 23
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp.EShop.Plugins.FlashSales.Application.csproj
  39. 33
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesApplicationModule.cs
  40. 71
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderFailedEventHandler.cs
  41. 450
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanAppService.cs
  42. 33
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanCacheInvalidator.cs
  43. 11
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanCacheItem.cs
  44. 20
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanTokenCacheItem.cs
  45. 11
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalesOptions.cs
  46. 13
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/ProductIsNotInThisStoreException.cs
  47. 12
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/ProductSkuIsNotFoundException.cs
  48. 13
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/RelatedFlashSaleResultsExistException.cs
  49. 12
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/UnexpectedInventoryStrategyException.cs
  50. 58
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultAppService.cs
  51. 13
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalesAppService.cs
  52. 28
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalesApplicationAutoMapperProfile.cs
  53. 33
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleInventoryManager.cs
  54. 3
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/FodyWeavers.xml
  55. 30
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/FodyWeavers.xsd
  56. 3
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared.abppkg.json
  57. 28
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared.csproj
  58. 36
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesDomainSharedModule.cs
  59. 24
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderCompleteEto.cs
  60. 27
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderEto.cs
  61. 23
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanEto.cs
  62. 31
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanHasher.cs
  63. 9
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/IFlashSalePlanHasher.cs
  64. 8
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultFailedReason.cs
  65. 8
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultStatus.cs
  66. 32
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalesErrorCodes.cs
  67. 23
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FullAuditedEntityEto.cs
  68. 9
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/Localization/FlashSalesResource.cs
  69. 53
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/Localization/en.json
  70. 53
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/Localization/zh-Hans.json
  71. 53
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/Localization/zh-Hant.json
  72. 3
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/FodyWeavers.xml
  73. 30
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/FodyWeavers.xsd
  74. 3
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp.EShop.Plugins.FlashSales.Domain.abppkg.json
  75. 16
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp.EShop.Plugins.FlashSales.Domain.csproj
  76. 16
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesDomainAutoMapperProfile.cs
  77. 33
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesDomainModule.cs
  78. 59
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlan.cs
  79. 8
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/IFlashSalePlanRepository.cs
  80. 12
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/InvalidEndTimeException.cs
  81. 37
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleOrderCompleteEventHandler.cs
  82. 56
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResult.cs
  83. 12
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultStatusNotPendingException.cs
  84. 9
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/IFlashSaleResultRepository.cs
  85. 10
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSalesDbProperties.cs
  86. 13
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/Settings/FlashSalesSettingDefinitionProvider.cs
  87. 10
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/Settings/FlashSalesSettings.cs
  88. 3
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/FodyWeavers.xml
  89. 30
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/FodyWeavers.xsd
  90. 3
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore.abppkg.json
  91. 15
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore.csproj
  92. 23
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore/EasyAbp/EShop/Plugins/FlashSales/EntityFrameworkCore/EShopPluginsFlashSalesEntityFrameworkCoreModule.cs
  93. 28
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore/EasyAbp/EShop/Plugins/FlashSales/EntityFrameworkCore/FlashSalesDbContext.cs
  94. 32
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore/EasyAbp/EShop/Plugins/FlashSales/EntityFrameworkCore/FlashSalesDbContextModelCreatingExtensions.cs
  95. 15
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore/EasyAbp/EShop/Plugins/FlashSales/EntityFrameworkCore/IFlashSalesDbContext.cs
  96. 21
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/EfCoreFlashSalePlanRepository.cs
  97. 18
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanEfCoreQuerableExtensions.cs
  98. 20
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/EfCoreFlashSaleResultRepository.cs
  99. 16
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultEfCoreQuerableExtensions.cs
  100. 3
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore/FodyWeavers.xml

229
EShop.sln

@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16 # Visual Studio Version 17
VisualStudioVersion = 16.0.29020.237 VisualStudioVersion = 17.2.32526.322
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EShopSample.Domain", "samples\EShopSample\aspnet-core\src\EShopSample.Domain\EShopSample.Domain.csproj", "{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EShopSample.Domain", "samples\EShopSample\aspnet-core\src\EShopSample.Domain\EShopSample.Domain.csproj", "{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}"
EndProject EndProject
@ -303,77 +303,77 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{D261
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EShopSample", "EShopSample", "{FE0A5FD6-AEE7-4738-9A6D-261BF086704D}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EShopSample", "EShopSample", "{FE0A5FD6-AEE7-4738-9A6D-261BF086704D}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Stores.Application.Shared", "modules\EasyAbp.EShop.Stores\src\EasyAbp.EShop.Stores.Application.Shared\EasyAbp.EShop.Stores.Application.Shared.csproj", "{06700175-4E04-4993-A1FB-24E7A8835E19}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Stores.Application.Shared", "modules\EasyAbp.EShop.Stores\src\EasyAbp.EShop.Stores.Application.Shared\EasyAbp.EShop.Stores.Application.Shared.csproj", "{06700175-4E04-4993-A1FB-24E7A8835E19}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{72F34527-9295-4F29-923E-4B075A4F31A2}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{72F34527-9295-4F29-923E-4B075A4F31A2}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Coupons.Application", "plugins\Coupons\src\EasyAbp.EShop.Plugins.Coupons.Application\EasyAbp.EShop.Plugins.Coupons.Application.csproj", "{1EB6504F-7FF0-4628-9D7C-81BDE33D9490}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.Coupons.Application", "plugins\Coupons\src\EasyAbp.EShop.Plugins.Coupons.Application\EasyAbp.EShop.Plugins.Coupons.Application.csproj", "{1EB6504F-7FF0-4628-9D7C-81BDE33D9490}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Coupons.Application.Contracts", "plugins\Coupons\src\EasyAbp.EShop.Plugins.Coupons.Application.Contracts\EasyAbp.EShop.Plugins.Coupons.Application.Contracts.csproj", "{C7D05698-E509-4F5C-A792-0036190AE6FF}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.Coupons.Application.Contracts", "plugins\Coupons\src\EasyAbp.EShop.Plugins.Coupons.Application.Contracts\EasyAbp.EShop.Plugins.Coupons.Application.Contracts.csproj", "{C7D05698-E509-4F5C-A792-0036190AE6FF}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Coupons.Domain", "plugins\Coupons\src\EasyAbp.EShop.Plugins.Coupons.Domain\EasyAbp.EShop.Plugins.Coupons.Domain.csproj", "{4DBC9216-80FD-4E71-BCB3-0F1ADA63AEC2}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.Coupons.Domain", "plugins\Coupons\src\EasyAbp.EShop.Plugins.Coupons.Domain\EasyAbp.EShop.Plugins.Coupons.Domain.csproj", "{4DBC9216-80FD-4E71-BCB3-0F1ADA63AEC2}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Coupons.Domain.Shared", "plugins\Coupons\src\EasyAbp.EShop.Plugins.Coupons.Domain.Shared\EasyAbp.EShop.Plugins.Coupons.Domain.Shared.csproj", "{31F1DC02-5790-4FAD-A8F2-0D6F9FE21F00}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.Coupons.Domain.Shared", "plugins\Coupons\src\EasyAbp.EShop.Plugins.Coupons.Domain.Shared\EasyAbp.EShop.Plugins.Coupons.Domain.Shared.csproj", "{31F1DC02-5790-4FAD-A8F2-0D6F9FE21F00}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Coupons.EntityFrameworkCore", "plugins\Coupons\src\EasyAbp.EShop.Plugins.Coupons.EntityFrameworkCore\EasyAbp.EShop.Plugins.Coupons.EntityFrameworkCore.csproj", "{EE6B9D10-7038-4CDE-A6BF-3941AFD84D7A}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.Coupons.EntityFrameworkCore", "plugins\Coupons\src\EasyAbp.EShop.Plugins.Coupons.EntityFrameworkCore\EasyAbp.EShop.Plugins.Coupons.EntityFrameworkCore.csproj", "{EE6B9D10-7038-4CDE-A6BF-3941AFD84D7A}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Coupons.HttpApi", "plugins\Coupons\src\EasyAbp.EShop.Plugins.Coupons.HttpApi\EasyAbp.EShop.Plugins.Coupons.HttpApi.csproj", "{0E22DE0A-032B-442E-AEB2-E299B333BADC}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.Coupons.HttpApi", "plugins\Coupons\src\EasyAbp.EShop.Plugins.Coupons.HttpApi\EasyAbp.EShop.Plugins.Coupons.HttpApi.csproj", "{0E22DE0A-032B-442E-AEB2-E299B333BADC}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Coupons.HttpApi.Client", "plugins\Coupons\src\EasyAbp.EShop.Plugins.Coupons.HttpApi.Client\EasyAbp.EShop.Plugins.Coupons.HttpApi.Client.csproj", "{85D39FAB-A9A8-4942-BF94-A6061FCFBF7A}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.Coupons.HttpApi.Client", "plugins\Coupons\src\EasyAbp.EShop.Plugins.Coupons.HttpApi.Client\EasyAbp.EShop.Plugins.Coupons.HttpApi.Client.csproj", "{85D39FAB-A9A8-4942-BF94-A6061FCFBF7A}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Coupons.MongoDB", "plugins\Coupons\src\EasyAbp.EShop.Plugins.Coupons.MongoDB\EasyAbp.EShop.Plugins.Coupons.MongoDB.csproj", "{C97330F5-4634-4865-9D7D-E33F87808938}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.Coupons.MongoDB", "plugins\Coupons\src\EasyAbp.EShop.Plugins.Coupons.MongoDB\EasyAbp.EShop.Plugins.Coupons.MongoDB.csproj", "{C97330F5-4634-4865-9D7D-E33F87808938}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Coupons.Web", "plugins\Coupons\src\EasyAbp.EShop.Plugins.Coupons.Web\EasyAbp.EShop.Plugins.Coupons.Web.csproj", "{C11BD71D-FA6B-4833-9CAE-82ED9D857A84}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.Coupons.Web", "plugins\Coupons\src\EasyAbp.EShop.Plugins.Coupons.Web\EasyAbp.EShop.Plugins.Coupons.Web.csproj", "{C11BD71D-FA6B-4833-9CAE-82ED9D857A84}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{4001814E-A67B-490D-9E13-2FB9A34B0A0B}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{4001814E-A67B-490D-9E13-2FB9A34B0A0B}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Coupons.Application.Tests", "plugins\Coupons\test\EasyAbp.EShop.Plugins.Coupons.Application.Tests\EasyAbp.EShop.Plugins.Coupons.Application.Tests.csproj", "{10675CED-5689-4B2F-895A-E13820928A3B}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.Coupons.Application.Tests", "plugins\Coupons\test\EasyAbp.EShop.Plugins.Coupons.Application.Tests\EasyAbp.EShop.Plugins.Coupons.Application.Tests.csproj", "{10675CED-5689-4B2F-895A-E13820928A3B}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Coupons.Domain.Tests", "plugins\Coupons\test\EasyAbp.EShop.Plugins.Coupons.Domain.Tests\EasyAbp.EShop.Plugins.Coupons.Domain.Tests.csproj", "{B29ABA23-37B2-419A-9E3C-F1A8703D584D}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.Coupons.Domain.Tests", "plugins\Coupons\test\EasyAbp.EShop.Plugins.Coupons.Domain.Tests\EasyAbp.EShop.Plugins.Coupons.Domain.Tests.csproj", "{B29ABA23-37B2-419A-9E3C-F1A8703D584D}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Coupons.EntityFrameworkCore.Tests", "plugins\Coupons\test\EasyAbp.EShop.Plugins.Coupons.EntityFrameworkCore.Tests\EasyAbp.EShop.Plugins.Coupons.EntityFrameworkCore.Tests.csproj", "{D81EC4DC-3944-4CFE-875A-5DD07BE487AE}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.Coupons.EntityFrameworkCore.Tests", "plugins\Coupons\test\EasyAbp.EShop.Plugins.Coupons.EntityFrameworkCore.Tests\EasyAbp.EShop.Plugins.Coupons.EntityFrameworkCore.Tests.csproj", "{D81EC4DC-3944-4CFE-875A-5DD07BE487AE}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Coupons.HttpApi.Client.ConsoleTestApp", "plugins\Coupons\test\EasyAbp.EShop.Plugins.Coupons.HttpApi.Client.ConsoleTestApp\EasyAbp.EShop.Plugins.Coupons.HttpApi.Client.ConsoleTestApp.csproj", "{8B22E009-5ABA-4878-83E5-38154F2B8E2F}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.Coupons.HttpApi.Client.ConsoleTestApp", "plugins\Coupons\test\EasyAbp.EShop.Plugins.Coupons.HttpApi.Client.ConsoleTestApp\EasyAbp.EShop.Plugins.Coupons.HttpApi.Client.ConsoleTestApp.csproj", "{8B22E009-5ABA-4878-83E5-38154F2B8E2F}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Coupons.MongoDB.Tests", "plugins\Coupons\test\EasyAbp.EShop.Plugins.Coupons.MongoDB.Tests\EasyAbp.EShop.Plugins.Coupons.MongoDB.Tests.csproj", "{86CAD303-A0E5-42C9-89A5-61D9AAA4AD8F}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.Coupons.MongoDB.Tests", "plugins\Coupons\test\EasyAbp.EShop.Plugins.Coupons.MongoDB.Tests\EasyAbp.EShop.Plugins.Coupons.MongoDB.Tests.csproj", "{86CAD303-A0E5-42C9-89A5-61D9AAA4AD8F}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Coupons.TestBase", "plugins\Coupons\test\EasyAbp.EShop.Plugins.Coupons.TestBase\EasyAbp.EShop.Plugins.Coupons.TestBase.csproj", "{B076C103-DF0B-464B-A9CB-4BE5CAFEE067}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.Coupons.TestBase", "plugins\Coupons\test\EasyAbp.EShop.Plugins.Coupons.TestBase\EasyAbp.EShop.Plugins.Coupons.TestBase.csproj", "{B076C103-DF0B-464B-A9CB-4BE5CAFEE067}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Orders.Plugins.Coupons", "plugins\Coupons\src\EasyAbp.EShop.Orders.Plugins.Coupons\EasyAbp.EShop.Orders.Plugins.Coupons.csproj", "{3C385657-8365-470F-9F4F-30F31F9FCA42}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Orders.Plugins.Coupons", "plugins\Coupons\src\EasyAbp.EShop.Orders.Plugins.Coupons\EasyAbp.EShop.Orders.Plugins.Coupons.csproj", "{3C385657-8365-470F-9F4F-30F31F9FCA42}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Inventories", "Inventories", "{9AC27747-E175-487F-92C9-434DEE543273}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Inventories", "Inventories", "{9AC27747-E175-487F-92C9-434DEE543273}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DaprActors", "DaprActors", "{6E6FE4B9-4117-4F57-B219-EE47E4046096}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DaprActors", "DaprActors", "{6E6FE4B9-4117-4F57-B219-EE47E4046096}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Products.DaprActorsInventory.Domain", "plugins\Inventories\DaprActors\src\EasyAbp.EShop.Products.DaprActorsInventory.Domain\EasyAbp.EShop.Products.DaprActorsInventory.Domain.csproj", "{6CD1A8B5-8AB7-4A31-8333-024A7FB602D1}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Products.DaprActorsInventory.Domain", "plugins\Inventories\DaprActors\src\EasyAbp.EShop.Products.DaprActorsInventory.Domain\EasyAbp.EShop.Products.DaprActorsInventory.Domain.csproj", "{6CD1A8B5-8AB7-4A31-8333-024A7FB602D1}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions", "plugins\Inventories\DaprActors\src\EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions\EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions.csproj", "{CF4DE32D-9629-4C48-9BE8-5B83A1C27291}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions", "plugins\Inventories\DaprActors\src\EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions\EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions.csproj", "{CF4DE32D-9629-4C48-9BE8-5B83A1C27291}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Inventories.DaprActors", "plugins\Inventories\DaprActors\src\EasyAbp.EShop.Plugins.Inventories.DaprActors\EasyAbp.EShop.Plugins.Inventories.DaprActors.csproj", "{B6F3ACD5-463E-4455-A094-057A82941A94}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.Inventories.DaprActors", "plugins\Inventories\DaprActors\src\EasyAbp.EShop.Plugins.Inventories.DaprActors\EasyAbp.EShop.Plugins.Inventories.DaprActors.csproj", "{B6F3ACD5-463E-4455-A094-057A82941A94}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F468A386-5660-4888-981A-6ECF15182D32}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F468A386-5660-4888-981A-6ECF15182D32}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{485204B1-7603-4EA0-B3A4-73CB89B0D5BC}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{485204B1-7603-4EA0-B3A4-73CB89B0D5BC}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests", "plugins\Inventories\DaprActors\test\EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests\EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests.csproj", "{733C51A3-19C8-45C4-8B22-3FD40CAF4EFB}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests", "plugins\Inventories\DaprActors\test\EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests\EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests.csproj", "{733C51A3-19C8-45C4-8B22-3FD40CAF4EFB}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Inventories.DaprActors.AspNetCore", "plugins\Inventories\DaprActors\src\EasyAbp.EShop.Plugins.Inventories.DaprActors.AspNetCore\EasyAbp.EShop.Plugins.Inventories.DaprActors.AspNetCore.csproj", "{3F0EA314-CCF4-4BB2-A8C1-79FAE4442884}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.Inventories.DaprActors.AspNetCore", "plugins\Inventories\DaprActors\src\EasyAbp.EShop.Plugins.Inventories.DaprActors.AspNetCore\EasyAbp.EShop.Plugins.Inventories.DaprActors.AspNetCore.csproj", "{3F0EA314-CCF4-4BB2-A8C1-79FAE4442884}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Inventories.OrleansGrains", "plugins\Inventories\OrleansGrains\src\EasyAbp.EShop.Plugins.Inventories.OrleansGrains\EasyAbp.EShop.Plugins.Inventories.OrleansGrains.csproj", "{83F6434F-74DC-4389-870D-46510E28C029}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.Inventories.OrleansGrains", "plugins\Inventories\OrleansGrains\src\EasyAbp.EShop.Plugins.Inventories.OrleansGrains\EasyAbp.EShop.Plugins.Inventories.OrleansGrains.csproj", "{83F6434F-74DC-4389-870D-46510E28C029}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OrleansGrains", "OrleansGrains", "{88D17635-75D7-48A1-B622-E6FB3DCACEF8}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OrleansGrains", "OrleansGrains", "{88D17635-75D7-48A1-B622-E6FB3DCACEF8}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8E978749-7972-4703-8A94-6A90080C78DE}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8E978749-7972-4703-8A94-6A90080C78DE}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions", "plugins\Inventories\OrleansGrains\src\EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions\EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions.csproj", "{AB3477DB-3457-4167-A086-BAD104D69604}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions", "plugins\Inventories\OrleansGrains\src\EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions\EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions.csproj", "{AB3477DB-3457-4167-A086-BAD104D69604}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo", "plugins\Inventories\OrleansGrains\src\EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo\EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo.csproj", "{0D613460-A0AD-4EAF-B719-785FE65E97E8}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo", "plugins\Inventories\OrleansGrains\src\EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo\EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo.csproj", "{0D613460-A0AD-4EAF-B719-785FE65E97E8}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Products.OrleansGrainsInventory.Domain", "plugins\Inventories\OrleansGrains\src\EasyAbp.EShop.Products.OrleansGrainsInventory.Domain\EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.csproj", "{DB1C55BF-0C0D-488C-9AFC-992A3DED2EAD}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Products.OrleansGrainsInventory.Domain", "plugins\Inventories\OrleansGrains\src\EasyAbp.EShop.Products.OrleansGrainsInventory.Domain\EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.csproj", "{DB1C55BF-0C0D-488C-9AFC-992A3DED2EAD}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{F58B6EEF-5AFF-4B79-BC71-A2D8C71F5E77}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{F58B6EEF-5AFF-4B79-BC71-A2D8C71F5E77}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests", "plugins\Inventories\OrleansGrains\test\EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests\EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests.csproj", "{D652EBF0-27CA-44C2-BB78-F446B87377C7}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests", "plugins\Inventories\OrleansGrains\test\EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests\EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests.csproj", "{D652EBF0-27CA-44C2-BB78-F446B87377C7}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Booking", "Booking", "{CE945F1D-6636-47D5-A619-C16C4E14CF8C}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Booking", "Booking", "{CE945F1D-6636-47D5-A619-C16C4E14CF8C}"
EndProject EndProject
@ -417,6 +417,56 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Orders.Bookin
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Payments.Booking.Application", "plugins\Booking\src\EasyAbp.EShop.Payments.Booking.Application\EasyAbp.EShop.Payments.Booking.Application.csproj", "{4480BFAF-C981-4242-A509-EDA6F572E45C}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Payments.Booking.Application", "plugins\Booking\src\EasyAbp.EShop.Payments.Booking.Application\EasyAbp.EShop.Payments.Booking.Application.csproj", "{4480BFAF-C981-4242-A509-EDA6F572E45C}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FlashSales", "FlashSales", "{867AD9F8-FD56-469D-A90D-C569EB9C3D2A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.FlashSales.Application", "plugins\FlashSales\src\EasyAbp.EShop.Plugins.FlashSales.Application\EasyAbp.EShop.Plugins.FlashSales.Application.csproj", "{26611C4C-6910-498A-9FBA-BECC09392ADE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.FlashSales.Application.Contracts", "plugins\FlashSales\src\EasyAbp.EShop.Plugins.FlashSales.Application.Contracts\EasyAbp.EShop.Plugins.FlashSales.Application.Contracts.csproj", "{FCD72398-F832-4914-8ACF-EA4C1DD24BBF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.FlashSales.Domain.Shared", "plugins\FlashSales\src\EasyAbp.EShop.Plugins.FlashSales.Domain.Shared\EasyAbp.EShop.Plugins.FlashSales.Domain.Shared.csproj", "{55145D4A-5A98-4B53-9293-CCB7236E69F5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.FlashSales.Domain", "plugins\FlashSales\src\EasyAbp.EShop.Plugins.FlashSales.Domain\EasyAbp.EShop.Plugins.FlashSales.Domain.csproj", "{7E535D1A-3259-4098-8E47-A81B3D8B3920}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore", "plugins\FlashSales\src\EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore\EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore.csproj", "{2D1C0E98-D86A-4C60-98CE-86C4E5571B91}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.FlashSales.MongoDB", "plugins\FlashSales\src\EasyAbp.EShop.Plugins.FlashSales.MongoDB\EasyAbp.EShop.Plugins.FlashSales.MongoDB.csproj", "{B5ED3D8B-E2DC-4B38-A9C7-2A18B47385EE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.FlashSales.HttpApi", "plugins\FlashSales\src\EasyAbp.EShop.Plugins.FlashSales.HttpApi\EasyAbp.EShop.Plugins.FlashSales.HttpApi.csproj", "{0D5558BD-A22F-4565-A92A-BB14D517B1A5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F29C5BCD-E6C0-4556-A631-CACA41B1050B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{9C180C9E-50E9-4624-BE06-5C8C24A028E4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.FlashSales.HttpApi.Client", "plugins\FlashSales\src\EasyAbp.EShop.Plugins.FlashSales.HttpApi.Client\EasyAbp.EShop.Plugins.FlashSales.HttpApi.Client.csproj", "{6658CE7D-CA7D-47A7-8B9E-6D8776CE607C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.FlashSales.Web", "plugins\FlashSales\src\EasyAbp.EShop.Plugins.FlashSales.Web\EasyAbp.EShop.Plugins.FlashSales.Web.csproj", "{6F06F221-1371-433A-8C90-3123B5EF32AD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.FlashSales.Application.Tests", "plugins\FlashSales\test\EasyAbp.EShop.Plugins.FlashSales.Application.Tests\EasyAbp.EShop.Plugins.FlashSales.Application.Tests.csproj", "{6EBE36C7-53DB-4194-BDDB-199F789F44E6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.FlashSales.Domain.Tests", "plugins\FlashSales\test\EasyAbp.EShop.Plugins.FlashSales.Domain.Tests\EasyAbp.EShop.Plugins.FlashSales.Domain.Tests.csproj", "{654C320E-78BD-4074-AC2D-154E95457BBC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore.Tests", "plugins\FlashSales\test\EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore.Tests\EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore.Tests.csproj", "{D2AAFF29-3F7B-4C90-AA99-AC686393B670}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.FlashSales.HttpApi.Client.ConsoleTestApp", "plugins\FlashSales\test\EasyAbp.EShop.Plugins.FlashSales.HttpApi.Client.ConsoleTestApp\EasyAbp.EShop.Plugins.FlashSales.HttpApi.Client.ConsoleTestApp.csproj", "{2291B087-FEC5-47BD-A7ED-04725D54E6A6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.FlashSales.MongoDB.Tests", "plugins\FlashSales\test\EasyAbp.EShop.Plugins.FlashSales.MongoDB.Tests\EasyAbp.EShop.Plugins.FlashSales.MongoDB.Tests.csproj", "{68993D65-2C9C-438F-8B94-15E98C469A16}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.FlashSales.TestBase", "plugins\FlashSales\test\EasyAbp.EShop.Plugins.FlashSales.TestBase\EasyAbp.EShop.Plugins.FlashSales.TestBase.csproj", "{417AB8E2-1488-4814-9699-3B189D1ABA67}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Orders.Plugins.FlashSales.Application", "plugins\FlashSales\src\EasyAbp.EShop.Orders.Plugins.FlashSales.Application\EasyAbp.EShop.Orders.Plugins.FlashSales.Application.csproj", "{5732E880-CB72-49A0-AC4F-A0620F4E4D16}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Products.Plugins.FlashSales.Application", "plugins\FlashSales\src\EasyAbp.EShop.Products.Plugins.FlashSales.Application\EasyAbp.EShop.Products.Plugins.FlashSales.Application.csproj", "{6AD2F468-D86C-4F9A-B280-3BCC15661C47}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions", "plugins\FlashSales\src\EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions\EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions.csproj", "{C1EB9DC0-F572-41DF-9716-893B154BBB13}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts", "plugins\FlashSales\src\EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts\EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts.csproj", "{F08D9409-4D01-4639-A7B8-A70B7ED8E0F9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Products.Plugins.FlashSales.HttpApi", "plugins\FlashSales\src\EasyAbp.EShop.Products.Plugins.FlashSales.HttpApi\EasyAbp.EShop.Products.Plugins.FlashSales.HttpApi.csproj", "{B137BF4B-8C0A-4CE2-AF22-BD9BD29C86B7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Products.Plugins.FlashSales.HttpApi.Client", "plugins\FlashSales\src\EasyAbp.EShop.Products.Plugins.FlashSales.HttpApi.Client\EasyAbp.EShop.Products.Plugins.FlashSales.HttpApi.Client.csproj", "{274769DC-5DD6-4CFD-8078-5E0E0CE8D6D8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests", "plugins\FlashSales\test\EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests\EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests.csproj", "{17A3486C-1845-4B4E-B1A6-752106F0C309}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -1083,6 +1133,94 @@ Global
{4480BFAF-C981-4242-A509-EDA6F572E45C}.Debug|Any CPU.Build.0 = Debug|Any CPU {4480BFAF-C981-4242-A509-EDA6F572E45C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4480BFAF-C981-4242-A509-EDA6F572E45C}.Release|Any CPU.ActiveCfg = Release|Any CPU {4480BFAF-C981-4242-A509-EDA6F572E45C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4480BFAF-C981-4242-A509-EDA6F572E45C}.Release|Any CPU.Build.0 = Release|Any CPU {4480BFAF-C981-4242-A509-EDA6F572E45C}.Release|Any CPU.Build.0 = Release|Any CPU
{26611C4C-6910-498A-9FBA-BECC09392ADE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{26611C4C-6910-498A-9FBA-BECC09392ADE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{26611C4C-6910-498A-9FBA-BECC09392ADE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{26611C4C-6910-498A-9FBA-BECC09392ADE}.Release|Any CPU.Build.0 = Release|Any CPU
{FCD72398-F832-4914-8ACF-EA4C1DD24BBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FCD72398-F832-4914-8ACF-EA4C1DD24BBF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FCD72398-F832-4914-8ACF-EA4C1DD24BBF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FCD72398-F832-4914-8ACF-EA4C1DD24BBF}.Release|Any CPU.Build.0 = Release|Any CPU
{55145D4A-5A98-4B53-9293-CCB7236E69F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{55145D4A-5A98-4B53-9293-CCB7236E69F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{55145D4A-5A98-4B53-9293-CCB7236E69F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{55145D4A-5A98-4B53-9293-CCB7236E69F5}.Release|Any CPU.Build.0 = Release|Any CPU
{7E535D1A-3259-4098-8E47-A81B3D8B3920}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7E535D1A-3259-4098-8E47-A81B3D8B3920}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7E535D1A-3259-4098-8E47-A81B3D8B3920}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7E535D1A-3259-4098-8E47-A81B3D8B3920}.Release|Any CPU.Build.0 = Release|Any CPU
{2D1C0E98-D86A-4C60-98CE-86C4E5571B91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2D1C0E98-D86A-4C60-98CE-86C4E5571B91}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2D1C0E98-D86A-4C60-98CE-86C4E5571B91}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2D1C0E98-D86A-4C60-98CE-86C4E5571B91}.Release|Any CPU.Build.0 = Release|Any CPU
{B5ED3D8B-E2DC-4B38-A9C7-2A18B47385EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B5ED3D8B-E2DC-4B38-A9C7-2A18B47385EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B5ED3D8B-E2DC-4B38-A9C7-2A18B47385EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B5ED3D8B-E2DC-4B38-A9C7-2A18B47385EE}.Release|Any CPU.Build.0 = Release|Any CPU
{0D5558BD-A22F-4565-A92A-BB14D517B1A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0D5558BD-A22F-4565-A92A-BB14D517B1A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0D5558BD-A22F-4565-A92A-BB14D517B1A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0D5558BD-A22F-4565-A92A-BB14D517B1A5}.Release|Any CPU.Build.0 = Release|Any CPU
{6658CE7D-CA7D-47A7-8B9E-6D8776CE607C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6658CE7D-CA7D-47A7-8B9E-6D8776CE607C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6658CE7D-CA7D-47A7-8B9E-6D8776CE607C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6658CE7D-CA7D-47A7-8B9E-6D8776CE607C}.Release|Any CPU.Build.0 = Release|Any CPU
{6F06F221-1371-433A-8C90-3123B5EF32AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6F06F221-1371-433A-8C90-3123B5EF32AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6F06F221-1371-433A-8C90-3123B5EF32AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6F06F221-1371-433A-8C90-3123B5EF32AD}.Release|Any CPU.Build.0 = Release|Any CPU
{6EBE36C7-53DB-4194-BDDB-199F789F44E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6EBE36C7-53DB-4194-BDDB-199F789F44E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6EBE36C7-53DB-4194-BDDB-199F789F44E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6EBE36C7-53DB-4194-BDDB-199F789F44E6}.Release|Any CPU.Build.0 = Release|Any CPU
{654C320E-78BD-4074-AC2D-154E95457BBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{654C320E-78BD-4074-AC2D-154E95457BBC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{654C320E-78BD-4074-AC2D-154E95457BBC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{654C320E-78BD-4074-AC2D-154E95457BBC}.Release|Any CPU.Build.0 = Release|Any CPU
{D2AAFF29-3F7B-4C90-AA99-AC686393B670}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D2AAFF29-3F7B-4C90-AA99-AC686393B670}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D2AAFF29-3F7B-4C90-AA99-AC686393B670}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D2AAFF29-3F7B-4C90-AA99-AC686393B670}.Release|Any CPU.Build.0 = Release|Any CPU
{2291B087-FEC5-47BD-A7ED-04725D54E6A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2291B087-FEC5-47BD-A7ED-04725D54E6A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2291B087-FEC5-47BD-A7ED-04725D54E6A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2291B087-FEC5-47BD-A7ED-04725D54E6A6}.Release|Any CPU.Build.0 = Release|Any CPU
{68993D65-2C9C-438F-8B94-15E98C469A16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{68993D65-2C9C-438F-8B94-15E98C469A16}.Debug|Any CPU.Build.0 = Debug|Any CPU
{68993D65-2C9C-438F-8B94-15E98C469A16}.Release|Any CPU.ActiveCfg = Release|Any CPU
{68993D65-2C9C-438F-8B94-15E98C469A16}.Release|Any CPU.Build.0 = Release|Any CPU
{417AB8E2-1488-4814-9699-3B189D1ABA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{417AB8E2-1488-4814-9699-3B189D1ABA67}.Debug|Any CPU.Build.0 = Debug|Any CPU
{417AB8E2-1488-4814-9699-3B189D1ABA67}.Release|Any CPU.ActiveCfg = Release|Any CPU
{417AB8E2-1488-4814-9699-3B189D1ABA67}.Release|Any CPU.Build.0 = Release|Any CPU
{5732E880-CB72-49A0-AC4F-A0620F4E4D16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5732E880-CB72-49A0-AC4F-A0620F4E4D16}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5732E880-CB72-49A0-AC4F-A0620F4E4D16}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5732E880-CB72-49A0-AC4F-A0620F4E4D16}.Release|Any CPU.Build.0 = Release|Any CPU
{6AD2F468-D86C-4F9A-B280-3BCC15661C47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6AD2F468-D86C-4F9A-B280-3BCC15661C47}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6AD2F468-D86C-4F9A-B280-3BCC15661C47}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6AD2F468-D86C-4F9A-B280-3BCC15661C47}.Release|Any CPU.Build.0 = Release|Any CPU
{C1EB9DC0-F572-41DF-9716-893B154BBB13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C1EB9DC0-F572-41DF-9716-893B154BBB13}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C1EB9DC0-F572-41DF-9716-893B154BBB13}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C1EB9DC0-F572-41DF-9716-893B154BBB13}.Release|Any CPU.Build.0 = Release|Any CPU
{F08D9409-4D01-4639-A7B8-A70B7ED8E0F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F08D9409-4D01-4639-A7B8-A70B7ED8E0F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F08D9409-4D01-4639-A7B8-A70B7ED8E0F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F08D9409-4D01-4639-A7B8-A70B7ED8E0F9}.Release|Any CPU.Build.0 = Release|Any CPU
{B137BF4B-8C0A-4CE2-AF22-BD9BD29C86B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B137BF4B-8C0A-4CE2-AF22-BD9BD29C86B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B137BF4B-8C0A-4CE2-AF22-BD9BD29C86B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B137BF4B-8C0A-4CE2-AF22-BD9BD29C86B7}.Release|Any CPU.Build.0 = Release|Any CPU
{274769DC-5DD6-4CFD-8078-5E0E0CE8D6D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{274769DC-5DD6-4CFD-8078-5E0E0CE8D6D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{274769DC-5DD6-4CFD-8078-5E0E0CE8D6D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{274769DC-5DD6-4CFD-8078-5E0E0CE8D6D8}.Release|Any CPU.Build.0 = Release|Any CPU
{17A3486C-1845-4B4E-B1A6-752106F0C309}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{17A3486C-1845-4B4E-B1A6-752106F0C309}.Debug|Any CPU.Build.0 = Debug|Any CPU
{17A3486C-1845-4B4E-B1A6-752106F0C309}.Release|Any CPU.ActiveCfg = Release|Any CPU
{17A3486C-1845-4B4E-B1A6-752106F0C309}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -1255,16 +1393,16 @@ Global
{3C385657-8365-470F-9F4F-30F31F9FCA42} = {72F34527-9295-4F29-923E-4B075A4F31A2} {3C385657-8365-470F-9F4F-30F31F9FCA42} = {72F34527-9295-4F29-923E-4B075A4F31A2}
{9AC27747-E175-487F-92C9-434DEE543273} = {94CC5A11-DA0F-413C-96CA-01DB0FC426E0} {9AC27747-E175-487F-92C9-434DEE543273} = {94CC5A11-DA0F-413C-96CA-01DB0FC426E0}
{6E6FE4B9-4117-4F57-B219-EE47E4046096} = {9AC27747-E175-487F-92C9-434DEE543273} {6E6FE4B9-4117-4F57-B219-EE47E4046096} = {9AC27747-E175-487F-92C9-434DEE543273}
{F468A386-5660-4888-981A-6ECF15182D32} = {6E6FE4B9-4117-4F57-B219-EE47E4046096}
{B6F3ACD5-463E-4455-A094-057A82941A94} = {F468A386-5660-4888-981A-6ECF15182D32}
{CF4DE32D-9629-4C48-9BE8-5B83A1C27291} = {F468A386-5660-4888-981A-6ECF15182D32}
{6CD1A8B5-8AB7-4A31-8333-024A7FB602D1} = {F468A386-5660-4888-981A-6ECF15182D32} {6CD1A8B5-8AB7-4A31-8333-024A7FB602D1} = {F468A386-5660-4888-981A-6ECF15182D32}
{CF4DE32D-9629-4C48-9BE8-5B83A1C27291} = {F468A386-5660-4888-981A-6ECF15182D32}
{B6F3ACD5-463E-4455-A094-057A82941A94} = {F468A386-5660-4888-981A-6ECF15182D32}
{F468A386-5660-4888-981A-6ECF15182D32} = {6E6FE4B9-4117-4F57-B219-EE47E4046096}
{485204B1-7603-4EA0-B3A4-73CB89B0D5BC} = {6E6FE4B9-4117-4F57-B219-EE47E4046096} {485204B1-7603-4EA0-B3A4-73CB89B0D5BC} = {6E6FE4B9-4117-4F57-B219-EE47E4046096}
{733C51A3-19C8-45C4-8B22-3FD40CAF4EFB} = {485204B1-7603-4EA0-B3A4-73CB89B0D5BC} {733C51A3-19C8-45C4-8B22-3FD40CAF4EFB} = {485204B1-7603-4EA0-B3A4-73CB89B0D5BC}
{3F0EA314-CCF4-4BB2-A8C1-79FAE4442884} = {F468A386-5660-4888-981A-6ECF15182D32} {3F0EA314-CCF4-4BB2-A8C1-79FAE4442884} = {F468A386-5660-4888-981A-6ECF15182D32}
{83F6434F-74DC-4389-870D-46510E28C029} = {8E978749-7972-4703-8A94-6A90080C78DE}
{88D17635-75D7-48A1-B622-E6FB3DCACEF8} = {9AC27747-E175-487F-92C9-434DEE543273} {88D17635-75D7-48A1-B622-E6FB3DCACEF8} = {9AC27747-E175-487F-92C9-434DEE543273}
{8E978749-7972-4703-8A94-6A90080C78DE} = {88D17635-75D7-48A1-B622-E6FB3DCACEF8} {8E978749-7972-4703-8A94-6A90080C78DE} = {88D17635-75D7-48A1-B622-E6FB3DCACEF8}
{83F6434F-74DC-4389-870D-46510E28C029} = {8E978749-7972-4703-8A94-6A90080C78DE}
{AB3477DB-3457-4167-A086-BAD104D69604} = {8E978749-7972-4703-8A94-6A90080C78DE} {AB3477DB-3457-4167-A086-BAD104D69604} = {8E978749-7972-4703-8A94-6A90080C78DE}
{0D613460-A0AD-4EAF-B719-785FE65E97E8} = {8E978749-7972-4703-8A94-6A90080C78DE} {0D613460-A0AD-4EAF-B719-785FE65E97E8} = {8E978749-7972-4703-8A94-6A90080C78DE}
{DB1C55BF-0C0D-488C-9AFC-992A3DED2EAD} = {8E978749-7972-4703-8A94-6A90080C78DE} {DB1C55BF-0C0D-488C-9AFC-992A3DED2EAD} = {8E978749-7972-4703-8A94-6A90080C78DE}
@ -1291,6 +1429,31 @@ Global
{739CAE6A-14E6-44FC-8863-DA905CBD289F} = {7087FDFF-196A-4C9F-8C66-EEBC2C49F2F8} {739CAE6A-14E6-44FC-8863-DA905CBD289F} = {7087FDFF-196A-4C9F-8C66-EEBC2C49F2F8}
{C0399352-1278-4D91-8D4E-7491FD77C18B} = {A8C4583C-034E-47AF-B7EC-1A34EE288E2F} {C0399352-1278-4D91-8D4E-7491FD77C18B} = {A8C4583C-034E-47AF-B7EC-1A34EE288E2F}
{4480BFAF-C981-4242-A509-EDA6F572E45C} = {A8C4583C-034E-47AF-B7EC-1A34EE288E2F} {4480BFAF-C981-4242-A509-EDA6F572E45C} = {A8C4583C-034E-47AF-B7EC-1A34EE288E2F}
{867AD9F8-FD56-469D-A90D-C569EB9C3D2A} = {94CC5A11-DA0F-413C-96CA-01DB0FC426E0}
{26611C4C-6910-498A-9FBA-BECC09392ADE} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B}
{FCD72398-F832-4914-8ACF-EA4C1DD24BBF} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B}
{55145D4A-5A98-4B53-9293-CCB7236E69F5} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B}
{7E535D1A-3259-4098-8E47-A81B3D8B3920} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B}
{2D1C0E98-D86A-4C60-98CE-86C4E5571B91} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B}
{B5ED3D8B-E2DC-4B38-A9C7-2A18B47385EE} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B}
{0D5558BD-A22F-4565-A92A-BB14D517B1A5} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B}
{F29C5BCD-E6C0-4556-A631-CACA41B1050B} = {867AD9F8-FD56-469D-A90D-C569EB9C3D2A}
{9C180C9E-50E9-4624-BE06-5C8C24A028E4} = {867AD9F8-FD56-469D-A90D-C569EB9C3D2A}
{6658CE7D-CA7D-47A7-8B9E-6D8776CE607C} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B}
{6F06F221-1371-433A-8C90-3123B5EF32AD} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B}
{6EBE36C7-53DB-4194-BDDB-199F789F44E6} = {9C180C9E-50E9-4624-BE06-5C8C24A028E4}
{654C320E-78BD-4074-AC2D-154E95457BBC} = {9C180C9E-50E9-4624-BE06-5C8C24A028E4}
{D2AAFF29-3F7B-4C90-AA99-AC686393B670} = {9C180C9E-50E9-4624-BE06-5C8C24A028E4}
{2291B087-FEC5-47BD-A7ED-04725D54E6A6} = {9C180C9E-50E9-4624-BE06-5C8C24A028E4}
{68993D65-2C9C-438F-8B94-15E98C469A16} = {9C180C9E-50E9-4624-BE06-5C8C24A028E4}
{417AB8E2-1488-4814-9699-3B189D1ABA67} = {9C180C9E-50E9-4624-BE06-5C8C24A028E4}
{5732E880-CB72-49A0-AC4F-A0620F4E4D16} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B}
{6AD2F468-D86C-4F9A-B280-3BCC15661C47} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B}
{C1EB9DC0-F572-41DF-9716-893B154BBB13} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B}
{F08D9409-4D01-4639-A7B8-A70B7ED8E0F9} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B}
{B137BF4B-8C0A-4CE2-AF22-BD9BD29C86B7} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B}
{274769DC-5DD6-4CFD-8078-5E0E0CE8D6D8} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B}
{17A3486C-1845-4B4E-B1A6-752106F0C309} = {9C180C9E-50E9-4624-BE06-5C8C24A028E4}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {28315BFD-90E7-4E14-A2EA-F3D23AF4126F} SolutionGuid = {28315BFD-90E7-4E14-A2EA-F3D23AF4126F}

46
docs/plugins/flash-sales/README.md

@ -0,0 +1,46 @@
# EShop.Plugins.FlashSales
[![ABP version](https://img.shields.io/badge/dynamic/xml?style=flat-square&color=yellow&label=abp&query=%2F%2FProject%2FPropertyGroup%2FAbpVersion&url=https%3A%2F%2Fraw.githubusercontent.com%2FEasyAbp%2FEShop%2Fmaster%2FDirectory.Build.props)](https://abp.io)
[![NuGet](https://img.shields.io/nuget/v/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared.svg?style=flat-square)](https://www.nuget.org/packages/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared)
[![NuGet Download](https://img.shields.io/nuget/dt/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared.svg?style=flat-square)](https://www.nuget.org/packages/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared)
[![Discord online](https://badgen.net/discord/online-members/S6QaezrCRq?label=Discord)](https://discord.gg/S6QaezrCRq)
[![GitHub stars](https://img.shields.io/github/stars/EasyAbp/EShop?style=social)](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.

1
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp.EShop.Products.Domain.csproj

@ -8,6 +8,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.*" />
<PackageReference Include="NodaMoney" Version="$(NodaMoneyVersion)" /> <PackageReference Include="NodaMoney" Version="$(NodaMoneyVersion)" />
<PackageReference Include="EasyAbp.Abp.Trees.Domain" Version="$(EasyAbpAbpTreesModuleVersion)" /> <PackageReference Include="EasyAbp.Abp.Trees.Domain" Version="$(EasyAbpAbpTreesModuleVersion)" />
<PackageReference Include="Volo.Abp.AutoMapper" Version="$(AbpVersion)" /> <PackageReference Include="Volo.Abp.AutoMapper" Version="$(AbpVersion)" />

41
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/DefaultProductInventoryProvider.cs

@ -3,7 +3,9 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using EasyAbp.EShop.Products.ProductInventories; using EasyAbp.EShop.Products.ProductInventories;
using Microsoft.Extensions.Logging;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.DistributedLocking;
using Volo.Abp.EventBus.Distributed; using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Guids; using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
@ -13,6 +15,14 @@ namespace EasyAbp.EShop.Products.Products
{ {
public class DefaultProductInventoryProvider : IProductInventoryProvider, ITransientDependency public class DefaultProductInventoryProvider : IProductInventoryProvider, ITransientDependency
{ {
/// <summary>
/// The <see cref="GetLockKeyAsync(InventoryQueryModel)"/> lock key format.
/// <para>{0}: Tenant ID</para>
/// <para>{1}: Product ID</para>
/// <para>{2}: ProductSku ID</para>
/// </summary>
public const string DefaultProductInventoryLockKeyFormat = "eshop-product-inventory-{0}-{1}-{2}";
public static string DefaultProductInventoryProviderName { get; set; } = "Default"; public static string DefaultProductInventoryProviderName { get; set; } = "Default";
public static string DefaultProductInventoryProviderDisplayName { get; set; } = "Default"; public static string DefaultProductInventoryProviderDisplayName { get; set; } = "Default";
public static string DefaultProductInventoryProviderDescription { get; set; } = "Default"; public static string DefaultProductInventoryProviderDescription { get; set; } = "Default";
@ -24,17 +34,23 @@ namespace EasyAbp.EShop.Products.Products
private readonly ICurrentTenant _currentTenant; private readonly ICurrentTenant _currentTenant;
private readonly IDistributedEventBus _distributedEventBus; private readonly IDistributedEventBus _distributedEventBus;
private readonly IProductInventoryRepository _productInventoryRepository; private readonly IProductInventoryRepository _productInventoryRepository;
private readonly IAbpDistributedLock _distributedLock;
private readonly ILogger<DefaultProductInventoryProvider> _logger;
public DefaultProductInventoryProvider( public DefaultProductInventoryProvider(
IGuidGenerator guidGenerator, IGuidGenerator guidGenerator,
ICurrentTenant currentTenant, ICurrentTenant currentTenant,
IDistributedEventBus distributedEventBus, IDistributedEventBus distributedEventBus,
IProductInventoryRepository productInventoryRepository) IProductInventoryRepository productInventoryRepository,
IAbpDistributedLock distributedLock,
ILogger<DefaultProductInventoryProvider> logger)
{ {
_guidGenerator = guidGenerator; _guidGenerator = guidGenerator;
_currentTenant = currentTenant; _currentTenant = currentTenant;
_distributedEventBus = distributedEventBus; _distributedEventBus = distributedEventBus;
_productInventoryRepository = productInventoryRepository; _productInventoryRepository = productInventoryRepository;
_distributedLock = distributedLock;
_logger = logger;
} }
[UnitOfWork] [UnitOfWork]
@ -62,6 +78,15 @@ namespace EasyAbp.EShop.Products.Products
public virtual async Task<bool> TryIncreaseInventoryAsync(InventoryQueryModel model, int quantity, public virtual async Task<bool> TryIncreaseInventoryAsync(InventoryQueryModel model, int quantity,
bool decreaseSold) bool decreaseSold)
{ {
await using var handle = await _distributedLock.TryAcquireAsync(await GetLockKeyAsync(model), TimeSpan.FromSeconds(30));
if (handle == null)
{
_logger.LogWarning("TryIncreaseInventory failed to acquire lock for product inventory: {TenantId},{ProductId},{ProductSkuId}",
model.TenantId, model.ProductId, model.ProductSkuId);
return false;
}
var productInventory = await GetOrCreateProductInventoryAsync(model.ProductId, model.ProductSkuId); var productInventory = await GetOrCreateProductInventoryAsync(model.ProductId, model.ProductSkuId);
return await TryIncreaseInventoryAsync(model, productInventory, quantity, decreaseSold); return await TryIncreaseInventoryAsync(model, productInventory, quantity, decreaseSold);
@ -71,6 +96,15 @@ namespace EasyAbp.EShop.Products.Products
public virtual async Task<bool> TryReduceInventoryAsync(InventoryQueryModel model, int quantity, public virtual async Task<bool> TryReduceInventoryAsync(InventoryQueryModel model, int quantity,
bool increaseSold) bool increaseSold)
{ {
await using var handle = await _distributedLock.TryAcquireAsync(await GetLockKeyAsync(model), TimeSpan.FromSeconds(30));
if (handle == null)
{
_logger.LogWarning("TryReduceInventory failed to acquire lock for product inventory: {TenantId},{ProductId},{ProductSkuId}",
model.TenantId, model.ProductId, model.ProductSkuId);
return false;
}
var productInventory = await GetOrCreateProductInventoryAsync(model.ProductId, model.ProductSkuId); var productInventory = await GetOrCreateProductInventoryAsync(model.ProductId, model.ProductSkuId);
return await TryReduceInventoryAsync(model, productInventory, quantity, increaseSold); return await TryReduceInventoryAsync(model, productInventory, quantity, increaseSold);
@ -158,5 +192,10 @@ namespace EasyAbp.EShop.Products.Products
newInventory, newInventory,
sold)); sold));
} }
protected virtual Task<string> GetLockKeyAsync(InventoryQueryModel model)
{
return Task.FromResult(string.Format(DefaultProductInventoryLockKeyFormat, model.TenantId, model.ProductId, model.ProductSkuId));
}
} }
} }

2
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductInventoryProviderResolver.cs

@ -9,4 +9,6 @@ public interface IProductInventoryProviderResolver
Task<bool> ExistProviderAsync([NotNull] string providerName); Task<bool> ExistProviderAsync([NotNull] string providerName);
Task<IProductInventoryProvider> GetAsync(Product product); Task<IProductInventoryProvider> GetAsync(Product product);
Task<IProductInventoryProvider> GetAsync([NotNull] string providerName);
} }

8
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductInventoryProviderResolver.cs

@ -7,6 +7,7 @@ using EasyAbp.EShop.Products.ProductInventories;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Volo.Abp;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
namespace EasyAbp.EShop.Products.Products; namespace EasyAbp.EShop.Products.Products;
@ -47,6 +48,11 @@ public class ProductInventoryProviderResolver : IProductInventoryProviderResolve
return Task.FromResult(GetProviderByName(options.Value.DefaultInventoryProviderName)); return Task.FromResult(GetProviderByName(options.Value.DefaultInventoryProviderName));
} }
public virtual Task<IProductInventoryProvider> GetAsync([CanBeNull] string providerName)
{
return Task.FromResult(GetProviderByName(providerName));
}
protected virtual IProductInventoryProvider GetProviderByName([CanBeNull] string providerName) protected virtual IProductInventoryProvider GetProviderByName([CanBeNull] string providerName)
{ {
if (providerName.IsNullOrEmpty()) if (providerName.IsNullOrEmpty())
@ -67,7 +73,7 @@ public class ProductInventoryProviderResolver : IProductInventoryProviderResolve
{ {
return; return;
} }
var options = ServiceProvider.GetRequiredService<IOptions<EShopProductsOptions>>().Value; var options = ServiceProvider.GetRequiredService<IOptions<EShopProductsOptions>>().Value;
foreach (var pair in options.InventoryProviders.GetConfigurationsDictionary()) foreach (var pair in options.InventoryProviders.GetConfigurationsDictionary())

223
plugins/FlashSales/.editorconfig

@ -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

1
plugins/FlashSales/host/EasyAbp.EShop.Plugins.FlashSales.Host.Shared/EasyAbp.EShop.Plugins.FlashSales.Host.Shared.abppkg.json

@ -0,0 +1 @@
{}

10
plugins/FlashSales/host/EasyAbp.EShop.Plugins.FlashSales.Host.Shared/EasyAbp.EShop.Plugins.FlashSales.Host.Shared.csproj

@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
</Project>

9
plugins/FlashSales/host/EasyAbp.EShop.Plugins.FlashSales.Host.Shared/EasyAbp/EShop/Plugins/FlashSales/MultiTenancy/MultiTenancyConsts.cs

@ -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;
}

3
plugins/FlashSales/host/EasyAbp.EShop.Plugins.FlashSales.Host.Shared/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
plugins/FlashSales/host/EasyAbp.EShop.Plugins.FlashSales.Host.Shared/FodyWeavers.xsd

@ -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>

15
plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.csproj

@ -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>

151
plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Orders/Orders/CreateFlashSaleOrderEventHandler.cs

@ -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);
}
}

24
plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Orders/Plugins/FlashSales/EShopOrdersPluginsFlashSalesApplicationModule.cs

@ -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);
});
}
}

3
plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/FodyWeavers.xsd

@ -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>

3
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts.abppkg.json

@ -0,0 +1,3 @@
{
"role": "lib.application-contracts"
}

16
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts.csproj

@ -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>

15
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesApplicationContractsModule.cs

@ -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
{
}

8
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesRemoteServiceConsts.cs

@ -0,0 +1,8 @@
namespace EasyAbp.EShop.Plugins.FlashSales;
public class EShopPluginsFlashSalesRemoteServiceConsts
{
public const string RemoteServiceName = "EasyAbpEShopPluginsFlashSales";
public const string ModuleName = "easyAbpEShopPluginsFlashSales";
}

8
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/Dtos/CreateOrderInput.cs

@ -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; }
}

11
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/Dtos/FlashSaleOrderResultDto.cs

@ -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; }
}

21
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/Dtos/FlashSalePlanCreateDto.cs

@ -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; }
}

23
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/Dtos/FlashSalePlanDto.cs

@ -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; }
}

20
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/Dtos/FlashSalePlanGetListInput.cs

@ -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; }
}

11
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/Dtos/FlashSalePlanPreOrderDto.cs

@ -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; }
}

21
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/Dtos/FlashSalePlanUpdateDto.cs

@ -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; }
}

19
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/IFlashSalePlanAppService.cs

@ -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);
}

20
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/Dtos/FlashSaleResultDto.cs

@ -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; }
}

17
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/Dtos/FlashSaleResultGetListInput.cs

@ -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; }
}

14
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/IFlashSaleResultAppService.cs

@ -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>
{
}

29
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/Permissions/FlashSalesPermissionDefinitionProvider.cs

@ -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);
}
}

30
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/Permissions/FlashSalesPermissions.cs

@ -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";
}
}

3
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/FodyWeavers.xsd

@ -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>

3
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp.EShop.Plugins.FlashSales.Application.abppkg.json

@ -0,0 +1,3 @@
{
"role": "lib.application"
}

23
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp.EShop.Plugins.FlashSales.Application.csproj

@ -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>

33
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesApplicationModule.cs

@ -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);
});
}
}

71
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderFailedEventHandler.cs

@ -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));
}
}

450
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanAppService.cs

@ -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);
}
}

33
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanCacheInvalidator.cs

@ -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);
});
}
}

11
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanCacheItem.cs

@ -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; }
}

20
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanTokenCacheItem.cs

@ -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; }
}

11
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalesOptions.cs

@ -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);
}

13
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/ProductIsNotInThisStoreException.cs

@ -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);
}
}

12
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/ProductSkuIsNotFoundException.cs

@ -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);
}
}

13
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/RelatedFlashSaleResultsExistException.cs

@ -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);
}
}

12
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/UnexpectedInventoryStrategyException.cs

@ -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);
}
}

58
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultAppService.cs

@ -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);
}
}

13
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalesAppService.cs

@ -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);
}
}

28
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalesApplicationAutoMapperProfile.cs

@ -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();
}
}

33
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleInventoryManager.cs

@ -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));
}
}

3
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/FodyWeavers.xsd

@ -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>

3
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared.abppkg.json

@ -0,0 +1,3 @@
{
"role": "lib.domain-shared"
}

28
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared.csproj

@ -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>

36
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesDomainSharedModule.cs

@ -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));
});
}
}

24
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderCompleteEto.cs

@ -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; }
}

27
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderEto.cs

@ -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; }
}

23
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanEto.cs

@ -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; }
}

31
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanHasher.cs

@ -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();
}
}

9
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/IFlashSalePlanHasher.cs

@ -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);
}

8
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultFailedReason.cs

@ -0,0 +1,8 @@
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults;
public static class FlashSaleResultFailedReason
{
public const string InsufficientInventory = "InsufficientInventory";
public const string InvalidHashToken = "InvalidHashToken";
}

8
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultStatus.cs

@ -0,0 +1,8 @@
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults;
public enum FlashSaleResultStatus
{
Pending = 0,
Successful = 1,
Failed = 2
}

32
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalesErrorCodes.cs

@ -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)}";
}

23
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FullAuditedEntityEto.cs

@ -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; }
}

9
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/Localization/FlashSalesResource.cs

@ -0,0 +1,9 @@
using Volo.Abp.Localization;
namespace EasyAbp.EShop.Plugins.FlashSales.Localization;
[LocalizationResourceName("EasyAbpEShopPluginsFlashSales")]
public class FlashSalesResource
{
}

53
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/Localization/en.json

@ -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"
}
}

53
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/Localization/zh-Hans.json

@ -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": "失败"
}
}

53
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/Localization/zh-Hant.json

@ -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": "失敗"
}
}

3
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/FodyWeavers.xsd

@ -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>

3
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp.EShop.Plugins.FlashSales.Domain.abppkg.json

@ -0,0 +1,3 @@
{
"role": "lib.domain"
}

16
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp.EShop.Plugins.FlashSales.Domain.csproj

@ -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>

16
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesDomainAutoMapperProfile.cs

@ -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();
}
}

33
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesDomainModule.cs

@ -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>();
});
}
}

59
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlan.cs

@ -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;
}
}

8
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/IFlashSalePlanRepository.cs

@ -0,0 +1,8 @@
using System;
using Volo.Abp.Domain.Repositories;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
public interface IFlashSalePlanRepository : IRepository<FlashSalePlan, Guid>
{
}

12
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/InvalidEndTimeException.cs

@ -0,0 +1,12 @@
using Volo.Abp;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
public class InvalidEndTimeException : BusinessException
{
public InvalidEndTimeException()
: base(FlashSalesErrorCodes.InvalidEndTime)
{
}
}

37
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleOrderCompleteEventHandler.cs

@ -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);
}
}

56
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResult.cs

@ -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));
}
}

12
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultStatusNotPendingException.cs

@ -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);
}
}

9
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/IFlashSaleResultRepository.cs

@ -0,0 +1,9 @@
using System;
using Volo.Abp.Domain.Repositories;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults;
public interface IFlashSaleResultRepository : IRepository<FlashSaleResult, Guid>
{
}

10
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSalesDbProperties.cs

@ -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";
}

13
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/Settings/FlashSalesSettingDefinitionProvider.cs

@ -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.
*/
}
}

10
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/Settings/FlashSalesSettings.cs

@ -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";
*/
}

3
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/FodyWeavers.xsd

@ -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>

3
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore.abppkg.json

@ -0,0 +1,3 @@
{
"role": "lib.ef"
}

15
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore.csproj

@ -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>

23
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore/EasyAbp/EShop/Plugins/FlashSales/EntityFrameworkCore/EShopPluginsFlashSalesEntityFrameworkCoreModule.cs

@ -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>();
});
}
}

28
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore/EasyAbp/EShop/Plugins/FlashSales/EntityFrameworkCore/FlashSalesDbContext.cs

@ -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();
}
}

32
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore/EasyAbp/EShop/Plugins/FlashSales/EntityFrameworkCore/FlashSalesDbContextModelCreatingExtensions.cs

@ -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 */
});
}
}

15
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore/EasyAbp/EShop/Plugins/FlashSales/EntityFrameworkCore/IFlashSalesDbContext.cs

@ -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; }
}

21
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/EfCoreFlashSalePlanRepository.cs

@ -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();
}
}
}

18
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanEfCoreQuerableExtensions.cs

@ -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
;
}
}

20
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/EfCoreFlashSaleResultRepository.cs

@ -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();
}
}

16
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultEfCoreQuerableExtensions.cs

@ -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;
}
}

3
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore/FodyWeavers.xml

@ -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…
Cancel
Save