diff --git a/docs/README.md b/docs/README.md index d35aefab..a156a1cb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -105,12 +105,13 @@ We can customize the EShop for complex application scenarios. * Plugin modules * [Baskets](https://github.com/EasyAbp/EShop/tree/dev/plugins/Baskets) + * [Booking](https://github.com/EasyAbp/EShop/tree/dev/plugins/Booking) * Coupons + * [FlashSales](https://github.com/EasyAbp/EShop/tree/dev/plugins/FlashSales) * Inventories * [DaprActors](https://github.com/EasyAbp/EShop/tree/dev/plugins/Inventories/DaprActors) * [OrleansGrains](https://github.com/EasyAbp/EShop/tree/dev/plugins/Inventories/OrleansGrains) - * [FlashSales](https://github.com/EasyAbp/EShop/tree/dev/plugins/FlashSales) - * [Booking](https://github.com/EasyAbp/EShop/tree/dev/plugins/Booking) + * [Promotions](https://github.com/EasyAbp/EShop/tree/dev/plugins/Promotions) ## Roadmap diff --git a/docs/plugins/promotions/README.md b/docs/plugins/promotions/README.md new file mode 100644 index 00000000..216110ea --- /dev/null +++ b/docs/plugins/promotions/README.md @@ -0,0 +1,88 @@ +# EShop.Plugins.Promotions + +[![ABP version](https://img.shields.io/badge/dynamic/xml?style=flat-square&color=yellow&label=abp&query=%2F%2FProject%2FPropertyGroup%2FAbpVersion&url=https%3A%2F%2Fraw.githubusercontent.com%2FEasyAbp%2FEShop%2Fmaster%2FDirectory.Build.props)](https://abp.io) +[![NuGet](https://img.shields.io/nuget/v/EasyAbp.EShop.Plugins.Promotions.Domain.Shared.svg?style=flat-square)](https://www.nuget.org/packages/EasyAbp.EShop.Plugins.Promotions.Domain.Shared) +[![NuGet Download](https://img.shields.io/nuget/dt/EasyAbp.EShop.Plugins.Promotions.Domain.Shared.svg?style=flat-square)](https://www.nuget.org/packages/EasyAbp.EShop.Plugins.Promotions.Domain.Shared) +[![Discord online](https://badgen.net/discord/online-members/xyg8TrRa27?label=Discord)](https://discord.gg/xyg8TrRa27) +[![GitHub stars](https://img.shields.io/github/stars/EasyAbp/EShop?style=social)](https://www.github.com/EasyAbp/EShop) + +A promotion discount provider plugin for EShop. + +## Installation + +1. Install the following NuGet packages. ([see how](https://github.com/EasyAbp/EasyAbpGuide/blob/master/docs/How-To.md#add-nuget-packages)) + + - EasyAbp.EShop.Orders.Plugins.Promotions.Domain (install at EasyAbp.EShop.Orders.Domain location) + - EasyAbp.EShop.Products.Plugins.Promotions.Domain (install at EasyAbp.EShop.Products.Domain location) + - EasyAbp.EShop.Plugins.Promotions.Application + - EasyAbp.EShop.Plugins.Promotions.Application.Contracts + - EasyAbp.EShop.Plugins.Promotions.Domain + - EasyAbp.EShop.Plugins.Promotions.Domain.Shared + - EasyAbp.EShop.Plugins.Promotions.EntityFrameworkCore + - EasyAbp.EShop.Plugins.Promotions.HttpApi + - EasyAbp.EShop.Plugins.Promotions.HttpApi.Client + - (Optional) EasyAbp.EShop.Plugins.Promotions.MongoDB + - (Optional) EasyAbp.EShop.Plugins.Promotions.Web + +2. Add `DependsOn(typeof(EShopXxxModule))` attribute to configure the module dependencies. ([see how](https://github.com/EasyAbp/EasyAbpGuide/blob/master/docs/How-To.md#add-module-dependencies)) + +3. Add `builder.ConfigureEShopPluginsPromotions();` to the `OnModelCreating()` method in **MyProjectDbContext.cs**. + +4. Add EF Core migrations and update your database. See: [ABP document](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=MVC&DB=EF#add-database-migration). + +## Concepts + +This promotion plugin module provides an easy way to define EShop's product discounts and order discounts. + +### Promotion Type + +Promotion type is defined to provide programs for different discount logics. + +We have created [SimpleProductDiscount](https://github.com/EasyAbp/EShop/tree/dev/plugins/Promotions/src/EasyAbp.EShop.Plugins.Promotions.Domain/EasyAbp/EShop/Plugins/Promotions/PromotionTypes/SimpleProductDiscount) and [MinQuantityOrderDiscount](https://github.com/EasyAbp/EShop/tree/dev/plugins/Promotions/src/EasyAbp.EShop.Plugins.Promotions.Domain/EasyAbp/EShop/Plugins/Promotions/PromotionTypes/MinQuantityOrderDiscount) promotion type definitions for you. +You can define custom promotion types yourself if you need different discount logic. + +### Product Discount + +Product discounts subtract the price of products before customers place an order. + +For a product SKU, if there are more than one promotion offering product discounts, customers will see all of them, +but only the one with the highest discount amount will be applied, +since they have the same `EffectGroup = "Promotion"`. + +The promotion product discounts coexist with other types of discounts with different EffectGroup. + +### Order Discount + +Order discounts subtract the price of products after customers place an order. + +For an order line, if there are more than one promotion offering order discounts, +only the one with the highest discount amount will be applied, +since they have the same `EffectGroup = "Promotion"`. + +The promotion order discounts coexist with other types of discounts with different EffectGroup. + +### The Best Combination of Discounts + +The [Orders](https://github.com/EasyAbp/EShop/tree/dev/modules/EasyAbp.EShop.Orders) module of EShop will search the best combination of discounts using BFS. + +See these codes for more: + +* [ProductDiscountResolver](https://github.com/EasyAbp/EShop/blob/dev/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductDiscountResolver.cs) +* [OrderDiscountResolver](https://github.com/EasyAbp/EShop/blob/dev/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderDiscountResolver.cs) +* [DemoOrderDiscountProvider](https://github.com/EasyAbp/EShop/blob/dev/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/DemoOrderDiscountProvider.cs) and [OrderDiscountProviderTests](https://github.com/EasyAbp/EShop/blob/dev/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/OrderDiscountProviderTests.cs) + +## Usage + +### Create a Promotion for Product Discounts + +You can create a promotion with the preset promotion type [SimpleProductDiscount](https://github.com/EasyAbp/EShop/tree/dev/plugins/Promotions/src/EasyAbp.EShop.Plugins.Promotions.Domain/EasyAbp/EShop/Plugins/Promotions/PromotionTypes/SimpleProductDiscount). +It offers direct product discounts for the products you specify. + +![SimpleProductDiscount](/docs/plugins/promotions/images/SimpleProductDiscount.apng) + +### Create a Promotion for Order Discounts + +You can create a promotion with the preset promotion type [SimpleProductDiscount](https://github.com/EasyAbp/EShop/tree/dev/plugins/Promotions/src/EasyAbp.EShop.Plugins.Promotions.Domain/EasyAbp/EShop/Plugins/Promotions/PromotionTypes/SimpleProductDiscount). +It discounts an order line if the `OrderLine.Quantity >= Configurations.MinQuantity`. + +![MinQuantityOrderDiscount](/docs/plugins/promotions/images/MinQuantityOrderDiscount.apng) diff --git a/docs/plugins/promotions/images/MinQuantityOrderDiscount.apng b/docs/plugins/promotions/images/MinQuantityOrderDiscount.apng new file mode 100644 index 00000000..77271d15 Binary files /dev/null and b/docs/plugins/promotions/images/MinQuantityOrderDiscount.apng differ diff --git a/docs/plugins/promotions/images/SimpleProductDiscount.apng b/docs/plugins/promotions/images/SimpleProductDiscount.apng new file mode 100644 index 00000000..fffe7d16 Binary files /dev/null and b/docs/plugins/promotions/images/SimpleProductDiscount.apng differ diff --git a/plugins/Promotions/README.md b/plugins/Promotions/README.md new file mode 120000 index 00000000..85593cf4 --- /dev/null +++ b/plugins/Promotions/README.md @@ -0,0 +1 @@ +../../docs/plugins/promotions/README.md \ No newline at end of file diff --git a/samples/EShopSample/aspnet-core/src/EShopSample.Domain.Shared/Localization/EShopSample/en.json b/samples/EShopSample/aspnet-core/src/EShopSample.Domain.Shared/Localization/EShopSample/en.json index d05236b2..f53b3246 100644 --- a/samples/EShopSample/aspnet-core/src/EShopSample.Domain.Shared/Localization/EShopSample/en.json +++ b/samples/EShopSample/aspnet-core/src/EShopSample.Domain.Shared/Localization/EShopSample/en.json @@ -8,7 +8,8 @@ "Product": "Product", "Cake": "Cake", "OrderCake": "Order a cake", - "CreateOrder": "Create order", + "Buy-1-PCS": "Buy 1 PCS", + "Buy-2-PCS": "Buy 2 PCS", "PayForOrder": "Pay for order", "TopUp-1-USD": "Top-up 1.00 USD", "OrderHistory": "Order history", @@ -30,6 +31,7 @@ "OrderStatusProcessing": "Processing", "OrderStatusCompleted": "Completed", "OrderStatusCanceled": "Canceled", + "Quantity": "Quantity", "TotalPrice": "Price", "AccountBalance": "Account balance", "TimeToAutoCancel": "Order auto-cancel in", diff --git a/samples/EShopSample/aspnet-core/src/EShopSample.Domain.Shared/Localization/EShopSample/zh-Hans.json b/samples/EShopSample/aspnet-core/src/EShopSample.Domain.Shared/Localization/EShopSample/zh-Hans.json index 7a10dedd..bda3f68d 100644 --- a/samples/EShopSample/aspnet-core/src/EShopSample.Domain.Shared/Localization/EShopSample/zh-Hans.json +++ b/samples/EShopSample/aspnet-core/src/EShopSample.Domain.Shared/Localization/EShopSample/zh-Hans.json @@ -8,7 +8,8 @@ "Product": "商品", "Cake": "蛋糕", "OrderCake": "下单买蛋糕", - "CreateOrder": "创建订单", + "Buy-1-PCS": "购买 1 份", + "Buy-2-PCS": "购买 2 份", "PayForOrder": "支付订单", "TopUp-1-USD": "充值 1.00 美元", "OrderHistory": "历史订单", @@ -30,6 +31,7 @@ "OrderStatusProcessing": "进行中", "OrderStatusCompleted": "已完成", "OrderStatusCanceled": "已取消", + "Quantity": "数量", "TotalPrice": "价格", "AccountBalance": "账户余额", "TimeToAutoCancel": "剩余可支付时间", diff --git a/samples/EShopSample/aspnet-core/src/EShopSample.Domain.Shared/Localization/EShopSample/zh-Hant.json b/samples/EShopSample/aspnet-core/src/EShopSample.Domain.Shared/Localization/EShopSample/zh-Hant.json index c2194d2f..94be4b56 100644 --- a/samples/EShopSample/aspnet-core/src/EShopSample.Domain.Shared/Localization/EShopSample/zh-Hant.json +++ b/samples/EShopSample/aspnet-core/src/EShopSample.Domain.Shared/Localization/EShopSample/zh-Hant.json @@ -8,7 +8,8 @@ "Product": "商品", "Cake": "蛋糕", "OrderCake": "下單買蛋糕", - "CreateOrder": "創建訂單", + "Buy-1-PCS": "購買 1 份", + "Buy-2-PCS": "購買 2 份", "PayForOrder": "支付訂單", "TopUp-1-USD": "充值 1.00 美元", "OrderHistory": "歷史訂單", @@ -30,6 +31,7 @@ "OrderStatusProcessing": "進行中", "OrderStatusCompleted": "已完成", "OrderStatusCanceled": "已取消", + "Quantity": "數量", "TotalPrice": "價格", "AccountBalance": "賬戶餘額", "TimeToAutoCancel": "剩餘可支付時間", diff --git a/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/Index.cshtml b/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/Index.cshtml index 488230d5..a3a70e80 100644 --- a/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/Index.cshtml +++ b/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/Index.cshtml @@ -62,7 +62,8 @@ let secondsToAutoCancel = @Model.GetSecondsToAutoCancel()

- @L["CreateOrder"] + @L["Buy-1-PCS"] + @L["Buy-2-PCS"] @if (Model.Order is not null) @@ -86,6 +87,10 @@ let secondsToAutoCancel = @Model.GetSecondsToAutoCancel() @L[option.DisplayName] } + + @L["Quantity"] + @Model.Order.OrderLines[0].Quantity + @L["TotalPrice"] @Model.Order.ActualTotalPrice.ToString("F2") @Model.Order.Currency diff --git a/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/Index.js b/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/Index.js index f568a742..5a79d80f 100644 --- a/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/Index.js +++ b/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/Index.js @@ -5,11 +5,13 @@ const paymentService = easyAbp.paymentService.payments.payment; const accountService = easyAbp.paymentService.prepayment.accounts.account; - $('#CreateOrderButton').click(function (e) { + $('#Buy1PcsButton').click(function (e) { e.preventDefault(); if (!cakeProductSkuId) return - let btn = $(this) - btn.buttonBusy(true); + let btn1 = $('Buy1PcsButton') + btn1.buttonBusy(true); + let btn2 = $('Buy2PcsButton') + btn2.buttonBusy(true); orderService.create({ storeId: storeId, orderLines: [{ @@ -23,7 +25,34 @@ window.location.reload(); }) }).catch(function () { - btn.buttonBusy(false); + btn1.buttonBusy(false); + btn2.buttonBusy(false); + }); + }); + + + $('#Buy2PcsButton').click(function (e) { + e.preventDefault(); + if (!cakeProductSkuId) return + let btn1 = $('Buy1PcsButton') + btn1.buttonBusy(true); + let btn2 = $('Buy2PcsButton') + btn2.buttonBusy(true); + orderService.create({ + storeId: storeId, + orderLines: [{ + productId: cakeProductId, + productSkuId: cakeProductSkuId, + quantity: 2 + }] + }).then(function () { + abp.message.success(l('OrderCreated')) + .then(function () { + window.location.reload(); + }) + }).catch(function () { + btn1.buttonBusy(false); + btn2.buttonBusy(false); }); });