Browse Source

Introduce order automatically cancel feature.

#69
pull/119/head
gdlcf88 5 years ago
parent
commit
fb659b14e7
  1. 2
      common.props
  2. 5
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/Dtos/CreateOrderLineDto.cs
  3. 2
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/Dtos/OrderDto.cs
  4. 10
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/NewOrderGenerator.cs
  5. 5
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/OrderAppService.cs
  6. 7
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/OrdersConsts.cs
  7. 1
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp.EShop.Orders.Domain.csproj
  8. 2
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/EShopOrdersDomainModule.cs
  9. 66
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/DefaultUnpaidOrderAutoCancelProvider.cs
  10. 7
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/IUnpaidOrderAutoCancelProvider.cs
  11. 17
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/Order.cs
  12. 25
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderPaymentIdChangedEto.cs
  13. 14
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/UnpaidOrderAutoCancelArgs.cs
  14. 53
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/UnpaidOrderAutoCancelJob.cs
  15. 222
      modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/OrderAppServiceTests.cs
  16. 46
      modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/CancelPaymentEventHandler.cs
  17. 14
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/CreateUpdateProductDto.cs
  18. 9
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductDto.cs
  19. 2
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductSkuDto.cs
  20. 25
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/UpdateProductSkuDto.cs
  21. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/cs.json
  22. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/en.json
  23. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pl.json
  24. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pt-BR.json
  25. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/sl.json
  26. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/tr.json
  27. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/vi.json
  28. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hans.json
  29. 8
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hant.json
  30. 2
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/ProductsConsts.cs
  31. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/Product.cs
  32. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductSku.cs
  33. 15
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Settings/ProductsSettingDefinitionProvider.cs
  34. 9
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Settings/ProductsSettings.cs
  35. 7
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/CreateModal.cshtml.cs
  36. 6
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/ViewModels/CreateEditProductViewModel.cs
  37. 1
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/index.js
  38. 8
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/ViewModels/EditProductSkuViewModel.cs
  39. 5107
      samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20211012091401_AddedPaymentExpireInAndPaymentExpireIn.Designer.cs
  40. 44
      samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20211012091401_AddedPaymentExpireInAndPaymentExpireIn.cs
  41. 11
      samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/EShopSampleDbContextModelSnapshot.cs

2
common.props

@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Version>2.2.7</Version>
<Version>2.3.0</Version>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>EasyAbp Team</Authors>

5
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/Dtos/CreateOrderLineDto.cs

@ -1,5 +1,6 @@
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.ObjectExtending;
namespace EasyAbp.EShop.Orders.Orders.Dtos
@ -7,6 +8,9 @@ namespace EasyAbp.EShop.Orders.Orders.Dtos
[Serializable]
public class CreateOrderLineDto : ExtensibleObject
{
public const int MinimumQuantity = 1;
public const int MaximumQuantity = int.MaxValue;
[DisplayName("OrderLineProductId")]
public Guid ProductId { get; set; }
@ -14,6 +18,7 @@ namespace EasyAbp.EShop.Orders.Orders.Dtos
public Guid ProductSkuId { get; set; }
[DisplayName("OrderLineQuantity")]
[Range(MinimumQuantity, MaximumQuantity)]
public int Quantity { get; set; }
}
}

2
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/Dtos/OrderDto.cs

@ -45,6 +45,8 @@ namespace EasyAbp.EShop.Orders.Orders.Dtos
public DateTime? ReducedInventoryAfterPaymentTime { get; set; }
public DateTime? PaymentExpiration { get; set; }
public List<OrderLineDto> OrderLines { get; set; }
}
}

10
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/NewOrderGenerator.cs

@ -10,11 +10,13 @@ using Volo.Abp.DependencyInjection;
using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectExtending;
using Volo.Abp.Timing;
namespace EasyAbp.EShop.Orders.Orders
{
public class NewOrderGenerator : INewOrderGenerator, ITransientDependency
{
private readonly IClock _clock;
private readonly IGuidGenerator _guidGenerator;
private readonly ICurrentTenant _currentTenant;
private readonly IServiceProvider _serviceProvider;
@ -22,12 +24,14 @@ namespace EasyAbp.EShop.Orders.Orders
private readonly IProductSkuDescriptionProvider _productSkuDescriptionProvider;
public NewOrderGenerator(
IClock clock,
IGuidGenerator guidGenerator,
ICurrentTenant currentTenant,
IServiceProvider serviceProvider,
IOrderNumberGenerator orderNumberGenerator,
IProductSkuDescriptionProvider productSkuDescriptionProvider)
{
_clock = clock;
_guidGenerator = guidGenerator;
_currentTenant = currentTenant;
_serviceProvider = serviceProvider;
@ -53,6 +57,8 @@ namespace EasyAbp.EShop.Orders.Orders
}
var productTotalPrice = orderLines.Select(x => x.TotalPrice).Sum();
var paymentExpireIn = orderLines.Select(x => productDict[x.ProductId].GetSkuPaymentExpireIn(x.ProductSkuId)).Min();
var totalPrice = productTotalPrice;
var totalDiscount = orderLines.Select(x => x.TotalDiscount).Sum();
@ -67,7 +73,9 @@ namespace EasyAbp.EShop.Orders.Orders
totalDiscount: totalDiscount,
totalPrice: totalPrice,
actualTotalPrice: totalPrice - totalDiscount,
customerRemark: input.CustomerRemark);
customerRemark: input.CustomerRemark,
paymentExpiration: paymentExpireIn.HasValue ? _clock.Now.Add(paymentExpireIn.Value) : null
);
input.MapExtraPropertiesTo(order, MappingPropertyDefinitionChecks.Destination);

5
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/OrderAppService.cs

