BugFix 1.ETF没有期限 2.收盘价和净价展示
This commit is contained in:
@@ -299,8 +299,8 @@ namespace YLErp.Web.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
//区间追保结构 + 按资产类型分类时,期限档仅允许"允许分期限档的资产类型"(利率债)或"允许分档的 ETF 子类"
|
||||
//(可转债 ETF/科创债 ETF,行 UnderlyingType=基金 + EtfKind=子类,不加枚举位)配置,防止非分档类型误配期限档;
|
||||
//区间追保结构 + 按资产类型分类时,期限档仅允许利率债配置(2026-08-27 裁定:ETF 无期限概念,
|
||||
//可转债 ETF/科创债 ETF 的分档白名单已移除,所有 ETF 子类/基金行一律单套参数不分档),防止非分档类型误配期限档;
|
||||
//该校验按行生效,与明细行数无关(单行明细同样拦截);
|
||||
//按严格掩码判定:行标的类型位必须全部落在可分档类型内(混合标志位如 利率债|信用债 配期限档同样拦截,与取数侧整行期限档过滤语义一致);
|
||||
//UnderlyingType 为空的通配行允许配期限档(取数侧期限档过滤在前、通配匹配在后,语义自洽)
|
||||
@@ -308,34 +308,24 @@ namespace YLErp.Web.Controllers
|
||||
&& marginTemplate.UnderlyingSeperateType == (int)UnderlyingSeperateTypeEnum.CustomInstrumentType)
|
||||
{
|
||||
var enabledMask = ConsMarginTerm.TermTierEnabledUnderlyingTypes.Aggregate(UnderlyingTypeEnum.None, (a, t) => a | t);
|
||||
var tierEnabledEtfKinds = ConsMarginTerm.TermTierEnabledEtfKinds;
|
||||
foreach (var detail in marginTemplate.Details)
|
||||
{
|
||||
var etfKind = detail.SpanConfig?.EtfKind;
|
||||
var bondTerm = detail.SpanConfig?.BondTerm;
|
||||
//ETF 子类行:仅允许纯基金行(配置页子类选择器也只在纯基金区块出现)
|
||||
if (!string.IsNullOrEmpty(etfKind) && detail.UnderlyingType != UnderlyingTypeEnum.Fund)
|
||||
{
|
||||
throw new Exception("配置了 ETF 子类(" + etfKind + ")的参数组资产类型必须为 基金及基金专户");
|
||||
}
|
||||
var isTierEnabledEtfKind = tierEnabledEtfKinds.Contains(etfKind ?? "");
|
||||
if (!string.IsNullOrEmpty(detail.SpanConfig?.BondTerm)
|
||||
&& (detail.UnderlyingType & ~enabledMask) != UnderlyingTypeEnum.None
|
||||
&& !isTierEnabledEtfKind)
|
||||
//ETF 子类不分期限档(对存量 4 档子类行回存给出精确报错;基金通配行由下面的掩码校验拦截)
|
||||
if (!string.IsNullOrEmpty(etfKind) && !string.IsNullOrEmpty(bondTerm))
|
||||
{
|
||||
throw new Exception("ETF 子类 " + etfKind + " 不分期限档,参数行不能配置期限档");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(bondTerm) && (detail.UnderlyingType & ~enabledMask) != UnderlyingTypeEnum.None)
|
||||
{
|
||||
throw new Exception("标的类型不允许配置期限档:" + UnderlyingTypeUtil.GetDesc(detail.UnderlyingType));
|
||||
}
|
||||
//分档 ETF 子类必须配期限档(区块固定4档),其余子类行禁配期限档
|
||||
if (!string.IsNullOrEmpty(etfKind))
|
||||
{
|
||||
if (isTierEnabledEtfKind && string.IsNullOrEmpty(detail.SpanConfig?.BondTerm))
|
||||
{
|
||||
throw new Exception("ETF 子类 " + etfKind + " 为分期限档类型,参数行必须配置期限档");
|
||||
}
|
||||
if (!isTierEnabledEtfKind && !string.IsNullOrEmpty(detail.SpanConfig?.BondTerm))
|
||||
{
|
||||
throw new Exception("ETF 子类 " + etfKind + " 不分期限档,参数行不能配置期限档");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,8 +384,8 @@ namespace YLErp.Web.Controllers
|
||||
{
|
||||
if (marginTemplate.RuleType == (int)MarginRuleTypeEnum.区间追保结构)
|
||||
{
|
||||
//区间追保结构:按 (利率债期限档, ETF细分) 元组分组校验(EtfKind 为预留扩展键,本期无 UI 写入,等价于仅期限档分组),
|
||||
//同一分组内标的类型不允许重复,不同分组允许相同标的类型;
|
||||
//区间追保结构:按 (利率债期限档, ETF细分) 元组分组校验——期限档仅利率债有值(其余恒空),
|
||||
//EtfKind 为基金行的子类键;同一分组内标的类型不允许重复,不同分组允许相同标的类型;
|
||||
//通配行(空/全部,UnderlyingType=None/All)两两之间位与恒为 0 检不出,须单独拦截(BUG-09)——
|
||||
//否则取数侧通配兜底 matched.First() 命中不确定
|
||||
foreach (var termGroup in marginTemplate.Details.GroupBy(x => (x.SpanConfig?.BondTerm ?? "", x.SpanConfig?.EtfKind ?? "")))
|
||||
|
||||
@@ -214,10 +214,10 @@
|
||||
参数组{{blk.no}}:<span v-if="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType)">资产类型:{{underlyingTypeNames(blk.ut)}}</span>
|
||||
@*ETF 子类(仅纯基金区块展示):选 可转债ETF/科创债ETF 分期限档(展开4档),其他子类/不区分不分档*@
|
||||
<span v-if="blk.isFundBlock"> ETF 子类:<select v-model="blk.sections[0].detail.SpanConfig.EtfKind" v-on:change="onEtfKindChange(blk)"><option value="">不区分</option><option v-for="it in etfSubtypeItems" v-bind:value="it">{{it}}</option></select></span>
|
||||
<span v-if="blk.isTBond || blk.isTieredEtf">(按期限分档:≤5y、(5y-10y]、(10y-30y]、>30y,每个档位独立设置)</span>
|
||||
<span v-if="blk.isTBond">(按期限分档:≤5y、(5y-10y]、(10y-30y]、>30y,每个档位独立设置)</span>
|
||||
</p>
|
||||
<div v-for="sec in blk.sections" v-bind:key="sec.key">
|
||||
<p v-if="blk.isTBond || blk.isTieredEtf" style="margin:8px 0 2px; font-weight:bold;">期限档位:{{sec.termLabel}}</p>
|
||||
<p v-if="blk.isTBond" style="margin:8px 0 2px; font-weight:bold;">期限档位:{{sec.termLabel}}</p>
|
||||
<template v-if="sec.detail && sec.detail.SpanConfig">
|
||||
<p style="margin:4px 0;">
|
||||
预警线:<vue-number-input v-model="sec.detail.SpanConfig.WarnLine" v-bind:format="inputFormatPercent"></vue-number-input>
|
||||
|
||||
@@ -215,10 +215,10 @@
|
||||
参数组{{blk.no}}:<span v-if="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType)">资产类型:{{underlyingTypeNames(blk.ut)}}</span>
|
||||
@*ETF 子类(仅纯基金区块展示):选 可转债ETF/科创债ETF 分期限档(展开4档),其他子类/不区分不分档*@
|
||||
<span v-if="blk.isFundBlock"> ETF 子类:<select v-model="blk.sections[0].detail.SpanConfig.EtfKind" v-on:change="onEtfKindChange(blk)"><option value="">不区分</option><option v-for="it in etfSubtypeItems" v-bind:value="it">{{it}}</option></select></span>
|
||||
<span v-if="blk.isTBond || blk.isTieredEtf">(按期限分档:≤5y、(5y-10y]、(10y-30y]、>30y,每个档位独立设置)</span>
|
||||
<span v-if="blk.isTBond">(按期限分档:≤5y、(5y-10y]、(10y-30y]、>30y,每个档位独立设置)</span>
|
||||
</p>
|
||||
<div v-for="sec in blk.sections" v-bind:key="sec.key">
|
||||
<p v-if="blk.isTBond || blk.isTieredEtf" style="margin:8px 0 2px; font-weight:bold;">期限档位:{{sec.termLabel}}</p>
|
||||
<p v-if="blk.isTBond" style="margin:8px 0 2px; font-weight:bold;">期限档位:{{sec.termLabel}}</p>
|
||||
<template v-if="sec.detail && sec.detail.SpanConfig">
|
||||
<p style="margin:4px 0;">
|
||||
预警线:<vue-number-input v-model="sec.detail.SpanConfig.WarnLine" v-bind:format="inputFormatPercent"></vue-number-input>
|
||||
|
||||
@@ -191,10 +191,10 @@
|
||||
参数组{{blk.no}}:<span v-if="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType)">资产类型:{{underlyingTypeNames(blk.ut)}}</span>
|
||||
@*ETF 子类(仅纯基金区块展示):选 可转债ETF/科创债ETF 分期限档(展开4档),其他子类/不区分不分档*@
|
||||
<span v-if="blk.isFundBlock"> ETF 子类:<select v-model="blk.sections[0].detail.SpanConfig.EtfKind" v-on:change="onEtfKindChange(blk)"><option value="">不区分</option><option v-for="it in etfSubtypeItems" v-bind:value="it">{{it}}</option></select></span>
|
||||
<span v-if="blk.isTBond || blk.isTieredEtf">(按期限分档:≤5y、(5y-10y]、(10y-30y]、>30y,每个档位独立设置)</span>
|
||||
<span v-if="blk.isTBond">(按期限分档:≤5y、(5y-10y]、(10y-30y]、>30y,每个档位独立设置)</span>
|
||||
</p>
|
||||
<div v-for="sec in blk.sections" v-bind:key="sec.key">
|
||||
<p v-if="blk.isTBond || blk.isTieredEtf" style="margin:8px 0 2px; font-weight:bold;">期限档位:{{sec.termLabel}}</p>
|
||||
<p v-if="blk.isTBond" style="margin:8px 0 2px; font-weight:bold;">期限档位:{{sec.termLabel}}</p>
|
||||
<template v-if="sec.detail && sec.detail.SpanConfig">
|
||||
<p style="margin:4px 0;">
|
||||
预警线:<vue-number-input v-model="sec.detail.SpanConfig.WarnLine" v-bind:format="inputFormatPercent"></vue-number-input>
|
||||
|
||||
@@ -35,10 +35,6 @@ const TermTierEnabledUnderlyingMask = 16;
|
||||
//基金及基金专户标志位(ETF 子类行的 UnderlyingType,不加新枚举位)
|
||||
const FundTypeMask = 32768;
|
||||
|
||||
//允许配置期限档的 ETF 子类(SpanConfig.EtfKind 值,取值=数据字典"ETF 子类"),
|
||||
//与后端 ConsMarginTerm.TermTierEnabledEtfKinds 保持一致;其余子类与"不区分"的基金行不分档
|
||||
const TermTierEnabledEtfKinds = ['可转债 ETF', '科创债 ETF'];
|
||||
|
||||
//利率债区块固定展开的4个期限档([BondTerm值, 展示文案]),与 docx 原型 4.3.1 一致
|
||||
const SpanBondTerms = [['<5y', '≤5y'], ['5y-10y', '(5y-10y]'], ['10y-30y', '(10y-30y]'], ['>30y', '>30y']];
|
||||
|
||||
@@ -62,6 +58,10 @@ const vue = new Vue({
|
||||
etfSubtypeItems: page.etfSubtypeItems || []
|
||||
},
|
||||
created: function () {
|
||||
//留档进入页面时的生效状态:v-model 改写的就是 page.marginTemplate 同一对象(data 浅拷贝的是引用),
|
||||
//原始值须在用户交互前记下,供保存时判断"生效中规则被改为否"弹失效确认;
|
||||
//select 的 v-model 取值是字符串,统一 String 化比较
|
||||
this.originalIsValid = String(this.marginTemplate.IsValid);
|
||||
//区间追保结构:加载时按 资产类型+期限 排序一次,再重建资产类型区块(利率债区块补齐固定4档),
|
||||
//编辑过程中不再重排,避免 chosen/选择器失序
|
||||
if (this.marginTemplate.RuleType == MarginRuleTypeEnum.区间追保结构 && this.marginTemplate.Details) {
|
||||
@@ -106,8 +106,8 @@ const vue = new Vue({
|
||||
}
|
||||
return rows;
|
||||
},
|
||||
//区间追保结构录入区块:每个资产类型区块一个;纯利率债(ut=16)与分档 ETF 子类
|
||||
//(ut=基金 + EtfKind∈可转债/科创债 ETF)区块固定展开4个期限档段(每段一条 detail),其他区块单段(BondTerm 为空)。
|
||||
//区间追保结构录入区块:每个资产类型区块一个;纯利率债(ut=16)区块固定展开4个期限档段(每段一条 detail),
|
||||
//其他区块(含全部 ETF 子类,2026-08-27 裁定 ETF 无期限概念不再分档)单段(BondTerm 为空)。
|
||||
//段的 detail 缺失时模板侧 v-if 兜底不渲染该段
|
||||
ruleRangeBlocks() {
|
||||
if (this.marginTemplate.RuleType != MarginRuleTypeEnum.区间追保结构) return [];
|
||||
@@ -120,10 +120,8 @@ const vue = new Vue({
|
||||
var ut = head.detail.UnderlyingType || 0;
|
||||
var kind = (head.detail.SpanConfig && head.detail.SpanConfig.EtfKind) || '';
|
||||
var isTBond = ut === 16;
|
||||
var isTieredEtf = ut === FundTypeMask && TermTierEnabledEtfKinds.indexOf(kind) >= 0;
|
||||
var isTiered = isTBond || isTieredEtf;
|
||||
var sections = [];
|
||||
if (isTiered) {
|
||||
if (isTBond) {
|
||||
for (var t = 0; t < SpanBondTerms.length; t++) {
|
||||
var found = null;
|
||||
for (var j = head.index; j < head.index + head.rowspan; j++) {
|
||||
@@ -135,7 +133,7 @@ const vue = new Vue({
|
||||
} else {
|
||||
sections.push({ key: 'single', termLabel: '', detail: head.detail });
|
||||
}
|
||||
blocks.push({ key: head.detail._bk || ('i' + head.index), no: blocks.length + 1, headIndex: head.index, ut: ut, etfKind: kind, isTBond: isTBond, isTieredEtf: isTieredEtf, isFundBlock: ut === FundTypeMask, sections: sections });
|
||||
blocks.push({ key: head.detail._bk || ('i' + head.index), no: blocks.length + 1, headIndex: head.index, ut: ut, etfKind: kind, isTBond: isTBond, isFundBlock: ut === FundTypeMask, sections: sections });
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
@@ -256,6 +254,18 @@ const vue = new Vue({
|
||||
})
|
||||
},
|
||||
saveMarginTemplateV2(isForClient) {
|
||||
//失效确认:编辑中把"生效中"规则改为否时先弹窗提示降级影响,确认后才真正提交;
|
||||
//新增规则尚未生效、生效状态未翻转,不弹。自定义规则编辑页无"是否生效"开关,不经过此逻辑
|
||||
if (!this.isAdd && this.originalIsValid === 'true' && String(this.marginTemplate.IsValid) === 'false') {
|
||||
var that = this;
|
||||
main.confirm("该规则失效后,适用范围内的持仓将降级匹配下一优先级规则(客户→全局),确认继续?", function () {
|
||||
that.doSaveMarginTemplateV2(isForClient);
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.doSaveMarginTemplateV2(isForClient);
|
||||
},
|
||||
doSaveMarginTemplateV2(isForClient) {
|
||||
if (!this.marginTemplate.Name) {
|
||||
main.message("模板名称不能为空");
|
||||
return;
|
||||
@@ -421,64 +431,35 @@ const vue = new Vue({
|
||||
d.SpanConfig.EtfKind = etfKind || null;
|
||||
return d;
|
||||
},
|
||||
//ETF 子类选择器变更(仅纯基金区块渲染):选可转债/科创债 ETF → 区块展开固定4档(缺档补行、异常档行删除);
|
||||
//选其他子类/不区分 → 收敛为单行(BondTerm 置空,多余档位行删除,首行承载 EtfKind)
|
||||
//ETF 子类选择器变更(仅纯基金区块渲染):子类仅用于行区分(取数侧子类行优先),
|
||||
//ETF 无期限概念不分档——任何子类切换后区块始终收敛为单行(BondTerm 置空,多余档位行删除,首行承载 EtfKind;
|
||||
//存量 4 档子类数据在这里被收敛时仅保留首行录入内容)
|
||||
onEtfKindChange(blk) {
|
||||
var head = blk.sections[0].detail;
|
||||
var kind = (head.SpanConfig && head.SpanConfig.EtfKind) || '';
|
||||
var tiered = TermTierEnabledEtfKinds.indexOf(kind) >= 0;
|
||||
var details = this.marginTemplate.Details;
|
||||
var bk = head._bk;
|
||||
var rows = details.filter(function (d) { return d._bk === bk; });
|
||||
if (tiered) {
|
||||
var byTerm = {};
|
||||
rows.forEach(function (r) {
|
||||
var bt = (r.SpanConfig && r.SpanConfig.BondTerm) || '';
|
||||
if (byTerm[bt] === undefined) byTerm[bt] = r;
|
||||
});
|
||||
var that = this;
|
||||
var merged = [];
|
||||
//首行未被4个合法档位命中时(单段行 BondTerm 为空)复用为首个档位行,保留已录入内容;
|
||||
//否则首行会以"EtfKind 已选、BondTerm 为空"的残留行留在明细里,保存时被服务端"分期限档类型必须配置期限档"拦截
|
||||
var headMatched = SpanBondTerms.some(function (t) { return byTerm[t[0]] === head; });
|
||||
SpanBondTerms.forEach(function (t, ti) {
|
||||
var r = byTerm[t[0]] || (ti === 0 && !headMatched ? head : null);
|
||||
if (!r) r = that.newSpanDetail(FundTypeMask, t[0], kind);
|
||||
r.UnderlyingType = FundTypeMask;
|
||||
r.SpanConfig.EtfKind = kind;
|
||||
r.SpanConfig.BondTerm = t[0];
|
||||
Vue.set(r, '_bk', bk);
|
||||
merged.push(r);
|
||||
rows.forEach(function (r) {
|
||||
if (r !== head) {
|
||||
var idx = details.indexOf(r);
|
||||
if (idx >= 0 && r !== head) details.splice(idx, 1);
|
||||
});
|
||||
//异常档/重复行全部移除后按档序回插到区块首行位置
|
||||
var headIdx = details.indexOf(head);
|
||||
if (headIdx < 0) headIdx = details.length;
|
||||
details.splice.apply(details, [headIdx + 1, 0].concat(merged.filter(function (r) { return r !== head; })));
|
||||
} else {
|
||||
rows.forEach(function (r) {
|
||||
if (r !== head) {
|
||||
var idx = details.indexOf(r);
|
||||
if (idx >= 0) details.splice(idx, 1);
|
||||
}
|
||||
});
|
||||
head.UnderlyingType = FundTypeMask;
|
||||
head.SpanConfig.BondTerm = '';
|
||||
head.SpanConfig.EtfKind = kind || null;
|
||||
}
|
||||
if (idx >= 0) details.splice(idx, 1);
|
||||
}
|
||||
});
|
||||
head.UnderlyingType = FundTypeMask;
|
||||
head.SpanConfig.BondTerm = '';
|
||||
head.SpanConfig.EtfKind = kind || null;
|
||||
this.refreshChosen();
|
||||
},
|
||||
//分档行判定与分档键:纯利率债 → 'tbond';基金 + 分档 ETF 子类 → 'etf:子类'(同一子类的连续行归一个区块)
|
||||
//分档行判定与分档键:纯利率债 → 'tbond'(连续行归一个区块按固定4档补齐);ETF 子类不分档,基金行一律独立成块
|
||||
spanTierKey(d) {
|
||||
var ut = d.UnderlyingType || 0;
|
||||
var kind = (d.SpanConfig && d.SpanConfig.EtfKind) || '';
|
||||
if (ut === TermTierEnabledUnderlyingMask) return 'tbond';
|
||||
if (ut === FundTypeMask && TermTierEnabledEtfKinds.indexOf(kind) >= 0) return 'etf:' + kind;
|
||||
return null;
|
||||
},
|
||||
//存量回显重建资产类型区块(分配区块键 _bk):连续的纯利率债行、基金+分档ETF子类行各自归为区块并按固定4档补齐(缺档补空行);
|
||||
//同档重复/异常档的分档行、非分档行(含基金+不分档子类、基金通配行)均各自独立成块展示,不丢数据
|
||||
//存量回显重建资产类型区块(分配区块键 _bk):连续的纯利率债行归为区块并按固定4档补齐(缺档补空行);
|
||||
//同档重复/异常档的分档行、其余全部行(含基金+子类行、基金通配行)均各自独立成块展示,不丢数据——
|
||||
//存量 ETF 子类 4 档行会以多个单行区块出现,保存时由服务端"ETF 子类不分期限档"校验拦截提示清理
|
||||
rebuildSpanBlocks(details) {
|
||||
var termValues = SpanBondTerms.map(function (t) { return t[0]; });
|
||||
var result = [];
|
||||
@@ -500,13 +481,10 @@ const vue = new Vue({
|
||||
if (termValues.indexOf(bt) >= 0 && !byTerm[bt]) byTerm[bt] = r; else anomalies.push(r);
|
||||
});
|
||||
var bk = 'bk_' + (++spanBlockSeq);
|
||||
var utv = tk === 'tbond' ? TermTierEnabledUnderlyingMask : FundTypeMask;
|
||||
var kind = tk === 'tbond' ? '' : tk.substring(4);
|
||||
termValues.forEach(function (t) {
|
||||
var r = byTerm[t];
|
||||
if (!r) r = that.newSpanDetail(utv, t, kind);
|
||||
r.UnderlyingType = utv;
|
||||
if (kind) r.SpanConfig.EtfKind = kind;
|
||||
if (!r) r = that.newSpanDetail(TermTierEnabledUnderlyingMask, t);
|
||||
r.UnderlyingType = TermTierEnabledUnderlyingMask;
|
||||
r.SpanConfig.BondTerm = t;
|
||||
Vue.set(r, '_bk', bk);
|
||||
result.push(r);
|
||||
@@ -538,15 +516,19 @@ const vue = new Vue({
|
||||
return map[v] !== undefined ? map[v] : v;
|
||||
},
|
||||
//规则15录入区块文案:按 detail 资产类型切换计价口径。
|
||||
//债券类(利率债16/信用债32/其它债券64)及其他类型(默认债券口径):期初净价/当前净价,金额=×期初全价×券面总额;
|
||||
//基金及基金专户(32768)/债券指数(536870912):参考标的期初净价/参考标的当前收盘价(空头各层统一收盘价,BUG-11 修正),
|
||||
//比较价格口径与计算侧对齐(MarginCalculationBase.CalcSwapSpanMaintenanceMargin:IsBond→中债估值净价,其余→收盘价):
|
||||
//纯债券类(利率债16/信用债32/其它债券64,可组合):期初净价/当前净价,金额=×期初全价×券面总额;
|
||||
//其余全部类型(基金/债券指数/股票/股指等):参考标的期初净价/参考标的当前收盘价(多空各层统一收盘价),
|
||||
//金额=×参考标的期初价格×参考标的名义份额。
|
||||
spanText(detail) {
|
||||
var ut = (detail && detail.UnderlyingType) || 0;
|
||||
if ((ut & 32768) > 0 || (ut & 536870912) > 0) {
|
||||
//非空且标志位全部落在债券三类内才按债券口径;混合行(债券|非债券)按收盘价口径显示,与取数侧标的实际类型判定方向一致
|
||||
var bondMask = 16 | 32 | 64;
|
||||
var isBond = ut !== 0 && (ut & ~bondMask) === 0;
|
||||
if (!isBond) {
|
||||
return {
|
||||
priceInit: '参考标的期初净价',
|
||||
priceCur: '参考标的当前净价',
|
||||
priceCur: '参考标的当前收盘价',
|
||||
priceCurShort1: '参考标的当前收盘价',
|
||||
priceCurShort: '参考标的当前收盘价',
|
||||
amountBase: '参考标的期初价格 × 参考标的名义份额'
|
||||
|
||||
@@ -35,10 +35,6 @@ const TermTierEnabledUnderlyingMask = 16;
|
||||
//基金及基金专户标志位(ETF 子类行的 UnderlyingType,不加新枚举位)
|
||||
const FundTypeMask = 32768;
|
||||
|
||||
//允许配置期限档的 ETF 子类(SpanConfig.EtfKind 值,取值=数据字典"ETF 子类"),
|
||||
//与后端 ConsMarginTerm.TermTierEnabledEtfKinds 保持一致;其余子类与"不区分"的基金行不分档
|
||||
const TermTierEnabledEtfKinds = ['可转债 ETF', '科创债 ETF'];
|
||||
|
||||
//利率债区块固定展开的4个期限档([BondTerm值, 展示文案]),与 docx 原型 4.3.1 一致
|
||||
const SpanBondTerms = [['<5y', '≤5y'], ['5y-10y', '(5y-10y]'], ['10y-30y', '(10y-30y]'], ['>30y', '>30y']];
|
||||
|
||||
@@ -106,8 +102,8 @@ const vue = new Vue({
|
||||
}
|
||||
return rows;
|
||||
},
|
||||
//区间追保结构录入区块:每个资产类型区块一个;纯利率债(ut=16)与分档 ETF 子类
|
||||
//(ut=基金 + EtfKind∈可转债/科创债 ETF)区块固定展开4个期限档段(每段一条 detail),其他区块单段(BondTerm 为空)。
|
||||
//区间追保结构录入区块:每个资产类型区块一个;纯利率债(ut=16)区块固定展开4个期限档段(每段一条 detail),
|
||||
//其他区块(含全部 ETF 子类,2026-08-27 裁定 ETF 无期限概念不再分档)单段(BondTerm 为空)。
|
||||
//段的 detail 缺失时模板侧 v-if 兜底不渲染该段
|
||||
ruleRangeBlocks() {
|
||||
if (this.marginTemplate.RuleType != MarginRuleTypeEnum.区间追保结构) return [];
|
||||
@@ -120,10 +116,8 @@ const vue = new Vue({
|
||||
var ut = head.detail.UnderlyingType || 0;
|
||||
var kind = (head.detail.SpanConfig && head.detail.SpanConfig.EtfKind) || '';
|
||||
var isTBond = ut === 16;
|
||||
var isTieredEtf = ut === FundTypeMask && TermTierEnabledEtfKinds.indexOf(kind) >= 0;
|
||||
var isTiered = isTBond || isTieredEtf;
|
||||
var sections = [];
|
||||
if (isTiered) {
|
||||
if (isTBond) {
|
||||
for (var t = 0; t < SpanBondTerms.length; t++) {
|
||||
var found = null;
|
||||
for (var j = head.index; j < head.index + head.rowspan; j++) {
|
||||
@@ -135,7 +129,7 @@ const vue = new Vue({
|
||||
} else {
|
||||
sections.push({ key: 'single', termLabel: '', detail: head.detail });
|
||||
}
|
||||
blocks.push({ key: head.detail._bk || ('i' + head.index), no: blocks.length + 1, headIndex: head.index, ut: ut, etfKind: kind, isTBond: isTBond, isTieredEtf: isTieredEtf, isFundBlock: ut === FundTypeMask, sections: sections });
|
||||
blocks.push({ key: head.detail._bk || ('i' + head.index), no: blocks.length + 1, headIndex: head.index, ut: ut, etfKind: kind, isTBond: isTBond, isFundBlock: ut === FundTypeMask, sections: sections });
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
@@ -432,64 +426,35 @@ const vue = new Vue({
|
||||
d.SpanConfig.EtfKind = etfKind || null;
|
||||
return d;
|
||||
},
|
||||
//ETF 子类选择器变更(仅纯基金区块渲染):选可转债/科创债 ETF → 区块展开固定4档(缺档补行、异常档行删除);
|
||||
//选其他子类/不区分 → 收敛为单行(BondTerm 置空,多余档位行删除,首行承载 EtfKind)
|
||||
//ETF 子类选择器变更(仅纯基金区块渲染):子类仅用于行区分(取数侧子类行优先),
|
||||
//ETF 无期限概念不分档——任何子类切换后区块始终收敛为单行(BondTerm 置空,多余档位行删除,首行承载 EtfKind;
|
||||
//存量 4 档子类数据在这里被收敛时仅保留首行录入内容)
|
||||
onEtfKindChange(blk) {
|
||||
var head = blk.sections[0].detail;
|
||||
var kind = (head.SpanConfig && head.SpanConfig.EtfKind) || '';
|
||||
var tiered = TermTierEnabledEtfKinds.indexOf(kind) >= 0;
|
||||
var details = this.marginTemplate.Details;
|
||||
var bk = head._bk;
|
||||
var rows = details.filter(function (d) { return d._bk === bk; });
|
||||
if (tiered) {
|
||||
var byTerm = {};
|
||||
rows.forEach(function (r) {
|
||||
var bt = (r.SpanConfig && r.SpanConfig.BondTerm) || '';
|
||||
if (byTerm[bt] === undefined) byTerm[bt] = r;
|
||||
});
|
||||
var that = this;
|
||||
var merged = [];
|
||||
//首行未被4个合法档位命中时(单段行 BondTerm 为空)复用为首个档位行,保留已录入内容;
|
||||
//否则首行会以"EtfKind 已选、BondTerm 为空"的残留行留在明细里,保存时被服务端"分期限档类型必须配置期限档"拦截
|
||||
var headMatched = SpanBondTerms.some(function (t) { return byTerm[t[0]] === head; });
|
||||
SpanBondTerms.forEach(function (t, ti) {
|
||||
var r = byTerm[t[0]] || (ti === 0 && !headMatched ? head : null);
|
||||
if (!r) r = that.newSpanDetail(FundTypeMask, t[0], kind);
|
||||
r.UnderlyingType = FundTypeMask;
|
||||
r.SpanConfig.EtfKind = kind;
|
||||
r.SpanConfig.BondTerm = t[0];
|
||||
Vue.set(r, '_bk', bk);
|
||||
merged.push(r);
|
||||
rows.forEach(function (r) {
|
||||
if (r !== head) {
|
||||
var idx = details.indexOf(r);
|
||||
if (idx >= 0 && r !== head) details.splice(idx, 1);
|
||||
});
|
||||
//异常档/重复行全部移除后按档序回插到区块首行位置
|
||||
var headIdx = details.indexOf(head);
|
||||
if (headIdx < 0) headIdx = details.length;
|
||||
details.splice.apply(details, [headIdx + 1, 0].concat(merged.filter(function (r) { return r !== head; })));
|
||||
} else {
|
||||
rows.forEach(function (r) {
|
||||
if (r !== head) {
|
||||
var idx = details.indexOf(r);
|
||||
if (idx >= 0) details.splice(idx, 1);
|
||||
}
|
||||
});
|
||||
head.UnderlyingType = FundTypeMask;
|
||||
head.SpanConfig.BondTerm = '';
|
||||
head.SpanConfig.EtfKind = kind || null;
|
||||
}
|
||||
if (idx >= 0) details.splice(idx, 1);
|
||||
}
|
||||
});
|
||||
head.UnderlyingType = FundTypeMask;
|
||||
head.SpanConfig.BondTerm = '';
|
||||
head.SpanConfig.EtfKind = kind || null;
|
||||
this.refreshChosen();
|
||||
},
|
||||
//分档行判定与分档键:纯利率债 → 'tbond';基金 + 分档 ETF 子类 → 'etf:子类'(同一子类的连续行归一个区块)
|
||||
//分档行判定与分档键:纯利率债 → 'tbond'(连续行归一个区块按固定4档补齐);ETF 子类不分档,基金行一律独立成块
|
||||
spanTierKey(d) {
|
||||
var ut = d.UnderlyingType || 0;
|
||||
var kind = (d.SpanConfig && d.SpanConfig.EtfKind) || '';
|
||||
if (ut === TermTierEnabledUnderlyingMask) return 'tbond';
|
||||
if (ut === FundTypeMask && TermTierEnabledEtfKinds.indexOf(kind) >= 0) return 'etf:' + kind;
|
||||
return null;
|
||||
},
|
||||
//存量回显重建资产类型区块(分配区块键 _bk):连续的纯利率债行、基金+分档ETF子类行各自归为区块并按固定4档补齐(缺档补空行);
|
||||
//同档重复/异常档的分档行、非分档行(含基金+不分档子类、基金通配行)均各自独立成块展示,不丢数据
|
||||
//存量回显重建资产类型区块(分配区块键 _bk):连续的纯利率债行归为区块并按固定4档补齐(缺档补空行);
|
||||
//同档重复/异常档的分档行、其余全部行(含基金+子类行、基金通配行)均各自独立成块展示,不丢数据——
|
||||
//存量 ETF 子类 4 档行会以多个单行区块出现,保存时由服务端"ETF 子类不分期限档"校验拦截提示清理
|
||||
rebuildSpanBlocks(details) {
|
||||
var termValues = SpanBondTerms.map(function (t) { return t[0]; });
|
||||
var result = [];
|
||||
@@ -511,13 +476,10 @@ const vue = new Vue({
|
||||
if (termValues.indexOf(bt) >= 0 && !byTerm[bt]) byTerm[bt] = r; else anomalies.push(r);
|
||||
});
|
||||
var bk = 'bk_' + (++spanBlockSeq);
|
||||
var utv = tk === 'tbond' ? TermTierEnabledUnderlyingMask : FundTypeMask;
|
||||
var kind = tk === 'tbond' ? '' : tk.substring(4);
|
||||
termValues.forEach(function (t) {
|
||||
var r = byTerm[t];
|
||||
if (!r) r = that.newSpanDetail(utv, t, kind);
|
||||
r.UnderlyingType = utv;
|
||||
if (kind) r.SpanConfig.EtfKind = kind;
|
||||
if (!r) r = that.newSpanDetail(TermTierEnabledUnderlyingMask, t);
|
||||
r.UnderlyingType = TermTierEnabledUnderlyingMask;
|
||||
r.SpanConfig.BondTerm = t;
|
||||
Vue.set(r, '_bk', bk);
|
||||
result.push(r);
|
||||
@@ -549,15 +511,19 @@ const vue = new Vue({
|
||||
return map[v] !== undefined ? map[v] : v;
|
||||
},
|
||||
//规则15录入区块文案:按 detail 资产类型切换计价口径。
|
||||
//债券类(利率债16/信用债32/其它债券64)及其他类型(默认债券口径):期初净价/当前净价,金额=×期初全价×券面总额;
|
||||
//基金及基金专户(32768)/债券指数(536870912):参考标的期初净价/参考标的当前收盘价(空头各层统一收盘价,BUG-11 修正),
|
||||
//比较价格口径与计算侧对齐(MarginCalculationBase.CalcSwapSpanMaintenanceMargin:IsBond→中债估值净价,其余→收盘价):
|
||||
//纯债券类(利率债16/信用债32/其它债券64,可组合):期初净价/当前净价,金额=×期初全价×券面总额;
|
||||
//其余全部类型(基金/债券指数/股票/股指等):参考标的期初净价/参考标的当前收盘价(多空各层统一收盘价),
|
||||
//金额=×参考标的期初价格×参考标的名义份额。
|
||||
spanText(detail) {
|
||||
var ut = (detail && detail.UnderlyingType) || 0;
|
||||
if ((ut & 32768) > 0 || (ut & 536870912) > 0) {
|
||||
//非空且标志位全部落在债券三类内才按债券口径;混合行(债券|非债券)按收盘价口径显示,与取数侧标的实际类型判定方向一致
|
||||
var bondMask = 16 | 32 | 64;
|
||||
var isBond = ut !== 0 && (ut & ~bondMask) === 0;
|
||||
if (!isBond) {
|
||||
return {
|
||||
priceInit: '参考标的期初净价',
|
||||
priceCur: '参考标的当前净价',
|
||||
priceCur: '参考标的当前收盘价',
|
||||
priceCurShort1: '参考标的当前收盘价',
|
||||
priceCurShort: '参考标的当前收盘价',
|
||||
amountBase: '参考标的期初价格 × 参考标的名义份额'
|
||||
|
||||
Reference in New Issue
Block a user