feat(trade): 新增真实除权日字段并完善基金公司行为处理 init
- 添加 EffectiveDate 字段用于标识真实除权生效日 - 实现公司行为价格系数和数量系数统一计算方法 - 增加基金除权回退和平仓基线恢复功能 - 完善除权日验证逻辑,确保真实除权日不早于股权登记日 - 重构平仓流程,支持按有效EOD基线重新计算损益和现金 - 添加基金拆合股和现金分红的特殊处理逻辑 - 增加单元测试验证各种公司行为场景下的正确性
This commit is contained in:
@@ -243,6 +243,246 @@ namespace YLErp.Modules.SwapModule
|
||||
return td.ExerciseDate.Value.AddDays(-1);
|
||||
}
|
||||
|
||||
/// <summary>查询交易当前有效的初始腿和实时腿。测试可返回内存快照,避免初始化测试触库。</summary>
|
||||
protected virtual List<swap_position> FindActiveSwapPositions(int tradeId)
|
||||
{
|
||||
return DbContext.swap_position
|
||||
.Where(x => x.SwapTradeId == tradeId && !x.Invalid)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 找到平仓数据对应的实时浮动腿。正式路径以 PositionId 绑定,缺失时才按标的代码兜底;
|
||||
/// 这样后台不会把前端传入的价格当成权威基线。测试可 override 为内存持仓。
|
||||
/// </summary>
|
||||
protected virtual swap_position FindRealtimeFloatPosition(UnwindData unwindData)
|
||||
{
|
||||
if (unwindData == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var floatEvent = unwindData.FlowEvents?.FirstOrDefault(x => !string.IsNullOrEmpty(x.UnderlyingCode));
|
||||
var query = DbContext.swap_position
|
||||
.Where(x => x.SwapTradeId == unwindData.SwapTradeId && !x.IsInitial && !x.Invalid
|
||||
&& !string.IsNullOrEmpty(x.UnderlyingCode));
|
||||
if (floatEvent?.PositionId > 0)
|
||||
{
|
||||
var byPositionId = query.FirstOrDefault(x => x.PositionId == floatEvent.PositionId);
|
||||
if (byPositionId != null)
|
||||
{
|
||||
return byPositionId;
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrEmpty(floatEvent?.UnderlyingCode))
|
||||
{
|
||||
var byCode = query.FirstOrDefault(x => x.UnderlyingCode == floatEvent.UnderlyingCode);
|
||||
if (byCode != null)
|
||||
{
|
||||
return byCode;
|
||||
}
|
||||
}
|
||||
return query.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>查询 valueDate 当日已经生效的最近有效 Fund EOD。</summary>
|
||||
protected virtual eod_swap_position FindLatestFundEodPosition(
|
||||
int tradeId,
|
||||
long positionId,
|
||||
DateTime valueDate)
|
||||
{
|
||||
return new SwapEodPositionService(this)
|
||||
.GetLatestValidEodPosition(tradeId, positionId, valueDate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询 valueDate 当天真正生效的 Fund 公司行为。
|
||||
/// ExDividendDate 只是登记日,盘中基线不能按登记日提前切换;只有
|
||||
/// EffectiveDate == valueDate 时才把上一 EOD 的 Q/P 转成当日 BOD 的除权后 Q/P。
|
||||
/// </summary>
|
||||
protected virtual ex_dividend_info FindFundCorporateAction(
|
||||
string underlyingCode,
|
||||
DateTime valueDate)
|
||||
{
|
||||
return DbContext.ex_dividend_info.FirstOrDefault(x => x.ValidStatus
|
||||
&& x.UnderlyingCode == underlyingCode
|
||||
&& x.EffectiveDate.HasValue
|
||||
&& x.EffectiveDate.Value == valueDate.Date);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 公司行为系数仍使用登记日收盘价,而不是生效日盘中/收盘价。
|
||||
/// 测试可用 EOD 快照价格作为回退值;生产从登记日行情表取真实收盘价。
|
||||
/// </summary>
|
||||
protected virtual decimal GetFundCorporateActionClosePrice(
|
||||
ex_dividend_info dividendInfo,
|
||||
decimal fallbackPrice)
|
||||
{
|
||||
if (!dividendInfo.ExDividendDate.HasValue)
|
||||
{
|
||||
return fallbackPrice;
|
||||
}
|
||||
|
||||
var closePrice = new EodPriceProvider(dividendInfo.ExDividendDate.Value)
|
||||
.GetPrice(dividendInfo.UnderlyingCode, SettlementTypeEnum.ClosePrice);
|
||||
return Convert.ToDecimal(closePrice);
|
||||
}
|
||||
|
||||
/// <summary>读取 Fund 现金分红税率;单元测试可固定为 0,避免依赖系统日期配置。</summary>
|
||||
protected virtual decimal GetFundDividendTaxRate()
|
||||
=> new DividendService(this).GetDividendTaxRateDecimal();
|
||||
|
||||
/// <summary>
|
||||
/// 判断最新 EOD 之后是否已有同一浮动腿的完成流水。若有,说明当日实时持仓已发生部分平仓/互换,
|
||||
/// 不能再把较早 EOD 的数量覆盖回来,否则会抹掉当日成交结果。
|
||||
/// </summary>
|
||||
protected virtual bool HasCompletedFlowAfterFundEod(
|
||||
int tradeId,
|
||||
long positionId,
|
||||
DateTime eodDate,
|
||||
DateTime valueDate)
|
||||
{
|
||||
var asOfDate = valueDate.Date;
|
||||
return DbContext.swap_flow_event.Any(x => x.SwapTradeId == tradeId
|
||||
&& x.PositionId == positionId
|
||||
&& x.DataState == (int)SwapFlowDateStateEnum.完成
|
||||
&& x.EventDate > eodDate
|
||||
&& x.EventDate <= asOfDate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 恢复实时 Fund 浮动腿到截至指定日有效的 EOD 基线。
|
||||
/// 这是唯一允许把 EOD 公司行为结果带入盘中平仓的入口:10 送 10 后 EOD 是 2000 份/50
|
||||
/// 时,下一日直接使用 2000/50,不再把前端可能传入的 1000/100 或已除权价格重复套系数。
|
||||
/// 若最新 EOD 后存在完成流水则保持实时腿原值,避免覆盖当日部分平仓;非 Fund、无 EOD
|
||||
/// 和固定/利息腿均返回 false,沿用原逻辑。
|
||||
/// </summary>
|
||||
protected virtual bool TryRestoreEffectiveFundPosition(
|
||||
swap_position position,
|
||||
DateTime valueDate)
|
||||
{
|
||||
// 只对收取方向的 Fund 浮动腿恢复 EOD;固定腿、利息腿和支付方向不应被公司行为改写。
|
||||
// 无历史 EOD 或最新 EOD 后已有完成流水时返回 false,由调用方保持实时持仓原值,
|
||||
// 不伪造一份快照,也不把较早的 2000 份/50 覆盖掉当日已经部分平仓后的实时数量。
|
||||
if (position == null
|
||||
|| position.PosiDirection <= 0
|
||||
|| position.UnderlyingInstrumentType != ConsGlobal.InstrumentType.Fund)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var eodPosition = FindLatestFundEodPosition(
|
||||
position.SwapTradeId,
|
||||
position.PositionId,
|
||||
valueDate);
|
||||
if (eodPosition == null
|
||||
|| HasCompletedFlowAfterFundEod(
|
||||
position.SwapTradeId,
|
||||
position.PositionId,
|
||||
eodPosition.ValueDate,
|
||||
valueDate))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SwapEodPositionService.RestoreFundPositionFromEod(position, eodPosition))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 最近 EOD 已经处于生效日或更晚时,说明该快照本身已经是除权后基线,
|
||||
// 不能再次套系数。只有“最近 EOD < EffectiveDate <= valueDate”时,
|
||||
// 才在盘中恢复后补一次公司行为。
|
||||
var corporateAction = FindFundCorporateAction(
|
||||
position.UnderlyingCode,
|
||||
valueDate);
|
||||
if (corporateAction?.EffectiveDate > eodPosition.ValueDate.Date
|
||||
&& corporateAction.EffectiveDate.Value.Date <= valueDate.Date)
|
||||
{
|
||||
var closePrice = GetFundCorporateActionClosePrice(
|
||||
corporateAction,
|
||||
position.PosiGrossPrice);
|
||||
if (closePrice <= 0)
|
||||
{
|
||||
throw new ServiceException(
|
||||
$"Fund 标的【{position.UnderlyingCode}】登记日【{corporateAction.ExDividendDate:yyyy-MM-dd}】缺少有效收盘价,无法执行除权");
|
||||
}
|
||||
|
||||
var dividendTaxRate = GetFundDividendTaxRate();
|
||||
SwapEodPositionService.ApplyFundCorporateActionToPosition(
|
||||
position,
|
||||
corporateAction,
|
||||
closePrice,
|
||||
dividendTaxRate);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在直接提交前复核前端平仓数据。基线恢复成功时同步浮动流水价格、有效数量和名义本金,
|
||||
/// 并拒绝 CloseQty 超过有效 EOD 数量;全平请求则把数量规范为当前有效全部持仓。
|
||||
/// </summary>
|
||||
protected virtual bool TryRestoreEffectiveFundPosition(
|
||||
UnwindData unwindData,
|
||||
DateTime valueDate)
|
||||
{
|
||||
var position = FindRealtimeFloatPosition(unwindData);
|
||||
if (!TryRestoreEffectiveFundPosition(position, valueDate))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var floatEvent = unwindData.FlowEvents?.FirstOrDefault(x => !string.IsNullOrEmpty(x.UnderlyingCode));
|
||||
var effectiveQty = position.PosiQuantity;
|
||||
var requestedQty = unwindData.CloseQty;
|
||||
var fullClose = unwindData.CloseMethod == (int)CloseMethodEnum.全部平仓
|
||||
|| unwindData.ClosePercent >= 1m;
|
||||
// CloseQty 是部分平仓请求的数量口径;全平请求忽略前端缓存的旧数量,统一取 EOD 有效数量。
|
||||
// 例如 10 送 10 后 EOD 为 2000 份/50,前端仍传 1000 份时,全平必须落成 2000 份,
|
||||
// 否则会遗留 1000 份;现金派现后若 EOD 名义本金为 99000,平一半应按 49500 扣减。
|
||||
// 若交易级余额仍沿用旧值 100000,再扣有效平仓额 49500,就会错误留下 50500。
|
||||
if (requestedQty < 0m || (!fullClose && requestedQty > effectiveQty))
|
||||
{
|
||||
throw new ServiceException(
|
||||
$"Fund 浮动腿平仓数量 {requestedQty} 超过截至 {valueDate:yyyy-MM-dd} 有效持仓 {effectiveQty}");
|
||||
}
|
||||
|
||||
var closeQty = fullClose ? effectiveQty : requestedQty;
|
||||
var closeNotional = fullClose
|
||||
? position.PosiNotionalValue
|
||||
: Math.Round(
|
||||
closeQty * position.PosiGrossPrice * position.ContractSize,
|
||||
ConsGlobal.MoneyRound,
|
||||
MidpointRounding.AwayFromZero);
|
||||
unwindData.PositionQty = effectiveQty;
|
||||
unwindData.PosiNotionalValue = position.PosiNotionalValue;
|
||||
unwindData.CloseQty = closeQty;
|
||||
unwindData.CloseNotionalValue = closeNotional;
|
||||
if (!fullClose)
|
||||
{
|
||||
unwindData.ClosePercent = unwindData.NotionalValue > 0m
|
||||
? closeNotional / unwindData.NotionalValue
|
||||
: (effectiveQty == 0m ? 0m : closeQty / effectiveQty);
|
||||
}
|
||||
|
||||
if (floatEvent != null)
|
||||
{
|
||||
floatEvent.PosiGrossPrice = position.PosiGrossPrice;
|
||||
floatEvent.PosiNetPrice = position.PosiNetPrice;
|
||||
floatEvent.TradingAmountNetAvg = position.PosiNetNoFeePrice;
|
||||
floatEvent.TradingAmountNetFeeAvg = position.PosiNetFeePrice;
|
||||
floatEvent.Quantity = closeQty;
|
||||
floatEvent.PositionQty = effectiveQty - closeQty;
|
||||
floatEvent.ContractSize = position.ContractSize;
|
||||
|
||||
// EOD 恢复会改变入场基准和有效平仓数量;按当前平仓价重算前端派生盈亏。
|
||||
// FloatPnlSum 是只读属性,由 MarkClosePnl、费用和分红自动派生,不能直接写入。
|
||||
RecalculateNormalizedUnwindAmounts(unwindData);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public SwapDealService(OptUserInfo optUser) : base(optUser)
|
||||
@@ -300,7 +540,7 @@ namespace YLErp.Modules.SwapModule
|
||||
public UnwindData InitUnwind(int tradeId)
|
||||
{
|
||||
var td = DbContext.trade.Find(tradeId);
|
||||
var positions = DbContext.swap_position.Where(x => x.SwapTradeId == tradeId && !x.Invalid);
|
||||
var positions = FindActiveSwapPositions(tradeId);
|
||||
var um = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
|
||||
bool commodity = ConsGlobal.InstrumentType.CalcTypeIsFutures(um.UnderlyingInstrumentType);
|
||||
List<int> eventTyps = new List<int>() { (int)SwapEventTypeEnum.自动互换, (int)SwapEventTypeEnum.互换 };
|
||||
@@ -310,6 +550,11 @@ namespace YLErp.Modules.SwapModule
|
||||
td.trade_extend = tradeExtend;
|
||||
var position = positions.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode) && !x.IsInitial).FirstOrDefault();
|
||||
var oriPosition = positions.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode) && x.IsInitial).FirstOrDefault();
|
||||
// Fund 的盘中平仓基线来自最近有效 EOD;10 送 10 后应直接使用 2000 份/50,
|
||||
// 不能继续读取实时表中的 1000 份/100 再让前端重复套用除权系数。
|
||||
var restoredFundBaseline = TryRestoreEffectiveFundPosition(position, dealDate);
|
||||
// 恢复失败表示非 Fund、无历史 EOD,或 EOD 后已有完成流水;此时保留当前实时值,
|
||||
// 继续原有盘中流程,避免用不完整快照制造数量/价格。
|
||||
var preDealDate = GetPreDealDate(tradeId, dealDate, eventTyps);
|
||||
var hasProcess = HasTradeProcess();
|
||||
swap_flow_event floatEvent = new swap_flow_event();
|
||||
@@ -344,7 +589,11 @@ namespace YLErp.Modules.SwapModule
|
||||
unwindData.StructureType = td.StructureType;
|
||||
unwindData.NotionalValue = Convert.ToDecimal(td.OriginalStockEqvNotional ?? 0);
|
||||
unwindData.NotionalQty = positions.Where(x => x.IsInitial).Sum(s => s.PosiQuantity);
|
||||
unwindData.PosiNotionalValue = Convert.ToDecimal(td.StockEqvNotional);
|
||||
// 现金分红会调整 EOD 期初价但不改数量,因此持仓名义本金可能从 100000 变为 99000。
|
||||
// 只有 Fund EOD 基线恢复成功时才使用该值;其他品种继续沿用 trade 原口径。
|
||||
unwindData.PosiNotionalValue = restoredFundBaseline
|
||||
? position.PosiNotionalValue
|
||||
: Convert.ToDecimal(td.StockEqvNotional);
|
||||
unwindData.PositionQty = position != null ? position.PosiQuantity : Convert.ToDecimal(td.TradeAmount);
|
||||
unwindData.AnnualDays = tradeExtend == null ? 365 : tradeExtend.ExtendObj.AnnualDays;
|
||||
unwindData.CloseMethod = (int)CloseMethodEnum.全部平仓;
|
||||
@@ -525,7 +774,7 @@ namespace YLErp.Modules.SwapModule
|
||||
{
|
||||
var checkEventTypes = new List<int>() { (int)SwapEventTypeEnum.互换, (int)SwapEventTypeEnum.自动互换 };
|
||||
var td = DbContext.trade.Find(tradeId);
|
||||
var positions = DbContext.swap_position.Where(x => x.SwapTradeId == tradeId && !x.Invalid);
|
||||
var positions = FindActiveSwapPositions(tradeId);
|
||||
var um = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
|
||||
List<int> eventTypes = new List<int>() { (int)SwapFlowEventTypeEnum.互换, (int)SwapFlowEventTypeEnum.自动互换 };
|
||||
var maxIncomeValueDate = GetMaxIncomeValueDate(td);
|
||||
@@ -534,6 +783,10 @@ namespace YLErp.Modules.SwapModule
|
||||
var tradeExtend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == tradeId);
|
||||
td.trade_extend = tradeExtend;
|
||||
var position = positions.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode) && !x.IsInitial).FirstOrDefault();
|
||||
// 收益结算与手工平仓共用 Fund 的有效 EOD 基线,避免仍返回除权前价格/数量。
|
||||
var restoredFundBaseline = TryRestoreEffectiveFundPosition(position, dealDate);
|
||||
// 若无法恢复(例如当日已有互换/平仓流水),这里故意沿用实时腿,不能把较早 EOD
|
||||
// 当作当日最终状态;收益结算的其余字段仍按原始实时口径组装。
|
||||
//var preSettleDate = CheckLastEod(dealDate, td.StartDate.Value, tradeId);//上一交易日期
|
||||
var preDealDate = GetPreDealDate(tradeId, dealDate, eventTypes);
|
||||
var hasProcess = HasTradeProcess();
|
||||
@@ -570,7 +823,9 @@ namespace YLErp.Modules.SwapModule
|
||||
unwindData.StructureType = td.StructureType;
|
||||
unwindData.NotionalValue = Convert.ToDecimal(td.OriginalStockEqvNotional ?? 0);
|
||||
unwindData.NotionalQty = positions.Where(x => x.IsInitial).Sum(s => s.PosiQuantity);
|
||||
unwindData.PosiNotionalValue = Convert.ToDecimal(td.StockEqvNotional);
|
||||
unwindData.PosiNotionalValue = restoredFundBaseline
|
||||
? position.PosiNotionalValue
|
||||
: Convert.ToDecimal(td.StockEqvNotional);
|
||||
unwindData.PositionQty = position != null ? position.PosiQuantity : Convert.ToDecimal(td.TradeAmount);
|
||||
unwindData.AnnualDays = tradeExtend == null ? 365 : tradeExtend.ExtendObj.AnnualDays;
|
||||
unwindData.ClosePercent = unwindData.PosiNotionalValue / unwindData.NotionalValue;
|
||||
@@ -1574,6 +1829,18 @@ namespace YLErp.Modules.SwapModule
|
||||
throw new ServiceException("未找到交易信息");
|
||||
}
|
||||
NormalizeEventUnwindDate(unwindData);
|
||||
// 提交时再次从有效 EOD/实时腿复核基线,不能只相信前端缓存的数量和价格。
|
||||
var restoredFundBaseline = TryRestoreEffectiveFundPosition(unwindData, unwindData.ValueDate);
|
||||
// 这是直接提交路径的最后一道复核。若返回 false(非 Fund、无快照、或 EOD 后已有完成流水),
|
||||
// 不改写前端数据,沿用当日实时持仓;审批冻结事件和自动平仓入口不经过此复核,见下方说明。
|
||||
if (restoredFundBaseline)
|
||||
{
|
||||
// 正式提交必须让交易级余额与同一 Fund EOD 基线一致,再执行原有扣减。
|
||||
// 例:派现后有效名义本金为 99000,平掉一半 49500 后应剩 49500;
|
||||
// 若仍从 trade 旧值 100000 扣减,会错误留下 50500。
|
||||
td.StockEqvNotional = Convert.ToDouble(unwindData.PosiNotionalValue);
|
||||
td.TradeAmount = Convert.ToDouble(unwindData.PositionQty);
|
||||
}
|
||||
NormalizeNotionalValues(unwindData);
|
||||
NormalizeManualSettlementAmounts(unwindData, (int)SwapEventTypeEnum.平仓, "系统操作_平仓");
|
||||
//CheckLastEod(unwindData.ValueDate, td.StartDate.Value, unwindData.SwapTradeId); //去掉平仓收盘限制
|
||||
@@ -1635,6 +1902,8 @@ namespace YLErp.Modules.SwapModule
|
||||
var tradeExtend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == td.id);
|
||||
td.trade_extend = tradeExtend;
|
||||
var position = positions.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode) && !x.IsInitial).FirstOrDefault();
|
||||
// 自动平仓由系统流水直接生成,当前入口沿用实时持仓和传入平仓数量,未重新读取 Fund EOD。
|
||||
// 因此它不具备手工 SwapUnwind 的 EOD 复核保护,生产上需确保自动流水已在正确的 EOD 基线之后生成。
|
||||
var storagePriceRound = ConsGlobal.InstrumentType.IsBond(position?.UnderlyingInstrumentType)
|
||||
? ConsGlobal.PriceRound
|
||||
: ConsGlobal.SwapDeliveryPriceRound;
|
||||
@@ -2047,6 +2316,9 @@ namespace YLErp.Modules.SwapModule
|
||||
throw new ServiceException("未找到交易信息");
|
||||
}
|
||||
NormalizeEventUnwindDate(unwindData);
|
||||
// 正常页面先由 InitIncome 读取最近有效 Fund EOD;本提交方法本身不再重读快照,
|
||||
// 直接使用调用方传入的数据。若数据来自待复核事件,则它是申请时冻结的快照,日期之后的除权
|
||||
// 不会在这里回写,属于审批链路的残余风险。
|
||||
ValidateIncomeValueDate(unwindData, td);
|
||||
NormalizeManualSettlementAmounts(unwindData, (int)SwapEventTypeEnum.互换, "系统操作_互换");
|
||||
//CheckLastEod(unwindData.ValueDate, td.StartDate.Value, unwindData.SwapTradeId); //去掉平仓收盘限制
|
||||
@@ -2083,6 +2355,9 @@ namespace YLErp.Modules.SwapModule
|
||||
{
|
||||
throw new Exception("该笔交易状态为平仓待复核,未找到相关记录,请检查该笔交易是否有效");
|
||||
}
|
||||
// 审批通过消费申请时序列化的 unwindData/流水,不重新按当前 Fund EOD 重建数量和价格。
|
||||
// 这是为了保持待复核事件可重放的一致性,但也意味着申请后发生除权时仍可能带入冻结的旧基线;
|
||||
// 直接提交路径的 EOD 复核不覆盖此审批路径。
|
||||
swapEvent.unwindData = JsonConvert.DeserializeObject<UnwindData>(swapEvent.EventData);
|
||||
NormalizeEventUnwindDate(swapEvent.unwindData);
|
||||
NormalizeNotionalValues(swapEvent.unwindData);
|
||||
@@ -2177,6 +2452,8 @@ namespace YLErp.Modules.SwapModule
|
||||
throw new ServiceException("未找到交易信息");
|
||||
}
|
||||
NormalizeEventUnwindDate(unwindData);
|
||||
// 进入审批申请时保存的是前端冻结的事件数据;当前路径不执行直接 SwapUnwind 的 Fund EOD 复核。
|
||||
// 因而申请发生在除权前、审批发生在除权后的场景,冻结数据仍是旧基线,需重新发起申请才能刷新。
|
||||
if (eventType == (int)SwapEventTypeEnum.互换)
|
||||
{
|
||||
ValidateIncomeValueDate(unwindData, td);
|
||||
|
||||
Reference in New Issue
Block a user