feat: add points rule matcher service
This commit is contained in:
parent
a92847530d
commit
9ee1e3bb72
@ -2,7 +2,6 @@
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Third-party package versions -->
|
||||
<NetCorePalVersion>3.2.1</NetCorePalVersion>
|
||||
@ -13,14 +12,12 @@
|
||||
<NetCorePalTestcontainerVersion>1.0.5</NetCorePalTestcontainerVersion>
|
||||
<NetCorePalAspireVersion>1.1.2</NetCorePalAspireVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="9.0.0" />
|
||||
<PackageVersion Include="AspNet.Security.OAuth.Feishu" Version="9.0.0" />
|
||||
<PackageVersion Include="AspNet.Security.OAuth.Weixin" Version="9.0.0" />
|
||||
|
||||
<!-- Database providers - framework specific versions -->
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0" />
|
||||
@ -39,7 +36,6 @@
|
||||
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis" Version="4.12.0" />
|
||||
|
||||
<!-- CAP packages for .NET 9.0+ -->
|
||||
<PackageVersion Include="DotNetCore.CAP.Dashboard" Version="8.4.1" />
|
||||
<PackageVersion Include="DotNetCore.CAP.RabbitMQ" Version="8.4.1" />
|
||||
@ -50,13 +46,10 @@
|
||||
<PackageVersion Include="DotNetCore.CAP.RedisStreams" Version="8.4.1" />
|
||||
<PackageVersion Include="DotNetCore.CAP.Pulsar" Version="8.4.1" />
|
||||
<PackageVersion Include="DotNetCore.CAP.OpenTelemetry" Version="8.4.1" />
|
||||
|
||||
<!-- FastEndpoints -->
|
||||
<PackageVersion Include="FastEndpoints" Version="$(FastEndpointsVersion)" />
|
||||
<PackageVersion Include="FastEndpoints.Swagger" Version="$(FastEndpointsVersion)" />
|
||||
<PackageVersion Include="FastEndpoints.Swagger.Swashbuckle" Version="2.3.0" />
|
||||
|
||||
|
||||
<!-- Other packages -->
|
||||
<PackageVersion Include="FluentValidation.AspNetCore" Version="11.3.1" />
|
||||
<PackageVersion Include="FluentValidation.DependencyInjectionExtensions" Version="11.3.1" />
|
||||
@ -75,7 +68,6 @@
|
||||
<PackageVersion Include="Serilog.Sinks.OpenTelemetry" Version="4.1.0" />
|
||||
<PackageVersion Include="StackExchange.Redis" Version="2.9.32" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="7.2.0" />
|
||||
|
||||
<!-- Aspire packages -->
|
||||
<PackageVersion Include="Aspire.Hosting.AppHost" Version="$(AspireVersion)" />
|
||||
<PackageVersion Include="Aspire.Hosting.Docker" Version="13.1.0-preview.1.25616.3" />
|
||||
@ -103,7 +95,6 @@
|
||||
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="$(OpenTelemetryVersion)" />
|
||||
<PackageVersion Include="OpenTelemetry.Instrumentation.SqlClient" Version="1.13.0-beta.1" />
|
||||
<PackageVersion Include="Npgsql.OpenTelemetry" Version="8.0.8" />
|
||||
|
||||
<!-- NetCorePal packages -->
|
||||
<PackageVersion Include="NetCorePal.Context.AspNetCore" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Context.CAP" Version="$(NetCorePalVersion)" />
|
||||
@ -124,14 +115,13 @@
|
||||
<PackageVersion Include="NetCorePal.Extensions.MicrosoftServiceDiscovery" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Extensions.MultiEnv" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Extensions.NewtonsoftJson" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Extensions.Primitives" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Extensions.Primitives" Version="3.2.1" />
|
||||
<PackageVersion Include="NetCorePal.Extensions.Repository.EntityFrameworkCore" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Extensions.Repository.EntityFrameworkCore.Snowflake" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.OpenTelemetry.Diagnostics" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Aspire.Hosting.DMDB" Version="$(NetCorePalAspireVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Aspire.Hosting.OpenGauss" Version="$(NetCorePalAspireVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Aspire.Hosting.MongoDB" Version="$(NetCorePalAspireVersion)" />
|
||||
|
||||
<!-- Testing packages -->
|
||||
<PackageVersion Include="Moq" Version="4.20.72" />
|
||||
<PackageVersion Include="Testcontainers" Version="$(TestcontainersVersion)" />
|
||||
@ -150,8 +140,7 @@
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.4" />
|
||||
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
|
||||
<PackageVersion Include="FastEndpoints.Testing" Version="$(FastEndpointsVersion)" />
|
||||
|
||||
<!-- Code analysis -->
|
||||
<PackageVersion Include="SonarAnalyzer.CSharp" Version="10.3.0.106239" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
114
src/Fengling.Member.Application/Services/PointsRuleMatcher.cs
Normal file
114
src/Fengling.Member.Application/Services/PointsRuleMatcher.cs
Normal file
@ -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<PointsCalculationResultDto> CalculatePointsAsync(CodeInfoDto codeInfo)
|
||||
{
|
||||
var result = new PointsCalculationResultDto(
|
||||
Success: false,
|
||||
Points: 0,
|
||||
ExpireAt: DateTime.MinValue,
|
||||
AppliedRuleId: null,
|
||||
Message: "No matching rule found",
|
||||
MatchedDimensions: new List<string>()
|
||||
);
|
||||
|
||||
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<Guid>)?.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<string> GetMatchedDimensions(PointsRule rule, CodeInfoDto codeInfo)
|
||||
{
|
||||
var matched = new List<string>();
|
||||
foreach (var condition in rule.Conditions)
|
||||
{
|
||||
if (MatchesCondition(condition, codeInfo))
|
||||
{
|
||||
matched.Add($"{condition.DimensionType}:{condition.DimensionValue}");
|
||||
}
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user