From aa26f712bbc645115d8da7a8ca6260c77b1459c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E6=96=B9=E6=B5=B7?= Date: Fri, 18 Jul 2025 17:44:58 +0800 Subject: [PATCH] =?UTF-8?q?TRS-ZS-566=E3=80=81TRS-ZS-568=20=E5=AE=A2?= =?UTF-8?q?=E6=88=B7=E4=BF=9D=E8=AF=81=E9=87=91=E7=8E=87=E9=9C=80=E6=B1=82?= =?UTF-8?q?=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Framework/YLErp.Cache/IYLCache.cs | 2 +- Framework/YLErp.Cache/YLRedisCache.cs | 4 +- .../YLErp.Core/DBModels/Base/DBModelBaseV6.cs | 62 ++++ .../DBModels/Consts/ConsMarginTerm.cs | 73 ++++ .../DBModels/client_margin_config.cs | 35 ++ .../DBModels/client_margin_detail.cs | 54 +++ UnitTestProject/Base/YLUnitTestBase.cs | 2 +- .../UnderlyingModule/UnderlyingHelperTest.cs | 129 +++++++ UnitTestProject/appsettings.json | 11 +- .../BLL/EodSettlement/ClientBalanceUtility.cs | 3 - .../RealTimeClientBanlanceService.cs | 25 +- YLErpDAL/BLL/EodSettlement/RealtimePnlCalc.cs | 2 +- YLErpDAL/DataBase/YLContext.cs | 3 + YLErpDAL/Model/ClientMarginConfigReq.cs | 15 + YLErpDAL/Model/ClientMarginDetailDto.cs | 77 ++++ YLErpDAL/Model/GetClientMarginDetailReq.cs | 15 + .../MarginModule/ClientMarginConfigService.cs | 154 ++++++++ .../SwapModule/SwapTradeAutoService.cs | 88 ++--- .../Modules/SwapModule/SwapTradeService.cs | 4 +- .../DealModule/TradeConfirmService.cs | 20 +- .../UnderlyingModule/UnderlyingHelper.cs | 267 +++++++++++++- YLErpWeb/App_Data/Menus.txt | 2 +- .../ClientMarginConfigController.cs | 227 ++++++++++++ YLErpWeb/Controllers/TrsOpenController.cs | 2 +- YLErpWeb/Controllers/tradeController.cs | 17 +- .../ClientMarginConfigEdit.cshtml | 184 ++++++++++ .../ClientMarginConfigList.cshtml | 337 ++++++++++++++++++ 27 files changed, 1696 insertions(+), 118 deletions(-) create mode 100644 Framework/YLErp.Core/DBModels/Base/DBModelBaseV6.cs create mode 100644 Framework/YLErp.Core/DBModels/Consts/ConsMarginTerm.cs create mode 100644 Framework/YLErp.Core/DBModels/client_margin_config.cs create mode 100644 Framework/YLErp.Core/DBModels/client_margin_detail.cs create mode 100644 UnitTestProject/Modules/UnderlyingModule/UnderlyingHelperTest.cs create mode 100644 YLErpDAL/Model/ClientMarginConfigReq.cs create mode 100644 YLErpDAL/Model/ClientMarginDetailDto.cs create mode 100644 YLErpDAL/Model/GetClientMarginDetailReq.cs create mode 100644 YLErpDAL/Modules/MarginModule/ClientMarginConfigService.cs create mode 100644 YLErpWeb/Controllers/ClientMarginConfigController.cs create mode 100644 YLErpWeb/Views/ClientMarginConfig/ClientMarginConfigEdit.cshtml create mode 100644 YLErpWeb/Views/ClientMarginConfig/ClientMarginConfigList.cshtml diff --git a/Framework/YLErp.Cache/IYLCache.cs b/Framework/YLErp.Cache/IYLCache.cs index 19047c9a..d3aa202d 100644 --- a/Framework/YLErp.Cache/IYLCache.cs +++ b/Framework/YLErp.Cache/IYLCache.cs @@ -41,7 +41,7 @@ namespace YLErp.Cache /// T StringGet(string key) where T : class; - bool StringSetWithNoPrefix(string key, object value) where T : class; + bool StringSetWithNoPrefix(string key, object value, TimeSpan? timeSpan) where T : class; T StringGetWithNoPrefix(string key) where T : class; #region Batch Operate /// diff --git a/Framework/YLErp.Cache/YLRedisCache.cs b/Framework/YLErp.Cache/YLRedisCache.cs index e153156c..6097729a 100644 --- a/Framework/YLErp.Cache/YLRedisCache.cs +++ b/Framework/YLErp.Cache/YLRedisCache.cs @@ -231,9 +231,9 @@ namespace YLErp.Cache /// /// /// - public bool StringSetWithNoPrefix(string key, object value) where T : class + public bool StringSetWithNoPrefix(string key, object value,TimeSpan? timeSpan) where T : class { - return db.StringSet(key, JsonConvert.SerializeObject(value)); + return db.StringSet(key, JsonConvert.SerializeObject(value), timeSpan); } } } diff --git a/Framework/YLErp.Core/DBModels/Base/DBModelBaseV6.cs b/Framework/YLErp.Core/DBModels/Base/DBModelBaseV6.cs new file mode 100644 index 00000000..9bf652cb --- /dev/null +++ b/Framework/YLErp.Core/DBModels/Base/DBModelBaseV6.cs @@ -0,0 +1,62 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace YLErp.DBModels +{ + /// + /// 数据模型基类 + /// + [Serializable] + public abstract class DBModelBaseV6 : DBModelBase + { + /// + /// 操作人ID + /// + [DisplayName("操作人")] + [Column("opt_id")] + public virtual int? OptId { set; get; } + + /// + /// 操作人名称 + /// + [DisplayName("操作人")] + [Column("opt_name")] + public virtual string OptName { set; get; } + + /// + /// 操作时间 + /// + [DisplayName("操作时间")] + [Column("opt_date")] + public virtual DateTime? OptDate { set; get; } + + /// + /// 设置操作人操作时间 + /// + public void SetOpt(DBModelWithOperator baseModel) + { + if (baseModel == null) + { + throw new ArgumentNullException(nameof(baseModel)); + } + + OptId = baseModel.OptId; + OptName = baseModel.OptName; + OptDate = baseModel.OptDate; + } + + /// + /// 设置操作人操作时间 + /// + public void SetOpt(OptUserInfo userInfo) + { + if (userInfo is null) + { + throw new ArgumentNullException(nameof(userInfo)); + } + + OptId = userInfo.UserId; + OptName = userInfo.UserName; + OptDate = DateTime.Now; + } + } +} diff --git a/Framework/YLErp.Core/DBModels/Consts/ConsMarginTerm.cs b/Framework/YLErp.Core/DBModels/Consts/ConsMarginTerm.cs new file mode 100644 index 00000000..17a2d00f --- /dev/null +++ b/Framework/YLErp.Core/DBModels/Consts/ConsMarginTerm.cs @@ -0,0 +1,73 @@ +namespace YLErp.DBModels +{ + /// + /// 保证金配置期限常量定义 + /// + public static class ConsMarginTerm + { + /// + /// 默认期限(空字符串) + /// + public const string Default = ""; + + /// + /// 2年期 + /// + public const string TwoYear = "2Y"; + + /// + /// 5年期 + /// + public const string FiveYear = "5Y"; + + /// + /// 10年期 + /// + public const string TenYear = "10Y"; + + /// + /// 30年期 + /// + public const string ThirtyYear = "30Y"; + + /// + /// 所有允许的期限值 + /// + public static readonly List AllowedTerms = new List + { + Default, + TwoYear, + FiveYear, + TenYear, + ThirtyYear + }; + + /// + /// 验证期限是否有效 + /// + /// 期限值 + /// 是否有效 + public static bool IsValidTerm(string term) + { + return AllowedTerms.Contains(term ?? string.Empty); + } + + /// + /// 获取期限的显示名称 + /// + /// 期限值 + /// 显示名称 + public static string GetDisplayName(string term) + { + return term switch + { + Default => "默认", + TwoYear => "2年", + FiveYear => "5年", + TenYear => "10年", + ThirtyYear => "30年", + _ => term?.Replace("Y", "年") ?? "默认" + }; + } + } +} \ No newline at end of file diff --git a/Framework/YLErp.Core/DBModels/client_margin_config.cs b/Framework/YLErp.Core/DBModels/client_margin_config.cs new file mode 100644 index 00000000..534c0439 --- /dev/null +++ b/Framework/YLErp.Core/DBModels/client_margin_config.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace YLErp.DBModels +{ + /// + /// 客户保证金配置 + /// + [Table("client_margin_config")] + public class client_margin_config : DBModelBaseV6 + { + /// + /// 客户ID + /// + [DisplayName("客户ID")] + public int client_id { get; set; } + + /// + /// 客户名称 + /// + [DisplayName("客户名称")] + [NotMapped] + public string client_name { get; set; } + + /// + /// 生效日期 + /// + [DisplayName("生效日期")] + public DateTime value_date { get; set; } + } +} diff --git a/Framework/YLErp.Core/DBModels/client_margin_detail.cs b/Framework/YLErp.Core/DBModels/client_margin_detail.cs new file mode 100644 index 00000000..63074ef1 --- /dev/null +++ b/Framework/YLErp.Core/DBModels/client_margin_detail.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace YLErp.DBModels +{ + /// + /// 客户保证金配置详情 + /// + [Table("client_bond_trs_margin_detail")] + public class client_margin_detail + { + /// + /// 主键ID + /// + [DisplayName("主键ID")] + [Key] + public int id { get; set; } + + /// + /// 保证金关联主表id + /// + [DisplayName("配置ID")] + public int config_id { get; set; } + + /// + /// 利率债期限 + /// + [DisplayName("利率债期限")] + public string bond_term { get; set; } + + /// + /// 初始保证金率(%) + /// + [DisplayName("初始保证金率")] + public decimal init_rate { get; set; } + + /// + /// 维持保证金率(%) + /// + [DisplayName("维持保证金率")] + public decimal maintain_rate { get; set; } + + /// + /// 互换默认天数 + /// + [DisplayName("互换默认天数")] + public int swap_days { get; set; } + } +} diff --git a/UnitTestProject/Base/YLUnitTestBase.cs b/UnitTestProject/Base/YLUnitTestBase.cs index 42475b9c..c6b78d42 100644 --- a/UnitTestProject/Base/YLUnitTestBase.cs +++ b/UnitTestProject/Base/YLUnitTestBase.cs @@ -2,7 +2,7 @@ { public class YLUnitTestBase : YLBaseService { - readonly StringBuilder _clearSQL; + public readonly StringBuilder _clearSQL; public YLUnitTestBase() : base(OptUserInfo.UnitTestUser) { diff --git a/UnitTestProject/Modules/UnderlyingModule/UnderlyingHelperTest.cs b/UnitTestProject/Modules/UnderlyingModule/UnderlyingHelperTest.cs new file mode 100644 index 00000000..97e60b01 --- /dev/null +++ b/UnitTestProject/Modules/UnderlyingModule/UnderlyingHelperTest.cs @@ -0,0 +1,129 @@ +using YLErp.Modules.UnderlyingModule; + +namespace YLErp.UnitTestProject.Modules.UnderlyingModule +{ + [TestClass] + public class UnderlyingHelperTest : YLUnitTestBase + { + + [TestMethod] + public void GetApplicableMarginTerm_RuleTable_AllCases() + { + // 发行期限不限(OpenDate=null),测试(0.00,2.00],应返回2年 + var code1 = "UNLIMITED_2Y"; + var maturity1 = new DateTime(2030, 1, 1); + var underlying1 = CreateTestUnderlying ( code1, null,maturity1); + var valueDate1 = maturity1.AddYears(-1); // 剩余1年 + var result1=UnderlyingHelper.GetApplicableMarginTerm(code1, valueDate1); + Assert.AreEqual(ConsMarginTerm.TwoYear, result1, "(0,2]年 不限 应为2年"); + + // (2.00,2.25] <5年,应返回2年 + var code2 = "LT5_2_25"; + var open2 = new DateTime(2020, 1, 1); var maturity2 = new DateTime(2024, 3, 1); // 4.17年 + var underlying2 = CreateTestUnderlying(code2, open2, maturity2); + var valueDate2 = maturity2.AddYears(-2).AddMonths(-2); // 剩余约2.17年 + var result2 = UnderlyingHelper.GetApplicableMarginTerm(code2, valueDate2); + Assert.AreEqual(ConsMarginTerm.TwoYear, result2, "(2,2.25]年 <5 应为2年"); + + // (2.00,2.25] >=5年,应返回5年 + var code3 = "GE5_2_25"; + var open3 = new DateTime(2020, 1, 1); var maturity3 = new DateTime(2026, 1, 1); // 6年 + var underlying3 = CreateTestUnderlying(code3, open3, maturity3); + var valueDate3_2 = maturity3.AddYears(-2).AddMonths(-1); // 剩余约2.08年 + var result3 = UnderlyingHelper.GetApplicableMarginTerm(code3, valueDate3_2); + Assert.AreEqual(ConsMarginTerm.FiveYear, result3, "(2,2.25]年 >=5 应为5年"); + + // (2.25,5.00] 不限,应返回5年 + var code4 = "UNLIMITED_5Y"; + var maturity4 = new DateTime(2030, 1, 1); + var underlying4 = CreateTestUnderlying(code4, null, maturity4); + var valueDate4 = maturity4.AddYears(-3); // 剩余3年 + var result4 = UnderlyingHelper.GetApplicableMarginTerm(code4, valueDate4); + Assert.AreEqual(ConsMarginTerm.FiveYear, result4, "(2.25,5]年 不限 应为5年"); + + // (5.00,5.25] <7年,应返回5年 + var code5 = "LT7_5_25"; + var open5 = new DateTime(2020, 1, 1); var maturity5 = new DateTime(2026, 1, 1); // 6年 + var underlying5 = CreateTestUnderlying(code5, open5, maturity5); + var valueDate5 = maturity5.AddYears(-5).AddMonths(-1); // 剩余约5.08年 + var result5 = UnderlyingHelper.GetApplicableMarginTerm(underlying5.UnderlyingCode, valueDate5); + Assert.AreEqual(ConsMarginTerm.FiveYear, result5, "(5,5.25]年 <7 应为5年"); + + // (5.00,5.25] >=7年,应返回10年 + var code6 = "GE7_5_25"; + var open6 = new DateTime(2015, 1, 1); var maturity6 = new DateTime(2023, 2, 1); // 8.08年 + var underlying6 = CreateTestUnderlying(code6, open6, maturity6); + var valueDate6 = maturity6.AddYears(-5).AddMonths(-1); // 剩余约5.08年 + var result6 = UnderlyingHelper.GetApplicableMarginTerm(code6, valueDate6); + Assert.AreEqual(ConsMarginTerm.TenYear, result6, "(5,5.25]年 >=7 应为10年"); + + // (5.25,25.00] 不限,应返回10年 + var code7 = "UNLIMITED_10Y"; + var maturity7 = new DateTime(2040, 1, 1); + var underlying7 = CreateTestUnderlying(code7, null, maturity7); + var valueDate7 = maturity7.AddYears(-10); // 剩余10年 + var result7 = UnderlyingHelper.GetApplicableMarginTerm(code7, valueDate7); + Assert.AreEqual(ConsMarginTerm.TenYear, result7, "(5.25,25]年 不限 应为10年"); + + // (25.00,30.00] 不限,应返回30年 + var code8 = "UNLIMITED_30Y"; + var maturity8 = new DateTime(2050, 1, 1); + var underlying8 = CreateTestUnderlying(code8, null, maturity8); + var valueDate8 = maturity8.AddYears(-28); // 剩余28年 + var result8 = UnderlyingHelper.GetApplicableMarginTerm(code8, valueDate8); + Assert.AreEqual(ConsMarginTerm.ThirtyYear, result8, "(25,30]年 不限 应为30年"); + + var code9 = "INVALID_TERM"; + DateTime? maturity9 = null; + var underlying9 = CreateTestUnderlying(code9, null, maturity9); + var valueDate9 = DateTime.Now.Date; + var result9 = UnderlyingHelper.GetApplicableMarginTerm(code9, valueDate9); + Assert.AreEqual(ConsMarginTerm.Default, result9, "无效的到期日 不限 应为默认"); + } + + #region 辅助方法 + + /// + /// 创建测试用的标的对象 + /// + private underlying_manager CreateTestUnderlying(string code, DateTime? openDate, DateTime? maturityDate) + { + DbContext.Database.ExecuteSqlRaw($"DELETE FROM underlying_manager WHERE UnderlyingCode = '{code}'"); + var underlying = new underlying_manager + { + UnderlyingCode = code, + OpenDate = openDate, + MaturityDate = maturityDate, + UnderlyingName = $"测试标的{code}", + MarketCode = "TEST", + MarketName = "测试市场", + OptDate = DateTime.Now, + OptId = UserId, + OptName = UserName + }; + + // 添加到数据库 + DbContext.underlying_manager.Add(underlying); + DbContext.SaveChanges(); + + // 添加到清理列表 + AddClearSQL($"DELETE FROM underlying_manager WHERE UnderlyingCode = '{code}';"); + + return underlying; + } + /// + /// 添加清理SQL + /// + private void AddClearSQL(string sql) + { + // _clearSQL 字段在 YLUnitTestBase 中定义 + // 这里假设 _clearSQL 是 protected 或 internal + if (!string.IsNullOrWhiteSpace(sql)) + { + _clearSQL.AppendLine(sql); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/UnitTestProject/appsettings.json b/UnitTestProject/appsettings.json index 08f7ba19..6f9abb0e 100644 --- a/UnitTestProject/appsettings.json +++ b/UnitTestProject/appsettings.json @@ -1,11 +1,10 @@ { "ConnectionStrings": { - "ylcms": "server=221.229.106.161;uid=roottest;pooling=true;port=20306;pwd=YieldChain!@#$2020;database=yltrs_ylcms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;", - "yladmin": "server=221.229.106.161;uid=roottest;pooling=true;port=20306;pwd=YieldChain!@#$2020;database=yltrs_admin;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;", - "ylclient": "server=221.229.106.161;uid=roottest;pooling=true;port=20306;pwd=YieldChain!@#$2020;database=yltrs_client;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;", - "bondoms": "server=221.229.106.161;uid=roottest;pooling=true;port=20306;pwd=YieldChain!@#$2020;database=bond_oms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;" - } - , + "ylcms": "server=139.196.109.225;uid=root;pooling=true;port=3306;pwd=Midnight001!@#$;database=yltrs_ylcms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;", + "yladmin": "server=139.196.109.225;uid=root;pooling=true;port=3306;pwd=Midnight001!@#$;database=yltrs_admin;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;", + "ylclient": "server=139.196.109.225;uid=root;pooling=true;port=3306;pwd=Midnight001!@#$;database=yltrs_client;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;", + "bondoms": "server=139.196.109.225;uid=root;pooling=true;port=3306;pwd=Midnight001!@#$;database=bond_oms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;" + }, "LibreOffice": { "ExePath": "", "UserInstallation": "D:\\_work\\111\\libreoffice\\user1" diff --git a/YLErpDAL/BLL/EodSettlement/ClientBalanceUtility.cs b/YLErpDAL/BLL/EodSettlement/ClientBalanceUtility.cs index fffd1532..6de2eb0c 100644 --- a/YLErpDAL/BLL/EodSettlement/ClientBalanceUtility.cs +++ b/YLErpDAL/BLL/EodSettlement/ClientBalanceUtility.cs @@ -117,8 +117,6 @@ namespace YLErp.BLL.EodSettlement var lastpushLog = db.push_log.Where(x => x.data_type == (int)SwapPushDataEnum.日终估值获取 && x.data_state == (int)SwapPushDataStateEnum.接收处理成功 && x.create_time > valuedate).OrderByDescending(o => o.create_time).FirstOrDefault(); //获取根据系统时间 var lastBalanceDate = EodOperationBase.GetLastSettlementDate(valuedate); - // 获取定义文件预付金率设置 - var clientMarginTemplates = db.client_marginrate.Where(x => x.ValueDate <= endDate).OrderByDescending(o => o.ValueDate).AsNoTracking().ToList(); var lastDate = lastBalanceDate; //查询历史记录 @@ -306,7 +304,6 @@ namespace YLErp.BLL.EodSettlement var lasttoDay = EodOperationBase.GetLastSettlementDate(lastDate, true); var lastClientBalanceDaily = db.ClientBalanceDaily.Where(a => a.BalanceDate == lasttoDay && a.ClientId == data.ClientId).FirstOrDefault(); - var marginRate = GetClientMarginRate(data.ClientId, clientMarginTemplates); balance.FrozenBalance = data.FrozenBalance; balance.FreezePremium = data.FreezePremium; balance.ReceivablesPremium = data.ReceivablesPremium; diff --git a/YLErpDAL/BLL/EodSettlement/RealTimeClientBanlanceService.cs b/YLErpDAL/BLL/EodSettlement/RealTimeClientBanlanceService.cs index 276ac848..b282eda1 100644 --- a/YLErpDAL/BLL/EodSettlement/RealTimeClientBanlanceService.cs +++ b/YLErpDAL/BLL/EodSettlement/RealTimeClientBanlanceService.cs @@ -1,4 +1,5 @@ using BaseOUDAL; +using Confluent.Kafka; using DocumentFormat.OpenXml.Drawing.Charts; using DocumentFormat.OpenXml.Office2010.Excel; using Microsoft.EntityFrameworkCore; @@ -18,6 +19,7 @@ using YLErp.Modules; using YLErp.Modules.DataProviderModule; using YLErp.Modules.EodModule; using YLErp.Modules.EodModule.QueryModule; +using YLErp.Modules.UnderlyingModule; namespace YLErp.BLL.Eod { @@ -232,7 +234,6 @@ namespace YLErp.BLL.Eod .ToDictionary(p => p.Key, p => p.OptDate); Dictionary dicCashInOut = DbContext.ClientCashInCashOut.Where(x => clientIds.Contains((int)x.ClientId)).AsEnumerable().GroupBy(p => (int)p.ClientId).Select(p => new { p.Key, OptDate = p.Max(d => d.OptDate) }).ToDictionary(p => p.Key, p => p.OptDate); - var clientMarginTemplates = DbContext.client_marginrate.Where(x => x.ValueDate <= calcDate).OrderByDescending(o => o.ValueDate).AsNoTracking().ToList(); foreach (var item in _clientBalanceDic.Values) { #region 判断实时持仓数据是否最新(包含了最新交易操作的数据) @@ -266,9 +267,6 @@ namespace YLErp.BLL.Eod item.PositionNotionalPrincipal += stockEqvNotionalDict.ContainsKey(item.ClientId) ? stockEqvNotionalDict[item.ClientId] : 0; //可用名义本金规模 item.AvailableStockEqvNotional = item.TotalCreditStockEqvNotional - item.PositionNotionalPrincipal; - - - var marginRate = GetClientMarginRate(item.ClientId, clientMarginTemplates); //预付金金额=期末结存-初始预付金金额 item.MarginBalance = item.AmountFund - item.MySideMargin; // 可用资金 = 期末结存 - 追保账户余额 - 初始保证金 @@ -1378,24 +1376,13 @@ namespace YLErp.BLL.Eod foreach (var itemGroup in flowGroup) { var balance = clientBalanceDic[itemGroup.Key ?? 0]; - //string marginType = balance.ClientType == 1 ? "多空组合" : "品种"; - // 获取客户预付金比例设置 - var clientMarginTemplate = clientMarginTemplates.FirstOrDefault(x => x.ClientId == itemGroup.Key ); - if (clientMarginTemplate == null) - { - clientMarginTemplate = clientMarginTemplates.FirstOrDefault(x => x.ClientId == 0 ); - } - // 没有设置预付金比例则跳过 - if (clientMarginTemplate == null) - { - continue; - } var clientPositions = positions.Where(x => x.ClientId == itemGroup.Key).ToList(); foreach (var item in itemGroup.GroupBy(s => s.UnderlyingCode)) { + var clientMarginDetail= UnderlyingHelper.GetApplicableMarginRate(itemGroup.Key ?? 0, item.Key, startDate); // 获取当前客户当前标的持仓数据,合并后的名义本金数量 var positionLsit = clientPositions.Where(x => x.UnderlyingCode == item.Key).ToList(); - decimal money = CalcDmaMoney(positionLsit, item.ToList(), clientMarginTemplate); + decimal money = CalcDmaMoney(positionLsit, item.ToList(), clientMarginDetail); balance.FrozenMarginMoney += Convert.ToDouble(money); } } @@ -1407,10 +1394,10 @@ namespace YLErp.BLL.Eod /// 流水列表 /// 客户保证金模板 /// 资金变化金额 - public decimal CalcDmaMoney(List positions, List flows, client_marginrate clientMarginTemplate) + public decimal CalcDmaMoney(List positions, List flows, client_margin_detail clientMarginTemplate) { decimal money = 0; - var marginRate = (decimal)clientMarginTemplate.InitMarginRate; + var marginRate = clientMarginTemplate?.init_rate??0; // 处理持仓与流水的平仓逻辑 foreach (var position in positions.ToList()) // 使用 ToList() 避免修改集合时的问题 diff --git a/YLErpDAL/BLL/EodSettlement/RealtimePnlCalc.cs b/YLErpDAL/BLL/EodSettlement/RealtimePnlCalc.cs index c84ec8a8..eb7fbb19 100644 --- a/YLErpDAL/BLL/EodSettlement/RealtimePnlCalc.cs +++ b/YLErpDAL/BLL/EodSettlement/RealtimePnlCalc.cs @@ -744,7 +744,7 @@ namespace YLErp.BLL.Eod if (resp!=null) { clientPosition.deal_yield_avg = resp.ytm* ConsGlobal.bondPriceMultiple; - _yLCache.StringSetWithNoPrefix("TRS-BondFullPrice:" + clientPosition.security_id, resp); + _yLCache.StringSetWithNoPrefix("TRS-BondFullPrice:" + clientPosition.security_id, resp,TimeSpan.FromHours(1)); } } /// diff --git a/YLErpDAL/DataBase/YLContext.cs b/YLErpDAL/DataBase/YLContext.cs index e8385372..5db0581d 100644 --- a/YLErpDAL/DataBase/YLContext.cs +++ b/YLErpDAL/DataBase/YLContext.cs @@ -402,5 +402,8 @@ namespace YLErp.BLL public DbSet trs_account_manage_detail { get; set; } public DbSet clientBalanceView { get; set; } + + public DbSet clientMarginConfig { get; set; } + public DbSet clientMarginDetail { get; set; } } } \ No newline at end of file diff --git a/YLErpDAL/Model/ClientMarginConfigReq.cs b/YLErpDAL/Model/ClientMarginConfigReq.cs new file mode 100644 index 00000000..eb67f091 --- /dev/null +++ b/YLErpDAL/Model/ClientMarginConfigReq.cs @@ -0,0 +1,15 @@ +using BaseOUDAL; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace YLErp.Model +{ + public class ClientMarginConfigReq + { + public int ClientId { get; set; } + public DateTime? ValueDate { get; set; } + } +} diff --git a/YLErpDAL/Model/ClientMarginDetailDto.cs b/YLErpDAL/Model/ClientMarginDetailDto.cs new file mode 100644 index 00000000..5c079852 --- /dev/null +++ b/YLErpDAL/Model/ClientMarginDetailDto.cs @@ -0,0 +1,77 @@ +using System.ComponentModel.DataAnnotations; + +namespace YLErp.Model +{ + /// + /// 客户保证金配置详情DTO - 用于前端数据传输 + /// + public class ClientMarginDetailDto + { + /// + /// 利率债期限 + /// + public string bond_term { get; set; } + + /// + /// 初始保证金率(%) - 可空 + /// + public decimal? init_rate { get; set; } + + /// + /// 维持保证金率(%) - 可空 + /// + public decimal? maintain_rate { get; set; } + + /// + /// 互换默认天数 - 可空 + /// + public int? swap_days { get; set; } + } + + /// + /// 客户保证金配置DTO - 用于前端数据传输 + /// + public class ClientMarginConfigDto + { + public string EncryptId { get; set; } + /// + /// 配置ID + /// + public int id { get; set; } + + /// + /// 客户ID + /// + public int client_id { get; set; } + + /// + /// 客户名称 + /// + public string client_name { get; set; } + + /// + /// 生效日期 + /// + public DateTime value_date { get; set; } + + /// + /// 操作人ID + /// + public int? OptId { get; set; } + + /// + /// 操作人姓名 + /// + public string OptName { get; set; } + + /// + /// 操作时间 + /// + public DateTime? OptDate { get; set; } + + /// + /// 配置详情列表 + /// + public List details { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/YLErpDAL/Model/GetClientMarginDetailReq.cs b/YLErpDAL/Model/GetClientMarginDetailReq.cs new file mode 100644 index 00000000..840fa358 --- /dev/null +++ b/YLErpDAL/Model/GetClientMarginDetailReq.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace YLErp.Model +{ + public class GetClientMarginDetailReq + { + public int clientId { get; set; } + public string underlyingCode { get; set; } + public DateTime valueDate { get; set;} + } +} diff --git a/YLErpDAL/Modules/MarginModule/ClientMarginConfigService.cs b/YLErpDAL/Modules/MarginModule/ClientMarginConfigService.cs new file mode 100644 index 00000000..b1c4efe1 --- /dev/null +++ b/YLErpDAL/Modules/MarginModule/ClientMarginConfigService.cs @@ -0,0 +1,154 @@ +using System.Data; +using YLErp.Model; + +namespace YLErp.Modules.MarginModule +{ + public class ClientMarginConfigService : YLBaseService + { + public ClientMarginConfigService(OptUserInfo userInfo) : base(userInfo) + { + } + + public void SaveMarginConfig(client_margin_config marginConfig) + { + if (marginConfig == null) + { + throw new ServiceException("无数据"); + } + + var dbMarginConfig = new client_margin_config(); + dbMarginConfig.SetOpt(UserInfo); + if (marginConfig.id != 0) + { + dbMarginConfig = DbContext.clientMarginConfig.Find(marginConfig.id); + if (dbMarginConfig == null) + { + throw new ServiceException("未找到该记录"); + } + dbMarginConfig.client_id = marginConfig.client_id; + dbMarginConfig.value_date = marginConfig.value_date; + + if (DbContext.clientMarginConfig.Any(o => o.client_id == marginConfig.client_id + && o.value_date == marginConfig.value_date + && o.id != marginConfig.id)) + { + throw new ServiceException("同一客户不支持在同一生效日有多条记录!"); + } + } + else + { + dbMarginConfig = DbContext.clientMarginConfig.Where(o => o.client_id == marginConfig.client_id + && o.value_date == marginConfig.value_date).FirstOrDefault(); + if (dbMarginConfig == null) + { + marginConfig.SetOpt(UserInfo); + DbContext.clientMarginConfig.Add(marginConfig); + } + else + { + throw new ServiceException("同一客户不支持在同一生效日有多条记录"); + } + } + + DbContext.SaveChanges(); + } + + public List SearchList(ClientMarginConfigReq req) + { + var query = from config in DbContext.clientMarginConfig + select new ClientMarginConfigDto + { + EncryptId=config.EncryptId, + id = config.id, + client_id = config.client_id, + client_name = config.client_name, + value_date = config.value_date, + OptId = config.OptId, + OptName = config.OptName, + OptDate = config.OptDate + }; + + if (req.ClientId > 0) + { + query = query.Where(o => o.client_id == req.ClientId); + } + + if (req.ValueDate.HasValue) + { + query = query.Where(o => o.value_date == req.ValueDate.Value); + } + + var configs = query.OrderByDescending(o=>o.id).ToList(); + + // 为每个配置加载5条固定期限的详情数据 + var fixedTerms = ConsMarginTerm.AllowedTerms.ToArray(); + foreach (var config in configs) + { + var existingDetails = DbContext.clientMarginDetail.Where(d => d.config_id == config.id).ToList(); + var detailDtos = new List(); + + for (int i = 0; i < fixedTerms.Length; i++) + { + var term = fixedTerms[i]; + var existingDetail = existingDetails.FirstOrDefault(d => d.bond_term == term); + if (existingDetail != null) + { + detailDtos.Add(new ClientMarginDetailDto + { + bond_term = existingDetail.bond_term, + init_rate = existingDetail.init_rate * 100, // 转换为百分比显示 + maintain_rate = existingDetail.maintain_rate * 100, // 转换为百分比显示 + swap_days = existingDetail.swap_days + }); + } + else + { + detailDtos.Add(new ClientMarginDetailDto + { + bond_term = term, + init_rate = null, + maintain_rate = null, + swap_days = null + }); + } + } + + // 直接使用DTO格式返回给前端 + config.details = detailDtos; + } + + return configs; + } + + public void SaveMarginConfigWithDetails(client_margin_config config, List details) + { + using (var trans = BeginTransaction()) + { + try + { + // 保存主表 + SaveMarginConfig(config); + + // 删除原有详情记录 + var existingDetails = DbContext.clientMarginDetail.Where(d => d.config_id == config.id).ToList(); + DbContext.clientMarginDetail.RemoveRange(existingDetails); + + // 保存新的详情记录 + foreach (var detail in details) + { + detail.config_id = config.id; + DbContext.clientMarginDetail.Add(detail); + } + + DbContext.SaveChanges(); + trans.Commit(); + } + catch + { + trans.Rollback(); + throw; + } + } + } + } +} \ No newline at end of file diff --git a/YLErpDAL/Modules/SwapModule/SwapTradeAutoService.cs b/YLErpDAL/Modules/SwapModule/SwapTradeAutoService.cs index 75b9f2ff..ad24400f 100644 --- a/YLErpDAL/Modules/SwapModule/SwapTradeAutoService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapTradeAutoService.cs @@ -437,7 +437,6 @@ namespace YLErp.Modules.SwapModule && !ConsTrade.TradeCompleteStatus.Contains(t.TradeStatus)).ToList(); var swapTradeIds = swaptrades.Select(s => s.id); var tradeExtends = DbContext.trade_extend.Where(x => swapTradeIds.Contains(x.TradeId)); - var clientMarginTemplates = DbContext.client_marginrate.Where(x => x.ValueDate <= valueDate).OrderByDescending(o => o.ValueDate).ToList(); var restSwapTrades = new List(); foreach (var swaptrade in swaptrades) { @@ -454,7 +453,7 @@ namespace YLErp.Modules.SwapModule int dealCount = 0; foreach (var groupItem in flowquery) { - MergeRestModelItem(groupItem, restSwapTrades, swapPositions, floatRateQuery, clientMarginTemplates, ref dealCount, action); + MergeRestModelItem(groupItem, restSwapTrades, swapPositions, floatRateQuery, ref dealCount, action); } } /// @@ -476,14 +475,13 @@ namespace YLErp.Modules.SwapModule && !ConsTrade.TradeCompleteStatus.Contains(t.TradeStatus)).ToList(); var swapTradeIds = swaptrades.Select(s => s.id); var swapPositions = DbContext.swap_position.Where(x => swapTradeIds.Contains(x.SwapTradeId) && x.PosiDirection > 0 && !x.IsInitial && x.PosiQuantity > 0 && !x.Invalid).ToList(); - var clientMarginTemplates = DbContext.client_marginrate.Where(x => x.ValueDate <= valueDate).OrderByDescending(o => o.ValueDate).ToList(); var matuirityDate = QdpCalendarHelper.GetNonHolidayDefore(valueDate.AddDays(14)); var floatRatePredicate = PredicateBuilder.Create(x => x.StartDate <= valueDate && x.EndDate >= matuirityDate); var floatRateQuery = DbContext.swap_float_rate.Where(floatRatePredicate); int dealCount = 0; foreach (var groupItem in flowquery) { - MergeAvgModelItem(groupItem, swaptrades, swapPositions, floatRateQuery, clientMarginTemplates, ref dealCount, action); + MergeAvgModelItem(groupItem, swaptrades, swapPositions, floatRateQuery, ref dealCount, action); } } public void UpdateSwapFlowState(List swapFlows) @@ -507,8 +505,7 @@ namespace YLErp.Modules.SwapModule private void MergeRestModelItem(IGrouping groupItem, List swaptrades, List swapPositions, - IQueryable floatRateQuery, - List client_Marginrates, ref int dealCount, Action? action) + IQueryable floatRateQuery, ref int dealCount, Action? action) { var clientId = groupItem.Key; var client = DataCacheProvider.GetClientDataSource().GetData(clientId ?? 0); @@ -535,11 +532,6 @@ namespace YLErp.Modules.SwapModule { throw new ServiceException($"{etradeRule.AssetAccount_0}的簿记账户未设置交易员"); } - var clientMarginTemplate = client_Marginrates.FirstOrDefault(x => x.ClientId == clientId); - if (clientMarginTemplate == null) - { - clientMarginTemplate = client_Marginrates.FirstOrDefault(x => x.ClientId == 0); - } var underlyingGroup = groupItem.GroupBy(g => g.UnderlyingCode); var clientSwapTrades = swaptrades.Where(x => x.ClientId == clientId).ToList(); var clientSwapTradeIds = clientSwapTrades.Select(s => s.id); @@ -583,11 +575,11 @@ namespace YLErp.Modules.SwapModule } if (!hasPayPosition)//没有持仓 { - DealNoPosition(mergeList, client, asset, underlying, floatRate, clientMarginTemplate, clearingAgency, cashNeedAfter); + DealNoPosition(mergeList, client, asset, underlying, floatRate, clearingAgency, cashNeedAfter); } else { - DealHasPosition(mergeList, client, asset, underlying, floatRate, clientSwapPositionList, clientSwapTrades, clientMarginTemplate, clearingAgency, cashNeedAfter); + DealHasPosition(mergeList, client, asset, underlying, floatRate, clientSwapPositionList, clientSwapTrades,clearingAgency, cashNeedAfter); } } @@ -607,8 +599,7 @@ namespace YLErp.Modules.SwapModule private void MergeAvgModelItem(IGrouping groupItem, List swaptrades, List swapPositions, - IQueryable floatRateQuery, - List client_Marginrates, ref int dealCount, Action? action) + IQueryable floatRateQuery,ref int dealCount, Action? action) { var clientId = groupItem.Key; var client = DataCacheProvider.GetClientDataSource().GetData(clientId ?? 0); @@ -619,11 +610,6 @@ namespace YLErp.Modules.SwapModule var clientSwapTrades = swaptrades.Where(x => x.ClientId == clientId).ToList(); var clientSwapTradeIds = clientSwapTrades.Select(s => s.id); var clientSwapPositions = swapPositions.Where(x => clientSwapTradeIds.Contains(x.SwapTradeId));//现有客户持仓 - var clientMarginTemplate = client_Marginrates.FirstOrDefault(x => x.ClientId == clientId); - if (clientMarginTemplate == null) - { - clientMarginTemplate = client_Marginrates.FirstOrDefault(x => x.ClientId == 0); - } var underlyingCodes = underlyingGroup.Select(s => s.Key).ToList(); var underlyings = DataCacheProvider.GetUnderlyingDataSource().AsQueryable(x => underlyingCodes.Contains(x.UnderlyingCode)); foreach (var underlyingGroupItem in underlyingGroup) @@ -643,11 +629,11 @@ namespace YLErp.Modules.SwapModule if (!hasPayPosition)//没有持仓 { var bsType = mergeList.OrderBy(o => o.OptTime).First().BsType; - AvgDealNoPosition(mergeList, client, asset, underlying, floatRate, clientMarginTemplate, clearingAgency, bsType); + AvgDealNoPosition(mergeList, client, asset, underlying, floatRate, clearingAgency, bsType); } else { - AvgDealHasPosition(mergeList, client, asset, underlying, floatRate, clientSwapPositionList, clientSwapTrades, clientMarginTemplate, clearingAgency); + AvgDealHasPosition(mergeList, client, asset, underlying, floatRate, clientSwapPositionList, clientSwapTrades, clearingAgency); } } @@ -667,7 +653,6 @@ namespace YLErp.Modules.SwapModule AssetUnit asset, underlying_manager underlying, SwapFloatRate floatRate, - client_marginrate clientMarginTemplate, string clearingAgency, bool cashNeedAfter) { @@ -675,7 +660,7 @@ namespace YLErp.Modules.SwapModule swap_flow_merge flowMergeMax = mergeOrderList.First();//先开最早的一条 swap_flow_merge flowMergeMin = mergeOrderList.Last(); var swapTradeService = new SwapTradeService(UserInfo); - var trade = swapTradeService.NewSwapTrade(flowMergeMax, client, asset, underlying, floatRate, clientMarginTemplate, clearingAgency, cashNeedAfter: cashNeedAfter); + var trade = swapTradeService.NewSwapTrade(flowMergeMax, client, asset, underlying, floatRate, clearingAgency, cashNeedAfter: cashNeedAfter); flowMergeMax.SwapTradeNo = trade.TradeNumber; flowMergeMin.SwapTradeNo = trade.TradeNumber; if (mergeList.Count == 2)//有两条流水 @@ -707,7 +692,7 @@ namespace YLErp.Modules.SwapModule var posi = DbContext.swap_position.FirstOrDefault(x => x.SwapTradeId == trade.id && x.PosiDirection > 0 && !x.IsInitial); SetNewOpenData(flowMergeMin, flowMergeClone, posi); } - var trade2 = swapTradeService.NewSwapTrade(flowMergeClone, client, asset, underlying, floatRate, clientMarginTemplate, clearingAgency); + var trade2 = swapTradeService.NewSwapTrade(flowMergeClone, client, asset, underlying, floatRate, clearingAgency); flowMergeMax.SwapTradeNo = trade2.TradeNumber; flowMergeMin.SwapTradeNo = trade2.TradeNumber; } @@ -728,7 +713,6 @@ namespace YLErp.Modules.SwapModule AssetUnit asset, underlying_manager underlying, SwapFloatRate floatRate, - client_marginrate clientMarginTemplate, string clearingAgency, int byType) { @@ -736,9 +720,9 @@ namespace YLErp.Modules.SwapModule var sameFlow = swapFlows.Where(x => x.BsType == byType).FirstOrDefault(); if (negativeFlow==null) { - return NewSwapTrade(sameFlow, client, asset, underlying, floatRate, clientMarginTemplate,clearingAgency); + return NewSwapTrade(sameFlow, client, asset, underlying, floatRate,clearingAgency); } - return DealTwoDirectionFlows(sameFlow, negativeFlow, client, asset, underlying, floatRate, clientMarginTemplate, clearingAgency); + return DealTwoDirectionFlows(sameFlow, negativeFlow, client, asset, underlying, floatRate, clearingAgency); } /// /// 当前无持仓,且有2个方向流水合成簿记 @@ -758,7 +742,6 @@ namespace YLErp.Modules.SwapModule AssetUnit asset, underlying_manager underlying, SwapFloatRate floatRate, - client_marginrate clientMarginTemplate, string clearingAgency) { var sameQty = sameFlow.TradingQty; @@ -771,7 +754,7 @@ namespace YLErp.Modules.SwapModule sameFlowClone= DataHelper.DeepCopyObject(negativeFlow); negaFlowClone = DataHelper.DeepCopyObject(sameFlow); } - var trade = NewSwapTrade(sameFlowClone, client, asset, underlying, floatRate, clientMarginTemplate, clearingAgency); + var trade = NewSwapTrade(sameFlowClone, client, asset, underlying, floatRate, clearingAgency); // 平仓 new SwapDealService(UserInfo).AuotoSwapUnwind(trade.id, negaFlowClone.TradingAmountAvg, @@ -799,11 +782,10 @@ namespace YLErp.Modules.SwapModule AssetUnit asset, underlying_manager underlying, SwapFloatRate floatRate, - client_marginrate clientMarginTemplate, string clearingAgency) { var swapTradeService = new SwapTradeService(UserInfo); - var trade = swapTradeService.NewSwapTrade(flowMergeFirst, client, asset, underlying, floatRate, clientMarginTemplate, clearingAgency, LongShortStructType); + var trade = swapTradeService.NewSwapTrade(flowMergeFirst, client, asset, underlying, floatRate, clearingAgency, LongShortStructType); flowMergeFirst.SwapTradeNo = trade.TradeNumber; flowMergeFirst.SwapTradeId = trade.id; DbContext.SaveChanges(); @@ -889,17 +871,16 @@ namespace YLErp.Modules.SwapModule SwapFloatRate floatRate, List clientSwapPositionList, List clientSwapTrades, - client_marginrate clientMarginTemplate, string clearingAgency, bool cashNeedAfter) { if (mergeList.Count == 1)//只有一条流水情况 { - DealSingleFlow(mergeList, clientSwapPositionList, clientSwapTrades, client, asset, underlying, floatRate, clientMarginTemplate, clearingAgency); + DealSingleFlow(mergeList, clientSwapPositionList, clientSwapTrades, client, asset, underlying, floatRate,clearingAgency); } else { - DealDoubleFlow(mergeList, clientSwapPositionList, clientSwapTrades, client, asset, underlying, floatRate, clientMarginTemplate, clearingAgency, cashNeedAfter); + DealDoubleFlow(mergeList, clientSwapPositionList, clientSwapTrades, client, asset, underlying, floatRate, clearingAgency, cashNeedAfter); } } /// @@ -920,17 +901,16 @@ namespace YLErp.Modules.SwapModule SwapFloatRate floatRate, List clientSwapPositionList, List clientSwapTrades, - client_marginrate clientMarginTemplate, string clearingAgency) { var firstFlow = flowList.First(); if (flowList.Count==1)//只有一条流水情况 { - AvgDealSingleFlow(firstFlow, clientSwapPositionList, clientSwapTrades, client, asset, underlying, floatRate, clientMarginTemplate, clearingAgency); + AvgDealSingleFlow(firstFlow, clientSwapPositionList, clientSwapTrades, client, asset, underlying, floatRate, clearingAgency); } else { - AvgDealDoubleFlow(flowList, clientSwapPositionList, clientSwapTrades, client, asset, underlying, floatRate, clientMarginTemplate,clearingAgency); + AvgDealDoubleFlow(flowList, clientSwapPositionList, clientSwapTrades, client, asset, underlying, floatRate,clearingAgency); } } /// @@ -951,7 +931,6 @@ namespace YLErp.Modules.SwapModule AssetUnit asset, underlying_manager underlying, SwapFloatRate floatRate, - client_marginrate clientMarginTemplate, string clearingAgency) { var swapTradeService = new SwapTradeService(UserInfo); @@ -978,13 +957,13 @@ namespace YLErp.Modules.SwapModule { SetNewOpenData(flowMergeMax, flowMergeClone, dealResult.Item4); } - var trade = swapTradeService.NewSwapTrade(flowMergeClone, client, asset, underlying, floatRate, clientMarginTemplate, clearingAgency); + var trade = swapTradeService.NewSwapTrade(flowMergeClone, client, asset, underlying, floatRate, clearingAgency); flowMergeMax.SwapTradeNo = trade.TradeNumber; } } else //只存在同向交易 { - var trade = swapTradeService.NewSwapTrade(flowMergeMax, client, asset, underlying, floatRate, clientMarginTemplate, clearingAgency); + var trade = swapTradeService.NewSwapTrade(flowMergeMax, client, asset, underlying, floatRate, clearingAgency); flowMergeMax.SwapTradeNo = trade.TradeNumber; } } @@ -1027,7 +1006,6 @@ namespace YLErp.Modules.SwapModule AssetUnit asset, underlying_manager underlying, SwapFloatRate floatRate, - client_marginrate clientMarginTemplate, string clearingAgency, bool cashNeedAfter) { @@ -1049,22 +1027,22 @@ namespace YLErp.Modules.SwapModule var negTrades = clientSwapTrades.Where(x => negTradeIds.Contains(x.id)).ToList();//取出与第一条流水方向相反的交易 //先处理第一条流水的反向持仓 - var firstTrade = DealDoubleFlowDetial(negTrades, negDirectionPositions, flowMergeFirstClone, client, asset, underlying, floatRate, clientMarginTemplate, clearingAgency, true, cashNeedAfter); + var firstTrade = DealDoubleFlowDetial(negTrades, negDirectionPositions, flowMergeFirstClone, client, asset, underlying, floatRate, clearingAgency, true, cashNeedAfter); flowMergeFirst.SwapTradeNo = flowMergeFirstClone.SwapTradeNo; //再处理第二条流水的反向持仓 - var lastTrade = DealDoubleFlowDetial(sameTrades, sameDirectionPositions, flowMergeLastClone, client, asset, underlying, floatRate, clientMarginTemplate, clearingAgency, false,false); + var lastTrade = DealDoubleFlowDetial(sameTrades, sameDirectionPositions, flowMergeLastClone, client, asset, underlying, floatRate, clearingAgency, false,false); flowMergeLast.SwapTradeNo = flowMergeLastClone.SwapTradeNo; if (flowMergeFirstClone.BsType != flowMergeLastClone.BsType && firstTrade != null) { var trades = new List { firstTrade }; var positions = DbContext.swap_position.Where(x => x.SwapTradeId == firstTrade.id && x.PosiDirection > 0 && !x.IsInitial && !x.Invalid).ToList(); - DealDoubleFlowDetial(trades, positions, flowMergeLastClone, client, asset, underlying, floatRate, clientMarginTemplate, clearingAgency, true,false); + DealDoubleFlowDetial(trades, positions, flowMergeLastClone, client, asset, underlying, floatRate, clearingAgency, true,false); flowMergeLast.SwapTradeNo = flowMergeLastClone.SwapTradeNo; } else if (lastTrade==null) { - swapTradeService.NewSwapTrade(flowMergeLastClone, client, asset, underlying, floatRate, clientMarginTemplate, clearingAgency); + swapTradeService.NewSwapTrade(flowMergeLastClone, client, asset, underlying, floatRate, clearingAgency); } } /// @@ -1085,7 +1063,6 @@ namespace YLErp.Modules.SwapModule AssetUnit asset, underlying_manager underlying, SwapFloatRate floatRate, - client_marginrate clientMarginTemplate, string clearingAgency) { var swapTradeService = new SwapTradeService(UserInfo); @@ -1097,11 +1074,11 @@ namespace YLErp.Modules.SwapModule // 同向新开 if (flow.BsType== firstPosi.PositionType) { - NewSwapTrade(flowClone, client, asset, underlying, floatRate, clientMarginTemplate, clearingAgency); + NewSwapTrade(flowClone, client, asset, underlying, floatRate, clearingAgency); } else //反向先平仓,有剩余开仓 { - AvgDealUnwind(flowClone, clientSwapTrades, swapPositions, client, asset, underlying, floatRate, clientMarginTemplate, clearingAgency); + AvgDealUnwind(flowClone, clientSwapTrades, swapPositions, client, asset, underlying, floatRate, clearingAgency); } } /// @@ -1122,7 +1099,6 @@ namespace YLErp.Modules.SwapModule AssetUnit asset, underlying_manager underlying, SwapFloatRate floatRate, - client_marginrate clientMarginTemplate, string clearingAgency) { var swapTradeService = new SwapTradeService(UserInfo); @@ -1137,7 +1113,7 @@ namespace YLErp.Modules.SwapModule var flowSameClone = DataHelper.DeepCopyObject(flowSame); var flowNegClone = DataHelper.DeepCopyObject(flowNeg); //先平反向 - var trade= AvgDealUnwind(flowNegClone, clientSwapTrades, swapPositions, client, asset, underlying, floatRate, clientMarginTemplate, clearingAgency); + var trade= AvgDealUnwind(flowNegClone, clientSwapTrades, swapPositions, client, asset, underlying, floatRate, clearingAgency); var newFlowList = new List(); if (trade!=null) { @@ -1162,12 +1138,12 @@ namespace YLErp.Modules.SwapModule { flowSameClone.TradingQty = flowQty; flowSameClone.TradingAmount = flowSameClone.TradingQty; - NewSwapTrade(flowSameClone, client, asset, underlying, floatRate, clientMarginTemplate, clearingAgency); + NewSwapTrade(flowSameClone, client, asset, underlying, floatRate, clearingAgency); } } else { - NewSwapTrade(flowSameClone, client, asset, underlying, floatRate, clientMarginTemplate, clearingAgency); + NewSwapTrade(flowSameClone, client, asset, underlying, floatRate, clearingAgency); } } /// @@ -1190,7 +1166,6 @@ namespace YLErp.Modules.SwapModule AssetUnit asset, underlying_manager underlying, SwapFloatRate floatRate, - client_marginrate clientMarginTemplate, string clearingAgency) { var swapTradeService = new SwapTradeService(UserInfo); @@ -1227,7 +1202,7 @@ namespace YLErp.Modules.SwapModule { swapFlow.TradingQty = flowQty; swapFlow.TradingAmount = swapFlow.TradingQty* swapFlow.TradingAmountAvg; - return NewSwapTrade(swapFlow, client, asset, underlying, floatRate, clientMarginTemplate,clearingAgency); + return NewSwapTrade(swapFlow, client, asset, underlying, floatRate, clearingAgency); } return null; } @@ -1251,7 +1226,6 @@ namespace YLErp.Modules.SwapModule AssetUnit asset, underlying_manager underlying, SwapFloatRate floatRate, - client_marginrate clientMarginTemplate, string clearingAgency, bool needOpen, bool cashNeedAfter @@ -1269,7 +1243,7 @@ namespace YLErp.Modules.SwapModule { SetNewOpenData(flowMergeMax, flowMergeSameClone, dealResult.Item4); } - return swapTradeService.NewSwapTrade(flowMergeSameClone, client, asset, underlying, floatRate, clientMarginTemplate, clearingAgency); + return swapTradeService.NewSwapTrade(flowMergeSameClone, client, asset, underlying, floatRate, clearingAgency); } return null; diff --git a/YLErpDAL/Modules/SwapModule/SwapTradeService.cs b/YLErpDAL/Modules/SwapModule/SwapTradeService.cs index 771c0dd8..05acac63 100644 --- a/YLErpDAL/Modules/SwapModule/SwapTradeService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapTradeService.cs @@ -247,12 +247,12 @@ namespace YLErp.Modules.SwapModule AssetUnit asset, underlying_manager underlying, SwapFloatRate swapFloatRate, - client_marginrate clientMarginTemplate, string clearingAgency, string structureType = "普通债券类收益互换", bool cashNeedAfter = false) { - var td = PrepareTrade(flowMerge, client, asset, underlying, clientMarginTemplate?.SwapEndDays??14, structureType); + int SwapEndDays = UnderlyingHelper.GetApplicableMarginRate(client.id,underlying.UnderlyingCode,flowMerge.OccurTime)?.swap_days??14; + var td = PrepareTrade(flowMerge, client, asset, underlying, SwapEndDays, structureType); PrepareTradeExtend(flowMerge, td, underlying, swapFloatRate); td.TradeNumber = BizLogicSingleton.Instance.GenerateTradeNumberBeforeConfirm(td, DbContext); flowMerge.SwapTradeNo = td.TradeNumber; diff --git a/YLErpDAL/Modules/TradeModule/DealModule/TradeConfirmService.cs b/YLErpDAL/Modules/TradeModule/DealModule/TradeConfirmService.cs index 227ed0fe..f6f74962 100644 --- a/YLErpDAL/Modules/TradeModule/DealModule/TradeConfirmService.cs +++ b/YLErpDAL/Modules/TradeModule/DealModule/TradeConfirmService.cs @@ -6,12 +6,14 @@ using System.Text; using YieldChain.Helpers; using YLErp.BLL; using YLErp.BLL.Eod; +using YLErp.DBModels; using YLErp.DBModels.Enums; using YLErp.Model.Enum; using YLErp.Modules.RiskModule; using YLErp.Modules.SalesModule; using YLErp.Modules.SwapModule; using YLErp.Modules.SystemModule; +using YLErp.Modules.UnderlyingModule; namespace YLErp.Modules.TradeModule.DealModule { @@ -391,18 +393,12 @@ namespace YLErp.Modules.TradeModule.DealModule /// private void InitTradeReport(trade td,int marginDiretion) { - var clientMarginTemplates = DbContext.client_marginrate.Where(x => x.ValueDate <= td.TradeDate).OrderByDescending(o => o.ValueDate).AsNoTracking().ToList(); - var marinRate = clientMarginTemplates.Where(x => x.ClientId == td.ClientId).FirstOrDefault(); + var marinRate= UnderlyingHelper.GetApplicableMarginRate(td.ClientId, td.UnderlyingCode, td.TradeDate.Value); if (marinRate == null) { - marinRate = clientMarginTemplates.Where(x => x.ClientId == 0).FirstOrDefault(); - } - if (marinRate == null) - { - marinRate = new client_marginrate - { - InitMarginRate = 1, - MaintenanceRate = 1, + marinRate = new client_margin_detail() { + init_rate = 1, + maintain_rate = 1 }; } var trade_Report = DbContext.trade_Report.Where(x => x.TradeId == td.id).FirstOrDefault(); @@ -420,8 +416,8 @@ namespace YLErp.Modules.TradeModule.DealModule trade_Report.IsPerformanceCollInterest = "否"; trade_Report.PerformanceCollType = "0"; trade_Report.PerformanceCollRange = "0"; - trade_Report.InitialPerformanceColl = (decimal)marinRate.InitMarginRate*100; - trade_Report.RecoveryPerformanceColl = (decimal)marinRate.MaintenanceRate * 100; + trade_Report.InitialPerformanceColl = (decimal)marinRate.init_rate*100; + trade_Report.RecoveryPerformanceColl = (decimal)marinRate.maintain_rate * 100; trade_Report.UnwindPerformanceColl = trade_Report.RecoveryPerformanceColl; trade_Report.EodPerformanceColl = trade_Report.RecoveryPerformanceColl; trade_Report.PerformanceRemark = "详见交易确认书及定义性文件"; diff --git a/YLErpDAL/Modules/UnderlyingModule/UnderlyingHelper.cs b/YLErpDAL/Modules/UnderlyingModule/UnderlyingHelper.cs index f238836e..17936953 100644 --- a/YLErpDAL/Modules/UnderlyingModule/UnderlyingHelper.cs +++ b/YLErpDAL/Modules/UnderlyingModule/UnderlyingHelper.cs @@ -1,4 +1,11 @@ -using System.Text; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Linq; +using System.Text; +using YieldChain.Commons; +using YLErp.Cache; +using YLErp.Commons; +using YLErp.DBModels; namespace YLErp.Modules.UnderlyingModule { @@ -7,6 +14,11 @@ namespace YLErp.Modules.UnderlyingModule /// public static class UnderlyingHelper { + readonly static IYLCache ylCache; + static UnderlyingHelper() + { + ylCache = YLServiceLocator.ServiceProvider.GetService(); + } /// /// 获取组合标的品种对象 /// @@ -59,6 +71,115 @@ namespace YLErp.Modules.UnderlyingModule } } } + /// + /// 根据客户ID、标的代码和计算日期计算适用的保证金率 + /// + /// 客户ID + /// 标的代码 + /// 计算日期 + /// 适用的保证金率配置,包含初始保证金率和维持保证金率 + public static client_margin_detail GetApplicableMarginRate(int clientId, string underlyingCode, DateTime valueDate) + { + if (string.IsNullOrEmpty(underlyingCode)) + return null; + + string cacheKey = $"Otc:ClientMarginRate:{clientId}:{underlyingCode}:{valueDate:yyyyMMdd}"; + try + { + if (ylCache!=null) + { + // 1. 先查Redis + var cacheDetail = ylCache.StringGetWithNoPrefix(cacheKey); + if (cacheDetail != null) + return cacheDetail; + } + + + // 2. 查数据库 + string applicableTerm = GetApplicableMarginTerm(underlyingCode, valueDate); + var marginConfig = GetClientMarginConfig(clientId, valueDate); + if (marginConfig == null) + return null; + var marginDetail = GetMarginDetailByTerm(marginConfig.id, applicableTerm); + + // 3. 放入Redis + if (marginDetail != null&& ylCache != null) + { + ylCache.StringSetWithNoPrefix(cacheKey, marginDetail,TimeSpan.FromHours(3)); + } + + return marginDetail; + } + catch (Exception ex) + { + LogFactory.GetLogger("UnderlyingHelper").Error($"计算保证金率时发生异常: {ex.Message}", ex); + return null; + } + } + + /// + /// 获取客户保证金配置 + /// + /// 客户ID + /// 估值日期 + /// 客户保证金配置 + private static client_margin_config GetClientMarginConfig(int clientId, DateTime valueDate) + { + try + { + using (var context = DbContextFactory.GetYLDbContext()) + { + // 查找指定日期有效的客户保证金配置 + var config = context.clientMarginConfig + .Where(c => c.client_id == clientId && c.value_date <= valueDate) + .OrderByDescending(c => c.value_date) + .FirstOrDefault(); + // 找不到,查找通用的那条 + if (config==null) + { + config = context.clientMarginConfig + .Where(c => c.client_id == 0 && c.value_date <= valueDate) + .OrderByDescending(c => c.value_date) + .FirstOrDefault(); + } + return config; + } + } + catch (Exception ex) + { + Console.WriteLine($"获取客户保证金配置时发生异常: {ex.Message}"); + return null; + } + } + + /// + /// 根据配置ID和期限获取保证金率详情 + /// + /// 配置ID + /// 债券期限 + /// 保证金率详情 + private static client_margin_detail GetMarginDetailByTerm(int configId, string bondTerm) + { + try + { + using (var context = DbContextFactory.GetYLDbContext()) + { + var details = context.clientMarginDetail + .Where(d => d.config_id == configId).ToList(); + var detail = details.FirstOrDefault(d => d.bond_term == bondTerm); + if (detail==null&& !string.IsNullOrEmpty(bondTerm)) + { + detail=details.FirstOrDefault(d => string.IsNullOrEmpty(d.bond_term)); + } + return detail; + } + } + catch (Exception ex) + { + Console.WriteLine($"获取保证金率详情时发生异常: {ex.Message}"); + return null; + } + } /// /// 获取篮子标的品种对象 @@ -124,5 +245,149 @@ namespace YLErp.Modules.UnderlyingModule } return ret; } + /// + /// 获取债券的期限,计算日期一定要小于原日期 + /// + /// 计算日期 + /// 原日期 + /// 返回3.12格式 + public static double? getYearTerm(DateTime? calcDate, DateTime? sourceDate) + { + if (!calcDate.HasValue) + { + return null;//不限 + } + if (sourceDate.HasValue) + { + if (calcDate > sourceDate) + { + throw new ArgumentException("计算日期必须小于原日期", nameof(calcDate)); + } + //计算sourceDate与calcDate年限差 + int yearDiff = sourceDate.Value.Year - calcDate.Value.Year; + int monthDiff = sourceDate.Value.Month - calcDate.Value.Month; + int dayDiff = sourceDate.Value.Day - calcDate.Value.Day; + + // 如果月差为负,说明还没到整年,需要向前借一年 + if (monthDiff < 0) + { + yearDiff -= 1; + monthDiff += 12; + } + + // 如果天数为负,说明还没到整月,需要向前借一个月 + if (dayDiff < 0) + { + monthDiff -= 1; + if (monthDiff < 0) + { + yearDiff -= 1; + monthDiff += 12; + } + } + + double yearTerm = yearDiff + monthDiff / 12.0; + return yearTerm; + } + return null;//不限 + } + /// + /// 根据标的发行年限和剩余期限计算适用保证金率 + /// 规则表格: + /// | 剩余期限(年) | 发行期限(年) | 则适用于 | + /// |------------------|---------------|----------| + /// | (0.00,2.00] | 不限 | 2年 | + /// | (2.00,2.25] | <5 | 2年 | + /// | (2.00,2.25] | >=5 | 5年 | + /// | (2.25,5.00] | 不限 | 5年 | + /// | (5.00,5.25] | <7 | 5年 | + /// | (5.00,5.25] | >=7 | 10年 | + /// | (5.25,25.00] | 不限 | 10年 | + /// | (25.00,30.00] | 不限 | 30年 | + /// + /// 标的代码 + /// 估值日期 + /// 适用的保证金率期限,如果不适用则返回空字符串 + public static string GetApplicableMarginTerm(string underlyingCode, DateTime valueDate) + { + if (string.IsNullOrEmpty(underlyingCode)) + return ConsMarginTerm.Default; + + try + { + // 获取标的信息 + var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(underlyingCode); + if (underlying == null || !underlying.MaturityDate.HasValue) + return ConsMarginTerm.Default; + + // 计算发行年限(从上市日期到到期日期) + var issueTermYears = getYearTerm(underlying.OpenDate, underlying.MaturityDate); + + // 计算剩余期限(从估值日期到到期日期) + var remainingTermYears = getYearTerm(valueDate, underlying.MaturityDate); + + // 根据业务规则确定适用的保证金率期限 + return DetermineMarginTerm(issueTermYears, remainingTermYears); + } + catch (Exception ex) + { + // 记录异常日志 + Console.WriteLine($"计算保证金率期限时发生异常: {ex.Message}"); + return ConsMarginTerm.Default; + } + } + + /// + /// 根据发行年限和剩余年限确定适用的保证金率期限 + /// 规则表格: + /// | 剩余期限(年) | 发行期限(年) | 则适用于 | + /// |------------------|---------------|----------| + /// | (0.00,2.00] | 不限 | 2年 | + /// | (2.00,2.25] | <5 | 2年 | + /// | (2.00,2.25] | >=5 | 5年 | + /// | (2.25,5.00] | 不限 | 5年 | + /// | (5.00,5.25] | <7 | 5年 | + /// | (5.00,5.25] | >=7 | 10年 | + /// | (5.25,25.00] | 不限 | 10年 | + /// | (25.00,30.00] | 不限 | 30年 | + /// 说明:发行年限为null或0时视为“不限” + /// + private static string DetermineMarginTerm(double? issueYears, double? remainingYears) + { + // (0.00,2.00]年 不限 2年 + if (remainingYears > 0.00 && remainingYears <= 2.00) + return ConsMarginTerm.TwoYear; + + // (2.00,2.25] <5 2年 + if (remainingYears > 2.00 && remainingYears <= 2.25 && issueYears.HasValue && issueYears < 5) + return ConsMarginTerm.TwoYear; + + // (2.00,2.25] >=5 5年 + if (remainingYears > 2.00 && remainingYears <= 2.25 && issueYears.HasValue && issueYears >= 5) + return ConsMarginTerm.FiveYear; + + // (2.25,5.00] 不限 5年 + if (remainingYears > 2.25 && remainingYears <= 5.00) + return ConsMarginTerm.FiveYear; + + // (5.00,5.25] <7 5年 + if (remainingYears > 5.00 && remainingYears <= 5.25 && issueYears.HasValue && issueYears < 7) + return ConsMarginTerm.FiveYear; + + // (5.00,5.25] >=7 10年 + if (remainingYears > 5.00 && remainingYears <= 5.25 && issueYears.HasValue && issueYears >= 7) + return ConsMarginTerm.TenYear; + + // (5.25,25.00] 不限 10年 + if (remainingYears > 5.25 && remainingYears <= 25.00) + return ConsMarginTerm.TenYear; + + // (25.00,30.00] 不限 30年 + if (remainingYears > 25.00 && remainingYears <= 30.00) + return ConsMarginTerm.ThirtyYear; + + // 其他情况返回默认 + return ConsMarginTerm.Default; + } } } diff --git a/YLErpWeb/App_Data/Menus.txt b/YLErpWeb/App_Data/Menus.txt index 8dec75aa..689f0137 100644 --- a/YLErpWeb/App_Data/Menus.txt +++ b/YLErpWeb/App_Data/Menus.txt @@ -44,7 +44,7 @@ {Name:"互换簿记预设",Rights:["互换簿记预设"],Icon:"menu-icon iconfour" ,SubItems:[ {Name:"TRS用簿记账户设置",Rights:["互换簿记预设-TRS用簿记账户设置"],Url:"EtradingRule/Index"}, - {Name:"互换预付金率维护",Rights:["互换簿记预设-互换预付金率维护"],Url:"MarginRateSwap/MarginRateSwapList"}, + {Name:"互换预付金率维护",Rights:["互换簿记预设-互换预付金率维护"],Url:"ClientMarginConfig/ClientMarginConfigList"}, {Name:"阶梯费率",Rights:["互换簿记预设-阶梯费率"],Url:"SwapRate"}, {Name:"浮动利率",Rights:["互换簿记预设-浮动利率"],Url:"SwapFloatRate"}, {Name:"簿记账户与衡泰关系",Rights:["互换簿记预设-簿记账户与衡泰关系"],Url:"EtradeAccount/Index"}, diff --git a/YLErpWeb/Controllers/ClientMarginConfigController.cs b/YLErpWeb/Controllers/ClientMarginConfigController.cs new file mode 100644 index 00000000..971f4b3d --- /dev/null +++ b/YLErpWeb/Controllers/ClientMarginConfigController.cs @@ -0,0 +1,227 @@ + +using Microsoft.AspNetCore.Authorization; +using YLErp.Cache; +using YLErp.Modules.MarginModule; + +namespace YLErp.Web.Controllers +{ + public class ClientMarginConfigController : BaseController + { + private IYLCache _cache; + public ClientMarginConfigController(IYLCache cache) + { + _cache = cache; + } + [MyAuthorize("互换簿记预设-互换预付金率维护")] + public ActionResult ClientMarginConfigList() + { + return View(); + } + + [MyAuthorize("互换簿记预设-互换预付金率修改")] + public ActionResult ClientMarginConfigEdit(string enid) + { + var id = DecryptInt(enid); + var fixedTerms = ConsMarginTerm.AllowedTerms.ToArray(); + if (id == 0) + { + // 新增时,创建空的DTO记录 + var emptyDetails = new List(); + + for (int i = 0; i < fixedTerms.Length; i++) + { + emptyDetails.Add(new ClientMarginDetailDto + { + bond_term = fixedTerms[i], + init_rate = null, + maintain_rate = null, + swap_days = null + }); + } + ViewBag.Details = emptyDetails; + return View(new client_margin_config() { value_date = DateTime.Now }); + } + var marginConfig = yldb.clientMarginConfig.Find(id); + + // 获取详情数据并转换为DTO + var details = yldb.clientMarginDetail.Where(d => d.config_id == id).ToList(); + var detailDtos = new List(); + + // 确保返回5条数据,按固定顺序 + for (int i = 0; i < fixedTerms.Count(); i++) + { + var term = fixedTerms[i]; + var existingDetail = details.FirstOrDefault(d => d.bond_term == term); + if (existingDetail != null) + { + detailDtos.Add(new ClientMarginDetailDto + { + bond_term = existingDetail.bond_term, + init_rate = existingDetail.init_rate * 100, // 转换为百分比显示 + maintain_rate = existingDetail.maintain_rate * 100, // 转换为百分比显示 + swap_days = existingDetail.swap_days + }); + } + else + { + detailDtos.Add(new ClientMarginDetailDto + { + bond_term = term, + init_rate = null, + maintain_rate = null, + swap_days = null + }); + } + } + + ViewBag.Details = detailDtos; + + return View(marginConfig); + } + + + + [MyAuthorize("互换簿记预设-互换预付金率修改")] + public JsonResult ClientMarginConfigDelete(string enid) + { + var id = DecryptInt(enid); + var marginConfig = yldb.clientMarginConfig.Find(id); + if (marginConfig == null) + { + throw new ServiceException("数据库中未找到"); + } + + // 删除详情记录 + var details = yldb.clientMarginDetail.Where(d => d.config_id == id).ToList(); + yldb.clientMarginDetail.RemoveRange(details); + + // 删除主记录 + yldb.clientMarginConfig.Remove(marginConfig); + yldb.SaveChanges(); + if (_cache!=null) + { + // 删除缓存ClientMarginRate开头的key + _cache.BatchDelete("ClientMarginRate:*"); + } + return JsonSuccess("已删除"); + } + + [MyAuthorize("互换簿记预设-互换预付金率修改")] + public JsonResult ClientMarginConfigEditJson(client_margin_config marginConfig, List details) + { + // 将DTO转换为数据库实体,处理必填和可选逻辑 + var detailEntities = new List(); + var hasDefaultTerm = false; + + if (details != null) + { + foreach (var dto in details) + { + // 默认期限(空字符串)必填 + if (string.IsNullOrEmpty(dto.bond_term)) + { + if (!dto.init_rate.HasValue || !dto.maintain_rate.HasValue) + { + return JsonError("默认期限的初始保证金率和维持保证金率必须填写"); + } + hasDefaultTerm = true; + + detailEntities.Add(new client_margin_detail + { + bond_term = "", + init_rate = dto.init_rate.Value / 100, // 转换百分比 + maintain_rate = dto.maintain_rate.Value / 100, // 转换百分比 + swap_days = dto.swap_days ?? 0 + }); + } + else + { + // 其他期限非必填,但如果填写了任何字段就需要验证 + if (dto.init_rate.HasValue || dto.maintain_rate.HasValue || dto.swap_days.HasValue) + { + // 如果swap_days有值,则init_rate和maintain_rate必须有值 + if (dto.swap_days.HasValue && (!dto.init_rate.HasValue || !dto.maintain_rate.HasValue)) + { + return JsonError($"期限\"{dto.bond_term}\":当互换默认天数有值时,初始保证金率和维持保证金率必须填写"); + } + + detailEntities.Add(new client_margin_detail + { + bond_term = dto.bond_term, + init_rate = (dto.init_rate ?? 0) / 100, // 转换百分比 + maintain_rate = (dto.maintain_rate ?? 0) / 100, // 转换百分比 + swap_days = dto.swap_days ?? 0 + }); + } + } + } + } + + // 验证默认期限必须填写 + if (!hasDefaultTerm) + { + return JsonError("默认期限的保证金配置信息必须填写"); + } + + new ClientMarginConfigService(CurUser).SaveMarginConfigWithDetails(marginConfig, detailEntities); + if (_cache != null) + { + // 删除缓存ClientMarginRate开头的key + _cache.BatchDelete("ClientMarginRate:*"); + } + return JsonSuccess("已修改"); + } + + [HttpPost] + public JsonResult ClientMarginConfigQuery(ClientMarginConfigReq req) + { + var sList = new ClientMarginConfigService(CurUser).SearchList(req); + GetExtendInfo(sList); + return JsonSuccess("",sList); + } + + [HttpPost] + public JsonResult GetMarginConfigDetails(int configId) + { + var details = yldb.clientMarginDetail.Where(d => d.config_id == configId).ToList(); + // 转换为百分比显示 + foreach (var detail in details) + { + detail.init_rate *= 100; + detail.maintain_rate *= 100; + } + return Json(details); + } + /// + /// 获取适用金率 + /// + /// + /// + [HttpPost] + [AllowAnonymous] + public JsonResult GetApplicableMarginRate([FromBody]GetClientMarginDetailReq req) + { + var detail= YLErp.Modules.UnderlyingModule.UnderlyingHelper.GetApplicableMarginRate(req.clientId, req.underlyingCode, req.valueDate); + return JsonSuccess("",detail); + } + + private void GetExtendInfo(IEnumerable marginConfigs) + { + foreach (var item in marginConfigs) + { + if (item.client_id != 0) + { + var client = DataCacheProvider.GetClientDataSource().GetData(item.client_id); + if (client != null) + { + item.client_name = client.Name; + } + } + + // 百分比转换已在服务层处理,此处不需要重复转换 + } + } + + + } +} diff --git a/YLErpWeb/Controllers/TrsOpenController.cs b/YLErpWeb/Controllers/TrsOpenController.cs index 4a1fc1c9..0bb1c754 100644 --- a/YLErpWeb/Controllers/TrsOpenController.cs +++ b/YLErpWeb/Controllers/TrsOpenController.cs @@ -43,7 +43,7 @@ namespace YLErp.Web.Controllers db.trs_open_config.Add(model); } db.SaveChanges(); - _yLCache.StringSetWithNoPrefix("TRS_Open_Hour", trsOpenConfig); + _yLCache.StringSetWithNoPrefix("TRS_Open_Hour", trsOpenConfig,TimeSpan.FromHours(8)); return JsonSuccess(""); } } diff --git a/YLErpWeb/Controllers/tradeController.cs b/YLErpWeb/Controllers/tradeController.cs index 6a96e468..01dfa593 100644 --- a/YLErpWeb/Controllers/tradeController.cs +++ b/YLErpWeb/Controllers/tradeController.cs @@ -8846,18 +8846,13 @@ namespace YLErp.Web.Controllers var trade_Report = yldb.trade_Report.Where(x => x.TradeId == id).FirstOrDefault(); if (trade_Report == null) { - var clientMarginTemplates = yldb.client_marginrate.Where(x => x.ValueDate <= trade.TradeDate).OrderByDescending(o => o.ValueDate).AsNoTracking().ToList(); - var marinRate = clientMarginTemplates.Where(x => x.ClientId == trade.ClientId).FirstOrDefault(); + var marinRate = YLErp.Modules.UnderlyingModule.UnderlyingHelper.GetApplicableMarginRate(trade.ClientId, trade.UnderlyingCode, trade.TradeDate.Value); if (marinRate == null) { - marinRate = clientMarginTemplates.Where(x => x.ClientId == 0).FirstOrDefault(); - } - if (marinRate == null) - { - marinRate = new client_marginrate + marinRate = new client_margin_detail() { - InitMarginRate = 1, - MaintenanceRate = 1, + init_rate = 1, + maintain_rate = 1 }; } trade_Report = new trade_report(); @@ -8867,8 +8862,8 @@ namespace YLErp.Web.Controllers trade_Report.IsUsePerformanceColl = "否"; trade_Report.PerformanceExplain = "详见交易确认书。"; trade_Report.IsPerformanceCollInterest = "否"; - trade_Report.InitialPerformanceColl = (decimal)marinRate.InitMarginRate * 100; - trade_Report.RecoveryPerformanceColl = (decimal)marinRate.MaintenanceRate * 100; + trade_Report.InitialPerformanceColl = (decimal)marinRate.init_rate * 100; + trade_Report.RecoveryPerformanceColl = (decimal)marinRate.maintain_rate * 100; trade_Report.UnwindPerformanceColl = trade_Report.RecoveryPerformanceColl; trade_Report.EodPerformanceColl = trade_Report.RecoveryPerformanceColl; trade_Report.PerformanceCollType = "0"; diff --git a/YLErpWeb/Views/ClientMarginConfig/ClientMarginConfigEdit.cshtml b/YLErpWeb/Views/ClientMarginConfig/ClientMarginConfigEdit.cshtml new file mode 100644 index 00000000..c837a9d3 --- /dev/null +++ b/YLErpWeb/Views/ClientMarginConfig/ClientMarginConfigEdit.cshtml @@ -0,0 +1,184 @@ +@using YLErp.Model +@model client_margin_config +@{ + ViewBag.Title = Model.id == 0 ? "新增客户保证金配置" : "编辑客户保证金配置"; + Layout = "~/Views/Shared/_InfoLayout.cshtml"; + var details = ViewBag.Details as List ?? new List(); +} +@section CSS{ + + +} +@section JS{ + + +} + +
+
+ @Html.HiddenFor(model => model.id) + @Html.MyDropdownFor(model => model.client_id, ClientDataModel.GetAllClient()) + +
+ + + * +
+
+ +
+ +
+
+

