From 6835a593b0679238f0361e109bcb137309a69c07 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com>
Date: Mon, 6 Jul 2026 15:20:21 +0800
Subject: [PATCH] =?UTF-8?q?feat(swaptrade):=20=E6=B7=BB=E5=8A=A0=E6=94=B6?=
=?UTF-8?q?=E7=9B=8A=E7=BB=93=E7=AE=97=E6=97=A5=E6=9C=9F=E9=AA=8C=E8=AF=81?=
=?UTF-8?q?=E5=92=8C=E9=99=90=E5=88=B6=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 新增 MaxIncomeValueDate 属性用于限制收益结算日期上限
- 实现 parseLocalDate 函数用于解析本地日期格式
- 在前端添加日期选择器的最大日期限制和验证逻辑
- 添加 isAfterMaxIncomeValueDate 和 validateIncomeValueDate 方法进行日期验证
- 在后端添加 GetMaxIncomeValueDate 方法计算最大收益结算日期
- 实现 ValidateIncomeValueDate 对收益结算日期进行服务器端验证
- 添加 NormalizeIncomeUnwindDate 方法统一处理平仓日期
- 在模型中新增 MaxIncomeValueDate 非映射属性用于前端显示
- 更新日期格式化函数确保日期字符串的一致性处理
---
Framework/YLErp.Core/DBModels/SwapEvent.cs | 5 ++
.../Modules/SwapModule/SwapDealService.cs | 37 ++++++++++++++-
YLErpWeb/Views/SwapTrade2/SwapIncome.cshtml | 15 ++++--
.../Scripts/app/swaptrade/incomeSwapTrade.js | 46 ++++++++++++++++++-
4 files changed, 96 insertions(+), 7 deletions(-)
diff --git a/Framework/YLErp.Core/DBModels/SwapEvent.cs b/Framework/YLErp.Core/DBModels/SwapEvent.cs
index ce7f1cc2..eff65bac 100644
--- a/Framework/YLErp.Core/DBModels/SwapEvent.cs
+++ b/Framework/YLErp.Core/DBModels/SwapEvent.cs
@@ -130,6 +130,11 @@ namespace YLErp.DBModels
///
public DateTime ValueDate { get; set; }
///
+ /// 收益结算日期上限
+ ///
+ [NotMapped]
+ public DateTime? MaxIncomeValueDate { get; set; }
+ ///
/// 平仓/互换日期
///
public DateTime? UnwindDate { get; set; }
diff --git a/YLErpDAL/Modules/SwapModule/SwapDealService.cs b/YLErpDAL/Modules/SwapModule/SwapDealService.cs
index 8c557d4b..48b18a45 100644
--- a/YLErpDAL/Modules/SwapModule/SwapDealService.cs
+++ b/YLErpDAL/Modules/SwapModule/SwapDealService.cs
@@ -113,6 +113,11 @@ namespace YLErp.Modules.SwapModule
new TradeUnwindService(this).CloseReCheck_SetTrade(swapTradeId, isSwap, needCheck);
}
+ protected virtual DateTime GetMaxIncomeValueDate(trade td)
+ {
+ return QdpCalendarHelper.GetNonHolidayDefore(td.ExerciseDate.Value.AddDays(-1));
+ }
+
#endregion
public SwapDealService(OptUserInfo optUser) : base(optUser)
@@ -394,7 +399,8 @@ namespace YLErp.Modules.SwapModule
var positions = DbContext.swap_position.Where(x => x.SwapTradeId == tradeId && !x.Invalid);
var um = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
List eventTypes = new List() { (int)SwapFlowEventTypeEnum.互换, (int)SwapFlowEventTypeEnum.自动互换 };
- var dealDate = valuedateBLL.ValueDate < td.ExerciseDate.Value ? valuedateBLL.ValueDate : td.ExerciseDate.Value;
+ var maxIncomeValueDate = GetMaxIncomeValueDate(td);
+ var dealDate = valuedateBLL.ValueDate.Date > maxIncomeValueDate.Date ? maxIncomeValueDate : valuedateBLL.ValueDate;
// 收益结算不检查收盘限制
var tradeExtend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == tradeId);
td.trade_extend = tradeExtend;
@@ -425,7 +431,7 @@ namespace YLErp.Modules.SwapModule
unwindData.UnwindDate = dealDate;
floatEvent.UnwindDate = unwindData.UnwindDate;
floatEvent.EventDate = dealDate;
- unwindData.PayDate = QdpCalendarHelper.GetNonHoliday(unwindData.UnwindDate.Value.AddDays(td.trade_extend.ExtendObj.SettlementRules));
+ unwindData.PayDate = valuedateBLL.ValueDate;
floatEvent.PayDate = unwindData.PayDate;
floatEvent.SwapTradeId = tradeId;
floatEvent.SwapTradeNo = td.TradeNumber;
@@ -464,6 +470,7 @@ namespace YLErp.Modules.SwapModule
}
unwindData.FlowEvents.Add(floatEvent);
}
+ unwindData.MaxIncomeValueDate = maxIncomeValueDate;
return unwindData;
}
///
@@ -1645,6 +1652,8 @@ namespace YLErp.Modules.SwapModule
{
throw new ServiceException("未找到交易信息");
}
+ NormalizeIncomeUnwindDate(unwindData);
+ ValidateIncomeValueDate(unwindData, td);
//CheckLastEod(unwindData.ValueDate, td.StartDate.Value, unwindData.SwapTradeId); //去掉平仓收盘限制
ValidateFrontendPnL(unwindData, isIncome: true); // 只读校验告警,不阻断交易
ExecuteInTransaction(() =>
@@ -1683,6 +1692,11 @@ namespace YLErp.Modules.SwapModule
throw new Exception("该笔交易状态为平仓待复核,未找到相关记录,请检查该笔交易是否有效");
}
swapEvent.unwindData = JsonConvert.DeserializeObject(swapEvent.EventData);
+ if (eventType == (int)SwapEventTypeEnum.互换)
+ {
+ NormalizeIncomeUnwindDate(swapEvent.unwindData);
+ ValidateIncomeValueDate(swapEvent.unwindData, td);
+ }
var flowList = FindFlowEventsByEventId(swapEvent.id);
string action = eventType == (int)SwapEventTypeEnum.互换 ? ClientCashInCashOut.系统操作_互换 : ClientCashInCashOut.系统操作_平仓费;
int clientCashId = AddClientCash(td, Convert.ToDouble(-swapEvent.unwindData.SwapRealizedPnL), action, swapEvent.unwindData.ValueDate);
@@ -1726,6 +1740,11 @@ namespace YLErp.Modules.SwapModule
{
throw new ServiceException("未找到交易信息");
}
+ if (eventType == (int)SwapEventTypeEnum.互换)
+ {
+ NormalizeIncomeUnwindDate(unwindData);
+ ValidateIncomeValueDate(unwindData, td);
+ }
unwindData.SwapRealizedPnL = unwindData.SwapCloseAmount;
string action = eventType == (int)SwapEventTypeEnum.互换 ? ClientCashInCashOut.系统操作_互换 : ClientCashInCashOut.系统操作_平仓费;
ExecuteInTransaction(() =>
@@ -1735,6 +1754,20 @@ namespace YLErp.Modules.SwapModule
SaveAllChanges();
});
}
+ private void ValidateIncomeValueDate(UnwindData unwindData, trade td)
+ {
+ var maxIncomeValueDate = GetMaxIncomeValueDate(td).Date;
+ if (unwindData.ValueDate.Date > maxIncomeValueDate)
+ {
+ throw new ServiceException($"手动互换结算日期不能晚于当前交易结束日期T-1:{maxIncomeValueDate:yyyy-MM-dd}");
+ }
+ }
+
+ private void NormalizeIncomeUnwindDate(UnwindData unwindData)
+ {
+ unwindData.UnwindDate = unwindData.ValueDate;
+ }
+
///
/// 保存平仓/互换事件
///
diff --git a/YLErpWeb/Views/SwapTrade2/SwapIncome.cshtml b/YLErpWeb/Views/SwapTrade2/SwapIncome.cshtml
index 8084253a..578d264e 100644
--- a/YLErpWeb/Views/SwapTrade2/SwapIncome.cshtml
+++ b/YLErpWeb/Views/SwapTrade2/SwapIncome.cshtml
@@ -11,10 +11,17 @@
@section JS
{
@@ -45,7 +52,7 @@
| 起始日期 |
{{deal.StartDate}} |
收益结算日期 |
- |
+ |
| 支付日期 |
diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/incomeSwapTrade.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/incomeSwapTrade.js
index 019e302f..09bcf96b 100644
--- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/incomeSwapTrade.js
+++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/incomeSwapTrade.js
@@ -9,6 +9,19 @@ const inputFormatDividend = Object.freeze({ precision: 2, append: '', negative:
const inputFormatMarginRate = Object.freeze({ precision: otcformat.trading.marginRateP.precision, append: '%' });
const inputFormatMarginRateNoPercent = Object.freeze({ precision: otcformat.trading.umpriceP.precision, append: '', percent: true });
let ValueDate = model.ValueDate;
+let MaxIncomeValueDate = model.MaxIncomeValueDate ? model.MaxIncomeValueDate.substr(0, 10) : ValueDate;
+
+function parseLocalDate(value) {
+ if (!value) {
+ return null;
+ }
+ var parts = value.substr(0, 10).split('-');
+ if (parts.length !== 3) {
+ return null;
+ }
+ return new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
+}
+
const vue = new Vue({
el: '#vueDiv',
data: {
@@ -22,7 +35,7 @@ const vue = new Vue({
},
computed: {
maxUnwindDate() {
- return ValueDate;
+ return MaxIncomeValueDate;
},
minStartDate() {
return this.deal.StartDate;
@@ -33,7 +46,32 @@ const vue = new Vue({
this.initDeal();
this.setValueDate();
},
+ mounted() {
+ this.$nextTick(() => {
+ var $incomeValueDatePicker = $(this.$refs.incomeValueDatePicker.$el);
+ $incomeValueDatePicker.datepicker("option", "maxDate", parseLocalDate(MaxIncomeValueDate));
+ if (this.isAfterMaxIncomeValueDate(this.deal.ValueDate)) {
+ this.validateIncomeValueDate(this.deal.ValueDate);
+ } else {
+ $incomeValueDatePicker.val(this.deal.ValueDate);
+ }
+ });
+ },
methods: {
+ isAfterMaxIncomeValueDate(valueDate) {
+ return valueDate && MaxIncomeValueDate && valueDate > MaxIncomeValueDate;
+ },
+ validateIncomeValueDate(valueDate) {
+ if (!this.isAfterMaxIncomeValueDate(valueDate)) {
+ return true;
+ }
+ main.message("手动互换结算日期不能晚于当前交易结束日期T-1:" + MaxIncomeValueDate);
+ this.deal.ValueDate = MaxIncomeValueDate;
+ this.deal.UnwindDate = MaxIncomeValueDate;
+ this.floatPosition.UnwindDate = MaxIncomeValueDate;
+ $(this.$refs.incomeValueDatePicker.$el).val(MaxIncomeValueDate);
+ return false;
+ },
getPriceScale() {
return this.multiplier == 100 ? 0.01 : 1;
},
@@ -93,6 +131,9 @@ const vue = new Vue({
},
setValueDate(e) {//修改平仓日期
if (e) {
+ if (!this.validateIncomeValueDate(e)) {
+ e = MaxIncomeValueDate;
+ }
this.deal.ValueDate = e;
this.deal.UnwindDate = e;
this.floatPosition.UnwindDate = e;
@@ -212,6 +253,9 @@ const vue = new Vue({
main.message("请输入平仓日期");
return;
}
+ if (!thisObj.validateIncomeValueDate(thisObj.deal.ValueDate)) {
+ return;
+ }
let reqObj = _.cloneDeep(thisObj.deal);
let marginCloneList = _.cloneDeep(thisObj.marginList);
reqObj.FlowEvents = _.cloneDeep(thisObj.interestList);