All checks were successful
Build and Deploy / deploy (push) Successful in 3m50s
Co-authored-by: Cursor <cursoragent@cursor.com>
142 lines
3.6 KiB
C#
142 lines
3.6 KiB
C#
namespace Oms.Application.Shared;
|
|
|
|
public class InviteRewardRuleSnapshot
|
|
{
|
|
public decimal MinPaidBaseFreight { get; set; } = 50M;
|
|
|
|
public decimal FirstOrderRate { get; set; } = 0.05M;
|
|
|
|
public decimal FirstOrderCap { get; set; } = 50M;
|
|
|
|
public decimal FollowOrderRate { get; set; } = 0.03M;
|
|
|
|
public decimal FollowOrderCap { get; set; } = 30M;
|
|
|
|
public int FollowWindowDays { get; set; } = 90;
|
|
|
|
public int MaxRewardOrders { get; set; } = 6;
|
|
|
|
public int SettleBusinessDays { get; set; } = 3;
|
|
|
|
public string MinInviteLevelCode { get; set; } = "V1";
|
|
}
|
|
|
|
public class InviteRewardDecision
|
|
{
|
|
public bool Eligible { get; set; }
|
|
|
|
public int OrderIndex { get; set; }
|
|
|
|
public decimal Rate { get; set; }
|
|
|
|
public decimal Cap { get; set; }
|
|
|
|
public decimal Amount { get; set; }
|
|
}
|
|
|
|
public static class MarketingInviteRewardCalculator
|
|
{
|
|
public static InviteRewardDecision Resolve(
|
|
decimal paidBaseFreight,
|
|
int completedOrderIndex,
|
|
DateTime refereeRegisterTime,
|
|
DateTime completeTime,
|
|
InviteRewardRuleSnapshot rule)
|
|
{
|
|
var decision = new InviteRewardDecision { OrderIndex = completedOrderIndex };
|
|
if (completedOrderIndex <= 0 || paidBaseFreight < rule.MinPaidBaseFreight)
|
|
{
|
|
return decision;
|
|
}
|
|
|
|
if (completedOrderIndex == 1)
|
|
{
|
|
decision.Eligible = true;
|
|
decision.Rate = rule.FirstOrderRate;
|
|
decision.Cap = rule.FirstOrderCap;
|
|
decision.Amount = Cap(paidBaseFreight * rule.FirstOrderRate, rule.FirstOrderCap);
|
|
return decision;
|
|
}
|
|
|
|
if (completedOrderIndex > rule.MaxRewardOrders)
|
|
{
|
|
return decision;
|
|
}
|
|
|
|
var windowEnd = refereeRegisterTime.Date.AddDays(rule.FollowWindowDays);
|
|
if (completeTime.Date > windowEnd)
|
|
{
|
|
return decision;
|
|
}
|
|
|
|
decision.Eligible = true;
|
|
decision.Rate = rule.FollowOrderRate;
|
|
decision.Cap = rule.FollowOrderCap;
|
|
decision.Amount = Cap(paidBaseFreight * rule.FollowOrderRate, rule.FollowOrderCap);
|
|
return decision;
|
|
}
|
|
|
|
public static DateTime AddBusinessDays(DateTime start, int days)
|
|
{
|
|
if (days <= 0)
|
|
{
|
|
return start;
|
|
}
|
|
|
|
var cursor = start;
|
|
var added = 0;
|
|
while (added < days)
|
|
{
|
|
cursor = cursor.AddDays(1);
|
|
if (cursor.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
added++;
|
|
}
|
|
|
|
return cursor;
|
|
}
|
|
|
|
public static int ParseLevelRank(string? levelCode)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(levelCode))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
var code = levelCode.Trim();
|
|
if (code.Length >= 2 && (code[0] == 'V' || code[0] == 'v') && int.TryParse(code[1..], out var n))
|
|
{
|
|
return n;
|
|
}
|
|
|
|
return code.ToLowerInvariant() switch
|
|
{
|
|
"normal" => 0,
|
|
"silver" => 2,
|
|
"gold" => 3,
|
|
"diamond" => 4,
|
|
_ => 0
|
|
};
|
|
}
|
|
|
|
public static bool CanInvite(string? memberLevelCode, string? minInviteLevelCode)
|
|
{
|
|
return ParseLevelRank(memberLevelCode) >= ParseLevelRank(
|
|
string.IsNullOrWhiteSpace(minInviteLevelCode) ? "V1" : minInviteLevelCode);
|
|
}
|
|
|
|
private static decimal Cap(decimal amount, decimal cap)
|
|
{
|
|
var value = Math.Round(Math.Max(0M, amount), 2, MidpointRounding.AwayFromZero);
|
|
if (cap > 0)
|
|
{
|
|
value = Math.Min(value, cap);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
}
|