保证金配置详情

+
+ + + + + + + + + + + + @for (int i = 0; i < details.Count; i++) + { + var detail = details[i]; + var displayName = detail.bond_term == "" ? "默认" : detail.bond_term.Replace("Y", "年"); + + + + + + + } + +
利率债期限初始保证金率(%)维持保证金率(%)互换默认天数
+ + +
+
+
+ +
+ @MyControls.Btn("保存", "saveConfig()") + @MyControls.Btn("关闭", "layer.closeMe()") +
+
\ No newline at end of file diff --git a/YLErpWeb/Views/ClientMarginConfig/ClientMarginConfigList.cshtml b/YLErpWeb/Views/ClientMarginConfig/ClientMarginConfigList.cshtml new file mode 100644 index 00000000..382fc3ba --- /dev/null +++ b/YLErpWeb/Views/ClientMarginConfig/ClientMarginConfigList.cshtml @@ -0,0 +1,337 @@ +@model IEnumerable +@{ + ViewBag.Title = "互换预付金率维护"; + Layout = "~/Views/Shared/_MainLayout.cshtml"; + var pageObj = new + { + canEdit = CurUser.基础参数管理.互换预付金率修改, + }; +} +
+ @Html.MyAceDropdownInput("ClientId", "客户名称", ClientDataModel.GetAllClient(), true, true, null, false) + @MyControls.SearchBtn() + @if (pageObj.canEdit) + { + @MyControls.Btn("新增", "addConfig()") + } +
+
+
+ + + + + + + + + + + + + + + + + +
操作客户名称生效日期利率债期限初始保证金率维持保证金率互换默认天数操作人操作时间
+
+
+
+
+ + + + \ No newline at end of file