#EQD-6948 国联民生-实现保证金规则(2)追保金额的产生与收盘计算 授信和现金tag
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace YLErp.DBModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 客户授信出入表(R4 决策④):记录授信占用/释放/调整的全量变化。
|
||||
/// 金额符号与资金流水同号(2026-08-20 业务确认):入金为负、出金为正;
|
||||
/// 已使用授信 = SUM(amount) 直接求和,可用授信 = 有效授信 − 已使用授信(入金使可用授信上升、出金使其收缩)。
|
||||
/// change_type(占用/释放/调整)仅作分类审计;业务字段名一律小写下划线(兼容 PostgreSQL)。
|
||||
/// </summary>
|
||||
[Table("client_credit_inout")]
|
||||
public class client_credit_inout : DBModelBaseV2
|
||||
{
|
||||
/// <summary>
|
||||
/// 变更类型:占用(簿记授信部分)
|
||||
/// </summary>
|
||||
public const int ChangeTypeOccupy = 1;
|
||||
/// <summary>
|
||||
/// 变更类型:释放(平仓/到期按原标签返还)
|
||||
/// </summary>
|
||||
public const int ChangeTypeRelease = 2;
|
||||
/// <summary>
|
||||
/// 变更类型:人工调整(授信审批额度变化/特批后补记)
|
||||
/// </summary>
|
||||
public const int ChangeTypeAdjust = 3;
|
||||
|
||||
/// <summary>
|
||||
/// 客户
|
||||
/// </summary>
|
||||
public int client_id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联预付金腿(swap_position.id,授信占用/释放绑定到那条腿;交易级占用/释放可为空)
|
||||
/// </summary>
|
||||
public long? position_id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联交易(与 position_id 并存,冗余便于按交易查询;授信调整类可为空)
|
||||
/// </summary>
|
||||
public int? trade_id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 变更类型 1=占用 2=释放 3=人工调整
|
||||
/// </summary>
|
||||
public int change_type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 变更金额:与资金流水同号(入金为负、出金为正);入金使可用授信上升、出金使其收缩
|
||||
/// </summary>
|
||||
public double amount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 变更后已使用授信(冗余快照,便于核对与报表)
|
||||
/// </summary>
|
||||
public double used_after { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 发生日期
|
||||
/// </summary>
|
||||
public DateTime happen_date { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注(如"簿记拆单授信部分""平仓释放")
|
||||
/// </summary>
|
||||
public string remark { get; set; }
|
||||
|
||||
// 基类操作人列为 PascalCase(其他存量表共用),本表全列小写下划线——override 并映射小写列名。
|
||||
// 新建表约定:所有列一律小写下划线,基类继承列按此方式覆写。
|
||||
/// <summary>操作人ID(列 opt_id)</summary>
|
||||
[Column("opt_id")]
|
||||
public override int OptId { get; set; }
|
||||
|
||||
/// <summary>操作人名称(列 opt_name)</summary>
|
||||
[Column("opt_name")]
|
||||
public override string OptName { get; set; }
|
||||
|
||||
/// <summary>操作时间(列 opt_time)</summary>
|
||||
[Column("opt_time")]
|
||||
public override DateTime OptTime { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace YLErp.DBModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 预付金腿资金标签常量(R4 授信/现金标签体系)。
|
||||
/// 授信不进资金:授信占用/释放通过 swap_position.FundTag + 授信出入表(client_credit_inout)体现,
|
||||
/// 资金流水(ClientCashInCashOut)只记录现金部分,不加标签。
|
||||
/// </summary>
|
||||
public static class ConsFundTag
|
||||
{
|
||||
/// <summary>
|
||||
/// 授信(该腿预付金占用授信额度,不产生资金流水,变化记入授信出入表)
|
||||
/// </summary>
|
||||
public const string Credit = "Credit";
|
||||
|
||||
/// <summary>
|
||||
/// 现金(该腿预付金正常产生资金流水)
|
||||
/// </summary>
|
||||
public const string Cash = "Cash";
|
||||
|
||||
/// <summary>
|
||||
/// 存量腿无标签时视同现金
|
||||
/// </summary>
|
||||
public static string EffectiveTag(string fundTag)
|
||||
{
|
||||
return fundTag == Credit ? Credit : Cash;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,17 +26,26 @@ namespace YLErp.DBModels
|
||||
public double? Credit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 原始授信值
|
||||
/// 原始授信值(数据库列 original_credit,小写)
|
||||
/// </summary>
|
||||
[DisplayName("原始授信值")]
|
||||
[Column("original_credit")]
|
||||
public double? OriginalCredit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最大授信可用比例(0-1,NULL按1)
|
||||
/// 最大授信可用比例(0-1,NULL按1)(数据库列 max_credit_use_ratio,小写)
|
||||
/// </summary>
|
||||
[DisplayName("最大授信可用比例")]
|
||||
[Column("max_credit_use_ratio")]
|
||||
public double? MaxCreditUseRatio { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 已使用授信(R4,非持久化:授信出入表 SUM(占用)−SUM(释放)+SUM(调整),列表展示用)
|
||||
/// </summary>
|
||||
[DisplayName("已使用授信")]
|
||||
[NotMapped]
|
||||
public double? UsedCredit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// PFE授信
|
||||
/// </summary>
|
||||
|
||||
@@ -255,6 +255,15 @@ namespace YLErp.DBModels
|
||||
/// </summary>
|
||||
public string category_tag { get; set; }
|
||||
/// <summary>
|
||||
/// 资金标签(R4,单列,数据库列名 fund_tag):Credit=授信 / Cash=现金 / NULL=未选(默认现金,存量视同现金)。
|
||||
/// 簿记录入时存用户逐腿选择;确认成交时系统在同列定稿——整腿授信→Credit、整腿现金→Cash、
|
||||
/// 授信额度不足跨界时拆单(原腿 Cash + 新拆授信腿 Credit);特批全部定稿为 Cash。
|
||||
/// 授信腿不产生资金流水,变化记入授信出入表(client_credit_inout)。
|
||||
/// </summary>
|
||||
[DisplayName("资金标签")]
|
||||
[Column("fund_tag")]
|
||||
public string FundTag { get; set; }
|
||||
/// <summary>
|
||||
/// 互换观察日集合
|
||||
/// </summary>
|
||||
[NotMapped]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using CsvHelper;
|
||||
using CsvHelper;
|
||||
using DocumentFormat.OpenXml.Bibliography;
|
||||
using DocumentFormat.OpenXml.Drawing;
|
||||
using DocumentFormat.OpenXml.Drawing.Charts;
|
||||
@@ -2327,12 +2327,44 @@ namespace YLErp.BLL.Eod
|
||||
}
|
||||
if (trade.ExerciseDate.Value.Date >= valuedateBLL.ValueDate.Date)
|
||||
{
|
||||
var margin = trade.InitialMargin ?? 0;
|
||||
var endMoney = margin + tradePrice;
|
||||
AvailableAmount = Math.Max(clientBalance.AvailableAmount + clientBalance.TotalCredit, 0);
|
||||
if (AvailableAmount < margin + tradePrice)
|
||||
// R4 簿记资金校验口径(2026-08-21 业务强调"走了资金的就不能占用授信"):
|
||||
// 按腿的资金走向分流——走现金的部分(未选/选现金腿 + 成交金额)只认现金结存;
|
||||
// 选授信的腿认 剩余可用授信(有效授信−已使用授信,授信出入表 Σ(amount)),
|
||||
// 授信不够覆盖的部分回落现金,同样只认现金结存。杜绝"现金腿拿授信垫付校验→现金透支"。
|
||||
var marginModes = new[] { (int)InterestModeEnum.追加预付金, (int)InterestModeEnum.初始预付金 };
|
||||
var legs = trade.swap_positions?.Where(x => marginModes.Contains(x.InterestMode)).ToList();
|
||||
if (legs == null || legs.Count == 0)
|
||||
{
|
||||
errorMsg = $"当前交易应付预付金:{trade.InitialMargin ?? 0:#,##0.000},应付成交金额:{tradePrice:#,##0.000},总应付资金:{endMoney:#,##0.000}。当前剩余资金:{clientBalance.AmountFund:F3},冻结资金:{clientBalance.AllFreezeBalance():F3},抵押品价值:{clientBalance.GuaranteesTotalAmount:F3},授信额度:{clientBalance.TotalCredit:F3},可用总额度:{AvailableAmount:F3}。不足以支付上述金额,交易费用:{tradePrice ?? 0:F3}";
|
||||
legs = db.swap_position.Where(x => x.SwapTradeId == trade.id && x.IsInitial && !x.Invalid
|
||||
&& marginModes.Contains(x.InterestMode)).ToList();
|
||||
}
|
||||
//客户应付为正:收取方向(dir=1)腿 fix 为正应付额;支付方向为客户收钱不参与
|
||||
double creditPayable = 0, cashPayable = 0;
|
||||
foreach (var leg in legs)
|
||||
{
|
||||
var payable = Convert.ToDouble(leg.InterestPrincipalFix) * (leg.InterestDirection == 1 ? 1 : -1);
|
||||
if (payable <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (leg.FundTag == YLErp.DBModels.ConsFundTag.Credit)
|
||||
{
|
||||
creditPayable += payable;
|
||||
}
|
||||
else
|
||||
{
|
||||
cashPayable += payable;
|
||||
}
|
||||
}
|
||||
var usedCredit = Modules.SwapModule.ClientCreditInoutService.GetUsedCredit(clientId, db);
|
||||
var creditCap = Math.Max(clientBalance.TotalCredit - usedCredit, 0);
|
||||
var creditCovered = Math.Min(creditPayable, creditCap);
|
||||
//授信覆盖不足的回落现金部分 + 走现金部分 + 成交金额,合计必须 ≤ 现金结存
|
||||
var cashNeed = tradePrice + cashPayable + (creditPayable - creditCovered);
|
||||
if (cashNeed > clientBalance.AmountFund)
|
||||
{
|
||||
var totalPayable = tradePrice + cashPayable + creditPayable;
|
||||
errorMsg = $"当前交易应付总额:{totalPayable:#,##0.000}(走现金:{cashPayable + tradePrice:#,##0.000},选授信:{creditPayable:#,##0.000})。当前现金结存:{clientBalance.AmountFund:F3}(走现金部分只认现金结存),授信额度:{clientBalance.TotalCredit:F3},已使用授信:{usedCredit:F3},剩余授信:{creditCap:F3}(授信仅覆盖选授信部分,不足回落现金)。现金不足以覆盖应付的现金部分。";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,6 +346,7 @@ namespace YLErp.BLL
|
||||
|
||||
public DbSet<swap_flow_merge> swap_flow_merge { get; set; }
|
||||
public DbSet<swap_position> swap_position { get; set; }
|
||||
public DbSet<client_credit_inout> client_credit_inout { get; set; }
|
||||
public DbSet<trade_extend> trade_extend { get; set; }
|
||||
public DbSet<trade_initial_margin> trade_initial_margin { get; set; }
|
||||
public DbSet<swap_flow_event> swap_flow_event { get; set; }
|
||||
|
||||
@@ -795,7 +795,12 @@ namespace YLErp.Modules.EodModule.SettlementModule
|
||||
var clientSpan = client.ClientSpan;
|
||||
if (clientSpan != null)
|
||||
{
|
||||
PayableMargin = clientSpan.WorstCastClientPayable ?? 0d;
|
||||
// 合约维度盯市(MarginWatchRule==0)客户成交时已产生出入金,保证金不再计入占用(PayableMargin),避免双重体现;
|
||||
// 客户维度盯市(null/1)维持现状,计入占用。存量客户为 null,行为不变。
|
||||
if (client.MarginWatchRule != 0)
|
||||
{
|
||||
PayableMargin = clientSpan.WorstCastClientPayable ?? 0d;
|
||||
}
|
||||
DeltaMargin = clientSpan.DeltaMargin ?? 0d;
|
||||
SwapPayableMargin = clientSpan.SwapWorstCastClientPayable ?? 0d;
|
||||
TwoSideMargin = clientSpan.TwoSideMargin ?? 0d;
|
||||
|
||||
@@ -150,10 +150,10 @@ namespace YLErp.Modules.MarginModule
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标的细分分类判定钩子(转债ETF/科创债ETF/中债指数等)。
|
||||
/// 标的细分分类判定钩子(转债ETF/科创债ETF/中债指数等),public 供单元测试回归默认行为。
|
||||
/// 本期默认返回 null → 走通配行兜底,行为与现状一致;后续需求按业务给的判定规则(代码段/标的维护字段)实现。
|
||||
/// </summary>
|
||||
private static string GetUnderlyingCategory(string underlyingCode, string underlyingInstrumentType)
|
||||
public static string GetUnderlyingCategory(string underlyingCode, string underlyingInstrumentType)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
using YLErp.BLL;
|
||||
using YLErp.DBModels;
|
||||
|
||||
namespace YLErp.Modules.SwapModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 客户授信出入服务(R4 授信/现金标签体系,决策④)。
|
||||
/// 授信出入表的写入口集中在 标签赋值(占用)与 平仓/到期返还(释放)两个链路内,禁止散落调用。
|
||||
/// 金额符号口径(2026-08-20 业务确认):与资金流水同号——入金为负、出金为正;
|
||||
/// 已使用授信 = SUM(amount) 直接求和(入金使已使用授信下降、可用授信=有效授信−已使用授信 上升;出金反之)。
|
||||
/// change_type(占用/释放/调整)仅作分类审计,不参与求和方向。
|
||||
/// </summary>
|
||||
public class ClientCreditInoutService : YLBaseService
|
||||
{
|
||||
public ClientCreditInoutService(OptUserInfo userInfo) : base(userInfo)
|
||||
{
|
||||
}
|
||||
|
||||
public ClientCreditInoutService(YLBaseService baseService) : base(baseService)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 客户已使用授信 = SUM(amount)(金额与资金流水同号:入金负、出金正)。
|
||||
/// 可用授信 = 有效授信 − 本值;入金使可用授信增长、出金使其收缩。
|
||||
/// 供交易确认校验(RealtimePnlCalc.TradeCanBeConfirm)等静态上下文直接调用。
|
||||
/// </summary>
|
||||
public static double GetUsedCredit(int clientId, YLContext db)
|
||||
{
|
||||
var records = db.client_credit_inout.Where(x => x.client_id == clientId).ToList();
|
||||
return Math.Round(records.Sum(x => x.amount), 2, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 客户当前已使用授信(实例方法,走服务 DbContext)
|
||||
/// </summary>
|
||||
public double GetUsedCredit(int clientId)
|
||||
{
|
||||
return GetUsedCredit(clientId, DbContext);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入一条授信变化记录(占用/释放/调整统一入口),并冗余记录变更后已使用授信。
|
||||
/// amount 带符号:入金(客户付)为负、出金(客户收)为正,与资金流水 Money 同号。
|
||||
/// </summary>
|
||||
public client_credit_inout Record(int clientId, long? positionId, int? tradeId, int changeType,
|
||||
double amount, DateTime happenDate, string remark)
|
||||
{
|
||||
if (amount == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var amountRounded = Math.Round(amount, 2, MidpointRounding.AwayFromZero);
|
||||
//变更后已使用授信快照:直接求和口径(符号已含方向)
|
||||
var usedAfter = Math.Round(GetUsedCredit(clientId) + amountRounded, 2, MidpointRounding.AwayFromZero);
|
||||
var record = new client_credit_inout
|
||||
{
|
||||
client_id = clientId,
|
||||
position_id = positionId,
|
||||
trade_id = tradeId,
|
||||
change_type = changeType,
|
||||
amount = amountRounded,
|
||||
used_after = usedAfter,
|
||||
happen_date = happenDate,
|
||||
remark = remark,
|
||||
OptId = UserId,
|
||||
OptName = UserName,
|
||||
OptTime = DateTime.Now
|
||||
};
|
||||
DbContext.client_credit_inout.Add(record);
|
||||
DbContext.SaveChanges();
|
||||
return record;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 占用:预付金腿标 Credit 的簿记入金(含拆单的授信部分;交易级占用 positionId 为空)。
|
||||
/// amount 传负数(入金方向,与资金流水同号)。
|
||||
/// </summary>
|
||||
public client_credit_inout Occupy(int clientId, long? positionId, int tradeId, double amount, DateTime happenDate, string remark)
|
||||
{
|
||||
return Record(clientId, positionId, tradeId, client_credit_inout.ChangeTypeOccupy, amount, happenDate, remark);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放:平仓/到期按原标签返还授信部分(按腿的 position_id 匹配原占用记录)。
|
||||
/// amount 传正数(出金方向,与资金流水同号)。
|
||||
/// </summary>
|
||||
public client_credit_inout Release(int clientId, long? positionId, int tradeId, double amount, DateTime happenDate, string remark)
|
||||
{
|
||||
return Record(clientId, positionId, tradeId, client_credit_inout.ChangeTypeRelease, amount, happenDate, remark);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除某交易的全部授信出入记录:与资金记录同生命周期——
|
||||
/// 交易回退到开仓(DeleteTradeCashInCashOut)、修改清除(ClearSwapPositions)、删除交易时同步清理,
|
||||
/// 重新确认/重补时按最新标签重写,避免占用悬挂。
|
||||
/// 注意:阶段四追加保证金占用落地后,此处需区分保留追加部分。
|
||||
/// </summary>
|
||||
public void RemoveByTrade(int tradeId)
|
||||
{
|
||||
var records = DbContext.client_credit_inout.Where(x => x.trade_id == tradeId).ToList();
|
||||
DbContext.client_credit_inout.RemoveRange(records);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按客户批量查询已使用授信(客户列表/详情"已使用授信"展示,数据源即本表)。
|
||||
/// </summary>
|
||||
public static Dictionary<int, double> GetUsedCreditByClients(List<int> clientIds, YLContext db)
|
||||
{
|
||||
var result = new Dictionary<int, double>();
|
||||
if (clientIds == null || clientIds.Count == 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
var records = db.client_credit_inout.Where(x => clientIds.Contains(x.client_id)).ToList();
|
||||
foreach (var group in records.GroupBy(x => x.client_id))
|
||||
{
|
||||
result[group.Key] = Math.Round(group.Sum(x => x.amount), 2, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
using YLErp.DBModels;
|
||||
|
||||
namespace YLErp.Modules.SwapModule.Margin;
|
||||
|
||||
/// <summary>
|
||||
/// 授信/现金资金标签分配的纯函数(从服务层剥离,便于单元测试)。
|
||||
/// 对应实现方案阶段二 §2.3 标签赋值四种情形 与 §2.4 平仓按原标签返还。
|
||||
/// 资金标签为预付金腿上的单列(swap_position.fund_tag,逐腿录入选择、确认时定稿),非交易级。
|
||||
/// </summary>
|
||||
public static class FundTagCalc
|
||||
{
|
||||
/// <summary>
|
||||
/// 按腿分配授信额度(§2.3 四种情形的逐腿版本):
|
||||
/// 选授信的腿按顺序消耗剩余额度(有效授信−已使用授信),额度耗尽的授信腿拆单(剩余授信+现金差额),
|
||||
/// 之后的授信腿全额现金;未选/选现金的腿直接现金;特批(情形4)全部现金不占授信;
|
||||
/// 客户净收取(负金额)的腿不参与授信分配。
|
||||
/// legs 需按预期占用顺序传入(HappenDate、id)。
|
||||
/// </summary>
|
||||
public static List<LegFundPlan> AllocateByLegPreference(List<LegAmount> legs, double creditAvailable, bool ignoreMoneyCheck)
|
||||
{
|
||||
var plans = new List<LegFundPlan>();
|
||||
var remaining = Math.Round(Math.Max(creditAvailable, 0), 2, MidpointRounding.AwayFromZero);
|
||||
foreach (var leg in legs)
|
||||
{
|
||||
var amount = Math.Round(leg.Amount, 2, MidpointRounding.AwayFromZero);
|
||||
var credit = 0.0;
|
||||
if (!ignoreMoneyCheck && leg.PreferCredit && amount > 0)
|
||||
{
|
||||
credit = Math.Min(amount, remaining);
|
||||
remaining = Math.Round(remaining - credit, 2, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
plans.Add(new LegFundPlan
|
||||
{
|
||||
Leg = leg.Leg,
|
||||
Amount = amount,
|
||||
CreditAmount = credit,
|
||||
CashAmount = Math.Round(amount - credit, 2, MidpointRounding.AwayFromZero)
|
||||
});
|
||||
}
|
||||
return plans;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 平仓/到期返还金额按被平仓腿的 FundTag 分流(§2.4):
|
||||
/// Credit 腿的返还本金与返息不产生资金流水(本金写授信出入表"释放",出金方向记正数),Cash/无标签(存量)走现金。
|
||||
/// 返回 现金部分返还本金、现金部分返息、以及按腿的授信释放明细。
|
||||
/// </summary>
|
||||
public static UnwindTagSplit SplitUnwindByTag(List<MarginLegSettlement> settlements)
|
||||
{
|
||||
var result = new UnwindTagSplit();
|
||||
foreach (var s in settlements)
|
||||
{
|
||||
var margin = Convert.ToDouble(s.MarginAmount);
|
||||
var rebate = Convert.ToDouble(s.RebateAmount);
|
||||
if (s.Tag == ConsFundTag.Credit)
|
||||
{
|
||||
result.CreditMargin += margin;
|
||||
result.CreditRebate += rebate;
|
||||
if (s.MarginAmount != 0)
|
||||
{
|
||||
result.Releases.Add(new TagRelease
|
||||
{
|
||||
PositionId = s.PositionId,
|
||||
Amount = Math.Round(margin, 2, MidpointRounding.AwayFromZero)
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.CashMargin += margin;
|
||||
result.CashRebate += rebate;
|
||||
}
|
||||
}
|
||||
result.CashMargin = Math.Round(result.CashMargin, 2, MidpointRounding.AwayFromZero);
|
||||
result.CreditMargin = Math.Round(result.CreditMargin, 2, MidpointRounding.AwayFromZero);
|
||||
result.CashRebate = Math.Round(result.CashRebate, 2, MidpointRounding.AwayFromZero);
|
||||
result.CreditRebate = Math.Round(result.CreditRebate, 2, MidpointRounding.AwayFromZero);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 参与标签分配的预付金腿及其簿记金额(客户应付为正)、资金来源选择
|
||||
/// </summary>
|
||||
public class LegAmount
|
||||
{
|
||||
public swap_position Leg { get; set; }
|
||||
public double Amount { get; set; }
|
||||
/// <summary>腿上是否选了授信(swap_position.fund_tag=='Credit')</summary>
|
||||
public bool PreferCredit { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单腿分配结果:CreditAmount 与 CashAmount 皆大于 0 时该腿需拆为两条(拆单)
|
||||
/// </summary>
|
||||
public class LegFundPlan
|
||||
{
|
||||
public swap_position Leg { get; set; }
|
||||
/// <summary>腿原簿记金额</summary>
|
||||
public double Amount { get; set; }
|
||||
/// <summary>授信部分金额</summary>
|
||||
public double CreditAmount { get; set; }
|
||||
/// <summary>现金部分金额</summary>
|
||||
public double CashAmount { get; set; }
|
||||
/// <summary>拆单时新拆出的授信腿(占用记录绑定到它)</summary>
|
||||
public swap_position CreditLeg { get; set; }
|
||||
public bool NeedSplit => CreditAmount > 0 && CashAmount > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 平仓/到期结算中的单条保证金腿结算额(客户应收返还为正)
|
||||
/// </summary>
|
||||
public class MarginLegSettlement
|
||||
{
|
||||
public long PositionId { get; set; }
|
||||
/// <summary>该腿资金标签(EffectiveTag 后:Credit 或 Cash)</summary>
|
||||
public string Tag { get; set; }
|
||||
/// <summary>返还本金(swap_flow_event.InterestPrincipal × 方向比)</summary>
|
||||
public decimal MarginAmount { get; set; }
|
||||
/// <summary>预付金返息(InterestClosePnL)</summary>
|
||||
public decimal RebateAmount { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 平仓利息事件逐腿构造结算额(纯函数,便于单元测试):
|
||||
/// 预付金腿(InterestMode=5/6、无标的代码)按标签表查标签(无标签存量按现金),
|
||||
/// 返还本金=InterestPrincipal×方向比(InterestDirection==1 取 -1,与 MarginCalc.AccumulateSettlement 一致),返息=InterestClosePnL。
|
||||
/// </summary>
|
||||
public static class MarginSettlementBuilder
|
||||
{
|
||||
private static readonly int[] MarginModes = { (int)InterestModeEnum.追加预付金, (int)InterestModeEnum.初始预付金 };
|
||||
|
||||
public static List<MarginLegSettlement> Build(IDictionary<long, string> positionTags, IEnumerable<swap_flow_event> interestEvents)
|
||||
{
|
||||
var settlements = new List<MarginLegSettlement>();
|
||||
foreach (var x in interestEvents ?? Array.Empty<swap_flow_event>())
|
||||
{
|
||||
if (!string.IsNullOrEmpty(x.UnderlyingCode) || !MarginModes.Contains(x.InterestMode))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var interestRatio = x.InterestDirection == 1 ? -1m : 1m;
|
||||
settlements.Add(new MarginLegSettlement
|
||||
{
|
||||
PositionId = x.PositionId,
|
||||
Tag = positionTags != null && positionTags.TryGetValue(x.PositionId, out var tag) ? tag : ConsFundTag.Cash,
|
||||
MarginAmount = x.InterestPrincipal * interestRatio,
|
||||
RebateAmount = x.InterestClosePnL
|
||||
});
|
||||
}
|
||||
return settlements;
|
||||
}
|
||||
}
|
||||
|
||||
public class TagRelease
|
||||
{
|
||||
public long PositionId { get; set; }
|
||||
public double Amount { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按标签分流后的平仓结算金额
|
||||
/// </summary>
|
||||
public class UnwindTagSplit
|
||||
{
|
||||
/// <summary>现金部分返还本金(产生 应付预付金 资金流水)</summary>
|
||||
public double CashMargin { get; set; }
|
||||
/// <summary>授信部分返还本金(写授信出入表"释放",不产生资金流水)</summary>
|
||||
public double CreditMargin { get; set; }
|
||||
/// <summary>现金部分预付金返息(产生 预付金返息 资金流水)</summary>
|
||||
public double CashRebate { get; set; }
|
||||
/// <summary>授信部分预付金返息(授信不进资金,不产生资金流水)</summary>
|
||||
public double CreditRebate { get; set; }
|
||||
/// <summary>按腿的授信释放明细(position_id 匹配原占用记录)</summary>
|
||||
public List<TagRelease> Releases { get; set; } = new List<TagRelease>();
|
||||
}
|
||||
@@ -1095,26 +1095,39 @@ namespace YLErp.Modules.SwapModule
|
||||
}
|
||||
/// <summary>
|
||||
/// 写入保证金的资金记录:应付预付金(SwapMarginAmount)和预付金返息(SwapMarginRebatePnl)。
|
||||
/// 依赖实例方法 AddClientCash/AddClientCashInCashOut,暂留此处。
|
||||
/// </summary>
|
||||
private void RecordMarginCashFlow(trade td, UnwindData unwindData)
|
||||
=> RecordMarginCashFlow(td, unwindData.ValueDate,
|
||||
unwindData.SwapMarginAmount, unwindData.SwapMarginRebatePnl,
|
||||
AddClientCashInCashOut);
|
||||
|
||||
/// <summary>
|
||||
/// 写入保证金资金记录的通用重载,接受资金写入委托。
|
||||
/// AddClientCash(virtual,测试可stub) 和 AddClientCashInCashOut(非virtual,直接写库)
|
||||
/// 都可通过此重载统一。
|
||||
/// R4 §2.4 平仓/到期按被平仓腿 FundTag 原路返还:
|
||||
/// Credit 腿的返还本金与返息不产生资金流水(本金写授信出入表"释放",按 position_id 匹配原占用),
|
||||
/// Cash/无标签(存量)部分正常产生资金流水。
|
||||
/// </summary>
|
||||
private void RecordMarginCashFlow(trade td, DateTime valueDate,
|
||||
List<swap_flow_event> interestEvents,
|
||||
decimal marginAmount, decimal marginRebate,
|
||||
Func<trade, double, string, DateTime, int> writeCash)
|
||||
{
|
||||
if (marginAmount != 0)
|
||||
writeCash(td, Convert.ToDouble(marginAmount), ClientCashInCashOut.系统操作_应付预付金, valueDate);
|
||||
if (marginRebate != 0)
|
||||
writeCash(td, Convert.ToDouble(-marginRebate), ClientCashInCashOut.系统操作_预付金返息, valueDate);
|
||||
var split = interestEvents != null && interestEvents.Any(x => string.IsNullOrEmpty(x.UnderlyingCode))
|
||||
? ReleaseMarginByFundTag(td, valueDate, interestEvents, marginAmount, marginRebate)
|
||||
//无预付金腿结算事件(如金额手工归一化/无腿场景)——退化原逻辑,全额现金
|
||||
: new UnwindTagSplit { CashMargin = Convert.ToDouble(marginAmount), CashRebate = Convert.ToDouble(marginRebate) };
|
||||
if (split.CashMargin != 0)
|
||||
writeCash(td, split.CashMargin, ClientCashInCashOut.系统操作_应付预付金, valueDate);
|
||||
if (split.CashRebate != 0)
|
||||
writeCash(td, -split.CashRebate, ClientCashInCashOut.系统操作_预付金返息, valueDate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按标签分流并写授信释放记录(virtual,测试可 stub 为全现金,见 TestableSwapDealService)。
|
||||
/// </summary>
|
||||
protected virtual UnwindTagSplit ReleaseMarginByFundTag(trade td, DateTime valueDate, List<swap_flow_event> interestEvents,
|
||||
decimal marginAmount, decimal marginRebate)
|
||||
{
|
||||
var settlements = new SwapFundTagService(this).GetSettlements(interestEvents);
|
||||
var split = new SwapFundTagService(this).ReleaseMarginByTag(td, valueDate, settlements);
|
||||
//结算事件缺失(异常数据)时保底按传入总额走现金,不丢资金记录
|
||||
if (settlements.Count == 0)
|
||||
{
|
||||
return new UnwindTagSplit { CashMargin = Convert.ToDouble(marginAmount), CashRebate = Convert.ToDouble(marginRebate) };
|
||||
}
|
||||
return split;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1528,7 +1541,7 @@ namespace YLErp.Modules.SwapModule
|
||||
ExecuteInTransaction(() =>
|
||||
{
|
||||
int clientCashId = AddClientCash(td, Convert.ToDouble(-unwindData.SwapRealizedPnL), ClientCashInCashOut.系统操作_平仓费, unwindData.ValueDate);
|
||||
RecordMarginCashFlow(td, unwindData.ValueDate, unwindData.SwapMarginAmount, 0m, AddClientCash);
|
||||
RecordMarginCashFlow(td, unwindData.ValueDate, unwindData.FlowEvents, unwindData.SwapMarginAmount, 0m, AddClientCash);
|
||||
DealFloatPosition(unwindData);
|
||||
var flowList = new List<swap_flow_event>(unwindData.FlowEvents);
|
||||
var eventId = SaveSwapDeal(unwindData, (int)SwapEventTypeEnum.平仓, clientCashId, "系统操作_平仓");
|
||||
@@ -1697,7 +1710,7 @@ namespace YLErp.Modules.SwapModule
|
||||
private void DealUnwind(UnwindData unwindData, trade td, string actionMsg = "系统操作_自动平仓")
|
||||
{
|
||||
int clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapRealizedPnL), ClientCashInCashOut.系统操作_平仓费, unwindData.ValueDate);
|
||||
RecordMarginCashFlow(td, unwindData.ValueDate, unwindData.SwapMarginAmount, 0m, AddClientCashInCashOut);
|
||||
RecordMarginCashFlow(td, unwindData.ValueDate, unwindData.FlowEvents, unwindData.SwapMarginAmount, 0m, AddClientCashInCashOut);
|
||||
var flowList = new List<swap_flow_event>(unwindData.FlowEvents);
|
||||
var eventId = SaveSwapDeal(unwindData, (int)SwapEventTypeEnum.平仓, clientCashId, actionMsg);
|
||||
if (unwindData.CloseMethod == (int)CloseMethodEnum.全部平仓)
|
||||
@@ -1768,7 +1781,7 @@ namespace YLErp.Modules.SwapModule
|
||||
ExecuteInTransaction(() =>
|
||||
{
|
||||
int clientCashId = AddClientCash(td, Convert.ToDouble(-unwindData.SwapRealizedPnL), ClientCashInCashOut.系统操作_互换, unwindData.ValueDate);
|
||||
RecordMarginCashFlow(td, unwindData.ValueDate, 0m, unwindData.SwapMarginRebatePnl, AddClientCash);
|
||||
RecordMarginCashFlow(td, unwindData.ValueDate, unwindData.FlowEvents, 0m, unwindData.SwapMarginRebatePnl, AddClientCash);
|
||||
foreach (var item in unwindData.FlowEvents)
|
||||
{
|
||||
item.OptLog = "手工操作";
|
||||
@@ -1837,7 +1850,7 @@ namespace YLErp.Modules.SwapModule
|
||||
int clientCashId = AddClientCash(td, Convert.ToDouble(-swapEvent.unwindData.SwapRealizedPnL), action, swapEvent.unwindData.ValueDate);
|
||||
if (eventType == (int)SwapEventTypeEnum.平仓)
|
||||
{
|
||||
RecordMarginCashFlow(td, swapEvent.unwindData.ValueDate, swapEvent.unwindData.SwapMarginAmount, 0m, AddClientCash);
|
||||
RecordMarginCashFlow(td, swapEvent.unwindData.ValueDate, swapEvent.unwindData.FlowEvents, swapEvent.unwindData.SwapMarginAmount, 0m, AddClientCash);
|
||||
}
|
||||
swapEvent.ClientCashId = clientCashId;
|
||||
td.UnWindDate = swapEvent.unwindData.UnwindDate;
|
||||
|
||||
@@ -741,10 +741,16 @@ namespace YLErp.Modules.SwapModule
|
||||
}
|
||||
|
||||
// 预付金腿:单独插入一条资金记录(系统操作_预付金返息)
|
||||
// R4 §2.4:返息按腿 FundTag 分流——授信部分不进资金(授信不产生流水),
|
||||
// 只对现金部分(含无标签存量)产生返息资金记录
|
||||
if (unwindData.SwapMarginRebatePnl != 0)
|
||||
{
|
||||
clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapMarginRebatePnl), ClientCashInCashOut.系统操作_预付金返息, unwindData.ValueDate);
|
||||
clientCashIds.Add(clientCashId);
|
||||
var cashRebate = GetAutoSwapCashRebate(td, flowEvents, unwindData.SwapMarginRebatePnl);
|
||||
if (cashRebate != 0)
|
||||
{
|
||||
clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(-cashRebate), ClientCashInCashOut.系统操作_预付金返息, unwindData.ValueDate);
|
||||
clientCashIds.Add(clientCashId);
|
||||
}
|
||||
}
|
||||
unwindData.SwapCloseAmount = unwindData.SwapRealizedPnL;//需要算上预付金利息 和 分红; 只是不算预付金返还
|
||||
// 分红:使用派息支付日偏移记录资金记录
|
||||
@@ -784,6 +790,36 @@ namespace YLErp.Modules.SwapModule
|
||||
}
|
||||
return swapEvent.id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// R4 §2.4:自动互换预付金返息按腿 FundTag 分流,返回现金部分返息。
|
||||
/// 授信腿返息不进资金(授信不产生流水);无标签存量/无预付金腿事件时全额现金。
|
||||
/// virtual 供纯内存测试 stub 为全额现金(见 TestableSwapEodPositionService)。
|
||||
/// </summary>
|
||||
protected virtual decimal GetAutoSwapCashRebate(trade td, List<swap_flow_event> flowEvents, decimal totalRebate)
|
||||
{
|
||||
if (flowEvents == null || flowEvents.Count == 0)
|
||||
{
|
||||
return totalRebate;
|
||||
}
|
||||
var premiumModes = MarginModes.ForLinq;
|
||||
var legs = flowEvents.Where(x => string.IsNullOrEmpty(x.UnderlyingCode) && premiumModes.Contains(x.InterestMode)).ToList();
|
||||
if (legs.Count == 0)
|
||||
{
|
||||
return totalRebate;
|
||||
}
|
||||
//GetSettlements 与 legs 同谓词同序过滤,settlements[i] 与 legs[i] 一一对应
|
||||
var settlements = new SwapFundTagService(this).GetSettlements(legs);
|
||||
for (var i = 0; i < legs.Count; i++)
|
||||
{
|
||||
//对齐 DealAutoInterests 返息符号口径:InterestClosePnL × −ReceivePay(方向);
|
||||
//自动互换只结返息,保证金本金不在此返还(不写释放记录)
|
||||
settlements[i].MarginAmount = 0m;
|
||||
settlements[i].RebateAmount = legs[i].InterestClosePnL * -DirectionRatio.ReceivePay(legs[i].InterestDirection);
|
||||
}
|
||||
var split = FundTagCalc.SplitUnwindByTag(settlements);
|
||||
return Math.Round(totalRebate - Convert.ToDecimal(split.CreditRebate), 2, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
/// <summary>
|
||||
/// 互换更新实时持仓信息
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
using YLErp.BLL;
|
||||
using YLErp.DBModels;
|
||||
using YLErp.Modules.SwapModule.Margin;
|
||||
using YLErp.Modules.TradeModule;
|
||||
|
||||
namespace YLErp.Modules.SwapModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 互换预付金 授信/现金 标签服务(实现方案阶段二 §2.3/§2.4)。
|
||||
/// 标签赋值与返还两个写入口集中在本服务,授信出入表(ClientCreditInoutService)的占用/释放由此统一触发。
|
||||
/// 口径:授信值取 credit.Credit 合计(已审批+日期有效+含母公司,阶段一已折算),已使用授信取授信出入表;
|
||||
/// 授信不进资金——授信部分不产生资金流水。
|
||||
/// 资金标签是预付金腿上的单列(swap_position.fund_tag,逐腿):录入时存用户选择(授信/现金/未选默认现金),确认成交时系统在同列定稿。
|
||||
/// </summary>
|
||||
public class SwapFundTagService : YLBaseService
|
||||
{
|
||||
public SwapFundTagService(OptUserInfo userInfo) : base(userInfo)
|
||||
{
|
||||
}
|
||||
|
||||
public SwapFundTagService(YLBaseService baseService) : base(baseService)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 有效授信合计:客户本人+母公司、已审批、日期有效(口径与 RealtimePnlCalc 取授信一致)。
|
||||
/// Credit 列在保存时已折算(OriginalCredit×MaxCreditUseRatio),此处直接取用。
|
||||
/// </summary>
|
||||
public double GetEffectiveCredit(int clientId, DateTime valueDate)
|
||||
{
|
||||
var client = DataCacheProvider.GetClientDataSource().GetData(clientId);
|
||||
var clientIds = new List<int> { clientId };
|
||||
if (client != null && client.ParentId > 0)
|
||||
{
|
||||
clientIds.Add(client.ParentId);
|
||||
}
|
||||
var credits = DbContext.credit.Where(t =>
|
||||
clientIds.Contains(t.ClientId ?? 0) && t.ProcessStatus == "已审批"
|
||||
&& (!t.CreditDeadLine.HasValue || t.CreditDeadLine >= valueDate)
|
||||
&& (!t.CreditStartDate.HasValue || t.CreditStartDate <= valueDate)).ToList();
|
||||
return credits.Sum(t => t.Credit ?? 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 剩余可用授信 = 有效授信 − 已使用授信。
|
||||
/// 已使用授信 = 授信出入表 Σ(amount)(入金负/出金正)——入金使可用授信上升、出金使其收缩(业务口径)。
|
||||
/// </summary>
|
||||
public double GetAvailableCredit(int clientId, DateTime valueDate)
|
||||
{
|
||||
return GetEffectiveCredit(clientId, valueDate) - ClientCreditInoutService.GetUsedCredit(clientId, DbContext);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 簿记确认时对预付金腿定稿资金标签并产生资金记录(§2.3 四种情形,逐腿)。
|
||||
/// fund_tag 单列:录入时存用户选择(Credit/Cash/NULL),本方法读取选择后在同列定稿——
|
||||
/// 特批全现金;选授信按剩余额度分配(跨界腿拆单为 授信+现金 两条),未选/现金直接现金。
|
||||
/// 授信腿只写授信出入表占用(入金方向记负数,绑定腿 position_id,冗余 trade_id),不产生资金流水;
|
||||
/// 现金腿走 SaveSwapTradeClientCash 幂等 upsert 产生 应付预付金 记录。
|
||||
/// marginLegs 需为已过滤(IsDeductPrincipal 等)的预付金腿(InterestMode=5/6)。
|
||||
/// </summary>
|
||||
public void ApplyMarginFundTags(trade td, List<swap_position> marginLegs, ClientCashInCashOutService cashService, bool ignoreMoneyCheck)
|
||||
{
|
||||
var creditService = new ClientCreditInoutService(this);
|
||||
//重确认/重补场景自愈:清掉本交易旧占用记录后按腿上当前选择与最新额度重写
|
||||
//(阶段四追加保证金占用落地后需区分保留追加部分)
|
||||
creditService.RemoveByTrade(td.id);
|
||||
|
||||
var valueDate = td.TradeDate ?? DateTime.Now;
|
||||
var creditAvailable = GetAvailableCredit(td.ClientId, valueDate);
|
||||
//客户应付为正:资金记录符号口径为 Money<0=客户付钱,即 收取方向(dir=1)腿 fix 为正应付额——
|
||||
//正是占用授信的场景;支付方向(dir=2)为客户收钱,不占用授信,直接现金。
|
||||
var allocateLegs = marginLegs
|
||||
.Select(x => new LegAmount
|
||||
{
|
||||
Leg = x,
|
||||
Amount = Convert.ToDouble(x.InterestPrincipalFix * (x.InterestDirection == 1 ? 1 : -1)),
|
||||
PreferCredit = x.FundTag == ConsFundTag.Credit
|
||||
})
|
||||
.Where(x => x.Amount > 0)
|
||||
.OrderBy(x => x.Leg.HappenDate ?? DateTime.MaxValue)
|
||||
.ThenBy(x => x.Leg.id)
|
||||
.ToList();
|
||||
var plans = FundTagCalc.AllocateByLegPreference(allocateLegs, creditAvailable, ignoreMoneyCheck);
|
||||
|
||||
//先落库拆分的新腿(需要 id 才能绑定占用记录)
|
||||
foreach (var plan in plans.Where(p => p.NeedSplit))
|
||||
{
|
||||
plan.CreditLeg = SplitLeg(td, plan);
|
||||
}
|
||||
//标签定稿(覆盖录入选择):拆单的两条腿在 SplitLeg 内已分别标 Cash/Credit;
|
||||
//整腿授信→Credit、整腿现金/负应付(客户净收取)腿→Cash
|
||||
foreach (var leg in marginLegs)
|
||||
{
|
||||
var plan = plans.FirstOrDefault(p => p.Leg.id == leg.id);
|
||||
if (plan == null || plan.NeedSplit)
|
||||
{
|
||||
if (plan == null)
|
||||
{
|
||||
leg.FundTag = ConsFundTag.Cash;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
leg.FundTag = plan.CreditAmount > 0 ? ConsFundTag.Credit : ConsFundTag.Cash;
|
||||
}
|
||||
DbContext.SaveChanges();
|
||||
|
||||
foreach (var leg in marginLegs)
|
||||
{
|
||||
var plan = plans.FirstOrDefault(p => p.Leg.id == leg.id);
|
||||
var happenDate = leg.HappenDate ?? td.TradeDate ?? DateTime.Now;
|
||||
if (plan != null && plan.CreditAmount > 0)
|
||||
{
|
||||
//整腿授信 或 拆单后的授信部分:不产生资金流水,只写占用(拆单绑新拆出的授信腿)。
|
||||
//入金方向记负数(业务口径:出入表金额与资金流水同号,入金负/出金正;入金使可用授信上升)
|
||||
creditService.Occupy(td.ClientId, plan.CreditLeg?.id ?? leg.id, td.id, -plan.CreditAmount, happenDate,
|
||||
plan.NeedSplit ? "簿记拆单授信部分" : "簿记授信占用");
|
||||
}
|
||||
//资金记录沿用既有符号口径(客户付钱为负 = -应付额):授信部分不产生流水,现金部分按差额产生
|
||||
var recordAmount = plan != null
|
||||
? -plan.CashAmount
|
||||
: Convert.ToDouble(leg.InterestPrincipalFix * (leg.InterestDirection == 1 ? -1 : 1));
|
||||
if (recordAmount != 0)
|
||||
{
|
||||
cashService.SaveSwapTradeClientCash(td, recordAmount, happenDate, leg.id, ClientCashInCashOut.系统操作_应付预付金);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拆单:把跨界腿拆为 授信+现金 两条。原腿保留现金部分并标 Cash(资金来源同步改现金,与最终标签一致),
|
||||
/// 克隆一条授信腿(InterestPrincipalFix 按授信金额折算)标 Credit,返回新腿。
|
||||
/// 拆出的腿为普通初始腿,后续编辑/回退/平仓链路按既有腿处理。
|
||||
/// </summary>
|
||||
private swap_position SplitLeg(trade td, LegFundPlan plan)
|
||||
{
|
||||
var leg = plan.Leg;
|
||||
//应付额 = fix × (dir==1 ? 1 : -1),反推 fix 用同一比例(±1 自反)
|
||||
var payableRatio = leg.InterestDirection == 1 ? 1 : -1;
|
||||
leg.InterestPrincipalFix = Math.Round(Convert.ToDecimal(plan.CashAmount) * payableRatio, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
leg.FundTag = ConsFundTag.Cash;
|
||||
|
||||
var creditLeg = leg.Clone();
|
||||
creditLeg.id = 0;
|
||||
creditLeg.PositionId = 0;
|
||||
creditLeg.InterestPrincipalFix = Math.Round(Convert.ToDecimal(plan.CreditAmount) * payableRatio, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
creditLeg.FundTag = ConsFundTag.Credit;
|
||||
creditLeg.OptId = UserId;
|
||||
creditLeg.OptName = UserName;
|
||||
creditLeg.OptTime = DateTime.Now;
|
||||
DbContext.swap_position.Add(creditLeg);
|
||||
DbContext.SaveChanges();
|
||||
creditLeg.PosiNumber = $"{td.TradeNumber}-{creditLeg.id}";
|
||||
return creditLeg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 平仓/到期返还按被平仓腿 FundTag 分流(§2.4):
|
||||
/// Credit 腿返还本金写授信出入表"释放"(按 position_id 匹配原占用)、本金与返息均不产生资金流水;
|
||||
/// Cash/无标签(存量)部分由调用方按返回的现金金额正常产生返还流水。
|
||||
/// settlements 由平仓利息事件(swap_flow_event)逐腿构造。
|
||||
/// </summary>
|
||||
public UnwindTagSplit ReleaseMarginByTag(trade td, DateTime valueDate, List<MarginLegSettlement> settlements)
|
||||
{
|
||||
var split = FundTagCalc.SplitUnwindByTag(settlements);
|
||||
if (split.Releases.Count > 0)
|
||||
{
|
||||
var creditService = new ClientCreditInoutService(this);
|
||||
foreach (var release in split.Releases)
|
||||
{
|
||||
creditService.Release(td.ClientId, release.PositionId, td.id, release.Amount, valueDate, "平仓/到期释放");
|
||||
}
|
||||
}
|
||||
return split;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 由平仓利息事件构造逐腿结算额(供 ReleaseMarginByTag 消费):
|
||||
/// 预付金腿按 PositionId 查腿标签(无标签存量按现金),纯函数构造见 MarginSettlementBuilder.Build。
|
||||
/// </summary>
|
||||
public List<MarginLegSettlement> GetSettlements(IEnumerable<swap_flow_event> interestEvents)
|
||||
{
|
||||
var marginModes = new[] { (int)InterestModeEnum.追加预付金, (int)InterestModeEnum.初始预付金 };
|
||||
var events = (interestEvents ?? Enumerable.Empty<swap_flow_event>())
|
||||
.Where(x => string.IsNullOrEmpty(x.UnderlyingCode) && marginModes.Contains(x.InterestMode)).ToList();
|
||||
var positionIds = events.Select(x => x.PositionId).Where(x => x > 0).Distinct().ToList();
|
||||
var tags = DbContext.swap_position.Where(p => positionIds.Contains(p.id))
|
||||
.ToDictionary(p => p.id, p => ConsFundTag.EffectiveTag(p.FundTag));
|
||||
return MarginSettlementBuilder.Build(tags, events);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ using System.Data;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using YLErp.BLL;
|
||||
using YLErp.BLL.Eod;
|
||||
using YLErp.Configuration;
|
||||
using YLErp.Configuration.Enums;
|
||||
using YLErp.CustomizedBizLogic;
|
||||
@@ -86,8 +87,16 @@ namespace YLErp.Modules.SwapModule
|
||||
{
|
||||
var um = checkUnderlying(req);
|
||||
trade dbTrade = new trade();
|
||||
//交易保存处理
|
||||
//交易保存处理(PrepareInitialMargin 在此把 trade_Initial_Margin 折算进 req.InitialMargin,
|
||||
//资金校验须在其后取值,否则互换表单不填平铺 InitialMargin 时校验会按 0 放行)
|
||||
var tradeNumberGenerated = PrepareTrade(req, TradeSourceEnum.系统交易, um);
|
||||
// R4 簿记资金校验:可用资金(现金结存+授信−已使用授信,口径见 RealtimePnlCalc.TradeCanBeConfirm)
|
||||
// 需覆盖 应付预付金+成交金额,不足拦截抛错;特批放行发生在确认成交环节(ignoreMoneyCheck)。
|
||||
// ExerciseDate 为空(异常数据)时跳过该校验,避免 TradeCanBeConfirm 内部解引用抛错。
|
||||
if (req.ExerciseDate.HasValue && !RealtimePnlCalc.TradeCanBeConfirm(req.ClientId, req, out var fundErrorMsg))
|
||||
{
|
||||
throw new ServiceException(fundErrorMsg);
|
||||
}
|
||||
|
||||
var trans = DbContext.Database.BeginTransaction();
|
||||
try
|
||||
@@ -215,6 +224,9 @@ namespace YLErp.Modules.SwapModule
|
||||
var delCashInCashOutArr = DbContext.ClientCashInCashOut.Where(predicate_cashIncashOut);
|
||||
|
||||
DbContext.ClientCashInCashOut.RemoveRange(delCashInCashOutArr);
|
||||
// R4:授信出入记录与资金记录同生命周期,随资金记录一并清理(回退到开仓/删除交易),
|
||||
// 重新确认时按最新标签与额度重写,避免授信占用悬挂
|
||||
new ClientCreditInoutService(this).RemoveByTrade(tradeId);
|
||||
}
|
||||
/// <summary>
|
||||
/// 单标的初始化实时持仓
|
||||
@@ -1462,6 +1474,8 @@ namespace YLErp.Modules.SwapModule
|
||||
position.interest_rest_days = swap.interest_rest_days;
|
||||
position.interest_rule = swap.interest_rule;
|
||||
position.category_tag = string.IsNullOrEmpty(swap.category_tag) ? "互换利率" : swap.category_tag;
|
||||
// R4:资金标签(fund_tag 单列)随录入保存用户逐腿选择,确认成交时系统在同列定稿
|
||||
position.FundTag = string.IsNullOrWhiteSpace(swap.FundTag) ? null : swap.FundTag;
|
||||
position.InitYtm = swap.InitYtm;
|
||||
if (position.InitYtm != null && position.InitYtm > 0)
|
||||
{
|
||||
@@ -1496,6 +1510,8 @@ namespace YLErp.Modules.SwapModule
|
||||
var eodSwapPositions = DbContext.eod_swap_position.Where(x => x.SwapTradeId == tradeId);
|
||||
var eodSwaps = DbContext.eod_swap.Where(x => x.SwapTradeId == tradeId);
|
||||
var clientcashinouts = DbContext.ClientCashInCashOut.Where(x => x.TradeId == tradeId && x.Action == ClientCashInCashOut.系统操作_应付预付金);
|
||||
// R4:修改清除资金记录时同步清理授信出入记录,重确认时按最新标签重写
|
||||
new ClientCreditInoutService(this).RemoveByTrade(tradeId);
|
||||
swapFlowEvents.ForEach(x =>
|
||||
{
|
||||
x.DataState = (int)SwapFlowDateStateEnum.废弃;
|
||||
@@ -1629,14 +1645,19 @@ namespace YLErp.Modules.SwapModule
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 重新补录预付金记录
|
||||
/// 重新补录预付金记录(回退到开仓后重补,与 SwapTradeConfirm 同口径)
|
||||
/// </summary>
|
||||
/// <param name="td"></param>
|
||||
/// <param name="happenDate"></param>
|
||||
/// <param name="swapPositions"></param>
|
||||
private void ResetMarginAmount(trade td, DateTime happenDate, List<swap_position> swapPositions)
|
||||
{
|
||||
new ClientCashInCashOutService(this).SaveSwapTradeClientCash(td, td.TradePrice ?? 0, happenDate, 0);
|
||||
var cashSvc = new ClientCashInCashOutService(this);
|
||||
cashSvc.SaveSwapTradeClientCash(td, td.TradePrice ?? 0, happenDate, 0);
|
||||
// R4:重补同样走标签分配(授信占用/拆单/现金流水);
|
||||
// 特批标志在回退重补场景不可得,按当前剩余额度重新分配(回退即重新簿记)
|
||||
var fundTagSvc = new SwapFundTagService(this);
|
||||
var generateMarginLegs = new List<swap_position>();
|
||||
foreach (var marginPositions in swapPositions.Where(x => x.HappenDate != null && x.IsInitial && x.InterestMode != (int)InterestModeEnum.追加预付金).GroupBy(g => g.HappenDate))
|
||||
{
|
||||
var marginHappenDate = marginPositions.Key;
|
||||
@@ -1653,18 +1674,21 @@ namespace YLErp.Modules.SwapModule
|
||||
}
|
||||
if (generateMargin)
|
||||
{
|
||||
var marginAmount = marginPosition.InterestPrincipalFix * (marginPosition.InterestDirection == 1 ? -1 : 1);
|
||||
new ClientCashInCashOutService(this).SaveSwapTradeClientCash(td, Convert.ToDouble(marginAmount), marginHappenDate.HasValue ? marginHappenDate.Value : td.TradeDate.Value, marginPosition.id, ClientCashInCashOut.系统操作_应付预付金);
|
||||
generateMarginLegs.Add(marginPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
fundTagSvc.ApplyMarginFundTags(td, generateMarginLegs, cashSvc, false);
|
||||
//标签定稿(含可能的拆单)后重克隆实时持仓:TradeBack 的克隆先于定稿生成,
|
||||
//重克隆使实时腿继承定稿标签、新拆出的授信腿也获得克隆(平仓返还分流查的是实时腿标签)
|
||||
InitialPosition(td);
|
||||
// 合约维度盯市+无预付金腿:重建交易级(positionId=0)初始预付金记录(与 SwapTradeConfirm 一致,回退重补场景)。
|
||||
// 有预付金腿的互换由上面 foreach 按腿重建,不在此重复生成。
|
||||
// 有预付金腿的互换由上面按腿重建,不在此重复生成。
|
||||
// R4:资金来源是预付金腿上的录入项,无腿即无从选择授信,本交易级记录恒为现金。
|
||||
var resetWatchClient = DataCacheProvider.GetClientDataSource().GetData(td.ClientId);
|
||||
var hasPrepayLeg = swapPositions != null && swapPositions.Any(x => x.InterestMode == (int)InterestModeEnum.初始预付金 || x.InterestMode == (int)InterestModeEnum.追加预付金);
|
||||
if (resetWatchClient != null && resetWatchClient.MarginWatchRule == 0 && !hasPrepayLeg)
|
||||
{
|
||||
var cashSvc = new ClientCashInCashOutService(this);
|
||||
var initMargin = cashSvc.GetInitialMarginAmount(td);
|
||||
if (initMargin > 0)
|
||||
{
|
||||
@@ -1706,6 +1730,8 @@ namespace YLErp.Modules.SwapModule
|
||||
interest.id = 0;
|
||||
interest.PositionId = item.id;
|
||||
interest.IsInitial = false;
|
||||
// R4:回到开仓保留 fund_tag(用户录入的选择),重新确认时按当前额度在同列定稿;
|
||||
// 授信出入记录已随资金记录清理,重确认时重写
|
||||
td.TradeAmount = td.TradeAmount + Convert.ToDouble(interest.PosiQuantity);
|
||||
DbContext.swap_position.Add(interest);
|
||||
}
|
||||
|
||||
@@ -189,6 +189,7 @@ namespace YLErp.Modules.TradeModule
|
||||
|
||||
// 追保规则-合约维度盯市:非互换交易(期权等)成交确认时,按计算保证金生成初始预付金资金记录(入金)。
|
||||
// 客户维度盯市(null/1)不生成出入金,保证金只进 clientbalancedaily.PayableMargin(占用),维持现状。
|
||||
// R4:资金来源是互换预付金腿上的录入项,期权等无腿交易无从选择授信,恒为现金(行为不变)。
|
||||
var marginClient = DataCacheProvider.GetClientDataSource().GetData(newtrade.ClientId);
|
||||
if (marginClient != null && marginClient.MarginWatchRule == 0)
|
||||
{
|
||||
|
||||
@@ -280,7 +280,7 @@ namespace YLErp.Modules.TradeModule.DealModule
|
||||
DbContext.processtradelog.Add(p);
|
||||
new SalesCommissionDataService(OptUser).SetCommissionVaild(td.id);
|
||||
EditReportStatus(td.id);
|
||||
SwapTradeConfirm(td, history,false,optlog:"手工操作");
|
||||
SwapTradeConfirm(td, history,false,optlog:"手工操作", ignoreMoneyCheck: ignoreMoneyCheck);
|
||||
}
|
||||
else if (passStatus == -3)
|
||||
{
|
||||
@@ -313,7 +313,7 @@ namespace YLErp.Modules.TradeModule.DealModule
|
||||
DbContext.processtradelog.Add(p);
|
||||
new SalesCommissionDataService(OptUser).SetCommissionVaild(td.id);
|
||||
EditReportStatus(td.id);
|
||||
SwapTradeConfirm(td, history, false, optlog: "手工操作");
|
||||
SwapTradeConfirm(td, history, false, optlog: "手工操作", ignoreMoneyCheck: ignoreMoneyCheck);
|
||||
}
|
||||
|
||||
new ClientCashInCashOutService(this).SaveClientCashInCashOut(td);
|
||||
@@ -352,7 +352,8 @@ namespace YLErp.Modules.TradeModule.DealModule
|
||||
/// 新版互换确认
|
||||
/// </summary>
|
||||
/// <param name="td"></param>
|
||||
public void SwapTradeConfirm(trade td,string history,bool addlog,DateTime? openDate=null,string optlog="")
|
||||
/// <param name="ignoreMoneyCheck">特批(忽略资金检查)推进:预付金腿全部标现金,不占授信(R4 情形4)</param>
|
||||
public void SwapTradeConfirm(trade td,string history,bool addlog,DateTime? openDate=null,string optlog="", bool ignoreMoneyCheck = false)
|
||||
{
|
||||
if (td.TradeType != "收益互换")
|
||||
{
|
||||
@@ -363,12 +364,9 @@ namespace YLErp.Modules.TradeModule.DealModule
|
||||
var marginModes = new int[]{ (int)InterestModeEnum.追加预付金, (int)InterestModeEnum.初始预付金 };
|
||||
var positions= DbContext.swap_position.Where(x => x.SwapTradeId == td.id && x.IsInitial && !x.Invalid ).ToList();
|
||||
td.swap_positions = positions.Where(x =>marginModes.Contains(x.InterestMode)).ToList();
|
||||
var posiPositions= positions.Where(x => x.PosiDirection>0).ToList();
|
||||
var posiPositions= positions.Where(x=>x.PosiDirection>0).ToList();
|
||||
SwapTradeService swapTradeService = new SwapTradeService(this);
|
||||
var swapEventService = new SwapEventService(this);
|
||||
swapTradeService.InitialPosition(td);
|
||||
swapTradeService.AddPositionEvent(td, optlog);
|
||||
//new SwapEodPositionService(this).InitSaveEodSwapPosition(td);
|
||||
var happenDate = td.TradeDate.Value;
|
||||
if (td.trade_extend != null && !td.trade_extend.ExtendObj.NeedOpenFee)
|
||||
{
|
||||
@@ -376,26 +374,43 @@ namespace YLErp.Modules.TradeModule.DealModule
|
||||
}
|
||||
var cashService = new ClientCashInCashOutService(this);
|
||||
cashService.SaveSwapTradeClientCash(td, td.TradePrice ?? 0, happenDate,0);
|
||||
foreach (var marginPositions in td.swap_positions.GroupBy(g=>g.HappenDate))
|
||||
// R4 授信/现金标签:预付金腿定稿资金标签(选授信按剩余授信分配,不足跨界腿拆单),
|
||||
// 授信部分不产生资金流水(只写授信出入表占用),现金部分产生 应付预付金 记录;特批全现金。
|
||||
// 必须在 InitialPosition/AddPositionEvent 之前执行:实时持仓克隆与初始事件要继承"定稿后"的标签,
|
||||
// 拆单新拆出的授信腿也要被克隆、建事件(否则平仓返还分流会查到克隆腿上的旧标签/漏腿)。
|
||||
var generateMarginLegs = new List<swap_position>();
|
||||
foreach (var marginPosition in td.swap_positions)
|
||||
{
|
||||
var marginHappenDate = marginPositions.Key;
|
||||
foreach (var marginPosition in marginPositions)
|
||||
marginPosition.Obervation = DbContext.trade_obervation.FirstOrDefault(x => x.PositionId == marginPosition.id);
|
||||
bool generateMargin = true;
|
||||
if (marginPosition.Obervation != null)
|
||||
{
|
||||
marginPosition.Obervation = DbContext.trade_obervation.FirstOrDefault(x => x.PositionId == marginPosition.id);
|
||||
bool generateMargin = true;
|
||||
if (marginPosition.Obervation != null)
|
||||
if (!marginPosition.Obervation.IsDeductPrincipal)
|
||||
{
|
||||
if (!marginPosition.Obervation.IsDeductPrincipal)
|
||||
{
|
||||
generateMargin = false;
|
||||
}
|
||||
}
|
||||
if (generateMargin)
|
||||
{
|
||||
var marginAmount = marginPosition.InterestPrincipalFix * (marginPosition.InterestDirection == 1 ? -1 : 1);
|
||||
cashService.SaveSwapTradeClientCash(td,Convert.ToDouble( marginAmount), marginHappenDate.HasValue ? marginHappenDate.Value : td.TradeDate.Value, marginPosition.id, ClientCashInCashOut.系统操作_应付预付金);
|
||||
generateMargin = false;
|
||||
}
|
||||
}
|
||||
if (generateMargin)
|
||||
{
|
||||
generateMarginLegs.Add(marginPosition);
|
||||
}
|
||||
}
|
||||
new SwapFundTagService(this).ApplyMarginFundTags(td, generateMarginLegs, cashService, ignoreMoneyCheck);
|
||||
//标签定稿(含拆单)完成后,再克隆实时持仓、生成初始事件——克隆继承定稿标签
|
||||
swapTradeService.InitialPosition(td);
|
||||
swapTradeService.AddPositionEvent(td, optlog);
|
||||
// 追保规则-合约维度盯市:互换无预付金腿时(td.swap_positions 在上方已过滤为只剩5/6预付金腿,为空即无预付金腿),
|
||||
// 按计算保证金补一条交易级(positionId=0)初始预付金资金记录(入金),与按腿生成的记录去重。
|
||||
// 客户维度盯市(null/1)不生成出入金,维持现状;有预付金腿的互换沿用上方既有记录,不在此重复生成。
|
||||
// R4:资金来源是预付金腿上的录入项,无腿即无从选择授信,本交易级记录恒为现金。
|
||||
var watchClient = DataCacheProvider.GetClientDataSource().GetData(td.ClientId);
|
||||
if (watchClient != null && watchClient.MarginWatchRule == 0 && !td.swap_positions.Any())
|
||||
{
|
||||
var initMargin = cashService.GetInitialMarginAmount(td);
|
||||
if (initMargin > 0)
|
||||
{
|
||||
cashService.SaveSwapTradeClientCash(td, initMargin, td.TradeDate.Value, 0, ClientCashInCashOut.系统操作_应付预付金);
|
||||
}
|
||||
}
|
||||
var swap_event= swapEventService.UpdateSwapEvent();
|
||||
swapEventService.AddSwapEventDate(openDate.Value, td.id, (int)SwapEventTypeEnum.确认交易, "", 0, true, history);
|
||||
|
||||
@@ -542,7 +542,7 @@ namespace YLErp.Modules.TradeModule.DealModule
|
||||
{
|
||||
SwapTradeService swapTradeService = new SwapTradeService(this);
|
||||
new SwapEventService(UserInfo).AddSwapEventDate(DateTime.Now.Date, td.id, (int)SwapEventTypeEnum.审批通过, "", 0, true, req.comments);
|
||||
new TradeConfirmService(UserInfo).SwapTradeConfirm(td, "确认交易",false, optlog: "手工操作");
|
||||
new TradeConfirmService(UserInfo).SwapTradeConfirm(td, "确认交易",false, optlog: "手工操作", ignoreMoneyCheck: req.ignoreMoneyCheck);
|
||||
}
|
||||
//组合交易(非互换)
|
||||
if (td.IsGroup == 1)
|
||||
|
||||
@@ -16,7 +16,7 @@ const editConfig =
|
||||
{ name: "Name", label: "客户名称", type: "text", required: true },
|
||||
{ name: "MainProtocolCode", label: "主协议编号", type: "text" },
|
||||
{ name: "CustomerManagerId", label: "对冲交易询价对象", type: "select-m" },
|
||||
|
||||
{ name: "MarginWatchRule", label: "追保规则", required: true, type: "select" },
|
||||
|
||||
{ type: "new-col" },
|
||||
{ name: "Abbreviation", label: "客户简称", type: "text" },
|
||||
@@ -62,11 +62,13 @@ const editConfig =
|
||||
IsAssessmentResultChange: [{ text: "否", value: 0 }, { text: "是", value: 1 }],
|
||||
IsEvaluate: [{ text: "否", value: 0 }, { text: "是", value: 1 }],
|
||||
EvaluateOfValidity: [{ text: "----", value: "" }, "3个月", "6个月", "12个月", "长期"],
|
||||
MarginWatchRule: [{ text: "合约维度盯市", value: 0 }, { text: "客户维度盯市", value: 1 }],
|
||||
},
|
||||
defaults: {
|
||||
ClientRight: "4",
|
||||
SwapTradeType: 0,
|
||||
DerivativesInvestmentVarieties: "2"
|
||||
DerivativesInvestmentVarieties: "2",
|
||||
AccessRule: "2", MarginOptionType: "0", MarginWatchRule: "1",
|
||||
},
|
||||
openList: [
|
||||
{ name: 'id', label: '', hidden: true, optionHide: true, sortable: false, align: '', width: '', },
|
||||
@@ -97,5 +99,6 @@ const editConfig =
|
||||
{ name: 'SupProtocolCode', label: '补充协议编号', hidden: false, sortable: false, align: 'left', width: '150' },
|
||||
{ name: 'ProcessStatus', label: '开户状态', hidden: false, sortable: false, align: 'left', width: '90' },
|
||||
{ name: "Manager", label: "管理人名称", hidden: false, sortable: false, align: 'left', width: '100' },
|
||||
{ name: 'MarginWatchRule', label: '追保规则', hidden: false, sortable: false, align: 'left', width: '90' },
|
||||
]
|
||||
}
|
||||
@@ -80,6 +80,13 @@ namespace YLErp.Web.Controllers
|
||||
{
|
||||
c.VarietyName = GetWhiteListVarietyNames(c);
|
||||
}
|
||||
// R4:已使用授信(授信出入表 SUM(占用)−SUM(释放)+SUM(调整),按客户汇总)
|
||||
var usedCredits = Modules.SwapModule.ClientCreditInoutService.GetUsedCreditByClients(
|
||||
sList.rows.Where(x => x.ClientId > 0).Select(x => x.ClientId ?? 0).Distinct().ToList(), yldb);
|
||||
foreach (var c in sList.rows)
|
||||
{
|
||||
c.UsedCredit = usedCredits.TryGetValue(c.ClientId ?? 0, out var used) ? used : 0;
|
||||
}
|
||||
return Json(sList);
|
||||
}
|
||||
|
||||
@@ -94,6 +101,11 @@ namespace YLErp.Web.Controllers
|
||||
{
|
||||
var intid = DataProtectHelper.DecryptInt(enid);
|
||||
var r = yldb.credit.Find(intid);
|
||||
// R4:已使用授信(授信出入表)
|
||||
if (r != null && r.ClientId > 0)
|
||||
{
|
||||
r.UsedCredit = Modules.SwapModule.ClientCreditInoutService.GetUsedCredit(r.ClientId ?? 0, yldb);
|
||||
}
|
||||
return View(r);
|
||||
}
|
||||
|
||||
|
||||
@@ -302,6 +302,7 @@
|
||||
<tr>
|
||||
<th>收支方向</th>
|
||||
<th>资金类别</th>
|
||||
<th>资金来源</th>
|
||||
<th>发生日期</th>
|
||||
<th>金额</th>
|
||||
<th>币种</th>
|
||||
@@ -323,6 +324,13 @@
|
||||
<option value=6>追加预付金</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<select v-model="item.FundTag" style="width:86px;" title="资金标签:确认成交时按此选择定稿——授信检查剩余额度,不足自动拆分为授信+现金两条;未选默认现金">
|
||||
<option value="">默认(现金)</option>
|
||||
<option value="Cash">现金</option>
|
||||
<option value="Credit">授信</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<vue-datepicker :mindate="trade.TradeDate" :maxdate="trade.ExerciseDate" name="HappenDate" :holiday="1" v-model="item.HappenDate" style="width:100px;" ref="happenDateRefs" />
|
||||
</td>
|
||||
|
||||
@@ -271,6 +271,7 @@
|
||||
<tr>
|
||||
<th>收支方向</th>
|
||||
<th>资金类别</th>
|
||||
<th>资金标签</th>
|
||||
<th>发生日期</th>
|
||||
<th>金额</th>
|
||||
<th>币种</th>
|
||||
@@ -286,6 +287,10 @@
|
||||
<tr class="color-bule">
|
||||
<td class="@bgclass" style="width:116px !important;">@((SwapDirectionEnum)item.InterestDirection)</td>
|
||||
<td>@((InterestModeEnum)item.InterestMode)</td>
|
||||
<td>
|
||||
@*R4 资金标签(fund_tag 单列):录入时为用户选择,确认成交后为系统定稿(授信/现金)*@
|
||||
@(item.FundTag == ConsFundTag.Credit ? "授信" : "现金")
|
||||
</td>
|
||||
<td>@item.HappenDate.OtcFormatDate()</td>
|
||||
<td><span class="js-swap-common" data-value="@SwapCommonData(item.InterestPrincipalFix)" data-kind="amount"></span></td>
|
||||
<td>@item.Currency</td>
|
||||
|
||||
@@ -136,9 +136,16 @@
|
||||
|
||||
function checkSubmitData() {
|
||||
var pass = $('#creditEditForm').valid();
|
||||
if (page.UseClientStockEqvNotional) {
|
||||
if ((!$("#Credit").val() && !$("#StockEqvNotional").val())) {
|
||||
main.alert("名义本金规模和授信额度应至少填一项");
|
||||
if (pass) {
|
||||
if (page.UseClientStockEqvNotional) {
|
||||
if ((!$("#Credit").val() && !$("#StockEqvNotional").val())) {
|
||||
main.alert("名义本金规模和授信额度应至少填一项");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
var ratio = $("#MaxCreditUseRatio").val();
|
||||
if (ratio && (Number(ratio) < 0 || Number(ratio) > 1)) {
|
||||
main.alert("最大授信可用比例取值范围为0-1");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -346,6 +353,11 @@
|
||||
$("#MarginLimit").val(moneyStr);
|
||||
}
|
||||
|
||||
function moneyOnFocusOriginalCredit() {
|
||||
var moneyStr = $("#OriginalCredit").val().replace(/,/g, "");
|
||||
$("#OriginalCredit").val(moneyStr);
|
||||
}
|
||||
|
||||
//转换为大写金额
|
||||
function toAmountMoney(n) {
|
||||
|
||||
@@ -406,6 +418,9 @@
|
||||
|
||||
@Html.MyDecimalFor(model => model.Credit, new { onfocus = "moneyOnFocus()", onblur = "this.value=cc(this.value);" }, !pageObj.UseClientStockEqvNotional)
|
||||
<input type="hidden" value="@Model.Credit" name="CreditVal" id="CreditVal" />
|
||||
@Html.MyDecimalFor(model => model.OriginalCredit, new { onfocus = "moneyOnFocusOriginalCredit()", onblur = "this.value=cc(this.value);" }, required: false)
|
||||
@Html.MyDecimalFor(model => model.MaxCreditUseRatio, required: false)
|
||||
<span style="color:red">注:填写原始授信值后,授信额度将按"原始授信值×最大授信可用比例(空白按1)"自动计算.</span>
|
||||
@if (PS.Config.Company == CompanyEnum.中金)
|
||||
{
|
||||
@Html.MyDecimalFor(model => model.PFECredit, new { onkeyup = "changePFECredit()", onfocus = "pfeCreditOnFocus()", onblur = "this.value=cc(this.value);" }, true)
|
||||
|
||||
@@ -78,6 +78,9 @@
|
||||
<tr>@Html.MyDisplayFor(m => m.AuditStockEqvNotional)</tr>
|
||||
}
|
||||
<tr>@Html.MyDisplayFor(m => m.Credit)</tr>
|
||||
<tr>@Html.MyDisplayFor(m => m.OriginalCredit)</tr>
|
||||
<tr>@Html.MyDisplayFor(m => m.MaxCreditUseRatio)</tr>
|
||||
<tr>@Html.MyDisplayFor(m => m.UsedCredit)</tr>
|
||||
if (PS.Config.Company == CompanyEnum.中金)
|
||||
{
|
||||
<tr>@Html.MyDisplayFor(m => m.PFECredit)</tr>
|
||||
|
||||
@@ -108,6 +108,12 @@ const jqgrid1Mgr = (new function () {
|
||||
}
|
||||
colModelGrid = colModelGrid.concat([{
|
||||
name: 'Credit', label: '授信额度', index: 'Credit', width: 110, align: 'right', formatter: 'number'
|
||||
}, {
|
||||
name: 'OriginalCredit', label: '原始授信值', index: 'OriginalCredit', width: 110, align: 'right', formatter: 'number'
|
||||
}, {
|
||||
name: 'MaxCreditUseRatio', label: '最大授信可用比例', index: 'MaxCreditUseRatio', width: 110, align: 'right', formatter: 'number'
|
||||
}, {
|
||||
name: 'UsedCredit', label: '已使用授信', index: 'UsedCredit', width: 110, align: 'right', formatter: 'number'
|
||||
}, {
|
||||
name: 'AuditCredit', label: '授信审批规模', index: 'AuditCredit', width: 110, align: 'right', formatter: 'number'
|
||||
}, {
|
||||
@@ -145,6 +151,10 @@ const jqgrid1Mgr = (new function () {
|
||||
name: 'AuditStockEqvNotional', label: '名义本金审批规模', index: 'AuditStockEqvNotional', width: 110, align: 'right', formatter: 'number'
|
||||
}, {
|
||||
name: 'Credit', label: '授信额度', index: 'Credit', width: 110, align: 'right', formatter: 'number'
|
||||
}, {
|
||||
name: 'OriginalCredit', label: '原始授信值', index: 'OriginalCredit', width: 110, align: 'right', formatter: 'number'
|
||||
}, {
|
||||
name: 'MaxCreditUseRatio', label: '最大授信可用比例', index: 'MaxCreditUseRatio', width: 110, align: 'right', formatter: 'number'
|
||||
}, {
|
||||
name: 'PFECredit', label: 'PFE授信', index: 'PFECredit', width: 110, align: 'right', formatter: 'number'
|
||||
}, {
|
||||
|
||||
@@ -1741,6 +1741,7 @@ const vue = new Vue({
|
||||
IsAnnualized: true,//是否年化,
|
||||
HappenDate: thisObj.trade.TradeDate,//发生日期,
|
||||
Currency: 'CNY',//币种
|
||||
FundTag: '',//资金标签(R4):空=默认现金,可选授信,录入存选择、确认时系统定稿
|
||||
interest_rest_days: 7,//重置频率
|
||||
interest_rule: null//利率准则
|
||||
}
|
||||
|
||||
@@ -357,7 +357,8 @@ function confirmAllSelect() {
|
||||
ors.push(or);
|
||||
}
|
||||
var pData = { tradeids: ors.join(",") };
|
||||
pData.IsSkipCheck = true;
|
||||
// R4:不再硬编码 IsSkipCheck=true 静默跳过资金校验——普通确认走校验,
|
||||
// 资金不足时由 LackOfMoney 弹窗走显式特批(additionalProcessing)链路
|
||||
if (!main.isEmpty(additionalProcessing)) {
|
||||
pData.additionalProcessing = additionalProcessing;
|
||||
}
|
||||
|
||||
@@ -1057,7 +1057,8 @@ function confirmAllSelect() {
|
||||
ors.push(or);
|
||||
}
|
||||
var pData = { tradeids: ors.join(",") };
|
||||
pData.IsSkipCheck = true;
|
||||
// R4:不再硬编码 IsSkipCheck=true 静默跳过资金校验——普通确认走校验,
|
||||
// 资金不足时由 LackOfMoney 弹窗走显式特批(additionalProcessing)链路
|
||||
if (!main.isEmpty(additionalProcessing)) {
|
||||
pData.additionalProcessing = additionalProcessing;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user