@ -84,8 +84,7 @@ namespace EasyAbp.EShop.Orders.Orders
{
// Todo: Check if the store is open.
var productDict = await GetProductDictionaryAsync(input.OrderLines.Select(dto => dto.ProductId).ToList(),
input.StoreId);
var productDict = await GetProductDictionaryAsync(input.OrderLines.Select(dto => dto.ProductId).ToList());
await AuthorizationService.CheckAsync(
new OrderCreationResource
@ -114,7 +113,7 @@ namespace EasyAbp.EShop.Orders.Orders
}
protected virtual async Task<Dictionary<Guid, ProductDto>> GetProductDictionaryAsync(
IEnumerable<Guid> productIds, Guid storeId)
IEnumerable<Guid> productIds)
{
var dict = new Dictionary<Guid, ProductDto>();

7
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/OrdersConsts.cs

@ -0,0 +1,7 @@
namespace EasyAbp.EShop.Orders
{
public static class OrdersConsts
{
public static string CancellationReason = "Order payment timed out and not paid";
}
}

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

@ -10,6 +10,7 @@
<ItemGroup>
<PackageReference Include="Volo.Abp.AutoMapper" Version="$(AbpVersion)" />
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="$(AbpVersion)" />
<PackageReference Include="Volo.Abp.BackgroundJobs.Abstractions" Version="$(AbpVersion)" />
<ProjectReference Include="..\..\..\EasyAbp.EShop.Payments\src\EasyAbp.EShop.Payments.Domain.Shared\EasyAbp.EShop.Payments.Domain.Shared.csproj" />
<ProjectReference Include="..\..\..\EasyAbp.EShop.Products\src\EasyAbp.EShop.Products.Domain.Shared\EasyAbp.EShop.Products.Domain.Shared.csproj" />
<ProjectReference Include="..\EasyAbp.EShop.Orders.Domain.Shared\EasyAbp.EShop.Orders.Domain.Shared.csproj" />

2
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/EShopOrdersDomainModule.cs

@ -4,6 +4,7 @@ using EasyAbp.EShop.Products;
using EasyAbp.EShop.Stores;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AutoMapper;
using Volo.Abp.BackgroundJobs;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Modularity;
@ -12,6 +13,7 @@ namespace EasyAbp.EShop.Orders
{
[DependsOn(
typeof(AbpAutoMapperModule),
typeof(AbpBackgroundJobsAbstractionsModule),
typeof(EShopPaymentsDomainSharedModule),
typeof(EShopProductsDomainSharedModule),
typeof(EShopOrdersDomainSharedModule)

66
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/DefaultUnpaidOrderAutoCancelProvider.cs

@ -0,0 +1,66 @@
using System.Threading.Tasks;
using Volo.Abp.BackgroundJobs;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.EventBus;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Timing;
using Volo.Abp.Uow;
namespace EasyAbp.EShop.Orders.Orders
{
[Dependency(TryRegister = true)]
[UnitOfWork]
public class DefaultUnpaidOrderAutoCancelProvider :
IUnpaidOrderAutoCancelProvider,
ILocalEventHandler<EntityCreatedEventData<Order>>,
ILocalEventHandler<OrderPaymentIdChangedEto>,
ITransientDependency
{
private readonly IClock _clock;
private readonly IOrderManager _orderManager;
private readonly IBackgroundJobManager _backgroundJobManager;
public DefaultUnpaidOrderAutoCancelProvider(
IClock clock,
IOrderManager orderManager,
IBackgroundJobManager backgroundJobManager)
{
_clock = clock;
_orderManager = orderManager;
_backgroundJobManager = backgroundJobManager;
}
public virtual async Task HandleEventAsync(EntityCreatedEventData<Order> eventData)
{
if (!eventData.Entity.PaymentExpiration.HasValue)
{
return;
}
var args = new UnpaidOrderAutoCancelArgs
{
TenantId = eventData.Entity.TenantId,
OrderId = eventData.Entity.Id
};
await _backgroundJobManager.EnqueueAsync(
args: args,
delay: eventData.Entity.PaymentExpiration.Value.Subtract(_clock.Now) // Todo: use a absolute time.
);
}
public virtual async Task HandleEventAsync(OrderPaymentIdChangedEto eventData)
{
if (!eventData.Order.PaymentExpiration.HasValue || eventData.ToPaymentId.HasValue)
{
return;
}
if (_clock.Now > eventData.Order.PaymentExpiration.Value && !eventData.Order.CanceledTime.HasValue)
{
await _orderManager.CancelAsync(eventData.Order, OrdersConsts.CancellationReason);
}
}
}
}

7
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/IUnpaidOrderAutoCancelProvider.cs

@ -0,0 +1,7 @@
namespace EasyAbp.EShop.Orders.Orders
{
public interface IUnpaidOrderAutoCancelProvider
{
}
}

17
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/Order.cs

@ -58,6 +58,8 @@ namespace EasyAbp.EShop.Orders.Orders
public virtual DateTime? ReducedInventoryAfterPaymentTime { get; protected set; }
public virtual DateTime? PaymentExpiration { get; protected set; }
public virtual List<OrderLine> OrderLines { get; protected set; }
public virtual List<OrderExtraFee> OrderExtraFees { get; protected set; }
@ -71,12 +73,13 @@ namespace EasyAbp.EShop.Orders.Orders
Guid? tenantId,
Guid storeId,
Guid customerUserId,
string currency,
[NotNull] string currency,
decimal productTotalPrice,
decimal totalDiscount,
decimal totalPrice,
decimal actualTotalPrice,
[CanBeNull] string customerRemark
[CanBeNull] string customerRemark,
DateTime? paymentExpiration
) : base(id)
{
TenantId = tenantId;
@ -88,6 +91,7 @@ namespace EasyAbp.EShop.Orders.Orders
TotalPrice = totalPrice;
ActualTotalPrice = actualTotalPrice;
CustomerRemark = customerRemark;
PaymentExpiration = paymentExpiration;
RefundAmount = 0;
@ -115,9 +119,16 @@ namespace EasyAbp.EShop.Orders.Orders
{
ReducedInventoryAfterPaymentTime = time;
}
public void SetPaymentExpiration(DateTime? paymentExpiration)
{
PaymentExpiration = paymentExpiration;
}
public void SetPaymentId(Guid? paymentId)
{
AddLocalEvent(new OrderPaymentIdChangedEto(TenantId, this, PaymentId, paymentId));
PaymentId = paymentId;
}

25
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderPaymentIdChangedEto.cs

@ -0,0 +1,25 @@
using System;
using Volo.Abp.MultiTenancy;
namespace EasyAbp.EShop.Orders.Orders
{
[Serializable]
public class OrderPaymentIdChangedEto : IMultiTenant
{
public Guid? TenantId { get; set; }
public Order Order { get; set; }
public Guid? FromPaymentId { get; set; }
public Guid? ToPaymentId { get; set; }
public OrderPaymentIdChangedEto(Guid? tenantId, Order order, Guid? fromPaymentId, Guid? toPaymentId)
{
TenantId = tenantId;
Order = order;
FromPaymentId = fromPaymentId;
ToPaymentId = toPaymentId;
}
}
}

14
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/UnpaidOrderAutoCancelArgs.cs

@ -0,0 +1,14 @@
using System;
using Volo.Abp.BackgroundJobs;
using Volo.Abp.MultiTenancy;
namespace EasyAbp.EShop.Orders.Orders
{
[BackgroundJobName("EShopUnpaidOrderAutoCancel")]
public class UnpaidOrderAutoCancelArgs : IMultiTenant
{
public Guid? TenantId { get; set; }
public Guid OrderId { get; set; }
}
}

53
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/UnpaidOrderAutoCancelJob.cs

@ -0,0 +1,53 @@
using System.Threading.Tasks;
using EasyAbp.PaymentService.Payments;
using Volo.Abp.BackgroundJobs;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Uow;
namespace EasyAbp.EShop.Orders.Orders
{
public class UnpaidOrderAutoCancelJob : AsyncBackgroundJob<UnpaidOrderAutoCancelArgs>, ITransientDependency
{
private readonly ICurrentTenant _currentTenant;
private readonly IOrderManager _orderManager;
private readonly IOrderRepository _orderRepository;
private readonly IDistributedEventBus _distributedEventBus;
public UnpaidOrderAutoCancelJob(
ICurrentTenant currentTenant,
IOrderManager orderManager,
IOrderRepository orderRepository,
IDistributedEventBus distributedEventBus)
{
_currentTenant = currentTenant;
_orderManager = orderManager;
_orderRepository = orderRepository;
_distributedEventBus = distributedEventBus;
}
[UnitOfWork]
public override async Task ExecuteAsync(UnpaidOrderAutoCancelArgs args)
{
using var changeTenant = _currentTenant.Change(args.TenantId);
var order = await _orderRepository.FindAsync(args.OrderId);
if (order is not {CanceledTime: null} || order.IsPaid())
{
return;
}
if (order.IsInPayment())
{
// Cancel the payment and cancel the order in EasyAbp.EShop.Orders.Orders.PaymentCanceledEventHandler
await _distributedEventBus.PublishAsync(new CancelPaymentEto(args.TenantId, order.PaymentId!.Value));
}
else
{
await _orderManager.CancelAsync(order, OrdersConsts.CancellationReason);
}
}
}
}

222
modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/OrderAppServiceTests.cs

@ -9,16 +9,19 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using NSubstitute;
using Shouldly;
using Volo.Abp.Timing;
using Xunit;
namespace EasyAbp.EShop.Orders.Orders
{
public class OrderAppServiceTests : OrdersApplicationTestBase
{
private readonly IClock _clock;
private readonly IOrderAppService _orderAppService;
public OrderAppServiceTests()
{
_clock = GetRequiredService<IClock>();
_orderAppService = GetRequiredService<IOrderAppService>();
}
@ -226,5 +229,224 @@ namespace EasyAbp.EShop.Orders.Orders
order.CancellationReason.ShouldBe("Repeat orders.");
});
}
[Fact]
public async Task Unpaid_Order_Should_Be_Auto_Canceled_When_Payment_Is_Canceled()
{
// Arrange
await Order_Should_Be_Created();
Guid orderId = Guid.Empty;
UsingDbContext(db =>
{
var order = db.Orders.First();
orderId = order.Id;
order.SetPaymentId(Guid.NewGuid());
db.SaveChanges();
});
// Act
var now = _clock.Now;
UsingDbContext(async db =>
{
var orderRepository = ServiceProvider.GetRequiredService<IOrderRepository>();
var order = await orderRepository.GetAsync(orderId);
order.SetPaymentExpiration(now);
order.SetPaymentId(null);
await orderRepository.UpdateAsync(order, true);
});
var response = await _orderAppService.GetAsync(orderId);
// Assert
response.ShouldNotBeNull();
response.PaymentId.ShouldBeNull();
response.PaymentExpiration.ShouldBe(now);
response.OrderStatus.ShouldBe(OrderStatus.Canceled);
response.CanceledTime.ShouldNotBeNull();
response.CancellationReason.ShouldBe(OrdersConsts.CancellationReason);
UsingDbContext(db =>
{
var order = db.Orders.FirstOrDefault(o => o.Id == orderId);
order.ShouldNotBeNull();
order.PaymentId.ShouldBeNull();
order.PaymentExpiration.ShouldBe(now);
order.OrderStatus.ShouldBe(OrderStatus.Canceled);
order.CanceledTime.ShouldNotBeNull();
order.CancellationReason.ShouldBe(OrdersConsts.CancellationReason);
});
}
[Fact]
public async Task Paid_Order_Should_Not_Be_Auto_Canceled_When_Payment_Overtime()
{
// Arrange
await Order_Should_Be_Created();
Guid orderId = Guid.Empty;
UsingDbContext(db =>
{
var order = db.Orders.First();
orderId = order.Id;
order.SetPaymentId(Guid.NewGuid());
order.SetPaidTime(_clock.Now);
order.SetOrderStatus(OrderStatus.Processing);
db.SaveChanges();
});
// Act
var now = _clock.Now;
UsingDbContext(async db =>
{
var orderRepository = ServiceProvider.GetRequiredService<IOrderRepository>();
var order = await orderRepository.GetAsync(orderId);
order.SetPaymentExpiration(now);
await orderRepository.UpdateAsync(order, true);
});
UsingDbContext(async db =>
{
var backgroundJob = ServiceProvider.GetRequiredService<UnpaidOrderAutoCancelJob>();
await backgroundJob.ExecuteAsync(new UnpaidOrderAutoCancelArgs
{
TenantId = null,
OrderId = orderId
});
});
var response = await _orderAppService.GetAsync(orderId);
// Assert
response.ShouldNotBeNull();
response.PaymentId.ShouldNotBeNull();
response.PaymentExpiration.ShouldBe(now);
response.OrderStatus.ShouldBe(OrderStatus.Processing);
response.CanceledTime.ShouldBeNull();
response.CancellationReason.ShouldBeNull();
UsingDbContext(db =>
{
var order = db.Orders.FirstOrDefault(o => o.Id == orderId);
order.ShouldNotBeNull();
order.PaymentId.ShouldNotBeNull();
order.PaymentExpiration.ShouldBe(now);
order.OrderStatus.ShouldBe(OrderStatus.Processing);
order.CanceledTime.ShouldBeNull();
order.CancellationReason.ShouldBeNull();
});
}
[Fact]
public async Task Unpaid_Order_Should_Be_Auto_Canceled_When_Payment_Overtime()
{
// Arrange
await Order_Should_Be_Created();
Guid orderId = Guid.Empty;
UsingDbContext(db =>
{
var order = db.Orders.First();
orderId = order.Id;
});
// Act
var now = _clock.Now;
UsingDbContext(async db =>
{
var orderRepository = ServiceProvider.GetRequiredService<IOrderRepository>();
var order = await orderRepository.GetAsync(orderId);
order.SetPaymentExpiration(now);
await orderRepository.UpdateAsync(order, true);
});
UsingDbContext(async db =>
{
var backgroundJob = ServiceProvider.GetRequiredService<UnpaidOrderAutoCancelJob>();
await backgroundJob.ExecuteAsync(new UnpaidOrderAutoCancelArgs
{
TenantId = null,
OrderId = orderId
});
});
var response = await _orderAppService.GetAsync(orderId);
// Assert
response.ShouldNotBeNull();
response.PaymentId.ShouldBeNull();
response.PaymentExpiration.ShouldBe(now);
response.OrderStatus.ShouldBe(OrderStatus.Canceled);
response.CanceledTime.ShouldNotBeNull();
response.CancellationReason.ShouldBe(OrdersConsts.CancellationReason);
UsingDbContext(db =>
{
var order = db.Orders.FirstOrDefault(o => o.Id == orderId);
order.ShouldNotBeNull();
order.PaymentId.ShouldBeNull();
order.PaymentExpiration.ShouldBe(now);
order.OrderStatus.ShouldBe(OrderStatus.Canceled);
order.CanceledTime.ShouldNotBeNull();
order.CancellationReason.ShouldBe(OrdersConsts.CancellationReason);
});
}
[Fact]
public async Task Payment_Pending_Order_Should_Be_Auto_Canceled_When_Payment_Overtime()
{
// Arrange
await Order_Should_Be_Created();
Guid orderId = Guid.Empty;
UsingDbContext(db =>
{
var order = db.Orders.First();
orderId = order.Id;
order.SetPaymentId(Guid.NewGuid());
db.SaveChanges();
});
// Act
var now = _clock.Now;
UsingDbContext(async db =>
{
var orderRepository = ServiceProvider.GetRequiredService<IOrderRepository>();
var order = await orderRepository.GetAsync(orderId);
order.SetPaymentExpiration(now);
await orderRepository.UpdateAsync(order, true);
});
UsingDbContext(async db =>
{
var backgroundJob = ServiceProvider.GetRequiredService<UnpaidOrderAutoCancelJob>();
await backgroundJob.ExecuteAsync(new UnpaidOrderAutoCancelArgs
{
TenantId = null,
OrderId = orderId
});
});
var response = await _orderAppService.GetAsync(orderId);
// Assert
response.ShouldNotBeNull();
response.PaymentId.ShouldBeNull();
response.PaymentExpiration.ShouldBe(now);
response.OrderStatus.ShouldBe(OrderStatus.Canceled);
response.CanceledTime.ShouldNotBeNull();
response.CancellationReason.ShouldBe(OrdersConsts.CancellationReason);
UsingDbContext(db =>
{
var order = db.Orders.FirstOrDefault(o => o.Id == orderId);
order.ShouldNotBeNull();
order.PaymentId.ShouldBeNull();
order.PaymentExpiration.ShouldBe(now);
order.OrderStatus.ShouldBe(OrderStatus.Canceled);
order.CanceledTime.ShouldNotBeNull();
order.CancellationReason.ShouldBe(OrdersConsts.CancellationReason);
});
}
}
}

46
modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/CancelPaymentEventHandler.cs

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using EasyAbp.EShop.Payments;
using EasyAbp.EShop.Payments.Payments;
using EasyAbp.PaymentService.Payments;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Uow;
namespace EasyAbp.EShop.Orders.Orders
{
[UnitOfWork]
public class CancelPaymentEventHandler : IDistributedEventHandler<CancelPaymentEto>, ITransientDependency
{
private readonly IOrderRepository _orderRepository;
private readonly IDistributedEventBus _distributedEventBus;
public CancelPaymentEventHandler(
IOrderRepository orderRepository,
IDistributedEventBus distributedEventBus)
{
_orderRepository = orderRepository;
_distributedEventBus = distributedEventBus;
}
public async Task HandleEventAsync(CancelPaymentEto eventData)
{
await _distributedEventBus.PublishAsync(new EShopPaymentCanceledEto(new EShopPaymentEto
{
TenantId = eventData.TenantId,
Id = eventData.PaymentId,
PaymentItems = new List<EShopPaymentItemEto>
{
new()
{
Id = Guid.NewGuid(),
ItemType = PaymentsConsts.PaymentItemType,
ItemKey = (await _orderRepository.FirstAsync()).Id.ToString()
}
}
}));
}
}
}

14
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/CreateUpdateProductDto.cs

@ -43,10 +43,24 @@ namespace EasyAbp.EShop.Products.Products.Dtos
[DisplayName("ProductIsPublished")]
public bool IsPublished { get; set; }
[DisplayName("ProductIsHidden")]
public bool IsHidden { get; set; }
[DisplayName("ProductPaymentExpireIn")]
public TimeSpan? PaymentExpireIn { get; set; }
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
base.Validate(validationContext);
if (PaymentExpireIn.HasValue && PaymentExpireIn.Value < TimeSpan.Zero)
{
yield return new ValidationResult(
"PaymentExpireIn should be greater than or equal to 0.",
new[] { "PaymentExpireIn" }
);
}
if (ProductAttributes.Select(a => a.DisplayName.Trim()).Distinct().Count() != ProductAttributes.Count)
{

9
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductDto.cs

@ -32,6 +32,8 @@ namespace EasyAbp.EShop.Products.Products.Dtos
public bool IsHidden { get; set; }
public TimeSpan? PaymentExpireIn { get; set; }
public long Sold { get; set; }
public decimal? MinimumPrice { get; set; }
@ -51,5 +53,12 @@ namespace EasyAbp.EShop.Products.Products.Dtos
{
return ProductSkus.FirstOrDefault(x => x.Id == skuId);
}
public TimeSpan? GetSkuPaymentExpireIn(Guid skuId)
{
var sku = GetSkuById(skuId);
return sku.PaymentExpireIn ?? PaymentExpireIn;
}
}
}

