Merge branch 'glms/feature/1.4.2' of http://git.yiliantech.com/gitlab/otc-dev/zszq-trs into glms/feature/1.4.2

This commit is contained in:
锦麟 王
2026-08-27 20:06:20 +08:00
17 changed files with 587 additions and 100 deletions
@@ -48,6 +48,10 @@ namespace YLErp.Web.Controllers
}
var marginTemplate = yldb.margin_template_v2.Find(clientMarginTemplate.MarginTemplateId);
if (marginTemplate == null)
{
throw new Exception("找不到有效的预付金模板");
}
var marginDetail = yldb.margin_template_detail.Where(x => x.MarginTemplateId == marginTemplate.id && x.ValueDate <= clientMarginTemplate.ValueDate).OrderByDescending(x => x.ValueDate).FirstOrDefault();
if (marginDetail == null && (marginTemplate.RuleType != (int)MarginRuleTypeEnum. && marginTemplate.RuleType != (int)MarginRuleTypeEnum. && marginTemplate.RuleType != (int)MarginRuleTypeEnum. && marginTemplate.RuleType != (int)MarginRuleTypeEnum.))
@@ -57,8 +61,14 @@ namespace YLErp.Web.Controllers
//一套规则多套默认参数的互斥验证
var existClientMarginTemplateOther = yldb.client_margin_template.Where(x => ((x.ClientId != 0 && x.ClientId == clientMarginTemplate.ClientId) || (x.ClientLevel != "" && x.ClientLevel == clientMarginTemplate.ClientLevel)) && x.ValueDate == clientMarginTemplate.ValueDate && x.id != clientMarginTemplate.id);
var existMarginTemplateIdsOther = existClientMarginTemplateOther.Select(x => x.MarginTemplateId).ToArray();
var existingTemplateIds = existClientMarginTemplateOther
.Select(x => x.MarginTemplateId)
.ToArray();
var existingMarginTemplates = yldb.margin_template_v2
.Where(x => existingTemplateIds.Contains(x.id))
.ToList()
.Where(x => margin_template_v2.AreBookScopesOverlapping(x.BookIds, marginTemplate.BookIds))
.ToList();
var msg = "";
if(clientMarginTemplate.ClientLevel == "")
{
@@ -72,17 +82,16 @@ namespace YLErp.Web.Controllers
if (marginTemplate.DefaultParameterType != 0)
{
//验证不同规则是否已经存在该交易类型
var marginTemplateDefault = yldb.margin_template_v2.Where(x => existMarginTemplateIdsOther.Contains(x.id) && x.RuleType != marginTemplate.RuleType);
var existTradeTypes = marginTemplateDefault.Select(x => x.TradeTypes).ToList();
var tradeTypes = new List<string>();
existTradeTypes.ForEach(x => tradeTypes.AddRange(x.Split(',')));
var marginTemplateDefault = existingMarginTemplates
.Where(x => x.RuleType != marginTemplate.RuleType)
.ToList();
var tradeTypes = margin_template_v2.GetOverlappingTradeTypes(marginTemplateDefault, marginTemplate.BookIds).ToList();
//验证相同规则相同的默认参数类型,是否包含相同的适用结构
var sameRuleParameterRecord = yldb.margin_template_v2.FirstOrDefault(x => existMarginTemplateIdsOther.Contains(x.id) && x.RuleType == marginTemplate.RuleType && x.DefaultParameterType == marginTemplate.DefaultParameterType);
if (sameRuleParameterRecord != null)
{
tradeTypes.AddRange(sameRuleParameterRecord.TradeTypes.Split(','));
}
var sameRuleParameterRecords = existingMarginTemplates
.Where(x => x.RuleType == marginTemplate.RuleType && x.DefaultParameterType == marginTemplate.DefaultParameterType)
.ToList();
tradeTypes.AddRange(margin_template_v2.GetOverlappingTradeTypes(sameRuleParameterRecords, marginTemplate.BookIds));
tradeTypes.ForEach(x =>
{
@@ -94,10 +103,8 @@ namespace YLErp.Web.Controllers
}
else
{
var marginTemplateDefault = yldb.margin_template_v2.Where(x => existMarginTemplateIdsOther.Contains(x.id));
var existTradeTypes = marginTemplateDefault.Select(x => x.TradeTypes).ToList();
List<string> tradeTypes = new List<string>();
existTradeTypes.ForEach(x => tradeTypes.AddRange(x.Split(',')));
var marginTemplateDefault = existingMarginTemplates;
var tradeTypes = margin_template_v2.GetOverlappingTradeTypes(marginTemplateDefault, marginTemplate.BookIds).ToList();
tradeTypes.ForEach(x =>
{
if (marginTemplate.TradeTypes.Contains(x))
@@ -202,4 +209,4 @@ namespace YLErp.Web.Controllers
}
}
}
}
}
@@ -247,6 +247,9 @@ namespace YLErp.Web.Controllers
{
try
{
marginTemplate.BookIds = margin_template_v2.NormalizeBookIds(marginTemplate.BookIds);
ValidateBookIds(marginTemplate.BookIds);
if (string.IsNullOrEmpty(marginTemplate.TradeTypes))
{
throw new Exception("默认规则的适用结构不能为空");
@@ -263,17 +266,18 @@ namespace YLErp.Web.Controllers
if (marginTemplate.DefaultParameterType != 0)
{
//验证不同规则是否已经存在该交易类型
var marginTemplateDefault = yldb.margin_template_v2.Where(x => x.id != marginTemplate.id && x.IsDefault && x.IsValid && !x.IsForClient && x.RuleType != marginTemplate.RuleType && x.ValueDate == marginTemplate.ValueDate);
var existTradeTypes = marginTemplateDefault.Select(x => x.TradeTypes).ToList();
var tradeTypes = new List<string>();
existTradeTypes.ForEach(x => tradeTypes.AddRange(x.Split(',')));
var marginTemplateDefault = yldb.margin_template_v2
.Where(x => x.id != marginTemplate.id && x.IsDefault && x.IsValid && !x.IsForClient && x.RuleType != marginTemplate.RuleType && x.ValueDate == marginTemplate.ValueDate)
.ToList()
.Where(x => margin_template_v2.AreBookScopesOverlapping(x.BookIds, marginTemplate.BookIds));
var tradeTypes = margin_template_v2.GetOverlappingTradeTypes(marginTemplateDefault, marginTemplate.BookIds).ToList();
//验证相同规则相同的默认参数类型,是否包含相同的适用结构
var sameRuleParameterRecord = yldb.margin_template_v2.FirstOrDefault(x => x.id != marginTemplate.id && x.IsDefault && x.IsValid && !x.IsForClient && x.RuleType == marginTemplate.RuleType && x.DefaultParameterType == marginTemplate.DefaultParameterType && (x.BuySellType & marginTemplate.BuySellType) > 0 && (x.MarginScenarios & marginTemplate.MarginScenarios) > 0 && x.ValueDate == marginTemplate.ValueDate);
if (sameRuleParameterRecord != null)
{
tradeTypes.AddRange(sameRuleParameterRecord.TradeTypes.Split(','));
}
var sameRuleParameterRecords = yldb.margin_template_v2
.Where(x => x.id != marginTemplate.id && x.IsDefault && x.IsValid && !x.IsForClient && x.RuleType == marginTemplate.RuleType && x.DefaultParameterType == marginTemplate.DefaultParameterType && (x.BuySellType & marginTemplate.BuySellType) > 0 && (x.MarginScenarios & marginTemplate.MarginScenarios) > 0 && x.ValueDate == marginTemplate.ValueDate)
.ToList()
.Where(x => margin_template_v2.AreBookScopesOverlapping(x.BookIds, marginTemplate.BookIds));
tradeTypes.AddRange(margin_template_v2.GetOverlappingTradeTypes(sameRuleParameterRecords, marginTemplate.BookIds));
tradeTypes.ForEach(x =>
{
@@ -285,10 +289,11 @@ namespace YLErp.Web.Controllers
}
else
{
var marginTemplateDefault = yldb.margin_template_v2.Where(x => x.id != marginTemplate.id && x.IsDefault && x.IsValid && !x.IsForClient && (x.BuySellType & marginTemplate.BuySellType) > 0 && (x.MarginScenarios & marginTemplate.MarginScenarios) > 0 && x.ValueDate == marginTemplate.ValueDate);
var existTradeTypes = marginTemplateDefault.Select(x => x.TradeTypes).ToList();
var tradeTypes = new List<string>();
existTradeTypes.ForEach(x => tradeTypes.AddRange(x.Split(',')));
var marginTemplateDefault = yldb.margin_template_v2
.Where(x => x.id != marginTemplate.id && x.IsDefault && x.IsValid && !x.IsForClient && (x.BuySellType & marginTemplate.BuySellType) > 0 && (x.MarginScenarios & marginTemplate.MarginScenarios) > 0 && x.ValueDate == marginTemplate.ValueDate)
.ToList()
.Where(x => margin_template_v2.AreBookScopesOverlapping(x.BookIds, marginTemplate.BookIds));
var tradeTypes = margin_template_v2.GetOverlappingTradeTypes(marginTemplateDefault, marginTemplate.BookIds).ToList();
tradeTypes.ForEach(x =>
{
if (marginTemplate.TradeTypes.Contains(x))
@@ -471,6 +476,7 @@ namespace YLErp.Web.Controllers
r.Name = marginTemplate.Name;
r.IsDefault = marginTemplate.IsDefault;
r.IsForClient = marginTemplate.IsForClient;
r.BookIds = marginTemplate.BookIds;
r.Comments = marginTemplate.Comments;
r.RuleType = marginTemplate.RuleType;
r.TradeTypes = marginTemplate.TradeTypes;
@@ -595,6 +601,27 @@ namespace YLErp.Web.Controllers
}
}
private void ValidateBookIds(string bookIds)
{
if (string.IsNullOrWhiteSpace(bookIds))
{
return;
}
var ids = margin_template_v2.ParseBookIds(bookIds)
.Select(x => int.Parse(x))
.ToList();
var existingIds = yldb.assetunit
.Where(x => ids.Contains(x.id))
.Select(x => x.id)
.ToList();
var missingIds = ids.Except(existingIds).ToList();
if (missingIds.Any())
{
throw new Exception("适用簿记账户组包含不存在的簿记账户ID:" + string.Join(",", missingIds));
}
}
[HttpPost]
public JsonResult deleteEditmargin_template_v2(string enid)
{
@@ -732,4 +759,4 @@ namespace YLErp.Web.Controllers
}
}
}
+25 -2
View File
@@ -7980,7 +7980,11 @@ namespace YLErp.Web.Controllers
var trade = yldb.trade.Find(tradeId);
//R1 三层级(2026-08-24 配合修改):候选只列有效模板——已作废模板一旦绑定,
//解析一级"找到即停"会让该交易直接视为无预付金(仅留日志),必须从源头挡住
var marginTemplates = yldb.margin_template_v2.Where(x => x.IsValid && x.TradeTypes.Contains(trade.TradeType) && !x.IsDefault).ToList();
var marginTemplates = yldb.margin_template_v2
.Where(x => x.IsValid && x.TradeTypes.Contains(trade.TradeType) && !x.IsDefault)
.ToList()
.Where(x => x.IsApplicableToBook(trade.AssetId))
.ToList();
marginTemplates.ForEach(x => x.Details = yldb.margin_template_detail.Where(y => y.MarginTemplateId == x.id).ToList());
var latestTradeMarginTemplate = yldb.trade_margin_template.Where(x => x.TradeId == tradeId).OrderByDescending(x => x.ValueDate).FirstOrDefault();
@@ -8025,6 +8029,26 @@ namespace YLErp.Web.Controllers
throw new Exception("请选择有效的预付金模板");
}
var trade = yldb.trade.Find(tradeMarginTemplate.TradeId);
if (trade == null)
{
throw new Exception("交易不存在");
}
var marginTemplate = yldb.margin_template_v2
.FirstOrDefault(x => x.id == tradeMarginTemplate.MarginTemplateId
&& x.IsValid
&& !x.IsDefault
&& x.TradeTypes.Contains(trade.TradeType));
if (marginTemplate == null)
{
throw new Exception("请选择有效的预付金模板");
}
if (!marginTemplate.IsApplicableToBook(trade.AssetId))
{
throw new Exception("所选预付金模板不适用当前簿记账户");
}
var deletedRecords = yldb.trade_margin_template.Where(x => x.TradeId == tradeMarginTemplate.TradeId && x.ValueDate >= tradeMarginTemplate.ValueDate).ToArray();
yldb.trade_margin_template.RemoveRange(deletedRecords);
var updatedRecords = yldb.trade_margin_template.Where(x => x.TradeId == tradeMarginTemplate.TradeId && x.ValueDate < tradeMarginTemplate.ValueDate).ToList();
@@ -8040,7 +8064,6 @@ namespace YLErp.Web.Controllers
#region
var trade = yldb.trade.Find(tradeMarginTemplate.TradeId);
//R1 三层级(2026-08-24 配合修改):收益互换跳过系统重算——互换初始预付金为手工录入/预付金腿承载
//(方案B 引擎回落链消费 trade.InitialMargin),且试算链路(GetInitialMarginWithPreHandle 克隆 id=0
//对互换取不到模板绑定恒得 0,无条件重置会把手工录入值清零;与 TradeSaveService 保存守卫语义对齐
@@ -525,6 +525,25 @@ namespace YLErp.Web.Controllers
return JsonSuccess("", underlying);
}
/// <summary>
/// 查询上游基金档案中的基金管理人。查询失败或结果不唯一时返回可降级结果。
/// </summary>
[HttpGet]
public JsonResult GetFundManager(string code, string instrumentType)
{
if (!string.Equals(instrumentType, ConsGlobal.InstrumentType.Fund, StringComparison.OrdinalIgnoreCase))
{
return JsonSuccess("", new FundManagerLookupResult { Status = FundManagerLookupStatus.NotFound });
}
var result = new FundManagerLookupService().Lookup(code);
return JsonSuccess("", new
{
result.InvestAdvisorName,
IsUnique = result.Status == FundManagerLookupStatus.Unique
});
}
/// <summary>
/// 预付金参数
/// </summary>
+12 -2
View File
@@ -15,11 +15,20 @@
var jsClient = canChangeClient && Model.ClientId > 0 ? jsClients.FirstOrDefault(n => n.id == Model.ClientId) : null;
var jsAssetUnits = JsDataModel.GetAssetUnits(CurUser);
var tradeMarginTemplates = new tradeController().GetMarginTemplates();
var swapMarginTemplateItems = SwapMarginTemplateConfigService.GetConfig().options.ToList();
var allSwapMarginTemplateItems = SwapMarginTemplateConfigService.GetConfig().options.ToList();
var swapMarginTemplateItems = allSwapMarginTemplateItems.ToList();
//保证金模板V2迁移:存量交易的保证金模板可能是历史文本值(如字典配置的"现金保证金"),不在模板V2选项中时追加显示,避免编辑保存时被静默清空
if (!string.IsNullOrWhiteSpace(Model.MarginTemplateName) && !swapMarginTemplateItems.Any(n => n.Value == Model.MarginTemplateName))
{
swapMarginTemplateItems.Add(new YLErp.Models.SelectItem { Text = Model.MarginTemplateName + "(历史值)", Value = Model.MarginTemplateName });
var legacyMarginTemplateItem = new SwapMarginTemplateOption
{
Text = Model.MarginTemplateName + "(历史值)",
Value = Model.MarginTemplateName,
BookIds = null,
IsLegacy = true
};
allSwapMarginTemplateItems.Add(legacyMarginTemplateItem);
swapMarginTemplateItems.Add(legacyMarginTemplateItem);
}
var tradeMarginTemplateItems = new tradeController().GetMarginTemplateItems();
var jsAssetUnit = Model.AssetId > 0 ? jsAssetUnits.FirstOrDefault(n => n.id == Model.AssetId) : null;
@@ -75,6 +84,7 @@
jsTrader,
jsTraders,
tradeMarginTemplates = tradeMarginTemplates,
allSwapMarginTemplateItems = allSwapMarginTemplateItems,
swapMarginTemplateItems = swapMarginTemplateItems,
tradeMarginTemplateItems = tradeMarginTemplateItems,
needRemark = !isAdd && valuedateBLL.SystemDate.EditTradeNeedRemark,
@@ -13,6 +13,7 @@
isGFSM = PS.Config.Company == CompanyEnum.广发商贸,
etfSubtypeItems = YLErp.BLL.DictionaryBLL.GetDictionaryItems("ETF 子类").Select(x => x.Name).ToList()
};
var selectedBookIds = margin_template_v2.ParseBookIds(Model?.BookIds);
}
@section CSS{
@@ -63,6 +64,16 @@
</select>
</div>
<div class="form-group">
<label class="formlabel">适用簿记账户</label>
<select class="chosen-select" id="BookIds" multiple data-placeholder="请选择簿记账户">
@foreach (var item in AssetunitController.GetAllassetunit())
{
<option value="@item.Value" @(selectedBookIds.Contains(item.Value?.Trim() ?? "") ? "selected" : "")>@item.Text</option>
}
</select>
</div>
@if (PS.Config.Company != CompanyEnum.广发商贸)
{
<div class="form-group">
@@ -1,4 +1,4 @@
@using YLErp.Enums;
@using YLErp.Enums;
@model margin_template_v2
@{
@@ -12,6 +12,7 @@
detail = new margin_template_detail() { ValueDate = new DateTime(2020, 1, 1), ComparedInitialMarginRate = 1 },
etfSubtypeItems = YLErp.BLL.DictionaryBLL.GetDictionaryItems("ETF 子类").Select(x => x.Name).ToList()
};
var selectedBookIds = margin_template_v2.ParseBookIds(Model?.BookIds);
}
@section CSS{
@@ -63,6 +64,16 @@
</select>
</div>
<div class="form-group">
<label class="formlabel">适用簿记账户</label>
<select class="chosen-select" id="BookIds" multiple data-placeholder="请选择簿记账户">
@foreach (var item in AssetunitController.GetAllassetunit())
{
<option value="@item.Value" @(selectedBookIds.Contains(item.Value?.Trim() ?? "") ? "selected" : "")>@item.Text</option>
}
</select>
</div>
<div class="form-group">
<label class="formlabel">适用买卖方向</label>
<select class="chosen-select" id="BuySellType" multiple>
@@ -1,8 +1,20 @@
//定价格式化(来自配置)
//定价格式化(来自配置)
const inputFormatPercent = Object.freeze({ precision: 2, append: '%' });
const inputFormatPercentNegative = Object.freeze({ precision: 2, append: '%', negative: true });
const inputFormatNegative = Object.freeze({ precision: 2, negative: true });
function normalizeBookIds(values) {
var ids = Array.isArray(values) ? values : (values ? [values] : []);
var normalized = [];
ids.forEach(function (id) {
id = (id || '').toString().trim();
if (id && normalized.indexOf(id) < 0) {
normalized.push(id);
}
});
return normalized.length ? normalized.join(',') : null;
}
const MarginRuleTypeEnum = {
无预付金: "0",
按固定利率: "1",
@@ -271,6 +283,7 @@ const vue = new Vue({
return;
}
this.marginTemplate.TradeTypes = $("#tradeTypes").val().join(",");
this.marginTemplate.BookIds = normalizeBookIds($("#BookIds").val());
this.marginTemplate.IsDefault = true;
this.marginTemplate.IsForClient = isForClient;
this.marginTemplate.BuySellType = this.sum($("#BuySellType").val());
@@ -619,4 +632,4 @@ $(function () {
});
SetAceDropDown();
});
});
@@ -264,6 +264,23 @@ const vue = new Vue({
});
},
methods: {
refreshMarginTemplateOptions() {
var assetId = Number(this.trade.AssetId) || 0;
var assetToken = assetId.toString();
var allItems = this.page.allSwapMarginTemplateItems || [];
this.page.swapMarginTemplateItems = allItems.filter(function (item) {
if (!assetId || item.IsLegacy || !item.BookIds) {
return true;
}
return ("," + item.BookIds + ",").split(",").some(function (token) {
return token.trim() === assetToken;
});
});
if (this.trade.MarginTemplateName
&& !this.page.swapMarginTemplateItems.some(function (item) { return item.Value === this.trade.MarginTemplateName; }, this)) {
this.trade.MarginTemplateName = "";
}
},
getQuantityInputFormat(item) {
return swapPricePrecision.getCommonInputFormat(
'quantity',
@@ -1914,7 +1931,7 @@ function __init(vue) {
});
let traderId = page.Trade.TraderId;
let trader = traderId ? consTraders.find(x => x.id === traderId) : null;
if (!trader && page.IsNew) {
if (!trader && page.isAdd) {
trader = consTraders[0];
trader && (vue.trade.TraderId = trader.id);
}
@@ -1926,15 +1943,17 @@ function __init(vue) {
nameField: 'Name', valueField: 'id', searchField: ['Name', 'PinYin'], lookup: consAssetUnits,
onSelect(data) {
vue.trade.AssetId = data.id;
vue.refreshMarginTemplateOptions();
}
});
let assetId = page.Trade.AssetId;
let asset = assetId ? consAssetUnits.find(x => x.id === assetId) : null;
if (!asset && page.IsNew) {
if (!asset && page.isAdd) {
asset = consAssetUnits[0];
asset && (vue.trade.AssetId = asset.id);
}
autoAssetUnit.setData(asset);
vue.refreshMarginTemplateOptions();
//客户名称
if (page.canChangeClient) {
@@ -1948,7 +1967,7 @@ function __init(vue) {
});
let clientId = page.Trade.ClientId;
let client = clientId ? consClients.find(x => x.id === clientId) : null;
!client && page.IsNew && (client = consClients[0]);
!client && page.isAdd && (client = consClients[0]);
autoClient.setData(client);
_getMainProtocolCode(client);
}
@@ -17,6 +17,10 @@
const consSelect = ['MarketCode', 'UnderlyingInstrumentType', 'UnderlyingState', 'UnderlyingStatus', 'UpDownLimitType', 'EtfSubType'];
var autoUpDownLimit, autoVariety;
var fundManagerLookupSeq = 0;
var fundManagerLookupTimer = null;
var fundManagerLookupXhr = null;
var fundManagerManualEdit = false;
$(function () {
@@ -46,6 +50,12 @@ $(function () {
lookup: ylotc.varieties
});
$('#InvestAdvisorName').on('input', function () {
fundManagerManualEdit = true;
});
$('#UnderlyingCode').on('input', refreshFundManagerLookup);
for (var i = 1; i <= 5; i++) {
let datas = ylotc.underlyingBlocks.filter(x => x.Group === i);
let autoBlock = FastVue.autocomplete(document.getElementById('inputBlock' + i), {
@@ -112,9 +122,57 @@ $(function () {
break;
}
$('#editForm').removeClass("form-None form-Stock form-CommodityFutures form-CommoditySpot form-Bonds form-Fund").addClass("form-" + classType);
refreshFundManagerLookup();
}).trigger('change');
});
function refreshFundManagerLookup() {
var requestSeq = ++fundManagerLookupSeq;
if (fundManagerLookupTimer) {
clearTimeout(fundManagerLookupTimer);
fundManagerLookupTimer = null;
}
if (fundManagerLookupXhr) {
fundManagerLookupXhr.abort();
fundManagerLookupXhr = null;
}
var codeInput = $('#UnderlyingCode');
var typeInput = $('#UnderlyingInstrumentType');
var managerInput = $('#InvestAdvisorName');
if (page.Model.id > 0 || !codeInput.length || !typeInput.length ||
typeInput.val() !== 'Fund' || !codeInput.val() || !managerInput.length) {
return;
}
var codeAtRequest = codeInput.val();
var managerAtRequest = managerInput.val() || '';
var manualAtRequest = fundManagerManualEdit;
fundManagerLookupTimer = setTimeout(function () {
fundManagerLookupTimer = null;
fundManagerLookupXhr = $.ajax({
url: '/underlying_manager/GetFundManager',
method: 'GET',
data: { code: codeAtRequest, instrumentType: 'Fund' }
}).done(function (resp) {
if (requestSeq !== fundManagerLookupSeq || page.Model.id > 0 ||
$('#UnderlyingCode').val() !== codeAtRequest ||
$('#UnderlyingInstrumentType').val() !== 'Fund' ||
manualAtRequest || fundManagerManualEdit) {
return;
}
var result = resp && resp.obj;
if (result && result.IsUnique && result.InvestAdvisorName) {
managerInput.val(result.InvestAdvisorName);
fundManagerManualEdit = false;
}
}).always(function () {
fundManagerLookupXhr = null;
});
}, 200);
}
function saveData() {
var data = $('#editForm').serializeObject();