refactor(dividend): 优化除权除息计算逻辑并改进数据类型精度

- 将除权除息相关数值字段从 double 类型改为 decimal 类型以提高精度
- 重构了红利比率计算方法 GetRatio,新增 GetRatioDecimal 方法使用 decimal 计算
- 修改价格和持仓数量计算逻辑,统一使用 decimal 进行高精度运算
- 更新数据库查询逻辑,将篮子标的判断从 IsBasket() 方法改为 CommodityCode 条件
- 优化 AddDividendInfos 方法中的批量处理逻辑,增加业务键冲突检测
- 添加数据源标识字段 DataSource 和来源更新时间字段 SourceUpdatedAt
- 新增 FindExDividendByBusinessKey 和 MergeNonZeroDividendValues 辅助方法
- 更新结算服务中除权除息信息的获取方式,使用字典查找替代 LINQ JOIN
- 修复前端保存除权信息时的响应处理逻辑
- 为基金类型也开放除权功能,不仅限于股票类型
- 添加单元测试验证篮子标的查询翻译逻辑的正确性
This commit is contained in:
张名锐
2026-08-13 10:53:57 +08:00
parent 304e04d60a
commit 5a51d66970
8 changed files with 296 additions and 80 deletions
@@ -0,0 +1,68 @@
using System;
using System.IO;
namespace YLErp.Modules.EodModule
{
[TestClass]
public class DividendBasketQueryTranslationTest
{
[TestMethod]
public void DividendBasketQueriesUseEfTranslatableCommodityCondition()
{
var source = ReadDividendServiceSource();
var addDividendQuery = ExtractQuery(
source,
"var basketList =",
"IEnumerable<eod_stock_price> priceList = null;");
var executeStatusQuery = ExtractQuery(
source,
"var umList = DataCacheProvider.GetUnderlyingDataSource().AsQueryable(",
").Select(O => O.UnderlyingCode).ToArray();");
AssertQueryUsesCommodityCondition(addDividendQuery, "AddDividendInfos");
AssertQueryUsesCommodityCondition(executeStatusQuery, "checkDividendInfoExecuteStatus");
}
private static void AssertQueryUsesCommodityCondition(string query, string methodName)
{
Assert.IsFalse(
query.Contains("IsBasket()", StringComparison.Ordinal),
$"{methodName} must not put IsBasket() in an IQueryable predicate.");
Assert.IsTrue(
query.Contains("O.CommodityCode == \"篮子标的\"", StringComparison.Ordinal),
$"{methodName} must filter baskets with the EF-translatable CommodityCode condition.");
}
private static string ExtractQuery(string source, string startMarker, string endMarker)
{
var start = source.IndexOf(startMarker, StringComparison.Ordinal);
Assert.IsTrue(start >= 0, $"Could not find query marker: {startMarker}");
var end = source.IndexOf(endMarker, start + startMarker.Length, StringComparison.Ordinal);
Assert.IsTrue(end >= 0, $"Could not find query end marker: {endMarker}");
return source.Substring(start, end + endMarker.Length - start);
}
private static string ReadDividendServiceSource()
{
var directory = new DirectoryInfo(AppContext.BaseDirectory);
while (directory != null)
{
var path = Path.Combine(
directory.FullName,
"YLErpDAL",
"Modules",
"TradeModule",
"DealModule",
"DividendService.cs");
if (File.Exists(path))
{
return File.ReadAllText(path);
}
directory = directory.Parent;
}
Assert.Fail("Could not locate DividendService.cs from the test output directory.");
return string.Empty;
}
}
}
@@ -123,7 +123,7 @@ namespace YLErp.Modules.EodModule
{
UnderlyingCode = "002043.SZ",
ExDividendDate = new DateTime(2020, 7, 6),
GiveCashAmount = 2.5,
GiveCashAmount = 2.5m,
GiveShareAmount = 0,
RationedSharesAmount = 0,
RationedSharesPrice = 0,
@@ -135,7 +135,7 @@ namespace YLErp.Modules.EodModule
{
UnderlyingCode = "600406.SH",
ExDividendDate = new DateTime(2020, 7, 8),
GiveCashAmount = 2.9,
GiveCashAmount = 2.9m,
GiveShareAmount = 0,
RationedSharesAmount = 0,
RationedSharesPrice = 0,
@@ -147,7 +147,7 @@ namespace YLErp.Modules.EodModule
{
UnderlyingCode = "600406.SH",
ExDividendDate = new DateTime(2020, 7, 8),
GiveCashAmount = 2.9,
GiveCashAmount = 2.9m,
GiveShareAmount = 0,
RationedSharesAmount = 0,
RationedSharesPrice = 0,
@@ -159,7 +159,7 @@ namespace YLErp.Modules.EodModule
{
UnderlyingCode = "601021.SH",
ExDividendDate = new DateTime(2020, 7, 8),
GiveCashAmount = 2.0006,
GiveCashAmount = 2.0006m,
GiveShareAmount = 0,
RationedSharesAmount = 0,
RationedSharesPrice = 0,
@@ -171,7 +171,7 @@ namespace YLErp.Modules.EodModule
{
UnderlyingCode = "300001.SZ",
ExDividendDate = new DateTime(2020, 7, 13),
GiveCashAmount = 0.2,
GiveCashAmount = 0.2m,
GiveShareAmount = 0,
RationedSharesAmount = 0,
RationedSharesPrice = 0,