diff --git a/Directory.Build.props b/Directory.Build.props
index 5b73325b..9cfc41d2 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -5,6 +5,8 @@
2.5.3
2.0.11
1.0.0
+ 1.7.0
+ 3.6.2
\ No newline at end of file
diff --git a/EShop.sln b/EShop.sln
index 7baee88f..33280ad8 100644
--- a/EShop.sln
+++ b/EShop.sln
@@ -341,6 +341,40 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Coupo
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}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Inventories", "Inventories", "{9AC27747-E175-487F-92C9-434DEE543273}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DaprActors", "DaprActors", "{6E6FE4B9-4117-4F57-B219-EE47E4046096}"
+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}"
+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}"
+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}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F468A386-5660-4888-981A-6ECF15182D32}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{485204B1-7603-4EA0-B3A4-73CB89B0D5BC}"
+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}"
+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}"
+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}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OrleansGrains", "OrleansGrains", "{88D17635-75D7-48A1-B622-E6FB3DCACEF8}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8E978749-7972-4703-8A94-6A90080C78DE}"
+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}"
+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}"
+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}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{F58B6EEF-5AFF-4B79-BC71-A2D8C71F5E77}"
+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}"
+EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Booking", "Booking", "{CE945F1D-6636-47D5-A619-C16C4E14CF8C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A8C4583C-034E-47AF-B7EC-1A34EE288E2F}"
@@ -943,6 +977,46 @@ Global
{3C385657-8365-470F-9F4F-30F31F9FCA42}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3C385657-8365-470F-9F4F-30F31F9FCA42}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3C385657-8365-470F-9F4F-30F31F9FCA42}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6CD1A8B5-8AB7-4A31-8333-024A7FB602D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6CD1A8B5-8AB7-4A31-8333-024A7FB602D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6CD1A8B5-8AB7-4A31-8333-024A7FB602D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6CD1A8B5-8AB7-4A31-8333-024A7FB602D1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CF4DE32D-9629-4C48-9BE8-5B83A1C27291}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CF4DE32D-9629-4C48-9BE8-5B83A1C27291}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CF4DE32D-9629-4C48-9BE8-5B83A1C27291}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CF4DE32D-9629-4C48-9BE8-5B83A1C27291}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B6F3ACD5-463E-4455-A094-057A82941A94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B6F3ACD5-463E-4455-A094-057A82941A94}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B6F3ACD5-463E-4455-A094-057A82941A94}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B6F3ACD5-463E-4455-A094-057A82941A94}.Release|Any CPU.Build.0 = Release|Any CPU
+ {733C51A3-19C8-45C4-8B22-3FD40CAF4EFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {733C51A3-19C8-45C4-8B22-3FD40CAF4EFB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {733C51A3-19C8-45C4-8B22-3FD40CAF4EFB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {733C51A3-19C8-45C4-8B22-3FD40CAF4EFB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3F0EA314-CCF4-4BB2-A8C1-79FAE4442884}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3F0EA314-CCF4-4BB2-A8C1-79FAE4442884}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3F0EA314-CCF4-4BB2-A8C1-79FAE4442884}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3F0EA314-CCF4-4BB2-A8C1-79FAE4442884}.Release|Any CPU.Build.0 = Release|Any CPU
+ {83F6434F-74DC-4389-870D-46510E28C029}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {83F6434F-74DC-4389-870D-46510E28C029}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {83F6434F-74DC-4389-870D-46510E28C029}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {83F6434F-74DC-4389-870D-46510E28C029}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AB3477DB-3457-4167-A086-BAD104D69604}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AB3477DB-3457-4167-A086-BAD104D69604}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AB3477DB-3457-4167-A086-BAD104D69604}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AB3477DB-3457-4167-A086-BAD104D69604}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0D613460-A0AD-4EAF-B719-785FE65E97E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0D613460-A0AD-4EAF-B719-785FE65E97E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0D613460-A0AD-4EAF-B719-785FE65E97E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0D613460-A0AD-4EAF-B719-785FE65E97E8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DB1C55BF-0C0D-488C-9AFC-992A3DED2EAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DB1C55BF-0C0D-488C-9AFC-992A3DED2EAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DB1C55BF-0C0D-488C-9AFC-992A3DED2EAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DB1C55BF-0C0D-488C-9AFC-992A3DED2EAD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D652EBF0-27CA-44C2-BB78-F446B87377C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D652EBF0-27CA-44C2-BB78-F446B87377C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D652EBF0-27CA-44C2-BB78-F446B87377C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D652EBF0-27CA-44C2-BB78-F446B87377C7}.Release|Any CPU.Build.0 = Release|Any CPU
{10C98582-61EB-49B9-9E6B-83E90CA3795D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{10C98582-61EB-49B9-9E6B-83E90CA3795D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{10C98582-61EB-49B9-9E6B-83E90CA3795D}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -1193,6 +1267,23 @@ Global
{86CAD303-A0E5-42C9-89A5-61D9AAA4AD8F} = {4001814E-A67B-490D-9E13-2FB9A34B0A0B}
{B076C103-DF0B-464B-A9CB-4BE5CAFEE067} = {4001814E-A67B-490D-9E13-2FB9A34B0A0B}
{3C385657-8365-470F-9F4F-30F31F9FCA42} = {72F34527-9295-4F29-923E-4B075A4F31A2}
+ {9AC27747-E175-487F-92C9-434DEE543273} = {94CC5A11-DA0F-413C-96CA-01DB0FC426E0}
+ {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}
+ {485204B1-7603-4EA0-B3A4-73CB89B0D5BC} = {6E6FE4B9-4117-4F57-B219-EE47E4046096}
+ {733C51A3-19C8-45C4-8B22-3FD40CAF4EFB} = {485204B1-7603-4EA0-B3A4-73CB89B0D5BC}
+ {3F0EA314-CCF4-4BB2-A8C1-79FAE4442884} = {F468A386-5660-4888-981A-6ECF15182D32}
+ {88D17635-75D7-48A1-B622-E6FB3DCACEF8} = {9AC27747-E175-487F-92C9-434DEE543273}
+ {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}
+ {0D613460-A0AD-4EAF-B719-785FE65E97E8} = {8E978749-7972-4703-8A94-6A90080C78DE}
+ {DB1C55BF-0C0D-488C-9AFC-992A3DED2EAD} = {8E978749-7972-4703-8A94-6A90080C78DE}
+ {F58B6EEF-5AFF-4B79-BC71-A2D8C71F5E77} = {88D17635-75D7-48A1-B622-E6FB3DCACEF8}
+ {D652EBF0-27CA-44C2-BB78-F446B87377C7} = {F58B6EEF-5AFF-4B79-BC71-A2D8C71F5E77}
{CE945F1D-6636-47D5-A619-C16C4E14CF8C} = {94CC5A11-DA0F-413C-96CA-01DB0FC426E0}
{A8C4583C-034E-47AF-B7EC-1A34EE288E2F} = {CE945F1D-6636-47D5-A619-C16C4E14CF8C}
{10C98582-61EB-49B9-9E6B-83E90CA3795D} = {A8C4583C-034E-47AF-B7EC-1A34EE288E2F}
diff --git a/EShop.sln.DotSettings b/EShop.sln.DotSettings
index a1a9e93c..7b7d4176 100644
--- a/EShop.sln.DotSettings
+++ b/EShop.sln.DotSettings
@@ -21,4 +21,5 @@
False
SQL
True
+ True
True
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
index b5c61e25..246fd767 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -48,7 +48,7 @@ We have launched an online demo for this module: [https://eshop.samples.easyabp.
* Define a Product Group (optional)
* Product group is used to classify different types of products, so we can customize different behavior for them, for example, products of the "GiftCard" product group could automatically send the card number and password to the customer's mailbox.
- * EShop provides a [default product group](https://github.com/EasyAbp/EShop/blob/master/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/EShopProductsDomainModule.cs#L29-L36).
+ * EShop provides a [default product group](https://github.com/EasyAbp/EShop/blob/master/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/EShopProductsDomainModule.cs#L33-L37).
* Refer to the configuration of the default product group and define a new product group.
* Create a Product
@@ -102,6 +102,9 @@ We can customize some features to use EShop in complex application scenarios.
* Plugin modules
* Baskets
* Coupons
+ * Inventories
+ * [DaprActors](https://github.com/EasyAbp/EShop/tree/dev/plugins/Inventories/DaprActors)
+ * [OrleansGrains](https://github.com/EasyAbp/EShop/tree/dev/plugins/Inventories/OrleansGrains)
* [Booking](https://github.com/EasyAbp/EShop/tree/dev/plugins/Booking)
## Roadmap
diff --git a/docs/plugins/inventories/dapr-actors/README.md b/docs/plugins/inventories/dapr-actors/README.md
new file mode 100644
index 00000000..ef24e465
--- /dev/null
+++ b/docs/plugins/inventories/dapr-actors/README.md
@@ -0,0 +1,40 @@
+# EShop.Plugins.Inventories.DaprActors
+
+[](https://abp.io)
+[](https://www.nuget.org/packages/EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions)
+[](https://www.nuget.org/packages/EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions)
+[](https://discord.gg/S6QaezrCRq)
+[](https://www.github.com/EasyAbp/EShop)
+
+EShop product-inventory implementation of [Dapr Actors](https://docs.dapr.io/developing-applications/building-blocks/actors/actors-overview).
+
+## 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.Products.DaprActorsInventory.Domain _(install at EasyAbp.EShop.Products.Domain location)_
+ * EasyAbp.EShop.Plugins.Inventories.DaprActors.AspNetCore _(install at a host project to run Actors)_
+
+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. Configure a state store for the inventory actor. ([see how](https://docs.dapr.io/reference/api/state_api/#configuring-state-store-for-actors))
+
+## Usage
+
+1. Configure the DaprActors inventory provider as default.
+ ```csharp
+ Configure(options =>
+ {
+ // Configure as the default inventory provider
+ options.DefaultInventoryProviderName = "DaprActors";
+
+ // Configure as the default inventory provider for MyProductGroup
+ options.Groups.Configure(group =>
+ {
+ group.DefaultInventoryProviderName = "DaprActors";
+ });
+ });
+ ```
+ > Better to use `DaprActorsProductInventoryProvider.DaprActorsProductInventoryProviderName` instead of `"DaprActors"` as the provider name.
+
+2. Create a product and set `InventoryProviderName` to `DaprActors`. Then the product is specified to use the Dapr Actors inventory provider.
diff --git a/docs/plugins/inventories/orleans-grains/README.md b/docs/plugins/inventories/orleans-grains/README.md
new file mode 100644
index 00000000..ff92953b
--- /dev/null
+++ b/docs/plugins/inventories/orleans-grains/README.md
@@ -0,0 +1,51 @@
+# EShop.Plugins.Inventories.OrleansGrains
+
+[](https://abp.io)
+[](https://www.nuget.org/packages/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions)
+[](https://www.nuget.org/packages/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions)
+[](https://discord.gg/S6QaezrCRq)
+[](https://www.github.com/EasyAbp/EShop)
+
+EShop product-inventory implementation of [Orleans Grains](https://docs.microsoft.com/en-us/dotnet/orleans/grains).
+
+## 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.Products.OrleansGrainsInventory.Domain _(install at EasyAbp.EShop.Products.Domain location)_
+ * EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo _(install at a host project to run Grains)_
+
+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. Open `Program.cs` in the host project to create an Orleans Silo. (see Microsoft's [document](https://docs.microsoft.com/en-us/dotnet/orleans/host/configuration-guide/server-configuration) for more information)
+
+ ```csharp
+ builder.Host.AddAppSettingsSecretsJson()
+ .UseAutofac()
+ .UseSerilog()
+ .UseOrleans(c =>
+ {
+ c.UseLocalhostClustering() // for test only
+ c.AddMemoryGrainStorage(InventoryGrain.StorageProviderName); // for test only
+ });
+ ```
+
+## Usage
+
+1. Configure the OrleansGrains inventory provider as default.
+ ```csharp
+ Configure(options =>
+ {
+ // Configure as the default inventory provider
+ options.DefaultInventoryProviderName = "OrleansGrains";
+
+ // Configure as the default inventory provider for MyProductGroup
+ options.Groups.Configure(group =>
+ {
+ group.DefaultInventoryProviderName = "OrleansGrains";
+ });
+ });
+ ```
+ > Better to use `OrleansGrainsProductInventoryProvider.OrleansGrainsProductInventoryProviderName` instead of `"OrleansGrains"` as the provider name.
+
+2. Create a product and set `InventoryProviderName` to `OrleansGrains`. Then the product is specified to use the Orleans Grains inventory provider.
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/OrdersConsts.cs b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/OrdersConsts.cs
index 051299d7..44ea4412 100644
--- a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/OrdersConsts.cs
+++ b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/OrdersConsts.cs
@@ -2,6 +2,7 @@
{
public static class OrdersConsts
{
- public static string CancellationReason = "Order payment timed out and not paid";
+ public static string UnpaidAutoCancellationReason = "Order payment timed out and not paid";
+ public static string InventoryReductionFailedAutoCancellationReason = "Insufficient inventory";
}
}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderAutoCancelOnCreatedHandler.cs b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderAutoCancelOnCreatedHandler.cs
index 69ad84f7..98924395 100644
--- a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderAutoCancelOnCreatedHandler.cs
+++ b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderAutoCancelOnCreatedHandler.cs
@@ -3,6 +3,7 @@ using Volo.Abp.BackgroundJobs;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.EventBus.Distributed;
+using Volo.Abp.Timing;
namespace EasyAbp.EShop.Orders.Orders
{
@@ -10,14 +11,17 @@ namespace EasyAbp.EShop.Orders.Orders
IDistributedEventHandler>,
ITransientDependency
{
+ private readonly IClock _clock;
private readonly IBackgroundJobManager _backgroundJobManager;
public OrderAutoCancelOnCreatedHandler(
+ IClock clock,
IBackgroundJobManager backgroundJobManager)
{
+ _clock = clock;
_backgroundJobManager = backgroundJobManager;
}
-
+
public virtual async Task HandleEventAsync(EntityCreatedEto eventData)
{
if (!eventData.Entity.PaymentExpiration.HasValue)
@@ -33,7 +37,7 @@ namespace EasyAbp.EShop.Orders.Orders
await _backgroundJobManager.EnqueueAsync(
args: args,
- delay: eventData.Entity.PaymentExpiration.Value.Subtract(eventData.Entity.CreationTime) // Todo: use a absolute time.
+ delay: eventData.Entity.PaymentExpiration.Value.Subtract(_clock.Now) // Todo: use a absolute time.
);
}
}
diff --git a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderAutoCancelOnUpdatedHandler.cs b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderAutoCancelOnUpdatedHandler.cs
index 93d77f0d..7297e668 100644
--- a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderAutoCancelOnUpdatedHandler.cs
+++ b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderAutoCancelOnUpdatedHandler.cs
@@ -32,7 +32,7 @@ namespace EasyAbp.EShop.Orders.Orders
if (_clock.Now > eventData.Entity.PaymentExpiration.Value && !eventData.Entity.CanceledTime.HasValue)
{
- await _orderManager.CancelAsync(eventData.Entity, OrdersConsts.CancellationReason);
+ await _orderManager.CancelAsync(eventData.Entity, OrdersConsts.UnpaidAutoCancellationReason);
}
}
}
diff --git a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderManager.cs b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderManager.cs
index 9d9fdc91..912b968f 100644
--- a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderManager.cs
+++ b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderManager.cs
@@ -56,7 +56,6 @@ namespace EasyAbp.EShop.Orders.Orders
return order;
}
- // Todo: should handler the inventory rollback.
[UnitOfWork]
public virtual async Task CancelAsync(Order order, string cancellationReason)
{
diff --git a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/ProductInventoryReductionEventHandler.cs b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/ProductInventoryReductionEventHandler.cs
index 35c07fe5..2e126164 100644
--- a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/ProductInventoryReductionEventHandler.cs
+++ b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/ProductInventoryReductionEventHandler.cs
@@ -1,6 +1,12 @@
-using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using EasyAbp.EShop.Payments.Refunds;
using EasyAbp.EShop.Products.Products;
+using EasyAbp.PaymentService.Refunds;
+using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
+using Volo.Abp.EventBus.Distributed;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Timing;
using Volo.Abp.Uow;
@@ -11,18 +17,24 @@ namespace EasyAbp.EShop.Orders.Orders
{
private readonly IClock _clock;
private readonly ICurrentTenant _currentTenant;
+ private readonly IOrderManager _orderManager;
private readonly IOrderRepository _orderRepository;
+ private readonly IDistributedEventBus _distributedEventBus;
public ProductInventoryReductionEventHandler(
IClock clock,
ICurrentTenant currentTenant,
- IOrderRepository orderRepository)
+ IOrderManager orderManager,
+ IOrderRepository orderRepository,
+ IDistributedEventBus distributedEventBus)
{
_clock = clock;
_currentTenant = currentTenant;
+ _orderManager = orderManager;
_orderRepository = orderRepository;
+ _distributedEventBus = distributedEventBus;
}
-
+
[UnitOfWork(true)]
public virtual async Task HandleEventAsync(ProductInventoryReductionAfterOrderPlacedResultEto eventData)
{
@@ -37,10 +49,11 @@ namespace EasyAbp.EShop.Orders.Orders
if (!eventData.IsSuccess)
{
- // Todo: Cancel order.
+ await _orderManager.CancelAsync(order, OrdersConsts.InventoryReductionFailedAutoCancellationReason);
+
return;
}
-
+
order.SetReducedInventoryAfterPlacingTime(_clock.Now);
await _orderRepository.UpdateAsync(order, true);
@@ -61,15 +74,58 @@ namespace EasyAbp.EShop.Orders.Orders
if (!eventData.IsSuccess)
{
- // Todo: Refund.
- // Todo: Cancel order.
+ var refundOrderEto = CreateRefundOrderEto(order);
+
+ await _orderManager.CancelAsync(order, OrdersConsts.InventoryReductionFailedAutoCancellationReason);
+
+ await RefundOrderAsync(refundOrderEto);
+
return;
}
-
+
order.SetReducedInventoryAfterPaymentTime(_clock.Now);
await _orderRepository.UpdateAsync(order, true);
}
}
+
+ [UnitOfWork(true)]
+ protected virtual async Task RefundOrderAsync(RefundOrderEto refundOrderEto)
+ {
+ await _distributedEventBus.PublishAsync(refundOrderEto);
+ }
+
+ protected virtual RefundOrderEto CreateRefundOrderEto(Order order)
+ {
+ if (!order.PaymentId.HasValue)
+ {
+ throw new OrderIsInWrongStageException(order.Id);
+ }
+
+ var eto = new RefundOrderEto(
+ order.TenantId,
+ order.Id,
+ order.StoreId,
+ order.PaymentId.Value,
+ OrdersConsts.InventoryReductionFailedAutoCancellationReason,
+ OrdersConsts.InventoryReductionFailedAutoCancellationReason,
+ OrdersConsts.InventoryReductionFailedAutoCancellationReason);
+
+ eto.OrderLines.AddRange(order.OrderLines.Select(x => new OrderLineRefundInfoModel
+ {
+ OrderLineId = x.Id,
+ Quantity = x.Quantity - x.RefundedQuantity,
+ TotalAmount = x.ActualTotalPrice - x.RefundAmount
+ }));
+
+ eto.OrderExtraFees.AddRange(order.OrderExtraFees.Select(x => new OrderExtraFeeRefundInfoModel
+ {
+ Name = x.Name,
+ Key = x.Key,
+ TotalAmount = x.Fee - x.RefundAmount
+ }));
+
+ return eto;
+ }
}
}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/UnpaidOrderAutoCancelJob.cs b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/UnpaidOrderAutoCancelJob.cs
index 7fd78888..71b26ac1 100644
--- a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/UnpaidOrderAutoCancelJob.cs
+++ b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/UnpaidOrderAutoCancelJob.cs
@@ -46,7 +46,7 @@ namespace EasyAbp.EShop.Orders.Orders
}
else
{
- await _orderManager.CancelAsync(order, OrdersConsts.CancellationReason);
+ await _orderManager.CancelAsync(order, OrdersConsts.UnpaidAutoCancellationReason);
}
}
}
diff --git a/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/OrderAppServiceTests.cs b/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/OrderAppServiceTests.cs
index 885afd26..18e5b70b 100644
--- a/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/OrderAppServiceTests.cs
+++ b/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/OrderAppServiceTests.cs
@@ -64,6 +64,17 @@ namespace EasyAbp.EShop.Orders.Orders
Price = 2m,
Currency = "CNY",
ProductDetailId = OrderTestData.ProductDetail2Id
+ },
+ new ProductSkuDto
+ {
+ Id = OrderTestData.ProductSku3Id,
+ Name = "My SKU 3",
+ OrderMinQuantity = 0,
+ OrderMaxQuantity = 100,
+ AttributeOptionIds = new List(),
+ Price = 3m,
+ Currency = "CNY",
+ ProductDetailId = OrderTestData.ProductDetail2Id
}
},
InventoryStrategy = InventoryStrategy.NoNeed,
@@ -278,7 +289,7 @@ namespace EasyAbp.EShop.Orders.Orders
response.PaymentExpiration.ShouldBe(now);
response.OrderStatus.ShouldBe(OrderStatus.Canceled);
response.CanceledTime.ShouldNotBeNull();
- response.CancellationReason.ShouldBe(OrdersConsts.CancellationReason);
+ response.CancellationReason.ShouldBe(OrdersConsts.UnpaidAutoCancellationReason);
UsingDbContext(db =>
{
@@ -288,7 +299,7 @@ namespace EasyAbp.EShop.Orders.Orders
order.PaymentExpiration.ShouldBe(now);
order.OrderStatus.ShouldBe(OrderStatus.Canceled);
order.CanceledTime.ShouldNotBeNull();
- order.CancellationReason.ShouldBe(OrdersConsts.CancellationReason);
+ order.CancellationReason.ShouldBe(OrdersConsts.UnpaidAutoCancellationReason);
});
}
@@ -392,7 +403,7 @@ namespace EasyAbp.EShop.Orders.Orders
response.PaymentExpiration.ShouldBe(now);
response.OrderStatus.ShouldBe(OrderStatus.Canceled);
response.CanceledTime.ShouldNotBeNull();
- response.CancellationReason.ShouldBe(OrdersConsts.CancellationReason);
+ response.CancellationReason.ShouldBe(OrdersConsts.UnpaidAutoCancellationReason);
UsingDbContext(db =>
{
@@ -402,7 +413,7 @@ namespace EasyAbp.EShop.Orders.Orders
order.PaymentExpiration.ShouldBe(now);
order.OrderStatus.ShouldBe(OrderStatus.Canceled);
order.CanceledTime.ShouldNotBeNull();
- order.CancellationReason.ShouldBe(OrdersConsts.CancellationReason);
+ order.CancellationReason.ShouldBe(OrdersConsts.UnpaidAutoCancellationReason);
});
}
@@ -449,7 +460,7 @@ namespace EasyAbp.EShop.Orders.Orders
response.PaymentExpiration.ShouldBe(now);
response.OrderStatus.ShouldBe(OrderStatus.Canceled);
response.CanceledTime.ShouldNotBeNull();
- response.CancellationReason.ShouldBe(OrdersConsts.CancellationReason);
+ response.CancellationReason.ShouldBe(OrdersConsts.UnpaidAutoCancellationReason);
UsingDbContext(db =>
{
@@ -459,7 +470,7 @@ namespace EasyAbp.EShop.Orders.Orders
order.PaymentExpiration.ShouldBe(now);
order.OrderStatus.ShouldBe(OrderStatus.Canceled);
order.CanceledTime.ShouldNotBeNull();
- order.CancellationReason.ShouldBe(OrdersConsts.CancellationReason);
+ order.CancellationReason.ShouldBe(OrdersConsts.UnpaidAutoCancellationReason);
});
}
@@ -481,7 +492,7 @@ namespace EasyAbp.EShop.Orders.Orders
new()
{
ProductId = OrderTestData.Product1Id,
- ProductSkuId = OrderTestData.ProductSku2Id,
+ ProductSkuId = OrderTestData.ProductSku3Id,
Quantity = 2
}
}
@@ -490,11 +501,11 @@ namespace EasyAbp.EShop.Orders.Orders
await WithUnitOfWorkAsync(async () =>
{
var order = await _orderAppService.CreateAsync(createOrderDto);
- var orderLine = order.OrderLines.Find(x => x.ProductSkuId == OrderTestData.ProductSku2Id);
+ var orderLine = order.OrderLines.Find(x => x.ProductSkuId == OrderTestData.ProductSku3Id);
- order.ProductTotalPrice.ShouldBe(10 * 1m + 2 * TestOrderLinePriceOverrider.Sku2UnitPrice);
+ order.ProductTotalPrice.ShouldBe(10 * 1m + 2 * TestOrderLinePriceOverrider.Sku3UnitPrice);
orderLine.ShouldNotBeNull();
- orderLine.UnitPrice.ShouldBe(TestOrderLinePriceOverrider.Sku2UnitPrice);
+ orderLine.UnitPrice.ShouldBe(TestOrderLinePriceOverrider.Sku3UnitPrice);
orderLine.TotalPrice.ShouldBe(orderLine.Quantity * orderLine.UnitPrice);
});
}
diff --git a/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/TestOrderLinePriceOverrider.cs b/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/TestOrderLinePriceOverrider.cs
index 22d4c016..5d580aec 100644
--- a/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/TestOrderLinePriceOverrider.cs
+++ b/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/TestOrderLinePriceOverrider.cs
@@ -7,14 +7,14 @@ namespace EasyAbp.EShop.Orders.Orders;
public class TestOrderLinePriceOverrider : IOrderLinePriceOverrider, ITransientDependency
{
- public static decimal Sku2UnitPrice { get; set; } = 100m;
+ public static decimal Sku3UnitPrice { get; set; } = 100m;
public async Task GetUnitPriceOrNullAsync(CreateOrderDto input, CreateOrderLineDto inputOrderLine,
ProductDto product, ProductSkuDto productSku)
{
- if (inputOrderLine.ProductSkuId == OrderTestData.ProductSku2Id)
+ if (inputOrderLine.ProductSkuId == OrderTestData.ProductSku3Id)
{
- return Sku2UnitPrice;
+ return Sku3UnitPrice;
}
return null;
diff --git a/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/InventoryReductionResultTests.cs b/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/InventoryReductionResultTests.cs
new file mode 100644
index 00000000..756c9eca
--- /dev/null
+++ b/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/InventoryReductionResultTests.cs
@@ -0,0 +1,192 @@
+using System;
+using System.Threading.Tasks;
+using EasyAbp.EShop.Products.Products;
+using Microsoft.Extensions.DependencyInjection;
+using NSubstitute;
+using Shouldly;
+using Xunit;
+
+namespace EasyAbp.EShop.Orders.Orders;
+
+public class InventoryReductionResultTests : OrdersDomainTestBase
+{
+ private Order Order1 { get; set; }
+
+ protected override void AfterAddApplication(IServiceCollection services)
+ {
+ var orderRepository = Substitute.For();
+ Order1 = new Order(
+ OrderTestData.Order1Id,
+ null,
+ OrderTestData.Store1Id,
+ Guid.NewGuid(),
+ "CNY",
+ 1m,
+ 0m,
+ 1.5m,
+ 1.5m,
+ null,
+ null);
+ Order1.OrderLines.Add(new OrderLine(
+ OrderTestData.OrderLine1Id,
+ OrderTestData.Product1Id,
+ OrderTestData.ProductSku1Id,
+ null,
+ DateTime.Now,
+ null,
+ "Default",
+ "Default",
+ null,
+ "Product 1",
+ null,
+ null,
+ null,
+ "CNY",
+ 0.5m,
+ 1m,
+ 0m,
+ 1m,
+ 2
+ ));
+ Order1.OrderExtraFees.Add(new OrderExtraFee(
+ OrderTestData.Order1Id,
+ "Name",
+ "Key",
+ 0.3m
+ ));
+
+ orderRepository.GetAsync(OrderTestData.Order1Id).Returns(Task.FromResult(Order1));
+
+ services.AddTransient(_ => orderRepository);
+ }
+
+ [Fact]
+ public async Task Should_Cancel_Order_If_Reduction_Failed_After_Placed()
+ {
+ typeof(Order).GetProperty(nameof(Order.CanceledTime))!.SetValue(Order1, null);
+ typeof(Order).GetProperty(nameof(Order.CancellationReason))!.SetValue(Order1, null);
+ Order1.SetReducedInventoryAfterPlacingTime(null);
+ Order1.SetReducedInventoryAfterPaymentTime(null);
+ Order1.SetOrderStatus(OrderStatus.Pending);
+ Order1.SetPaymentId(null);
+ Order1.SetPaidTime(null);
+
+ var handler = ServiceProvider.GetRequiredService();
+
+ await handler.HandleEventAsync(new ProductInventoryReductionAfterOrderPlacedResultEto()
+ {
+ TenantId = null,
+ OrderId = OrderTestData.Order1Id,
+ IsSuccess = false
+ });
+
+ Order1.CanceledTime.ShouldNotBeNull();
+ Order1.CancellationReason.ShouldBe(OrdersConsts.InventoryReductionFailedAutoCancellationReason);
+ Order1.ReducedInventoryAfterPlacingTime.ShouldBeNull();
+ Order1.ReducedInventoryAfterPaymentTime.ShouldBeNull();
+ }
+
+ [Fact]
+ public async Task Should_Not_Cancel_Order_If_Reduction_Succeeded_After_Placed()
+ {
+ typeof(Order).GetProperty(nameof(Order.CanceledTime))!.SetValue(Order1, null);
+ typeof(Order).GetProperty(nameof(Order.CancellationReason))!.SetValue(Order1, null);
+ Order1.SetReducedInventoryAfterPlacingTime(null);
+ Order1.SetReducedInventoryAfterPaymentTime(null);
+ Order1.SetOrderStatus(OrderStatus.Pending);
+ Order1.SetPaymentId(null);
+ Order1.SetPaidTime(null);
+
+ var handler = ServiceProvider.GetRequiredService();
+
+ await handler.HandleEventAsync(new ProductInventoryReductionAfterOrderPlacedResultEto()
+ {
+ TenantId = null,
+ OrderId = OrderTestData.Order1Id,
+ IsSuccess = true
+ });
+
+ Order1.CanceledTime.ShouldBeNull();
+ Order1.CancellationReason.ShouldBeNull();
+ Order1.ReducedInventoryAfterPlacingTime.ShouldNotBeNull();
+ Order1.ReducedInventoryAfterPaymentTime.ShouldBeNull();
+ }
+
+ [Fact]
+ public async Task Should_Cancel_Order_And_Refund_If_Reduction_Failed_After_Paid()
+ {
+ typeof(Order).GetProperty(nameof(Order.CanceledTime))!.SetValue(Order1, null);
+ typeof(Order).GetProperty(nameof(Order.CancellationReason))!.SetValue(Order1, null);
+ Order1.SetReducedInventoryAfterPlacingTime(DateTime.Now);
+ Order1.SetReducedInventoryAfterPaymentTime(null);
+ Order1.SetOrderStatus(OrderStatus.Processing);
+ Order1.SetPaymentId(OrderTestData.Payment1Id);
+ Order1.SetPaidTime(DateTime.Now);
+
+ var handler = ServiceProvider.GetRequiredService();
+
+ await handler.HandleEventAsync(new ProductInventoryReductionAfterOrderPaidResultEto()
+ {
+ TenantId = null,
+ OrderId = OrderTestData.Order1Id,
+ IsSuccess = false
+ });
+
+ var eventData = TestRefundOrderEventHandler.LastEto;
+ TestRefundOrderEventHandler.LastEto = null;
+ eventData.ShouldNotBeNull();
+ eventData.DisplayReason.ShouldBe(OrdersConsts.InventoryReductionFailedAutoCancellationReason);
+ eventData.StaffRemark.ShouldBe(OrdersConsts.InventoryReductionFailedAutoCancellationReason);
+ eventData.CustomerRemark.ShouldBe(OrdersConsts.InventoryReductionFailedAutoCancellationReason);
+ eventData.PaymentId.ShouldBe(OrderTestData.Payment1Id);
+ eventData.TenantId.ShouldBeNull();
+ eventData.OrderId.ShouldBe(OrderTestData.Order1Id);
+
+ eventData.OrderLines.Count.ShouldBe(1);
+ var orderLine = eventData.OrderLines[0];
+ orderLine.OrderLineId.ShouldBe(OrderTestData.OrderLine1Id);
+ orderLine.Quantity.ShouldBe(2);
+ orderLine.TotalAmount.ShouldBe(1m);
+
+ eventData.OrderExtraFees.Count.ShouldBe(1);
+ var orderExtraFee = eventData.OrderExtraFees[0];
+ orderExtraFee.Name.ShouldBe("Name");
+ orderExtraFee.Key.ShouldBe("Key");
+ orderExtraFee.TotalAmount.ShouldBe(0.3m);
+
+ Order1.CanceledTime.ShouldNotBeNull();
+ Order1.CancellationReason.ShouldBe(OrdersConsts.InventoryReductionFailedAutoCancellationReason);
+ Order1.ReducedInventoryAfterPlacingTime.ShouldNotBeNull();
+ Order1.ReducedInventoryAfterPaymentTime.ShouldBeNull();
+ }
+
+ [Fact]
+ public async Task Should_Not_Cancel_And_Refund_Order_If_Reduction_Succeeded_After_Paid()
+ {
+ typeof(Order).GetProperty(nameof(Order.CanceledTime))!.SetValue(Order1, null);
+ typeof(Order).GetProperty(nameof(Order.CancellationReason))!.SetValue(Order1, null);
+ Order1.SetReducedInventoryAfterPlacingTime(DateTime.Now);
+ Order1.SetReducedInventoryAfterPaymentTime(null);
+ Order1.SetOrderStatus(OrderStatus.Processing);
+ Order1.SetPaymentId(OrderTestData.Payment1Id);
+ Order1.SetPaidTime(DateTime.Now);
+
+ var handler = ServiceProvider.GetRequiredService();
+
+ await handler.HandleEventAsync(new ProductInventoryReductionAfterOrderPaidResultEto()
+ {
+ TenantId = null,
+ OrderId = OrderTestData.Order1Id,
+ IsSuccess = true
+ });
+
+ var eventData = TestRefundOrderEventHandler.LastEto;
+ TestRefundOrderEventHandler.LastEto = null;
+ eventData.ShouldBeNull();
+
+ Order1.CanceledTime.ShouldBeNull();
+ Order1.CancellationReason.ShouldBeNull();
+ Order1.ReducedInventoryAfterPlacingTime.ShouldNotBeNull();
+ Order1.ReducedInventoryAfterPaymentTime.ShouldNotBeNull();
+ }
+}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/TestRefundOrderEventHandler.cs b/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/TestRefundOrderEventHandler.cs
new file mode 100644
index 00000000..52c6555b
--- /dev/null
+++ b/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/TestRefundOrderEventHandler.cs
@@ -0,0 +1,18 @@
+using System.Threading.Tasks;
+using EasyAbp.EShop.Payments.Refunds;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.EventBus.Distributed;
+
+namespace EasyAbp.EShop.Orders.Orders;
+
+public class TestRefundOrderEventHandler : IDistributedEventHandler, ITransientDependency
+{
+ public static RefundOrderEto LastEto { get; set; }
+
+ public Task HandleEventAsync(RefundOrderEto eventData)
+ {
+ LastEto = eventData;
+
+ return Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.TestBase/OrderTestData.cs b/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.TestBase/OrderTestData.cs
index a12c181d..403c6e02 100644
--- a/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.TestBase/OrderTestData.cs
+++ b/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.TestBase/OrderTestData.cs
@@ -6,25 +6,27 @@ namespace EasyAbp.EShop.Orders
public class OrderTestData
{
public static Guid Order1Id { get; } = Guid.NewGuid();
-
+
public static Guid OrderLine1Id { get; } = Guid.NewGuid();
-
+
public static Guid Payment1Id { get; } = Guid.NewGuid();
-
+
public static Guid Store1Id { get; } = Guid.NewGuid();
public static Guid Product1Id { get; } = Guid.NewGuid();
public static Guid ProductSku1Id { get; } = Guid.NewGuid();
-
+
public static Guid ProductSku2Id { get; } = Guid.NewGuid();
-
+
+ public static Guid ProductSku3Id { get; } = Guid.NewGuid();
+
public static Guid ProductDetail1Id { get; } = Guid.NewGuid();
-
+
public static Guid ProductDetail2Id { get; } = Guid.NewGuid();
-
+
public static DateTime ProductLastModificationTime { get; } = DateTime.Today;
-
+
public static DateTime ProductDetailLastModificationTime { get; } = DateTime.Today;
}
}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application.Contracts/EasyAbp/EShop/Payments/Refunds/Dtos/CreateEShopRefundItemInput.cs b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application.Contracts/EasyAbp/EShop/Payments/Refunds/Dtos/CreateEShopRefundItemInput.cs
index 0eb0c99b..2d211c63 100644
--- a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application.Contracts/EasyAbp/EShop/Payments/Refunds/Dtos/CreateEShopRefundItemInput.cs
+++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application.Contracts/EasyAbp/EShop/Payments/Refunds/Dtos/CreateEShopRefundItemInput.cs
@@ -1,23 +1,9 @@
using System;
-using System.Collections.Generic;
-using JetBrains.Annotations;
-using Volo.Abp.ObjectExtending;
namespace EasyAbp.EShop.Payments.Refunds.Dtos
{
[Serializable]
- public class CreateEShopRefundItemInput : ExtensibleObject
+ public class CreateEShopRefundItemInput : CreateEShopRefundItemInfoModel
{
- public Guid OrderId { get; set; }
-
- [CanBeNull]
- public string CustomerRemark { get; set; }
-
- [CanBeNull]
- public string StaffRemark { get; set; }
-
- public List OrderLines { get; set; } = new();
-
- public List OrderExtraFees { get; set; } = new();
}
}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/CreateEShopRefundItemInfoModel.cs b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/CreateEShopRefundItemInfoModel.cs
new file mode 100644
index 00000000..03176bf7
--- /dev/null
+++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/CreateEShopRefundItemInfoModel.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using JetBrains.Annotations;
+using Volo.Abp.ObjectExtending;
+
+namespace EasyAbp.EShop.Payments.Refunds;
+
+[Serializable]
+public class CreateEShopRefundItemInfoModel : ExtensibleObject
+{
+ public Guid OrderId { get; set; }
+
+ [CanBeNull]
+ public string CustomerRemark { get; set; }
+
+ [CanBeNull]
+ public string StaffRemark { get; set; }
+
+ public List OrderLines { get; set; } = new();
+
+ public List OrderExtraFees { get; set; } = new();
+}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/RefundOrderEto.cs b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/RefundOrderEto.cs
new file mode 100644
index 00000000..a594a7c3
--- /dev/null
+++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/RefundOrderEto.cs
@@ -0,0 +1,34 @@
+using System;
+using JetBrains.Annotations;
+using Volo.Abp.MultiTenancy;
+
+namespace EasyAbp.EShop.Payments.Refunds;
+
+[Serializable]
+public class RefundOrderEto : CreateEShopRefundItemInfoModel, IMultiTenant
+{
+ public Guid? TenantId { get; set; }
+
+ public Guid StoreId { get; set; }
+
+ public Guid PaymentId { get; set; }
+
+ [CanBeNull]
+ public string DisplayReason { get; set; }
+
+ protected RefundOrderEto()
+ {
+ }
+
+ public RefundOrderEto(Guid? tenantId, Guid orderId, Guid storeId, Guid paymentId, [CanBeNull] string displayReason,
+ [CanBeNull] string customerRemark, [CanBeNull] string staffRemark)
+ {
+ TenantId = tenantId;
+ OrderId = orderId;
+ StoreId = storeId;
+ PaymentId = paymentId;
+ DisplayReason = displayReason;
+ CustomerRemark = customerRemark;
+ StaffRemark = staffRemark;
+ }
+}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/Refunds/RefundOrderEventHandler.cs b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/Refunds/RefundOrderEventHandler.cs
new file mode 100644
index 00000000..76b9d6a1
--- /dev/null
+++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/Refunds/RefundOrderEventHandler.cs
@@ -0,0 +1,65 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using EasyAbp.EShop.Payments.Payments;
+using EasyAbp.PaymentService.Payments;
+using EasyAbp.PaymentService.Refunds;
+using Volo.Abp.Data;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.EventBus.Distributed;
+using Volo.Abp.Json;
+
+namespace EasyAbp.EShop.Payments.Refunds;
+
+public class RefundOrderEventHandler : IDistributedEventHandler, ITransientDependency
+{
+ private readonly IJsonSerializer _jsonSerializer;
+ private readonly IPaymentRepository _paymentRepository;
+ private readonly IDistributedEventBus _distributedEventBus;
+
+ public RefundOrderEventHandler(
+ IJsonSerializer jsonSerializer,
+ IPaymentRepository paymentRepository,
+ IDistributedEventBus distributedEventBus)
+ {
+ _jsonSerializer = jsonSerializer;
+ _paymentRepository = paymentRepository;
+ _distributedEventBus = distributedEventBus;
+ }
+
+ public virtual async Task HandleEventAsync(RefundOrderEto eventData)
+ {
+ var refundAmount = eventData.OrderLines.Sum(x => x.TotalAmount) +
+ eventData.OrderExtraFees.Sum(x => x.TotalAmount);
+
+ var payment = await _paymentRepository.GetAsync(eventData.PaymentId);
+
+ var paymentItem = payment.PaymentItems.Single(x => x.ItemKey == eventData.OrderId.ToString());
+
+ var createRefundItemInput = new CreateRefundItemInput
+ {
+ PaymentItemId = paymentItem.Id,
+ RefundAmount = refundAmount,
+ CustomerRemark = eventData.CustomerRemark,
+ StaffRemark = eventData.StaffRemark
+ };
+
+ createRefundItemInput.SetProperty(nameof(RefundItem.StoreId), eventData.StoreId);
+ createRefundItemInput.SetProperty(nameof(RefundItem.OrderId), eventData.OrderId);
+ createRefundItemInput.SetProperty(nameof(RefundItem.OrderLines),
+ _jsonSerializer.Serialize(eventData.OrderLines));
+ createRefundItemInput.SetProperty(nameof(RefundItem.OrderExtraFees),
+ _jsonSerializer.Serialize(eventData.OrderExtraFees));
+
+ var eto = new RefundPaymentEto(eventData.TenantId, new CreateRefundInput
+ {
+ PaymentId = eventData.PaymentId,
+ DisplayReason = eventData.DisplayReason,
+ CustomerRemark = eventData.CustomerRemark,
+ StaffRemark = eventData.StaffRemark,
+ RefundItems = new List { createRefundItemInput }
+ });
+
+ await _distributedEventBus.PublishAsync(eto);
+ }
+}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Application.Tests/Refunds/RefundAppServiceTests.cs b/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Application.Tests/Refunds/RefundAppServiceTests.cs
index 2e580825..7ab2f1a6 100644
--- a/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Application.Tests/Refunds/RefundAppServiceTests.cs
+++ b/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Application.Tests/Refunds/RefundAppServiceTests.cs
@@ -47,7 +47,8 @@ namespace EasyAbp.EShop.Payments.Refunds
paymentItemType.GetProperty(nameof(PaymentItem.ActualPaymentAmount))?.SetValue(paymentItem, 1m);
paymentItemType.GetProperty(nameof(PaymentItem.ItemType))?.SetValue(paymentItem, PaymentsConsts.PaymentItemType);
paymentItemType.GetProperty(nameof(PaymentItem.ItemKey))?.SetValue(paymentItem, PaymentsTestData.Order1.ToString());
- paymentItem.ExtraProperties.Add(nameof(paymentItem.StoreId), PaymentsTestData.Store1);
+ paymentItemType.GetProperty(nameof(PaymentItem.StoreId))?.SetValue(paymentItem, PaymentsTestData.Store1);
+ // paymentItem.ExtraProperties.Add(nameof(paymentItem.StoreId), PaymentsTestData.Store1);
var payment = Activator.CreateInstance(paymentType, true) as Payment;
payment.ShouldNotBeNull();
@@ -75,7 +76,9 @@ namespace EasyAbp.EShop.Payments.Refunds
?.SetValue(paymentItem, PaymentsConsts.PaymentItemType);
paymentItemType.GetProperty(nameof(PaymentItem.ItemKey))
?.SetValue(paymentItem, PaymentsTestData.Order1.ToString());
- paymentItem.ExtraProperties.Add(nameof(paymentItem.StoreId), PaymentsTestData.Store1);
+ paymentItemType.GetProperty(nameof(PaymentItem.StoreId))
+ ?.SetValue(paymentItem, PaymentsTestData.Store1);
+ // paymentItem.ExtraProperties.Add(nameof(paymentItem.StoreId), PaymentsTestData.Store1);
var payment = Activator.CreateInstance(paymentType, true) as Payment;
payment.ShouldNotBeNull();
@@ -182,10 +185,9 @@ namespace EasyAbp.EShop.Payments.Refunds
// Act & Assert
await _refundAppService.CreateAsync(request);
-
- _testRefundPaymentEventHandler.IsEventPublished.ShouldBe(true);
- var eventData = _testRefundPaymentEventHandler.EventData;
+ var eventData = TestRefundPaymentEventHandler.LastEto;
+ TestRefundPaymentEventHandler.LastEto = null;
eventData.ShouldNotBeNull();
eventData.CreateRefundInput.RefundItems.Count.ShouldBe(1);
diff --git a/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Domain.Tests/Refunds/RefundOrderEventHandlerTests.cs b/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Domain.Tests/Refunds/RefundOrderEventHandlerTests.cs
new file mode 100644
index 00000000..e1f7b468
--- /dev/null
+++ b/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Domain.Tests/Refunds/RefundOrderEventHandlerTests.cs
@@ -0,0 +1,116 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using EasyAbp.EShop.Payments.Payments;
+using Microsoft.Extensions.DependencyInjection;
+using NSubstitute;
+using NSubstitute.Core;
+using Shouldly;
+using Volo.Abp.Data;
+using Volo.Abp.Json;
+using Xunit;
+
+namespace EasyAbp.EShop.Payments.Refunds;
+
+public class RefundOrderEventHandlerTests : PaymentsDomainTestBase
+{
+ private readonly IJsonSerializer _jsonSerializer;
+
+ public RefundOrderEventHandlerTests()
+ {
+ _jsonSerializer = ServiceProvider.GetRequiredService();
+ }
+
+ protected override void AfterAddApplication(IServiceCollection services)
+ {
+ MockPaymentRepository(services);
+ }
+
+ private static void MockPaymentRepository(IServiceCollection services)
+ {
+ var paymentRepository = Substitute.For();
+
+ Payment Payment1Returns(CallInfo _)
+ {
+ var paymentType = typeof(Payment);
+ var paymentItemType = typeof(PaymentItem);
+
+ var paymentItem = Activator.CreateInstance(paymentItemType, true) as PaymentItem;
+ paymentItem.ShouldNotBeNull();
+ paymentItemType.GetProperty(nameof(PaymentItem.Id))?.SetValue(paymentItem, PaymentsTestData.PaymentItem1);
+ paymentItemType.GetProperty(nameof(PaymentItem.ActualPaymentAmount))?.SetValue(paymentItem, 1m);
+ paymentItemType.GetProperty(nameof(PaymentItem.ItemType))
+ ?.SetValue(paymentItem, PaymentsConsts.PaymentItemType);
+ paymentItemType.GetProperty(nameof(PaymentItem.ItemKey))
+ ?.SetValue(paymentItem, PaymentsTestData.Order1.ToString());
+ paymentItemType.GetProperty(nameof(PaymentItem.StoreId))
+ ?.SetValue(paymentItem, PaymentsTestData.Store1);
+
+ var payment = Activator.CreateInstance(paymentType, true) as Payment;
+ payment.ShouldNotBeNull();
+ paymentType.GetProperty(nameof(Payment.Id))?.SetValue(payment, PaymentsTestData.Payment1);
+ paymentType.GetProperty(nameof(Payment.Currency))?.SetValue(payment, "CNY");
+ paymentType.GetProperty(nameof(Payment.ActualPaymentAmount))?.SetValue(payment, 1m);
+ paymentType.GetProperty(nameof(Payment.PaymentItems))
+ ?.SetValue(payment, new List { paymentItem });
+
+ return payment;
+ }
+
+ paymentRepository.GetAsync(PaymentsTestData.Payment1).Returns(Payment1Returns);
+ paymentRepository.FindAsync(PaymentsTestData.Payment1).Returns(Payment1Returns);
+
+ services.AddTransient(_ => paymentRepository);
+ }
+
+ [Fact]
+ public async Task Should_Refund_Order()
+ {
+ var handler = ServiceProvider.GetRequiredService();
+
+ var eto = new RefundOrderEto(null, PaymentsTestData.Order1, PaymentsTestData.Store1,
+ PaymentsTestData.Payment1, "Test", null, null);
+
+ eto.OrderLines.Add(new OrderLineRefundInfoModel
+ {
+ OrderLineId = PaymentsTestData.OrderLine1,
+ Quantity = 2,
+ TotalAmount = 0.4m
+ });
+
+ eto.OrderExtraFees.Add(new OrderExtraFeeRefundInfoModel
+ {
+ Name = "Name",
+ Key = "Key",
+ TotalAmount = 0.6m
+ });
+
+ await handler.HandleEventAsync(eto);
+
+ var eventData = TestRefundPaymentEventHandler.LastEto;
+ TestRefundPaymentEventHandler.LastEto = null;
+ eventData.ShouldNotBeNull();
+ eventData.CreateRefundInput.RefundItems.Count.ShouldBe(1);
+
+ var refundItem = eventData.CreateRefundInput.RefundItems[0];
+ refundItem.GetProperty(nameof(RefundItem.OrderId)).ShouldBe(PaymentsTestData.Order1);
+
+ var orderLines =
+ _jsonSerializer.Deserialize>(
+ refundItem.GetProperty(nameof(RefundItem.OrderLines)));
+
+ orderLines.Count.ShouldBe(1);
+ orderLines[0].OrderLineId.ShouldBe(PaymentsTestData.OrderLine1);
+ orderLines[0].Quantity.ShouldBe(2);
+ orderLines[0].TotalAmount.ShouldBe(0.4m);
+
+ var orderExtraFees =
+ _jsonSerializer.Deserialize>(
+ refundItem.GetProperty(nameof(RefundItem.OrderExtraFees)));
+
+ orderExtraFees.Count.ShouldBe(1);
+ orderExtraFees[0].Name.ShouldBe("Name");
+ orderExtraFees[0].Key.ShouldBe("Key");
+ orderExtraFees[0].TotalAmount.ShouldBe(0.6m);
+ }
+}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Application.Tests/Refunds/TestRefundPaymentEventHandler.cs b/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Domain.Tests/Refunds/TestRefundPaymentEventHandler.cs
similarity index 58%
rename from modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Application.Tests/Refunds/TestRefundPaymentEventHandler.cs
rename to modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Domain.Tests/Refunds/TestRefundPaymentEventHandler.cs
index d84a9ef7..b71a2d06 100644
--- a/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Application.Tests/Refunds/TestRefundPaymentEventHandler.cs
+++ b/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Domain.Tests/Refunds/TestRefundPaymentEventHandler.cs
@@ -5,17 +5,14 @@ using Volo.Abp.EventBus.Distributed;
namespace EasyAbp.EShop.Payments.Refunds
{
- public class TestRefundPaymentEventHandler : IDistributedEventHandler, ISingletonDependency
+ public class TestRefundPaymentEventHandler : IDistributedEventHandler, ITransientDependency
{
- public bool IsEventPublished { get; protected set; }
-
- public RefundPaymentEto EventData { get; protected set; }
-
+ public static RefundPaymentEto LastEto { get; set; }
+
public Task HandleEventAsync(RefundPaymentEto eventData)
{
- IsEventPublished = true;
- EventData = eventData;
-
+ LastEto = eventData;
+
return Task.CompletedTask;
}
}
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/ProductInventories/Dtos/ProductInventoryDto.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/ProductInventories/Dtos/ProductInventoryDto.cs
deleted file mode 100644
index 4dc5ca9d..00000000
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/ProductInventories/Dtos/ProductInventoryDto.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-using Volo.Abp.Application.Dtos;
-
-namespace EasyAbp.EShop.Products.ProductInventories.Dtos
-{
- [Serializable]
- public class ProductInventoryDto : ExtensibleFullAuditedEntityDto
- {
- public Guid ProductId { get; set; }
-
- public Guid ProductSkuId { get; set; }
-
- public int Inventory { get; set; }
-
- public long Sold { get; set; }
- }
-}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/ProductInventories/Dtos/UpdateProductInventoryDto.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/ProductInventories/Dtos/UpdateProductInventoryDto.cs
deleted file mode 100644
index aa56c690..00000000
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/ProductInventories/Dtos/UpdateProductInventoryDto.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System;
-using Volo.Abp.ObjectExtending;
-
-namespace EasyAbp.EShop.Products.ProductInventories.Dtos
-{
- [Serializable]
- public class UpdateProductInventoryDto : ExtensibleObject
- {
- public Guid ProductId { get; set; }
-
- public Guid ProductSkuId { get; set; }
-
- ///
- /// Reduce inventory if the value is less than 0
- ///
- public int ChangedInventory { get; set; }
- }
-}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/ProductInventories/IProductInventoryAppService.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/ProductInventories/IProductInventoryAppService.cs
deleted file mode 100644
index 5da554be..00000000
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/ProductInventories/IProductInventoryAppService.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using System;
-using System.Threading.Tasks;
-using EasyAbp.EShop.Products.ProductInventories.Dtos;
-using Volo.Abp.Application.Dtos;
-using Volo.Abp.Application.Services;
-
-namespace EasyAbp.EShop.Products.ProductInventories
-{
- public interface IProductInventoryAppService : IApplicationService
- {
- Task GetAsync(Guid productId, Guid productSkuId);
-
- Task UpdateAsync(UpdateProductInventoryDto input);
- }
-}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ChangeProductInventoryDto.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ChangeProductInventoryDto.cs
new file mode 100644
index 00000000..52c139a4
--- /dev/null
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ChangeProductInventoryDto.cs
@@ -0,0 +1,13 @@
+using System;
+using Volo.Abp.ObjectExtending;
+
+namespace EasyAbp.EShop.Products.Products.Dtos;
+
+[Serializable]
+public class ChangeProductInventoryDto : ExtensibleObject
+{
+ ///
+ /// Reduce inventory if the value is less than 0
+ ///
+ public int ChangedInventory { get; set; }
+}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ChangeProductInventoryResultDto.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ChangeProductInventoryResultDto.cs
new file mode 100644
index 00000000..77d93312
--- /dev/null
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ChangeProductInventoryResultDto.cs
@@ -0,0 +1,14 @@
+using System;
+using Volo.Abp.ObjectExtending;
+
+namespace EasyAbp.EShop.Products.Products.Dtos;
+
+[Serializable]
+public class ChangeProductInventoryResultDto : ExtensibleObject
+{
+ public bool Changed { get; set; }
+
+ public int ChangedInventory { get; set; }
+
+ public int CurrentInventory { get; set; }
+}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/CreateUpdateProductDto.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/CreateUpdateProductDto.cs
index 763e533b..6301fa24 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/CreateUpdateProductDto.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/CreateUpdateProductDto.cs
@@ -34,7 +34,10 @@ namespace EasyAbp.EShop.Products.Products.Dtos
[DisplayName("ProductInventoryStrategy")]
public InventoryStrategy InventoryStrategy { get; set; }
-
+
+ [DisplayName("ProductInventoryProviderName")]
+ public string InventoryProviderName { get; set; }
+
[DisplayName("ProductDisplayOrder")]
public int DisplayOrder { get; set; }
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductDto.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductDto.cs
index d79f0aaa..344cd3e5 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductDto.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductDto.cs
@@ -22,6 +22,8 @@ namespace EasyAbp.EShop.Products.Products.Dtos
public InventoryStrategy InventoryStrategy { get; set; }
+ public string InventoryProviderName { get; set; }
+
public string MediaResources { get; set; }
public int DisplayOrder { get; set; }
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductViewDto.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductViewDto.cs
index c44d2320..d6ea59df 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductViewDto.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductViewDto.cs
@@ -19,6 +19,8 @@ namespace EasyAbp.EShop.Products.Products.Dtos
public InventoryStrategy InventoryStrategy { get; set; }
+ public string InventoryProviderName { get; set; }
+
public string MediaResources { get; set; }
public int DisplayOrder { get; set; }
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/IProductAppService.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/IProductAppService.cs
index 09790399..e0253224 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/IProductAppService.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/IProductAppService.cs
@@ -7,9 +7,9 @@ using Volo.Abp.Application.Services;
namespace EasyAbp.EShop.Products.Products
{
public interface IProductAppService :
- ICrudAppService<
- ProductDto,
- Guid,
+ ICrudAppService<
+ ProductDto,
+ Guid,
GetProductListInput,
CreateUpdateProductDto,
CreateUpdateProductDto>
@@ -19,9 +19,12 @@ namespace EasyAbp.EShop.Products.Products
Task UpdateSkuAsync(Guid productId, Guid productSkuId, UpdateProductSkuDto input);
Task DeleteSkuAsync(Guid productId, Guid productSkuId);
-
+
Task GetByUniqueNameAsync(Guid storeId, string uniqueName);
Task> GetProductGroupListAsync();
+
+ Task ChangeInventoryAsync(Guid id, Guid productSkuId,
+ ChangeProductInventoryDto input);
}
}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/ProductInventories/ProductInventoryAppService.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/ProductInventories/ProductInventoryAppService.cs
deleted file mode 100644
index 3d595c87..00000000
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/ProductInventories/ProductInventoryAppService.cs
+++ /dev/null
@@ -1,103 +0,0 @@
-using EasyAbp.EShop.Products.Permissions;
-using EasyAbp.EShop.Products.ProductInventories.Dtos;
-using EasyAbp.EShop.Products.Products;
-using EasyAbp.EShop.Stores.Authorization;
-using Microsoft.AspNetCore.Authorization;
-using System;
-using System.Threading.Tasks;
-using Volo.Abp.Application.Services;
-using Volo.Abp.Domain.Entities;
-
-namespace EasyAbp.EShop.Products.ProductInventories
-{
- public class ProductInventoryAppService : ApplicationService, IProductInventoryAppService
- {
- private readonly IProductRepository _productRepository;
- private readonly IProductInventoryRepository _repository;
- private readonly DefaultProductInventoryProvider _productInventoryProvider;
-
- public ProductInventoryAppService(
- IProductRepository productRepository,
- IProductInventoryRepository repository,
- DefaultProductInventoryProvider productInventoryProvider)
- {
- _productRepository = productRepository;
- _repository = repository;
- _productInventoryProvider = productInventoryProvider;
- }
-
- [Authorize(ProductsPermissions.ProductInventory.Default)]
- public virtual async Task GetAsync(Guid productId, Guid productSkuId)
- {
- var productInventory = await _repository.FindAsync(x => x.ProductSkuId == productSkuId);
-
- if (productInventory == null)
- {
- var product = await _productRepository.GetAsync(productId);
-
- if (!product.ProductSkus.Exists(x => x.Id == productSkuId))
- {
- throw new EntityNotFoundException(typeof(ProductSku), productSkuId);
- }
-
- productInventory = new ProductInventory(GuidGenerator.Create(), CurrentTenant.Id, productId,
- productSkuId, 0, 0);
-
- await _repository.InsertAsync(productInventory, true);
- }
-
- return ObjectMapper.Map(productInventory);
- }
-
- public virtual async Task UpdateAsync(UpdateProductInventoryDto input)
- {
- var product = await _productRepository.GetAsync(input.ProductId);
-
- if (!product.ProductSkus.Exists(x => x.Id == input.ProductSkuId))
- {
- throw new EntityNotFoundException(typeof(ProductSku), input.ProductSkuId);
- }
-
- await AuthorizationService.CheckMultiStorePolicyAsync(product.StoreId,
- ProductsPermissions.ProductInventory.Update, ProductsPermissions.ProductInventory.CrossStore);
-
- var productInventory = await _repository.FindAsync(x => x.ProductSkuId == input.ProductSkuId);
-
- if (productInventory == null)
- {
- productInventory =
- new ProductInventory(GuidGenerator.Create(), CurrentTenant.Id, input.ProductId, input.ProductSkuId,
- 0, 0);
-
- await _repository.InsertAsync(productInventory, true);
- }
-
- await ChangeInventoryAsync(product, productInventory, input.ChangedInventory);
-
- return ObjectMapper.Map(productInventory);
- }
-
- protected virtual async Task ChangeInventoryAsync(Product product, ProductInventory productInventory,
- int changedInventory)
- {
- if (changedInventory >= 0)
- {
- if (!await _productInventoryProvider.TryIncreaseInventoryAsync(product, productInventory,
- changedInventory, false))
- {
- throw new InventoryChangeFailedException(productInventory.ProductId, productInventory.ProductSkuId,
- productInventory.Inventory, changedInventory);
- }
- }
- else
- {
- if (!await _productInventoryProvider.TryReduceInventoryAsync(product, productInventory,
- -changedInventory, false))
- {
- throw new InventoryChangeFailedException(productInventory.ProductId, productInventory.ProductSkuId,
- productInventory.Inventory, changedInventory);
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/Products/ProductAppService.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/Products/ProductAppService.cs
index 495c62f1..0f9b052c 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/Products/ProductAppService.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/Products/ProductAppService.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EasyAbp.EShop.Products.Options;
+using EasyAbp.EShop.Products.ProductInventories;
using EasyAbp.EShop.Products.Products.CacheItems;
using EasyAbp.EShop.Stores.Stores;
using Microsoft.Extensions.Options;
@@ -14,7 +15,9 @@ using Volo.Abp.Domain.Entities;
namespace EasyAbp.EShop.Products.Products
{
- public class ProductAppService : MultiStoreCrudAppService, IProductAppService
+ public class ProductAppService :
+ MultiStoreCrudAppService, IProductAppService
{
protected override string CreatePolicyName { get; set; } = ProductsPermissions.Products.Create;
protected override string DeletePolicyName { get; set; } = ProductsPermissions.Products.Delete;
@@ -26,7 +29,7 @@ namespace EasyAbp.EShop.Products.Products
private readonly IProductManager _productManager;
private readonly IDistributedCache _cache;
private readonly EShopProductsOptions _options;
- private readonly IProductInventoryProvider _productInventoryProvider;
+ private readonly IProductInventoryProviderResolver _productInventoryProviderResolver;
private readonly IProductViewCacheKeyProvider _productViewCacheKeyProvider;
private readonly IAttributeOptionIdsSerializer _attributeOptionIdsSerializer;
private readonly IProductRepository _repository;
@@ -35,7 +38,7 @@ namespace EasyAbp.EShop.Products.Products
IProductManager productManager,
IOptions options,
IDistributedCache cache,
- IProductInventoryProvider productInventoryProvider,
+ IProductInventoryProviderResolver productInventoryProviderResolver,
IProductViewCacheKeyProvider productViewCacheKeyProvider,
IAttributeOptionIdsSerializer attributeOptionIdsSerializer,
IProductRepository repository) : base(repository)
@@ -43,7 +46,7 @@ namespace EasyAbp.EShop.Products.Products
_productManager = productManager;
_cache = cache;
_options = options.Value;
- _productInventoryProvider = productInventoryProvider;
+ _productInventoryProviderResolver = productInventoryProviderResolver;
_productViewCacheKeyProvider = productViewCacheKeyProvider;
_attributeOptionIdsSerializer = attributeOptionIdsSerializer;
_repository = repository;
@@ -74,14 +77,11 @@ namespace EasyAbp.EShop.Products.Products
await _productManager.CreateAsync(product, input.CategoryIds);
var dto = await MapToGetOutputDtoAsync(product);
-
+
await LoadDtoExtraDataAsync(product, dto);
- await LoadDtosProductGroupDisplayNameAsync(new[] {dto});
-
- UnitOfWorkManager.Current.OnCompleted(async () =>
- {
- await ClearProductViewCacheAsync(product.StoreId);
- });
+ await LoadDtosProductGroupDisplayNameAsync(new[] { dto });
+
+ UnitOfWorkManager.Current.OnCompleted(async () => { await ClearProductViewCacheAsync(product.StoreId); });
return dto;
}
@@ -94,14 +94,14 @@ namespace EasyAbp.EShop.Products.Products
public override async Task UpdateAsync(Guid id, CreateUpdateProductDto input)
{
var product = await GetEntityByIdAsync(id);
-
+
await CheckMultiStorePolicyAsync(product.StoreId, UpdatePolicyName);
if (input.StoreId != product.StoreId)
{
await CheckMultiStorePolicyAsync(input.StoreId, UpdatePolicyName);
}
-
+
CheckProductIsNotStatic(product);
await MapToEntityAsync(input, product);
@@ -111,15 +111,12 @@ namespace EasyAbp.EShop.Products.Products
await _productManager.UpdateAsync(product, input.CategoryIds);
var dto = await MapToGetOutputDtoAsync(product);
-
+
await LoadDtoExtraDataAsync(product, dto);
- await LoadDtosProductGroupDisplayNameAsync(new[] {dto});
+ await LoadDtosProductGroupDisplayNameAsync(new[] { dto });
+
+ UnitOfWorkManager.Current.OnCompleted(async () => { await ClearProductViewCacheAsync(product.StoreId); });
- UnitOfWorkManager.Current.OnCompleted(async () =>
- {
- await ClearProductViewCacheAsync(product.StoreId);
- });
-
return dto;
}
@@ -130,10 +127,10 @@ namespace EasyAbp.EShop.Products.Products
var usedAttributeOptionIds = new HashSet();
foreach (var serializedAttributeOptionIds in product.ProductSkus.Select(sku =>
- sku.SerializedAttributeOptionIds))
+ sku.SerializedAttributeOptionIds))
{
foreach (var attributeOptionId in await _attributeOptionIdsSerializer.DeserializeAsync(
- serializedAttributeOptionIds))
+ serializedAttributeOptionIds))
{
usedAttributeOptionIds.Add(attributeOptionId);
}
@@ -175,9 +172,9 @@ namespace EasyAbp.EShop.Products.Products
.Except(attributeDto.ProductAttributeOptions.Select(o => o.DisplayName)).ToList();
if (!isProductSkusEmpty && removedOptionNames.Any() && usedAttributeOptionIds
- .Intersect(attribute.ProductAttributeOptions
- .Where(option => removedOptionNames.Contains(option.DisplayName))
- .Select(option => option.Id)).Any())
+ .Intersect(attribute.ProductAttributeOptions
+ .Where(option => removedOptionNames.Contains(option.DisplayName))
+ .Select(option => option.Id)).Any())
{
throw new ProductAttributeOptionsDeletionFailedException();
}
@@ -210,7 +207,7 @@ namespace EasyAbp.EShop.Products.Products
var dto = await MapToGetOutputDtoAsync(product);
await LoadDtoExtraDataAsync(product, dto);
- await LoadDtosProductGroupDisplayNameAsync(new[] {dto});
+ await LoadDtosProductGroupDisplayNameAsync(new[] { dto });
return dto;
}
@@ -241,7 +238,7 @@ namespace EasyAbp.EShop.Products.Products
var dto = await MapToGetOutputDtoAsync(product);
await LoadDtoExtraDataAsync(product, dto);
- await LoadDtosProductGroupDisplayNameAsync(new[] {dto});
+ await LoadDtosProductGroupDisplayNameAsync(new[] { dto });
return dto;
}
@@ -283,7 +280,12 @@ namespace EasyAbp.EShop.Products.Products
protected virtual async Task LoadDtoInventoryDataAsync(Product product, ProductDto productDto)
{
- var inventoryDataDict = await _productInventoryProvider.GetInventoryDataDictionaryAsync(product);
+ var models = product.ProductSkus.Select(x =>
+ new InventoryQueryModel(product.TenantId, product.StoreId, product.Id, x.Id)).ToList();
+
+ var inventoryProvider = await _productInventoryProviderResolver.GetAsync(product);
+
+ var inventoryDataDict = await inventoryProvider.GetSkuIdInventoryDataMappingAsync(models);
productDto.Sold = 0;
@@ -306,7 +308,7 @@ namespace EasyAbp.EShop.Products.Products
return productDto;
}
-
+
protected virtual async Task LoadDtoPriceDataAsync(Product product, ProductDto productDto)
{
foreach (var productSku in product.ProductSkus)
@@ -314,7 +316,7 @@ namespace EasyAbp.EShop.Products.Products
var productSkuDto = productDto.ProductSkus.First(x => x.Id == productSku.Id);
var priceDataModel = await _productManager.GetRealPriceAsync(product, productSku);
-
+
productSkuDto.Price = priceDataModel.Price;
productSkuDto.DiscountedPrice = priceDataModel.DiscountedPrice;
}
@@ -331,17 +333,14 @@ namespace EasyAbp.EShop.Products.Products
public override async Task DeleteAsync(Guid id)
{
var product = await GetEntityByIdAsync(id);
-
+
await CheckMultiStorePolicyAsync(product.StoreId, DeletePolicyName);
-
+
CheckProductIsNotStatic(product);
await _productManager.DeleteAsync(product);
-
- UnitOfWorkManager.Current.OnCompleted(async () =>
- {
- await ClearProductViewCacheAsync(product.StoreId);
- });
+
+ UnitOfWorkManager.Current.OnCompleted(async () => { await ClearProductViewCacheAsync(product.StoreId); });
}
private static void CheckProductIsNotStatic(Product product)
@@ -355,9 +354,9 @@ namespace EasyAbp.EShop.Products.Products
public async Task CreateSkuAsync(Guid productId, CreateProductSkuDto input)
{
var product = await GetEntityByIdAsync(productId);
-
+
await CheckMultiStorePolicyAsync(product.StoreId, UpdatePolicyName);
-
+
CheckProductIsNotStatic(product);
var sku = ObjectMapper.Map(input);
@@ -367,15 +366,12 @@ namespace EasyAbp.EShop.Products.Products
await _productManager.CreateSkuAsync(product, sku);
var dto = await MapToGetOutputDtoAsync(product);
-
+
await LoadDtoExtraDataAsync(product, dto);
- await LoadDtosProductGroupDisplayNameAsync(new[] {dto});
+ await LoadDtosProductGroupDisplayNameAsync(new[] { dto });
+
+ UnitOfWorkManager.Current.OnCompleted(async () => { await ClearProductViewCacheAsync(product.StoreId); });
- UnitOfWorkManager.Current.OnCompleted(async () =>
- {
- await ClearProductViewCacheAsync(product.StoreId);
- });
-
return dto;
}
@@ -394,15 +390,12 @@ namespace EasyAbp.EShop.Products.Products
await _productManager.UpdateSkuAsync(product, sku);
var dto = await MapToGetOutputDtoAsync(product);
-
+
await LoadDtoExtraDataAsync(product, dto);
- await LoadDtosProductGroupDisplayNameAsync(new[] {dto});
+ await LoadDtosProductGroupDisplayNameAsync(new[] { dto });
+
+ UnitOfWorkManager.Current.OnCompleted(async () => { await ClearProductViewCacheAsync(product.StoreId); });
- UnitOfWorkManager.Current.OnCompleted(async () =>
- {
- await ClearProductViewCacheAsync(product.StoreId);
- });
-
return dto;
}
@@ -419,15 +412,12 @@ namespace EasyAbp.EShop.Products.Products
await _productManager.DeleteSkuAsync(product, sku);
var dto = await MapToGetOutputDtoAsync(product);
-
+
await LoadDtoExtraDataAsync(product, dto);
- await LoadDtosProductGroupDisplayNameAsync(new[] {dto});
+ await LoadDtosProductGroupDisplayNameAsync(new[] { dto });
+
+ UnitOfWorkManager.Current.OnCompleted(async () => { await ClearProductViewCacheAsync(product.StoreId); });
- UnitOfWorkManager.Current.OnCompleted(async () =>
- {
- await ClearProductViewCacheAsync(product.StoreId);
- });
-
return dto;
}
@@ -445,6 +435,29 @@ namespace EasyAbp.EShop.Products.Products
).ToList()));
}
+ public virtual async Task ChangeInventoryAsync(Guid id, Guid productSkuId,
+ ChangeProductInventoryDto input)
+ {
+ var product = await GetEntityByIdAsync(id);
+ var sku = product.ProductSkus.Single(x => x.Id == productSkuId);
+
+ var changed = input.ChangedInventory switch
+ {
+ > 0 => await _productManager.TryIncreaseInventoryAsync(product, sku, input.ChangedInventory, false),
+ < 0 => await _productManager.TryReduceInventoryAsync(product, sku, -1 * input.ChangedInventory, false),
+ _ => false
+ };
+
+ var model = await _productManager.GetInventoryDataAsync(product, sku);
+
+ return new ChangeProductInventoryResultDto
+ {
+ Changed = changed,
+ ChangedInventory = input.ChangedInventory,
+ CurrentInventory = model.Inventory
+ };
+ }
+
protected override ProductDto MapToGetOutputDto(Product entity)
{
var productDto = base.MapToGetOutputDto(entity);
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/ProductsApplicationAutoMapperProfile.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/ProductsApplicationAutoMapperProfile.cs
index 43c98589..bfefd578 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/ProductsApplicationAutoMapperProfile.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/ProductsApplicationAutoMapperProfile.cs
@@ -9,8 +9,6 @@ using EasyAbp.EShop.Products.ProductDetails;
using EasyAbp.EShop.Products.ProductDetails.Dtos;
using EasyAbp.EShop.Products.ProductHistories;
using EasyAbp.EShop.Products.ProductHistories.Dtos;
-using EasyAbp.EShop.Products.ProductInventories;
-using EasyAbp.EShop.Products.ProductInventories.Dtos;
using EasyAbp.EShop.Products.Products;
using EasyAbp.EShop.Products.Products.Dtos;
using System.Linq;
@@ -65,7 +63,6 @@ namespace EasyAbp.EShop.Products
CreateMap();
CreateMap();
CreateMap();
- CreateMap();
CreateMap();
CreateMap(MemberList.Destination);
}
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/cs.json b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/cs.json
index 8dd0d2ff..5e2bde28 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/cs.json
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/cs.json
@@ -40,6 +40,8 @@
"EditProductSku": "Edit",
"ProductSkuDeletionConfirmationMessage": "Are you sure to delete the product SKU {0}?",
"ProductInventoryStrategy": "Inventory strategy",
+ "ProductInventoryProviderName": "Inventory provider",
+ "ProductInventoryProviderNamePlaceholder": "Keep it empty if you don't understand",
"InventoryStrategy.NoNeed": "No need",
"InventoryStrategy.ReduceAfterPlacing": "Reduce inventory after placing",
"InventoryStrategy.ReduceAfterPayment": "Reduce inventory after payment",
@@ -85,6 +87,7 @@
"EasyAbp.EShop.Products:DuplicatedProductUniqueName": "The product unique name '{uniqueName}' is duplicated.",
"EasyAbp.EShop.Products:InventoryChangeFailed": "Inventory of product {productId} (SKU: {productSkuId}) cannot be changed by {changedInventory} from {originalInventory}",
"EasyAbp.EShop.Products:NonexistentProductGroup": "The specified product group ({productGroupName}) is nonexistent.",
+ "EasyAbp.EShop.Products:NonexistentInventoryProvider": "The specified inventory provider ({inventoryProviderName}) is nonexistent.",
"EasyAbp.EShop.Products:ProductSkuCodeDuplicated": "Sku code {code} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuDuplicated": "Sku {serializedAttributeOptionIds} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuIncorrectAttributeOptions": "Sku {serializedAttributeOptionIds} is incorrect for the product {productId}",
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/en.json b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/en.json
index 7d39ef3b..62c0f8e0 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/en.json
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/en.json
@@ -41,6 +41,8 @@
"EditProductSku": "Edit",
"ProductSkuDeletionConfirmationMessage": "Are you sure to delete the product SKU {0}?",
"ProductInventoryStrategy": "Inventory strategy",
+ "ProductInventoryProviderName": "Inventory provider",
+ "ProductInventoryProviderNamePlaceholder": "Keep it empty if you don't understand",
"InventoryStrategy.NoNeed": "No need",
"InventoryStrategy.ReduceAfterPlacing": "Reduce inventory after placing",
"InventoryStrategy.ReduceAfterPayment": "Reduce inventory after payment",
@@ -86,6 +88,7 @@
"EasyAbp.EShop.Products:DuplicatedProductUniqueName": "The product unique name '{uniqueName}' is duplicated.",
"EasyAbp.EShop.Products:InventoryChangeFailed": "Inventory of product {productId} (SKU: {productSkuId}) cannot be changed by {changedInventory} from {originalInventory}",
"EasyAbp.EShop.Products:NonexistentProductGroup": "The specified product group ({productGroupName}) is nonexistent.",
+ "EasyAbp.EShop.Products:NonexistentInventoryProvider": "The specified inventory provider ({inventoryProviderName}) is nonexistent.",
"EasyAbp.EShop.Products:ProductSkuCodeDuplicated": "Sku code {code} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuDuplicated": "Sku {serializedAttributeOptionIds} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuIncorrectAttributeOptions": "Sku {serializedAttributeOptionIds} is incorrect for the product {productId}",
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pl.json b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pl.json
index 67b62e2b..b9d29fbc 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pl.json
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pl.json
@@ -40,6 +40,8 @@
"EditProductSku": "Edit",
"ProductSkuDeletionConfirmationMessage": "Are you sure to delete the product SKU {0}?",
"ProductInventoryStrategy": "Inventory strategy",
+ "ProductInventoryProviderName": "Inventory provider",
+ "ProductInventoryProviderNamePlaceholder": "Keep it empty if you don't understand",
"InventoryStrategy.NoNeed": "No need",
"InventoryStrategy.ReduceAfterPlacing": "Reduce inventory after placing",
"InventoryStrategy.ReduceAfterPayment": "Reduce inventory after payment",
@@ -85,6 +87,7 @@
"EasyAbp.EShop.Products:DuplicatedProductUniqueName": "The product unique name '{uniqueName}' is duplicated.",
"EasyAbp.EShop.Products:InventoryChangeFailed": "Inventory of product {productId} (SKU: {productSkuId}) cannot be changed by {changedInventory} from {originalInventory}",
"EasyAbp.EShop.Products:NonexistentProductGroup": "The specified product group ({productGroupName}) is nonexistent.",
+ "EasyAbp.EShop.Products:NonexistentInventoryProvider": "The specified inventory provider ({inventoryProviderName}) is nonexistent.",
"EasyAbp.EShop.Products:ProductSkuCodeDuplicated": "Sku code {code} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuDuplicated": "Sku {serializedAttributeOptionIds} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuIncorrectAttributeOptions": "Sku {serializedAttributeOptionIds} is incorrect for the product {productId}",
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pt-BR.json b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pt-BR.json
index ba6b48db..8bbec7b8 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pt-BR.json
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pt-BR.json
@@ -40,6 +40,8 @@
"EditProductSku": "Edit",
"ProductSkuDeletionConfirmationMessage": "Are you sure to delete the product SKU {0}?",
"ProductInventoryStrategy": "Inventory strategy",
+ "ProductInventoryProviderName": "Inventory provider",
+ "ProductInventoryProviderNamePlaceholder": "Keep it empty if you don't understand",
"InventoryStrategy.NoNeed": "No need",
"InventoryStrategy.ReduceAfterPlacing": "Reduce inventory after placing",
"InventoryStrategy.ReduceAfterPayment": "Reduce inventory after payment",
@@ -85,6 +87,7 @@
"EasyAbp.EShop.Products:DuplicatedProductUniqueName": "The product unique name '{uniqueName}' is duplicated.",
"EasyAbp.EShop.Products:InventoryChangeFailed": "Inventory of product {productId} (SKU: {productSkuId}) cannot be changed by {changedInventory} from {originalInventory}",
"EasyAbp.EShop.Products:NonexistentProductGroup": "The specified product group ({productGroupName}) is nonexistent.",
+ "EasyAbp.EShop.Products:NonexistentInventoryProvider": "The specified inventory provider ({inventoryProviderName}) is nonexistent.",
"EasyAbp.EShop.Products:ProductSkuCodeDuplicated": "Sku code {code} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuDuplicated": "Sku {serializedAttributeOptionIds} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuIncorrectAttributeOptions": "Sku {serializedAttributeOptionIds} is incorrect for the product {productId}",
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/sl.json b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/sl.json
index 1fee2035..21e82951 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/sl.json
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/sl.json
@@ -41,6 +41,8 @@
"EditProductSku": "Edit",
"ProductSkuDeletionConfirmationMessage": "Are you sure to delete the product SKU {0}?",
"ProductInventoryStrategy": "Inventory strategy",
+ "ProductInventoryProviderName": "Inventory provider",
+ "ProductInventoryProviderNamePlaceholder": "Keep it empty if you don't understand",
"InventoryStrategy.NoNeed": "No need",
"InventoryStrategy.ReduceAfterPlacing": "Reduce inventory after placing",
"InventoryStrategy.ReduceAfterPayment": "Reduce inventory after payment",
@@ -86,6 +88,7 @@
"EasyAbp.EShop.Products:DuplicatedProductUniqueName": "The product unique name '{uniqueName}' is duplicated.",
"EasyAbp.EShop.Products:InventoryChangeFailed": "Inventory of product {productId} (SKU: {productSkuId}) cannot be changed by {changedInventory} from {originalInventory}",
"EasyAbp.EShop.Products:NonexistentProductGroup": "The specified product group ({productGroupName}) is nonexistent.",
+ "EasyAbp.EShop.Products:NonexistentInventoryProvider": "The specified inventory provider ({inventoryProviderName}) is nonexistent.",
"EasyAbp.EShop.Products:ProductSkuCodeDuplicated": "Sku code {code} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuDuplicated": "Sku {serializedAttributeOptionIds} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuIncorrectAttributeOptions": "Sku {serializedAttributeOptionIds} is incorrect for the product {productId}",
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/tr.json b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/tr.json
index cb9c4994..193036ab 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/tr.json
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/tr.json
@@ -41,6 +41,8 @@
"EditProductSku": "Edit",
"ProductSkuDeletionConfirmationMessage": "Are you sure to delete the product SKU {0}?",
"ProductInventoryStrategy": "Inventory strategy",
+ "ProductInventoryProviderName": "Inventory provider",
+ "ProductInventoryProviderNamePlaceholder": "Keep it empty if you don't understand",
"InventoryStrategy.NoNeed": "No need",
"InventoryStrategy.ReduceAfterPlacing": "Reduce inventory after placing",
"InventoryStrategy.ReduceAfterPayment": "Reduce inventory after payment",
@@ -86,6 +88,7 @@
"EasyAbp.EShop.Products:DuplicatedProductUniqueName": "The product unique name '{uniqueName}' is duplicated.",
"EasyAbp.EShop.Products:InventoryChangeFailed": "Inventory of product {productId} (SKU: {productSkuId}) cannot be changed by {changedInventory} from {originalInventory}",
"EasyAbp.EShop.Products:NonexistentProductGroup": "The specified product group ({productGroupName}) is nonexistent.",
+ "EasyAbp.EShop.Products:NonexistentInventoryProvider": "The specified inventory provider ({inventoryProviderName}) is nonexistent.",
"EasyAbp.EShop.Products:ProductSkuCodeDuplicated": "Sku code {code} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuDuplicated": "Sku {serializedAttributeOptionIds} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuIncorrectAttributeOptions": "Sku {serializedAttributeOptionIds} is incorrect for the product {productId}",
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/vi.json b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/vi.json
index aa785b24..a652814e 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/vi.json
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/vi.json
@@ -40,6 +40,8 @@
"EditProductSku": "Edit",
"ProductSkuDeletionConfirmationMessage": "Are you sure to delete the product SKU {0}?",
"ProductInventoryStrategy": "Inventory strategy",
+ "ProductInventoryProviderName": "Inventory provider",
+ "ProductInventoryProviderNamePlaceholder": "Keep it empty if you don't understand",
"InventoryStrategy.NoNeed": "No need",
"InventoryStrategy.ReduceAfterPlacing": "Reduce inventory after placing",
"InventoryStrategy.ReduceAfterPayment": "Reduce inventory after payment",
@@ -85,6 +87,7 @@
"EasyAbp.EShop.Products:DuplicatedProductUniqueName": "The product unique name '{uniqueName}' is duplicated.",
"EasyAbp.EShop.Products:InventoryChangeFailed": "Inventory of product {productId} (SKU: {productSkuId}) cannot be changed by {changedInventory} from {originalInventory}",
"EasyAbp.EShop.Products:NonexistentProductGroup": "The specified product group ({productGroupName}) is nonexistent.",
+ "EasyAbp.EShop.Products:NonexistentInventoryProvider": "The specified inventory provider ({inventoryProviderName}) is nonexistent.",
"EasyAbp.EShop.Products:ProductSkuCodeDuplicated": "Sku code {code} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuDuplicated": "Sku {serializedAttributeOptionIds} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuIncorrectAttributeOptions": "Sku {serializedAttributeOptionIds} is incorrect for the product {productId}",
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hans.json b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hans.json
index e261b4ed..b6eb22b0 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hans.json
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hans.json
@@ -41,6 +41,8 @@
"EditProductSku": "编辑",
"ProductSkuDeletionConfirmationMessage": "确认删除 SKU {0}?",
"ProductInventoryStrategy": "库存扣减策略",
+ "ProductInventoryProviderName": "库存提供者",
+ "ProductInventoryProviderNamePlaceholder": "如果你不理解它的意义,请留空",
"InventoryStrategy.NoNeed": "无需库存",
"InventoryStrategy.ReduceAfterPlacing": "下单后减库存",
"InventoryStrategy.ReduceAfterPayment": "支付后减库存",
@@ -83,6 +85,7 @@
"EasyAbp.EShop.Products:DuplicatedProductUniqueName": "唯一的产品名称'{uniqueName}'重复",
"EasyAbp.EShop.Products:InventoryChangeFailed": "产品{productId} (SKU: {productSkuId})的库存不能由{originalInventory}中的{changedInventory}更改",
"EasyAbp.EShop.Products:NonexistentProductGroup": "指定的产品组({productGroupName})不存在",
+ "EasyAbp.EShop.Products:NonexistentInventoryProvider": "指定的库存提供者({inventoryProviderName})不存在",
"EasyAbp.EShop.Products:ProductSkuCodeDuplicated": "商品{productId}的Sku代码{code}重复",
"EasyAbp.EShop.Products:ProductSkuDuplicated": "商品{productId}的Sku{serializedAttributeOptionIds}重复",
"EasyAbp.EShop.Products:ProductSkuIncorrectAttributeOptions": "商品{productId}的Sku{serializedAttributeOptionIds}不正确",
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hant.json b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hant.json
index eabbaa0b..395a71e0 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hant.json
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hant.json
@@ -41,7 +41,9 @@
"EditProductSku": "編輯",
"ProductSkuDeletionConfirmationMessage": "確認刪除 SKU {0}?",
"ProductInventoryStrategy": "庫存扣減策略",
- "InventoryStrategy.NoNeed": "No need",
+ "ProductInventoryProviderName": "庫存提供者",
+ "ProductInventoryProviderNamePlaceholder": "如果你不理解它的意義,請留空",
+ "InventoryStrategy.NoNeed": "無需庫存",
"InventoryStrategy.ReduceAfterPlacing": "下單後減庫存",
"InventoryStrategy.ReduceAfterPayment": "支付後減庫存",
"ProductIsPublished": "是否發布",
@@ -83,6 +85,7 @@
"EasyAbp.EShop.Products:DuplicatedProductUniqueName": "唯一的產品名稱'{uniqueName}'重復",
"EasyAbp.EShop.Products:InventoryChangeFailed": "產品{productId} (SKU: {productSkuId})的庫存不能由{originalInventory}中的{changedInventory}更改",
"EasyAbp.EShop.Products:NonexistentProductGroup": "指定的產品組({productGroupName})不存在",
+ "EasyAbp.EShop.Products:NonexistentInventoryProvider": "指定的庫存提供者({inventoryProviderName})不存在",
"EasyAbp.EShop.Products:ProductSkuCodeDuplicated": "商品{productId}的Sku代碼{code}重復",
"EasyAbp.EShop.Products:ProductSkuDuplicated": "商品{productId}的Sku{serializedAttributeOptionIds}重復",
"EasyAbp.EShop.Products:ProductSkuIncorrectAttributeOptions": "商品{productId}的Sku{serializedAttributeOptionIds}不正確",
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/ProductInventories/IProductInventoryProvider.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/ProductInventories/IProductInventoryProvider.cs
new file mode 100644
index 00000000..eaf89186
--- /dev/null
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/ProductInventories/IProductInventoryProvider.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace EasyAbp.EShop.Products.ProductInventories
+{
+ public interface IProductInventoryProvider
+ {
+ string InventoryProviderName { get; }
+
+ Task GetInventoryDataAsync(InventoryQueryModel model);
+
+ Task> GetSkuIdInventoryDataMappingAsync(IList models);
+
+ Task TryIncreaseInventoryAsync(InventoryQueryModel model, int quantity, bool decreaseSold);
+
+ Task TryReduceInventoryAsync(InventoryQueryModel model, int quantity, bool increaseSold);
+ }
+}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/InventoryDataModel.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/ProductInventories/InventoryDataModel.cs
similarity index 71%
rename from modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/InventoryDataModel.cs
rename to modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/ProductInventories/InventoryDataModel.cs
index bdc1e838..11355343 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/InventoryDataModel.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/ProductInventories/InventoryDataModel.cs
@@ -1,4 +1,4 @@
-namespace EasyAbp.EShop.Products.Products
+namespace EasyAbp.EShop.Products.ProductInventories
{
public class InventoryDataModel
{
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/ProductInventories/InventoryQueryModel.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/ProductInventories/InventoryQueryModel.cs
new file mode 100644
index 00000000..99d2c3c1
--- /dev/null
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/ProductInventories/InventoryQueryModel.cs
@@ -0,0 +1,28 @@
+using System;
+using EasyAbp.EShop.Stores.Stores;
+using Volo.Abp.MultiTenancy;
+
+namespace EasyAbp.EShop.Products.ProductInventories;
+
+public class InventoryQueryModel : IMultiTenant, IMultiStore
+{
+ public Guid? TenantId { get; set; }
+
+ public Guid StoreId { get; set; }
+
+ public Guid ProductId { get; set; }
+
+ public Guid ProductSkuId { get; set; }
+
+ public InventoryQueryModel()
+ {
+ }
+
+ public InventoryQueryModel(Guid? tenantId, Guid storeId, Guid productId, Guid productSkuId)
+ {
+ TenantId = tenantId;
+ StoreId = storeId;
+ ProductId = productId;
+ ProductSkuId = productSkuId;
+ }
+}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/IProduct.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/IProduct.cs
index 4f094498..35017018 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/IProduct.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/IProduct.cs
@@ -1,5 +1,6 @@
using System;
using EasyAbp.EShop.Stores.Stores;
+using JetBrains.Annotations;
using Volo.Abp.Data;
namespace EasyAbp.EShop.Products.Products
@@ -7,23 +8,25 @@ namespace EasyAbp.EShop.Products.Products
public interface IProduct : IHasExtraProperties, IMultiStore
{
string ProductGroupName { get; }
-
+
Guid? ProductDetailId { get; }
string UniqueName { get; }
string DisplayName { get; }
-
+
InventoryStrategy InventoryStrategy { get; }
-
+
+ [CanBeNull] string InventoryProviderName { get; }
+
string MediaResources { get; }
-
+
int DisplayOrder { get; }
bool IsPublished { get; }
-
+
bool IsStatic { get; }
-
+
bool IsHidden { get; }
}
}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductEto.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductEto.cs
index 6c72fc38..c0a1ce36 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductEto.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductEto.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using Volo.Abp.Data;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectExtending;
@@ -11,11 +10,11 @@ namespace EasyAbp.EShop.Products.Products
public Guid? TenantId { get; set; }
public Guid Id { get; set; }
-
+
public Guid StoreId { get; set; }
public string ProductGroupName { get; set; }
-
+
public Guid? ProductDetailId { get; set; }
public string UniqueName { get; set; }
@@ -24,6 +23,8 @@ namespace EasyAbp.EShop.Products.Products
public InventoryStrategy InventoryStrategy { get; set; }
+ public string InventoryProviderName { get; set; }
+
public string MediaResources { get; set; }
public int DisplayOrder { get; set; }
@@ -33,9 +34,9 @@ namespace EasyAbp.EShop.Products.Products
public bool IsStatic { get; set; }
public bool IsHidden { get; set; }
-
+
public List ProductAttributes { get; set; }
-
+
public List ProductSkus { get; set; }
}
}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/ProductsErrorCodes.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/ProductsErrorCodes.cs
index 6b198991..7f7baeca 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/ProductsErrorCodes.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/ProductsErrorCodes.cs
@@ -6,6 +6,7 @@
public const string DuplicatedProductUniqueName = "EasyAbp.EShop.Products:DuplicatedProductUniqueName";
public const string InventoryChangeFailed = "EasyAbp.EShop.Products:InventoryChangeFailed";
public const string NonexistentProductGroup = "EasyAbp.EShop.Products:NonexistentProductGroup";
+ public const string NonexistentInventoryProvider = "EasyAbp.EShop.Products:NonexistentInventoryProvider";
public const string ProductSkuCodeDuplicated = "EasyAbp.EShop.Products:ProductSkuCodeDuplicated";
public const string ProductSkuDuplicated = "EasyAbp.EShop.Products:ProductSkuDuplicated";
public const string ProductSkuIncorrectAttributeOptions = "EasyAbp.EShop.Products:ProductSkuIncorrectAttributeOptions";
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/EShopProductsDomainModule.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/EShopProductsDomainModule.cs
index eba59744..3cfeb6af 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/EShopProductsDomainModule.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/EShopProductsDomainModule.cs
@@ -24,10 +24,10 @@ namespace EasyAbp.EShop.Products
Configure(options =>
{
options.EtoMappings.Add();
-
+
options.AutoEventSelectors.Add();
});
-
+
Configure(options =>
{
options.Groups.Configure(group =>
@@ -35,6 +35,16 @@ namespace EasyAbp.EShop.Products
group.DisplayName = ProductsConsts.DefaultProductGroupDisplayName;
group.Description = ProductsConsts.DefaultProductGroupDescription;
});
+
+ options.InventoryProviders.Configure(
+ DefaultProductInventoryProvider.DefaultProductInventoryProviderName, provider =>
+ {
+ provider.DisplayName =
+ DefaultProductInventoryProvider.DefaultProductInventoryProviderDisplayName;
+ provider.Description =
+ DefaultProductInventoryProvider.DefaultProductInventoryProviderDescription;
+ provider.ProviderType = typeof(DefaultProductInventoryProvider);
+ });
});
}
@@ -48,4 +58,4 @@ namespace EasyAbp.EShop.Products
});
}
}
-}
+}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/EShopProductsOptions.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/EShopProductsOptions.cs
index 6f654ba6..c96ce4ee 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/EShopProductsOptions.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/EShopProductsOptions.cs
@@ -1,17 +1,21 @@
-using System;
+using EasyAbp.EShop.Products.Options.InventoryProviders;
using EasyAbp.EShop.Products.Options.ProductGroups;
+using EasyAbp.EShop.Products.Products;
+using JetBrains.Annotations;
namespace EasyAbp.EShop.Products.Options
{
public class EShopProductsOptions
{
- public ProductGroupConfigurations Groups { get; }
+ public ProductGroupConfigurations Groups { get; } = new();
- public Type DefaultFileDownloadProviderType { get; set; }
-
- public EShopProductsOptions()
- {
- Groups = new ProductGroupConfigurations();
- }
+ public InventoryProviderConfigurations InventoryProviders { get; } = new();
+
+ ///
+ /// If the value is null, it will fall back to DefaultProductInventoryProviderName
+ /// in the .
+ ///
+ [CanBeNull]
+ public string DefaultInventoryProviderName { get; set; }
}
}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/InventoryProviders/IInventoryProviderConfigurationProvider.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/InventoryProviders/IInventoryProviderConfigurationProvider.cs
new file mode 100644
index 00000000..879c859f
--- /dev/null
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/InventoryProviders/IInventoryProviderConfigurationProvider.cs
@@ -0,0 +1,7 @@
+namespace EasyAbp.EShop.Products.Options.InventoryProviders
+{
+ public interface IInventoryProviderConfigurationProvider
+ {
+ InventoryProviderConfiguration Get(string providerName);
+ }
+}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/InventoryProviders/InventoryProviderConfiguration.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/InventoryProviders/InventoryProviderConfiguration.cs
new file mode 100644
index 00000000..00827d60
--- /dev/null
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/InventoryProviders/InventoryProviderConfiguration.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace EasyAbp.EShop.Products.Options.InventoryProviders
+{
+ public class InventoryProviderConfiguration
+ {
+ public string DisplayName { get; set; }
+
+ public string Description { get; set; }
+
+ public Type ProviderType { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/InventoryProviders/InventoryProviderConfigurationProvider.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/InventoryProviders/InventoryProviderConfigurationProvider.cs
new file mode 100644
index 00000000..e4fb1910
--- /dev/null
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/InventoryProviders/InventoryProviderConfigurationProvider.cs
@@ -0,0 +1,20 @@
+using Microsoft.Extensions.Options;
+using Volo.Abp.DependencyInjection;
+
+namespace EasyAbp.EShop.Products.Options.InventoryProviders
+{
+ public class InventoryProviderConfigurationProvider : IInventoryProviderConfigurationProvider, ITransientDependency
+ {
+ private readonly EShopProductsOptions _options;
+
+ public InventoryProviderConfigurationProvider(IOptions options)
+ {
+ _options = options.Value;
+ }
+
+ public InventoryProviderConfiguration Get(string providerName)
+ {
+ return _options.InventoryProviders.GetConfiguration(providerName);
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/InventoryProviders/InventoryProviderConfigurations.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/InventoryProviders/InventoryProviderConfigurations.cs
new file mode 100644
index 00000000..8a5c1262
--- /dev/null
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/InventoryProviders/InventoryProviderConfigurations.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+using JetBrains.Annotations;
+using Volo.Abp;
+
+namespace EasyAbp.EShop.Products.Options.InventoryProviders
+{
+ public class InventoryProviderConfigurations
+ {
+ private readonly Dictionary _providers;
+
+ public InventoryProviderConfigurations()
+ {
+ _providers = new Dictionary();
+ }
+
+ public InventoryProviderConfigurations Configure(
+ [NotNull] string name,
+ [NotNull] Action configureAction)
+ {
+ Check.NotNullOrWhiteSpace(name, nameof(name));
+ Check.NotNull(configureAction, nameof(configureAction));
+
+ configureAction(
+ _providers.GetOrAdd(
+ name,
+ () => new InventoryProviderConfiguration()
+ )
+ );
+
+ return this;
+ }
+
+ public InventoryProviderConfigurations ConfigureAll(
+ Action configureAction)
+ {
+ foreach (var provider in _providers)
+ {
+ configureAction(provider.Key, provider.Value);
+ }
+
+ return this;
+ }
+
+ [NotNull]
+ public InventoryProviderConfiguration GetConfiguration([NotNull] string name)
+ {
+ Check.NotNullOrWhiteSpace(name, nameof(name));
+
+ return _providers.GetOrDefault(name);
+ }
+
+ [NotNull]
+ public Dictionary GetConfigurationsDictionary()
+ {
+ return _providers;
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/ProductGroups/ProductGroupConfiguration.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/ProductGroups/ProductGroupConfiguration.cs
index 6a1715c2..3a2b625c 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/ProductGroups/ProductGroupConfiguration.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/ProductGroups/ProductGroupConfiguration.cs
@@ -1,9 +1,18 @@
-namespace EasyAbp.EShop.Products.Options.ProductGroups
+using JetBrains.Annotations;
+
+namespace EasyAbp.EShop.Products.Options.ProductGroups
{
public class ProductGroupConfiguration
{
public string DisplayName { get; set; }
-
+
public string Description { get; set; }
+
+ ///
+ /// If the value is null, it will fall back to DefaultInventoryProviderName
+ /// in the .
+ ///
+ [CanBeNull]
+ public string DefaultInventoryProviderName { get; set; }
}
}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/ProductInventories/IProductInventoryRepository.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/ProductInventories/IProductInventoryRepository.cs
index 7eb77d15..86d23060 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/ProductInventories/IProductInventoryRepository.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/ProductInventories/IProductInventoryRepository.cs
@@ -11,6 +11,6 @@ namespace EasyAbp.EShop.Products.ProductInventories
{
Task GetInventoryDataAsync(Guid productSkuId, CancellationToken cancellationToken = default);
- Task> GetInventoryDataDictionaryAsync(List productSkuIds, CancellationToken cancellationToken = default);
+ Task> GetSkuIdInventoryDataMappingAsync(List productSkuIds, CancellationToken cancellationToken = default);
}
}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ConsumeInventoryModel.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ConsumeInventoryModel.cs
index 1d25894c..204e1bb1 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ConsumeInventoryModel.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ConsumeInventoryModel.cs
@@ -4,12 +4,27 @@ namespace EasyAbp.EShop.Products.Products
{
public class ConsumeInventoryModel
{
- public Product Product { get; set; }
+ public Product Product { get; }
- public ProductSku ProductSku { get; set; }
+ public ProductSku ProductSku { get; }
- public Guid StoreId { get; set; }
+ public Guid StoreId { get; }
- public int Quantity { get; set; }
+ public int Quantity { get; }
+
+ public bool Consumed { get; private set;}
+
+ public ConsumeInventoryModel(Product product, ProductSku productSku, Guid storeId, int quantity)
+ {
+ Product = product;
+ ProductSku = productSku;
+ StoreId = storeId;
+ Quantity = quantity;
+ }
+
+ public void SetConsumed(bool consumed)
+ {
+ Consumed = consumed;
+ }
}
}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/DefaultProductInventoryProvider.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/DefaultProductInventoryProvider.cs
index e22a1881..613a434c 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/DefaultProductInventoryProvider.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/DefaultProductInventoryProvider.cs
@@ -13,70 +13,77 @@ namespace EasyAbp.EShop.Products.Products
{
public class DefaultProductInventoryProvider : IProductInventoryProvider, ITransientDependency
{
+ public static string DefaultProductInventoryProviderName { get; set; } = "Default";
+ public static string DefaultProductInventoryProviderDisplayName { get; set; } = "Default";
+ public static string DefaultProductInventoryProviderDescription { get; set; } = "Default";
+
+ public string InventoryProviderName { get; } = DefaultProductInventoryProviderName;
+
// Todo: should use IProductInventoryStore.
private readonly IGuidGenerator _guidGenerator;
private readonly ICurrentTenant _currentTenant;
- private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly IDistributedEventBus _distributedEventBus;
private readonly IProductInventoryRepository _productInventoryRepository;
public DefaultProductInventoryProvider(
IGuidGenerator guidGenerator,
ICurrentTenant currentTenant,
- IUnitOfWorkManager unitOfWorkManager,
IDistributedEventBus distributedEventBus,
IProductInventoryRepository productInventoryRepository)
{
_guidGenerator = guidGenerator;
_currentTenant = currentTenant;
- _unitOfWorkManager = unitOfWorkManager;
_distributedEventBus = distributedEventBus;
_productInventoryRepository = productInventoryRepository;
}
-
+
[UnitOfWork]
- public virtual async Task GetInventoryDataAsync(Product product, ProductSku productSku)
+ public virtual async Task GetInventoryDataAsync(InventoryQueryModel model)
{
- return await _productInventoryRepository.GetInventoryDataAsync(productSku.Id);
+ return await _productInventoryRepository.GetInventoryDataAsync(model.ProductSkuId);
}
[UnitOfWork]
- public virtual async Task> GetInventoryDataDictionaryAsync(Product product)
+ public virtual async Task> GetSkuIdInventoryDataMappingAsync(
+ IList models)
{
- var dict = await _productInventoryRepository.GetInventoryDataDictionaryAsync(product.ProductSkus
- .Select(sku => sku.Id).ToList());
+ var dict = await _productInventoryRepository.GetSkuIdInventoryDataMappingAsync(
+ models.Select(x => x.ProductSkuId).ToList());
- foreach (var sku in product.ProductSkus)
+ foreach (var model in models)
{
- dict.GetOrAdd(sku.Id, () => new InventoryDataModel());
+ dict.GetOrAdd(model.ProductSkuId, () => new InventoryDataModel());
}
return dict;
}
[UnitOfWork(true)]
- public virtual async Task TryIncreaseInventoryAsync(Product product, ProductSku productSku, int quantity, bool decreaseSold)
+ public virtual async Task TryIncreaseInventoryAsync(InventoryQueryModel model, int quantity,
+ bool decreaseSold)
{
- var productInventory = await GetOrCreateProductInventoryAsync(product.Id, productSku.Id);
-
- return await TryIncreaseInventoryAsync(product, productInventory, quantity, decreaseSold);
+ var productInventory = await GetOrCreateProductInventoryAsync(model.ProductId, model.ProductSkuId);
+
+ return await TryIncreaseInventoryAsync(model, productInventory, quantity, decreaseSold);
}
[UnitOfWork(true)]
- public virtual async Task TryReduceInventoryAsync(Product product, ProductSku productSku, int quantity, bool increaseSold)
+ public virtual async Task TryReduceInventoryAsync(InventoryQueryModel model, int quantity,
+ bool increaseSold)
{
- var productInventory = await GetOrCreateProductInventoryAsync(product.Id, productSku.Id);
-
- return await TryReduceInventoryAsync(product, productInventory, quantity, increaseSold);
+ var productInventory = await GetOrCreateProductInventoryAsync(model.ProductId, model.ProductSkuId);
+
+ return await TryReduceInventoryAsync(model, productInventory, quantity, increaseSold);
}
-
+
[UnitOfWork]
- protected virtual async Task GetOrCreateProductInventoryAsync(Guid productId, Guid productSkuId)
+ protected virtual async Task GetOrCreateProductInventoryAsync(Guid productId,
+ Guid productSkuId)
{
var productInventory =
await _productInventoryRepository.FindAsync(x =>
x.ProductId == productId && x.ProductSkuId == productSkuId);
-
+
if (productInventory is null)
{
productInventory = new ProductInventory(_guidGenerator.Create(), _currentTenant.Id, productId,
@@ -87,15 +94,16 @@ namespace EasyAbp.EShop.Products.Products
return productInventory;
}
-
+
[UnitOfWork(true)]
- public virtual async Task TryIncreaseInventoryAsync(Product product, ProductInventory productInventory, int quantity, bool decreaseSold)
+ protected virtual async Task TryIncreaseInventoryAsync(InventoryQueryModel model,
+ ProductInventory productInventory, int quantity, bool decreaseSold)
{
if (quantity < 0)
{
return false;
}
-
+
var originalInventory = productInventory.Inventory;
if (!productInventory.TryIncreaseInventory(quantity, decreaseSold))
@@ -105,15 +113,16 @@ namespace EasyAbp.EShop.Products.Products
await _productInventoryRepository.UpdateAsync(productInventory, true);
- await PublishInventoryChangedEventAsync(product.TenantId, product.StoreId,
+ await PublishInventoryChangedEventAsync(model.TenantId, model.StoreId,
productInventory.ProductId, productInventory.ProductSkuId, originalInventory,
productInventory.Inventory, productInventory.Sold);
-
+
return true;
}
[UnitOfWork(true)]
- public virtual async Task TryReduceInventoryAsync(Product product, ProductInventory productInventory, int quantity, bool increaseSold)
+ protected virtual async Task TryReduceInventoryAsync(InventoryQueryModel model,
+ ProductInventory productInventory, int quantity, bool increaseSold)
{
if (quantity < 0)
{
@@ -129,7 +138,7 @@ namespace EasyAbp.EShop.Products.Products
await _productInventoryRepository.UpdateAsync(productInventory, true);
- await PublishInventoryChangedEventAsync(product.TenantId, product.StoreId,
+ await PublishInventoryChangedEventAsync(model.TenantId, model.StoreId,
productInventory.ProductId, productInventory.ProductSkuId, originalInventory,
productInventory.Inventory, productInventory.Sold);
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductInventoryProvider.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductInventoryProvider.cs
deleted file mode 100644
index 769a410a..00000000
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductInventoryProvider.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-
-namespace EasyAbp.EShop.Products.Products
-{
- public interface IProductInventoryProvider
- {
- Task GetInventoryDataAsync(Product product, ProductSku productSku);
-
- Task> GetInventoryDataDictionaryAsync(Product product);
-
- Task TryIncreaseInventoryAsync(Product product, ProductSku productSku, int quantity, bool decreaseSold);
-
- Task TryReduceInventoryAsync(Product product, ProductSku productSku, int quantity, bool increaseSold);
- }
-}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductInventoryProviderResolver.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductInventoryProviderResolver.cs
new file mode 100644
index 00000000..1fd1daca
--- /dev/null
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductInventoryProviderResolver.cs
@@ -0,0 +1,12 @@
+using System.Threading.Tasks;
+using EasyAbp.EShop.Products.ProductInventories;
+using JetBrains.Annotations;
+
+namespace EasyAbp.EShop.Products.Products;
+
+public interface IProductInventoryProviderResolver
+{
+ Task ExistProviderAsync([NotNull] string providerName);
+
+ Task GetAsync(Product product);
+}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductManager.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductManager.cs
index 6ae5c05f..884fe3e5 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductManager.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductManager.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
+using EasyAbp.EShop.Products.ProductInventories;
using Volo.Abp.Domain.Services;
namespace EasyAbp.EShop.Products.Products
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/NonexistentInventoryProviderException.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/NonexistentInventoryProviderException.cs
new file mode 100644
index 00000000..f586b453
--- /dev/null
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/NonexistentInventoryProviderException.cs
@@ -0,0 +1,14 @@
+using System;
+using Volo.Abp;
+
+namespace EasyAbp.EShop.Products.Products
+{
+ public class NonexistentInventoryProviderException : BusinessException
+ {
+ public NonexistentInventoryProviderException(string inventoryProviderName) :
+ base(ProductsErrorCodes.NonexistentInventoryProvider)
+ {
+ WithData(nameof(inventoryProviderName), inventoryProviderName);
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/OrderCreatedEventHandler.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/OrderCreatedEventHandler.cs
index 4c5f24da..1e16d1ba 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/OrderCreatedEventHandler.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/OrderCreatedEventHandler.cs
@@ -2,6 +2,7 @@
using System.Linq;
using System.Threading.Tasks;
using EasyAbp.EShop.Orders.Orders;
+using Microsoft.Extensions.Logging;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.EventBus.Distributed;
@@ -15,6 +16,7 @@ namespace EasyAbp.EShop.Products.Products
private readonly ICurrentTenant _currentTenant;
private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly IDistributedEventBus _distributedEventBus;
+ private readonly ILogger _logger;
private readonly IProductRepository _productRepository;
private readonly IProductManager _productManager;
@@ -22,12 +24,14 @@ namespace EasyAbp.EShop.Products.Products
ICurrentTenant currentTenant,
IUnitOfWorkManager unitOfWorkManager,
IDistributedEventBus distributedEventBus,
+ ILogger logger,
IProductRepository productRepository,
IProductManager productManager)
{
_currentTenant = currentTenant;
_unitOfWorkManager = unitOfWorkManager;
_distributedEventBus = distributedEventBus;
+ _logger = logger;
_productRepository = productRepository;
_productManager = productManager;
}
@@ -65,33 +69,52 @@ namespace EasyAbp.EShop.Products.Products
return;
}
- models.Add(new ConsumeInventoryModel
- {
- Product = product,
- ProductSku = productSku,
- StoreId = eventData.Entity.StoreId,
- Quantity = orderLine.Quantity
- });
+ models.Add(new ConsumeInventoryModel(
+ product, productSku, eventData.Entity.StoreId, orderLine.Quantity));
+
}
foreach (var model in models)
{
- if (await _productManager.TryReduceInventoryAsync(model.Product, model.ProductSku, model.Quantity, true))
+ if (await _productManager.TryReduceInventoryAsync(
+ model.Product, model.ProductSku, model.Quantity, true))
{
continue;
}
- // Todo: should release unused inventory since (external) inventory providers may not be transactional.
+ await TryRollbackInventoriesAsync(models);
+
await _unitOfWorkManager.Current.RollbackAsync();
-
+
await PublishInventoryReductionResultEventAsync(eventData, false, true);
-
+
return;
}
await PublishInventoryReductionResultEventAsync(eventData, true);
}
+ protected virtual async Task TryRollbackInventoriesAsync(IEnumerable models)
+ {
+ var result = true;
+
+ foreach (var model in models.Where(x => x.Consumed))
+ {
+ if (await _productManager.TryIncreaseInventoryAsync(
+ model.Product, model.ProductSku, model.Quantity, true))
+ {
+ continue;
+ }
+
+ result = false;
+ _logger.LogWarning(
+ "OrderCreatedEventHandler: inventory rollback failed! productId = {productId}, productSkuId = {productSkuId}, quantity = {quantity}, reduceSold = {reduceSold}",
+ model.Product.Id, model.ProductSku.Id, model.Quantity, true);
+ }
+
+ return result;
+ }
+
protected virtual async Task PublishInventoryReductionResultEventAsync(EntityCreatedEto orderCreatedEto, bool isSuccess, bool publishNow = false)
{
await _distributedEventBus.PublishAsync(new ProductInventoryReductionAfterOrderPlacedResultEto
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/OrderPaidEventHandler.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/OrderPaidEventHandler.cs
index 3f561dd6..0d2a82fc 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/OrderPaidEventHandler.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/OrderPaidEventHandler.cs
@@ -2,6 +2,7 @@
using System.Linq;
using System.Threading.Tasks;
using EasyAbp.EShop.Orders.Orders;
+using Microsoft.Extensions.Logging;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.MultiTenancy;
@@ -13,6 +14,7 @@ namespace EasyAbp.EShop.Products.Products
{
private readonly ICurrentTenant _currentTenant;
private readonly IUnitOfWorkManager _unitOfWorkManager;
+ private readonly ILogger _logger;
private readonly IDistributedEventBus _distributedEventBus;
private readonly IProductRepository _productRepository;
private readonly IProductManager _productManager;
@@ -20,24 +22,26 @@ namespace EasyAbp.EShop.Products.Products
public OrderPaidEventHandler(
ICurrentTenant currentTenant,
IUnitOfWorkManager unitOfWorkManager,
+ ILogger logger,
IDistributedEventBus distributedEventBus,
IProductRepository productRepository,
IProductManager productManager)
{
_currentTenant = currentTenant;
_unitOfWorkManager = unitOfWorkManager;
+ _logger = logger;
_distributedEventBus = distributedEventBus;
_productRepository = productRepository;
_productManager = productManager;
}
-
+
[UnitOfWork(true)]
public virtual async Task HandleEventAsync(OrderPaidEto eventData)
{
using var changeTenant = _currentTenant.Change(eventData.Order.TenantId);
var models = new List();
-
+
foreach (var orderLine in eventData.Order.OrderLines)
{
// Todo: Should use ProductHistory.
@@ -48,10 +52,10 @@ namespace EasyAbp.EShop.Products.Products
if (productSku == null)
{
await PublishInventoryReductionResultEventAsync(eventData, false);
-
+
return;
}
-
+
if (product.InventoryStrategy != InventoryStrategy.ReduceAfterPayment)
{
continue;
@@ -60,37 +64,56 @@ namespace EasyAbp.EShop.Products.Products
if (!await _productManager.IsInventorySufficientAsync(product, productSku, orderLine.Quantity))
{
await PublishInventoryReductionResultEventAsync(eventData, false);
-
+
return;
}
- models.Add(new ConsumeInventoryModel
- {
- Product = product,
- ProductSku = productSku,
- StoreId = eventData.Order.StoreId,
- Quantity = orderLine.Quantity
- });
+ models.Add(new ConsumeInventoryModel(product, productSku, eventData.Order.StoreId, orderLine.Quantity));
}
foreach (var model in models)
{
- if (await _productManager.TryReduceInventoryAsync(model.Product, model.ProductSku, model.Quantity, true))
+ if (await _productManager.TryReduceInventoryAsync(
+ model.Product, model.ProductSku, model.Quantity, true))
{
continue;
}
+ await TryRollbackInventoriesAsync(models);
+
await _unitOfWorkManager.Current.RollbackAsync();
-
+
await PublishInventoryReductionResultEventAsync(eventData, false, true);
-
+
return;
}
await PublishInventoryReductionResultEventAsync(eventData, true);
}
- protected virtual async Task PublishInventoryReductionResultEventAsync(OrderPaidEto orderPaidEto, bool isSuccess, bool publishNow = false)
+ protected virtual async Task TryRollbackInventoriesAsync(IEnumerable models)
+ {
+ var result = true;
+
+ foreach (var model in models.Where(x => x.Consumed))
+ {
+ if (await _productManager.TryIncreaseInventoryAsync(
+ model.Product, model.ProductSku, model.Quantity, true))
+ {
+ continue;
+ }
+
+ result = false;
+ _logger.LogWarning(
+ "OrderPaidEventHandler: inventory rollback failed! productId = {productId}, productSkuId = {productSkuId}, quantity = {quantity}, reduceSold = {reduceSold}",
+ model.Product.Id, model.ProductSku.Id, model.Quantity, true);
+ }
+
+ return result;
+ }
+
+ protected virtual async Task PublishInventoryReductionResultEventAsync(OrderPaidEto orderPaidEto,
+ bool isSuccess, bool publishNow = false)
{
await _distributedEventBus.PublishAsync(new ProductInventoryReductionAfterOrderPaidResultEto
{
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/Product.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/Product.cs
index 9caccf04..1de5fec9 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/Product.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/Product.cs
@@ -1,6 +1,7 @@
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
+using EasyAbp.EShop.Products.Options.ProductGroups;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy;
@@ -9,37 +10,39 @@ namespace EasyAbp.EShop.Products.Products
public class Product : FullAuditedAggregateRoot, IProduct, IMultiTenant
{
public virtual Guid? TenantId { get; protected set; }
-
+
public virtual Guid StoreId { get; protected set; }
- [NotNull]
- public virtual string ProductGroupName { get; protected set; }
-
+ [NotNull] public virtual string ProductGroupName { get; protected set; }
+
public virtual Guid? ProductDetailId { get; protected set; }
- [CanBeNull]
- public virtual string UniqueName { get; protected set; }
+ [CanBeNull] public virtual string UniqueName { get; protected set; }
+
+ [NotNull] public virtual string DisplayName { get; protected set; }
- [NotNull]
- public virtual string DisplayName { get; protected set; }
-
public virtual InventoryStrategy InventoryStrategy { get; protected set; }
-
- [CanBeNull]
- public virtual string MediaResources { get; protected set; }
+
+ ///
+ /// If the value is null, it will fall back to DefaultInventoryProviderName
+ /// in the .
+ ///
+ public virtual string InventoryProviderName { get; protected set; }
+
+ [CanBeNull] public virtual string MediaResources { get; protected set; }
public virtual int DisplayOrder { get; protected set; }
public virtual bool IsPublished { get; protected set; }
-
+
public virtual bool IsStatic { get; protected set; }
-
+
public virtual bool IsHidden { get; protected set; }
-
+
public virtual TimeSpan? PaymentExpireIn { get; protected set; }
-
+
public virtual List ProductAttributes { get; protected set; }
-
+
public virtual List ProductSkus { get; protected set; }
protected Product()
@@ -55,6 +58,7 @@ namespace EasyAbp.EShop.Products.Products
[CanBeNull] string uniqueName,
[NotNull] string displayName,
InventoryStrategy inventoryStrategy,
+ [CanBeNull] string inventoryProviderName,
bool isPublished,
bool isStatic,
bool isHidden,
@@ -70,13 +74,14 @@ namespace EasyAbp.EShop.Products.Products
UniqueName = uniqueName?.Trim();
DisplayName = displayName;
InventoryStrategy = inventoryStrategy;
+ InventoryProviderName = inventoryProviderName;
IsPublished = isPublished;
IsStatic = isStatic;
IsHidden = isHidden;
PaymentExpireIn = paymentExpireIn;
MediaResources = mediaResources;
DisplayOrder = displayOrder;
-
+
ProductAttributes = new List();
ProductSkus = new List();
}
@@ -92,4 +97,4 @@ namespace EasyAbp.EShop.Products.Products
UniqueName = UniqueName?.Trim();
}
}
-}
+}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductInventoryProviderResolver.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductInventoryProviderResolver.cs
new file mode 100644
index 00000000..bd1e4472
--- /dev/null
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductInventoryProviderResolver.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using EasyAbp.EShop.Products.Options;
+using EasyAbp.EShop.Products.ProductInventories;
+using JetBrains.Annotations;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Volo.Abp.DependencyInjection;
+
+namespace EasyAbp.EShop.Products.Products;
+
+public class ProductInventoryProviderResolver : IProductInventoryProviderResolver, ITransientDependency
+{
+ protected static ConcurrentDictionary NameToProviderTypeMapping { get; } = new();
+
+ protected IServiceProvider ServiceProvider { get; }
+
+ public ProductInventoryProviderResolver(IServiceProvider serviceProvider)
+ {
+ ServiceProvider = serviceProvider;
+ }
+
+ public virtual Task ExistProviderAsync(string providerName)
+ {
+ TryBuildNameToProviderTypeMapping();
+
+ return Task.FromResult(NameToProviderTypeMapping.ContainsKey(providerName));
+ }
+
+ public virtual Task GetAsync(Product product)
+ {
+ if (!product.InventoryProviderName.IsNullOrWhiteSpace())
+ {
+ return Task.FromResult(GetProviderByName(product.InventoryProviderName));
+ }
+
+ var options = ServiceProvider.GetRequiredService>();
+ var productGroupConfiguration = options.Value.Groups.GetConfiguration(product.ProductGroupName);
+
+ if (!productGroupConfiguration.DefaultInventoryProviderName.IsNullOrWhiteSpace())
+ {
+ return Task.FromResult(GetProviderByName(productGroupConfiguration.DefaultInventoryProviderName));
+ }
+
+ return Task.FromResult(GetProviderByName(options.Value.DefaultInventoryProviderName));
+ }
+
+ protected virtual IProductInventoryProvider GetProviderByName([CanBeNull] string providerName)
+ {
+ if (providerName.IsNullOrEmpty())
+ {
+ providerName = DefaultProductInventoryProvider.DefaultProductInventoryProviderName;
+ }
+
+ TryBuildNameToProviderTypeMapping();
+
+ var providerType = NameToProviderTypeMapping[providerName!];
+
+ return (IProductInventoryProvider)ServiceProvider.GetService(providerType);
+ }
+
+ protected virtual void TryBuildNameToProviderTypeMapping()
+ {
+ if (!NameToProviderTypeMapping.IsEmpty)
+ {
+ return;
+ }
+
+ var options = ServiceProvider.GetRequiredService>().Value;
+
+ foreach (var pair in options.InventoryProviders.GetConfigurationsDictionary())
+ {
+ NameToProviderTypeMapping[pair.Key] = pair.Value.ProviderType;
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductManager.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductManager.cs
index abfce434..a6f7e35c 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductManager.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductManager.cs
@@ -5,7 +5,7 @@ using System.Threading.Tasks;
using EasyAbp.EShop.Products.Options.ProductGroups;
using EasyAbp.EShop.Products.ProductCategories;
using EasyAbp.EShop.Products.ProductDetails;
-using Microsoft.Extensions.DependencyInjection;
+using EasyAbp.EShop.Products.ProductInventories;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Services;
using Volo.Abp.Uow;
@@ -18,7 +18,7 @@ namespace EasyAbp.EShop.Products.Products
private readonly IProductPriceProvider _productPriceProvider;
private readonly IProductDetailRepository _productDetailRepository;
private readonly IProductCategoryRepository _productCategoryRepository;
- private readonly IProductInventoryProvider _productInventoryProvider;
+ private readonly IProductInventoryProviderResolver _productInventoryProviderResolver;
private readonly IAttributeOptionIdsSerializer _attributeOptionIdsSerializer;
private readonly IProductGroupConfigurationProvider _productGroupConfigurationProvider;
@@ -27,7 +27,7 @@ namespace EasyAbp.EShop.Products.Products
IProductPriceProvider productPriceProvider,
IProductDetailRepository productDetailRepository,
IProductCategoryRepository productCategoryRepository,
- IProductInventoryProvider productInventoryProvider,
+ IProductInventoryProviderResolver productInventoryProviderResolver,
IAttributeOptionIdsSerializer attributeOptionIdsSerializer,
IProductGroupConfigurationProvider productGroupConfigurationProvider)
{
@@ -35,7 +35,7 @@ namespace EasyAbp.EShop.Products.Products
_productPriceProvider = productPriceProvider;
_productDetailRepository = productDetailRepository;
_productCategoryRepository = productCategoryRepository;
- _productInventoryProvider = productInventoryProvider;
+ _productInventoryProviderResolver = productInventoryProviderResolver;
_attributeOptionIdsSerializer = attributeOptionIdsSerializer;
_productGroupConfigurationProvider = productGroupConfigurationProvider;
}
@@ -44,9 +44,11 @@ namespace EasyAbp.EShop.Products.Products
public virtual async Task CreateAsync(Product product, IEnumerable categoryIds = null)
{
product.TrimUniqueName();
-
+
await CheckProductGroupNameAsync(product);
-
+
+ await CheckInventoryProviderNameAsync(product);
+
await CheckProductUniqueNameAsync(product);
await _productRepository.InsertAsync(product, autoSave: true);
@@ -64,15 +66,30 @@ namespace EasyAbp.EShop.Products.Products
{
throw new NonexistentProductGroupException(product.ProductGroupName);
}
-
+
return Task.CompletedTask;
}
+ protected virtual async Task CheckInventoryProviderNameAsync(Product product)
+ {
+ if (product.InventoryProviderName.IsNullOrEmpty())
+ {
+ return;
+ }
+
+ if (!await _productInventoryProviderResolver.ExistProviderAsync(product.InventoryProviderName!))
+ {
+ throw new NonexistentInventoryProviderException(product.InventoryProviderName);
+ }
+ }
+
[UnitOfWork(true)]
public virtual async Task UpdateAsync(Product product, IEnumerable categoryIds = null)
{
await CheckProductGroupNameAsync(product);
+ await CheckInventoryProviderNameAsync(product);
+
await CheckProductUniqueNameAsync(product);
await _productRepository.UpdateAsync(product, autoSave: true);
@@ -91,7 +108,7 @@ namespace EasyAbp.EShop.Products.Products
await _productRepository.DeleteAsync(product, true);
}
-
+
[UnitOfWork(true)]
public virtual async Task DeleteAsync(Guid id)
{
@@ -108,13 +125,13 @@ namespace EasyAbp.EShop.Products.Products
await CheckSkuAttributeOptionsAsync(product, productSku);
await CheckProductSkuNameUniqueAsync(product, productSku);
-
+
productSku.TrimName();
-
+
product.ProductSkus.AddIfNotContains(productSku);
-
+
await CheckProductDetailAsync(product);
-
+
return await _productRepository.UpdateAsync(product, true);
}
@@ -124,9 +141,9 @@ namespace EasyAbp.EShop.Products.Products
{
return Task.CompletedTask;
}
-
+
if (product.ProductSkus.Where(sku => sku.Id != productSku.Id)
- .FirstOrDefault(sku => sku.Name == productSku.Name) != null)
+ .FirstOrDefault(sku => sku.Name == productSku.Name) != null)
{
throw new ProductSkuCodeDuplicatedException(product.Id, productSku.Name);
}
@@ -169,7 +186,7 @@ namespace EasyAbp.EShop.Products.Products
public virtual async Task DeleteSkuAsync(Product product, ProductSku productSku)
{
product.ProductSkus.Remove(productSku);
-
+
return await _productRepository.UpdateAsync(product, true);
}
@@ -178,20 +195,20 @@ namespace EasyAbp.EShop.Products.Products
{
await _productRepository.CheckUniqueNameAsync(product);
}
-
+
protected virtual async Task CheckProductDetailAsync(Product product)
{
if (product.ProductDetailId.HasValue)
{
await CheckProductDetailExistAsync(product.ProductDetailId.Value, product.StoreId);
}
-
+
foreach (var sku in product.ProductSkus.Where(x => x.ProductDetailId.HasValue))
{
await CheckProductDetailExistAsync(sku.ProductDetailId!.Value, product.StoreId);
}
}
-
+
[UnitOfWork]
protected virtual async Task CheckProductDetailExistAsync(Guid productDetailId, Guid storeId)
{
@@ -202,7 +219,7 @@ namespace EasyAbp.EShop.Products.Products
throw new EntityNotFoundException(typeof(ProductDetail), productDetailId);
}
}
-
+
[UnitOfWork(true)]
protected virtual async Task UpdateProductCategoriesAsync(Guid productId, IEnumerable categoryIds)
{
@@ -212,7 +229,7 @@ namespace EasyAbp.EShop.Products.Products
{
return;
}
-
+
foreach (var categoryId in categoryIds)
{
await _productCategoryRepository.InsertAsync(
@@ -222,24 +239,37 @@ namespace EasyAbp.EShop.Products.Products
public virtual async Task IsInventorySufficientAsync(Product product, ProductSku productSku, int quantity)
{
- var inventoryData = await _productInventoryProvider.GetInventoryDataAsync(product, productSku);
-
+ var model = new InventoryQueryModel(product.TenantId, product.StoreId, product.Id, productSku.Id);
+
+ var inventoryData =
+ await (await _productInventoryProviderResolver.GetAsync(product)).GetInventoryDataAsync(model);
+
return product.InventoryStrategy == InventoryStrategy.NoNeed || inventoryData.Inventory - quantity >= 0;
}
public virtual async Task GetInventoryDataAsync(Product product, ProductSku productSku)
{
- return await _productInventoryProvider.GetInventoryDataAsync(product, productSku);
+ var model = new InventoryQueryModel(product.TenantId, product.StoreId, product.Id, productSku.Id);
+
+ return await (await _productInventoryProviderResolver.GetAsync(product)).GetInventoryDataAsync(model);
}
- public virtual async Task TryIncreaseInventoryAsync(Product product, ProductSku productSku, int quantity, bool reduceSold)
+ public virtual async Task TryIncreaseInventoryAsync(Product product, ProductSku productSku, int quantity,
+ bool reduceSold)
{
- return await _productInventoryProvider.TryIncreaseInventoryAsync(product, productSku, quantity, reduceSold);
+ var model = new InventoryQueryModel(product.TenantId, product.StoreId, product.Id, productSku.Id);
+
+ return await (await _productInventoryProviderResolver.GetAsync(product))
+ .TryIncreaseInventoryAsync(model, quantity, reduceSold);
}
- public virtual async Task TryReduceInventoryAsync(Product product, ProductSku productSku, int quantity, bool increaseSold)
+ public virtual async Task TryReduceInventoryAsync(Product product, ProductSku productSku, int quantity,
+ bool increaseSold)
{
- return await _productInventoryProvider.TryReduceInventoryAsync(product, productSku, quantity, increaseSold);
+ var model = new InventoryQueryModel(product.TenantId, product.StoreId, product.Id, productSku.Id);
+
+ return await (await _productInventoryProviderResolver.GetAsync(product))
+ .TryReduceInventoryAsync(model, quantity, increaseSold);
}
public virtual async Task GetRealPriceAsync(Product product, ProductSku productSku)
@@ -247,7 +277,7 @@ namespace EasyAbp.EShop.Products.Products
var price = await _productPriceProvider.GetPriceAsync(product, productSku);
var discountedPrice = price;
-
+
// Todo: provider execution ordering.
foreach (var provider in LazyServiceProvider.LazyGetService>())
{
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductView.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductView.cs
index 6da6d49c..cd0de652 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductView.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductView.cs
@@ -22,6 +22,8 @@ namespace EasyAbp.EShop.Products.Products
public virtual InventoryStrategy InventoryStrategy { get; protected set; }
+ public string InventoryProviderName { get; protected set; }
+
public virtual string MediaResources { get; protected set; }
public virtual int DisplayOrder { get; protected set; }
@@ -33,19 +35,19 @@ namespace EasyAbp.EShop.Products.Products
public virtual bool IsHidden { get; protected set; }
#endregion
-
+
public virtual string ProductGroupDisplayName { get; protected set; }
-
+
public virtual decimal? MinimumPrice { get; protected set; }
-
+
public virtual decimal? MaximumPrice { get; protected set; }
-
+
public virtual long Sold { get; protected set; }
-
+
protected ProductView()
{
}
-
+
public ProductView(
Guid id,
Guid? tenantId,
@@ -55,6 +57,7 @@ namespace EasyAbp.EShop.Products.Products
string uniqueName,
string displayName,
InventoryStrategy inventoryStrategy,
+ string inventoryProviderName,
bool isPublished,
bool isStatic,
bool isHidden,
@@ -73,27 +76,28 @@ namespace EasyAbp.EShop.Products.Products
UniqueName = uniqueName?.Trim();
DisplayName = displayName;
InventoryStrategy = inventoryStrategy;
+ InventoryProviderName = inventoryProviderName;
IsPublished = isPublished;
IsStatic = isStatic;
IsHidden = isHidden;
MediaResources = mediaResources;
DisplayOrder = displayOrder;
-
+
ProductGroupDisplayName = productGroupDisplayName;
MinimumPrice = minimumPrice;
MaximumPrice = maximumPrice;
Sold = sold;
}
-
+
public void SetSold(long sold)
{
Sold = sold;
}
-
+
public void SetPrices(decimal? minimumPrice, decimal? maximumPrice)
{
MinimumPrice = minimumPrice;
MaximumPrice = maximumPrice;
}
}
-}
+}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.EntityFrameworkCore/EasyAbp/EShop/Products/ProductInventories/ProductInventoryRepository.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.EntityFrameworkCore/EasyAbp/EShop/Products/ProductInventories/ProductInventoryRepository.cs
index 4001e37b..a9c380cc 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.EntityFrameworkCore/EasyAbp/EShop/Products/ProductInventories/ProductInventoryRepository.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.EntityFrameworkCore/EasyAbp/EShop/Products/ProductInventories/ProductInventoryRepository.cs
@@ -4,7 +4,6 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using EasyAbp.EShop.Products.EntityFrameworkCore;
-using EasyAbp.EShop.Products.Products;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
@@ -29,9 +28,9 @@ namespace EasyAbp.EShop.Products.ProductInventories
.FirstOrDefaultAsync(cancellationToken);
}
- public async Task> GetInventoryDataDictionaryAsync(List productSkuIds, CancellationToken cancellationToken = default)
+ public async Task> GetSkuIdInventoryDataMappingAsync(List productSkuIds, CancellationToken cancellationToken = default)
{
- return await GetQueryable()
+ return await (await GetQueryableAsync())
.Where(x => productSkuIds.Contains(x.ProductSkuId))
.ToDictionaryAsync(x => x.ProductSkuId, x => new InventoryDataModel
{
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.HttpApi/EasyAbp/EShop/Products/ProductInventories/ProductInventoryController.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.HttpApi/EasyAbp/EShop/Products/ProductInventories/ProductInventoryController.cs
deleted file mode 100644
index 375e5b48..00000000
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.HttpApi/EasyAbp/EShop/Products/ProductInventories/ProductInventoryController.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using System;
-using System.Threading.Tasks;
-using EasyAbp.EShop.Products.ProductInventories.Dtos;
-using Microsoft.AspNetCore.Mvc;
-using Volo.Abp;
-using Volo.Abp.Application.Dtos;
-
-namespace EasyAbp.EShop.Products.ProductInventories
-{
- [RemoteService(Name = EShopProductsRemoteServiceConsts.RemoteServiceName)]
- [Route("/api/e-shop/products/product-inventory")]
- public class ProductInventoryController : ProductsController, IProductInventoryAppService
- {
- private readonly IProductInventoryAppService _service;
-
- public ProductInventoryController(IProductInventoryAppService service)
- {
- _service = service;
- }
-
- [HttpGet]
- public Task GetAsync(Guid productId, Guid productSkuId)
- {
- return _service.GetAsync(productId, productSkuId);
- }
-
- [HttpPut]
- public Task UpdateAsync(UpdateProductInventoryDto input)
- {
- return _service.UpdateAsync(input);
- }
- }
-}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.HttpApi/EasyAbp/EShop/Products/Products/ProductController.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.HttpApi/EasyAbp/EShop/Products/Products/ProductController.cs
index 58bb6e85..09565735 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.HttpApi/EasyAbp/EShop/Products/Products/ProductController.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.HttpApi/EasyAbp/EShop/Products/Products/ProductController.cs
@@ -93,5 +93,13 @@ namespace EasyAbp.EShop.Products.Products
{
return _service.GetProductGroupListAsync();
}
+
+ [HttpPost]
+ [Route("{id}/sku/{productSkuId}/change-inventory")]
+ public Task ChangeInventoryAsync(Guid id, Guid productSkuId,
+ ChangeProductInventoryDto input)
+ {
+ return _service.ChangeInventoryAsync(id, productSkuId, input);
+ }
}
}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/ViewModels/CreateEditProductViewModel.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/ViewModels/CreateEditProductViewModel.cs
index 3c2c4ea8..d1e962a7 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/ViewModels/CreateEditProductViewModel.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/ViewModels/CreateEditProductViewModel.cs
@@ -50,7 +50,11 @@ namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.Product.ViewM
[Display(Name = "ProductInventoryStrategy")]
public InventoryStrategy InventoryStrategy { get; set; }
-
+
+ [Placeholder("ProductInventoryProviderNamePlaceholder")]
+ [Display(Name = "ProductInventoryProviderName")]
+ public string InventoryProviderName { get; set; }
+
[Display(Name = "ProductDisplayOrder")]
public int DisplayOrder { get; set; }
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/ChangeInventoryModal.cshtml.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/ChangeInventoryModal.cshtml.cs
index 7d8d7208..1733ec0b 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/ChangeInventoryModal.cshtml.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/ChangeInventoryModal.cshtml.cs
@@ -1,7 +1,7 @@
using System;
using System.Threading.Tasks;
-using EasyAbp.EShop.Products.ProductInventories;
-using EasyAbp.EShop.Products.ProductInventories.Dtos;
+using EasyAbp.EShop.Products.Products;
+using EasyAbp.EShop.Products.Products.Dtos;
using EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku.ViewModels;
using Microsoft.AspNetCore.Mvc;
@@ -12,7 +12,7 @@ namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku
[HiddenInput]
[BindProperty(SupportsGet = true)]
public Guid ProductId { get; set; }
-
+
[HiddenInput]
[BindProperty(SupportsGet = true)]
public Guid ProductSkuId { get; set; }
@@ -20,17 +20,18 @@ namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku
[BindProperty]
public ChangeProductInventoryViewModel ViewModel { get; set; }
- private readonly IProductInventoryAppService _service;
+ private readonly IProductAppService _service;
- public ChangeInventoryModal(IProductInventoryAppService service)
+ public ChangeInventoryModal(IProductAppService service)
{
_service = service;
}
public virtual async Task OnGetAsync()
{
- var dto = await _service.GetAsync(ProductId, ProductSkuId);
-
+ var product = await _service.GetAsync(ProductId);
+ product.GetSkuById(ProductSkuId); // ensure the specified sku exists.
+
ViewModel = new ChangeProductInventoryViewModel
{
ChangedInventory = 0,
@@ -40,15 +41,13 @@ namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku
public virtual async Task OnPostAsync()
{
- await _service.UpdateAsync(new UpdateProductInventoryDto
+ await _service.ChangeInventoryAsync(ProductId, ProductSkuId, new ChangeProductInventoryDto
{
- ProductId = ProductId,
- ProductSkuId = ProductSkuId,
ChangedInventory = ViewModel.ProductInventoryChangeType == InventoryChangeType.IncreaseInventory
? ViewModel.ChangedInventory
: -ViewModel.ChangedInventory
});
-
+
return NoContent();
}
}
diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/CreateModal.cshtml.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/CreateModal.cshtml.cs
index 667e0cb2..234bd13b 100644
--- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/CreateModal.cshtml.cs
+++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/CreateModal.cshtml.cs
@@ -4,8 +4,6 @@ using System.Linq;
using System.Threading.Tasks;
using EasyAbp.EShop.Products.ProductDetails;
using EasyAbp.EShop.Products.ProductDetails.Dtos;
-using EasyAbp.EShop.Products.ProductInventories;
-using EasyAbp.EShop.Products.ProductInventories.Dtos;
using EasyAbp.EShop.Products.Products;
using EasyAbp.EShop.Products.Products.Dtos;
using EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku.ViewModels;
@@ -19,25 +17,22 @@ namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku
[HiddenInput]
[BindProperty(SupportsGet = true)]
public Guid ProductId { get; set; }
-
+
[BindProperty]
public CreateProductSkuViewModel ProductSku { get; set; } = new CreateProductSkuViewModel();
-
+
[BindProperty]
public Dictionary SelectedAttributeOptionIdDict { get; set; }
-
+
public Dictionary> Attributes { get; set; }
- private readonly IProductInventoryAppService _productInventoryAppService;
private readonly IProductDetailAppService _productDetailAppService;
private readonly IProductAppService _productAppService;
public CreateModalModel(
- IProductInventoryAppService productInventoryAppService,
IProductDetailAppService productDetailAppService,
IProductAppService productAppService)
{
- _productInventoryAppService = productInventoryAppService;
_productDetailAppService = productDetailAppService;
_productAppService = productAppService;
}
@@ -47,7 +42,7 @@ namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku
var product = await _productAppService.GetAsync(ProductId);
Attributes = new Dictionary>();
-
+
foreach (var attribute in product.ProductAttributes.ToList())
{
Attributes.Add(attribute.DisplayName,
@@ -55,13 +50,12 @@ namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku
.Select(dto => new SelectListItem(dto.DisplayName, dto.Id.ToString())).ToList());
}
}
-
+
public virtual async Task OnPostAsync()
{
var createDto = ObjectMapper.Map(ProductSku);
createDto.AttributeOptionIds = SelectedAttributeOptionIdDict.Values.ToList();
-
if (ProductSku.ProductDetail.HasContent())
{
var detail = await _productDetailAppService.CreateAsync(
@@ -70,15 +64,13 @@ namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku
createDto.ProductDetailId = detail.Id;
}
-
+
var product = await _productAppService.CreateSkuAsync(ProductId, createDto);
var productSku = product.ProductSkus
.Single(x => !x.AttributeOptionIds.Except(createDto.AttributeOptionIds).Any());
- await _productInventoryAppService.UpdateAsync(new UpdateProductInventoryDto
+ await _productAppService.ChangeInventoryAsync(product.Id, productSku.Id, new ChangeProductInventoryDto
{
- ProductId = product.Id,
- ProductSkuId = productSku.Id,
ChangedInventory = ProductSku.Inventory
});
diff --git a/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Application.Tests/ProductInventories/ProductInventoryAppServiceTests.cs b/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Application.Tests/ProductInventories/ProductInventoryAppServiceTests.cs
deleted file mode 100644
index e6e069de..00000000
--- a/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Application.Tests/ProductInventories/ProductInventoryAppServiceTests.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using Shouldly;
-using System.Threading.Tasks;
-using Xunit;
-
-namespace EasyAbp.EShop.Products.ProductInventories
-{
- public class ProductInventoryAppServiceTests : ProductsApplicationTestBase
- {
- private readonly IProductInventoryAppService _productInventoryAppService;
-
- public ProductInventoryAppServiceTests()
- {
- _productInventoryAppService = GetRequiredService();
- }
-
- [Fact]
- public async Task Test1()
- {
- // Arrange
-
- // Act
-
- // Assert
- }
- }
-}
diff --git a/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Domain.Tests/Products/ProductDomainTests.cs b/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Domain.Tests/Products/ProductDomainTests.cs
index 6bc7dca4..5ab449a8 100644
--- a/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Domain.Tests/Products/ProductDomainTests.cs
+++ b/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Domain.Tests/Products/ProductDomainTests.cs
@@ -25,13 +25,12 @@ namespace EasyAbp.EShop.Products.Products
public async Task Should_Set_ProductDetailId()
{
var product2 = new Product(ProductsTestData.Product2Id, null, ProductsTestData.Store1Id, "Default",
- ProductsTestData.ProductDetails2Id, "Ball", "Ball", InventoryStrategy.NoNeed, true, false, false, null,
- null, 0);
-
+ ProductsTestData.ProductDetails2Id, "Ball", "Ball", InventoryStrategy.NoNeed, null, true, false, false,
+ null, null, 0);
await ProductManager.CreateAsync(product2);
product2 = await ProductRepository.GetAsync(product2.Id);
-
+
product2.ProductDetailId.ShouldBe(ProductsTestData.ProductDetails2Id);
}
@@ -42,9 +41,9 @@ namespace EasyAbp.EShop.Products.Products
{
var product1 = await ProductRepository.GetAsync(ProductsTestData.Product1Id);
var sku1 = product1.ProductSkus.Single(x => x.Id == ProductsTestData.Product1Sku1Id);
-
+
sku1.ProductDetailId.ShouldBeNull();
-
+
typeof(ProductSku).GetProperty(nameof(ProductSku.ProductDetailId))!.SetValue(sku1,
ProductsTestData.ProductDetails1Id);
@@ -65,20 +64,20 @@ namespace EasyAbp.EShop.Products.Products
product1.ProductDetailId.ShouldBe(ProductsTestData.ProductDetails1Id);
var product2 = new Product(ProductsTestData.Product2Id, null, ProductsTestData.Store1Id, "Default",
- ProductsTestData.ProductDetails2Id, "Ball", "Ball", InventoryStrategy.NoNeed, true, false, false, null,
- null, 0);
+ ProductsTestData.ProductDetails2Id, "Ball", "Ball", InventoryStrategy.NoNeed, null, true, false, false,
+ null, null, 0);
await ProductManager.CreateAsync(product2);
product2 = await ProductRepository.GetAsync(product2.Id);
-
+
product2.ProductDetailId.ShouldBe(ProductsTestData.ProductDetails2Id);
typeof(Product).GetProperty(nameof(Product.ProductDetailId))!.SetValue(product2,
ProductsTestData.ProductDetails1Id);
-
+
await ProductManager.UpdateAsync(product2);
-
+
product2 = await ProductRepository.GetAsync(product2.Id);
product2.ProductDetailId.ShouldBe(ProductsTestData.ProductDetails1Id);
@@ -88,17 +87,17 @@ namespace EasyAbp.EShop.Products.Products
public async Task Should_Remove_ProductDetailId()
{
await Should_Set_ProductDetailId();
-
+
var product2 = await ProductRepository.GetAsync(ProductsTestData.Product2Id);
product2.ProductDetailId.ShouldNotBeNull();
-
+
typeof(Product).GetProperty(nameof(Product.ProductDetailId))!.SetValue(product2, null);
await ProductManager.UpdateAsync(product2);
product2 = await ProductRepository.GetAsync(product2.Id);
-
+
product2.ProductDetailId.ShouldBeNull();
}
@@ -135,7 +134,7 @@ namespace EasyAbp.EShop.Products.Products
ProductsTestData.Product1Attribute1Option4Id,
ProductsTestData.Product1Attribute2Option2Id
};
-
+
await Should.NotThrowAsync(async () =>
{
await ProductManager.CreateSkuAsync(product1, await CreateTestSkuAsync(attributeOptionIds));
@@ -178,10 +177,27 @@ namespace EasyAbp.EShop.Products.Products
});
}
+ [Fact]
+ public async Task Should_Use_Fake_Inventory_Provider()
+ {
+ var product2 = new Product(ProductsTestData.Product2Id, null, ProductsTestData.Store1Id, "Default",
+ ProductsTestData.ProductDetails2Id, "Ball", "Ball", InventoryStrategy.NoNeed, "Fake", true, false,
+ false, null, null, 0);
+
+ await ProductManager.CreateAsync(product2);
+
+ var fakeSku = new ProductSku(Guid.NewGuid(), "", null, "", null, 0m, 1, 1, null, null, null);
+
+ var inventoryDataModel = await ProductManager.GetInventoryDataAsync(product2, fakeSku);
+
+ inventoryDataModel.ShouldNotBeNull();
+ inventoryDataModel.Inventory.ShouldBe(9998);
+ }
+
private async Task CreateTestSkuAsync(IEnumerable attributeOptionIds)
{
return new ProductSku(Guid.NewGuid(), await AttributeOptionIdsSerializer.SerializeAsync(attributeOptionIds),
"test-sku", "CNY", null, 0m, 1, 10, null, null, null);
}
}
-}
+}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/EShopProductsTestBaseModule.cs b/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/EShopProductsTestBaseModule.cs
index 330bbe21..03d557e8 100644
--- a/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/EShopProductsTestBaseModule.cs
+++ b/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/EShopProductsTestBaseModule.cs
@@ -1,4 +1,7 @@
-using Microsoft.Extensions.DependencyInjection;
+using EasyAbp.EShop.Products.Options;
+using EasyAbp.EShop.Products.Options.ProductGroups;
+using EasyAbp.EShop.Products.Products;
+using Microsoft.Extensions.DependencyInjection;
using Volo.Abp;
using Volo.Abp.Authorization;
using Volo.Abp.Autofac;
@@ -19,6 +22,17 @@ namespace EasyAbp.EShop.Products
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAlwaysAllowAuthorization();
+
+ Configure(options =>
+ {
+ options.InventoryProviders.Configure(
+ "Fake", provider =>
+ {
+ provider.DisplayName = "Fake";
+ provider.Description = "For tests";
+ provider.ProviderType = typeof(FakeProductInventoryProvider);
+ });
+ });
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
diff --git a/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/FakeProductInventoryProvider.cs b/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/FakeProductInventoryProvider.cs
new file mode 100644
index 00000000..e49d2ff2
--- /dev/null
+++ b/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/FakeProductInventoryProvider.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using EasyAbp.EShop.Products.ProductInventories;
+using Volo.Abp.DependencyInjection;
+
+namespace EasyAbp.EShop.Products;
+
+public class FakeProductInventoryProvider : IProductInventoryProvider, ITransientDependency
+{
+ public string InventoryProviderName { get; } = "Fake";
+
+ private static InventoryDataModel Model { get; } = new()
+ {
+ Inventory = 9998,
+ Sold = 0
+ };
+
+ public Task GetInventoryDataAsync(InventoryQueryModel model)
+ {
+ return Task.FromResult(Model);
+ }
+
+ public Task> GetSkuIdInventoryDataMappingAsync(
+ IList models)
+ {
+ var result = new Dictionary();
+
+ foreach (var model in models)
+ {
+ result.Add(model.ProductSkuId, Model);
+ }
+
+ return Task.FromResult(result);
+ }
+
+ public Task TryIncreaseInventoryAsync(InventoryQueryModel model, int quantity, bool decreaseSold)
+ {
+ Model.Inventory++;
+
+ if (decreaseSold)
+ {
+ Model.Sold--;
+ }
+
+ return Task.FromResult(true);
+ }
+
+ public Task TryReduceInventoryAsync(InventoryQueryModel model, int quantity, bool increaseSold)
+ {
+ Model.Inventory--;
+
+ if (increaseSold)
+ {
+ Model.Sold++;
+ }
+
+ return Task.FromResult(true);
+ }
+}
\ No newline at end of file
diff --git a/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/ProductsTestDataBuilder.cs b/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/ProductsTestDataBuilder.cs
index b26cae67..3962853d 100644
--- a/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/ProductsTestDataBuilder.cs
+++ b/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/ProductsTestDataBuilder.cs
@@ -45,7 +45,7 @@ namespace EasyAbp.EShop.Products
"Product details for store 1"), true);
var product = new Product(ProductsTestData.Product1Id, null, ProductsTestData.Store1Id, "Default",
- productDetail1.Id, "Cake", "Cake", InventoryStrategy.NoNeed, true, false, false, null, null, 0);
+ productDetail1.Id, "Cake", "Cake", InventoryStrategy.NoNeed, null, true, false, false, null, null, 0);
var attribute1 = new ProductAttribute(ProductsTestData.Product1Attribute1Id, "Size", null, 2);
var attribute2 = new ProductAttribute(ProductsTestData.Product1Attribute2Id, "Color", null, 1);
diff --git a/plugins/Inventories/DaprActors/README.md b/plugins/Inventories/DaprActors/README.md
new file mode 120000
index 00000000..6aba629a
--- /dev/null
+++ b/plugins/Inventories/DaprActors/README.md
@@ -0,0 +1 @@
+../../../docs/plugins/inventories/dapr-actors/README.md
\ No newline at end of file
diff --git a/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions/EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions.csproj b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions/EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions.csproj
new file mode 100644
index 00000000..f5d2ebb7
--- /dev/null
+++ b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions/EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions.csproj
@@ -0,0 +1,18 @@
+
+
+
+
+
+ net6.0
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions/EasyAbp/EShop/Plugins/Inventories/DaprActors/EShopPluginsInventoriesDaprActorsAbstractionsModule.cs b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions/EasyAbp/EShop/Plugins/Inventories/DaprActors/EShopPluginsInventoriesDaprActorsAbstractionsModule.cs
new file mode 100644
index 00000000..37b93cf2
--- /dev/null
+++ b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions/EasyAbp/EShop/Plugins/Inventories/DaprActors/EShopPluginsInventoriesDaprActorsAbstractionsModule.cs
@@ -0,0 +1,11 @@
+using EasyAbp.EShop.Products;
+using Volo.Abp.Modularity;
+
+namespace EasyAbp.EShop.Plugins.Inventories.DaprActors;
+
+[DependsOn(
+ typeof(EShopProductsDomainSharedModule)
+)]
+public class EShopPluginsInventoriesDaprActorsAbstractionsModule : AbpModule
+{
+}
\ No newline at end of file
diff --git a/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions/EasyAbp/EShop/Plugins/Inventories/DaprActors/IInventoryActor.cs b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions/EasyAbp/EShop/Plugins/Inventories/DaprActors/IInventoryActor.cs
new file mode 100644
index 00000000..714904b5
--- /dev/null
+++ b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions/EasyAbp/EShop/Plugins/Inventories/DaprActors/IInventoryActor.cs
@@ -0,0 +1,13 @@
+using System.Threading.Tasks;
+using Dapr.Actors;
+
+namespace EasyAbp.EShop.Plugins.Inventories.DaprActors;
+
+public interface IInventoryActor : IActor
+{
+ Task GetInventoryStateAsync();
+
+ Task IncreaseInventoryAsync(int quantity, bool decreaseSold);
+
+ Task ReduceInventoryAsync(int quantity, bool increaseSold);
+}
\ No newline at end of file
diff --git a/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions/EasyAbp/EShop/Plugins/Inventories/DaprActors/InventoryStateModel.cs b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions/EasyAbp/EShop/Plugins/Inventories/DaprActors/InventoryStateModel.cs
new file mode 100644
index 00000000..96f0ef28
--- /dev/null
+++ b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions/EasyAbp/EShop/Plugins/Inventories/DaprActors/InventoryStateModel.cs
@@ -0,0 +1,8 @@
+namespace EasyAbp.EShop.Plugins.Inventories.DaprActors;
+
+public class InventoryStateModel
+{
+ public int Inventory { get; set; }
+
+ public long Sold { get; set; }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions/FodyWeavers.xml b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions/FodyWeavers.xml
new file mode 100644
index 00000000..1715698c
--- /dev/null
+++ b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions/FodyWeavers.xsd b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions/FodyWeavers.xsd
new file mode 100644
index 00000000..ffa6fc4b
--- /dev/null
+++ b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.Abstractions/FodyWeavers.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.AspNetCore/EasyAbp.EShop.Plugins.Inventories.DaprActors.AspNetCore.csproj b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.AspNetCore/EasyAbp.EShop.Plugins.Inventories.DaprActors.AspNetCore.csproj
new file mode 100644
index 00000000..9a6f080b
--- /dev/null
+++ b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.AspNetCore/EasyAbp.EShop.Plugins.Inventories.DaprActors.AspNetCore.csproj
@@ -0,0 +1,19 @@
+
+
+
+
+
+ net6.0
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.AspNetCore/EasyAbp/EShop/Plugins/Inventories/DaprActors/EShopPluginsInventoriesDaprActorsAspNetCoreModule.cs b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.AspNetCore/EasyAbp/EShop/Plugins/Inventories/DaprActors/EShopPluginsInventoriesDaprActorsAspNetCoreModule.cs
new file mode 100644
index 00000000..88d04834
--- /dev/null
+++ b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.AspNetCore/EasyAbp/EShop/Plugins/Inventories/DaprActors/EShopPluginsInventoriesDaprActorsAspNetCoreModule.cs
@@ -0,0 +1,29 @@
+using EasyAbp.EShop.Products;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.AspNetCore;
+using Volo.Abp.Modularity;
+
+namespace EasyAbp.EShop.Plugins.Inventories.DaprActors;
+
+[DependsOn(
+ typeof(AbpAspNetCoreModule),
+ typeof(EShopPluginsInventoriesDaprActorsModule)
+)]
+public class EShopPluginsInventoriesDaprActorsAspNetCoreModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ context.Services.AddActors(options => { options.Actors.RegisterActor(); });
+
+ Configure(options =>
+ {
+ options.EndpointConfigureActions.Add(ctx =>
+ {
+ // Register actors handlers that interface with the Dapr runtime.
+ ctx.Endpoints.MapActorsHandlers();
+ });
+ });
+ }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.AspNetCore/FodyWeavers.xml b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.AspNetCore/FodyWeavers.xml
new file mode 100644
index 00000000..be0de3a9
--- /dev/null
+++ b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.AspNetCore/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.AspNetCore/FodyWeavers.xsd b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.AspNetCore/FodyWeavers.xsd
new file mode 100644
index 00000000..3f3946e2
--- /dev/null
+++ b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors.AspNetCore/FodyWeavers.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors/EasyAbp.EShop.Plugins.Inventories.DaprActors.csproj b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors/EasyAbp.EShop.Plugins.Inventories.DaprActors.csproj
new file mode 100644
index 00000000..4b5cd92d
--- /dev/null
+++ b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors/EasyAbp.EShop.Plugins.Inventories.DaprActors.csproj
@@ -0,0 +1,18 @@
+
+
+
+
+
+ net6.0
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors/EasyAbp/EShop/Plugins/Inventories/DaprActors/EShopPluginsInventoriesDaprActorsModule.cs b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors/EasyAbp/EShop/Plugins/Inventories/DaprActors/EShopPluginsInventoriesDaprActorsModule.cs
new file mode 100644
index 00000000..0b59cb9b
--- /dev/null
+++ b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors/EasyAbp/EShop/Plugins/Inventories/DaprActors/EShopPluginsInventoriesDaprActorsModule.cs
@@ -0,0 +1,10 @@
+using Volo.Abp.Modularity;
+
+namespace EasyAbp.EShop.Plugins.Inventories.DaprActors;
+
+[DependsOn(
+ typeof(EShopPluginsInventoriesDaprActorsAbstractionsModule)
+)]
+public class EShopPluginsInventoriesDaprActorsModule : AbpModule
+{
+}
\ No newline at end of file
diff --git a/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors/EasyAbp/EShop/Plugins/Inventories/DaprActors/InventoryActor.cs b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors/EasyAbp/EShop/Plugins/Inventories/DaprActors/InventoryActor.cs
new file mode 100644
index 00000000..28457019
--- /dev/null
+++ b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors/EasyAbp/EShop/Plugins/Inventories/DaprActors/InventoryActor.cs
@@ -0,0 +1,87 @@
+using System.Threading.Tasks;
+using Dapr;
+using Dapr.Actors.Runtime;
+
+namespace EasyAbp.EShop.Plugins.Inventories.DaprActors;
+
+public class InventoryActor : Actor, IInventoryActor
+{
+ public static string InventoryStateName { get; set; } = "i";
+
+ public InventoryActor(ActorHost host) : base(host)
+ {
+ }
+
+ protected override async Task OnActivateAsync()
+ {
+ await StateManager.TryAddStateAsync(InventoryStateName, new InventoryStateModel());
+ }
+
+ public virtual async Task GetInventoryStateAsync()
+ {
+ return await StateManager.GetStateAsync(InventoryStateName);
+ }
+
+ public virtual async Task IncreaseInventoryAsync(int quantity, bool decreaseSold)
+ {
+ var state = await GetInventoryStateAsync();
+
+ InternalIncreaseInventory(state, quantity, decreaseSold);
+
+ await SetInventoryStateAsync(state);
+ }
+
+ public async Task ReduceInventoryAsync(int quantity, bool increaseSold)
+ {
+ var state = await GetInventoryStateAsync();
+
+ InternalReduceInventory(state, quantity, increaseSold);
+
+ await SetInventoryStateAsync(state);
+ }
+
+ protected virtual async Task SetInventoryStateAsync(InventoryStateModel state)
+ {
+ await StateManager.SetStateAsync(InventoryStateName, state);
+ }
+
+ protected virtual void InternalIncreaseInventory(InventoryStateModel stateModel, int quantity, bool decreaseSold)
+ {
+ if (quantity < 0)
+ {
+ throw new DaprException("Quantity should not be less than 0.");
+ }
+
+ if (decreaseSold && stateModel.Sold - quantity < 0)
+ {
+ throw new DaprException("Target Sold cannot be less than 0.");
+ }
+
+ stateModel.Inventory = checked(stateModel.Inventory + quantity);
+
+ if (decreaseSold)
+ {
+ stateModel.Sold -= quantity;
+ }
+ }
+
+ protected virtual void InternalReduceInventory(InventoryStateModel stateModel, int quantity, bool increaseSold)
+ {
+ if (quantity < 0)
+ {
+ throw new DaprException("Quantity should not be less than 0.");
+ }
+
+ if (quantity > stateModel.Inventory)
+ {
+ throw new DaprException("Insufficient inventory.");
+ }
+
+ if (increaseSold)
+ {
+ stateModel.Sold = checked(stateModel.Sold + quantity);
+ }
+
+ stateModel.Inventory -= quantity;
+ }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors/FodyWeavers.xml b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors/FodyWeavers.xml
new file mode 100644
index 00000000..1715698c
--- /dev/null
+++ b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors/FodyWeavers.xsd b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors/FodyWeavers.xsd
new file mode 100644
index 00000000..ffa6fc4b
--- /dev/null
+++ b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Plugins.Inventories.DaprActors/FodyWeavers.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Products.DaprActorsInventory.Domain/EasyAbp.EShop.Products.DaprActorsInventory.Domain.csproj b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Products.DaprActorsInventory.Domain/EasyAbp.EShop.Products.DaprActorsInventory.Domain.csproj
new file mode 100644
index 00000000..93c911a8
--- /dev/null
+++ b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Products.DaprActorsInventory.Domain/EasyAbp.EShop.Products.DaprActorsInventory.Domain.csproj
@@ -0,0 +1,15 @@
+
+
+
+
+
+ net6.0
+
+
+
+
+
+
+
+
+
diff --git a/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Products.DaprActorsInventory.Domain/EasyAbp/EShop/Products/DaprActorsInventory/DaprActorsProductInventoryProvider.cs b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Products.DaprActorsInventory.Domain/EasyAbp/EShop/Products/DaprActorsInventory/DaprActorsProductInventoryProvider.cs
new file mode 100644
index 00000000..14d4faa5
--- /dev/null
+++ b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Products.DaprActorsInventory.Domain/EasyAbp/EShop/Products/DaprActorsInventory/DaprActorsProductInventoryProvider.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Dapr.Actors;
+using EasyAbp.EShop.Plugins.Inventories.DaprActors;
+using EasyAbp.EShop.Products.ProductInventories;
+using Microsoft.Extensions.Logging;
+using Volo.Abp.DependencyInjection;
+
+namespace EasyAbp.EShop.Products.DaprActorsInventory;
+
+public class DaprActorsProductInventoryProvider : IProductInventoryProvider, ITransientDependency
+{
+ public static string DaprActorsProductInventoryProviderName { get; set; } = "DaprActors";
+ public static string DaprActorsProductInventoryProviderDisplayName { get; set; } = "DaprActors";
+ public static string DaprActorsProductInventoryProviderDescription { get; set; } = "DaprActors";
+
+ public string InventoryProviderName { get; } = DaprActorsProductInventoryProviderName;
+
+ private readonly ILogger _logger;
+ protected IInventoryActorProvider InventoryActorProvider { get; }
+
+ public DaprActorsProductInventoryProvider(
+ IInventoryActorProvider inventoryActorProvider,
+ ILogger logger)
+ {
+ InventoryActorProvider = inventoryActorProvider;
+ _logger = logger;
+ }
+
+ public virtual async Task GetInventoryDataAsync(InventoryQueryModel model)
+ {
+ var actor = await GetActorAsync(model);
+
+ var stateModel = await actor.GetInventoryStateAsync();
+
+ return new InventoryDataModel
+ {
+ Inventory = stateModel.Inventory,
+ Sold = stateModel.Sold
+ };
+ }
+
+ public virtual async Task> GetSkuIdInventoryDataMappingAsync(
+ IList models)
+ {
+ var result = new Dictionary();
+
+ foreach (var model in models)
+ {
+ result.Add(model.ProductSkuId, await GetInventoryDataAsync(model));
+ }
+
+ return result;
+ }
+
+ public virtual async Task TryIncreaseInventoryAsync(InventoryQueryModel model, int quantity,
+ bool decreaseSold)
+ {
+ var actor = await GetActorAsync(model);
+
+ try
+ {
+ await actor.IncreaseInventoryAsync(quantity, decreaseSold);
+ }
+ catch (Exception e)
+ {
+ _logger.LogError("Actor threw: {Message}", e.Message);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ public virtual async Task TryReduceInventoryAsync(InventoryQueryModel model, int quantity, bool increaseSold)
+ {
+ var actor = await GetActorAsync(model);
+
+ var stateModel = await actor.GetInventoryStateAsync();
+
+ if (stateModel.Inventory < quantity)
+ {
+ return false;
+ }
+
+ try
+ {
+ await actor.ReduceInventoryAsync(quantity, increaseSold);
+ }
+ catch (Exception e)
+ {
+ _logger.LogError("Actor threw: {Message}", e.Message);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ protected virtual async Task GetActorAsync(InventoryQueryModel model)
+ {
+ return await InventoryActorProvider.GetAsync(GetActorId(model));
+ }
+
+ protected virtual ActorId GetActorId(InventoryQueryModel model)
+ {
+ return new ActorId(
+ $"eshop_inventory_{(model.TenantId.HasValue ? model.TenantId.Value : "host")}_{model.ProductSkuId}");
+ }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Products.DaprActorsInventory.Domain/EasyAbp/EShop/Products/DaprActorsInventory/EShopProductsDaprActorsInventoryDomainModule.cs b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Products.DaprActorsInventory.Domain/EasyAbp/EShop/Products/DaprActorsInventory/EShopProductsDaprActorsInventoryDomainModule.cs
new file mode 100644
index 00000000..3c9dc09a
--- /dev/null
+++ b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Products.DaprActorsInventory.Domain/EasyAbp/EShop/Products/DaprActorsInventory/EShopProductsDaprActorsInventoryDomainModule.cs
@@ -0,0 +1,28 @@
+using EasyAbp.EShop.Plugins.Inventories.DaprActors;
+using EasyAbp.EShop.Products.Options;
+using Volo.Abp.Modularity;
+
+namespace EasyAbp.EShop.Products.DaprActorsInventory;
+
+[DependsOn(
+ typeof(EShopProductsDomainModule),
+ typeof(EShopPluginsInventoriesDaprActorsAbstractionsModule)
+)]
+public class EShopProductsDaprActorsInventoryDomainModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ Configure(options =>
+ {
+ options.InventoryProviders.Configure(
+ DaprActorsProductInventoryProvider.DaprActorsProductInventoryProviderName, provider =>
+ {
+ provider.DisplayName =
+ DaprActorsProductInventoryProvider.DaprActorsProductInventoryProviderDisplayName;
+ provider.Description = DaprActorsProductInventoryProvider
+ .DaprActorsProductInventoryProviderDescription;
+ provider.ProviderType = typeof(DaprActorsProductInventoryProvider);
+ });
+ });
+ }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Products.DaprActorsInventory.Domain/EasyAbp/EShop/Products/DaprActorsInventory/IInventoryActorProvider.cs b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Products.DaprActorsInventory.Domain/EasyAbp/EShop/Products/DaprActorsInventory/IInventoryActorProvider.cs
new file mode 100644
index 00000000..fc1dc827
--- /dev/null
+++ b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Products.DaprActorsInventory.Domain/EasyAbp/EShop/Products/DaprActorsInventory/IInventoryActorProvider.cs
@@ -0,0 +1,10 @@
+using System.Threading.Tasks;
+using Dapr.Actors;
+using EasyAbp.EShop.Plugins.Inventories.DaprActors;
+
+namespace EasyAbp.EShop.Products.DaprActorsInventory;
+
+public interface IInventoryActorProvider
+{
+ Task GetAsync(ActorId actorId);
+}
\ No newline at end of file
diff --git a/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Products.DaprActorsInventory.Domain/EasyAbp/EShop/Products/DaprActorsInventory/InventoryActorProvider.cs b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Products.DaprActorsInventory.Domain/EasyAbp/EShop/Products/DaprActorsInventory/InventoryActorProvider.cs
new file mode 100644
index 00000000..16dd18f1
--- /dev/null
+++ b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Products.DaprActorsInventory.Domain/EasyAbp/EShop/Products/DaprActorsInventory/InventoryActorProvider.cs
@@ -0,0 +1,17 @@
+using System.Threading.Tasks;
+using Dapr.Actors;
+using Dapr.Actors.Client;
+using EasyAbp.EShop.Plugins.Inventories.DaprActors;
+using Volo.Abp.DependencyInjection;
+
+namespace EasyAbp.EShop.Products.DaprActorsInventory;
+
+public class InventoryActorProvider : IInventoryActorProvider, ITransientDependency
+{
+ public static string ActorType { get; set; } = "InventoryActor";
+
+ public virtual Task GetAsync(ActorId actorId)
+ {
+ return Task.FromResult(ActorProxy.Create(actorId, ActorType));
+ }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Products.DaprActorsInventory.Domain/FodyWeavers.xml b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Products.DaprActorsInventory.Domain/FodyWeavers.xml
new file mode 100644
index 00000000..1715698c
--- /dev/null
+++ b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Products.DaprActorsInventory.Domain/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Products.DaprActorsInventory.Domain/FodyWeavers.xsd b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Products.DaprActorsInventory.Domain/FodyWeavers.xsd
new file mode 100644
index 00000000..ffa6fc4b
--- /dev/null
+++ b/plugins/Inventories/DaprActors/src/EasyAbp.EShop.Products.DaprActorsInventory.Domain/FodyWeavers.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/Inventories/DaprActors/test/EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests/DaprActorsProductInventoryProviderTests.cs b/plugins/Inventories/DaprActors/test/EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests/DaprActorsProductInventoryProviderTests.cs
new file mode 100644
index 00000000..ce701107
--- /dev/null
+++ b/plugins/Inventories/DaprActors/test/EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests/DaprActorsProductInventoryProviderTests.cs
@@ -0,0 +1,48 @@
+using System.Threading.Tasks;
+using EasyAbp.EShop.Products.ProductInventories;
+using Microsoft.Extensions.DependencyInjection;
+using Shouldly;
+using Xunit;
+
+namespace EasyAbp.EShop.Products.DaprActorsInventory.Domain;
+
+public class DaprActorsProductInventoryProviderTests : ProductsDaprActorsInventoryTestBase
+{
+ [Fact]
+ public async Task Should_Get_Inventory()
+ {
+ var inventoryProvider = ServiceProvider.GetRequiredService();
+
+ var inventoryDataModel = await inventoryProvider.GetInventoryDataAsync(new InventoryQueryModel());
+
+ inventoryDataModel.ShouldNotBeNull();
+ inventoryDataModel.Inventory.ShouldBe(100);
+ inventoryDataModel.Sold.ShouldBe(0);
+ }
+
+ [Fact]
+ public async Task Should_Change_Inventory()
+ {
+ var inventoryProvider = ServiceProvider.GetRequiredService();
+
+ var result = await inventoryProvider.TryReduceInventoryAsync(new InventoryQueryModel(), 2, true);
+
+ result.ShouldBeTrue();
+
+ var inventoryDataModel = await inventoryProvider.GetInventoryDataAsync(new InventoryQueryModel());
+
+ inventoryDataModel.ShouldNotBeNull();
+ inventoryDataModel.Inventory.ShouldBe(98);
+ inventoryDataModel.Sold.ShouldBe(2);
+
+ result = await inventoryProvider.TryIncreaseInventoryAsync(new InventoryQueryModel(), 1, true);
+
+ result.ShouldBeTrue();
+
+ inventoryDataModel = await inventoryProvider.GetInventoryDataAsync(new InventoryQueryModel());
+
+ inventoryDataModel.ShouldNotBeNull();
+ inventoryDataModel.Inventory.ShouldBe(99);
+ inventoryDataModel.Sold.ShouldBe(1);
+ }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/DaprActors/test/EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests/EShopProductsDaprActorsInventoryDomainTestModule.cs b/plugins/Inventories/DaprActors/test/EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests/EShopProductsDaprActorsInventoryDomainTestModule.cs
new file mode 100644
index 00000000..8b3c22fb
--- /dev/null
+++ b/plugins/Inventories/DaprActors/test/EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests/EShopProductsDaprActorsInventoryDomainTestModule.cs
@@ -0,0 +1,16 @@
+using Volo.Abp;
+using Volo.Abp.Authorization;
+using Volo.Abp.Autofac;
+using Volo.Abp.Modularity;
+
+namespace EasyAbp.EShop.Products.DaprActorsInventory.Domain;
+
+[DependsOn(
+ typeof(AbpAutofacModule),
+ typeof(AbpTestBaseModule),
+ typeof(AbpAuthorizationModule),
+ typeof(EShopProductsDaprActorsInventoryDomainModule)
+)]
+public class EShopProductsDaprActorsInventoryDomainTestModule : AbpModule
+{
+}
\ No newline at end of file
diff --git a/plugins/Inventories/DaprActors/test/EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests/EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests.csproj b/plugins/Inventories/DaprActors/test/EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests/EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests.csproj
new file mode 100644
index 00000000..5fc3de69
--- /dev/null
+++ b/plugins/Inventories/DaprActors/test/EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests/EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net6.0
+ EasyAbp.EShop.Products.DaprActorsInventory.Domain
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/Inventories/DaprActors/test/EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests/FakeInventoryActor.cs b/plugins/Inventories/DaprActors/test/EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests/FakeInventoryActor.cs
new file mode 100644
index 00000000..58ae0129
--- /dev/null
+++ b/plugins/Inventories/DaprActors/test/EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests/FakeInventoryActor.cs
@@ -0,0 +1,43 @@
+using System.Threading.Tasks;
+using EasyAbp.EShop.Plugins.Inventories.DaprActors;
+using Volo.Abp.DependencyInjection;
+
+namespace EasyAbp.EShop.Products.DaprActorsInventory.Domain;
+
+public class FakeInventoryActor : IInventoryActor, ITransientDependency
+{
+ private InventoryStateModel StateModel { get; } = new()
+ {
+ Inventory = 100,
+ Sold = 0
+ };
+
+ public Task GetInventoryStateAsync()
+ {
+ return Task.FromResult(StateModel);
+ }
+
+ public Task IncreaseInventoryAsync(int quantity, bool decreaseSold)
+ {
+ StateModel.Inventory += quantity;
+
+ if (decreaseSold)
+ {
+ StateModel.Sold -= quantity;
+ }
+
+ return Task.CompletedTask;
+ }
+
+ public Task ReduceInventoryAsync(int quantity, bool increaseSold)
+ {
+ StateModel.Inventory -= quantity;
+
+ if (increaseSold)
+ {
+ StateModel.Sold += quantity;
+ }
+
+ return Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/DaprActors/test/EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests/ProductsDaprActorsInventoryTestBase.cs b/plugins/Inventories/DaprActors/test/EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests/ProductsDaprActorsInventoryTestBase.cs
new file mode 100644
index 00000000..55c5d3fc
--- /dev/null
+++ b/plugins/Inventories/DaprActors/test/EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests/ProductsDaprActorsInventoryTestBase.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp;
+using Volo.Abp.Modularity;
+using Volo.Abp.Testing;
+using Volo.Abp.Uow;
+
+namespace EasyAbp.EShop.Products.DaprActorsInventory.Domain
+{
+ /* All test classes are derived from this class, directly or indirectly. */
+ public abstract class
+ ProductsDaprActorsInventoryTestBase : AbpIntegratedTest
+ {
+ protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
+ {
+ options.UseAutofac();
+ }
+
+ protected virtual Task WithUnitOfWorkAsync(Func func)
+ {
+ return WithUnitOfWorkAsync(new AbpUnitOfWorkOptions(), func);
+ }
+
+ protected virtual async Task WithUnitOfWorkAsync(AbpUnitOfWorkOptions options, Func action)
+ {
+ using (var scope = ServiceProvider.CreateScope())
+ {
+ var uowManager = scope.ServiceProvider.GetRequiredService();
+
+ using (var uow = uowManager.Begin(options))
+ {
+ await action();
+
+ await uow.CompleteAsync();
+ }
+ }
+ }
+
+ protected virtual Task WithUnitOfWorkAsync(Func> func)
+ {
+ return WithUnitOfWorkAsync(new AbpUnitOfWorkOptions(), func);
+ }
+
+ protected virtual async Task WithUnitOfWorkAsync(AbpUnitOfWorkOptions options,
+ Func> func)
+ {
+ using (var scope = ServiceProvider.CreateScope())
+ {
+ var uowManager = scope.ServiceProvider.GetRequiredService();
+
+ using (var uow = uowManager.Begin(options))
+ {
+ var result = await func();
+ await uow.CompleteAsync();
+ return result;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/DaprActors/test/EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests/TestInventoryActorProvider.cs b/plugins/Inventories/DaprActors/test/EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests/TestInventoryActorProvider.cs
new file mode 100644
index 00000000..1b889758
--- /dev/null
+++ b/plugins/Inventories/DaprActors/test/EasyAbp.EShop.Products.DaprActorsInventory.Domain.Tests/TestInventoryActorProvider.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Threading.Tasks;
+using Dapr.Actors;
+using EasyAbp.EShop.Plugins.Inventories.DaprActors;
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.DependencyInjection;
+
+namespace EasyAbp.EShop.Products.DaprActorsInventory.Domain;
+
+[Dependency(ReplaceServices = true)]
+public class TestInventoryActorProvider : IInventoryActorProvider, ITransientDependency
+{
+ private IInventoryActor Actor { get; set; }
+
+ private readonly IServiceProvider _serviceProvider;
+
+ public TestInventoryActorProvider(IServiceProvider serviceProvider)
+ {
+ _serviceProvider = serviceProvider;
+ }
+
+ public Task GetAsync(ActorId actorId)
+ {
+ return Task.FromResult(Actor ??= _serviceProvider.GetRequiredService());
+ }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/README.md b/plugins/Inventories/OrleansGrains/README.md
new file mode 120000
index 00000000..1dd0889c
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/README.md
@@ -0,0 +1 @@
+../../../docs/plugins/inventories/orleans-grains/README.md
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions.csproj b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions.csproj
new file mode 100644
index 00000000..42c2a83c
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions.csproj
@@ -0,0 +1,22 @@
+
+
+
+
+
+ net6.0
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+
+
+
+
+
+
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/EShopPluginsInventoriesOrleansGrainsAbstractionsModule.cs b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/EShopPluginsInventoriesOrleansGrainsAbstractionsModule.cs
new file mode 100644
index 00000000..a327dd08
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/EShopPluginsInventoriesOrleansGrainsAbstractionsModule.cs
@@ -0,0 +1,11 @@
+using EasyAbp.EShop.Products;
+using Volo.Abp.Modularity;
+
+namespace EasyAbp.EShop.Plugins.Inventories.OrleansGrains;
+
+[DependsOn(
+ typeof(EShopProductsDomainSharedModule)
+)]
+public class EShopPluginsInventoriesOrleansGrainsAbstractionsModule : AbpModule
+{
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/IInventoryGrain.cs b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/IInventoryGrain.cs
new file mode 100644
index 00000000..7deb1fe6
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/IInventoryGrain.cs
@@ -0,0 +1,13 @@
+using System.Threading.Tasks;
+using Orleans;
+
+namespace EasyAbp.EShop.Plugins.Inventories.OrleansGrains;
+
+public interface IInventoryGrain : IGrainWithStringKey
+{
+ Task GetInventoryStateAsync();
+
+ Task IncreaseInventoryAsync(int quantity, bool decreaseSold);
+
+ Task ReduceInventoryAsync(int quantity, bool increaseSold);
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/InventoryStateModel.cs b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/InventoryStateModel.cs
new file mode 100644
index 00000000..5d7482ec
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/InventoryStateModel.cs
@@ -0,0 +1,8 @@
+namespace EasyAbp.EShop.Plugins.Inventories.OrleansGrains;
+
+public class InventoryStateModel
+{
+ public int Inventory { get; set; }
+
+ public long Sold { get; set; }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/FodyWeavers.xml b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/FodyWeavers.xml
new file mode 100644
index 00000000..1715698c
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/FodyWeavers.xsd b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/FodyWeavers.xsd
new file mode 100644
index 00000000..ffa6fc4b
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/FodyWeavers.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo.csproj b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo.csproj
new file mode 100644
index 00000000..5d010900
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo.csproj
@@ -0,0 +1,18 @@
+
+
+
+
+
+ net6.0
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/EShopPluginsInventoriesOrleansGrainsSiloModule.cs b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/EShopPluginsInventoriesOrleansGrainsSiloModule.cs
new file mode 100644
index 00000000..6d974fdf
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/EShopPluginsInventoriesOrleansGrainsSiloModule.cs
@@ -0,0 +1,14 @@
+using EasyAbp.EShop.Products;
+using Volo.Abp.Modularity;
+
+namespace EasyAbp.EShop.Plugins.Inventories.OrleansGrains;
+
+[DependsOn(
+ typeof(EShopPluginsInventoriesOrleansGrainsModule)
+)]
+public class EShopPluginsInventoriesOrleansGrainsSiloModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo/FodyWeavers.xml b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo/FodyWeavers.xml
new file mode 100644
index 00000000..be0de3a9
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo/FodyWeavers.xsd b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo/FodyWeavers.xsd
new file mode 100644
index 00000000..3f3946e2
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo/FodyWeavers.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.csproj b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.csproj
new file mode 100644
index 00000000..6e831f2a
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.csproj
@@ -0,0 +1,22 @@
+
+
+
+
+
+ net6.0
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+
+
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/EShopPluginsInventoriesOrleansGrainsModule.cs b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/EShopPluginsInventoriesOrleansGrainsModule.cs
new file mode 100644
index 00000000..087bb7b9
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/EShopPluginsInventoriesOrleansGrainsModule.cs
@@ -0,0 +1,10 @@
+using Volo.Abp.Modularity;
+
+namespace EasyAbp.EShop.Plugins.Inventories.OrleansGrains;
+
+[DependsOn(
+ typeof(EShopPluginsInventoriesOrleansGrainsAbstractionsModule)
+)]
+public class EShopPluginsInventoriesOrleansGrainsModule : AbpModule
+{
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/InventoryGrain.cs b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/InventoryGrain.cs
new file mode 100644
index 00000000..454fdb86
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/InventoryGrain.cs
@@ -0,0 +1,68 @@
+using System.Threading.Tasks;
+using Orleans;
+using Orleans.Providers;
+using Orleans.Runtime;
+
+namespace EasyAbp.EShop.Plugins.Inventories.OrleansGrains;
+
+[StorageProvider(ProviderName = StorageProviderName)]
+public class InventoryGrain : Grain, IInventoryGrain
+{
+ public const string StorageProviderName = "EShopInventoryStorage";
+
+ public virtual Task GetInventoryStateAsync() => Task.FromResult(State);
+
+ public virtual async Task IncreaseInventoryAsync(int quantity, bool decreaseSold)
+ {
+ InternalIncreaseInventory(quantity, decreaseSold);
+
+ await WriteStateAsync();
+ }
+
+ public async Task ReduceInventoryAsync(int quantity, bool increaseSold)
+ {
+ InternalReduceInventory(quantity, increaseSold);
+
+ await WriteStateAsync();
+ }
+
+ protected virtual void InternalIncreaseInventory(int quantity, bool decreaseSold)
+ {
+ if (quantity < 0)
+ {
+ throw new OrleansException("Quantity should not be less than 0.");
+ }
+
+ if (decreaseSold && State.Sold - quantity < 0)
+ {
+ throw new OrleansException("Target Sold cannot be less than 0.");
+ }
+
+ State.Inventory = checked(State.Inventory + quantity);
+
+ if (decreaseSold)
+ {
+ State.Sold -= quantity;
+ }
+ }
+
+ protected virtual void InternalReduceInventory(int quantity, bool increaseSold)
+ {
+ if (quantity < 0)
+ {
+ throw new OrleansException("Quantity should not be less than 0.");
+ }
+
+ if (quantity > State.Inventory)
+ {
+ throw new OrleansException("Insufficient inventory.");
+ }
+
+ if (increaseSold)
+ {
+ State.Sold = checked(State.Sold + quantity);
+ }
+
+ State.Inventory -= quantity;
+ }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/FodyWeavers.xml b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/FodyWeavers.xml
new file mode 100644
index 00000000..1715698c
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/FodyWeavers.xsd b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/FodyWeavers.xsd
new file mode 100644
index 00000000..ffa6fc4b
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/FodyWeavers.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.csproj b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.csproj
new file mode 100644
index 00000000..708e1aa1
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.csproj
@@ -0,0 +1,19 @@
+
+
+
+
+
+ net6.0
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp/EShop/Products/OrleansGrainsInventory/EShopProductsOrleansGrainsInventoryDomainModule.cs b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp/EShop/Products/OrleansGrainsInventory/EShopProductsOrleansGrainsInventoryDomainModule.cs
new file mode 100644
index 00000000..84838e9b
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp/EShop/Products/OrleansGrainsInventory/EShopProductsOrleansGrainsInventoryDomainModule.cs
@@ -0,0 +1,28 @@
+using EasyAbp.EShop.Plugins.Inventories.OrleansGrains;
+using EasyAbp.EShop.Products.Options;
+using Volo.Abp.Modularity;
+
+namespace EasyAbp.EShop.Products.OrleansGrainsInventory;
+
+[DependsOn(
+ typeof(EShopProductsDomainModule),
+ typeof(EShopPluginsInventoriesOrleansGrainsAbstractionsModule)
+)]
+public class EShopProductsOrleansGrainsInventoryDomainModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ Configure(options =>
+ {
+ options.InventoryProviders.Configure(
+ OrleansGrainsProductInventoryProvider.OrleansGrainsProductInventoryProviderName, provider =>
+ {
+ provider.DisplayName =
+ OrleansGrainsProductInventoryProvider.OrleansGrainsProductInventoryProviderDisplayName;
+ provider.Description = OrleansGrainsProductInventoryProvider
+ .OrleansGrainsProductInventoryProviderDescription;
+ provider.ProviderType = typeof(OrleansGrainsProductInventoryProvider);
+ });
+ });
+ }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp/EShop/Products/OrleansGrainsInventory/IInventoryGrainProvider.cs b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp/EShop/Products/OrleansGrainsInventory/IInventoryGrainProvider.cs
new file mode 100644
index 00000000..5f5d1966
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp/EShop/Products/OrleansGrainsInventory/IInventoryGrainProvider.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+using EasyAbp.EShop.Plugins.Inventories.OrleansGrains;
+
+namespace EasyAbp.EShop.Products.OrleansGrainsInventory;
+
+public interface IInventoryGrainProvider
+{
+ Task GetAsync(string grainKey);
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp/EShop/Products/OrleansGrainsInventory/InventoryGrainProvider.cs b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp/EShop/Products/OrleansGrainsInventory/InventoryGrainProvider.cs
new file mode 100644
index 00000000..0ef6d00f
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp/EShop/Products/OrleansGrainsInventory/InventoryGrainProvider.cs
@@ -0,0 +1,21 @@
+using System.Threading.Tasks;
+using EasyAbp.EShop.Plugins.Inventories.OrleansGrains;
+using Orleans;
+using Volo.Abp.DependencyInjection;
+
+namespace EasyAbp.EShop.Products.OrleansGrainsInventory;
+
+public class InventoryGrainProvider : IInventoryGrainProvider, ITransientDependency
+{
+ private readonly IGrainFactory _grainFactory;
+
+ public InventoryGrainProvider(IGrainFactory grainFactory)
+ {
+ _grainFactory = grainFactory;
+ }
+
+ public virtual Task GetAsync(string grainKey)
+ {
+ return Task.FromResult(_grainFactory.GetGrain(grainKey));
+ }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp/EShop/Products/OrleansGrainsInventory/OrleansGrainsProductInventoryProvider.cs b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp/EShop/Products/OrleansGrainsInventory/OrleansGrainsProductInventoryProvider.cs
new file mode 100644
index 00000000..54a95b54
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp/EShop/Products/OrleansGrainsInventory/OrleansGrainsProductInventoryProvider.cs
@@ -0,0 +1,109 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using EasyAbp.EShop.Plugins.Inventories.OrleansGrains;
+using EasyAbp.EShop.Products.ProductInventories;
+using Microsoft.Extensions.Logging;
+using Volo.Abp.DependencyInjection;
+
+namespace EasyAbp.EShop.Products.OrleansGrainsInventory;
+
+public class OrleansGrainsProductInventoryProvider : IProductInventoryProvider, ITransientDependency
+{
+ public static string OrleansGrainsProductInventoryProviderName { get; set; } = "OrleansGrains";
+ public static string OrleansGrainsProductInventoryProviderDisplayName { get; set; } = "OrleansGrains";
+ public static string OrleansGrainsProductInventoryProviderDescription { get; set; } = "OrleansGrains";
+
+ public string InventoryProviderName { get; } = OrleansGrainsProductInventoryProviderName;
+
+ private readonly ILogger _logger;
+ protected IInventoryGrainProvider InventoryGrainProvider { get; }
+
+ public OrleansGrainsProductInventoryProvider(
+ IInventoryGrainProvider inventoryGrainProvider,
+ ILogger logger)
+ {
+ InventoryGrainProvider = inventoryGrainProvider;
+ _logger = logger;
+ }
+
+ public virtual async Task GetInventoryDataAsync(InventoryQueryModel model)
+ {
+ var grain = await GetGrainAsync(model);
+
+ var stateModel = await grain.GetInventoryStateAsync();
+
+ return new InventoryDataModel
+ {
+ Inventory = stateModel.Inventory,
+ Sold = stateModel.Sold
+ };
+ }
+
+ public virtual async Task> GetSkuIdInventoryDataMappingAsync(
+ IList models)
+ {
+ var result = new Dictionary();
+
+ foreach (var model in models)
+ {
+ result.Add(model.ProductSkuId, await GetInventoryDataAsync(model));
+ }
+
+ return result;
+ }
+
+ public virtual async Task TryIncreaseInventoryAsync(InventoryQueryModel model, int quantity,
+ bool decreaseSold)
+ {
+ var grain = await GetGrainAsync(model);
+
+ try
+ {
+ await grain.IncreaseInventoryAsync(quantity, decreaseSold);
+ }
+ catch (Exception e)
+ {
+ _logger.LogError("Grain threw: {Message}", e.Message);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ public virtual async Task TryReduceInventoryAsync(InventoryQueryModel model, int quantity, bool increaseSold)
+ {
+ var grain = await GetGrainAsync(model);
+
+ var stateModel = await grain.GetInventoryStateAsync();
+
+ if (stateModel.Inventory < quantity)
+ {
+ return false;
+ }
+
+ try
+ {
+ await grain.ReduceInventoryAsync(quantity, increaseSold);
+ }
+ catch (Exception e)
+ {
+ _logger.LogError("Grain threw: {Message}", e.Message);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ protected virtual async Task GetGrainAsync(InventoryQueryModel model)
+ {
+ return await InventoryGrainProvider.GetAsync(GetGrainId(model));
+ }
+
+ protected virtual string GetGrainId(InventoryQueryModel model)
+ {
+ return $"eshop_inventory_{(model.TenantId.HasValue ? model.TenantId.Value : "host")}_{model.ProductSkuId}";
+ }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/FodyWeavers.xml b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/FodyWeavers.xml
new file mode 100644
index 00000000..1715698c
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/FodyWeavers.xsd b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/FodyWeavers.xsd
new file mode 100644
index 00000000..ffa6fc4b
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/FodyWeavers.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/EShopProductsOrleansGrainsInventoryDomainTestModule.cs b/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/EShopProductsOrleansGrainsInventoryDomainTestModule.cs
new file mode 100644
index 00000000..38074ed7
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/EShopProductsOrleansGrainsInventoryDomainTestModule.cs
@@ -0,0 +1,16 @@
+using Volo.Abp;
+using Volo.Abp.Authorization;
+using Volo.Abp.Autofac;
+using Volo.Abp.Modularity;
+
+namespace EasyAbp.EShop.Products.OrleansGrainsInventory.Domain;
+
+[DependsOn(
+ typeof(AbpAutofacModule),
+ typeof(AbpTestBaseModule),
+ typeof(AbpAuthorizationModule),
+ typeof(EShopProductsOrleansGrainsInventoryDomainModule)
+)]
+public class EShopProductsOrleansGrainsInventoryDomainTestModule : AbpModule
+{
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests.csproj b/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests.csproj
new file mode 100644
index 00000000..ee834e1b
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net6.0
+ EasyAbp.EShop.Products.OrleansGrainsInventory.Domain
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/FakeInventoryGrain.cs b/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/FakeInventoryGrain.cs
new file mode 100644
index 00000000..49f7199a
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/FakeInventoryGrain.cs
@@ -0,0 +1,43 @@
+using System.Threading.Tasks;
+using EasyAbp.EShop.Plugins.Inventories.OrleansGrains;
+using Volo.Abp.DependencyInjection;
+
+namespace EasyAbp.EShop.Products.OrleansGrainsInventory.Domain;
+
+public class FakeInventoryGrain : IInventoryGrain, ITransientDependency
+{
+ private InventoryStateModel StateModel { get; } = new()
+ {
+ Inventory = 100,
+ Sold = 0
+ };
+
+ public Task GetInventoryStateAsync()
+ {
+ return Task.FromResult(StateModel);
+ }
+
+ public Task IncreaseInventoryAsync(int quantity, bool decreaseSold)
+ {
+ StateModel.Inventory += quantity;
+
+ if (decreaseSold)
+ {
+ StateModel.Sold -= quantity;
+ }
+
+ return Task.CompletedTask;
+ }
+
+ public Task ReduceInventoryAsync(int quantity, bool increaseSold)
+ {
+ StateModel.Inventory -= quantity;
+
+ if (increaseSold)
+ {
+ StateModel.Sold += quantity;
+ }
+
+ return Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/OrleansGrainsProductInventoryProviderTests.cs b/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/OrleansGrainsProductInventoryProviderTests.cs
new file mode 100644
index 00000000..3570b22c
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/OrleansGrainsProductInventoryProviderTests.cs
@@ -0,0 +1,48 @@
+using System.Threading.Tasks;
+using EasyAbp.EShop.Products.ProductInventories;
+using Microsoft.Extensions.DependencyInjection;
+using Shouldly;
+using Xunit;
+
+namespace EasyAbp.EShop.Products.OrleansGrainsInventory.Domain;
+
+public class OrleansGrainsProductInventoryProviderTests : ProductsOrleansGrainsInventoryTestBase
+{
+ [Fact]
+ public async Task Should_Get_Inventory()
+ {
+ var inventoryProvider = ServiceProvider.GetRequiredService();
+
+ var inventoryDataModel = await inventoryProvider.GetInventoryDataAsync(new InventoryQueryModel());
+
+ inventoryDataModel.ShouldNotBeNull();
+ inventoryDataModel.Inventory.ShouldBe(100);
+ inventoryDataModel.Sold.ShouldBe(0);
+ }
+
+ [Fact]
+ public async Task Should_Change_Inventory()
+ {
+ var inventoryProvider = ServiceProvider.GetRequiredService();
+
+ var result = await inventoryProvider.TryReduceInventoryAsync(new InventoryQueryModel(), 2, true);
+
+ result.ShouldBeTrue();
+
+ var inventoryDataModel = await inventoryProvider.GetInventoryDataAsync(new InventoryQueryModel());
+
+ inventoryDataModel.ShouldNotBeNull();
+ inventoryDataModel.Inventory.ShouldBe(98);
+ inventoryDataModel.Sold.ShouldBe(2);
+
+ result = await inventoryProvider.TryIncreaseInventoryAsync(new InventoryQueryModel(), 1, true);
+
+ result.ShouldBeTrue();
+
+ inventoryDataModel = await inventoryProvider.GetInventoryDataAsync(new InventoryQueryModel());
+
+ inventoryDataModel.ShouldNotBeNull();
+ inventoryDataModel.Inventory.ShouldBe(99);
+ inventoryDataModel.Sold.ShouldBe(1);
+ }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/ProductsOrleansGrainsInventoryTestBase.cs b/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/ProductsOrleansGrainsInventoryTestBase.cs
new file mode 100644
index 00000000..1830da9d
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/ProductsOrleansGrainsInventoryTestBase.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp;
+using Volo.Abp.Modularity;
+using Volo.Abp.Testing;
+using Volo.Abp.Uow;
+
+namespace EasyAbp.EShop.Products.OrleansGrainsInventory.Domain
+{
+ /* All test classes are derived from this class, directly or indirectly. */
+ public abstract class
+ ProductsOrleansGrainsInventoryTestBase : AbpIntegratedTest
+ {
+ protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
+ {
+ options.UseAutofac();
+ }
+
+ protected virtual Task WithUnitOfWorkAsync(Func func)
+ {
+ return WithUnitOfWorkAsync(new AbpUnitOfWorkOptions(), func);
+ }
+
+ protected virtual async Task WithUnitOfWorkAsync(AbpUnitOfWorkOptions options, Func action)
+ {
+ using (var scope = ServiceProvider.CreateScope())
+ {
+ var uowManager = scope.ServiceProvider.GetRequiredService();
+
+ using (var uow = uowManager.Begin(options))
+ {
+ await action();
+
+ await uow.CompleteAsync();
+ }
+ }
+ }
+
+ protected virtual Task WithUnitOfWorkAsync(Func> func)
+ {
+ return WithUnitOfWorkAsync(new AbpUnitOfWorkOptions(), func);
+ }
+
+ protected virtual async Task WithUnitOfWorkAsync(AbpUnitOfWorkOptions options,
+ Func> func)
+ {
+ using (var scope = ServiceProvider.CreateScope())
+ {
+ var uowManager = scope.ServiceProvider.GetRequiredService();
+
+ using (var uow = uowManager.Begin(options))
+ {
+ var result = await func();
+ await uow.CompleteAsync();
+ return result;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/TestInventoryGrainProvider.cs b/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/TestInventoryGrainProvider.cs
new file mode 100644
index 00000000..42c9990e
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/TestInventoryGrainProvider.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Threading.Tasks;
+using EasyAbp.EShop.Plugins.Inventories.OrleansGrains;
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.DependencyInjection;
+
+namespace EasyAbp.EShop.Products.OrleansGrainsInventory.Domain;
+
+[Dependency(ReplaceServices = true)]
+public class TestInventoryGrainProvider : IInventoryGrainProvider, ITransientDependency
+{
+ private IInventoryGrain Grain { get; set; }
+
+ private readonly IServiceProvider _serviceProvider;
+
+ public TestInventoryGrainProvider(IServiceProvider serviceProvider)
+ {
+ _serviceProvider = serviceProvider;
+ }
+
+ public Task GetAsync(string grainKey)
+ {
+ return Task.FromResult(Grain ??= _serviceProvider.GetRequiredService());
+ }
+}
\ No newline at end of file
diff --git a/samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20220610120322_AddedInventoryProviderName.Designer.cs b/samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20220610120322_AddedInventoryProviderName.Designer.cs
new file mode 100644
index 00000000..95b0d7be
--- /dev/null
+++ b/samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20220610120322_AddedInventoryProviderName.Designer.cs
@@ -0,0 +1,5177 @@
+//
+using System;
+using EShopSample.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Volo.Abp.EntityFrameworkCore;
+
+#nullable disable
+
+namespace EShopSample.Migrations
+{
+ [DbContext(typeof(EShopSampleDbContext))]
+ [Migration("20220610120322_AddedInventoryProviderName")]
+ partial class AddedInventoryProviderName
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer)
+ .HasAnnotation("ProductVersion", "6.0.5")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1);
+
+ modelBuilder.Entity("EasyAbp.EShop.Orders.Orders.Order", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("ActualTotalPrice")
+ .HasColumnType("decimal(20,8)");
+
+ b.Property("CanceledTime")
+ .HasColumnType("datetime2");
+
+ b.Property("CancellationReason")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CompletionTime")
+ .HasColumnType("datetime2");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasMaxLength(40)
+ .HasColumnType("nvarchar(40)")
+ .HasColumnName("ConcurrencyStamp");
+
+ b.Property("CreationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("CreatorId");
+
+ b.Property("Currency")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CustomerRemark")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CustomerUserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("DeleterId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("DeleterId");
+
+ b.Property("DeletionTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("DeletionTime");
+
+ b.Property("ExtraProperties")
+ .HasColumnType("nvarchar(max)")
+ .HasColumnName("ExtraProperties");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false)
+ .HasColumnName("IsDeleted");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("LastModificationTime");
+
+ b.Property("LastModifierId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("LastModifierId");
+
+ b.Property("OrderNumber")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("OrderStatus")
+ .HasColumnType("int");
+
+ b.Property("PaidTime")
+ .HasColumnType("datetime2");
+
+ b.Property("PaymentExpiration")
+ .HasColumnType("datetime2");
+
+ b.Property("PaymentId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("ProductTotalPrice")
+ .HasColumnType("decimal(20,8)");
+
+ b.Property("ReducedInventoryAfterPaymentTime")
+ .HasColumnType("datetime2");
+
+ b.Property("ReducedInventoryAfterPlacingTime")
+ .HasColumnType("datetime2");
+
+ b.Property