diff --git a/EShop.sln b/EShop.sln index cb7e623f..15b9023c 100644 --- a/EShop.sln +++ b/EShop.sln @@ -417,6 +417,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Orders.Bookin EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Payments.Booking.Application", "plugins\Booking\src\EasyAbp.EShop.Payments.Booking.Application\EasyAbp.EShop.Payments.Booking.Application.csproj", "{4480BFAF-C981-4242-A509-EDA6F572E45C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Baskets.Booking.Application", "plugins\Booking\src\EasyAbp.EShop.Plugins.Baskets.Booking.Application\EasyAbp.EShop.Plugins.Baskets.Booking.Application.csproj", "{F9FD36E6-0ED0-4649-8E1C-33B2CDE85B14}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FlashSales", "FlashSales", "{867AD9F8-FD56-469D-A90D-C569EB9C3D2A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.FlashSales.Application", "plugins\FlashSales\src\EasyAbp.EShop.Plugins.FlashSales.Application\EasyAbp.EShop.Plugins.FlashSales.Application.csproj", "{26611C4C-6910-498A-9FBA-BECC09392ADE}" @@ -1133,6 +1135,10 @@ Global {4480BFAF-C981-4242-A509-EDA6F572E45C}.Debug|Any CPU.Build.0 = Debug|Any CPU {4480BFAF-C981-4242-A509-EDA6F572E45C}.Release|Any CPU.ActiveCfg = Release|Any CPU {4480BFAF-C981-4242-A509-EDA6F572E45C}.Release|Any CPU.Build.0 = Release|Any CPU + {F9FD36E6-0ED0-4649-8E1C-33B2CDE85B14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F9FD36E6-0ED0-4649-8E1C-33B2CDE85B14}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F9FD36E6-0ED0-4649-8E1C-33B2CDE85B14}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F9FD36E6-0ED0-4649-8E1C-33B2CDE85B14}.Release|Any CPU.Build.0 = Release|Any CPU {26611C4C-6910-498A-9FBA-BECC09392ADE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {26611C4C-6910-498A-9FBA-BECC09392ADE}.Debug|Any CPU.Build.0 = Debug|Any CPU {26611C4C-6910-498A-9FBA-BECC09392ADE}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1429,6 +1435,7 @@ Global {739CAE6A-14E6-44FC-8863-DA905CBD289F} = {7087FDFF-196A-4C9F-8C66-EEBC2C49F2F8} {C0399352-1278-4D91-8D4E-7491FD77C18B} = {A8C4583C-034E-47AF-B7EC-1A34EE288E2F} {4480BFAF-C981-4242-A509-EDA6F572E45C} = {A8C4583C-034E-47AF-B7EC-1A34EE288E2F} + {F9FD36E6-0ED0-4649-8E1C-33B2CDE85B14} = {A8C4583C-034E-47AF-B7EC-1A34EE288E2F} {867AD9F8-FD56-469D-A90D-C569EB9C3D2A} = {94CC5A11-DA0F-413C-96CA-01DB0FC426E0} {26611C4C-6910-498A-9FBA-BECC09392ADE} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B} {FCD72398-F832-4914-8ACF-EA4C1DD24BBF} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B} diff --git a/docs/README.md b/docs/README.md index 5a7805ed..2bbc2bd3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -104,7 +104,7 @@ We can customize the EShop for complex application scenarios. * Stores * Plugin modules - * Baskets + * [Baskets](https://github.com/EasyAbp/EShop/tree/dev/plugins/Baskets) * Coupons * Inventories * [DaprActors](https://github.com/EasyAbp/EShop/tree/dev/plugins/Inventories/DaprActors) diff --git a/docs/plugins/baskets/README.md b/docs/plugins/baskets/README.md new file mode 100644 index 00000000..8f525b25 --- /dev/null +++ b/docs/plugins/baskets/README.md @@ -0,0 +1,72 @@ +# EShop.Plugins.Baskets + +[![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.Baskets.Domain.Shared.svg?style=flat-square)](https://www.nuget.org/packages/EasyAbp.EShop.Plugins.Baskets.Domain.Shared) +[![NuGet Download](https://img.shields.io/nuget/dt/EasyAbp.EShop.Plugins.Baskets.Domain.Shared.svg?style=flat-square)](https://www.nuget.org/packages/EasyAbp.EShop.Plugins.Baskets.Domain.Shared) +[![Discord online](https://badgen.net/discord/online-members/S6QaezrCRq?label=Discord)](https://discord.gg/S6QaezrCRq) +[![GitHub stars](https://img.shields.io/github/stars/EasyAbp/EShop?style=social)](https://www.github.com/EasyAbp/EShop) + +🛒 A baskets (cart) plugin for EShop. It supports both the server-side pattern and the client-side pattern. + +## 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.Plugins.Baskets.Application + * EasyAbp.EShop.Plugins.Baskets.Application.Contracts + * EasyAbp.EShop.Plugins.Baskets.Domain + * EasyAbp.EShop.Plugins.Baskets.Domain.Shared + * EasyAbp.EShop.Plugins.Baskets.EntityFrameworkCore + * EasyAbp.EShop.Plugins.Baskets.HttpApi + * EasyAbp.EShop.Plugins.Baskets.HttpApi.Client + * (Optional) EasyAbp.EShop.Plugins.Baskets.MongoDB + * (Optional) EasyAbp.EShop.Plugins.Baskets.Web + +1. 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)) + +1. Add `builder.ConfigureEShopPluginsBaskets();` to the `OnModelCreating()` method in **MyProjectMigrationsDbContext.cs**. + +1. 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). + +## Server-side Baskets Pattern + +The server-side basket is for identified(logon) users. It requests the basket [backend service APIs](https://github.com/EasyAbp/EShop/blob/dev/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.HttpApi/EasyAbp/EShop/Plugins/Baskets/BasketItems/BasketItemController.cs) are available. + +1. Before you create a basket item, use `/api/e-shop/orders/order/check-create` (POST) to check whether the current user is allowed to create an order with it. +1. Use `/api/e-shop/plugins/baskets/basket-item` (POST) to create basket items on the server side. Extra properties are allowed. +1. Use `/api/e-shop/plugins/baskets/basket-item` (GET) to get basket the item list. The returned "IsInvalid" property shows whether you can use the item to create an order. +1. Use `/api/e-shop/plugins/baskets/basket-item/{id}` (PUT) or `/api/e-shop/plugins/baskets/basket-item/{id}` (DELETE) to change an item's quantity or remove an item. + +## Client-side Baskets Pattern + +You should store the basket items in the client(browser) cache as a collection of [IBasketItem](https://github.com/EasyAbp/EShop/blob/dev/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/BasketItems/IBasketItem.cs). The client-side doesn't depend on the baskets module backend service APIs. + +### With Backend + +If you install the backend, it provides APIs to help refresh your client-side basket items. + +1. Before you create a basket item, use `/api/e-shop/orders/order/check-create` (POST) to check whether the current user is allowed to create an order with it. +1. Use `/api/e-shop/plugins/baskets/basket-item/generate-client-side-data` (POST) to refresh your client-side basket items anytime. + +### Without Backend + +If you don't install the backend, you can still create and store the basket items locally, but the client-side basket items will not validate and never update. + +## How To Determine a Basket Pattern + +The server-side baskets pattern will take effect if all the following conditions are met: +1. The baskets module backend has been installed and is available. +1. The `EasyAbp.EShop.Plugins.Baskets.EnableServerSideBaskets` setting value is "True". +1. The current user has the `EasyAbp.EShop.Plugins.Baskets.BasketItem` permission. + +## What if Anonymous Users Add Basket Items and Then Log In? + +The client(browser) should try to add the existing items to the server-side and remove them from the client-side. You can implement it by referring to the [index.js](https://github.com/EasyAbp/EShop/blob/dev/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Web/Pages/EShop/Plugins/Baskets/BasketItems/BasketItem/index.js). + +## Use Basket Items To Create an Order + +We don't provide a built-in way to convert basket items to an order creation DTO. + +Create an order manually by the front end, and remember to map the basket item's extra properties to the order creation DTO since some special product may need them. + +After creating the order, the front end should clear the unused basket items. \ No newline at end of file diff --git a/docs/plugins/booking/README.md b/docs/plugins/booking/README.md index ebf463d6..ed03f48b 100644 --- a/docs/plugins/booking/README.md +++ b/docs/plugins/booking/README.md @@ -16,7 +16,7 @@ A booking-business plugin for EShop. It extends EShop to use the [EasyAbp.Bookin * EasyAbp.EShop.Orders.Booking.Application (install at EasyAbp.EShop.Orders.Application location) * (Optional) EasyAbp.EShop.Payments.Booking.Application (install at EasyAbp.EShop.Payments.Application location) - * EasyAbp.EShop.Plugins.Booking.Application + * (Optional) EasyAbp.EShop.Plugins.Baskets.Booking.Application (install at EasyAbp.EShop.Plugins.Baskets.Application location) * EasyAbp.EShop.Plugins.Booking.Application * EasyAbp.EShop.Plugins.Booking.Application.Contracts * EasyAbp.EShop.Plugins.Booking.Domain diff --git a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/Dtos/CheckCreateOrderInput.cs b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/Dtos/CheckCreateOrderInput.cs new file mode 100644 index 00000000..c9b83892 --- /dev/null +++ b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/Dtos/CheckCreateOrderInput.cs @@ -0,0 +1,8 @@ +using System; + +namespace EasyAbp.EShop.Orders.Orders.Dtos; + +[Serializable] +public class CheckCreateOrderInput : CreateOrderDto +{ +} \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/Dtos/CheckCreateOrderResultDto.cs b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/Dtos/CheckCreateOrderResultDto.cs new file mode 100644 index 00000000..f6add213 --- /dev/null +++ b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/Dtos/CheckCreateOrderResultDto.cs @@ -0,0 +1,11 @@ +using System; + +namespace EasyAbp.EShop.Orders.Orders.Dtos; + +[Serializable] +public class CheckCreateOrderResultDto +{ + public bool CanCreate { get; set; } + + public string Reason { get; set; } +} \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/IOrderAppService.cs b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/IOrderAppService.cs index 5c6ae2cc..081c7b0b 100644 --- a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/IOrderAppService.cs +++ b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/IOrderAppService.cs @@ -19,5 +19,7 @@ namespace EasyAbp.EShop.Orders.Orders Task CancelAsync(Guid id, CancelOrderInput input); Task UpdateStaffRemarkAsync(Guid id, UpdateStaffRemarkInput input); + + Task CheckCreateAsync(CheckCreateOrderInput input); } } \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/OrderAppService.cs b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/OrderAppService.cs index f8759d9a..06c5d04d 100644 --- a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/OrderAppService.cs +++ b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/OrderAppService.cs @@ -12,6 +12,7 @@ using EasyAbp.EShop.Stores.Stores; using Microsoft.AspNetCore.Authorization; using Volo.Abp; using Volo.Abp.Application.Dtos; +using Volo.Abp.Uow; using Volo.Abp.Users; namespace EasyAbp.EShop.Orders.Orders @@ -91,7 +92,7 @@ namespace EasyAbp.EShop.Orders.Orders var productDict = await GetProductDictionaryAsync(input.OrderLines.Select(dto => dto.ProductId).ToList()); ThrowIfExistFlashSalesProduct(productDict); - + await AuthorizationService.CheckAsync( new OrderCreationResource { @@ -108,11 +109,12 @@ namespace EasyAbp.EShop.Orders.Orders .Where(x => x.HasValue) .Select(x => x.Value) .ToList(); - + var productDetailDict = await GetProductDetailDictionaryAsync(productDetailIds); // Todo: Can we use IProductDataScopedCache/IProductDetailDataScopedCache instead of productDict/productDetailDict? - var order = await _newOrderGenerator.GenerateAsync(CurrentUser.GetId(), input, productDict, productDetailDict); + var order = await _newOrderGenerator.GenerateAsync(CurrentUser.GetId(), input, productDict, + productDetailDict); await DiscountOrderAsync(order, productDict); @@ -203,11 +205,11 @@ namespace EasyAbp.EShop.Orders.Orders return await MapToGetOutputDtoAsync(order); } - + public virtual async Task CancelAsync(Guid id, CancelOrderInput input) { var order = await GetEntityByIdAsync(id); - + await AuthorizationService.CheckAsync( order, new OrderOperationAuthorizationRequirement(OrderOperation.Cancellation) @@ -225,10 +227,38 @@ namespace EasyAbp.EShop.Orders.Orders await CheckMultiStorePolicyAsync(order.StoreId, OrdersPermissions.Orders.Manage); order.SetStaffRemark(input.StaffRemark); - + await Repository.UpdateAsync(order, true); return await MapToGetOutputDtoAsync(order); } + + public virtual async Task CheckCreateAsync(CheckCreateOrderInput input) + { + using var uow = UnitOfWorkManager.Begin(new AbpUnitOfWorkOptions(true), true); + + try + { + await CreateAsync(input); + + await uow.RollbackAsync(); + } + catch + { + await uow.RollbackAsync(); + + return new CheckCreateOrderResultDto + { + CanCreate = false, + Reason = "Unknown" // Todo: get failure reason + }; + } + + return new CheckCreateOrderResultDto + { + CanCreate = true, + Reason = null, + }; + } } } \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.HttpApi/EasyAbp/EShop/Orders/Orders/OrderController.cs b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.HttpApi/EasyAbp/EShop/Orders/Orders/OrderController.cs index b109f3af..644e636a 100644 --- a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.HttpApi/EasyAbp/EShop/Orders/Orders/OrderController.cs +++ b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.HttpApi/EasyAbp/EShop/Orders/Orders/OrderController.cs @@ -81,5 +81,12 @@ namespace EasyAbp.EShop.Orders.Orders { return _service.UpdateStaffRemarkAsync(id, input); } + + [HttpPost] + [Route("check-create")] + public virtual Task CheckCreateAsync(CheckCreateOrderInput input) + { + return _service.CheckCreateAsync(input); + } } } 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 3da6263f..7ca763f0 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 @@ -13,6 +13,7 @@ using Microsoft.Extensions.DependencyInjection; using NSubstitute; using Shouldly; using Volo.Abp; +using Volo.Abp.Domain.Repositories; using Volo.Abp.Settings; using Volo.Abp.Timing; using Xunit; @@ -113,6 +114,88 @@ namespace EasyAbp.EShop.Orders.Orders services.AddTransient(_ => productDetailAppService); } + [Fact] + public async Task Check_Create_Order_Should_Succeed() + { + var orderRepository = ServiceProvider.GetRequiredService(); + var orderCount = 0; + await WithUnitOfWorkAsync(async () => + { + orderCount = await orderRepository.CountAsync(); + }); + + // Arrange + var checkCreateOrderInput = new CheckCreateOrderInput + { + CustomerRemark = "customer remark", + StoreId = OrderTestData.Store1Id, + OrderLines = new List + { + new CreateOrderLineDto + { + ProductId = OrderTestData.Product1Id, + ProductSkuId = OrderTestData.ProductSku1Id, + Quantity = 10 + } + } + }; + + CheckCreateOrderResultDto resultDto = null; + // Act + await WithUnitOfWorkAsync(async () => + { + resultDto = await _orderAppService.CheckCreateAsync(checkCreateOrderInput); + }); + + // Assert + resultDto.CanCreate.ShouldBeTrue(); + await WithUnitOfWorkAsync(async () => + { + orderCount.ShouldBeEquivalentTo(await orderRepository.CountAsync()); + }); + } + + [Fact] + public async Task Check_Create_Order_Should_Fail() + { + var orderRepository = ServiceProvider.GetRequiredService(); + var orderCount = 0; + await WithUnitOfWorkAsync(async () => + { + orderCount = await orderRepository.CountAsync(); + }); + + // Arrange + var checkCreateOrderInput = new CheckCreateOrderInput + { + CustomerRemark = "customer remark", + StoreId = OrderTestData.Store1Id, + OrderLines = new List + { + new CreateOrderLineDto + { + ProductId = OrderTestData.Product1Id, + ProductSkuId = OrderTestData.ProductSku1Id, + Quantity = 101 // limited range: 1-100 + } + } + }; + + CheckCreateOrderResultDto resultDto = null; + // Act + await WithUnitOfWorkAsync(async () => + { + resultDto = await _orderAppService.CheckCreateAsync(checkCreateOrderInput); + }); + + // Assert + resultDto.CanCreate.ShouldBeFalse(); + await WithUnitOfWorkAsync(async () => + { + orderCount.ShouldBeEquivalentTo(await orderRepository.CountAsync()); + }); + } + [Fact] public async Task Order_Should_Be_Created() { diff --git a/plugins/Baskets/README.md b/plugins/Baskets/README.md new file mode 120000 index 00000000..559ea800 --- /dev/null +++ b/plugins/Baskets/README.md @@ -0,0 +1 @@ +../../docs/plugins/baskets/README.md \ No newline at end of file diff --git a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application.Contracts/EasyAbp/EShop/Plugins/Baskets/BasketItems/Dtos/ClientSideBasketItemModel.cs b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application.Contracts/EasyAbp/EShop/Plugins/Baskets/BasketItems/Dtos/ClientSideBasketItemModel.cs index 5bc1688b..2256d63e 100644 --- a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application.Contracts/EasyAbp/EShop/Plugins/Baskets/BasketItems/Dtos/ClientSideBasketItemModel.cs +++ b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application.Contracts/EasyAbp/EShop/Plugins/Baskets/BasketItems/Dtos/ClientSideBasketItemModel.cs @@ -1,10 +1,11 @@ using System; using JetBrains.Annotations; +using Volo.Abp.ObjectExtending; namespace EasyAbp.EShop.Plugins.Baskets.BasketItems.Dtos; [Serializable] -public class ClientSideBasketItemModel : IBasketItem +public class ClientSideBasketItemModel : ExtensibleObject, IBasketItem { public Guid Id { get; set; } diff --git a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application.Contracts/EasyAbp/EShop/Plugins/Baskets/BasketItems/Dtos/GetBasketItemListDto.cs b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application.Contracts/EasyAbp/EShop/Plugins/Baskets/BasketItems/Dtos/GetBasketItemListDto.cs index a0610631..185fc31e 100644 --- a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application.Contracts/EasyAbp/EShop/Plugins/Baskets/BasketItems/Dtos/GetBasketItemListDto.cs +++ b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application.Contracts/EasyAbp/EShop/Plugins/Baskets/BasketItems/Dtos/GetBasketItemListDto.cs @@ -1,13 +1,11 @@ using System; -using System.ComponentModel.DataAnnotations; namespace EasyAbp.EShop.Plugins.Baskets.BasketItems.Dtos { [Serializable] public class GetBasketItemListDto { - [Required] - public string BasketName { get; set; } + public string BasketName { get; set; } = BasketsConsts.DefaultBasketName; /// /// Specify the basket item owner user ID. Use current user ID if this property is null. diff --git a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application/EasyAbp/EShop/Plugins/Baskets/BasketItems/BasicBasketItemProductInfoUpdater.cs b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application/EasyAbp/EShop/Plugins/Baskets/BasketItems/BasicBasketItemProductInfoUpdater.cs new file mode 100644 index 00000000..83cbaa78 --- /dev/null +++ b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application/EasyAbp/EShop/Plugins/Baskets/BasketItems/BasicBasketItemProductInfoUpdater.cs @@ -0,0 +1,70 @@ +using System; +using System.Threading.Tasks; +using EasyAbp.EShop.Products.Products; +using EasyAbp.EShop.Products.Products.Dtos; +using Volo.Abp.DependencyInjection; + +namespace EasyAbp.EShop.Plugins.Baskets.BasketItems; + +public class BasicBasketItemProductInfoUpdater : IBasketItemProductInfoUpdater, ITransientDependency +{ + protected IProductSkuDescriptionProvider ProductSkuDescriptionProvider { get; } + + public BasicBasketItemProductInfoUpdater(IProductSkuDescriptionProvider productSkuDescriptionProvider) + { + ProductSkuDescriptionProvider = productSkuDescriptionProvider; + } + + public virtual Task UpdateForAnonymousAsync(int targetQuantity, IBasketItem item, ProductDto productDto) + { + return InternalUpdateAsync(targetQuantity, item, productDto); + } + + public virtual Task UpdateForIdentifiedAsync(int targetQuantity, IBasketItem item, ProductDto productDto) + { + return InternalUpdateAsync(targetQuantity, item, productDto); + } + + protected virtual async Task InternalUpdateAsync(int targetQuantity, IBasketItem item, ProductDto productDto) + { + var productSkuDto = productDto.FindSkuById(item.ProductSkuId); + + if (productSkuDto == null) + { + item.SetIsInvalid(true); + return; + } + + item.UpdateProductData(targetQuantity, new ProductDataModel + { + MediaResources = productSkuDto.MediaResources ?? productDto.MediaResources, + ProductUniqueName = productDto.UniqueName, + ProductDisplayName = productDto.DisplayName, + SkuName = productSkuDto.Name, + SkuDescription = await ProductSkuDescriptionProvider.GenerateAsync(productDto, productSkuDto), + Currency = productSkuDto.Currency, + UnitPrice = productSkuDto.DiscountedPrice, + TotalPrice = productSkuDto.DiscountedPrice * item.Quantity, + TotalDiscount = (productSkuDto.Price - productSkuDto.DiscountedPrice) * item.Quantity, + Inventory = productSkuDto.Inventory + }); + + if (productDto.InventoryStrategy != InventoryStrategy.NoNeed && targetQuantity > productSkuDto.Inventory) + { + item.SetIsInvalid(true); + return; + } + + if (!productDto.IsPublished) + { + item.SetIsInvalid(true); + return; + } + + if (!targetQuantity.IsBetween(productSkuDto.OrderMinQuantity, productSkuDto.OrderMaxQuantity)) + { + item.SetIsInvalid(true); + return; + } + } +} \ No newline at end of file diff --git a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application/EasyAbp/EShop/Plugins/Baskets/BasketItems/BasketItemAppService.cs b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application/EasyAbp/EShop/Plugins/Baskets/BasketItems/BasketItemAppService.cs index 383fe0e0..f58bbea8 100644 --- a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application/EasyAbp/EShop/Plugins/Baskets/BasketItems/BasketItemAppService.cs +++ b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application/EasyAbp/EShop/Plugins/Baskets/BasketItems/BasketItemAppService.cs @@ -11,11 +11,13 @@ using Microsoft.AspNetCore.Authorization; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; using Volo.Abp.Authorization; +using Volo.Abp.ObjectExtending; using Volo.Abp.Users; namespace EasyAbp.EShop.Plugins.Baskets.BasketItems { - public class BasketItemAppService : CrudAppService, + public class BasketItemAppService : CrudAppService, IBasketItemAppService { protected override string GetPolicyName { get; set; } = BasketsPermissions.BasketItem.Default; @@ -27,18 +29,15 @@ namespace EasyAbp.EShop.Plugins.Baskets.BasketItems private readonly IBasketItemRepository _repository; private readonly IProductUpdateRepository _productUpdateRepository; private readonly IProductAppService _productAppService; - private readonly IProductSkuDescriptionProvider _productSkuDescriptionProvider; public BasketItemAppService( IBasketItemRepository repository, IProductUpdateRepository productUpdateRepository, - IProductAppService productAppService, - IProductSkuDescriptionProvider productSkuDescriptionProvider) : base(repository) + IProductAppService productAppService) : base(repository) { _repository = repository; _productUpdateRepository = productUpdateRepository; _productAppService = productAppService; - _productSkuDescriptionProvider = productSkuDescriptionProvider; } public override async Task GetAsync(Guid id) @@ -46,14 +45,14 @@ namespace EasyAbp.EShop.Plugins.Baskets.BasketItems await CheckGetPolicyAsync(); var item = await GetEntityByIdAsync(id); - + if (item.UserId != CurrentUser.GetId() && !await IsCurrentUserManagerAsync()) { throw new AbpAuthorizationException(); } var productUpdate = await _productUpdateRepository.FindAsync(x => x.ProductSkuId == item.ProductSkuId); - + if (productUpdate != null) { var itemUpdateTime = item.LastModificationTime ?? item.CreationTime; @@ -62,9 +61,9 @@ namespace EasyAbp.EShop.Plugins.Baskets.BasketItems if (itemUpdateTime < productUpdateTime) { var productDto = await _productAppService.GetAsync(item.ProductId); - + await UpdateProductDataAsync(item.Quantity, item, productDto); - + await _repository.UpdateAsync(item, true); } } @@ -75,7 +74,7 @@ namespace EasyAbp.EShop.Plugins.Baskets.BasketItems public override async Task> GetListAsync(GetBasketItemListDto input) { await CheckGetListPolicyAsync(); - + if (input.UserId != CurrentUser.GetId()) { await AuthorizationService.CheckAsync(BasketsPermissions.BasketItem.Manage); @@ -124,49 +123,33 @@ namespace EasyAbp.EShop.Plugins.Baskets.BasketItems await MapToGetListOutputDtosAsync(items) ); } - - protected virtual async Task UpdateProductDataAsync(int quantity, IBasketItem item, ProductDto productDto) + + protected virtual async Task UpdateProductDataAsync(int targetQuantity, IBasketItem item, ProductDto productDto) { item.SetIsInvalid(false); - var productSkuDto = productDto.FindSkuById(item.ProductSkuId); - - if (productSkuDto == null) - { - item.SetIsInvalid(true); - - return; - } + var updaters = LazyServiceProvider.LazyGetRequiredService>(); - if (productDto.InventoryStrategy != InventoryStrategy.NoNeed && quantity > productSkuDto.Inventory) + if (CurrentUser.IsAuthenticated) { - item.SetIsInvalid(true); + foreach (var updater in updaters) + { + await updater.UpdateForIdentifiedAsync(targetQuantity, item, productDto); + } } - - item.UpdateProductData(quantity, new ProductDataModel - { - MediaResources = productSkuDto.MediaResources ?? productDto.MediaResources, - ProductUniqueName = productDto.UniqueName, - ProductDisplayName = productDto.DisplayName, - SkuName = productSkuDto.Name, - SkuDescription = await _productSkuDescriptionProvider.GenerateAsync(productDto, productSkuDto), - Currency = productSkuDto.Currency, - UnitPrice = productSkuDto.DiscountedPrice, - TotalPrice = productSkuDto.DiscountedPrice * item.Quantity, - TotalDiscount = (productSkuDto.Price - productSkuDto.DiscountedPrice) * item.Quantity, - Inventory = productSkuDto.Inventory - }); - - if (!productDto.IsPublished) + else { - item.SetIsInvalid(true); + foreach (var updater in updaters) + { + await updater.UpdateForAnonymousAsync(targetQuantity, item, productDto); + } } } protected override async Task> CreateFilteredQueryAsync(GetBasketItemListDto input) { var userId = input.UserId ?? CurrentUser.GetId(); - + return (await ReadOnlyRepository.GetQueryableAsync()) .Where(item => item.UserId == userId && item.BasketName == input.BasketName); } @@ -190,12 +173,12 @@ namespace EasyAbp.EShop.Plugins.Baskets.BasketItems if (item != null) { await UpdateProductDataAsync(input.Quantity + item.Quantity, item, productDto); - + await Repository.UpdateAsync(item, autoSave: true); return await MapToGetOutputDtoAsync(item); } - + var productSkuDto = productDto.FindSkuById(input.ProductSkuId); if (productSkuDto == null) @@ -205,9 +188,11 @@ namespace EasyAbp.EShop.Plugins.Baskets.BasketItems item = new BasketItem(GuidGenerator.Create(), CurrentTenant.Id, input.BasketName, CurrentUser.GetId(), productDto.StoreId, input.ProductId, input.ProductSkuId); + + input.MapExtraPropertiesTo(item); await UpdateProductDataAsync(input.Quantity, item, productDto); - + await Repository.InsertAsync(item, autoSave: true); return await MapToGetOutputDtoAsync(item); @@ -218,7 +203,7 @@ namespace EasyAbp.EShop.Plugins.Baskets.BasketItems await CheckUpdatePolicyAsync(); var item = await GetEntityByIdAsync(id); - + if (item.UserId != CurrentUser.GetId() && !await IsCurrentUserManagerAsync()) { throw new AbpAuthorizationException(); @@ -226,8 +211,10 @@ namespace EasyAbp.EShop.Plugins.Baskets.BasketItems var productDto = await _productAppService.GetAsync(item.ProductId); + input.MapExtraPropertiesTo(item); + await UpdateProductDataAsync(input.Quantity, item, productDto); - + await Repository.UpdateAsync(item, autoSave: true); return await MapToGetOutputDtoAsync(item); @@ -243,20 +230,20 @@ namespace EasyAbp.EShop.Plugins.Baskets.BasketItems { throw new AbpAuthorizationException(); } - + await _repository.DeleteAsync(item, true); } public virtual async Task BatchDeleteAsync(IEnumerable ids) { await CheckDeletePolicyAsync(); - + var isCurrentUserManager = await IsCurrentUserManagerAsync(); - + foreach (var id in ids) { var item = await GetEntityByIdAsync(id); - + if (item.UserId != CurrentUser.GetId() && !isCurrentUserManager) { throw new AbpAuthorizationException(); @@ -270,7 +257,7 @@ namespace EasyAbp.EShop.Plugins.Baskets.BasketItems GenerateClientSideDataInput input) { var itemList = new List(); - + var products = new Dictionary(); foreach (var dto in input.Items) @@ -290,7 +277,7 @@ namespace EasyAbp.EShop.Plugins.Baskets.BasketItems } var id = dto.Id ?? GuidGenerator.Create(); - + var item = new ClientSideBasketItemModel(id, dto.BasketName, productDto.StoreId, dto.ProductId, dto.ProductSkuId); diff --git a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application/EasyAbp/EShop/Plugins/Baskets/BasketItems/IBasketItemProductInfoUpdater.cs b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application/EasyAbp/EShop/Plugins/Baskets/BasketItems/IBasketItemProductInfoUpdater.cs new file mode 100644 index 00000000..5aa940d3 --- /dev/null +++ b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application/EasyAbp/EShop/Plugins/Baskets/BasketItems/IBasketItemProductInfoUpdater.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using EasyAbp.EShop.Products.Products.Dtos; + +namespace EasyAbp.EShop.Plugins.Baskets.BasketItems; + +public interface IBasketItemProductInfoUpdater +{ + Task UpdateForAnonymousAsync(int targetQuantity, IBasketItem item, ProductDto productDto); + + Task UpdateForIdentifiedAsync(int targetQuantity, IBasketItem item, ProductDto productDto); +} \ No newline at end of file diff --git a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/BasketItems/IBasketItem.cs b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/BasketItems/IBasketItem.cs index bd4c67e7..15d13d5a 100644 --- a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/BasketItems/IBasketItem.cs +++ b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/BasketItems/IBasketItem.cs @@ -1,9 +1,10 @@ using System; using JetBrains.Annotations; +using Volo.Abp.Data; namespace EasyAbp.EShop.Plugins.Baskets.BasketItems; -public interface IBasketItem : IProductData +public interface IBasketItem : IProductData, IHasExtraProperties { Guid Id { get; } diff --git a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/BasketsErrorCodes.cs b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/BasketsErrorCodes.cs index 6bdd1cc5..d123b535 100644 --- a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/BasketsErrorCodes.cs +++ b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/BasketsErrorCodes.cs @@ -2,6 +2,7 @@ { public static class BasketsErrorCodes { + public const string CheckCreateOrderFailed = "EasyAbp.EShop.Plugins.Baskets:CheckCreateOrderFailed"; public const string ProductSkuNotFound = "EasyAbp.EShop.Plugins.Baskets:ProductSkuNotFound"; } } diff --git a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/Localization/en.json b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/Localization/en.json index dd7ff74c..ba85b081 100644 --- a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/Localization/en.json +++ b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/Localization/en.json @@ -27,6 +27,7 @@ "EditBasketItem": "Edit", "BasketItemDeletionConfirmationMessage": "Are you sure to delete the basket item {0}?", "SuccessfullyDeleted": "Successfully deleted", + "EasyAbp.EShop.Plugins.Baskets:CheckCreateOrderFailed": "You are not allowed to purchase the specified item. Reason: {reason}", "EasyAbp.EShop.Plugins.Baskets:ProductSkuNotFound": "Product {productId} (SKU: {productSkuId}) not found." } } \ No newline at end of file diff --git a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/Localization/zh-Hans.json b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/Localization/zh-Hans.json index 31ebc3d7..9d917b82 100644 --- a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/Localization/zh-Hans.json +++ b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/Localization/zh-Hans.json @@ -27,6 +27,7 @@ "EditBasketItem": "编辑", "BasketItemDeletionConfirmationMessage": "确认删除购物车项 {0}?", "SuccessfullyDeleted": "删除成功", + "EasyAbp.EShop.Plugins.Baskets:CheckCreateOrderFailed": "您无法购买指定商品,原因:{reason}", "EasyAbp.EShop.Plugins.Baskets:ProductSkuNotFound": "商品{productId}(SKU: {productSkuId})未找到" } } \ No newline at end of file diff --git a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/Localization/zh-Hant.json b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/Localization/zh-Hant.json index a88995bb..20e509a7 100644 --- a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/Localization/zh-Hant.json +++ b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/Localization/zh-Hant.json @@ -27,6 +27,7 @@ "EditBasketItem": "編輯", "BasketItemDeletionConfirmationMessage": "確認刪除購物車項 {0}?", "SuccessfullyDeleted": "刪除成功", + "EasyAbp.EShop.Plugins.Baskets:CheckCreateOrderFailed": "您無法購買指定商品,原因:{reason}", "EasyAbp.EShop.Plugins.Baskets:ProductSkuNotFound": "商品{productId}(SKU: {productSkuId})未找到" } } \ No newline at end of file diff --git a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain/EasyAbp/EShop/Plugins/Baskets/BasketItems/IProductUpdateRecorder.cs b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain/EasyAbp/EShop/Plugins/Baskets/BasketItems/IProductUpdateRecorder.cs deleted file mode 100644 index 3dfdc29b..00000000 --- a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain/EasyAbp/EShop/Plugins/Baskets/BasketItems/IProductUpdateRecorder.cs +++ /dev/null @@ -1,12 +0,0 @@ -using EasyAbp.EShop.Products.ProductInventories; -using EasyAbp.EShop.Products.Products; -using Volo.Abp.Domain.Entities.Events.Distributed; -using Volo.Abp.EventBus.Distributed; - -namespace EasyAbp.EShop.Plugins.Baskets.BasketItems -{ - public interface IProductUpdateRecorder - { - - } -} \ No newline at end of file diff --git a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain/EasyAbp/EShop/Plugins/Baskets/ProductUpdates/IProductUpdateRecorder.cs b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain/EasyAbp/EShop/Plugins/Baskets/ProductUpdates/IProductUpdateRecorder.cs new file mode 100644 index 00000000..2ec3e842 --- /dev/null +++ b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain/EasyAbp/EShop/Plugins/Baskets/ProductUpdates/IProductUpdateRecorder.cs @@ -0,0 +1,6 @@ +namespace EasyAbp.EShop.Plugins.Baskets.ProductUpdates; + +public interface IProductUpdateRecorder +{ + +} \ No newline at end of file diff --git a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain/EasyAbp/EShop/Plugins/Baskets/BasketItems/ProductUpdateRecorder.cs b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain/EasyAbp/EShop/Plugins/Baskets/ProductUpdates/ProductUpdateRecorder.cs similarity index 95% rename from plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain/EasyAbp/EShop/Plugins/Baskets/BasketItems/ProductUpdateRecorder.cs rename to plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain/EasyAbp/EShop/Plugins/Baskets/ProductUpdates/ProductUpdateRecorder.cs index 673171ef..c8d577f3 100644 --- a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain/EasyAbp/EShop/Plugins/Baskets/BasketItems/ProductUpdateRecorder.cs +++ b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain/EasyAbp/EShop/Plugins/Baskets/ProductUpdates/ProductUpdateRecorder.cs @@ -1,7 +1,6 @@ using System; using System.Linq; using System.Threading.Tasks; -using EasyAbp.EShop.Plugins.Baskets.ProductUpdates; using EasyAbp.EShop.Products.ProductInventories; using EasyAbp.EShop.Products.Products; using Volo.Abp.DependencyInjection; @@ -11,7 +10,7 @@ using Volo.Abp.Guids; using Volo.Abp.MultiTenancy; using Volo.Abp.Uow; -namespace EasyAbp.EShop.Plugins.Baskets.BasketItems +namespace EasyAbp.EShop.Plugins.Baskets.ProductUpdates { public class ProductUpdateRecorder : IProductUpdateRecorder, diff --git a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Web/EShopPluginsBasketsWebModule.cs b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Web/EShopPluginsBasketsWebModule.cs index 87e8b5b0..df6b7ca1 100644 --- a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Web/EShopPluginsBasketsWebModule.cs +++ b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Web/EShopPluginsBasketsWebModule.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Mvc.RazorPages; +using EasyAbp.EShop.Orders; +using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.DependencyInjection; using EasyAbp.EShop.Plugins.Baskets.Localization; using EasyAbp.EShop.Plugins.Baskets.Web.Menus; @@ -12,6 +13,7 @@ using Volo.Abp.VirtualFileSystem; namespace EasyAbp.EShop.Plugins.Baskets.Web { [DependsOn( + typeof(EShopOrdersApplicationContractsModule), typeof(EShopPluginsBasketsApplicationContractsModule), typeof(AbpAspNetCoreMvcUiThemeSharedModule), typeof(AbpAutoMapperModule) diff --git a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Web/EasyAbp.EShop.Plugins.Baskets.Web.csproj b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Web/EasyAbp.EShop.Plugins.Baskets.Web.csproj index 26f20ae2..2ac0989a 100644 --- a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Web/EasyAbp.EShop.Plugins.Baskets.Web.csproj +++ b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Web/EasyAbp.EShop.Plugins.Baskets.Web.csproj @@ -17,6 +17,7 @@ + diff --git a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Web/Pages/EShop/Plugins/Baskets/BasketItems/BasketItem/CreateModal.cshtml b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Web/Pages/EShop/Plugins/Baskets/BasketItems/BasketItem/CreateModal.cshtml index ac0ede94..fea6d104 100644 --- a/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Web/Pages/EShop/Plugins/Baskets/BasketItems/BasketItem/CreateModal.cshtml +++ b/plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Web/Pages/EShop/Plugins/Baskets/BasketItems/BasketItem/CreateModal.cshtml @@ -1,6 +1,6 @@ @page +@using EasyAbp.EShop.Plugins.Baskets @using EasyAbp.EShop.Plugins.Baskets.Localization -@using EasyAbp.EShop.Plugins.Baskets.Permissions @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Mvc.Localization @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal @@ -25,16 +25,15 @@