Merge remote-tracking branch 'dest/glms/feature/1.4.2' into feature/p132_74-risk-engine

This commit is contained in:
lisong
2026-07-17 15:41:32 +08:00
101 changed files with 13790 additions and 1083 deletions
@@ -1,4 +1,6 @@
using Org.BouncyCastle.Ocsp;
using Newtonsoft.Json;
using YLErp.Helpers;
using YLErp.DBModels.Enums;
using YLErp.Model.Enum;
using YLErp.Modules.AppModule;
@@ -43,7 +45,7 @@ namespace YLErp.Web.Controllers
[HttpPost]
public ActionResult AddProcess(string type, List<ApprovalProcessAddRequest> data)
{
if (type == "TradeProcess" && data != null && data.Count > 0)
if ((type == "TradeProcess" || type == "CloseProcess") && data != null && data.Count > 0)
{
foreach (var item in data)
{
@@ -54,6 +56,25 @@ namespace YLErp.Web.Controllers
}
}
// 校验节点触发条件 JSON 格式,避免非法数据入库
if (data != null)
{
foreach (var item in data)
{
if (!string.IsNullOrWhiteSpace(item.triggerCondition))
{
try
{
JsonConvert.DeserializeObject<ConditionExpressionConfig>(item.triggerCondition);
}
catch
{
return JsonError("触发条件格式非法,请检查括号与条件是否完整");
}
}
}
}
new ApprovalProcessService(CurUser).AddProcess(type, data);
return JsonSuccess("设置成功");
}
@@ -66,10 +87,13 @@ namespace YLErp.Web.Controllers
var tradeProcess = list.Where(s => s.processType == "TradeProcess").OrderBy(s => s.order).ThenBy(s => s.parentNode).ThenBy(s => s.node).ToList();
// 需求②:了结/平仓/行权审批流程
var closeProcess = list.Where(s => s.processType == "CloseProcess").OrderBy(s => s.order).ThenBy(s => s.parentNode).ThenBy(s => s.node).ToList();
var creditProcess = list.Where(s => s.processType == "CreditProcess").OrderBy(s => s.order).ToList();
var outCashProcess = list.Where(s => s.processType == "OutCashProcess").OrderBy(s => s.order).ToList();
var clientProcess = list.Where(s => s.processType == "ClientProcess").OrderBy(s => s.order).ThenBy(s => s.parentNode).ThenBy(s => s.node).ToList();
return Json(new { OpenProcess = openProcess, TradeProcess = tradeProcess, CreditProcess = creditProcess, OutCashProcess= outCashProcess,ClientProcess = clientProcess });
return Json(new { OpenProcess = openProcess, TradeProcess = tradeProcess, CloseProcess = closeProcess, CreditProcess = creditProcess, OutCashProcess= outCashProcess,ClientProcess = clientProcess });
}
+142 -7
View File
@@ -1,4 +1,5 @@
using YLErp.DBModels;
using YLErp.Core;
using YLErp.DBModels;
using YLErp.Modules.EodModule;
namespace YLErp.Web.Controllers
@@ -54,9 +55,13 @@ namespace YLErp.Web.Controllers
public ActionResult EodFuturePriceEdit(string enid)
{
if (string.IsNullOrEmpty(enid) || enid == "0")
bool isNew = string.IsNullOrEmpty(enid) || enid == "0";
ViewBag.IsNew = isNew;
if (isNew)
{
return View(new eod_commodity_future_price());
// 新建:手工输入标的代码,失焦时调 LookupUnderlyingForEod 校验并带出市场/UnderlyingId。
// 默认估值日期=今天:避免未填日期时落库为 0001-01-01、落在列表默认窗口(今天)之外而查不出。
return View(new eod_commodity_future_price { ValueDate = DateTime.Today });
}
var intid = DecryptInt(enid);
var dbmodel = yldb.eod_commodity_future_price.Find(intid);
@@ -70,9 +75,12 @@ namespace YLErp.Web.Controllers
public ActionResult EodStockPriceEdit(string enid)
{
if (string.IsNullOrEmpty(enid) || enid == "0")
bool isNew = string.IsNullOrEmpty(enid) || enid == "0";
ViewBag.IsNew = isNew;
if (isNew)
{
return View(new eod_stock_price());
// 新建:手工输入标的代码,失焦时调 LookupUnderlyingForEod 校验并带出市场。
return View(new eod_stock_price { ValueDate = DateTime.Today });
}
var intid = DecryptInt(enid);
var dbmodel = yldb.eod_stock_price.Find(intid);
@@ -85,9 +93,13 @@ namespace YLErp.Web.Controllers
}
public ActionResult EodBondPriceEdit(string enid)
{
if (string.IsNullOrEmpty(enid) || enid == "0")
bool isNew = string.IsNullOrEmpty(enid) || enid == "0";
ViewBag.IsNew = isNew;
if (isNew)
{
return View(new ChinaBondValuation());
// 新建:手工输入债券代码(bond_id),失焦时调 LookupUnderlyingForEod 校验并带出市场。
// 默认估值日期=今天:避免未填日期时落库为 0001-01-01、落在列表默认窗口(今天)之外而查不出。
return View(new ChinaBondValuation { valuation_date = DateTime.Today });
}
var intid = DecryptLong(enid);
var dbmodel = yldb.china_bond_valuation.Find(intid);
@@ -98,6 +110,129 @@ namespace YLErp.Web.Controllers
ViewData["市场"] = DataCacheProvider.GetUnderlyingDataSource().GetData(dbmodel.bond_id)?.MarketName;
return View(dbmodel);
}
/// <summary>
/// 新建日终价格时,按用户手工输入的标的代码精确查一条标的,带出名称/市场/Id。
/// 只返回已上市(LaunchState=="1")且类型匹配的标的——与列表查询 inner join 的过滤对齐,
/// 从源头杜绝"新增能存但查不出"的幽灵记录。前端在输入框失焦时调用,不依赖任何下拉/补全插件。
/// </summary>
/// <param name="code">标的代码(用户手工输入)</param>
/// <param name="kind">bond | future | stock,决定允许的标的类型集合</param>
[HttpPost]
public JsonResult LookupUnderlyingForEod(string code, string kind)
{
if (string.IsNullOrWhiteSpace(code))
{
return JsonError("请输入标的代码");
}
code = code.Trim();
string[] types = kind switch
{
"future" => new[]
{
ConsGlobal.InstrumentType.CommodityFutures,
ConsGlobal.InstrumentType.GoldFutures,
ConsGlobal.InstrumentType.TBFutures,
ConsGlobal.InstrumentType.OtherFutures,
ConsGlobal.InstrumentType.AbroadFutures,
},
"stock" => new[]
{
ConsGlobal.InstrumentType.Stock,
ConsGlobal.InstrumentType.StockIndex,
ConsGlobal.InstrumentType.StockIF,
ConsGlobal.InstrumentType.HKStock,
ConsGlobal.InstrumentType.HKStockIndex,
ConsGlobal.InstrumentType.NewOtcStock,
ConsGlobal.InstrumentType.AbroadStock,
ConsGlobal.InstrumentType.AbroadStockIndex,
},
_ => new[]
{
ConsGlobal.InstrumentType.Bonds,
ConsGlobal.InstrumentType.TBonds,
ConsGlobal.InstrumentType.CreditBonds,
ConsGlobal.InstrumentType.OtherBonds,
},
};
var set = new HashSet<string>(types);
var u = yldb.underlying_manager
.Where(n => n.UnderlyingCode == code && n.LaunchState == "1" && set.Contains(n.UnderlyingInstrumentType))
.Select(n => new { n.id, n.UnderlyingCode, n.UnderlyingName, n.MarketName })
.FirstOrDefault();
if (u == null)
{
return JsonError("未找到该标的(不存在、未上市或类型不匹配),无法录入");
}
return JsonSuccess("", new
{
Id = u.id,
Code = u.UnderlyingCode,
Name = u.UnderlyingName,
Market = u.MarketName,
});
}
/// <summary>
/// 新建日终价格时,按手工输入的片段做服务端模糊联想(代码或名称包含匹配),只回前 20 条。
/// 与 LookupUnderlyingForEod 同样只返回已上市(LaunchState=="1")且类型匹配的标的。
/// 用原生下拉渲染(不依赖 jQuery UI,bundle 未打包),避免几十万标的全量渲染卡死。
/// </summary>
[HttpPost]
public JsonResult SuggestUnderlyingForEod(string q, string kind)
{
if (string.IsNullOrWhiteSpace(q))
{
return JsonSuccess("", new List<object>());
}
q = q.Trim();
string[] types = kind switch
{
"future" => new[]
{
ConsGlobal.InstrumentType.CommodityFutures,
ConsGlobal.InstrumentType.GoldFutures,
ConsGlobal.InstrumentType.TBFutures,
ConsGlobal.InstrumentType.OtherFutures,
ConsGlobal.InstrumentType.AbroadFutures,
},
"stock" => new[]
{
ConsGlobal.InstrumentType.Stock,
ConsGlobal.InstrumentType.StockIndex,
ConsGlobal.InstrumentType.StockIF,
ConsGlobal.InstrumentType.HKStock,
ConsGlobal.InstrumentType.HKStockIndex,
ConsGlobal.InstrumentType.NewOtcStock,
ConsGlobal.InstrumentType.AbroadStock,
ConsGlobal.InstrumentType.AbroadStockIndex,
},
_ => new[]
{
ConsGlobal.InstrumentType.Bonds,
ConsGlobal.InstrumentType.TBonds,
ConsGlobal.InstrumentType.CreditBonds,
ConsGlobal.InstrumentType.OtherBonds,
},
};
var set = new HashSet<string>(types);
var list = yldb.underlying_manager
.Where(n => n.LaunchState == "1" && set.Contains(n.UnderlyingInstrumentType)
&& (n.UnderlyingCode.Contains(q) || n.UnderlyingName.Contains(q)))
.OrderBy(n => n.UnderlyingCode)
.Take(20)
.Select(n => new { n.id, Code = n.UnderlyingCode, Name = n.UnderlyingName, Market = n.MarketName })
.ToList();
return JsonSuccess("", list);
}
[HttpPost]
public JsonResult EodFuturePriceEditJson(eod_commodity_future_price req)
{
+5 -2
View File
@@ -283,9 +283,12 @@ namespace YLErp.Web.Controllers
/// <param name="tradeId"></param>
/// <param name="closePercent"></param>
/// <returns></returns>
public JsonResult GetUnwindInterestList(DateTime valueDate,DateTime unwindDate, int tradeId, decimal closePercent, int eventType)
public JsonResult GetUnwindInterestList(DateTime valueDate,DateTime unwindDate, int tradeId, decimal closePercent, int eventType, decimal notionalValue = 0, decimal posiNotionalValue = 0)
{
var interests = new SwapDealService(CurUser).GetUnwindInterests(valueDate, unwindDate, tradeId, closePercent, eventType);
// 前端按"占期初(original)"语义传 closePercent(A);后端 GetUnwindInterests 按"占剩余(remaining)"语义(B)计算。
// 多空互换前端不传 notionalValue/posiNotionalValue(默认 0),则跳过转换保持原行为。
var convertedClosePercent = SwapDealService.ToRemainingClosePercent(closePercent, notionalValue, posiNotionalValue);
var interests = new SwapDealService(CurUser).GetUnwindInterests(valueDate, unwindDate, tradeId, convertedClosePercent, eventType);
foreach (var interest in interests)
{
interest.TdInterestAmount=Math.Round(interest.TdInterestAmount, ConsGlobal.MoneyRound,MidpointRounding.AwayFromZero);
@@ -296,5 +296,13 @@ namespace YLErp.Web.Controllers
CalendarBLL.ResetCalendarForQdp();
return JsonSuccess("删除成功");
}
[HttpPost]
public JsonResult Reset()
{
CalendarBLL.IsListOld = true;
CalendarBLL.ResetCalendarForQdp();
return JsonSuccess("重置成功");
}
}
}
+10 -7
View File
@@ -6370,14 +6370,17 @@ namespace YLErp.Web.Controllers
return ShowError("请选择文档类型!");
}
if (req.TradeDateStart == null) { req.TradeDateStart = DateTime.MinValue; }
if (req.TradeDateEnd == null) { req.TradeDateEnd = DateTime.MaxValue; }
var db_trade_contract_r = yldb.trade_contract_r.AsQueryable();
var documentQuery = TradeConfirmationDocumentQuery.Create(
yldb.trade_contract_document,
yldb.trade_contract_r,
yldb.trade,
req.TradeDateStart,
req.TradeDateEnd);
var query = from doc in yldb.trade_contract_document
join r in db_trade_contract_r
on doc.Code equals r.ContractCode
where doc.Type == ContractTypeEnum.Trade && doc.ValueDate >= req.TradeDateStart && doc.ValueDate <= req.TradeDateEnd && r.IsValid
var query = from item in documentQuery
let doc = item.Document
let r = item.Relation
where doc.Type == ContractTypeEnum.Trade
select new
{
doc.ClientId,
+1 -1
View File
@@ -148,7 +148,7 @@ namespace YLErp.Web.Controllers
{
valueDate = QdpCalendarHelper.GetNonHolidayDefore(valueDate);
}
if (!EodPriceQueryService.TryGetEodPrice(valueDate, td.UnderlyingCode, out _))
if (!EodPriceQueryService.TryGetSettlementEodPrice(valueDate, td.UnderlyingCode, out _))
{
return JsonError($"交易日{valueDate:yyyy-MM-dd}的结算价或收盘价未找到!");
}