diff --git a/Directory.Packages.props b/Directory.Packages.props index 596ad04..0407568 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,7 +2,6 @@ true - 3.2.1 @@ -13,14 +12,12 @@ 1.0.5 1.1.2 - - @@ -39,7 +36,6 @@ - @@ -50,13 +46,10 @@ - - - @@ -75,7 +68,6 @@ - @@ -103,7 +95,6 @@ - @@ -124,14 +115,13 @@ - + - @@ -150,8 +140,7 @@ - - + \ No newline at end of file diff --git a/src/Fengling.Member.Application/Services/PointsRuleMatcher.cs b/src/Fengling.Member.Application/Services/PointsRuleMatcher.cs new file mode 100644 index 0000000..d7de4a1 --- /dev/null +++ b/src/Fengling.Member.Application/Services/PointsRuleMatcher.cs @@ -0,0 +1,114 @@ +using Fengling.Member.Application.Dtos; +using Fengling.Member.Domain.Aggregates.PointsRuleModel; +using Fengling.Member.Domain.Aggregates.PointsRuleModel.Enums; +using Fengling.Member.Domain.Repositories; +using NetCorePal.Extensions.Domain; + +namespace Fengling.Member.Application.Services; + +public class PointsRuleMatcher +{ + private readonly IPointsRuleRepository _ruleRepository; + + public PointsRuleMatcher(IPointsRuleRepository ruleRepository) + { + _ruleRepository = ruleRepository; + } + + public async Task CalculatePointsAsync(CodeInfoDto codeInfo) + { + var result = new PointsCalculationResultDto( + Success: false, + Points: 0, + ExpireAt: DateTime.MinValue, + AppliedRuleId: null, + Message: "No matching rule found", + MatchedDimensions: new List() + ); + + var allRules = await _ruleRepository.GetActiveRulesAsync(); + + var matchedRules = new List<(PointsRule Rule, int MatchCount)>(); + + foreach (var rule in allRules) + { + var matchCount = CountMatchingConditions(rule, codeInfo); + if (matchCount > 0) + { + matchedRules.Add((rule, matchCount)); + } + } + + if (!matchedRules.Any()) + { + return result; + } + + var bestMatch = matchedRules + .OrderByDescending(x => x.MatchCount) + .ThenByDescending(x => x.Rule.Priority) + .First(); + + var matchedRule = bestMatch.Rule; + + int points = matchedRule.BasePoints; + if (matchedRule.RuleType == RuleType.PriceWeighted && + decimal.TryParse(codeInfo.ProductPrice, out var price)) + { + points = (int)(price * (matchedRule.WeightFactor ?? 1m)); + } + + var matchedDimensions = GetMatchedDimensions(matchedRule, codeInfo); + + return new PointsCalculationResultDto( + Success: true, + Points: points, + ExpireAt: DateTime.UtcNow.AddDays(matchedRule.ValidityDays), + AppliedRuleId: (matchedRule.Id as IStronglyTypedId)?.Id, + Message: "Success", + MatchedDimensions: matchedDimensions + ); + } + + private int CountMatchingConditions(PointsRule rule, CodeInfoDto codeInfo) + { + var count = 0; + foreach (var condition in rule.Conditions) + { + if (MatchesCondition(condition, codeInfo)) + { + count++; + } + } + return count; + } + + private bool MatchesCondition(PointsRuleCondition condition, CodeInfoDto codeInfo) + { + return condition.DimensionValue switch + { + "*" => true, + _ => condition.DimensionType switch + { + DimensionType.Product => condition.DimensionValue == codeInfo.ProductId, + DimensionType.Dealer => condition.DimensionValue == codeInfo.DealerId, + DimensionType.Distributor => condition.DimensionValue == codeInfo.DistributorId, + DimensionType.Store => condition.DimensionValue == codeInfo.StoreId, + _ => false + } + }; + } + + private List GetMatchedDimensions(PointsRule rule, CodeInfoDto codeInfo) + { + var matched = new List(); + foreach (var condition in rule.Conditions) + { + if (MatchesCondition(condition, codeInfo)) + { + matched.Add($"{condition.DimensionType}:{condition.DimensionValue}"); + } + } + return matched; + } +} diff --git a/src/Fengling.Member.Domain/Fengling.Member.Domain.csproj b/src/Fengling.Member.Domain/Fengling.Member.Domain.csproj index 1ab15ae..e970442 100644 --- a/src/Fengling.Member.Domain/Fengling.Member.Domain.csproj +++ b/src/Fengling.Member.Domain/Fengling.Member.Domain.csproj @@ -1,4 +1,4 @@ - + net10.0