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; } }