using Fake.Application;
using Microsoft.AspNetCore.Mvc;
using Oms.Application.Dtos.Marketing;
using Oms.Application.Dtos.Orders;
using Oms.Application.Shared;
using Oms.Domain.OrdersAggregate.Entities;
namespace Oms.Application.Services.Orders;
public partial class OrderService
{
private const string CouponNotBetterThanLevelReason = "优惠不高于会员折扣";
///
/// 支付预览:按当前应收计算会员折/券互斥后的应付,并返回本单可选券。
///
/// 入参:订单 Id;couponInstanceId 为 null 自动推荐,0 不用券,大于 0 试算指定券。
/// 应付拆解、实际生效券与可选券列表;指定券不可用(未解锁/过期/线路不符等)时抛业务异常。
[HttpGet("/order/pay-preview")]
public async Task GetPayPreviewAsync([FromQuery] OrderPayPreviewInput input)
{
var order = await GetOrderOrThrowAsync(input.Id);
var fees = await OrderRepository.GetFeesByOrderIdsAsync([order.Id]);
var receivableSubtotal = MarketingFeeHelper.SumReceivable(fees);
var baseFreightAmount = MarketingFeeHelper.SumReceivableBaseFreight(fees);
var marketingHelper = LazyServiceProvider.GetRequiredService();
var memberOnly = await marketingHelper.BuildBreakdownAsync(
order.CustomerId,
receivableSubtotal,
baseFreightAmount,
null,
order.ConsigneeCountry,
order.Id);
var coupons = await marketingHelper.BuildAvailableCouponsAsync(
order.CustomerId,
baseFreightAmount,
order.ConsigneeCountry,
order.Id);
foreach (var coupon in coupons.Where(x => x.CanUse
&& (x.EstimatedDiscount ?? 0M) <= memberOnly.LevelDiscountAmount))
{
coupon.CanUse = false;
coupon.UnavailableReason = CouponNotBetterThanLevelReason;
}
coupons = coupons
.OrderByDescending(x => x.CanUse)
.ThenByDescending(x => x.EstimatedDiscount ?? 0M)
.ThenBy(x => x.ValidEndTime)
.ToList();
var targetCouponId = input.CouponInstanceId switch
{
0 => null,
> 0 => input.CouponInstanceId,
_ => ResolveRecommendedPayCouponId(order, coupons)
};
var breakdown = targetCouponId.HasValue
? await marketingHelper.BuildBreakdownAsync(
order.CustomerId,
receivableSubtotal,
baseFreightAmount,
targetCouponId,
order.ConsigneeCountry,
order.Id)
: memberOnly;
return new OrderPayPreviewOutput
{
Id = order.Id,
ReceivableSubtotal = breakdown.ReceivableSubtotal,
BaseFreightAmount = breakdown.BaseFreightAmount,
LevelDiscountAmount = breakdown.LevelDiscountAmount,
CouponDiscountAmount = breakdown.CouponDiscountAmount,
PayableAmount = breakdown.PayableAmount,
MemberLevelName = breakdown.MemberLevelName,
AppliedCouponInstanceId = breakdown.CouponInstanceId,
AvailableCoupons = coupons
};
}
///
/// 计算支付应付:couponInstanceId 为 null 沿用订单已绑定券(不可用则不用券),0 不用券,大于 0 使用指定券。
///
private async Task BuildPayBreakdownAsync(
Order order,
IEnumerable fees,
int? couponInstanceId)
{
var feeList = fees as IList ?? fees.ToList();
var receivableSubtotal = MarketingFeeHelper.SumReceivable(feeList);
var baseFreightAmount = MarketingFeeHelper.SumReceivableBaseFreight(feeList);
var marketingHelper = LazyServiceProvider.GetRequiredService();
Task Build(int? couponId) =>
marketingHelper.BuildBreakdownAsync(
order.CustomerId,
receivableSubtotal,
baseFreightAmount,
couponId,
order.ConsigneeCountry,
order.Id);
if (couponInstanceId is > 0)
{
var breakdown = await Build(couponInstanceId);
if (breakdown.CouponInstanceId != couponInstanceId)
{
throw new BusinessException("所选优惠券未达使用门槛或优惠不高于会员折扣,请重新选择");
}
return breakdown;
}
if (couponInstanceId == 0 || order.CouponInstanceId is not > 0)
{
return await Build(null);
}
try
{
return await Build(order.CouponInstanceId);
}
catch (BusinessException)
{
return await Build(null);
}
}
private static int? ResolveRecommendedPayCouponId(Order order, List coupons)
{
if (order.CouponInstanceId is > 0
&& coupons.Any(x => x.CanUse && x.CouponInstanceId == order.CouponInstanceId.Value))
{
return order.CouponInstanceId;
}
return coupons.FirstOrDefault(x => x.CanUse)?.CouponInstanceId;
}
}