TRS-ZS-566、TRS-ZS-568 客户保证金率需求完成

This commit is contained in:
吴方海
2025-07-18 17:44:58 +08:00
parent 723dc682eb
commit aa26f712bb
27 changed files with 1696 additions and 118 deletions
+1 -1
View File
@@ -41,7 +41,7 @@ namespace YLErp.Cache
/// <returns></returns>
T StringGet<T>(string key) where T : class;
bool StringSetWithNoPrefix<T>(string key, object value) where T : class;
bool StringSetWithNoPrefix<T>(string key, object value, TimeSpan? timeSpan) where T : class;
T StringGetWithNoPrefix<T>(string key) where T : class;
#region Batch Operate
/// <summary>
+2 -2
View File
@@ -231,9 +231,9 @@ namespace YLErp.Cache
/// <param name="key"></param>
/// <param name="value"></param>
/// <returns></returns>
public bool StringSetWithNoPrefix<T>(string key, object value) where T : class
public bool StringSetWithNoPrefix<T>(string key, object value,TimeSpan? timeSpan) where T : class
{
return db.StringSet(key, JsonConvert.SerializeObject(value));
return db.StringSet(key, JsonConvert.SerializeObject(value), timeSpan);
}
}
}
@@ -0,0 +1,62 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace YLErp.DBModels
{
/// <summary>
/// 数据模型基类
/// </summary>
[Serializable]
public abstract class DBModelBaseV6 : DBModelBase
{
/// <summary>
/// 操作人ID
/// </summary>
[DisplayName("操作人")]
[Column("opt_id")]
public virtual int? OptId { set; get; }
/// <summary>
/// 操作人名称
/// </summary>
[DisplayName("操作人")]
[Column("opt_name")]
public virtual string OptName { set; get; }
/// <summary>
/// 操作时间
/// </summary>
[DisplayName("操作时间")]
[Column("opt_date")]
public virtual DateTime? OptDate { set; get; }
/// <summary>
/// 设置操作人操作时间
/// </summary>
public void SetOpt(DBModelWithOperator baseModel)
{
if (baseModel == null)
{
throw new ArgumentNullException(nameof(baseModel));
}
OptId = baseModel.OptId;
OptName = baseModel.OptName;
OptDate = baseModel.OptDate;
}
/// <summary>
/// 设置操作人操作时间
/// </summary>
public void SetOpt(OptUserInfo userInfo)
{
if (userInfo is null)
{
throw new ArgumentNullException(nameof(userInfo));
}
OptId = userInfo.UserId;
OptName = userInfo.UserName;
OptDate = DateTime.Now;
}
}
}
@@ -0,0 +1,73 @@
namespace YLErp.DBModels
{
/// <summary>
/// 保证金配置期限常量定义
/// </summary>
public static class ConsMarginTerm
{
/// <summary>
/// 默认期限(空字符串)
/// </summary>
public const string Default = "";
/// <summary>
/// 2年期
/// </summary>
public const string TwoYear = "2Y";
/// <summary>
/// 5年期
/// </summary>
public const string FiveYear = "5Y";
/// <summary>
/// 10年期
/// </summary>
public const string TenYear = "10Y";
/// <summary>
/// 30年期
/// </summary>
public const string ThirtyYear = "30Y";
/// <summary>
/// 所有允许的期限值
/// </summary>
public static readonly List<string> AllowedTerms = new List<string>
{
Default,
TwoYear,
FiveYear,
TenYear,
ThirtyYear
};
/// <summary>
/// 验证期限是否有效
/// </summary>
/// <param name="term">期限值</param>
/// <returns>是否有效</returns>
public static bool IsValidTerm(string term)
{
return AllowedTerms.Contains(term ?? string.Empty);
}
/// <summary>
/// 获取期限的显示名称
/// </summary>
/// <param name="term">期限值</param>
/// <returns>显示名称</returns>
public static string GetDisplayName(string term)
{
return term switch
{
Default => "默认",
TwoYear => "2年",
FiveYear => "5年",
TenYear => "10年",
ThirtyYear => "30年",
_ => term?.Replace("Y", "年") ?? "默认"
};
}
}
}
@@ -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
{
/// <summary>
/// 客户保证金配置
/// </summary>
[Table("client_margin_config")]
public class client_margin_config : DBModelBaseV6
{
/// <summary>
/// 客户ID
/// </summary>
[DisplayName("客户ID")]
public int client_id { get; set; }
/// <summary>
/// 客户名称
/// </summary>
[DisplayName("客户名称")]
[NotMapped]
public string client_name { get; set; }
/// <summary>
/// 生效日期
/// </summary>
[DisplayName("生效日期")]
public DateTime value_date { get; set; }
}
}
@@ -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
{
/// <summary>
/// 客户保证金配置详情
/// </summary>
[Table("client_bond_trs_margin_detail")]
public class client_margin_detail
{
/// <summary>
/// 主键ID
/// </summary>
[DisplayName("主键ID")]
[Key]
public int id { get; set; }
/// <summary>
/// 保证金关联主表id
/// </summary>
[DisplayName("配置ID")]
public int config_id { get; set; }
/// <summary>
/// 利率债期限
/// </summary>
[DisplayName("利率债期限")]
public string bond_term { get; set; }
/// <summary>
/// 初始保证金率(%)
/// </summary>
[DisplayName("初始保证金率")]
public decimal init_rate { get; set; }
/// <summary>
/// 维持保证金率(%)
/// </summary>
[DisplayName("维持保证金率")]
public decimal maintain_rate { get; set; }
/// <summary>
/// 互换默认天数
/// </summary>
[DisplayName("互换默认天数")]
public int swap_days { get; set; }
}
}
+1 -1
View File
@@ -2,7 +2,7 @@
{
public class YLUnitTestBase : YLBaseService
{
readonly StringBuilder _clearSQL;
public readonly StringBuilder _clearSQL;
public YLUnitTestBase() : base(OptUserInfo.UnitTestUser)
{
@@ -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
/// <summary>
/// 创建测试用的标的对象
/// </summary>
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;
}
/// <summary>
/// 添加清理SQL
/// </summary>
private void AddClearSQL(string sql)
{
// _clearSQL 字段在 YLUnitTestBase 中定义
// 这里假设 _clearSQL 是 protected 或 internal
if (!string.IsNullOrWhiteSpace(sql))
{
_clearSQL.AppendLine(sql);
}
}
#endregion
}
}
+5 -6
View File
@@ -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"
@@ -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;
@@ -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<int, DateTime?> 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
/// <param name="flows">流水列表</param>
/// <param name="clientMarginTemplate">客户保证金模板</param>
/// <returns>资金变化金额</returns>
public decimal CalcDmaMoney(List<swap_flow> positions, List<swap_flow> flows, client_marginrate clientMarginTemplate)
public decimal CalcDmaMoney(List<swap_flow> positions, List<swap_flow> 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() 避免修改集合时的问题
@@ -744,7 +744,7 @@ namespace YLErp.BLL.Eod
if (resp!=null)
{
clientPosition.deal_yield_avg = resp.ytm* ConsGlobal.bondPriceMultiple;
_yLCache.StringSetWithNoPrefix<CalBondResult>("TRS-BondFullPrice:" + clientPosition.security_id, resp);
_yLCache.StringSetWithNoPrefix<CalBondResult>("TRS-BondFullPrice:" + clientPosition.security_id, resp,TimeSpan.FromHours(1));
}
}
/// <summary>
+3
View File
@@ -402,5 +402,8 @@ namespace YLErp.BLL
public DbSet<TrsAccountManageDetail> trs_account_manage_detail { get; set; }
public DbSet<ClientBalanceView> clientBalanceView { get; set; }
public DbSet<client_margin_config> clientMarginConfig { get; set; }
public DbSet<client_margin_detail> clientMarginDetail { get; set; }
}
}
+15
View File
@@ -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; }
}
}
+77
View File
@@ -0,0 +1,77 @@
using System.ComponentModel.DataAnnotations;
namespace YLErp.Model
{
/// <summary>
/// 客户保证金配置详情DTO - 用于前端数据传输
/// </summary>
public class ClientMarginDetailDto
{
/// <summary>
/// 利率债期限
/// </summary>
public string bond_term { get; set; }
/// <summary>
/// 初始保证金率(%) - 可空
/// </summary>
public decimal? init_rate { get; set; }
/// <summary>
/// 维持保证金率(%) - 可空
/// </summary>
public decimal? maintain_rate { get; set; }
/// <summary>
/// 互换默认天数 - 可空
/// </summary>
public int? swap_days { get; set; }
}
/// <summary>
/// 客户保证金配置DTO - 用于前端数据传输
/// </summary>
public class ClientMarginConfigDto
{
public string EncryptId { get; set; }
/// <summary>
/// 配置ID
/// </summary>
public int id { get; set; }
/// <summary>
/// 客户ID
/// </summary>
public int client_id { get; set; }
/// <summary>
/// 客户名称
/// </summary>
public string client_name { get; set; }
/// <summary>
/// 生效日期
/// </summary>
public DateTime value_date { get; set; }
/// <summary>
/// 操作人ID
/// </summary>
public int? OptId { get; set; }
/// <summary>
/// 操作人姓名
/// </summary>
public string OptName { get; set; }
/// <summary>
/// 操作时间
/// </summary>
public DateTime? OptDate { get; set; }
/// <summary>
/// 配置详情列表
/// </summary>
public List<ClientMarginDetailDto> details { get; set; } = new List<ClientMarginDetailDto>();
}
}
@@ -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;}
}
}
@@ -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<ClientMarginConfigDto> 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<ClientMarginDetailDto>();
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<client_margin_detail> 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;
}
}
}
}
}
@@ -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<trade>();
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);
}
}
/// <summary>
@@ -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<SwapFloatRate>(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<swap_flow> swapFlows)
@@ -507,8 +505,7 @@ namespace YLErp.Modules.SwapModule
private void MergeRestModelItem(IGrouping<int?, swap_flow_merge> groupItem,
List<trade> swaptrades,
List<swap_position> swapPositions,
IQueryable<SwapFloatRate> floatRateQuery,
List<client_marginrate> client_Marginrates, ref int dealCount, Action<int>? action)
IQueryable<SwapFloatRate> floatRateQuery, ref int dealCount, Action<int>? 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<int?, swap_flow_merge> groupItem,
List<trade> swaptrades,
List<swap_position> swapPositions,
IQueryable<SwapFloatRate> floatRateQuery,
List<client_marginrate> client_Marginrates, ref int dealCount, Action<int>? action)
IQueryable<SwapFloatRate> floatRateQuery,ref int dealCount, Action<int>? 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);
}
/// <summary>
/// 当前无持仓,且有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<swap_position> clientSwapPositionList,
List<trade> 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);
}
}
/// <summary>
@@ -920,17 +901,16 @@ namespace YLErp.Modules.SwapModule
SwapFloatRate floatRate,
List<swap_position> clientSwapPositionList,
List<trade> 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);
}
}
/// <summary>
@@ -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<trade> { 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);
}
}
/// <summary>
@@ -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);
}
}
/// <summary>
@@ -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<swap_flow>();
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);
}
}
/// <summary>
@@ -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;
@@ -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;
@@ -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
/// <param name="td"></param>
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 = "详见交易确认书及定义性文件";
@@ -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
/// </summary>
public static class UnderlyingHelper
{
readonly static IYLCache ylCache;
static UnderlyingHelper()
{
ylCache = YLServiceLocator.ServiceProvider.GetService<IYLCache>();
}
/// <summary>
/// 获取组合标的品种对象
/// </summary>
@@ -59,6 +71,115 @@ namespace YLErp.Modules.UnderlyingModule
}
}
}
/// <summary>
/// 根据客户ID、标的代码和计算日期计算适用的保证金率
/// </summary>
/// <param name="clientId">客户ID</param>
/// <param name="underlyingCode">标的代码</param>
/// <param name="valueDate">计算日期</param>
/// <returns>适用的保证金率配置,包含初始保证金率和维持保证金率</returns>
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<client_margin_detail>(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<client_margin_detail>(cacheKey, marginDetail,TimeSpan.FromHours(3));
}
return marginDetail;
}
catch (Exception ex)
{
LogFactory.GetLogger("UnderlyingHelper").Error($"计算保证金率时发生异常: {ex.Message}", ex);
return null;
}
}
/// <summary>
/// 获取客户保证金配置
/// </summary>
/// <param name="clientId">客户ID</param>
/// <param name="valueDate">估值日期</param>
/// <returns>客户保证金配置</returns>
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;
}
}
/// <summary>
/// 根据配置ID和期限获取保证金率详情
/// </summary>
/// <param name="configId">配置ID</param>
/// <param name="bondTerm">债券期限</param>
/// <returns>保证金率详情</returns>
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;
}
}
/// <summary>
/// 获取篮子标的品种对象
@@ -124,5 +245,149 @@ namespace YLErp.Modules.UnderlyingModule
}
return ret;
}
/// <summary>
/// 获取债券的期限,计算日期一定要小于原日期
/// </summary>
/// <param name="calcDate">计算日期</param>
/// <param name="sourceDate">原日期</param>
/// <returns>返回3.12格式</returns>
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;//不限
}
/// <summary>
/// 根据标的发行年限和剩余期限计算适用保证金率
/// 规则表格:
/// | 剩余期限(年) | 发行期限(年) | 则适用于 |
/// |------------------|---------------|----------|
/// | (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年 |
/// </summary>
/// <param name="underlyingCode">标的代码</param>
/// <param name="valueDate">估值日期</param>
/// <returns>适用的保证金率期限,如果不适用则返回空字符串</returns>
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;
}
}
/// <summary>
/// 根据发行年限和剩余年限确定适用的保证金率期限
/// 规则表格:
/// | 剩余期限(年) | 发行期限(年) | 则适用于 |
/// |------------------|---------------|----------|
/// | (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时视为“不限”
/// </summary>
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;
}
}
}
+1 -1
View File
@@ -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"},
@@ -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<ClientMarginDetailDto>();
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<ClientMarginDetailDto>();
// 确保返回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<ClientMarginDetailDto> details)
{
// 将DTO转换为数据库实体,处理必填和可选逻辑
var detailEntities = new List<client_margin_detail>();
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);
}
/// <summary>
/// 获取适用金率
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
[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<ClientMarginConfigDto> 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;
}
}
// 百分比转换已在服务层处理,此处不需要重复转换
}
}
}
}
+1 -1
View File
@@ -43,7 +43,7 @@ namespace YLErp.Web.Controllers
db.trs_open_config.Add(model);
}
db.SaveChanges();
_yLCache.StringSetWithNoPrefix<TrsOpenConfigDto>("TRS_Open_Hour", trsOpenConfig);
_yLCache.StringSetWithNoPrefix<TrsOpenConfigDto>("TRS_Open_Hour", trsOpenConfig,TimeSpan.FromHours(8));
return JsonSuccess("");
}
}
+6 -11
View File
@@ -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_margin_detail()
{
marinRate = new client_marginrate
{
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";
@@ -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<ClientMarginDetailDto> ?? new List<ClientMarginDetailDto>();
}
@section CSS{
<link href="~/Statics/libs/datetime/flatpickr/flatpickr.min.css" rel="stylesheet" />
<style>
.formlabel{
width:128px !important;
}
</style>
}
@section JS{
<script src="~/Statics/libs/datetime/flatpickr/flatpickr.min.js?v=@(HtmlUtil.JsVersion)"></script>
<script type="text/javascript">
function saveConfig() {
if (!$("#valueDate").val()) {
main.message("生效日期不能为空");
return false;
}
// 验证必填字段
var clientId = $("#client_id").val();
// 收集表单数据
var configData = {
id: parseInt($("#id").val()) || 0,
client_id: parseInt(clientId),
value_date: $("#valueDate").val()
};
// 收集详情数据并进行验证
var details = [];
var rows = document.querySelectorAll('#detailTableBody tr');
var hasError = false;
var errorMessage = "";
rows.forEach(function(row, index) {
var bondTerm = row.querySelector('input[type="hidden"][name*="bond_term"]').value;
var initRateInput = row.querySelector('input[name*="init_rate"]');
var maintainRateInput = row.querySelector('input[name*="maintain_rate"]');
var swapDaysInput = row.querySelector('input[name*="swap_days"]');
var initRateValue = initRateInput.value.trim();
var maintainRateValue = maintainRateInput.value.trim();
var swapDaysValue = swapDaysInput.value.trim();
var initRate = initRateValue === '' ? null : parseFloat(initRateValue);
var maintainRate = maintainRateValue === '' ? null : parseFloat(maintainRateValue);
var swapDays = swapDaysValue === '' ? null : parseInt(swapDaysValue);
// 验证数值不能为负数
if (initRate !== null && initRate < 0) {
hasError = true;
errorMessage = "初始保证金率不能为负数";
return;
}
if (maintainRate !== null && maintainRate < 0) {
hasError = true;
errorMessage = "维持保证金率不能为负数";
return;
}
if (swapDays !== null && swapDays < 0) {
hasError = true;
errorMessage = "互换天数不能为负数";
return;
}
// 默认数据(第一条,bond_term为空)的所有字段都必填
if (bondTerm === "") {
if (initRate === null || maintainRate === null || swapDays === null) {
hasError = true;
errorMessage = "默认配置的所有字段都必须填写";
return;
}
details.push({
bond_term: bondTerm,
init_rate: initRate,
maintain_rate: maintainRate,
swap_days: swapDays
});
} else {
// 其他数据:当保证金率有值时,互换天数必填
if ((initRate !== null || maintainRate !== null) && swapDays === null) {
hasError = true;
errorMessage = "当保证金率有值时,互换天数必须填写";
return;
}
// 当保证金率与互换天数都为空时,不加到details里
if (initRate !== null || maintainRate !== null || swapDays !== null) {
details.push({
bond_term: bondTerm,
init_rate: initRate,
maintain_rate: maintainRate,
swap_days: swapDays
});
}
}
});
if (hasError) {
main.message(errorMessage);
return false;
}
var data = {
marginConfig: configData,
details: details
};
main.post("/ClientMarginConfig/ClientMarginConfigEditJson", data).done(function (res) {
window.parent.loadData();
layer.closeMe();
});
}
// 页面初始化
$(function () {
$(".datepicker").datepicker({ changeMonth: true, changeYear: true, showButtonPanel: true, showOtherMonths: true, selectOtherMonths: true });
});
</script>
}
<form id="configForm" method="post" autocomplete="off">
<div class="form-layout">
@Html.HiddenFor(model => model.id)
@Html.MyDropdownFor(model => model.client_id, ClientDataModel.GetAllClient())
<div id="ValueDateDiv" class="form-group col-md-6">
<label class="formlabel">生效日期:</label>
<input class="search-input datepicker" id="valueDate" name="valueDate" value="@(Model.id == 0 ? "" : Model.value_date.ToString("yyyy-MM-dd"))" type="text" autocomplete="off" required="required">
<span style="color:red">*</span>
</div>
</div>
<hr />
<div class="row">
<div class="col-md-12">
<h4>保证金配置详情</h4>
<br />
<table class="table table-bordered" id="detailTable">
<thead>
<tr>
<th>利率债期限</th>
<th>初始保证金率(%)</th>
<th>维持保证金率(%)</th>
<th>互换默认天数</th>
</tr>
</thead>
<tbody id="detailTableBody">
@for (int i = 0; i < details.Count; i++)
{
var detail = details[i];
var displayName = detail.bond_term == "" ? "默认" : detail.bond_term.Replace("Y", "年");
<tr data-index="@i">
<td>
<input type="hidden" name="details[@i].bond_term" value="@detail.bond_term" />
<input type="text" class="form-control" value="@displayName" readonly />
</td>
<td><input type="number" class="form-control" name="details[@i].init_rate" value="@(detail.init_rate?.ToString("0.##") ?? "")" step="0.01" min="0" max="100" /></td>
<td><input type="number" class="form-control" name="details[@i].maintain_rate" value="@(detail.maintain_rate?.ToString("0.##") ?? "")" step="0.01" min="0" max="100" /></td>
<td><input type="number" class="form-control" name="details[@i].swap_days" value="@(detail.swap_days?.ToString() ?? "")" min="0" /></td>
</tr>
}
</tbody>
</table>
</div>
</div>
<div class="form-buttons">
@MyControls.Btn("保存", "saveConfig()")
@MyControls.Btn("关闭", "layer.closeMe()")
</div>
</form>
@@ -0,0 +1,337 @@
@model IEnumerable<client_margin_config>
@{
ViewBag.Title = "互换预付金率维护";
Layout = "~/Views/Shared/_MainLayout.cshtml";
var pageObj = new
{
canEdit = CurUser.基础参数管理.互换预付金率修改,
};
}
<div class="searchdiv">
@Html.MyAceDropdownInput("ClientId", "客户名称", ClientDataModel.GetAllClient(), true, true, null, false)
@MyControls.SearchBtn()
@if (pageObj.canEdit)
{
@MyControls.Btn("新增", "addConfig()")
}
<div class="row" style="margin-top:15px">
<div class="col-md-12">
<div class="table-responsive">
<table class="table table-bordered table-striped" id="configTable">
<thead>
<tr>
<th width="250">操作</th>
<th>客户名称</th>
<th>生效日期</th>
<th>利率债期限</th>
<th>初始保证金率</th>
<th>维持保证金率</th>
<th>互换默认天数</th>
<th>操作人</th>
<th>操作时间</th>
</tr>
</thead>
<tbody id="configTableBody">
<!-- 数据将通过Ajax加载 -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<script>
// 页面权限配置
var pageObj = @Html.Raw(Json.Serialize(pageObj));
$(document).ready(function () {
loadData();
initEventHandlers();
});
// 初始化事件处理器
function initEventHandlers() {
// 回车键查询
$('#ClientId, #ValueDate').on('keypress', function(e) {
if (e.which === 13) {
SearchClick();
}
});
// 表格行点击高亮
$(document).on('click', '#configTable tbody tr', function() {
$(this).siblings().removeClass('row-selected');
$(this).addClass('row-selected');
});
// 快捷键支持
$(document).on('keydown', function(e) {
// Ctrl+R 查询
if (e.ctrlKey && e.which === 82) {
e.preventDefault();
loadData();
}
// F5 查询
if (e.which === 116) {
e.preventDefault();
loadData();
}
});
}
function loadData() {
var searchData = {
ClientId: $('#ClientId').val(),
ValueDate: $('#ValueDate').val()
};
main.post('/ClientMarginConfig/ClientMarginConfigQuery', searchData).done(function (result) {
if (result.success) {
renderTable(result.obj);
} else {
main.alert('加载数据失败:' + result.message);
}
});
}
function renderTable(data) {
var tbody = $('#configTableBody');
tbody.empty();
if (!data || data.length === 0) {
tbody.append('<tr><td colspan="9" class="text-center text-muted"><i class="fa fa-info-circle"></i> 暂无数据</td></tr>');
return;
}
$.each(data, function (index, item) {
try {
var detailsCount = (item.details && item.details.length > 0) ? item.details.length : 1;
// 主行数据
var mainRowClass = detailsCount > 1 ? 'main-row' : '';
var row = '<tr class="' + mainRowClass + '">';
// 操作列 - 合并单元格
row += '<td class="text-center" rowspan="' + detailsCount + '">';
if (pageObj.canEdit) {
row += '<input type="button" class="wentiEdit" title="修改" onclick="editConfig(\'' + item.EncryptId + '\')" value="修改" />';
row += '<input type="button" class="wentiEdit" title="删除" onclick="deleteConfig(\'' + item.EncryptId + '\')" value="删除" />';
} else {
row += '<span class="text-muted"></span>';
}
row += '</td>';
// 客户名称 - 合并单元格
row += '<td rowspan="' + detailsCount + '"><strong>' + escapeHtml(item.client_name || '--') + '</strong></td>';
// 生效日期 - 合并单元格
var valueDate = formatDate(item.value_date);
row += '<td rowspan="' + detailsCount + '">' + valueDate + '</td>';
// 显示第一条详情信息
if (item.details && item.details.length > 0) {
var detail = item.details[0];
row += generateDetailCells(detail);
} else {
row += '<td class="text-muted">--</td>';
row += '<td class="text-muted">--</td>';
row += '<td class="text-muted">--</td>';
row += '<td class="text-muted">--</td>';
}
// 操作人和操作时间 - 合并单元格
var operator = item.OptName || item.OptName || '--';
var operateTime = formatDateTime(item.OptDate);
row += '<td rowspan="' + detailsCount + '">' + escapeHtml(operator) + '</td>';
row += '<td rowspan="' + detailsCount + '">' + operateTime + '</td>';
row += '</tr>';
tbody.append(row);
// 如果有多个详情,显示其他详情行
if (item.details && item.details.length > 1) {
for (var i = 1; i < item.details.length; i++) {
var detailRow = '<tr class="detail-row">';
// 不需要操作、客户名称、生效日期、操作人、操作时间列,因为已经合并
detailRow += generateDetailCells(item.details[i]);
detailRow += '</tr>';
tbody.append(detailRow);
}
}
} catch (error) {
console.error('渲染表格行时出错:', error, item);
var errorRow = '<tr><td colspan="9" class="text-danger"><i class="fa fa-exclamation-triangle"></i> 数据渲染异常</td></tr>';
tbody.append(errorRow);
}
});
}
function SearchClick() {
loadData();
}
function addConfig() {
main.infopage("新增互换预付金率", '/ClientMarginConfig/ClientMarginConfigEdit', { area: ['750px', '700px'] });
}
function editConfig(id) {
main.infopage("修改互换预付金率", '/ClientMarginConfig/ClientMarginConfigEdit?enid=' + id, { area: ['750px', '700px'] });
}
function deleteConfig(id) {
if (confirm('确定要删除这条记录吗?')) {
$.post('/ClientMarginConfig/ClientMarginConfigDelete', { enid: id }, function (result) {
if (result.success) {
main.alert('删除成功');
SearchClick();
} else {
main.alert('删除失败:' + result.message);
}
});
}
}
// 辅助函数:HTML转义
function escapeHtml(text) {
if (!text) return '--';
var map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return text.toString().replace(/[&<>"']/g, function(m) { return map[m]; });
}
// 辅助函数:格式化日期
function formatDate(dateStr) {
if (!dateStr) return '--';
try {
var date = new Date(dateStr);
if (isNaN(date.getTime())) return '--';
return date.getFullYear() + '-' +
String(date.getMonth() + 1).padStart(2, '0') + '-' +
String(date.getDate()).padStart(2, '0');
} catch (e) {
return '--';
}
}
// 辅助函数:格式化日期时间
function formatDateTime(dateStr) {
if (!dateStr) return '--';
try {
var date = new Date(dateStr);
if (isNaN(date.getTime())) return '--';
return date.getFullYear() + '-' +
String(date.getMonth() + 1).padStart(2, '0') + '-' +
String(date.getDate()).padStart(2, '0') + ' ' +
String(date.getHours()).padStart(2, '0') + ':' +
String(date.getMinutes()).padStart(2, '0');
} catch (e) {
return '--';
}
}
// 辅助函数:格式化百分比
function formatPercentage(value) {
if (value === null || value === undefined || value === '') return '--';
try {
var num = parseFloat(value);
if (isNaN(num)) return '--';
return num.toFixed(2) + '%';
} catch (e) {
return '--';
}
}
// 辅助函数:格式化期限显示
function formatBondTerm(bondTerm) {
if (!bondTerm || bondTerm === '') return '默认';
if (bondTerm === null || bondTerm === undefined) return '--';
// 将Y替换为年
var formatted = bondTerm.toString().replace(/Y/g, '年');
return escapeHtml(formatted);
}
// 辅助函数:生成详情单元格HTML
function generateDetailCells(detail) {
return '<td>' + formatBondTerm(detail.bond_term) + '</td>' +
'<td class="text-right">' + formatPercentage(detail.init_rate) + '</td>' +
'<td class="text-right">' + formatPercentage(detail.maintain_rate) + '</td>' +
'<td class="text-center">' + (detail.swap_days || '--') + '天</td>';
}
</script>
<style>
/* 表格样式优化 */
#configTable {
font-size: 13px;
}
#configTable th {
background-color: #f8f9fa;
font-weight: bold;
text-align: center;
vertical-align: middle;
border: 1px solid #dee2e6;
}
#configTable td {
vertical-align: middle;
border: 1px solid #dee2e6;
padding: 8px;
}
/* 文本对齐 */
.text-right {
text-align: right;
}
.text-center {
text-align: center;
}
/* 空数据样式 */
.text-muted {
color: #6c757d;
font-style: italic;
}
/* 错误信息样式 */
.text-danger {
color: #dc3545;
font-weight: bold;
}
/* 响应式表格 */
.table-responsive {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
/* 表格行悬停效果 */
#configTable tbody tr:hover {
background-color: #f8f9fa;
cursor: pointer;
}
@@media (max-width: 768px) {
.responsive-table table {
min-width: 800px;
}
#configTable {
font-size: 12px;
}
#configTable th,
#configTable td {
padding: 4px;
}
}
</style>