2
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductSkuDto.cs

@ -28,6 +28,8 @@ namespace EasyAbp.EShop.Products.Products.Dtos
public int OrderMaxQuantity { get; set; }
public TimeSpan? PaymentExpireIn { get; set; }
public string MediaResources { get; set; }
public Guid? ProductDetailId { get; set; }

25
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/UpdateProductSkuDto.cs

@ -9,6 +9,11 @@ namespace EasyAbp.EShop.Products.Products.Dtos
[Serializable]
public class UpdateProductSkuDto : ExtensibleObject
{
public const double MinimumPrice = 0f;
public const double MaximumPrice = 999999999999.99999999f;
public const int MinimumQuantity = 1;
public const int MaximumQuantity = int.MaxValue;
[DisplayName("ProductSkuName")]
public string Name { get; set; }
@ -17,23 +22,43 @@ namespace EasyAbp.EShop.Products.Products.Dtos
public string Currency { get; set; }
[DisplayName("ProductSkuOriginalPrice")]
[Range(MinimumPrice, MaximumPrice)]
public decimal? OriginalPrice { get; set; }
[DisplayName("ProductSkuPrice")]
[Range(MinimumPrice, MaximumPrice)]
public decimal Price { get; set; }
[DefaultValue(1)]
[DisplayName("ProductSkuOrderMinQuantity")]
[Range(MinimumQuantity, MaximumQuantity)]
public int OrderMinQuantity { get; set; }
[DefaultValue(99)]
[DisplayName("ProductSkuOrderMaxQuantity")]
[Range(MinimumQuantity, MaximumQuantity)]
public int OrderMaxQuantity { get; set; }
[DisplayName("ProductSkuPaymentExpireIn")]
public TimeSpan? PaymentExpireIn { get; set; }
[DisplayName("ProductSkuMediaResources")]
public string MediaResources { get; set; }
[DisplayName("ProductSkuProductDetailId")]
public Guid? ProductDetailId { get; set; }
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
base.Validate(validationContext);
if (PaymentExpireIn.HasValue && PaymentExpireIn.Value < TimeSpan.Zero)
{
yield return new ValidationResult(
"PaymentExpireIn should be greater than or equal to 0.",
new[] { "PaymentExpireIn" }
);
}
}
}
}

