refactor(trade): 优化除权信息处理逻辑和数据查询策略
- 在 FindExDividendByBusinessKey 方法中添加注释说明业务唯一键按"标的 + 自然日"定义 - 实现字段独立合并策略,当前值非零时覆盖旧值,为零时保留旧值 - 保存前统一截断时间部分,确保同一天不同时间能命中同一个自然日业务键 - 先在当前批次内按业务键归并,避免重复行生成多条数据库记录 - 除权数据查询时不使用 SQL 左连接,防止重复行扩增影响风险计算 - 使用不区分大小写的 UnderlyingCode 匹配,兼容代码大小写差异 - 除权记录不参与 SQL 左连接,先完成基础关联再内存查找唯一记录 - 风险对象的除权 Pv 重算规则与持仓处理保持一致,仅在股票交易类型下执行
This commit is contained in:
@@ -860,6 +860,9 @@ namespace YLErp.Modules.TradeModule.DealModule
|
||||
|
||||
private ex_dividend_info FindExDividendByBusinessKey(int underlyingId, DateTime exDividendDate, int excludedId = 0)
|
||||
{
|
||||
// 业务唯一键按“标的 + 自然日”定义,而不是按完整 DateTime 定义。
|
||||
// 因此这里使用 [当天 00:00, 次日 00:00) 查询,兼容历史数据中可能存在的时分秒。
|
||||
// excludedId 用于编辑已有记录时排除自身,避免把当前记录误判为重复记录。
|
||||
return DbContext.ex_dividend_info.FirstOrDefault(O => O.UnderlyingId == underlyingId
|
||||
&& O.ExDividendDate >= exDividendDate
|
||||
&& O.ExDividendDate < exDividendDate.AddDays(1)
|
||||
@@ -877,6 +880,10 @@ namespace YLErp.Modules.TradeModule.DealModule
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
}
|
||||
|
||||
// 同一业务键可能分别来自多行导入,或来自“数据库旧记录 + 当前导入记录”。
|
||||
// 每个字段独立合并:当前值非零时覆盖旧值,当前值为零时保留旧值,
|
||||
// 这样派息、送股、配股数量、配股价格可以从不同来源补齐到同一行。
|
||||
// 该约定将零解释为“未提供”,因此不能通过普通导入把已有字段显式清零。
|
||||
if (source.GiveCashAmount != 0m)
|
||||
{
|
||||
target.GiveCashAmount = source.GiveCashAmount;
|
||||
@@ -929,6 +936,8 @@ namespace YLErp.Modules.TradeModule.DealModule
|
||||
return false;
|
||||
}
|
||||
|
||||
// 保存前统一截断时间部分,确保 Excel/接口传入的同一天不同时间
|
||||
// 能命中同一个自然日业务键,也与数据库的一行模型保持一致。
|
||||
var exDividendDate = item.ExDividendDate.Value.Date;
|
||||
var businessKey = (underlying.id, exDividendDate);
|
||||
if (item.id > 0
|
||||
@@ -946,10 +955,13 @@ namespace YLErp.Modules.TradeModule.DealModule
|
||||
item.RationedSharesPrice = OtcFormatHelper.FormatValue(item.RationedSharesPrice, 6);
|
||||
item.GiveShareAmount = OtcFormatHelper.FormatValue(item.GiveShareAmount, 6);
|
||||
|
||||
// 先在当前批次内按业务键归并。第一条记录作为待保存目标,后续记录
|
||||
// 只补充/覆盖非零字段,不会因为重复行而生成多条数据库记录。
|
||||
if (preparedIndexes.TryGetValue(businessKey, out var preparedIndex))
|
||||
{
|
||||
var preparedItem = preparedInfos[preparedIndex].Item;
|
||||
// 批量保存除权信息时,检测同一业务键(标的+日期)下是否存在冲突的数据库记录。
|
||||
// 同一业务键下允许重复的是同一条记录(两个新对象都为 id=0,
|
||||
// 或两个对象的 id 相同);不同 id 代表不同存量记录,不能静默合并。
|
||||
if ((preparedItem.id == 0) != (item.id == 0)
|
||||
|| preparedItem.id > 0 && item.id > 0 && preparedItem.id != item.id)
|
||||
{
|
||||
@@ -983,6 +995,8 @@ namespace YLErp.Modules.TradeModule.DealModule
|
||||
var item = prepared.Item;
|
||||
var underlying = prepared.Underlying;
|
||||
var itemDate = prepared.ExDividendDate;
|
||||
// id>0 表示前端正在编辑指定的存量记录;id=0 时先按自然日业务键
|
||||
// 查找数据库旧记录,使“新增导入”也能与已有记录合并,而不是重复插入。
|
||||
var dividend = item.id > 0
|
||||
? DbContext.ex_dividend_info.FirstOrDefault(O => O.id == item.id)
|
||||
: FindExDividendByBusinessKey(underlying.id, itemDate);
|
||||
@@ -1018,10 +1032,10 @@ namespace YLErp.Modules.TradeModule.DealModule
|
||||
dividend.UnderlyingCode = item.UnderlyingCode;
|
||||
dividend.UnderlyingId = item.UnderlyingId;
|
||||
dividend.ExDividendDate = item.ExDividendDate;
|
||||
dividend.GiveCashAmount = item.GiveCashAmount;
|
||||
dividend.RationedSharesAmount = item.RationedSharesAmount;
|
||||
dividend.RationedSharesPrice = item.RationedSharesPrice;
|
||||
dividend.GiveShareAmount = item.GiveShareAmount;
|
||||
// 数据库已有记录也必须走与批次内重复行相同的合并规则:导入字段非零
|
||||
// 才覆盖旧值,导入字段为零则保留数据库存量值,避免一次不完整导入
|
||||
// 把旧的派息/送股/配股信息误清零。
|
||||
MergeNonZeroDividendValues(dividend, item);
|
||||
dividend.ValidStatus = true;
|
||||
dividend.DataSource = ExDividendDataSources.Manual;
|
||||
dividend.SourceUpdatedAt = sourceUpdatedAt;
|
||||
|
||||
Reference in New Issue
Block a user