From 9ee1e3bb722d49da5291f62bfaf2e7f1a55e0d9f Mon Sep 17 00:00:00 2001
From: Sam <315859133@qq.com>
Date: Mon, 9 Feb 2026 18:56:55 +0800
Subject: [PATCH] feat: add points rule matcher service
---
Directory.Packages.props | 15 +--
.../Services/PointsRuleMatcher.cs | 114 ++++++++++++++++++
.../Fengling.Member.Domain.csproj | 2 +-
3 files changed, 117 insertions(+), 14 deletions(-)
create mode 100644 src/Fengling.Member.Application/Services/PointsRuleMatcher.cs
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