4
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/cs.json

@ -33,6 +33,7 @@
"ProductSkuSold": "Sold",
"ProductSkuOrderMinQuantity": "Minimum quantity per purchase",
"ProductSkuOrderMaxQuantity": "Maximum quantity per purchase",
"ProductSkuPaymentExpireIn": "Payment expire in",
"ProductSkuMediaResources": "Media resources",
"ProductSkuProductDetailId": "Product detail ID",
"ProductSkuContentDescription": "SKU content",
@ -44,6 +45,9 @@
"InventoryStrategy.ReduceAfterPlacing": "Reduce inventory after placing",
"InventoryStrategy.ReduceAfterPayment": "Reduce inventory after payment",
"ProductIsPublished": "Published",
"ProductIsStatic": "Static",
"ProductIsHidden": "Hidden",
"ProductPaymentExpireIn": "Payment expire in",
"ProductMediaResources": "Media resources",
"ProductAttributeDisplayName": "Display name",
"ProductAttributeDescription": "Description",

4
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/en.json

@ -34,6 +34,7 @@
"ProductSkuSold": "Sold",
"ProductSkuOrderMinQuantity": "Minimum quantity per purchase",
"ProductSkuOrderMaxQuantity": "Maximum quantity per purchase",
"ProductSkuPaymentExpireIn": "Payment expire in",
"ProductSkuMediaResources": "Media resources",
"ProductSkuProductDetailId": "Product detail ID",
"ProductSkuContentDescription": "SKU content",
@ -45,6 +46,9 @@
"InventoryStrategy.ReduceAfterPlacing": "Reduce inventory after placing",
"InventoryStrategy.ReduceAfterPayment": "Reduce inventory after payment",
"ProductIsPublished": "Published",
"ProductIsStatic": "Static",
"ProductIsHidden": "Hidden",
"ProductPaymentExpireIn": "Payment expire in",
"ProductMediaResources": "Media resources",
"ProductAttributeDisplayName": "Display name",
"ProductAttributeDescription": "Description",

