All checks were successful
Build and Deploy / deploy (push) Successful in 3m50s
Co-authored-by: Cursor <cursoragent@cursor.com>
201 lines
6.3 KiB
C#
201 lines
6.3 KiB
C#
using FluentAssertions;
|
|
using Oms.Application.Dtos.Marketing;
|
|
using Oms.Application.Shared;
|
|
using Oms.Domain.MarketingAggregate.Entities;
|
|
using Oms.Domain.OrdersAggregate.Consts;
|
|
using Oms.Domain.OrdersAggregate.Entities;
|
|
|
|
namespace Oms.Tests;
|
|
|
|
public class MarketingMemberPointsTests
|
|
{
|
|
[Fact]
|
|
public void MemberDiscount_ShouldApplyOnlyToBaseFreight()
|
|
{
|
|
var result = MarketingBillingCalculator.Calculate(
|
|
receivableSubtotal: 1200M,
|
|
baseFreightAmount: 1000M,
|
|
extraDiscountRate: 0.05M,
|
|
memberLevelId: 5,
|
|
memberLevelName: "皇冠会员",
|
|
priceTierKey: null,
|
|
coupon: null);
|
|
|
|
result.LevelDiscountAmount.Should().Be(50M);
|
|
result.PayableAmount.Should().Be(1150M);
|
|
result.PaidBaseFreightAmount.Should().Be(950M);
|
|
result.CouponInstanceId.Should().BeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void CouponAndMember_ShouldPickCheaperWithoutStacking()
|
|
{
|
|
var coupon = new CouponDiscountInput
|
|
{
|
|
CouponInstanceId = 9,
|
|
CouponType = MarketingCouponTypeCodes.FixedOff,
|
|
FaceValue = 80M,
|
|
MinOrderAmount = 0M
|
|
};
|
|
|
|
var result = MarketingBillingCalculator.Calculate(
|
|
1200M,
|
|
1000M,
|
|
0.05M,
|
|
5,
|
|
"皇冠会员",
|
|
null,
|
|
coupon);
|
|
|
|
result.LevelDiscountAmount.Should().Be(0M);
|
|
result.CouponDiscountAmount.Should().Be(80M);
|
|
result.PayableAmount.Should().Be(1120M);
|
|
result.CouponInstanceId.Should().Be(9);
|
|
result.PaidBaseFreightAmount.Should().Be(920M);
|
|
}
|
|
|
|
[Fact]
|
|
public void Coupon_ShouldCapAtBaseFreightAndIgnoreSurcharge()
|
|
{
|
|
var coupon = new CouponDiscountInput
|
|
{
|
|
CouponInstanceId = 1,
|
|
CouponType = MarketingCouponTypeCodes.FixedOff,
|
|
FaceValue = 200M,
|
|
MinOrderAmount = 0M
|
|
};
|
|
|
|
var result = MarketingBillingCalculator.Calculate(
|
|
150M, 100M, 0M, null, null, null, coupon);
|
|
|
|
result.CouponDiscountAmount.Should().Be(100M);
|
|
result.PayableAmount.Should().Be(50M);
|
|
result.PaidBaseFreightAmount.Should().Be(0M);
|
|
}
|
|
|
|
[Fact]
|
|
public void CouponThreshold_ShouldUseBaseFreightNotOrderTotal()
|
|
{
|
|
var coupon = new CouponDiscountInput
|
|
{
|
|
CouponInstanceId = 1,
|
|
CouponType = MarketingCouponTypeCodes.FixedOff,
|
|
FaceValue = 15M,
|
|
MinOrderAmount = 150M
|
|
};
|
|
|
|
var result = MarketingBillingCalculator.Calculate(
|
|
250M, 140M, 0M, null, null, null, coupon);
|
|
|
|
result.CouponDiscountAmount.Should().Be(0M);
|
|
result.CouponInstanceId.Should().BeNull();
|
|
result.PayableAmount.Should().Be(250M);
|
|
}
|
|
|
|
[Fact]
|
|
public void CouponScope_ShouldMatchCountryCodes()
|
|
{
|
|
MarketingCouponScope.Matches("""{"countryCodes":["MY","SG"]}""", "MY").Should().BeTrue();
|
|
MarketingCouponScope.Matches("""{"countryCodes":["MY","SG"]}""", "US").Should().BeFalse();
|
|
MarketingCouponScope.Matches(null, "MY").Should().BeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void EqualOrWorseCoupon_ShouldKeepMemberDiscount()
|
|
{
|
|
var coupon = new CouponDiscountInput
|
|
{
|
|
CouponInstanceId = 9,
|
|
CouponType = MarketingCouponTypeCodes.FixedOff,
|
|
FaceValue = 50M
|
|
};
|
|
|
|
var result = MarketingBillingCalculator.Calculate(
|
|
1200M,
|
|
1000M,
|
|
0.05M,
|
|
5,
|
|
"皇冠会员",
|
|
null,
|
|
coupon);
|
|
|
|
result.LevelDiscountAmount.Should().Be(50M);
|
|
result.CouponDiscountAmount.Should().Be(0M);
|
|
result.CouponInstanceId.Should().BeNull();
|
|
result.PayableAmount.Should().Be(1150M);
|
|
}
|
|
|
|
[Fact]
|
|
public void DiscountRate_ShouldCapAtFivePercent()
|
|
{
|
|
var result = MarketingBillingCalculator.Calculate(
|
|
1000M,
|
|
1000M,
|
|
0.2M,
|
|
1,
|
|
"测试",
|
|
null,
|
|
null);
|
|
|
|
result.LevelDiscountAmount.Should().Be(50M);
|
|
}
|
|
|
|
[Fact]
|
|
public void FuelSurcharge_ShouldNotCountAsBaseFreight()
|
|
{
|
|
var fees = new[]
|
|
{
|
|
new OrderFee { Type = OrderFeeTypeCodes.Receivable, CostItem = "freight", Amount = 100 },
|
|
new OrderFee { Type = OrderFeeTypeCodes.Receivable, CostItem = "燃油附加费", Amount = 30 },
|
|
new OrderFee { Type = OrderFeeTypeCodes.Receivable, CostItem = "偏远附加费", Amount = 20 }
|
|
};
|
|
|
|
MarketingFeeHelper.SumReceivableBaseFreight(fees).Should().Be(100M);
|
|
MarketingFeeHelper.IsBaseFreightCostItem("燃油附加费").Should().BeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void V5_ShouldStepDownOneLevelOnly()
|
|
{
|
|
var levels = new List<MemberLevel>
|
|
{
|
|
new() { Id = 1, LevelCode = "V0", MinPoints = 0, CanDowngrade = false },
|
|
new() { Id = 2, LevelCode = "V3", MinPoints = 120000, CanDowngrade = false },
|
|
new() { Id = 3, LevelCode = "V4", MinPoints = 240000, CanDowngrade = true },
|
|
new() { Id = 4, LevelCode = "V5", MinPoints = 600000, CanDowngrade = true }
|
|
};
|
|
var v5 = levels[3];
|
|
var v0Target = levels[0];
|
|
|
|
var next = MarketingPointsManager.ResolveNextLevel(levels, v5, v0Target, allowDowngrade: true);
|
|
next!.LevelCode.Should().Be("V4");
|
|
}
|
|
|
|
[Fact]
|
|
public void V3_ShouldNotDowngrade()
|
|
{
|
|
var levels = new List<MemberLevel>
|
|
{
|
|
new() { Id = 1, LevelCode = "V0", MinPoints = 0, CanDowngrade = false },
|
|
new() { Id = 2, LevelCode = "V3", MinPoints = 120000, CanDowngrade = false }
|
|
};
|
|
|
|
var next = MarketingPointsManager.ResolveNextLevel(levels, levels[1], levels[0], allowDowngrade: true);
|
|
next!.LevelCode.Should().Be("V3");
|
|
}
|
|
|
|
[Fact]
|
|
public void Upgrade_CanJumpToMatchingLevel()
|
|
{
|
|
var levels = new List<MemberLevel>
|
|
{
|
|
new() { Id = 1, LevelCode = "V0", MinPoints = 0, CanDowngrade = false },
|
|
new() { Id = 2, LevelCode = "V4", MinPoints = 240000, CanDowngrade = true },
|
|
new() { Id = 3, LevelCode = "V5", MinPoints = 600000, CanDowngrade = true }
|
|
};
|
|
|
|
var next = MarketingPointsManager.ResolveNextLevel(levels, levels[0], levels[2], allowDowngrade: false);
|
|
next!.LevelCode.Should().Be("V5");
|
|
}
|
|
}
|