Files
zszq-trs/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js
T
hjhan b271a675d4 fix: 新增交易默认交易对手方未回写ClientId,保存报"请设置交易对手方"
autocomplete.setData 只写控件显示值、不触发 onSelect 回写 v-model——
新增时默认取客户列表第一个(consClients[0])仅 setData,trade.ClientId 仍为空,
前端校验拦截且不发请求;用户须手动从下拉重选一次才能保存。

- swaptrade/swapTradeEdit.js(SwapTrade2 现用页):默认客户经 vue.changeClient 显式回写,与手动选择等价
- trade/swapTradeEdit.js(SwapTrade 旧版页,同缺陷):直接赋值回写 trade.ClientId,不走 changeClient 以避免初始化触发 initMarginRate 后端调用

与 demo/autocomplete.js 死代码删除(3be4b48c)无关:该文件零引用,生产实现在 fastVue.base.js 未改动,缺陷自山证 v2.3.0 拷贝带入即存在。
2026-08-28 13:57:40 +08:00

2039 lines
101 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//otcformat禁止千分位分组
window.otcformat.options.disableGrouping = true;
// 模块加载横幅(排查用):仅在 ?otcdebug=1 或 localStorage.otcdebug=1 开启时打印,含 bundle 版本+git sha。
// otcDebug 由 main.js 提供(在 bundle.js 内),本文件是独立 <script> 且晚于 bundle.js 加载,故 otcDebug 必已就绪。
otcDebug.banner('swapTradeEdit.js', '1.4.2');
const consClients = ylotc.clients;
const consTraders = ylotc.traders;
const consAssetUnits = page.canAddNewTrader ? ylotc.assetunits
: ylotc.assetunits.filter(x => x.TraderIds.includes(page.Trade.TraderId));
const inputFormatInteger = Object.freeze({ precision: 0, append: '' });
const inputFormatEqvNotional = swapPricePrecision.getCommonInputFormat('amount', { append: '' });
const inputFormatSwapRate = swapPricePrecision.getCommonInputFormat('rate', { negative: true, append: '%' });
const inputFormatTradePrice = Object.freeze({ precision: otcformat.trading.tradePrice.precision, negative: true, append: '' });
const inputFormatTradeSinglePrice = swapPricePrecision.getCommonInputFormat('amount', { negative: true, append: '', percent: false });
const inputFormatTradeSinglePriceFixed2 = swapPricePrecision.getCommonInputFormat('amount', { negative: true, append: '', percent: false });
const inputFormatPosiFeePercent = Object.freeze({ precision: 4, negative: true, append: '' });
const inputFormatPosiFeeUnit = Object.freeze({ precision: 6, negative: true, append: '' });
const inputFormatMarginRate = Object.freeze({ precision: otcformat.trading.marginRateP.precision, append: '%' });
const inputFormatSwapDeliveryPrice = Object.freeze({ precision: 9, negative: true, append: '', percent: false });
const consPosiFeeType = Object.freeze({ Percent: 0, Unit: 1 });
const observationRatePrecision = 12;
const roundObservationRate = function (value) {
const rounded = swapPricePrecision.roundDecimal(value, observationRatePrecision);
return rounded === null || rounded === '' ? value : rounded;
};
const observationRateFromPercent = function (value) {
const rate = roundObservationRate(swapPricePrecision.shiftDecimal(value, -2));
return rate === null || rate === '' ? value : Number(rate);
};
const observationRateTextFromPercent = function (value) {
return roundObservationRate(swapPricePrecision.shiftDecimal(value, -2));
};
const formatObservationRate = function (value) {
if (value === null || value === undefined || value === '') return '';
const number = Number(value);
return Number.isFinite(number) ? number.toFixed(swapPricePrecision.getCommonPrecision('rate')) : value;
};
const swapPosiFeeCalc = Object.freeze({
normalizeFeeType(feeType) {
return Number(feeType) === consPosiFeeType.Unit ? consPosiFeeType.Unit : consPosiFeeType.Percent;
},
calcPending(feeType, feeUnit, stockEqvNotional, quantity) {
const normalizedFeeType = this.normalizeFeeType(feeType);
const normalizedFeeUnit = Number(feeUnit) || 0;
const normalizedNotional = Number(stockEqvNotional) || 0;
const normalizedQuantity = Number(quantity) || 0;
const tradingFeePending = normalizedFeeType === consPosiFeeType.Percent
? normalizedFeeUnit / 100 * normalizedNotional
: normalizedFeeUnit * normalizedQuantity;
return otcformat.trading.tradeSinglePrice(tradingFeePending);
},
calcFeeUnit(feeType, tradingFeePending, stockEqvNotional, quantity) {
const normalizedFeeType = this.normalizeFeeType(feeType);
const normalizedTradingFeePending = Number(tradingFeePending) || 0;
const normalizedNotional = Number(stockEqvNotional) || 0;
const normalizedQuantity = Number(quantity) || 0;
if (normalizedFeeType === consPosiFeeType.Percent) {
return normalizedNotional === 0 ? 0 : _.round(normalizedTradingFeePending / normalizedNotional * 100, inputFormatPosiFeePercent.precision);
}
return normalizedQuantity === 0 ? 0 : _.round(normalizedTradingFeePending / normalizedQuantity, inputFormatPosiFeeUnit.precision);
}
});
const consUnderlyingFlagBase = (function () {
let unSelFlag = tradeHelper.UnderlyingSelectFlag;
return unSelFlag.UseForTrading | unSelFlag.IncludeMatured | unSelFlag.UsePinYinFilter | unSelFlag.IncludeBasket | unSelFlag.IncludeSynthetic | unSelFlag.CheckLaunch;
}());
const consUnderlyingFlagNonBond = (function () {
let unSelFlag = tradeHelper.UnderlyingSelectFlag;
return consUnderlyingFlagBase;
}());
const consUnderlyingFlagBond = (function () {
let unSelFlag = tradeHelper.UnderlyingSelectFlag;
const consUnderlyingFlagNonBond = (function () {
let unSelFlag = tradeHelper.UnderlyingSelectFlag;
return consUnderlyingFlagBase;
}());
}());
const inputFormatDouble2 = Object.freeze({ precision: 2, append: '' });
var autoMarginTemplateName;
//定价格式化(来自配置)
const InputFormatPercent = Object.freeze({ precision: 2, append: '%' });
const consMetaDic = {
'组合标的': JSON.parse(page.Trade.MetaDic['组合标的'] || '{}'),
'组合标的2': JSON.parse(page.Trade.MetaDic['组合标的2'] || '{}')
};
//定价格式化(来自配置)
const pricingFormat = otcformat.trading;
//数字格式化
const consNumberFormat = Object.freeze(new function () {
this.umpriceP = pricingFormat.umpriceP;
this.umprice = pricingFormat.umprice;
this.premiumRateP = pricingFormat.premiumRateP;
return this;
}());
const createVueUnderlying = function (selectFlag) {
return {
props: ['value', 'index'],
data() {
return { autoUnderlying: null };
},
mounted() {
var selFlag = tradeHelper.UnderlyingSelectFlag;
this.autoUnderlying = tradeHelper.UnderlyingAutoComplete(this.$el, { SelectFlag: selFlag.UsePinYinFilter |
selFlag.UseForTrading |
selFlag.IncludeSynthetic |
selFlag.IncludeMatured |
selFlag.IncludeBasket |
selFlag.CheckLaunch, BlackLimit: 2, });
this.autoUnderlying.onSelect(this.onchange);
this.value && this.autoUnderlying.selectByCode(this.value);
},
methods: {
onchange(data) {
this.$emit('change', data);
this.$emit('input', data.Code);
}
},
destroyed() {
this.autoUnderlying && this.autoUnderlying.dispose();
},
template: '<input type="text" v-model="value" />'
};
};
const vueUnderlyingNonBond = function () {
return createVueUnderlying(consUnderlyingFlagNonBond);
};
const vueUnderlyingBond = function () {
return createVueUnderlying(consUnderlyingFlagBond);
};
//标的选择组件 银行间回购定盘&其他利率
const vueUnderlyingRate = function () {
return {
props: ['value', 'index'],
data() {
return { autoUnderlying: null };
},
mounted() {
var selFlag = tradeHelper.UnderlyingSelectFlag;
var instrumentTypes = ["FixingRepoRate", "OtherRate"];
this.autoUnderlying = tradeHelper.UnderlyingAutoComplete(this.$el,
{ SelectFlag: selFlag.UsePinYinFilter |
selFlag.UseForTrading |
selFlag.IncludeSynthetic |
selFlag.IncludeMatured |
selFlag.IncludeBasket |
selFlag.CheckLaunch, BlackLimit: 2, InstrumentTypes: instrumentTypes, UseAll: true, UseSignle: true });
this.autoUnderlying.onSelect(this.onchange);
this.value && this.autoUnderlying.selectByCode(this.value);
},
methods: {
onchange(data) {
data.index = this.index;
if (this.value !== data.Code) {
this.$emit('change', data);
this.$emit('input', data.Code);
}
}
},
destroyed() {
this.autoUnderlying && this.autoUnderlying.dispose();
},
template: '<input type="text" v-model="value" />'
};
};
//ESC
$(document).keyup(function (event) {
if (event.keyCode === 27) {
window.parent.layer.closeAll();
}
});
const vue = new Vue({
el: '#tradeEdit',
data: {
page: page,
trade: _.cloneDeep(page.Trade),
viewState: {
variety: tradeHelper.getEmptyVariety(),
underlying: tradeHelper.getEmptyUnderlying(),
synthetic: null,
Extend: { TradingPlace: "柜台市场", ClearingAgency: "交易员方" }
},
currencys: page.currencys,
getNotionalSingleFee: 0,
isSingleFee: page.Trade.trade_extend.ExtendObj.OpenFeeType == 0,
posiFeeModePercent: true,
observation: {//互换观察日
ObservationInterval: "",
IntervalList: [],
ObservationNum: 1,
ObservationUnit: 'D',
ObservationHolidayType: 'Following',
ObservationAlignEnd: true,
ObservationCalendar: 'Chn',
ObservationSettlementRules: 0,
DefaultTitle1Value: 0.1,
ObservationDataList: [],
CheckedAll: false,
index: 0,
IsDeductPrincipal: true,
ObservationStart: page.Trade.TradeDate
},
observationHolidayTypes: [
{ text: "向后调整", value: "Following" },
{ text: "向前调整", value: "Previous" },
{ text: "不调整", value: "None" }
],
alignEndTypes: [
{ text: "向到期日对齐", value: true },
{ text: "向开始日对齐", value: false }
],
marginSwapList: [],
getSwapList: [],//利息端
paySwapList: [],//浮动端,
observationType: 0,
client: null,//客户信息
StockEqvNotional: 0,
underlying: {
UnderlyingCode: '',
UnderlyingName: '',
UnderlyingIssuer: '',
IssueSize: '',
MaturityDate: '',
Price: '',
UnderlyingInstrumentType: '',
QuoteUnitString: ''
}
},
watch: {
},
created: function () {
if (this.trade.MetaDic['组合标的']) {
this.viewState.synthetic = JSON.parse(this.trade.MetaDic['组合标的']);
}
if (this.trade.MetaDic["交易场所"]) {
this.viewState.Extend.TradingPlace = this.trade.MetaDic["交易场所"];
}
if (this.trade.MetaDic["清算机构"]) {
this.viewState.Extend.ClearingAgency = this.trade.MetaDic["清算机构"];
}
},
mounted() {
__init(this);
$("#MainProtocolCode").val(this.trade.MetaDic["主协议编号"]);
$("#SupProtocolCode").val(this.trade.MetaDic["补充协议编号"]);
this.initSwapRateList();
// 初始化到期日期选择器下限
this.$nextTick(() => {
if (this.$refs.exerciseDatePicker) {
var minDate = this.trade.StartDate > this.trade.TradeDate ? this.trade.StartDate : this.trade.TradeDate;
this.$refs.exerciseDatePicker.refresh(minDate, null);
}
});
},
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',
{ append: '' },
item && item.UnderlyingInstrumentType);
},
getPosiPriceInputFormat(item, field) {
return swapPricePrecision.getInputFormat(
item && item.UnderlyingInstrumentType,
field,
Object.assign({}, inputFormatSwapDeliveryPrice, {
percent: this.trade.StructureType !== '普通收益互换'
}));
},
roundStoragePrice(item, price, field) {
const storagePrecisionOffset = tradeHelper.IsBond(item && item.UnderlyingInstrumentType) ? 2 : 0;
return swapPricePrecision.roundForSubmit(
price,
item && item.UnderlyingInstrumentType,
field,
storagePrecisionOffset);
},
getPosiPriceFormatKey(item, field) {
const index = item && item.index != null ? item.index : '';
const instrumentType = item && item.UnderlyingInstrumentType ? item.UnderlyingInstrumentType : 'unknown';
return `${index}-${field}-${instrumentType}`;
},
getCurrentPosiFeeType() {
return this.posiFeeModePercent ? consPosiFeeType.Percent : consPosiFeeType.Unit;
},
normalizePosiFeeType(feeType) {
return swapPosiFeeCalc.normalizeFeeType(feeType);
},
syncPosiFeeModeByItem(item) {
this.posiFeeModePercent = this.normalizePosiFeeType(item && item.PosiFeeType) !== consPosiFeeType.Unit;
},
syncPayItemFeeType(item) {
item.PosiFeeType = this.getCurrentPosiFeeType();
},
refreshTradingFeePendingByUnit(item) {
this.syncPayItemFeeType(item);
item.PosiTradingFeePending = swapPosiFeeCalc.calcPending(
item.PosiFeeType,
item.PosiTradingFeeUnit,
this.trade.StockEqvNotional,
item.PosiQuantity
);
},
refreshTradingFeeUnitByPending(item) {
this.syncPayItemFeeType(item);
item.PosiTradingFeeUnit = swapPosiFeeCalc.calcFeeUnit(
item.PosiFeeType,
item.PosiTradingFeePending,
this.trade.StockEqvNotional,
item.PosiQuantity
);
},
refreshPayTradingFeesByUnit() {
this.paySwapList.forEach(item => {
this.refreshTradingFeePendingByUnit(item);
});
},
changeStructureType() {
this.trade.StockEqvNotional = 0;
let direction = this.trade.trade_extend.ExtendObj.Direction;
if (page.swapFloatingIncomeReceiveOnlyMode) {
direction = 1;
this.trade.trade_extend.ExtendObj.Direction = direction;
} else if (direction !== 1 && direction !== 2) {
direction = 2;
this.trade.trade_extend.ExtendObj.Direction = direction;
}
this.paySwapList = [];
this.addSwapFloat(direction);
//this.trade.trade_extend.ExtendObj.FlowBookMode = 0;
},
//变更初始预付金收取方向
changeInitialMargin() {
if (this.trade.trade_Initial_Margin.Direction == 1) {
this.trade.trade_Initial_Margin.Direction = 2;
} else {
this.trade.trade_Initial_Margin.Direction = 1;
}
},
//变更交易场所
changeTradingPlace() {
this.trade.MetaDic["交易场所"] = this.viewState.Extend.TradingPlace;
},
//变更清算机构
changeClearingAgency() {
this.trade.MetaDic["清算机构"] = this.viewState.Extend.ClearingAgency;
},
//交互约定1+2:在净价/全价/收益率任一输入框【敲回车】→ 以该字段为源调计算器反算另两个。
// 成功:源字段标"源",另两个标"AUTO"SwapCalc.applyBondCalcSuccess);
// 失败:保留源字段值、另两个清空、三字段标识全清(SwapCalc.applyBondCalcFailure)。
// 注意:计算触发只在回车(由 vue-number-input 的 enter 事件驱动),失焦不触发(见 onBondPriceEdit 约定3)。
onBondPriceEnter(item, type) { // type: 'CP'净价 / 'DP'全价 / 'YD'收益率
if (!item.isBond) return;
if (!item.UnderlyingCode) { main.message("请先选择债券标的"); return; }
var hasVal = function (v) { return v !== null && v !== '' && !isNaN(v); };
var price = type === 'CP' ? item.PosiNetNoFeePrice
: type === 'DP' ? item.PosiGrossPrice
: item.InitYtm;
if (!hasVal(price)) return; // 空/非数:不触发计算器
this.calcBondForItem(item, type);
},
//交互约定3:编辑某字段(失焦/输入)但未回车 → 不联动另两字段,清掉 源/AUTO 标识,
// 仅本字段标"REV"(人工输入),由 SwapCalc.applyBondManualEdit 落地。
// 由 vue-number-input 的 input 事件(失焦/回车都会触发)驱动;回车时 enter 事件会随后把标识修正为 源/AUTO。
onBondPriceEdit(item, type) {
if (!item.isBond) return;
SwapCalc.applyBondManualEdit(item, type);
this.syncBondFlags(item);
otcDebug.log('[onBondPriceEdit] type=' + type + ' after: driver=' + item.bondDriverType + ' rev=' + JSON.stringify(item.bondRev));
},
//交互约定3(按键实时触发):vue-number-input 的 input 事件只在失焦/回车时触发,
// 按键输入过程中不触发,导致用户输入时 源/AUTO 标识不会消失。
// 此方法绑在 v-on:keydown 上(组件内部 addEventListener + $emit),按键即清 源/AUTO 标识、标本字段 REV。
// 回车键(13/108)除外——回车由 v-on:enter 驱动 calcBondForItem,会把标识修正为 源/AUTO。
// 修饰键(Shift/Ctrl/Alt/Tab/方向键/F键等)除外——这些不改变数值,不应清标识。
onBondPriceKeydown(item, type, event) {
if (!item.isBond) return;
var kc = event.which || event.keyCode;
// 仅"会改变数值"的按键才清 源/AUTO 标识并标本字段 REV;
// 回车/修饰键/导航键/功能键/纯 Shift 不改标识(见 SwapCalc.isBondPriceValueKey)。
// 修复:此前守卫漏掉 ↑(38)/↓(40)/Insert(45)/PageUp(33)/PageDown(34)/F键/Shift(16)
// 导致"鼠标放进去没改值、只按了方向键/功能键"也会误标 REV。
if (!SwapCalc.isBondPriceValueKey(event)) return;
// 调试埋点(otcDebug 仅在 ?otcdebug=1 / localStorage.otcdebug=1 时输出,平时零噪音):
// 抓"逐键前后 driver/rev 变化",用于定位 REV 标记异常。
otcDebug.log('[onBondPriceKeydown] type=' + type + ' kc=' + kc +
' before: driver=' + item.bondDriverType + ' rev=' + JSON.stringify(item.bondRev));
SwapCalc.applyBondManualEdit(item, type);
this.syncBondFlags(item);
otcDebug.log('[onBondPriceKeydown] after: driver=' + item.bondDriverType + ' rev=' + JSON.stringify(item.bondRev));
},
//确保 bondDriverType/bondAuto/bondRev 的变更触发 Vue 响应式更新。
//swapCalc.js 的纯函数直接对 state 赋值,但这些属性不在后端数据中(非响应式),
//直接赋值不触发视图更新。$set 设置相同值也不会触发更新,所以每次都创建新对象引用。
syncBondFlags(item) {
// 用全新对象引用强制触发 Vue 2 响应式更新(旧引用相等时 Vue 会跳过更新)
this.$set(item, 'bondDriverType', item.bondDriverType === undefined ? null : item.bondDriverType);
this.$set(item, 'bondAuto', { CP: !!item.bondAuto.CP, DP: !!item.bondAuto.DP, YD: !!item.bondAuto.YD });
this.$set(item, 'bondRev', { CP: !!item.bondRev.CP, DP: !!item.bondRev.DP, YD: !!item.bondRev.YD });
// $forceUpdate 确保视图刷新(兜底:即使 $set 因相同值未触发也能更新)
this.$forceUpdate();
},
//全价列:普通标的重算名义本金;债券标的按交互约定3 标 REV(不联动)。
// 回车计算由独立的 v-on:enter="onBondPriceEnter(item,'DP')" 负责。
onDpPriceInput(item) {
this.changeSpotPrice(item); // 名义本金随全价变化(非债券标的的既有逻辑)
if (item.isBond) {
SwapCalc.applyBondManualEdit(item, 'DP'); // 交互约定3:失焦未回车→REV
this.syncBondFlags(item);
}
},
//放弃手动标记、以当前某字段为源重新自动反算("重算"按钮)。
resetBondCalc(item) {
if (!item.isBond) return;
// 优先沿用用户最后一次回车锁定的源(bondDriverType),避免"净价残留脏值"时误以净价为源反算出连锁错误;
// 无历史源时再按 净价→全价→收益率 回退挑一个有值的字段。
var hasVal = function (v) { return v !== null && v !== '' && !isNaN(v); };
var driver = item.bondDriverType
|| (hasVal(item.PosiNetNoFeePrice) ? 'CP'
: hasVal(item.PosiGrossPrice) ? 'DP'
: hasVal(item.InitYtm) ? 'YD' : null);
if (!driver) return;
this.calcBondForItem(item, driver);
},
//以 driver(回车字段) 为源调 /Bond/CalcBond;回写时跳过源字段(保留用户手输值)、覆盖另两个。
//错误处理(对齐 bond-oms-ui / zszq-bond-oms):
// - 业务/参数错误(success=false 或 errCode!=0,如债券不存在/信息不全/参数非法)
// 以非阻塞 toast(main.message) 反馈真实原因,绝不回写、不弹模态框、不阻止手工输入;
// - 网络/服务异常:框架 main.post 已弹"请求失败",此处不再二次提示。
// ⚠️【部署 / 踩坑必读】本文件 swapTradeEdit.js 是【独立 <script> 引用】,并不在 bundle.js 里拼接!
// 页面用 ?v=JsVersion 缓存键引用(JsVersion = bin/YLErp* DLL 最大 mtime)。
// 因此:改了本文件后必须 dotnet publish(重编 DLL 才会刷新 JsVersion 缓存键),
// 否则浏览器仍读旧缓存 —— 表现为"本地/某次提交明明改了却没生效"。
// ⚠️【接线 / 踩坑必读】计算器成败路由见 base/main.js __post 注释:业务错误走 reject。
// 本函数曾把"约定2补充(算不出→清另两字段+清标识)"只写在 .done 内,
// 而 .done 在 reject 时不触发 → 那段是死代码、长期静默失效;现已改到 .fail(见下)。
calcBondForItem(item, explicitDriver) {
var driver = explicitDriver || item.bondDriverType;
if (!item.isBond || !driver) return;
item.bondDriverType = driver; // 先定源,回写时 applyBondCalcResult 会跳过该字段
if (!item.UnderlyingCode) { main.message("请先选择债券标的"); return; }
var price = driver === 'CP' ? item.PosiNetNoFeePrice
: driver === 'DP' ? item.PosiGrossPrice
: item.InitYtm;
if (price === null || price === '' || isNaN(price)) {
// 交互约定2补充:源字段为空/非数也算"算不出来",清另两字段+所有标识
SwapCalc.applyBondCalcFailure(item, driver);
self.syncBondFlags(item);
return;
}
var priceType = driver === 'CP' ? 'CP' : driver === 'DP' ? 'DP' : 'YD';
var self = this;
// 估值日:债券互换的"期初"净价/全价/收益率应以【互换起始日 StartDate】估值。
// 该字段在页面上本就是可选择的日期控件(vue-datepicker),用户可改;现已默认即有。
// 不再"悄悄回退到交易日(TradeDate)"——真的为空时直接报错提示,由用户填写/手动填三数(需求2)。
var targetDate = (this.trade && this.trade.StartDate) ? this.trade.StartDate : null;
// 调试埋点(otcDebug 仅在 ?otcdebug=1 时输出):抓"回车→调计算器→源/结果"全链路,定位三字段互算异常。
otcDebug.log('[calcBondForItem] enter driver=' + driver + ' priceType=' + priceType +
' price(sent)=' + SwapCalc.bondPriceToCalc(price) + ' targetDate=' + targetDate);
var sdMsg = SwapCalc.getBondStartDateMissingMsg(targetDate);
if (sdMsg) {
// D3 同款去重:开始日缺失时用户在价格格逐键输入会连刷相同提示,故同一文案只弹一次
if (SwapCalc.shouldShowBondErr(item, sdMsg)) main.message(sdMsg);
// 交互约定2补充:开始日缺失也算"算不出来",清另两字段+所有标识
SwapCalc.applyBondCalcFailure(item, driver);
self.syncBondFlags(item);
return; // 估值日缺失:不调用计算器、不覆盖任何手工输入
}
main.post('/Bond/CalcBond', {
underlyingCode: item.UnderlyingCode,
// 模型 item.PosiGrossPrice/PosiNetNoFeePrice/InitYtm 存的是【存储态小数】(如 0.995),
// 而 bond-calc 要的是【展示态/每百元百分比】(如 99.5)。这俩靠 BondPriceConverter.ToDisplay(×100)/ToStorage(÷100) 对齐。
// 此处传给计算器前必须 bondPriceToCalc(存储→展示);回写时再 bondCalcPriceToStorage(展示→存储),
// 否则计算器拿到 0.995≈1 当 1% of par 直接发散成离谱值。
price: SwapCalc.bondPriceToCalc(price),
priceType: priceType,
targetDate: targetDate
}, { alertFn: main.message }).done(function (resp) { // ⚠️ .done 仅在 success(=resolve) 时触发;业务失败/网络异常请走下方 .fail,此处写失败处理是死代码
if (!resp || !resp.obj) {
// 网络错误/响应异常:框架已弹"请求失败",按交互约定2补充清另两字段+标识
SwapCalc.applyBondCalcFailure(item, driver);
self.syncBondFlags(item);
return;
}
// 业务层错误(债券不存在/信息不全/参数非法):仅提示,绝不清空手工输入
var err = SwapCalc.getBondCalcErrorMessage(resp.obj);
if (err) {
// D3 修复:不可算债券上用户逐键手填时,每次按键都触发一次失败计算并弹 toast,会连刷相同提示;
// 同一错误文案连续出现只弹一次(shouldShowBondErr 维护 item._lastBondErr),抑制噪声、不影响任何计算逻辑。
if (SwapCalc.shouldShowBondErr(item, err)) main.message(err);
SwapCalc.applyBondCalcFailure(item, driver); // 交互约定2:失败→清另两字段+标识
self.syncBondFlags(item);
otcDebug.log('[calcBondForItem] calc-error driver=' + driver + ' msg=' + err);
return;
}
item._lastBondErr = null; // 成功则清标记,便于下次真出不同错误时仍能提示
// 以既有三字段为代理,调用纯函数;手工输入字段保留原始十进制字符串,避免经 Number 参与计算后丢失末位。
// 关键:proxy 内必须统一为【展示态】(per-100-face),因为 applyBondCalcResult 写入的是计算器返回的展示态。
// 模型字段是【存储态小数】(percent:true 下 1.00 对应界面 100),所以初始化时要 bondPriceToCalc(×100)
// 若直接用存储态初始化,则源字段被 applyBondCalcResult 跳过后,proxy 中仍残留存储态,
// 后续再 ÷100 就会导致该字段被二次缩小(如输入 100 变成 1)。
var proxy = {
cleanPrice: SwapCalc.bondPriceToCalc(item.PosiNetNoFeePrice),
dirtyPrice: SwapCalc.bondPriceToCalc(item.PosiGrossPrice),
ytm: SwapCalc.bondPriceToCalc(item.InitYtm)
};
SwapCalc.applyBondCalcResult(proxy, resp.obj, driver);
// 回写前 bondCalcPriceToStorage(÷100)(展示态→存储态小数):proxy 里均为展示态;
// 模型字段存存储态(0.995),须 ÷100 落回模型,否则配合 percent:true 显示会 ×100 成离谱值。
item.PosiNetNoFeePrice = self.roundStoragePrice(item, SwapCalc.bondCalcPriceToStorage(proxy.cleanPrice), 'netPrice');
item.PosiGrossPrice = self.roundStoragePrice(item, SwapCalc.bondCalcPriceToStorage(proxy.dirtyPrice), 'grossPrice');
item.InitYtm = self.roundStoragePrice(item, SwapCalc.bondCalcPriceToStorage(proxy.ytm), 'yield');
SwapCalc.applyBondCalcSuccess(item, driver); // 交互约定2:成功→标 源/AUTO
self.syncBondFlags(item);
otcDebug.log('[calcBondForItem] success driver=' + driver + ' auto=' + JSON.stringify(item.bondAuto) + ' rev=' + JSON.stringify(item.bondRev));
// 名义本金依赖全价(PosiGrossPrice):以净价/收益率为源反算出的全价被回写后,
// 直接赋值不会触发组件 input 事件,需在此显式重算,保持名义本金与全价一致。
if (self.calcNotional) self.calcNotional();
}).fail(function (resp) {
// ⚠️【踩坑记录 / 约定2补充 为何一度不生效】main.post(__post) 在 resp.success===false
// (业务错误=计算器算不出来)或网络异常时调用 deferred.reject()Promise 进入 rejected 态;
// 而 .done() 只在 resolved 态触发 —— 历史上把 applyBondCalcFailure(清另两字段+清标识)
// 写在 .done 内的「业务错误分支」里,业务失败时那段根本不执行,表现就是
// "约定2补充静默失效、auto/源标识纹丝不动"。正确做法就是像现在这样统一在 .fail 里落地。
// 交互约定2补充:计算器算不出来 → 保留源字段值、另两字段清空为空白、三个数值旁标识(源/AUTO/REV)全清。
// 错误文案已由 __post 的 alertFn(main.message) 非阻塞提示,此处不重复弹,仅清数值+清标识
// (与 .done 内 !resp.obj 分支行为一致;左侧 .done 的 getBondCalcErrorMessage 分支为兼容冗余,主路径在此)。
SwapCalc.applyBondCalcFailure(item, driver);
self.syncBondFlags(item);
otcDebug.log('[calcBondForItem] post-fail/reject driver=' + driver);
});
},
//变更收费基本单位
changeOpenFeeType() {
var thisObj = this;
thisObj.paySwapList.forEach(item => {
thisObj.changeTradingFeeUnit(item);
})
},
//变更数量
changeQuantity(item) {
this.calcNotional();
},
//变更标的单价
changeSpotPrice(item) {
this.calcNotional();
},
//变更标的合约乘数
changeContractSize(item) {
this.calcNotional();
},
//变更名义本金(仅格式化,不反算数量)
changeStockEqvNotional() {
this.trade.StockEqvNotional = swapPricePrecision.normalizeCommon('amount', this.trade.StockEqvNotional);
this.refreshPayTradingFeesByUnit();
},
//变更初始预付金 为¥
showAbsPrice() {
this.trade.trade_Initial_Margin.MarginType = 1;
this.trade.trade_Initial_Margin.MarginValue = this.trade.trade_Initial_Margin.MarginValue * this.trade.StockEqvNotional;
},
//变更单位交易费用 为%
showPercentPrice() {
this.trade.trade_Initial_Margin.MarginType = 0;
this.trade.trade_Initial_Margin.MarginValue = this.trade.StockEqvNotional == 0 ? 0 : this.trade.trade_Initial_Margin.MarginValue / this.trade.StockEqvNotional;
},
//成交日期变更时自动修正联动日期
onTradeDateChange() {
if (this.trade.StartDate < this.trade.TradeDate) {
// 开始日期 = 成交日期 + 1天,跳过节假日
var d = new Date(this.trade.TradeDate);
d.setDate(d.getDate() + 1);
while (ylotc.isHoliday(d)) {
d.setDate(d.getDate() + 1);
}
this.trade.StartDate = this.formatDate(d);
}
// 到期日期必须 ≥ 成交日期 且 ≥ 开始日期
if (this.trade.ExerciseDate < this.trade.TradeDate || this.trade.ExerciseDate < this.trade.StartDate) {
// 到期日期 = 开始日期 + 14天,跳过节假日
var sd = new Date(this.trade.StartDate);
sd.setDate(sd.getDate() + 14);
while (ylotc.isHoliday(sd)) {
sd.setDate(sd.getDate() + 1);
}
this.trade.ExerciseDate = this.formatDate(sd);
}
this.changeTradeDate();
},
//开始日期变更时检查到期日期
onStartDateChange() {
if (this.trade.ExerciseDate < this.trade.StartDate) {
var sd = new Date(this.trade.StartDate);
sd.setDate(sd.getDate() + 14);
while (ylotc.isHoliday(sd)) {
sd.setDate(sd.getDate() + 1);
}
this.trade.ExerciseDate = this.formatDate(sd);
}
this.changeTradeDate();
},
//变更交易日期
changeTradeDate(force) {
this.changeObservationStart();
if (this.Obervation) {
this.Obervation.ObservationStart = this.trade.StartDate;
}
this.calcNotional();
this.refreshDatepicker();
this.changeMarginDate();
// 动态更新开始日期和到期日期的下限
this.$nextTick(() => {
if (this.$refs.startDatePicker) {
this.$refs.startDatePicker.refresh(this.trade.TradeDate, null);
}
if (this.$refs.exerciseDatePicker) {
var minExerciseDate = this.trade.StartDate > this.trade.TradeDate ? this.trade.StartDate : this.trade.TradeDate;
this.$refs.exerciseDatePicker.refresh(minExerciseDate, null);
}
});
//this.initMarginRate();
},
calcNotional(calcPrice) {
if (this.paySwapList.length > 0) {
var payItem = this.paySwapList[0];
if (calcPrice) {
this.getSpotPrice(payItem.UnderlyingCode, this.trade.StartDate, payItem);
}
// 守卫: 名义本金必须 round 到 2 位 → 对应历史 bug f873239a(缺 _.round); 外置到 swapCalc.calcStockEqvNotional
var deliveryPrice = this.roundStoragePrice(payItem, payItem.PosiGrossPrice, 'grossPrice');
var stockEqvNotional = SwapCalc.calcStockEqvNotional(deliveryPrice, payItem.PosiQuantity, payItem.ContractSize);//名义本金=期初价格*数量*乘数
this.trade.StockEqvNotional = swapPricePrecision.normalizeCommon('amount', stockEqvNotional);
payItem.PosiNotionalValue = this.trade.StockEqvNotional;
this.refreshPayTradingFeesByUnit();
}
},
//变更到期日
changeExerciseDate() {
this.refreshDatepicker();
this.changeMarginDate();
},
//变更预付金生效日期
changeMarginDate() {
var thisObj = this;
thisObj.observation.ObservationStart = thisObj.trade.StartDate;
var startDate = thisObj.trade.StartDate;
var endDate = thisObj.trade.ExerciseDate;
if (thisObj.marginSwapList) {
thisObj.marginSwapList.forEach((item, index) => {
if (item.HappenDate < startDate) {
thisObj.marginSwapList[index].HappenDate = startDate;
thisObj.changeStartMarinRateDate(thisObj.marginSwapList[index]);
}
else if (item.HappenDate > endDate) {
thisObj.marginSwapList[index].HappenDate = endDate;
thisObj.changeStartMarinRateDate(thisObj.marginSwapList[index]);
}
});
}
},
// 变更预付金结算规则起息日期
changeStartMarinRateDate(item) {
if (item.Obervation) {
item.Obervation.ObservationStart = item.HappenDate;
}
},
//变更单位交易费用
changeTradingFeeUnit(item) {
this.refreshTradingFeePendingByUnit(item);
},
//变更交易费用
changeTradingFee(item) {
this.refreshTradingFeeUnitByPending(item);
},
showPayAbsPrice() {
this.posiFeeModePercent = false;
this.paySwapList.forEach(item => {
item.PosiFeeType = consPosiFeeType.Unit;
item.PosiTradingFeeUnit = 0;
item.PosiTradingFeePending = 0;
});
},
showPayPercentPrice() {
this.posiFeeModePercent = true;
this.paySwapList.forEach(item => {
item.PosiFeeType = consPosiFeeType.Percent;
item.PosiTradingFeeUnit = 0;
item.PosiTradingFeePending = 0;
});
},
savetrade() {
if (!this.checkSubmitData()) {
return false;
}
this.savetrade_inner();
},
savetrade_inner() {
var thisObj = this;
//this.paySwapList.forEach(x => {
// x.SwapIntervals = this.trade.ExerciseDate + ";" + x.InterestRate;
//});
this.trade.IsUsePremiumRate = true;
this.trade.IsTradePricePayType = true;
this.trade.UnderlyingInstrumentType = "Stock";
this.trade.swap_positions = [];
var date = thisObj.trade.ExerciseDate;
var errorcount = 0;
if (this.trade.trade_extend.ExtendObj.InterestCalcMode == "00" || this.trade.trade_extend.ExtendObj.InterestCalcMode == "10") {
date = moment(date).add(-1, 'days').format('YYYY-MM-DD');
}
const serializeSwapIntervals = function (position) {
position.SwapIntervalList.forEach(interval => {
const rate = roundObservationRate(interval.Rate);
if (rate !== null && rate !== '') {
interval.Rate = Number(rate);
}
});
position.InterestSwapInterval = JSON.stringify(position.SwapIntervalList);
};
this.getSwapList.forEach((x,index) => {
if ((x.UnderlyingCode == null || x.UnderlyingCode.length == 0) && x.SwapIntervalList.length == 0) {
var interval = {
Date: date,
Rate: x.InterestRateDefault,
Settlement: 0
}
x.SwapIntervalList.push(interval);
}
if (x.interest_rest_days != null && x.interest_rest_days <= 0) {
main.message("利息端第" + (index + 1) + "行重置频率必须大于0");
errorcount++;
return false;
}
x.FloatRateUnderlyingCode = x.FloatRateUnderlyingCode == "--" ? null : x.FloatRateUnderlyingCode;
serializeSwapIntervals(x);
thisObj.trade.swap_positions.push(x);
});
if (errorcount > 0) {
return;
}
var margin = 0;
this.marginSwapList.forEach((x, index) => {
if ((x.UnderlyingCode == null || x.UnderlyingCode.length == 0) && x.SwapIntervalList.length == 0) {
var interval = {
Date: date,
Rate: x.InterestRateDefault,
Settlement: 0
}
x.SwapIntervalList.push(interval);
}
if (!x.HappenDate) {
main.message("预付金/预付金第" + (index + 1) + "行生效日期不能为空");
errorcount++;
return false;
}
x.FloatRateUnderlyingCode = x.FloatRateUnderlyingCode == "--" ? null : x.FloatRateUnderlyingCode;
margin = margin + x.InterestPrincipalFix * (x.InterestDirection == 1 ? 1 : -1);
serializeSwapIntervals(x);
thisObj.trade.swap_positions.push(x);
});
if (errorcount > 0) {
return;
}
thisObj.trade.trade_Initial_Margin.MarginType = 1;
thisObj.trade.trade_Initial_Margin.MarginValue = margin;
thisObj.trade.trade_Initial_Margin.Direction = margin > 0 ? 1 : 2;
if (this.trade.StructureType != "多空组合") {
this.paySwapList.forEach((x, index) => {
if (!x.UnderlyingCode) {
main.message("浮动端第" + (index + 1) + "行标的不能为空");
errorcount++;
return false;
}
if (!x.PosiQuantity > 0) {
main.message("浮动端第" + (index + 1) + "数量不能为0");
errorcount++;
return false;
}
if (!x.PosiGrossPrice) {
main.message("浮动端第" + (index + 1) + "期初价格不能为空");
errorcount++;
return false;
}
x.PosiGrossPrice = thisObj.roundStoragePrice(x, x.PosiGrossPrice, 'grossPrice');
x.PosiNetNoFeePrice = thisObj.roundStoragePrice(x, x.PosiNetNoFeePrice, 'netPrice');
x.InitYtm = x.InitYtm == null ? null : thisObj.roundStoragePrice(x, x.InitYtm, 'yield');
x.PosiFeeType = thisObj.normalizePosiFeeType(x.PosiFeeType);
thisObj.trade.swap_positions.push(x);
});
} else {
this.trade.trade_extend.ExtendObj.Direction = this.paySwapList[0].PosiDirection;
}
if (errorcount > 0) {
return;
}
this.trade.MetaDic = {
'组合标的': consMetaDic["组合标的"] ? JSON.stringify(consMetaDic["组合标的"]) : '',
'组合标的2': consMetaDic["组合标的2"] ? JSON.stringify(consMetaDic["组合标的2"]) : '',
'交易场所': this.viewState.Extend.TradingPlace,
'清算机构': this.viewState.Extend.ClearingAgency,
"主协议编号": $("#MainProtocolCode").val(),
"补充协议编号": $("#SupProtocolCode").val()
};
this.trade.trade_extend.ExtendJson = JSON.stringify(this.trade.trade_extend.ExtendObj);
//保存环节两类拦截确认(服务端按确认/审批同一协议返回 AdditionalProcessing):
//1) MarginCreditSplit 预付金授信不足——确认后按 剩余授信+现金差额 拆腿再保存(标准流程);
//2) LackOfMoney 资金不足且系统开启"允许交易特批"——弹"交易特批",特批放行。
//两类可链式发生(拆单后现金仍不足再走特批),确认标记累积在 query 上一并带上
var thisObj = this;
var confirmedProcessings = [];
var doSave = function (additionalProcessing) {
var url = "/swaptrade2/tradeEditJson";
if (!main.isEmpty(additionalProcessing) && confirmedProcessings.indexOf(additionalProcessing) < 0) {
confirmedProcessings.push(additionalProcessing);
}
if (confirmedProcessings.length) {
url += "?additionalProcessing=" + confirmedProcessings.join(",");
}
main.post(url, thisObj.trade).done(function (resp) {
if (resp.obj && resp.obj.proccessType == "AdditionalProcessing") {
if (resp.obj.type == "MarginCreditSplit") {
var splitContent = '<div style="padding:10px">' + resp.obj.message + '</div>';
main.open2("提示",
splitContent,
{
area: ["460px", "260px"],
btn: ['确认拆分', '取消'],
yes: function (index, layero) {
layer.close(index);
doSave("MarginCreditSplit");
}
});
} else if (resp.obj.type == "LackOfMoney") {
var htmlContent = '<div style="padding:10px">' + resp.obj.message + '</div>';
var lackMoneyConfirmLayer = main.open2("提示",
htmlContent,
{
area: ["430px", "175px"],
btn: ['交易特批', '取消'],
yes: function (index, layero) {
var layerIndex = lackMoneyConfirmLayer;
main.confirm("客户资金或授信不足,强制保存会导致本机构产生风险!要继续特批保存?", function () {
layer.close(layerIndex);
doSave("LackOfMoney");
});
}
});
}
return;
}
window.location.href = "/swaptrade2/tradeview?enid=" + resp.obj.EncryptId;
if (window.parent && window.parent.reloadtrade) {
window.parent.reloadtrade();
}
});
};
doSave();
},
changeMainProtocolCode() {
this.getSupProtocolCode();
},
getMainProtocolCode() {
var thisObj = this;
$("#MainProtocolCode option").remove();
main.post("/Client/getMainProtocolCodes", { clientId: $("#ClientId").val() }, { async: false }).done(function (resp) {
var obj = $("#MainProtocolCode");
if (thisObj.client.MainProtocolCode != null && thisObj.client.MainProtocolCode != '') {
obj.append("<option >" + thisObj.client.MainProtocolCode + "</option>");
}
_.forEach(resp, (v) => {
obj.append("<option >" + v.Text + "</option>");
})
});
this.getSupProtocolCode()
},
getSupProtocolCode() {
$("#SupProtocolCode option").remove();
if ($("#MainProtocolCode").val()) {
main.post("/Client/getSideProtocols", { mainProtocol: $("#MainProtocolCode").val() }, { async: false }).done(function (resp) {
var obj = $("#SupProtocolCode");
if (thisObj.client.SupProtocolCode != null && thisObj.client.SupProtocolCode != '') {
obj.append("<option >" + thisObj.client.SupProtocolCode + "</option>");
}
_.forEach(resp, (v) => {
obj.append("<option >" + v + "</option>");
})
});
}
},
changeInterestMode(item) {
},
changeInterestType(item) {
if (!item.interest_rest_days) {
item.interest_rest_days = 7;
}
},
setUnderlyingCode(data, item) {
if (!data || !data.Code) return;
var underlyingCode = item.UnderlyingCode;
if (underlyingCode != data.Code) {
// 簿记基础逻辑1:新增互换交易选择标的后,不自动填充价格等数值。
// 切换标的时清空净价/全价/收益率三字段,由用户手填或回车调计算器反算。
// 历史上此处调 getSpotPrice 自动回填,会导致用户未确认的价格被静默填入。
item.PosiNetNoFeePrice = null;
item.PosiGrossPrice = null;
item.InitYtm = null;
}
item.CountRatio = data.CountRatio;
item.ContractSize = data.ContractSize;
item.UnderlyingInstrumentType = data.InstrumentType;
item.UnderlyingCode = data.Code;
item.underlying = { ...this.underlying };
item.underlying.UnderlyingCode = data.Code;
item.underlying.UnderlyingName = data.Name;
item.underlying.UnderlyingIssuer = data.UnderlyingIssuer;
item.underlying.IssueSize = data.IssueSize;
item.underlying.MaturityDate = data.MaturityDate;
item.underlying.Price = data.Price;
item.underlying.UnderlyingInstrumentType = data.InstrumentType;
item.underlying.QuoteUnitString = data.QuoteUnitString;
this.trade.UnderlyingCode = data.Code;
// 债券标的:标记该浮动腿可启用净价/全价/收益率互算
this.$set(item, 'isBond', tradeHelper.IsBond(data.InstrumentType));
// 切换标的时清空债券三字段互算的手动/源标志(D1 修复:避免旧债券手填状态污染新债券)
SwapCalc.clearBondCalcFlags(item);
this.syncBondFlags(item);
// 簿记基础逻辑1:不再自动调 getSpotPrice 回填价格,由用户手填或回车调计算器
// 续发(renewal)场景的初始价恢复由 refreshInitialUnderlyingPrices 独立处理
//this.initMarginRate();
},
setFloatRateUnderlyingCode(data, item) {
item.FloatRateUnderlyingCode = data.Code;
if (data.Code.length > 0 && data.Code != '--') {
item.IsAnnualized = true;
item.interest_rest_days = 7;
item.interest_rule = data.Code === 'FR007' ? -1 : 0;
} else {
item.interest_rule = null;
}
//this.getFloatRate(item);
},
getFloatRate(item) {
var thisObj = this;
if (item.FloatRateUnderlyingCode == null || item.FloatRateUnderlyingCode == '' || item.FloatRateUnderlyingCode == '--' || thisObj.paySwapList.length == 0) {
item.InterestRateDefault = 0;
blurSwapRate(item);
return;
}
var floatPositionType = thisObj.paySwapList[0].PositionType;
main.post("/SwapFloatRate/MatchRate", { clientId: thisObj.trade.ClientId, underlyingCode: item.FloatRateUnderlyingCode, startDate: thisObj.trade.StartDate, endDate: thisObj.trade.ExerciseDate })
.done(function (resp) {
if (resp.success) {
if (resp.obj != null) {
item.InterestRateDefault = floatPositionType == 1 ? resp.obj.LongPricePoint : resp.obj.ShortPricePoint;
item.InterestRateDefault = item.InterestRateDefault * 0.0001;
} else {
item.InterestRateDefault = 0;
}
thisObj.blurSwapRate(item);
}
});
},
getSpotPrice(underlyingCode, StartDate, item) {
var thisObj = this;
if (!underlyingCode) return;
main.post("/pricing/AjaxGetUnderlyingPrice", { underlyingCode: underlyingCode, tradeDate: StartDate })
.done(function (resp) {
if (!resp || !resp.success || !resp.obj) {
main.message("未获取到标的价格");
return;
}
// 行情接口(AjaxGetUnderlyingPrice)对债券返回的 price/netPrice 本就是【存储态小数】(1.0 代表 100 元),
// 与 PosiGrossPrice/PosiNetNoFeePrice 模型字段同量纲(见 PricingController: EodPrice 直接返回、
// 非 EodPrice 分支 ×bondPriceMultiple=0.01)。故此处仅做精度格式化,**不可**再 bondCalcPriceToStorage(÷100)
// 否则默认价 1.0 被除成 0.01,界面 percent:true 再 ×100 显示为 1"被自动除以100"bug)。
// 计算器(/Bond/CalcBond)返回的才是展示态,其 ÷100 落库逻辑在 calcBondForItem 内处理。
item.PosiNetNoFeePrice = thisObj.roundStoragePrice(item, resp.obj.netPrice, 'netPrice');
item.PosiGrossPrice = thisObj.roundStoragePrice(item, resp.obj.price, 'grossPrice');
thisObj.calcNotional();
});
},
//年化利率变更同步修改 同行观察日利率
blurSwapRate(item) {
if (item.SwapIntervalList.length > 0) {
var swapRate = item.InterestRateDefault;
var same = true;
var rate1 = item.SwapIntervalList[0].Rate;
item.SwapIntervalList.forEach(function (item) {
if (item.Rate != rate1) {
same = false;
}
});
if (same) {
var confirmMsg = "检测到互换利率发生变化,将自动覆盖观察列表";
main.confirm(confirmMsg, function () {
item.SwapIntervalList.forEach(function (element, index, arr) {
arr[index].Rate = swapRate;
});
var observationDates = JSON.stringify(item.SwapIntervalList);
item.InterestSwapInterval = observationDates;
});
} else {
main.message("检测到互换利率与互换利率列表不一致,请手动重新生成互换列表");
}
}
},
changeObservationStart() {
var thisObj = this;
thisObj.getSwapList.forEach((item, index) => {
if (item.Obervation) {
thisObj.getSwapList[index].Obervation.ObservationStart = thisObj.trade.TradeDate;
}
});
},
//设置观察日弹框
formatObservationRateInput(item) {
item.val = formatObservationRate(item.val);
},
initObservationDates(item, type) {
this.observationType = type;
var thisObj = this;
var observation = item.Obervation;
thisObj.observation.index = item.index;
thisObj.observation.IntervalList = item.SwapIntervalList;
thisObj.observation.DefaultTitle1Value = item.InterestRateDefault;
thisObj.observation.ObservationNum = observation ? observation.ObservationNum : 1;
thisObj.observation.ObservationUnit = observation ? observation.ObservationUnit : 'D';
thisObj.observation.ObservationHolidayType = observation ? observation.ObservationHolidayType : 'Following';
thisObj.observation.ObservationAlignEnd = observation ? observation.ObservationAlignEnd : true;
if (type == 1) {
thisObj.observation.ObservationCalendar = observation ? observation.ObservationCalendar : 'Chn';
thisObj.observation.ObservationSettlementRules = observation ? observation.ObservationSettlementRules : 0;
}
thisObj.observation.IsDeductPrincipal = observation ? observation.IsDeductPrincipal : true;
if (type == 1) {
thisObj.observation.IsDeductPrincipal = false;
}
var startTime = thisObj.trade.TradeDate;
var endTime = thisObj.trade.ExerciseDate;
if (main.isEmpty(startTime)) {
main.message("请输入开始日期");
return false;
}
if (main.isEmpty(endTime)) {
main.message("请输入到期日期");
return false;
}
if (thisObj.observation.ObservationStart < startTime) {
thisObj.observation.ObservationStart = startTime;
}
if (thisObj.observation.ObservationStart > endTime) {
thisObj.observation.ObservationStart = endTime;
}
thisObj.observation.ObservationDataList = [];
var floatRateCode = type == 1 ? (item.FloatRateUnderlyingCode || '--') : '';
thisObj.observation.IntervalList.forEach((value, num, arr) => {
var val = value.Rate;
var _date = "";
var m = new moment(value.Date);
if (!isNaN(m.date())) {
_date = m.format("YYYY-MM-DD");
}
var itemChecked = thisObj.observation.IntervalList.length >= num ? (value.Settlement == 1 ? true : false) : false;
var disabled = false;
if (_date == thisObj.trade.ExerciseDate) {
disabled = true;
itemChecked = false;
}
var obdate = {};
if (type == 1) {
var settlementDate = value.Date;
if (value.SettlementDate!=null) {
settlementDate = value.SettlementDate;
}
var m = new moment(settlementDate);
obdate = {
date: _date,
SettlementDate: m.format("YYYY-MM-DD"),
floatRateCode: floatRateCode,
val: formatObservationRate(_.toString(val) ? swapPricePrecision.shiftDecimal(val, 2) : ""),
itemChecked: itemChecked,
disabled: disabled
};
} else {
obdate = {
date: _date,
val: formatObservationRate(_.toString(val) ? swapPricePrecision.shiftDecimal(val, 2) : ""),
itemChecked: itemChecked,
disabled: disabled
};
}
thisObj.observation.ObservationDataList.push(obdate);
});
var area = type == 1 ? ['800px', '600px'] : ['750px', '600px'];
layer.open({
type: 1,
area: area,
title: "设置互换日期",
shadeClose: false,
shade: 0.4,
content: $("#observationInfosEdit")
});
},
//计算结算日期
calcSettleDate(observationDate, settlementRules) {
if (!observationDate) return "";
var m = new moment(observationDate);
var settleDate = m.add(settlementRules, 'days').format("YYYY-MM-DD");
return settleDate;
},
//生成观察日操作
GetObservationDates() {
var thisObj = this;
var observationNum = thisObj.observation.ObservationNum;
var observationUnit = thisObj.observation.ObservationUnit;
var observationHolidayType = thisObj.observation.ObservationHolidayType;
var alignEnd = thisObj.observation.ObservationAlignEnd;
var settlementRules = thisObj.observation.ObservationSettlementRules;
var calendar = thisObj.observation.ObservationCalendar;
var floatRateCode = this.observationType == 1 ? (thisObj.getSwapList[thisObj.observation.index]?.FloatRateUnderlyingCode || '--') : '';
thisObj.observation.ObservationDataList = [];
if (this.observationType == 1) {
var postData = {
startDate: thisObj.observation.ObservationStart,
endDate: thisObj.trade.ExerciseDate,
termStr: observationNum + observationUnit,
holidayAdjustment: observationHolidayType,
alignEnd: alignEnd,
calcMode: thisObj.trade.trade_swap.RateCalcMode,
calendar: calendar,
settlementRules: settlementRules
};
main.post("/trade/GetSwapObservationDateList", postData).done(
function (res) {
$.each(res.obj, function (i) {
var _date = "";
var m = new moment(this.ObservationDate);
if (!isNaN(m.date())) {
_date = m.format("YYYY-MM-DD");
}
var _settleDate = "";
var sm = new moment(this.SettleDate);
if (!isNaN(sm.date())) {
_settleDate = sm.format("YYYY-MM-DD");
}
var val = swapPricePrecision.shiftDecimal(thisObj.observation.DefaultTitle1Value, 2);
var itemChecked = true;
var disabled = false;
if (_date == thisObj.trade.ExerciseDate) {
disabled = true;
itemChecked = false;
}
var obdate = {
date: _date,
SettlementDate: _settleDate,
floatRateCode: floatRateCode,
val: formatObservationRate(val),
itemChecked: itemChecked,
disabled: disabled
};
thisObj.observation.ObservationDataList.push(obdate);
});
thisObj.initObservationCheckedAll();
});
} else {
var postData = {
startDate: thisObj.observation.ObservationStart,
endDate: thisObj.trade.ExerciseDate,
termStr: observationNum + observationUnit,
holidayAdjustment: observationHolidayType,
alignEnd: alignEnd,
calcMode: thisObj.trade.trade_swap.RateCalcMode
};
main.post("/trade/GetObservationDateList", postData).done(
function (res) {
$.each(res.obj, function (i) {
var _date = "";
var m = new moment(this);
if (!isNaN(m.date())) {
_date = m.format("YYYY-MM-DD");
}
var val = swapPricePrecision.shiftDecimal(thisObj.observation.DefaultTitle1Value, 2);
var itemChecked = true;
var disabled = false;
if (_date == thisObj.trade.ExerciseDate) {
disabled = true;
itemChecked = false;
}
var obdate = {
date: _date,
val: formatObservationRate(val),
itemChecked: itemChecked,
disabled: disabled
};
thisObj.observation.ObservationDataList.push(obdate);
});
thisObj.initObservationCheckedAll();
});
}
},
//编辑观察日功能数据处理
SetObservationDates() {
var observationStr = "";
if (this.observation.ObservationDataList != null) {
this.observation.ObservationDataList.forEach(item => {
observationStr = observationStr + item.date + ", " + item.SettlementDate + ", " + observationRateTextFromPercent(item.val) + ", " + item.itemChecked + ";\n";
});
}
this.observation.ObservationInterval = observationStr;
$("#myModal").modal("show");
},
// 粘贴功能
pasteMe(e, rowData, index) {
let copyData = '';
var thisObj = this;
if (e.clipboardData || e.originalEvent) {
var clipboardData = (e.clipboardData || e.originalEvent.clipboardData);
copyData = clipboardData.getData('Text');
var arr = copyData.replace(/\t/g, ', ').replace(/\r/g, '').split('\n');
arr.forEach((v, i) => {
if (v && !v.trim().endsWith(';')) {
arr[i] = v + ";";
}
});
thisObj.observation.ObservationInterval = arr.join('\r\n');
e.preventDefault();
}
},
//编辑观察日模态框保存操作
saveObservationModal() {
this.initObservationDataList();
$("#myModal").modal("hide");
},
//编辑观察日模态框保存 数据转换
initObservationDataList() {
var thisObj = this;
thisObj.observation.ObservationInterval = thisObj.observation.ObservationInterval.trim().replaceAll("\n", "").replaceAll(" ", "");
var observationDates = thisObj.observation.ObservationInterval;
var items = observationDates.split(";").filter(o => o);
thisObj.observation.ObservationDataList = [];
var floatRateCode = thisObj.getSwapList[thisObj.observation.index]?.FloatRateUnderlyingCode || '--';
items.forEach(function (item) {
if (item) {
var values = item.split(",");
var itemChecked = JSON.parse(values[3].trim());
var disabled = false;
if (values[0] == thisObj.trade.ExerciseDate) {
disabled = true;
itemChecked = false;
}
var val = values[2].trim();
var obdate = {
date: values[0],
SettlementDate: values[1] || "",
floatRateCode: floatRateCode,
val: formatObservationRate(_.toString(val) ? swapPricePrecision.shiftDecimal(val, 2) : ""),
itemChecked: itemChecked,
disabled: disabled
}
thisObj.observation.ObservationDataList.push(obdate);
}
});
thisObj.initObservationCheckedAll();
},
//观察日列表初始化是否全选
initObservationCheckedAll() {
var thisObj = this;
var noCheckedItems = thisObj.observation.ObservationDataList.filter(item => {
if ((item.itemChecked == false) && item.date != thisObj.trade.ExerciseDate) {
return item;
}
});
this.observation.CheckedAll = noCheckedItems.length == 0 && thisObj.observation.ObservationDataList.length > 0;
},
//删除单行观察日
deleteObItem(item) {
var thisObj = this;
var curindex = 0;
for (var i = 0; i < thisObj.observation.ObservationDataList.length; i++) {
if (thisObj.observation.ObservationDataList[i].date == item.date) {
curindex = i;
break;
}
}
thisObj.observation.ObservationDataList.splice(curindex, 1);
},
//添加观察日
addnewitem() {
var thisObj = this;
var obdate = {};
if (this.observationType == 1) {
var floatRateCode = thisObj.getSwapList[thisObj.observation.index]?.FloatRateUnderlyingCode || '--';
obdate = {
date: '',
SettlementDate: '',
floatRateCode: floatRateCode,
val: formatObservationRate(0),
itemChecked: true,
disabled: false
};
} else {
obdate = {
date: '',
val: formatObservationRate(0),
itemChecked: true,
disabled: false
};
}
thisObj.observation.ObservationDataList.push(obdate);
},
//编辑观察日模态框关闭
closeObservationModal() {
$('#myModal').modal('hide');
},
//关闭互换layer层
closeParentWindow() {
layer.closeAll();
},
//观察日列表确认
ObservationConfirm() {
var thisObj = this;
var hasTimeError = false;
var hasNumberError = false;
var observationArr = [];
thisObj.observation.ObservationDataList.forEach(item => {
var m = new moment(item.date);
if (isNaN(m.date())) {
hasTimeError = true;
}
var dateFormat = /^(-?\d+)(\.\d+)?$/;
if (!dateFormat.test(item.val)) {
hasNumberError = true;
}
var observation = {
Date: item.date,
Rate: observationRateFromPercent(item.val),
Settlement: item.itemChecked ? 1 : 0,
SettlementDate: item.SettlementDate || null // 结算日期
}
observationArr.push(observation);
});
if (hasTimeError) {
alert("日期格式有误,请输入正确格式");
return false;
}
if (hasNumberError) {
alert("请输入正确的数字格式");
return false;
}
if (this.observationType == 1) {
thisObj.getSwapList.forEach((val, num, arr) => {
if (val.index == thisObj.observation.index) {
arr[num].InterestSwapInterval = JSON.stringify(observationArr);
thisObj.observation.ObservationInterval = arr[num].InterestSwapInterval;
arr[num].SwapIntervalList = observationArr;
arr[num].Obervation = JSON.parse(JSON.stringify(thisObj.observation));
}
});
} else {
thisObj.marginSwapList.forEach((val, num, arr) => {
if (val.index == thisObj.observation.index) {
arr[num].InterestSwapInterval = JSON.stringify(observationArr);
thisObj.observation.ObservationInterval = arr[num].InterestSwapInterval;
arr[num].SwapIntervalList = observationArr;
arr[num].Obervation = JSON.parse(JSON.stringify(thisObj.observation));
}
});
}
this.closeParentWindow();
},
//互换提交数据有效性检查
checkSubmitData() {
//验证输入的日期格式是否正确(包括交易日期、开始日、到期日)
var ExerciseDate = this.trade.ExerciseDate;
var regTime = /^[0-9]{4}-[0-1]?[0-9]{1}-[0-3]?[0-9]{1}$/;
if (main.isEmpty(ExerciseDate)) {
main.message("请输入到期日期");
return false;
}
if (!regTime.test(ExerciseDate)) {
main.message("请输入正确的到期日期格式");
return false;
}
var TradeDate = this.trade.TradeDate;
if (main.isEmpty(TradeDate)) {
main.message("请输入成交日期");
return false;
}
if (!regTime.test(TradeDate)) {
main.message("请输入正确的成交日期格式");
return false;
}
if (TradeDate > ExerciseDate) {
main.message("到期日期不能早于成交日期");
return false;
}
var StartDate = this.trade.StartDate;
if (main.isEmpty(StartDate)) {
main.message("请输入开始日期");
return false;
}
if (!regTime.test(StartDate)) {
main.message("请输入正确的开始日期格式");
return false;
}
if (StartDate < TradeDate) {
main.message("开始日期不能早于成交日期");
return false;
}
if (StartDate > ExerciseDate) {
main.message("到期日期不能早于开始日期");
return false;
}
if (!this.trade.AssetId) {
main.message("请设置簿记账户");
return false;
}
if (!this.trade.ClientId) {
main.message("请设置交易对手方");
return false;
}
if (this.trade.StructureType!="多空组合"&&(!this.trade.StockEqvNotional > 0 || this.trade.StockEqvNotional == null)) {
main.message("请设置名义本金");
return false;
}
if (!this.trade.trade_extend.ExtendObj.AnnualDays > 0) {
main.message("请设置年化天数");
return false;
}
if (this.trade.StructureType == "多空组合"&&!this.checkSwapRateList()) {
return false;
}
if (!this.checkObservationDates()) {
return false;
}
return true;
},
//观察日校验
checkObservationDates() {
var checkGet = true;
var thisObj = this;
if (thisObj.getSwapList != null) {
thisObj.getSwapList.forEach((val, num, arr) => {
if (checkGet && val.SwapIntervalList.length > 0) {
checkGet = this.checkSwapTimeRates(val.SwapIntervalList, num + 1, '利息端');
}
});
}
if (!checkGet) {
return false;
}
if (thisObj.marginSwapList != null) {
thisObj.marginSwapList.forEach((val, num, arr) => {
if (checkGet && val.SwapIntervalList.length > 0) {
checkGet = this.checkSwapTimeRates(val.SwapIntervalList, num + 1, '预付金/预付金');
}
});
}
if (!checkGet) {
return false;
}
return true;
},
// 多空组合利息腿校验
checkSwapRateList() {
var check = true;
return check;
},
//观察日起始日期跟交易起始日期检查
checkSwapTimeRates(SwapIntervalList, num, title) {
var StartDate = this.trade.StartDate;
var ExerciseDate = this.trade.ExerciseDate;
var check = true;
SwapIntervalList.forEach(item => {
var _date = "";
var m = new moment(item.Date);
if (!isNaN(m.date())) {
_date = m.format("YYYY-MM-DD");
}
if (_date < StartDate) {
main.message(title + "第" + num + "行互换观察日不可以早于交易开始日");
check = false;
} else if (_date > ExerciseDate) {
main.message(title + "第" + num + "行互换观察日不可以晚于交易到期日");
check = false;
}
});
return check;
},
changeClient: function (client) {
this.trade.ClientId = client.id;
this.client = client;
},
initMarginRate() { //初始预付金 初始话
_that = this;
var swapType = this.trade.StructureType;
var UnderlyingCode = "";
if (swapType != "多空组合") {
swapType = "普通";
UnderlyingCode = this.paySwapList[0].UnderlyingCode;
}
if (page.isAdd && this.trade.ClientId != null && ((swapType == "普通" && UnderlyingCode != "") || this.trade.trade_swap.SwapType == "多空组合")) {
main.post("/swapTrade/GetInitMarginRate", { ClientId: this.trade.ClientId, Type: swapType, UnderlyingCode: UnderlyingCode, tradeDate: this.trade.TradeDate })
.done(function (resp) {
if (resp.obj > 0) {
_that.trade.trade_Initial_Margin.MarginValue = resp.obj
}
});
}
},
//交易审批
submit(status) {
var pop = '';
if (status === 'pass') {
pop = "确认通过审批?";
}
if (status === 'reject') {
pop = "确认拒绝?";
}
var confirmFunc = function (additionalProcessing) {
var pData = { tradeId: page.Trade.id, status: status, text: "" };
if (!main.isEmpty(additionalProcessing)) {
pData.additionalProcessing = additionalProcessing;
}
main.post("/processtradelog/UpdateTradeProcessLog", pData).done(
function (data) {
if (data.obj && data.obj.proccessType == "AdditionalProcessing") {
if (data.obj.type == "LackOfMoney") {
var htmlContent = `<div style="padding:10px">${data.obj.message}</div>`;
var lackMoneyConfirmLayer = main.open2("提示",
htmlContent,
{
area: ["430px", "175px"],
btn: ['交易特批', '取消'],
yes: function (index, layero) {
var layerIndex = lackMoneyConfirmLayer;
main.confirm("客户资金或授信不足,强制成交会导致本机构产生风险!要继续审批通过?", function () {
layer.close(layerIndex);
confirmFunc("LackOfMoney");
});
},
cancel: function (index, layero) {
if (window.parent && window.parent.reloadtrade) {
window.parent.reloadtrade();
}
(parent || window).layer.closeAll();
}
});
}
return;
}
(parent || window).main.message(data.msg);
if (page.generateAmendDoc) {
if (data.obj && data.obj.generateChangeSuccess) {
main.downloadFiles(data.obj.url);
try { window.parent.reloadtrade(); } catch (e) { }
}
}
if (page.needSendChangeEmail) {
if (data.obj && data.obj.generateChangeSuccess) {
main.confirm("确认发送变更确认书?",
function () {
main.post("/trade/SendChangeConfirmEmails", { tradeids: tradeId }).done(
function (data) {
if (data.success) {
main.message("发送成功");
} else {
main.message(data.msg);
}
try {
window.parent.reloadtrade();
} catch (e) {
}
});
});
} else {
try {
window.parent.reloadtrade();
} catch (e) {
}
}
}
else {
try { window.parent.reloadtrade(); } catch (e) { }
}
if (parent) {
parent.layer.closeAll();
}
});
}
main.confirm(pop, confirmFunc);
},
//初始化利息端列表
initSwapRateList() {
var thisObj = this;
thisObj.getSwapList = thisObj.trade.swap_positions.filter(x => { if ((x.UnderlyingCode == null || x.UnderlyingCode.length == 0) && x.IsInitial && (x.InterestMode == 1 || x.InterestMode == 2 || x.InterestMode == 9)) return x; });
thisObj.getSwapList.forEach((val, num, arr) => {
arr[num].index = num;
arr[num].category_tag = arr[num].category_tag || '互换利率';
// 解析 InterestSwapInterval 为 SwapIntervalList
if (arr[num].InterestSwapInterval && !arr[num].SwapIntervalList) {
try {
arr[num].SwapIntervalList = JSON.parse(arr[num].InterestSwapInterval);
} catch (e) { }
}
});
thisObj.marginSwapList = thisObj.trade.swap_positions.filter(x => { if ((x.UnderlyingCode == null || x.UnderlyingCode.length == 0) && x.IsInitial && (x.InterestMode == 5 || x.InterestMode == 6)) return x; });
thisObj.marginSwapList.forEach((val, num, arr) => {
arr[num].index = 1000 + num; // 保证金列表使用 1000+ 偏移,避免与利息腿冲突
arr[num].HappenDate = thisObj.formatDate(arr[num].HappenDate);
// 解析 InterestSwapInterval 为 SwapIntervalList
if (arr[num].InterestSwapInterval && !arr[num].SwapIntervalList) {
try {
arr[num].SwapIntervalList = JSON.parse(arr[num].InterestSwapInterval);
} catch (e) { }
}
});
if (thisObj.trade.StructureType == '多空组合') {
thisObj.paySwapList = [];
} else {
thisObj.paySwapList = thisObj.trade.swap_positions.filter(x => { if (x.UnderlyingCode != null && x.UnderlyingCode.length != 0 && x.IsInitial) return x; });
thisObj.paySwapList.forEach((val, num, arr) => {
arr[num].index = num;
arr[num].PosiFeeType = thisObj.normalizePosiFeeType(arr[num].PosiFeeType);
this.StockEqvNotional = val.ContractSize * val.PosiQuantity * val.PosiGrossPrice;
// 加载(重开/刷新)已保存的债券成交单:三字段互算状态标识全部清空,纯展示保存值、无任何 源/AUTO/REV 标记。
// 交互约定3规定"编辑未回车不联动",故用户载入后即使改某格(未回车)也不会反算另两格;
// 只有用户主动回车某格时,才以该格为源重新推导(符合交互约定1)。
var hasV = function (v) { return v !== null && v !== undefined && v !== '' && !isNaN(Number(v)); };
var isBond = val.isBond || tradeHelper.IsBond(val.UnderlyingInstrumentType);
if (isBond && hasV(val.PosiNetNoFeePrice) && hasV(val.PosiGrossPrice) && hasV(val.InitYtm)) {
SwapCalc.clearBondCalcMarksOnLoad(arr[num]);
thisObj.syncBondFlags(arr[num]);
}
});
if (thisObj.paySwapList.length > 0) {
thisObj.syncPosiFeeModeByItem(thisObj.paySwapList[0]);
}
}
if (page.isAdd && page.swapFloatingIncomeReceiveOnlyMode) {
thisObj.trade.trade_extend.ExtendObj.Direction = 1;
thisObj.paySwapList.forEach(x => x.PosiDirection = 1);
}
if (thisObj.paySwapList.length == 0) {
thisObj.trade.trade_extend.ExtendObj.Direction = thisObj.trade.trade_extend.ExtendObj.Direction == 0 ? 2 : thisObj.trade.trade_extend.ExtendObj.Direction;
var direction = thisObj.trade.trade_extend.ExtendObj.Direction;
thisObj.addSwapFloat(direction);
}
if (thisObj.getSwapList.length == 0) {
thisObj.addGetSwapRate();
}
},
// A renewal opens as a new trade, so the underlying autocomplete only restores
// its code and does not emit change. Historically fetched the initial price explicitly.
// 簿记基础逻辑1:续发场景也不自动填充价格,由用户手填或回车调计算器反算。
refreshInitialUnderlyingPrices() {
if (!page.isAdd) return;
// 不再自动调 getSpotPrice 回填价格;用户可手动回车调计算器或手填
},
//利息端添加行
addGetSwapRate() {
var InterestMode = 9;
var thisObj = this;
if (thisObj.trade.StructureType == '多空组合') {
InterestMode = 1;
}
var getSwap = {
index: thisObj.getSwapList.length,//利息序号,做删除以及观察日用
id: 0,
PosiDirection: 0,//浮动收支方式
PositionType: 0,//多空方向 1long2short
UnderlyingCode: "",//标的代码
UnderlyingInstrumentType: "",
CountRatio: 0,//乘积因子
ContractSize: 0,//合约乘数
PosiNetPrice: 0,//期初标的价格含费
PosiGrossPrice: 0,//期初标的价格不含费
PosiNetFeePrice: 0,//净价含费
PosiNetNoFeePrice: 0,//净价不含费
PosiQuantity: 0,//持仓数量
PosiTradingFeePending: 0,//交易费用后付
PosiTradingFee: 0,//交易费用
PosiTradingFeeUnit: 0,//单位交易费用
InterestDirection: 1,//利息收支方式
InterestRateDefault: 0,//计息利率
InterestMode: InterestMode,//计息基本类型
InterestPrincipalFix: 0,//计息基准
FloatRate: 0,// 浮动利率
FloatRateUnderlyingCode: "",//浮动利率标的
InterestType: 1,//计息方式 0 单利,1:复利
InterestSwapInterval: "",//观察间隔
SwapIntervalList: [],//观察间隔集合
observation: null,//观察信息,
IsAnnualized: true,//是否年化
HappenDate: null,//发生日期,
Currency: 'CNY',//币种
interest_rest_days: 7,//重置频率
interest_rule: null,//利率准则
category_tag: '互换利率'//类别
}
thisObj.getSwapList.push(getSwap);
},
//预付金添加行
addMarginSwapRate() {
var thisObj = this;
var getSwap = {
index: thisObj.marginSwapList.length,//利息序号,做删除以及观察日用
id: 0,
PosiDirection: 0,//浮动收支方式
PositionType: 0,//多空方向 1long2short
UnderlyingCode: "",//标的代码
UnderlyingInstrumentType:"",
CountRatio: 0,//乘积因子
ContractSize: 0,//合约乘数
PosiNetPrice: 0,//期初标的价格含费
PosiGrossPrice: 0,//期初标的价格不含费
PosiNetFeePrice: 0,//净价含费
PosiNetNoFeePrice: 0,//净价不含费
PosiQuantity: 0,//持仓数量
PosiTradingFeePending: 0,//交易费用后付
PosiTradingFee: 0,//交易费用
PosiTradingFeeUnit: 0,//单位交易费用
InterestDirection: 1,//利息收支方式
InterestRateDefault: 0,//计息利率
InterestMode: 5,//计息基本类型
InterestPrincipalFix: 0,//计息基准
FloatRate: 0,// 浮动利率
FloatRateUnderlyingCode: "",//浮动利率标的
InterestType: 0,//计息方式 0 单利,1:复利
InterestSwapInterval: "",//观察间隔
SwapIntervalList: [],//观察间隔集合
observation: null,//观察信息,
IsAnnualized: true,//是否年化,
HappenDate: thisObj.trade.TradeDate,//发生日期,
Currency: 'CNY',//币种
FundTag: '',//资金标签(R4):空=默认现金,可选授信,录入存选择、确认时系统定稿
interest_rest_days: 7,//重置频率
interest_rule: null//利率准则
}
thisObj.marginSwapList.push(getSwap);
},
//浮动端添加行
addSwapFloat(direction) {
var thisObj = this;
var petSwap = {
index: thisObj.paySwapList.length,//利息序号,做删除以及观察日用
id: 0,
PosiDirection: direction,//浮动收支方式
PositionType: 1,//多空方向 1long2short
UnderlyingCode: "",//标的代码
underlying: {
UnderlyingCode: '',
UnderlyingName: '',
UnderlyingIssuer: '',
IssueSize: '',
MaturityDate: '',
Price: '',
UnderlyingInstrumentType: '',
QuoteUnitString: ''
},//标的信息
UnderlyingInstrumentType: "",
CountRatio: 1,//乘积因子
ContractSize: 1,//合约乘数
PosiNetPrice: 0,//期初标的价格含费
PosiGrossPrice: 0,//期初标的价格不含费
PosiNetFeePrice: 0,//净价含费
PosiNetNoFeePrice: 0,//净价不含费
PosiQuantity: 0,//持仓数量
PosiTradingFee: 0,//交易费用
PosiTradingFeePending: 0,//交易费用后付
PosiTradingFeeUnit: 0,//单位交易费用
PosiFeeType: thisObj.getCurrentPosiFeeType(),//单位交易费用模式
InterestDirection: 0,//利息收支方式
InterestRateDefault: 0,//计息利率
InterestMode: 0,//计息基本类型
InterestPrincipalFix: 0,//计息基准
FloatRate: 0,// 浮动利率
FloatRateUnderlyingCode: "",//浮动利率标的
InterestType: 0,//计息方式 0 单利,1:复利
InterestSwapInterval: "",//观察间隔
SwapIntervalList: [],//观察间隔集合
observation: null,//观察信息
IsAnnualized: false,//是否年化
HappenDate: null,//发生日期,
Currency: 'CNY',//币种
interest_rest_days: 7,//重置频率
interest_rule: null//利率准则
}
thisObj.paySwapList.push(petSwap);
},
//利息端删除行
deleteSwapRate(index) {
var thisObj = this;
thisObj.getSwapList.splice(index, 1);
thisObj.getSwapList.forEach((val, num, arr) => {
arr[num].index = num;
});
},
//利息端删除行
deleteMarginSwapRate(index) {
var thisObj = this;
thisObj.marginSwapList.splice(index, 1);
thisObj.marginSwapList.forEach((val, num, arr) => {
arr[num].index = num;
});
},
//浮动端删除行
deleteSwapFloat(index) {
var thisObj = this;
thisObj.paySwapList.splice(index, 1);
thisObj.paySwapList.forEach((val, num, arr) => {
arr[num].index = num;
});
},
formatDate(time, formatStr) {
if (!formatStr) {
formatStr = "YYYY-MM-DD";
}
var momDate = new moment(time);
if (!momDate.isValid() || typeof time === "undefined") return "/";
return momDate.format(formatStr);
},
showUnderlyingInfo(item) {
var underlying = item.underlying;
var html = '<div class="row no-gutters">'
+ '<ul> '
+ '<li class="font16" ><span class="font_title">' + underlying.UnderlyingCode + '</span><span class="font_desc"> ' + underlying.UnderlyingName + ' </span>'
+ '<a href="javascript:void(0)" onclick="hideUnderlyingInfo(' + item.index + ')"><span class="glyphicon glyphicon-remove" style="color:#ff0000"></span></a></li>'
+ '<li class="font_desc"> 发行人:' + underlying.UnderlyingIssuer + '</li>'
+ '<li class="font_desc">面额:' + underlying.Price + '元</li>'
+ '<li class="font_desc">债券规模:' + (underlying.IssueSize ? underlying.IssueSize : '') + '亿</li>'
+ '<li class="font_desc">到期日:' + this.formatDate(underlying.MaturityDate, 'YYYY年MM月DD日') + '</li>'
+ '</ul>'
+ '</div>'
$(".un").popover('show');
$(".popover-body").html(html);
//$(".un").popover('show');
},
refreshDatepicker() {
var thisObj = this;
var startDate = thisObj.trade.StartDate;
var endDate = thisObj.trade.ExerciseDate;
if (this.$refs.happenDateRefs) {
for (let i = 0; i < this.$refs.happenDateRefs.length; i++) {
this.$refs.happenDateRefs[i].refresh(startDate, endDate);
}
}
this.$refs.observationStartRefs.refresh(startDate, endDate);
},
IsBond(instType) {
return tradeHelper.IsBond(instType);
}
},
computed: {
maxTradeDate() {
let exerciseDate = this.trade.ExerciseDate;
return exerciseDate && exerciseDate <= page.valuedate ? exerciseDate : page.valuedate;
},
},
components: {
'vue-datepicker': FastVue.vueDatePicker(),
'vue-number-input': FastVue.vueNumberInput(),
'vue-swap-price-input': swapPricePrecision.createVueInputComponent(),
'vue-underlying-nonbond': vueUnderlyingNonBond(),
'vue-underlying-bond': vueUnderlyingBond(),
'vue-underlying-rate': vueUnderlyingRate()
},
destroyed() {
}
});
function hideUnderlyingInfo(index) {
$(".un").popover('hide');
}
function __init(vue) {
//交易员
if (page.canAddNewTrader) {
autoTrader = FastVue.autocomplete(document.getElementById('TraderId'), {
nameField: 'Name', valueField: 'id', searchField: ['Name', 'PinYin'], lookup: consTraders,
onSelect(data) {
vue.trade.TraderId = data.id;
vue.trade.TraderName = data.Name;
}
});
let traderId = page.Trade.TraderId;
let trader = traderId ? consTraders.find(x => x.id === traderId) : null;
if (!trader && page.isAdd) {
trader = consTraders[0];
trader && (vue.trade.TraderId = trader.id);
}
autoTrader.setData(trader);
}
//簿记账户
let autoAssetUnit = FastVue.autocomplete(document.getElementById('AssetId'), {
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.isAdd) {
asset = consAssetUnits[0];
asset && (vue.trade.AssetId = asset.id);
}
autoAssetUnit.setData(asset);
vue.refreshMarginTemplateOptions();
//客户名称
if (page.canChangeClient) {
let autoClient = FastVue.autocomplete(document.getElementById('ClientId'), {
nameField: 'Name', valueField: 'id', searchField: ['Name', 'PinYin'],
lookup: consClients, onSelect: function (rep) {
vue.changeClient(rep);
_getMainProtocolCode(rep);
}
});
let clientId = page.Trade.ClientId;
let client = clientId ? consClients.find(x => x.id === clientId) : null;
!client && page.isAdd && (client = consClients[0]);
//setData 只写控件显示值、不触发 onSelect 回写 v-model——新增时默认客户须显式回写,否则 trade.ClientId 为空、保存报"请设置交易对手方"
client && page.isAdd && vue.changeClient(client);
autoClient.setData(client);
_getMainProtocolCode(client);
}
}
function _getMainProtocolCode(client) {
$("#MainProtocolCode option").remove();
if (client) {
let clientId = typeof client === 'object' ? client.id : client;
main.post("/Client/getMainProtocolCodes", { clientId: clientId }, { async: false }).done(function (resp) {
var obj = $("#MainProtocolCode");
if (client.MainProtocolCode != null && client.MainProtocolCode != '') {
obj.append("<option >" + client.MainProtocolCode + "</option>");
}
_.forEach(resp, (v) => {
obj.append("<option >" + v.Text + "</option>");
})
});
}
$("#SupProtocolCode option").remove();
if ($("#MainProtocolCode").val()) {
main.post("/Client/getSideProtocols", { mainProtocol: $("#MainProtocolCode").val() }, { async: false }).done(function (resp) {
var obj = $("#SupProtocolCode");
if (client.SupProtocolCode != null && client.SupProtocolCode != '') {
obj.append("<option >" + client.SupProtocolCode + "</option>");
}
_.forEach(resp, (v) => {
obj.append("<option >" + v + "</option>");
})
});
}
}
var getObservationDatesSetting = vue.getObservationDatesSetting;
$(function () {
$(".un").popover({
html: true,
title: function () {
return "发行情况";
},
content: function () {
return "";
},
placement: "right"
});
$(".datepicker").change(function () {
var dateVal = $(this).val();
if (!dateVal) return;
dateVal = dateVal.replace(/\D/g, '').padStart(4, '0');
switch (dateVal.length) {
case 4:
dateVal = new Date().getFullYear() + dateVal; break;
case 6:
dateVal = '20' + dateVal; break;
case 8: break;
default:
dateVal = dateVal.length > 8 ? dateVal.substring(0, 8) : ''; break;
}
if (dateVal) {
dateVal = moment(dateVal).format('YYYY-MM-DD');
!/^\d+/.test(dateVal) && (dateVal = '');
}
$(this).datepicker("setDate", dateVal);
});
});