4
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pl.json

@ -33,6 +33,7 @@
"ProductSkuSold": "Sold",
"ProductSkuOrderMinQuantity": "Minimum quantity per purchase",
"ProductSkuOrderMaxQuantity": "Maximum quantity per purchase",
"ProductSkuPaymentExpireIn": "Payment expire in",
"ProductSkuMediaResources": "Media resources",
"ProductSkuProductDetailId": "Product detail ID",
"ProductSkuContentDescription": "SKU content",
@ -44,6 +45,9 @@
"InventoryStrategy.ReduceAfterPlacing": "Reduce inventory after placing",
"InventoryStrategy.ReduceAfterPayment": "Reduce inventory after payment",
"ProductIsPublished": "Published",
"ProductIsStatic": "Static",
"ProductIsHidden": "Hidden",
"ProductPaymentExpireIn": "Payment expire in",
"ProductMediaResources": "Media resources",
"ProductAttributeDisplayName": "Display name",
"ProductAttributeDescription": "Description",

4
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pt-BR.json

@ -33,6 +33,7 @@
"ProductSkuSold": "Sold",
"ProductSkuOrderMinQuantity": "Minimum quantity per purchase",
"ProductSkuOrderMaxQuantity": "Maximum quantity per purchase",
"ProductSkuPaymentExpireIn": "Payment expire in",
"ProductSkuMediaResources": "Media resources",
"ProductSkuProductDetailId": "Product detail ID",
"ProductSkuContentDescription": "SKU content",
@ -44,6 +45,9 @@
"InventoryStrategy.ReduceAfterPlacing": "Reduce inventory after placing",
"InventoryStrategy.ReduceAfterPayment": "Reduce inventory after payment",
"ProductIsPublished": "Published",
"ProductIsStatic": "Static",
"ProductIsHidden": "Hidden",
"ProductPaymentExpireIn": "Payment expire in",
"ProductMediaResources": "Media resources",
"ProductAttributeDisplayName": "Display name",
"ProductAttributeDescription": "Description",

4
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/sl.json

@ -34,6 +34,7 @@
"ProductSkuSold": "Sold",
"ProductSkuOrderMinQuantity": "Minimum quantity per purchase",
"ProductSkuOrderMaxQuantity": "Maximum quantity per purchase",
"ProductSkuPaymentExpireIn": "Payment expire in",
"ProductSkuMediaResources": "Media resources",
"ProductSkuProductDetailId": "Product detail ID",
"ProductSkuContentDescription": "SKU content",
@ -45,6 +46,9 @@
"InventoryStrategy.ReduceAfterPlacing": "Reduce inventory after placing",
"InventoryStrategy.ReduceAfterPayment": "Reduce inventory after payment",
"ProductIsPublished": "Published",
"ProductIsStatic": "Static",
"ProductIsHidden": "Hidden",
"ProductPaymentExpireIn": "Payment expire in",
"ProductMediaResources": "Media resources",
"ProductAttributeDisplayName": "Display name",
"ProductAttributeDescription": "Description",

4
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/tr.json

@ -34,6 +34,7 @@
"ProductSkuSold": "Sold",
"ProductSkuOrderMinQuantity": "Minimum quantity per purchase",
"ProductSkuOrderMaxQuantity": "Maximum quantity per purchase",
"ProductSkuPaymentExpireIn": "Payment expire in",
"ProductSkuMediaResources": "Media resources",
"ProductSkuProductDetailId": "Product detail ID",
"ProductSkuContentDescription": "SKU content",
@ -45,6 +46,9 @@
"InventoryStrategy.ReduceAfterPlacing": "Reduce inventory after placing",
"InventoryStrategy.ReduceAfterPayment": "Reduce inventory after payment",
"ProductIsPublished": "Published",
"ProductIsStatic": "Static",
"ProductIsHidden": "Hidden",
"ProductPaymentExpireIn": "Payment expire in",
"ProductMediaResources": "Media resources",
"ProductAttributeDisplayName": "Display name",
"ProductAttributeDescription": "Description",

4
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/vi.json

@ -33,6 +33,7 @@
"ProductSkuSold": "Sold",
"ProductSkuOrderMinQuantity": "Minimum quantity per purchase",
"ProductSkuOrderMaxQuantity": "Maximum quantity per purchase",
"ProductSkuPaymentExpireIn": "Payment expire in",
"ProductSkuMediaResources": "Media resources",
"ProductSkuProductDetailId": "Product detail ID",
"ProductSkuContentDescription": "SKU content",
@ -44,6 +45,9 @@
"InventoryStrategy.ReduceAfterPlacing": "Reduce inventory after placing",
"InventoryStrategy.ReduceAfterPayment": "Reduce inventory after payment",
"ProductIsPublished": "Published",
"ProductIsStatic": "Static",
"ProductIsHidden": "Hidden",
"ProductPaymentExpireIn": "Payment expire in",
"ProductMediaResources": "Media resources",
"ProductAttributeDisplayName": "Display name",
"ProductAttributeDescription": "Description",

4
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hans.json

@ -34,6 +34,7 @@
"ProductSkuSold": "已售",
"ProductSkuOrderMinQuantity": "单次最小购买数量",
"ProductSkuOrderMaxQuantity": "单次最大购买数量",
"ProductSkuPaymentExpireIn": "支付限时",
"ProductSkuMediaResources": "多媒体资源",
"ProductSkuProductDetailId": "商品详情 ID",
"ProductSkuContentDescription": "SKU 描述",
@ -45,6 +46,9 @@
"InventoryStrategy.ReduceAfterPlacing": "下单后减库存",
"InventoryStrategy.ReduceAfterPayment": "支付后减库存",
"ProductIsPublished": "是否发布",
"ProductIsStatic": "是否静态",
"ProductIsHidden": "是否隐藏",
"ProductPaymentExpireIn": "支付限时",
"ProductMediaResources": "多媒体资源",
"ProductAttributeDisplayName": "名称",
"ProductAttributeDescription": "描述",

8
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hant.json

@ -34,6 +34,7 @@
"ProductSkuSold": "已售",
"ProductSkuOrderMinQuantity": "單次最小購買數量",
"ProductSkuOrderMaxQuantity": "單次最大購買數量",
"ProductSkuPaymentExpireIn": "支付限時",
"ProductSkuMediaResources": "多媒體資源",
"ProductSkuProductDetailId": "商品詳情 ID",
"ProductSkuContentDescription": "SKU 描述",
@ -42,9 +43,12 @@
"ProductSkuDeletionConfirmationMessage": "確認刪除 SKU {0}?",
"ProductInventoryStrategy": "庫存扣減策略",
"InventoryStrategy.NoNeed": "No need",
"InventoryStrategy.ReduceAfterPlacing": "Reduce inventory after placing",
"InventoryStrategy.ReduceAfterPayment": "Reduce inventory after payment",
"InventoryStrategy.ReduceAfterPlacing": "下單後減庫存",
"InventoryStrategy.ReduceAfterPayment": "支付後減庫存",
"ProductIsPublished": "是否發布",
"ProductIsStatic": "是否靜態",
"ProductIsHidden": "是否隱藏",
"ProductPaymentExpireIn": "支付限時",
"ProductMediaResources": "多媒體資源",
"ProductAttributeDisplayName": "名稱",
"ProductAttributeDescription": "描述",

2
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/ProductsConsts.cs

@ -13,5 +13,7 @@
public const string GetCategorySummaryListedDataSourceUrl = CategoryRouteBase + "/summary";
public const string GetCategorySummarySingleDataSourceUrl = CategoryRouteBase + "/{id}";
public const string DefaultPaymentExpireInSettingName = "EasyAbp.EShop.Products.Product.DefaultPaymentExpireIn";
}
}

4
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/Product.cs

@ -36,6 +36,8 @@ namespace EasyAbp.EShop.Products.Products
public virtual bool IsHidden { get; protected set; }
public virtual TimeSpan? PaymentExpireIn { get; protected set; }
public virtual List<ProductAttribute> ProductAttributes { get; protected set; }
public virtual List<ProductSku> ProductSkus { get; protected set; }
@ -56,6 +58,7 @@ namespace EasyAbp.EShop.Products.Products
bool isPublished,
bool isStatic,
bool isHidden,
TimeSpan? paymentExpireIn,
[CanBeNull] string mediaResources,
int displayOrder
) : base(id)
@ -70,6 +73,7 @@ namespace EasyAbp.EShop.Products.Products
IsPublished = isPublished;
IsStatic = isStatic;
IsHidden = isHidden;
PaymentExpireIn = paymentExpireIn;
MediaResources = mediaResources;
DisplayOrder = displayOrder;

4
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductSku.cs

@ -25,6 +25,8 @@ namespace EasyAbp.EShop.Products.Products
public virtual int OrderMaxQuantity { get; protected set; }
public virtual TimeSpan? PaymentExpireIn { get; protected set; }
[CanBeNull]
public virtual string MediaResources { get; protected set; }
@ -47,6 +49,7 @@ namespace EasyAbp.EShop.Products.Products
decimal price,
int orderMinQuantity,
int orderMaxQuantity,
TimeSpan? paymentExpireIn,
[CanBeNull] string mediaResources,
Guid? productDetailId) : base(id)
{
@ -57,6 +60,7 @@ namespace EasyAbp.EShop.Products.Products
Price = price;
OrderMinQuantity = orderMinQuantity;
OrderMaxQuantity = orderMaxQuantity;
PaymentExpireIn = paymentExpireIn;
MediaResources = mediaResources;
ProductDetailId = productDetailId;

15
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Settings/ProductsSettingDefinitionProvider.cs

@ -1,4 +1,5 @@
using Volo.Abp.Settings;
using System;
using Volo.Abp.Settings;
namespace EasyAbp.EShop.Products.Settings
{
@ -10,7 +11,17 @@ namespace EasyAbp.EShop.Products.Settings
* Use names from ProductsSettings class.
*/
context.Add(new SettingDefinition(ProductsSettings.ProductView.CacheDurationSeconds, "60"));
context.Add(
new SettingDefinition(
ProductsSettings.ProductView.CacheDurationSeconds,
"60"
),
new SettingDefinition(
ProductsSettings.Product.DefaultPaymentExpireIn,
TimeSpan.FromMinutes(15).ToString(),
isVisibleToClients: true
)
);
}
}
}

9
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Settings/ProductsSettings.cs

@ -8,11 +8,18 @@
* public const string MySettingName = GroupName + ".MySettingName";
*/
public class ProductView
public static class ProductView
{
private const string ProductViewGroupName = GroupName + ".GroupName";
public const string CacheDurationSeconds = ProductViewGroupName + ".CacheDuration";
}
public static class Product
{
private const string ProductGroupName = GroupName + ".Product";
public const string DefaultPaymentExpireIn = ProductGroupName + ".DefaultPaymentExpireIn";
}
}
}

7
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/CreateModal.cshtml.cs

@ -12,6 +12,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Settings;
namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.Product
{
@ -62,6 +63,12 @@ namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.Product
{
Product.CategoryIds = new List<Guid>(new[] { categoryId.Value });
}
var paymentExpireInString =
await SettingProvider.GetOrNullAsync(ProductsConsts.DefaultPaymentExpireInSettingName);
Product.PaymentExpireIn =
!paymentExpireInString.IsNullOrEmpty() ? TimeSpan.Parse(paymentExpireInString) : null;
}
public virtual async Task<IActionResult> OnPostAsync()

6
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/ViewModels/CreateEditProductViewModel.cs

@ -59,6 +59,12 @@ namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.Product.ViewM
[Display(Name = "ProductIsPublished")]
public bool IsPublished { get; set; }
[Display(Name = "ProductIsHidden")]
public bool IsHidden { get; set; }
[Display(Name = "ProductPaymentExpireIn")]
public TimeSpan? PaymentExpireIn { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{

1
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/index.js

@ -56,6 +56,7 @@ $(function () {
{ data: "inventoryStrategy" },
{ data: "sold" },
{ data: "isPublished" },
{ data: "isHidden" },
]
}));

8
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/ViewModels/EditProductSkuViewModel.cs

@ -1,6 +1,7 @@
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using EasyAbp.EShop.Products.Products.Dtos;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form;
@ -17,21 +18,28 @@ namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku.Vi
[Required]
[Display(Name = "ProductSkuPrice")]
[Range(UpdateProductSkuDto.MinimumPrice, UpdateProductSkuDto.MaximumPrice)]
public decimal Price { get; set; }
[Display(Name = "ProductSkuOriginalPrice")]
[Range(UpdateProductSkuDto.MinimumPrice, UpdateProductSkuDto.MaximumPrice)]
public decimal? OriginalPrice { get; set; }
[Required]
[DefaultValue(1)]
[Display(Name = "ProductSkuOrderMinQuantity")]
[Range(UpdateProductSkuDto.MinimumQuantity, UpdateProductSkuDto.MaximumQuantity)]
public int OrderMinQuantity { get; set; }
[Required]
[DefaultValue(99)]
[Display(Name = "ProductSkuOrderMaxQuantity")]
[Range(UpdateProductSkuDto.MinimumQuantity, UpdateProductSkuDto.MaximumQuantity)]
public int OrderMaxQuantity { get; set; }
[DisplayName("ProductSkuPaymentExpireIn")]
public TimeSpan? PaymentExpireIn { get; set; }
[Display(Name = "ProductSkuMediaResources")]
public string MediaResources { get; set; }

5107
samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20211012091401_AddedPaymentExpireInAndPaymentExpireIn.Designer.cs

File diff suppressed because it is too large

44
samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20211012091401_AddedPaymentExpireInAndPaymentExpireIn.cs

@ -0,0 +1,44 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace EShopSample.Migrations
{
public partial class AddedPaymentExpireInAndPaymentExpireIn : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<TimeSpan>(
name: "PaymentExpireIn",
table: "EasyAbpEShopProductsProductSkus",
type: "time",
nullable: true);
migrationBuilder.AddColumn<TimeSpan>(
name: "PaymentExpireIn",
table: "EasyAbpEShopProductsProducts",
type: "time",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "PaymentExpiration",
table: "EasyAbpEShopOrdersOrders",
type: "datetime2",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "PaymentExpireIn",
table: "EasyAbpEShopProductsProductSkus");
migrationBuilder.DropColumn(
name: "PaymentExpireIn",
table: "EasyAbpEShopProductsProducts");
migrationBuilder.DropColumn(
name: "PaymentExpiration",
table: "EasyAbpEShopOrdersOrders");
}
}
}

11
samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/EShopSampleDbContextModelSnapshot.cs

@ -18,7 +18,7 @@ namespace EShopSample.Migrations
modelBuilder
.HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer)
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("ProductVersion", "5.0.9")
.HasAnnotation("ProductVersion", "5.0.10")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("EasyAbp.EShop.Orders.Orders.Order", b =>
@ -97,6 +97,9 @@ namespace EShopSample.Migrations
b.Property<DateTime?>("PaidTime")
.HasColumnType("datetime2");
b.Property<DateTime?>("PaymentExpiration")
.HasColumnType("datetime2");
b.Property<Guid?>("PaymentId")
.HasColumnType("uniqueidentifier");
@ -1389,6 +1392,9 @@ namespace EShopSample.Migrations
b.Property<string>("MediaResources")
.HasColumnType("nvarchar(max)");
b.Property<TimeSpan?>("PaymentExpireIn")
.HasColumnType("time");
b.Property<Guid>("ProductDetailId")
.HasColumnType("uniqueidentifier");
@ -1588,6 +1594,9 @@ namespace EShopSample.Migrations
b.Property<decimal?>("OriginalPrice")
.HasColumnType("decimal(20,8)");
b.Property<TimeSpan?>("PaymentExpireIn")
.HasColumnType("time");
b.Property<decimal>("Price")
.HasColumnType("decimal(20,8)");

Loading…
Cancel
Save