From 05ce6fb02fd7e77d6369c9ad882efae4fe8ed64f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E5=86=B0=E5=86=B0?= <437394478@qq.com> Date: Wed, 22 Jul 2026 14:46:01 +0800 Subject: [PATCH 01/22] =?UTF-8?q?(feature):=E4=BF=9D=E8=AF=81=E9=87=91?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E5=AD=97=E6=AE=B5=E6=B7=BB=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- YLErpDAL/Modules/AppModule/AppUpgrader.cs | 3 + .../SwapMarginTemplateConfigService.cs | 80 +++++++++++++++++++ .../Admin/Controllers/OtcConfigController.cs | 8 +- YLErpWeb/Controllers/SwapTrade2Controller.cs | 5 +- YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml | 11 +++ YLErpWeb/Views/SwapTrade2/TradeView.cshtml | 4 + 6 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 YLErpDAL/Modules/SwapModule/SwapMarginTemplateConfigService.cs diff --git a/YLErpDAL/Modules/AppModule/AppUpgrader.cs b/YLErpDAL/Modules/AppModule/AppUpgrader.cs index 4a35b61f..edf14f79 100644 --- a/YLErpDAL/Modules/AppModule/AppUpgrader.cs +++ b/YLErpDAL/Modules/AppModule/AppUpgrader.cs @@ -309,6 +309,9 @@ namespace YLErp.Modules.AppModule //----------------------------------------------- // 删除不再使用的 //----------------------------------------------- + configService.AddDataIfNotExists("ProjectConfig", YLErp.Modules.SwapModule.SwapMarginTemplateConfigService.ConfigName, + YLErp.Modules.SwapModule.SwapMarginTemplateConfigService.DefaultConfigJson, "text", "互换保证金模板(JSON)"); + RemoveUnUsed(configService); } diff --git a/YLErpDAL/Modules/SwapModule/SwapMarginTemplateConfigService.cs b/YLErpDAL/Modules/SwapModule/SwapMarginTemplateConfigService.cs new file mode 100644 index 00000000..b873dbd1 --- /dev/null +++ b/YLErpDAL/Modules/SwapModule/SwapMarginTemplateConfigService.cs @@ -0,0 +1,80 @@ +using YLErp.Modules.AppModule; +using YLErp.Models; + +namespace YLErp.Modules.SwapModule +{ + public static class SwapMarginTemplateConfigService + { + public const string ConfigName = "Trade.SwapMarginTemplateConfig"; + + public const string DefaultConfigJson = @"{ + ""options"": [ + { ""text"": ""授信保证金"", ""value"": ""授信保证金"" }, + { ""text"": ""现金保证金"", ""value"": ""现金保证金"" } + ], + ""defaultValue"": ""现金保证金"" +}"; + + public static SwapMarginTemplateConfig GetConfig() + { + var json = AppManager.GetAppConfigValue("ProjectConfig", ConfigName); + return TryParse(json, out var config, out _) ? config : GetDefaultConfig(); + } + + public static bool TryValidate(string json, out string error) + { + return TryParse(json, out _, out error); + } + + private static SwapMarginTemplateConfig GetDefaultConfig() + { + return JsonHelper.Deserialize(DefaultConfigJson); + } + + private static bool TryParse(string json, out SwapMarginTemplateConfig config, out string error) + { + config = null; + error = null; + + try + { + config = JsonHelper.Deserialize(json); + } + catch (Exception) + { + error = "保证金模板配置不是有效的 JSON"; + return false; + } + + var items = config?.options? + .Where(item => item != null && !string.IsNullOrWhiteSpace(item.Value)) + .ToArray(); + if (items == null || items.Length == 0) + { + error = "保证金模板配置至少需要一个有效选项"; + return false; + } + if (items.Select(item => item.Value).Distinct(StringComparer.OrdinalIgnoreCase).Count() != items.Length) + { + error = "保证金模板配置的选项值不能重复"; + return false; + } + var defaultValue = config.defaultValue; + if (!items.Any(item => item.Value == defaultValue)) + { + error = "保证金模板默认值必须在选项中"; + return false; + } + + config.options = items; + return true; + } + } + + public class SwapMarginTemplateConfig + { + public IEnumerable options { get; set; } + + public string defaultValue { get; set; } + } +} diff --git a/YLErpWeb/Areas/Admin/Controllers/OtcConfigController.cs b/YLErpWeb/Areas/Admin/Controllers/OtcConfigController.cs index b186e765..cc4f6321 100644 --- a/YLErpWeb/Areas/Admin/Controllers/OtcConfigController.cs +++ b/YLErpWeb/Areas/Admin/Controllers/OtcConfigController.cs @@ -3,6 +3,7 @@ using Org.BouncyCastle.Asn1.Ocsp; using YLErp.Commons; using YLErp.Modules.AppModule; using YLErp.Modules.EodModule.SettlementModule; +using YLErp.Modules.SwapModule; using YLErp.Web.App_Start.AppConfig; using YLErp.Web.Areas.Admin.Models; @@ -53,6 +54,11 @@ namespace YLErp.Web.Areas.Admin.Controllers { value = Uri.UnescapeDataString(value); } + if (name == SwapMarginTemplateConfigService.ConfigName + && !SwapMarginTemplateConfigService.TryValidate(value, out var configError)) + { + return JsonError(configError); + } using (var db = DbContextFactory.GetYLDbContext()) { var data = db.AppConfig.FirstOrDefault(n => n.PGroup == "ProjectConfig" && n.PName == name); @@ -282,4 +288,4 @@ namespace YLErp.Web.Areas.Admin.Controllers #endregion } -} \ No newline at end of file +} diff --git a/YLErpWeb/Controllers/SwapTrade2Controller.cs b/YLErpWeb/Controllers/SwapTrade2Controller.cs index 717ad13f..76a82824 100644 --- a/YLErpWeb/Controllers/SwapTrade2Controller.cs +++ b/YLErpWeb/Controllers/SwapTrade2Controller.cs @@ -52,6 +52,7 @@ namespace YLErp.Web.Controllers trade r = null; if (intid == 0) { + var defaultMarginTemplateName = SwapMarginTemplateConfigService.GetConfig().defaultValue; TradeExtendJson tradeExtendJson = new TradeExtendJson() { FlowBookMode = (int)FlowBookModeEnum.否, @@ -71,7 +72,7 @@ namespace YLErp.Web.Controllers TradeDate = QdpCalendarHelper.GetNonHolidayDefore(valuedateBLL.ValueDate.AddDays(-1), tradeDateCountry), TraderId = CurUser.UserId, TraderName = CurUser.UserName, - MarginTemplateName = "系统默认", + MarginTemplateName = defaultMarginTemplateName, OpponentRole = "乙方", OriginalStockEqvNotional = 0, StructureType = "普通收益互换", @@ -1111,4 +1112,4 @@ namespace YLErp.Web.Controllers } -} \ No newline at end of file +} diff --git a/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml b/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml index b8c7d03f..50d78b66 100644 --- a/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml +++ b/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml @@ -1,5 +1,6 @@ @using YLErp.Web.Models.JsModels; @using YLErp.Commons; +@using YLErp.Modules.SwapModule; @model trade @{ ViewBag.Title = "交易信息 | 编辑"; @@ -13,6 +14,7 @@ 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; var tradeMarginTemplateItems = new tradeController().GetMarginTemplateItems(); var jsAssetUnit = Model.AssetId > 0 ? jsAssetUnits.FirstOrDefault(n => n.id == Model.AssetId) : null; var assetunits = JsDataModel.GetAssetUnits(CurUser); @@ -66,6 +68,7 @@ jsTrader, jsTraders, tradeMarginTemplates = tradeMarginTemplates, + swapMarginTemplateItems = swapMarginTemplateItems, tradeMarginTemplateItems = tradeMarginTemplateItems, needRemark = !isAdd && valuedateBLL.SystemDate.EditTradeNeedRemark, parentTradeId = ViewBag.ParentTradeId, @@ -266,6 +269,14 @@ +
+ + +
diff --git a/YLErpWeb/Views/SwapTrade2/TradeView.cshtml b/YLErpWeb/Views/SwapTrade2/TradeView.cshtml index 310b7897..5b2e1e92 100644 --- a/YLErpWeb/Views/SwapTrade2/TradeView.cshtml +++ b/YLErpWeb/Views/SwapTrade2/TradeView.cshtml @@ -236,6 +236,10 @@ 派息金额支付日 @(trade.trade_extend.ExtendObj.DividendPayDate == 0 ? "到期结算日" : "派息日+" + (trade.trade_extend.ExtendObj.DividendPayDate - 1)) + + 保证金模板 + @trade.MarginTemplateName +
From 4419a4125bce13e31fffaed24975893f97177bd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E5=86=B0=E5=86=B0?= <437394478@qq.com> Date: Wed, 22 Jul 2026 16:27:36 +0800 Subject: [PATCH 02/22] =?UTF-8?q?(fix):=20=E5=B0=86=E5=8E=9F=E4=BF=9D?= =?UTF-8?q?=E8=AF=81=E9=87=91=E6=A8=A1=E6=9D=BF=E5=90=8D=E7=A7=B0=3D?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E9=BB=98=E8=AE=A4=20=E7=9A=84=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=80=BC=E6=94=B9=E4=B8=BAnull=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EodModule/SettlementModule/EodHandleSwapFlowService.cs | 4 ++-- YLErpDAL/Modules/SwapModule/SwapTradeService.cs | 2 +- .../TradeModule/SwapModule/SwapTradeFlowImportService.cs | 2 +- .../SwapModule/SwapTradeFlowMoreImportService.cs | 2 +- .../TradeModule/SwapModule/SwapTradeImportService.cs | 4 ++-- YLErpWeb/Controllers/SwapTrade2Controller.cs | 6 ++++++ YLErpWeb/Controllers/SwapTradeController.cs | 4 ++-- 7 files changed, 15 insertions(+), 9 deletions(-) diff --git a/YLErpDAL/Modules/EodModule/SettlementModule/EodHandleSwapFlowService.cs b/YLErpDAL/Modules/EodModule/SettlementModule/EodHandleSwapFlowService.cs index 4fbe9235..44683a41 100644 --- a/YLErpDAL/Modules/EodModule/SettlementModule/EodHandleSwapFlowService.cs +++ b/YLErpDAL/Modules/EodModule/SettlementModule/EodHandleSwapFlowService.cs @@ -455,7 +455,7 @@ namespace YLErp.Modules.EodModule Lots = swapFlow.Lots, IsNight = swapFlow.IsNight, OpponentRole = "甲方", - MarginTemplateName = "系统默认", + MarginTemplateName = null, MarginType = MarginTypeEnum.DEFAULT, IsGroup = isSingleTrade ? 0 : 2 }; @@ -743,7 +743,7 @@ namespace YLErp.Modules.EodModule td.OriginalNotional = td.Notional; td.OriginalStockEqvNotional = td.StockEqvNotional; td.StockEqvNotionalReal = td.StockEqvNotionalReal; - td.MarginTemplateName = "系统默认"; + td.MarginTemplateName = null; td.MarginType = MarginTypeEnum.DEFAULT; td.IsTradePricePayType = true; td.TradeSource = TradeSourceEnum.导入交易.ToString(); diff --git a/YLErpDAL/Modules/SwapModule/SwapTradeService.cs b/YLErpDAL/Modules/SwapModule/SwapTradeService.cs index f95b4718..d5c04664 100644 --- a/YLErpDAL/Modules/SwapModule/SwapTradeService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapTradeService.cs @@ -321,7 +321,7 @@ namespace YLErp.Modules.SwapModule TradeDate = flowMerge.OccurTime, TraderId = asset.TraderIdsInt.FirstOrDefault(), TraderName = asset.TraderNamesList.FirstOrDefault(), - MarginTemplateName = "系统默认", + MarginTemplateName = null, OpponentRole = "乙方", StructureType = structureType, InitialMargin = 0, diff --git a/YLErpDAL/Modules/TradeModule/SwapModule/SwapTradeFlowImportService.cs b/YLErpDAL/Modules/TradeModule/SwapModule/SwapTradeFlowImportService.cs index efac3554..fb8d835d 100644 --- a/YLErpDAL/Modules/TradeModule/SwapModule/SwapTradeFlowImportService.cs +++ b/YLErpDAL/Modules/TradeModule/SwapModule/SwapTradeFlowImportService.cs @@ -378,7 +378,7 @@ namespace YLErp.Modules.TradeModule.SwapModule importTrade.StockEqvNotionalReal = importTrade.StockEqvNotionalReal; importTrade.trade_swap.RateCalcMode = "01"; importTrade.IsUsePremiumRate = true; - importTrade.MarginTemplateName = "系统默认"; + importTrade.MarginTemplateName = null; importTrade.MarginType = MarginTypeEnum.DEFAULT; importTrade.IsTradePricePayType = true; importTrade.TradeSource = TradeSourceEnum.导入交易.ToString(); diff --git a/YLErpDAL/Modules/TradeModule/SwapModule/SwapTradeFlowMoreImportService.cs b/YLErpDAL/Modules/TradeModule/SwapModule/SwapTradeFlowMoreImportService.cs index 64c89cae..5e0f2ee2 100644 --- a/YLErpDAL/Modules/TradeModule/SwapModule/SwapTradeFlowMoreImportService.cs +++ b/YLErpDAL/Modules/TradeModule/SwapModule/SwapTradeFlowMoreImportService.cs @@ -519,7 +519,7 @@ namespace YLErp.Modules.TradeModule.SwapModule importTrade.ParticipationRate = 1; //预付金 - importTrade.MarginTemplateName = "系统默认"; + importTrade.MarginTemplateName = null; importTrade.MarginType = MarginTypeEnum.DEFAULT; SetDBModelCreator(importTrade); diff --git a/YLErpDAL/Modules/TradeModule/SwapModule/SwapTradeImportService.cs b/YLErpDAL/Modules/TradeModule/SwapModule/SwapTradeImportService.cs index 4ae8a21b..6f985cbf 100644 --- a/YLErpDAL/Modules/TradeModule/SwapModule/SwapTradeImportService.cs +++ b/YLErpDAL/Modules/TradeModule/SwapModule/SwapTradeImportService.cs @@ -726,7 +726,7 @@ namespace YLErp.Modules.TradeModule.SwapModule td.OriginalStockEqvNotional = td.StockEqvNotional; td.StockEqvNotionalReal = td.StockEqvNotionalReal; td.IsUsePremiumRate = true; - td.MarginTemplateName = "系统默认"; + td.MarginTemplateName = null; td.MarginType = MarginTypeEnum.DEFAULT; td.IsTradePricePayType = true; td.TradeSource = TradeSourceEnum.导入交易.ToString(); @@ -2815,7 +2815,7 @@ namespace YLErp.Modules.TradeModule.SwapModule td.SettlementDate = td.ExerciseDate; td.StockEqvNotional = reader.GetDouble("名义本金(人民币)", true) ?? 0; - td.MarginTemplateName = "系统默认"; + td.MarginTemplateName = null; td.UnWindDate = reader.GetDate("提前终止日/终止日", true); td.Comments = reader.GetString("备注", false); //td.UnderlyingName = reader.GetString("标的名称", true); diff --git a/YLErpWeb/Controllers/SwapTrade2Controller.cs b/YLErpWeb/Controllers/SwapTrade2Controller.cs index 76a82824..6f38abe5 100644 --- a/YLErpWeb/Controllers/SwapTrade2Controller.cs +++ b/YLErpWeb/Controllers/SwapTrade2Controller.cs @@ -116,6 +116,12 @@ namespace YLErp.Web.Controllers { return ShowError("没有找到交易数据"); } + var marginTemplateConfig = SwapMarginTemplateConfigService.GetConfig(); + if (string.IsNullOrWhiteSpace(tradeObj.MarginTemplateName) + || !marginTemplateConfig.options.Any(item => item.Value == tradeObj.MarginTemplateName)) + { + tradeObj.MarginTemplateName = marginTemplateConfig.defaultValue; + } TradeViewModel model; model = new TradeViewModel(tradeObj) diff --git a/YLErpWeb/Controllers/SwapTradeController.cs b/YLErpWeb/Controllers/SwapTradeController.cs index 8746b33b..ad82b7c7 100644 --- a/YLErpWeb/Controllers/SwapTradeController.cs +++ b/YLErpWeb/Controllers/SwapTradeController.cs @@ -303,7 +303,7 @@ namespace YLErp.Web.Controllers TradeDate = valuedateBLL.ValueDate, TraderId = CurUser.UserId, TraderName = CurUser.UserName, - MarginTemplateName = "系统默认", + MarginTemplateName = null, OpponentRole = "乙方", trade_swap = new trade_swap() { @@ -1196,4 +1196,4 @@ namespace YLErp.Web.Controllers return JsonSuccess("", 0); } } -} \ No newline at end of file +} From 4f9a9e2d72747c841a10758e4dfdc71f45fb65b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E5=86=B0=E5=86=B0?= <437394478@qq.com> Date: Wed, 22 Jul 2026 16:51:40 +0800 Subject: [PATCH 03/22] =?UTF-8?q?fix:=20=E6=9B=B4=E6=94=B9=E4=BF=9D?= =?UTF-8?q?=E8=AF=81=E9=87=91=E6=A8=A1=E6=9D=BF=E5=90=8D=E7=A7=B0=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E7=9A=84=E5=8F=96=E5=80=BC=20=E7=94=B1admin->=20?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=AD=97=E5=85=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Dictionary/db_dictionaries.xml | 1 + YLErpDAL/Modules/AppModule/AppUpgrader.cs | 40 ++++++++-- .../SwapMarginTemplateConfigService.cs | 75 +++++-------------- .../Admin/Controllers/OtcConfigController.cs | 6 -- 4 files changed, 52 insertions(+), 70 deletions(-) diff --git a/Framework/YLErp.Resources/Dictionary/db_dictionaries.xml b/Framework/YLErp.Resources/Dictionary/db_dictionaries.xml index a0f9091e..97770296 100644 --- a/Framework/YLErp.Resources/Dictionary/db_dictionaries.xml +++ b/Framework/YLErp.Resources/Dictionary/db_dictionaries.xml @@ -30,6 +30,7 @@ + diff --git a/YLErpDAL/Modules/AppModule/AppUpgrader.cs b/YLErpDAL/Modules/AppModule/AppUpgrader.cs index edf14f79..ead11cb0 100644 --- a/YLErpDAL/Modules/AppModule/AppUpgrader.cs +++ b/YLErpDAL/Modules/AppModule/AppUpgrader.cs @@ -306,17 +306,13 @@ namespace YLErp.Modules.AppModule //----------------------------------------------- configService.AddDataIfNotExists("ProjectConfig", "Erp.IsAutoSealAfterGeneratedBook", "false", "bool", "确认书生成时是否自动用印(IsAutoSealAndUploadFiles勾选时生效)"); configService.AddDataIfNotExists("ProjectConfig", "Erp.ReportFileBeginNumber", "0", "int", "报送文件开始编号"); - //----------------------------------------------- - // 删除不再使用的 - //----------------------------------------------- - configService.AddDataIfNotExists("ProjectConfig", YLErp.Modules.SwapModule.SwapMarginTemplateConfigService.ConfigName, - YLErp.Modules.SwapModule.SwapMarginTemplateConfigService.DefaultConfigJson, "text", "互换保证金模板(JSON)"); - RemoveUnUsed(configService); } private static void RemoveUnUsed(InnerAppConfigService configService) { + configService.RemoveData("ProjectConfig", "Trade.SwapMarginTemplateConfig"); + if (AppManager.Version.Major < 3) { configService.RemoveData("ProjectConfig", "Erp.TradeConfirmBookEmailTPL"); @@ -456,6 +452,38 @@ namespace YLErp.Modules.AppModule } adminDb.SaveChanges(); + + var marginTemplateDictionary = adminDb.Dictionaries.FirstOrDefault(item => item.Name == YLErp.Modules.SwapModule.SwapMarginTemplateConfigService.DictionaryName); + if (marginTemplateDictionary == null) + { + return; + } + + var existingNames = adminDb.DictionaryItems + .Where(item => item.DictId == marginTemplateDictionary.Id) + .Select(item => item.Name) + .ToHashSet(); + var nextIndex = adminDb.DictionaryItems + .Where(item => item.DictId == marginTemplateDictionary.Id) + .Select(item => item.IndexNum) + .DefaultIfEmpty(-1) + .Max(); + foreach (var templateName in YLErp.Modules.SwapModule.SwapMarginTemplateConfigService.InitialTemplateNames) + { + if (existingNames.Contains(templateName)) + { + continue; + } + + adminDb.DictionaryItems.Add(new BaseOUDAL.DictionaryItem + { + DictId = marginTemplateDictionary.Id, + Name = templateName, + ShortName = templateName, + IndexNum = ++nextIndex + }); + } + adminDb.SaveChanges(); } catch (Exception ex) { diff --git a/YLErpDAL/Modules/SwapModule/SwapMarginTemplateConfigService.cs b/YLErpDAL/Modules/SwapModule/SwapMarginTemplateConfigService.cs index b873dbd1..667670dd 100644 --- a/YLErpDAL/Modules/SwapModule/SwapMarginTemplateConfigService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapMarginTemplateConfigService.cs @@ -1,73 +1,32 @@ -using YLErp.Modules.AppModule; +using BaseOUDAL; using YLErp.Models; namespace YLErp.Modules.SwapModule { public static class SwapMarginTemplateConfigService { - public const string ConfigName = "Trade.SwapMarginTemplateConfig"; + public const string DictionaryName = "保证金模板名称"; - public const string DefaultConfigJson = @"{ - ""options"": [ - { ""text"": ""授信保证金"", ""value"": ""授信保证金"" }, - { ""text"": ""现金保证金"", ""value"": ""现金保证金"" } - ], - ""defaultValue"": ""现金保证金"" -}"; + public static readonly string[] InitialTemplateNames = { "现金保证金", "授信保证金" }; public static SwapMarginTemplateConfig GetConfig() { - var json = AppManager.GetAppConfigValue("ProjectConfig", ConfigName); - return TryParse(json, out var config, out _) ? config : GetDefaultConfig(); - } - - public static bool TryValidate(string json, out string error) - { - return TryParse(json, out _, out error); - } - - private static SwapMarginTemplateConfig GetDefaultConfig() - { - return JsonHelper.Deserialize(DefaultConfigJson); - } - - private static bool TryParse(string json, out SwapMarginTemplateConfig config, out string error) - { - config = null; - error = null; - - try - { - config = JsonHelper.Deserialize(json); - } - catch (Exception) - { - error = "保证金模板配置不是有效的 JSON"; - return false; - } - - var items = config?.options? - .Where(item => item != null && !string.IsNullOrWhiteSpace(item.Value)) + using var db = new ErpBaseContext(); + var dictionaryId = db.Dictionaries + .Where(item => item.Name == DictionaryName) + .Select(item => item.Id) + .FirstOrDefault(); + var items = db.DictionaryItems + .Where(item => item.DictId == dictionaryId && !string.IsNullOrWhiteSpace(item.Name)) + .OrderBy(item => item.IndexNum) + .Select(item => new SelectItem { Text = item.Name, Value = item.Name }) .ToArray(); - if (items == null || items.Length == 0) - { - error = "保证金模板配置至少需要一个有效选项"; - return false; - } - if (items.Select(item => item.Value).Distinct(StringComparer.OrdinalIgnoreCase).Count() != items.Length) - { - error = "保证金模板配置的选项值不能重复"; - return false; - } - var defaultValue = config.defaultValue; - if (!items.Any(item => item.Value == defaultValue)) - { - error = "保证金模板默认值必须在选项中"; - return false; - } - config.options = items; - return true; + return new SwapMarginTemplateConfig + { + options = items, + defaultValue = items.FirstOrDefault()?.Value + }; } } diff --git a/YLErpWeb/Areas/Admin/Controllers/OtcConfigController.cs b/YLErpWeb/Areas/Admin/Controllers/OtcConfigController.cs index cc4f6321..dd95f9a2 100644 --- a/YLErpWeb/Areas/Admin/Controllers/OtcConfigController.cs +++ b/YLErpWeb/Areas/Admin/Controllers/OtcConfigController.cs @@ -3,7 +3,6 @@ using Org.BouncyCastle.Asn1.Ocsp; using YLErp.Commons; using YLErp.Modules.AppModule; using YLErp.Modules.EodModule.SettlementModule; -using YLErp.Modules.SwapModule; using YLErp.Web.App_Start.AppConfig; using YLErp.Web.Areas.Admin.Models; @@ -54,11 +53,6 @@ namespace YLErp.Web.Areas.Admin.Controllers { value = Uri.UnescapeDataString(value); } - if (name == SwapMarginTemplateConfigService.ConfigName - && !SwapMarginTemplateConfigService.TryValidate(value, out var configError)) - { - return JsonError(configError); - } using (var db = DbContextFactory.GetYLDbContext()) { var data = db.AppConfig.FirstOrDefault(n => n.PGroup == "ProjectConfig" && n.PName == name); From ef7fa7c6e5a8987db483d7dcd0caf4b4ede7c6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E5=86=B0=E5=86=B0?= <437394478@qq.com> Date: Thu, 23 Jul 2026 14:37:34 +0800 Subject: [PATCH 04/22] =?UTF-8?q?Changes=E7=BB=AD=E4=BD=9C=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E7=9A=84=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- YLErpWeb/Controllers/SwapTrade2Controller.cs | 137 +++++++++++++----- YLErpWeb/Views/SwapTrade2/header.cshtml | 1 + .../Scripts/app/swaptrade/swapTradeView.js | 6 +- 3 files changed, 106 insertions(+), 38 deletions(-) diff --git a/YLErpWeb/Controllers/SwapTrade2Controller.cs b/YLErpWeb/Controllers/SwapTrade2Controller.cs index 6f38abe5..09cd2df1 100644 --- a/YLErpWeb/Controllers/SwapTrade2Controller.cs +++ b/YLErpWeb/Controllers/SwapTrade2Controller.cs @@ -45,49 +45,26 @@ namespace YLErp.Web.Controllers ViewBag.observationDate = observationDate; return View(); } - public ActionResult TradeEdit(string enid, bool isUseApproval = false) + public ActionResult TradeEdit(string enid, string renewEnid = null, bool isUseApproval = false) { ViewBag.isUseApproval = isUseApproval; - var intid = DecryptInt(enid); + // The new/renew flow uses the literal "0" to indicate that no trade exists yet. + var intid = enid == "0" ? 0 : DecryptInt(enid); trade r = null; if (intid == 0) { - var defaultMarginTemplateName = SwapMarginTemplateConfigService.GetConfig().defaultValue; - TradeExtendJson tradeExtendJson = new TradeExtendJson() + r = CreateNewTrade(); + var renewTradeId = DecryptInt(renewEnid); + if (renewTradeId > 0) { - FlowBookMode = (int)FlowBookModeEnum.否, - FloatingPnlAnnualized = false, - NeedOpenFee = false, - OpenFeeType = 0, - InterestCalcMode = "10", - SettlementRules=0, - DividendPayDate=0 - }; - var tradeDateCountry = GetBestCountry(valuedateBLL.ValueDate.Year); - r = new trade() - { - TradeType = "收益互换", - UnderlyingInstrumentType = "Stock", - StartDate = valuedateBLL.ValueDate, - TradeDate = QdpCalendarHelper.GetNonHolidayDefore(valuedateBLL.ValueDate.AddDays(-1), tradeDateCountry), - TraderId = CurUser.UserId, - TraderName = CurUser.UserName, - MarginTemplateName = defaultMarginTemplateName, - OpponentRole = "乙方", - OriginalStockEqvNotional = 0, - StructureType = "普通收益互换", - InitialMargin = 0 - }; - r.StructureType = "普通债券类收益互换"; - tradeExtendJson.FlowBookMode = (int)FlowBookModeEnum.先进先出; - r.trade_extend = new trade_extend() - { - ExtendJson = JsonHelper.Serialize(tradeExtendJson) - }; - r.MetaDic = new Dictionary - { - { "清算机构", "甲方" } - }; + var sourceTrade = new SwapTradeService(CurUser).GetSwapTrade(renewTradeId); + if (sourceTrade == null) + { + return ShowError("没有找到交易数据"); + } + + r = CreateRenewTrade(sourceTrade, r); + } return View(r); } SwapTradeService swapTradeService = new SwapTradeService(CurUser); @@ -99,6 +76,92 @@ namespace YLErp.Web.Controllers return View(r); } + + private trade CreateNewTrade() + { + var defaultMarginTemplateName = SwapMarginTemplateConfigService.GetConfig().defaultValue; + var tradeExtendJson = new TradeExtendJson() + { + FlowBookMode = (int)FlowBookModeEnum.先进先出, + FloatingPnlAnnualized = false, + NeedOpenFee = false, + OpenFeeType = 0, + InterestCalcMode = "10", + SettlementRules = 0, + DividendPayDate = 0 + }; + var tradeDateCountry = GetBestCountry(valuedateBLL.ValueDate.Year); + return new trade() + { + TradeType = "收益互换", + UnderlyingInstrumentType = "Stock", + StartDate = valuedateBLL.ValueDate, + TradeDate = QdpCalendarHelper.GetNonHolidayDefore(valuedateBLL.ValueDate.AddDays(-1), tradeDateCountry), + TraderId = CurUser.UserId, + TraderName = CurUser.UserName, + MarginTemplateName = defaultMarginTemplateName, + OpponentRole = "乙方", + OriginalStockEqvNotional = 0, + StructureType = "普通债券类收益互换", + InitialMargin = 0, + trade_extend = new trade_extend() + { + ExtendJson = JsonHelper.Serialize(tradeExtendJson) + }, + MetaDic = new Dictionary + { + { "清算机构", "甲方" } + } + }; + } + + private trade CreateRenewTrade(trade sourceTrade, trade defaultTrade) + { + var renewTrade = sourceTrade.Clone(); + renewTrade.id = 0; + renewTrade.TradeNumber = string.Empty; + renewTrade.ParentTradeId = 0; + renewTrade.TradeDate = defaultTrade.TradeDate; + renewTrade.StartDate = defaultTrade.StartDate; + renewTrade.ExerciseDate = null; + renewTrade.MaturityDate = null; + renewTrade.SettlementDate = null; + renewTrade.UnWindDate = null; + renewTrade.PremiumPayDate = null; + renewTrade.SettlementFlagDate = null; + renewTrade.HasPartialUnWind = null; + renewTrade.TradeStatus = null; + renewTrade.CheckStatus = null; + renewTrade.ProcessStatus = null; + renewTrade.ProcessOrderId = 0; + renewTrade.ProcessOrderBranch = 0; + renewTrade.ProcessOptDate = null; + renewTrade.ValidState = null; + renewTrade.CreateDate = null; + renewTrade.TradeSource = null; + renewTrade.trade_extend = sourceTrade.trade_extend?.Clone() ?? defaultTrade.trade_extend; + renewTrade.trade_extend.TradeId = 0; + renewTrade.trade_Initial_Margin = sourceTrade.trade_Initial_Margin?.Clone() ?? new trade_initial_margin(); + renewTrade.trade_Initial_Margin.TradeId = 0; + renewTrade.MetaDic = sourceTrade.MetaDic == null + ? new Dictionary() + : new Dictionary(sourceTrade.MetaDic); + renewTrade.swap_positions = sourceTrade.swap_positions.Select(position => + { + var renewPosition = position.Clone(); + renewPosition.id = 0; + renewPosition.PositionId = 0; + renewPosition.SwapTradeId = 0; + renewPosition.PosiNumber = null; + renewPosition.PosiStartDate = defaultTrade.StartDate.Value; + renewPosition.PosiMatuirityDate = null; + renewPosition.HappenDate = null; + renewPosition.InterestSwapInterval = null; + renewPosition.Obervation = null; + return renewPosition; + }).ToList(); + return renewTrade; + } /// /// 详情 /// diff --git a/YLErpWeb/Views/SwapTrade2/header.cshtml b/YLErpWeb/Views/SwapTrade2/header.cshtml index 1e9a93e1..d6706aff 100644 --- a/YLErpWeb/Views/SwapTrade2/header.cshtml +++ b/YLErpWeb/Views/SwapTrade2/header.cshtml @@ -157,6 +157,7 @@ { @MyControls.Btn("收益结算", string.Format("unWindLongShortSwap('{0}')", tradeModel.EncryptId)) } + @MyControls.Btn("续做", string.Format("renewTrade('{0}')", tradeModel.EncryptId)) } diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeView.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeView.js index d0759efc..585a37cb 100644 --- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeView.js +++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeView.js @@ -26,6 +26,10 @@ function editTrade(enid) { window.location.href = `/swapTrade2/tradeEdit/?enid=${enid}`; } +function renewTrade(enid) { + window.location.href = `/swapTrade2/tradeEdit/?enid=0&renewEnid=${encodeURIComponent(enid)}`; +} + function editTradeRemarkInfo(enid) { main.open("修改备注", "/trade/EditRemarkInfo?enid=" + enid, { area: ["700px", "500px"] }); } @@ -586,4 +590,4 @@ function SubmissionFields(enid) { function SubmissionFieldsHistory(enid) { main.open("报送相关字段填写", "/trade/submissionFieldsHistory?encryptId=" + enid); -} \ No newline at end of file +} From ef179fc26cda201b5ea8a7feeebbe14baf94846c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E5=86=B0=E5=86=B0?= <437394478@qq.com> Date: Thu, 23 Jul 2026 16:59:58 +0800 Subject: [PATCH 05/22] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=88=A9=E6=81=AF?= =?UTF-8?q?=E7=AB=AF=E7=B1=BB=E5=9E=8B=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Framework/YLErp.Core/DBModels/SwapPosition.cs | 4 ++++ YLErpDAL/Modules/SwapModule/SwapTradeService.cs | 1 + YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml | 10 ++++++++++ YLErpWeb/Views/SwapTrade2/TradeView.cshtml | 4 ++++ .../wwwroot/Scripts/app/swaptrade/swapTradeEdit.js | 4 +++- 5 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Framework/YLErp.Core/DBModels/SwapPosition.cs b/Framework/YLErp.Core/DBModels/SwapPosition.cs index bcc18c36..44fb89d9 100644 --- a/Framework/YLErp.Core/DBModels/SwapPosition.cs +++ b/Framework/YLErp.Core/DBModels/SwapPosition.cs @@ -246,6 +246,10 @@ namespace YLErp.DBModels /// public int? interest_rule { get; set; } /// + /// 利息端类别 + /// + public string category_tag { get; set; } + /// /// 互换观察日集合 /// [NotMapped] diff --git a/YLErpDAL/Modules/SwapModule/SwapTradeService.cs b/YLErpDAL/Modules/SwapModule/SwapTradeService.cs index d5c04664..2f8caedf 100644 --- a/YLErpDAL/Modules/SwapModule/SwapTradeService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapTradeService.cs @@ -1377,6 +1377,7 @@ namespace YLErp.Modules.SwapModule position.FloatRateUnderlyingCode = swap.FloatRateUnderlyingCode; position.interest_rest_days = swap.interest_rest_days; position.interest_rule = swap.interest_rule; + position.category_tag = string.IsNullOrEmpty(swap.category_tag) ? "互换利率" : swap.category_tag; position.InitYtm = swap.InitYtm; if (swap.InitYtm != null && swap.InitYtm > 0) { diff --git a/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml b/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml index 50d78b66..9776cec2 100644 --- a/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml +++ b/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml @@ -21,6 +21,7 @@ var jsTraders = canAddNewTrader ? JsDataModel.GetTraders(assetunits) : Enumerable.Empty(); var jsTrader = canAddNewTrader && Model.TraderId > 0 ? jsTraders.FirstOrDefault(n => n.id == Model.TraderId) : null; var currencys = CurrencyController.getList(); + var categoryTagOptions = DictionaryBLL.GetList("利息端类别", false, "互换利率"); List places = new List(); List agencys = new List(); var tradingPlaceMap = YLErp.DBModels.Consts.ConsReport.TradingPlaceMapDisplay; @@ -362,6 +363,7 @@ 计息方式 重置频率(天) 利率准则 + 类别 结算规则 @@ -412,6 +414,14 @@ + + + diff --git a/YLErpWeb/Views/SwapTrade2/TradeView.cshtml b/YLErpWeb/Views/SwapTrade2/TradeView.cshtml index 5b2e1e92..9172e401 100644 --- a/YLErpWeb/Views/SwapTrade2/TradeView.cshtml +++ b/YLErpWeb/Views/SwapTrade2/TradeView.cshtml @@ -304,6 +304,7 @@ 计息方式 重置频率(天) 利率准则 + 类别 结算规则 @if (trade.swap_positions != null) @@ -348,6 +349,7 @@ @item.interest_rest_days @((item.interest_rule != null) ? (SwapInterestRule)item.interest_rule : "") + @(string.IsNullOrEmpty(item.category_tag) ? "互换利率" : item.category_tag) @@ -508,6 +510,7 @@ 计息方式 重置频率(天) 利率准则 + 类别 结算规则 @{ @@ -549,6 +552,7 @@ @(item.InterestType == 0 ? "单利" : "复利") @item.interest_rest_days @((item.interest_rule != null) ? (SwapInterestRule)item.interest_rule : "") + @(string.IsNullOrEmpty(item.category_tag) ? "互换利率" : item.category_tag) } diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js index dc726ce2..eed5a34f 100644 --- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js +++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js @@ -1289,6 +1289,7 @@ const vue = new Vue({ 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 == 7 || x.InterestMode == 8 || 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 { @@ -1368,7 +1369,8 @@ const vue = new Vue({ HappenDate: null,//发生日期, Currency: 'CNY',//币种 interest_rest_days: 7,//重置频率 - interest_rule: null//利率准则 + interest_rule: null,//利率准则 + category_tag: '互换利率'//类别 } thisObj.getSwapList.push(getSwap); }, From 44ea57b8f6fb4670eecb89c7d5479d36eb35624f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E5=86=B0=E5=86=B0?= <437394478@qq.com> Date: Fri, 24 Jul 2026 11:12:00 +0800 Subject: [PATCH 06/22] =?UTF-8?q?feature:=20=E5=A2=9E=E5=BC=BA=E6=94=B6?= =?UTF-8?q?=E7=9B=8Atag=20=E5=B0=86=E5=88=A9=E7=8E=87=E5=A1=AB=E5=85=A5?= =?UTF-8?q?=E5=9B=BA=E5=AE=9A=E6=94=B6=E7=9B=8A=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...模板-【客户看多】-【债券ETF】-清洁版.docx | Bin 46222 -> 43878 bytes ...模板-【客户看空】-【债券ETF】-清洁版.docx | Bin 47705 -> 45298 bytes .../TradeConfirmationGenerator.cs | 35 ++++++++++++++---- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/Plugins/YLErp.Plugins.GuoLian/App_Docs/contract_template/国联民生-收益互换交易确认书-境内模板-【客户看多】-【债券ETF】-清洁版.docx b/Plugins/YLErp.Plugins.GuoLian/App_Docs/contract_template/国联民生-收益互换交易确认书-境内模板-【客户看多】-【债券ETF】-清洁版.docx index 5347c9742b1874cd6ba12b703d360f16d4f1b7b9..a98c760bfbfb809889455547b4d84024cb6fb531 100644 GIT binary patch literal 43878 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�R^ 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 literal 46222 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∨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≦7bU|5GJ^7OG#+UjF;iSS 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 diff --git a/Plugins/YLErp.Plugins.GuoLian/DocumentGenerator/TradeConfirmationGenerator.cs b/Plugins/YLErp.Plugins.GuoLian/DocumentGenerator/TradeConfirmationGenerator.cs index 1aeed0a5..a926b273 100644 --- a/Plugins/YLErp.Plugins.GuoLian/DocumentGenerator/TradeConfirmationGenerator.cs +++ b/Plugins/YLErp.Plugins.GuoLian/DocumentGenerator/TradeConfirmationGenerator.cs @@ -241,11 +241,21 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator dic["参考标的期初全价%"] = ((double)swapPosition.PosiGrossPrice * 100).ToString("N4"); dic["参考标的期初净价%"] = ((double)(swapPosition.PosiNetNoFeePrice ?? 0m) * 100).ToString("N4"); - // 固定收益率(年化)- 债券期初到期收益率 - //dic["固定收益率(年化)"] = swapPosition.InitYtm.HasValue - // ? ((double)swapPosition.InitYtm.Value * 100).ToString("N4") - // : "0.0000"; - dic["固定收益率(年化)"] = "0.0000"; //需求说直接都是0 + // 固定收益率(年化)- ETF默认取"增强收益"腿的计息利率 + bool isEtf = IsBondEtf(underlying?.UnderlyingCode ?? string.Empty); + if (isEtf) + { + var enhancePosition = swapPositions + .Where(x => ConsTrade.InterestModels.Contains(x.InterestMode) && x.category_tag == "增强收益") + .FirstOrDefault(); + dic["固定收益率"] = enhancePosition != null + ? ((double)enhancePosition.InterestRateDefault * 100).ToString("N4") + : "0.0000"; + } + else + { + dic["固定收益率"] = "0.0000"; + } // 获取客户适用的保证金率 var clientMarginRate = UnderlyingHelper.GetApplicableMarginRate( @@ -405,9 +415,18 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator : "0.0000"; // 利率类型判断(固定/浮动) - var interestMargin = swapPositions - .Where(x => ConsTrade.InterestModels.Contains(x.InterestMode) && x.interest_rest_days != null) - .FirstOrDefault(); + swap_position interestMargin = null; + // ETF: 优先取"互换利率"腿 + if (isEtf) + { + interestMargin = swapPositions + .Where(x => ConsTrade.InterestModels.Contains(x.InterestMode) && x.category_tag == "互换利率") + .FirstOrDefault(); + } + if (interestMargin == null) + interestMargin = swapPositions + .Where(x => ConsTrade.InterestModels.Contains(x.InterestMode) && x.interest_rest_days != null) + .FirstOrDefault(); if (interestMargin == null) interestMargin = swapPositions .Where(x => ConsTrade.InterestModels.Contains(x.InterestMode) && string.IsNullOrWhiteSpace(x.FloatRateUnderlyingCode)) From a3616bdaaf3569761eb9a84331114deae34900d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Fri, 24 Jul 2026 11:18:20 +0800 Subject: [PATCH 07/22] =?UTF-8?q?feature:=20=E6=89=8B=E5=8A=A8=E5=A1=AB?= =?UTF-8?q?=E5=86=99=E5=90=8D=E4=B9=89=E6=9C=AC=E9=87=91=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=E5=8F=8D=E7=AE=97=E6=95=B0=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Scripts/app/swaptrade/swapTradeEdit.js | 16 ++++++------- .../Scripts/app/trade/swapTradeEdit.js | 23 +++++++------------ 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js index 6c9fcdf1..a7c692d0 100644 --- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js +++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js @@ -283,17 +283,17 @@ const vue = new Vue({ changeContractSize(item) { this.calcNotional(); }, - //变更名义本金 + //变更名义本金(仅格式化,不反算数量) changeStockEqvNotional() { this.trade.StockEqvNotional = otcformat.trading.StockEqvNotional(this.trade.StockEqvNotional); //计算数量 - if (this.paySwapList.length > 0) { - var item = this.paySwapList[0]; - var deliveryPrice = this.roundStorageDeliveryPrice(item, item.PosiGrossPrice); - var notional = deliveryPrice * item.ContractSize; - item.PosiQuantity = notional == 0 ? 0 : _.round(this.trade.StockEqvNotional / notional, page.otcFormatConfig.StockEqvNotional.precision); - this.calcNotional(); - } + // if (this.paySwapList.length > 0) { + // var item = this.paySwapList[0]; + // var deliveryPrice = this.roundStorageDeliveryPrice(item, item.PosiGrossPrice); + // var notional = deliveryPrice * item.ContractSize; + // item.PosiQuantity = notional == 0 ? 0 : _.round(this.trade.StockEqvNotional / notional, page.otcFormatConfig.StockEqvNotional.precision); + // this.calcNotional(); + // } }, //变更初始预付金 为¥ diff --git a/YLErpWeb/wwwroot/Scripts/app/trade/swapTradeEdit.js b/YLErpWeb/wwwroot/Scripts/app/trade/swapTradeEdit.js index 95b22a66..0ad5dc9e 100644 --- a/YLErpWeb/wwwroot/Scripts/app/trade/swapTradeEdit.js +++ b/YLErpWeb/wwwroot/Scripts/app/trade/swapTradeEdit.js @@ -263,18 +263,18 @@ const vue = new Vue({ this.CnStockEqvNotional(); this.changeStockEqvNotional(); }, - //变更名义本金 + //变更名义本金(仅格式化,不反算数量) changeStockEqvNotional() { this.trade.StockEqvNotional = otcformat.trading.StockEqvNotional(this.trade.StockEqvNotional); if (this.trade.trade_swap.IsPayFloatingProfit) { - var spotPrice = isPaySyntheticUnderlying ? payMaxPrice : $("#trade_swap\\.PaySpotPrice").val(); - var notional = spotPrice != 0 ? this.trade.StockEqvNotional / spotPrice : 0; - this.trade.trade_swap.PayNotional = otcformat.trading.notional(Math.abs(notional)); - var tradeAmount = notional / payCountRatio; - this.trade.trade_swap.PayTradeAmount = otcformat.trading.notional(Math.abs(tradeAmount)); - var lots = notional / payContractSize; - this.trade.trade_swap.PayLot = otcformat.trading.notional(Math.abs(lots)); + // var spotPrice = isPaySyntheticUnderlying ? payMaxPrice : $("#trade_swap\\.PaySpotPrice").val(); + // var notional = spotPrice != 0 ? this.trade.StockEqvNotional / spotPrice : 0; + // this.trade.trade_swap.PayNotional = otcformat.trading.notional(Math.abs(notional)); + // var tradeAmount = notional / payCountRatio; + // this.trade.trade_swap.PayTradeAmount = otcformat.trading.notional(Math.abs(tradeAmount)); + // var lots = notional / payContractSize; + // this.trade.trade_swap.PayLot = otcformat.trading.notional(Math.abs(lots)); if (this.isSingleFee) { this.changeGetSingleFee(); } @@ -284,13 +284,6 @@ const vue = new Vue({ } if (this.trade.trade_swap.IsGetFloatingProfit) { - var spotPrice = isGetSyntheticUnderlying ? getMaxPrice : $("#trade_swap\\.GetSpotPrice").val(); - var notional = spotPrice != 0 ? this.trade.StockEqvNotional / spotPrice : 0; - this.trade.trade_swap.GetNotional = otcformat.trading.notional(Math.abs(notional)); - var tradeAmount = notional / getCountRatio; - this.trade.trade_swap.GetTradeAmount = otcformat.trading.notional(Math.abs(tradeAmount)); - var lots = notional / getContractSize; - this.trade.trade_swap.GetLot = otcformat.trading.notional(Math.abs(lots)); if (this.isSingleFee) { this.changePaySingleFee(); } From b729b388146ac3d30f38c4d7ae5d815b2d0b888f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Fri, 24 Jul 2026 13:23:35 +0800 Subject: [PATCH 08/22] =?UTF-8?q?fix(swap):=20=E4=BF=AE=E5=A4=8D=E7=BB=AD?= =?UTF-8?q?=E5=81=9A=E4=BA=A4=E6=98=93=E4=B8=AD=E6=8C=81=E4=BB=93=E5=92=8C?= =?UTF-8?q?=E5=90=8D=E4=B9=89=E6=9C=AC=E9=87=91=E7=9A=84=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 恢复初始持仓名义本金,使用OriginalStockEqvNotional和OriginalNotional的原始值 - 只克隆初始持仓(IsInitial=true),避免带入部分平仓后的实时持仓 - 设置预付金腿HappenDate为新交易起始日,非预付金腿置为null - 清空源交易的事件/持仓快照等集合,避免与源交易共享引用 - 初始化swap_Events、swap_Flow_Events等集合为空列表 --- YLErpWeb/Controllers/SwapTrade2Controller.cs | 29 ++++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/YLErpWeb/Controllers/SwapTrade2Controller.cs b/YLErpWeb/Controllers/SwapTrade2Controller.cs index 09cd2df1..954b0f14 100644 --- a/YLErpWeb/Controllers/SwapTrade2Controller.cs +++ b/YLErpWeb/Controllers/SwapTrade2Controller.cs @@ -139,6 +139,14 @@ namespace YLErp.Web.Controllers renewTrade.ValidState = null; renewTrade.CreateDate = null; renewTrade.TradeSource = null; + // 恢复初始持仓名义本金(源交易若有过部分平仓,StockEqvNotional/TradeAmount 已递减, + // 但 OriginalStockEqvNotional 和 OriginalNotional 始终保留原始值不被递减) + if (renewTrade.OriginalStockEqvNotional != null) + { + renewTrade.StockEqvNotional = (double)renewTrade.OriginalStockEqvNotional; + } + + renewTrade.TradeAmount = renewTrade.OriginalNotional ?? renewTrade.TradeAmount; renewTrade.trade_extend = sourceTrade.trade_extend?.Clone() ?? defaultTrade.trade_extend; renewTrade.trade_extend.TradeId = 0; renewTrade.trade_Initial_Margin = sourceTrade.trade_Initial_Margin?.Clone() ?? new trade_initial_margin(); @@ -146,7 +154,10 @@ namespace YLErp.Web.Controllers renewTrade.MetaDic = sourceTrade.MetaDic == null ? new Dictionary() : new Dictionary(sourceTrade.MetaDic); - renewTrade.swap_positions = sourceTrade.swap_positions.Select(position => + // 只克隆初始持仓(IsInitial=true),避免将部分平仓后的实时持仓(名义本金已递减)带入续做交易 + renewTrade.swap_positions = sourceTrade.swap_positions + ?.Where(p => p.IsInitial) + .Select(position => { var renewPosition = position.Clone(); renewPosition.id = 0; @@ -155,11 +166,23 @@ namespace YLErp.Web.Controllers renewPosition.PosiNumber = null; renewPosition.PosiStartDate = defaultTrade.StartDate.Value; renewPosition.PosiMatuirityDate = null; - renewPosition.HappenDate = null; + // 预付金腿的 HappenDate 用于后续生成资金流水(ResetMarginAmount 按 HappenDate 过滤), + // 续做时设为新交易的起始日;非预付金腿的 HappenDate 无实际用途,置 null + renewPosition.HappenDate = + position.InterestMode == (int)YLErp.DBModels.InterestModeEnum.初始预付金 + ? defaultTrade.StartDate + : null; renewPosition.InterestSwapInterval = null; renewPosition.Obervation = null; return renewPosition; - }).ToList(); + }).ToList() ?? new List(); + // 清空源交易的事件/持仓快照等集合,避免与源交易共享引用 + renewTrade.swap_Events = new List(); + renewTrade.swap_Flow_Events = new List(); + renewTrade.eod_swaps = new List(); + renewTrade.inital_eod_swap_positions = new List(); + renewTrade.eod_swap_positions = new List(); + renewTrade.ClientCashInCashOutList = new List(); return renewTrade; } /// From 3518be19755916c73816f392c3225b99cf6a1ba8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Fri, 24 Jul 2026 13:56:41 +0800 Subject: [PATCH 09/22] =?UTF-8?q?fix(swap-trade):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E7=BB=AD=E5=81=9A=E4=BA=A4=E6=98=93=E6=97=B6=E5=90=8D=E4=B9=89?= =?UTF-8?q?=E6=9C=AC=E9=87=91=E5=92=8C=E7=BB=93=E7=AE=97=E6=A0=87=E8=AF=86?= =?UTF-8?q?=E7=9A=84=E9=87=8D=E7=BD=AE=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 恢复初始名义本金时同时处理 Notional、TradeAmount 和 StockEqvNotional 字段 - 重置结算标识为正常结算模式,清除延期结算相关字段 - 重置 trade_swap 子对象的主键和外键关联 - 清空持仓的运行时累计字段如利息金额、手续费等 --- YLErpWeb/Controllers/SwapTrade2Controller.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/YLErpWeb/Controllers/SwapTrade2Controller.cs b/YLErpWeb/Controllers/SwapTrade2Controller.cs index be227c9b..00297abc 100644 --- a/YLErpWeb/Controllers/SwapTrade2Controller.cs +++ b/YLErpWeb/Controllers/SwapTrade2Controller.cs @@ -139,14 +139,24 @@ namespace YLErp.Web.Controllers renewTrade.ValidState = null; renewTrade.CreateDate = null; renewTrade.TradeSource = null; - // 恢复初始持仓名义本金(源交易若有过部分平仓,StockEqvNotional/TradeAmount 已递减, + // 恢复初始名义本金(源交易若有过部分平仓,StockEqvNotional/TradeAmount/Notional 已递减, // 但 OriginalStockEqvNotional 和 OriginalNotional 始终保留原始值不被递减) if (renewTrade.OriginalStockEqvNotional != null) { renewTrade.StockEqvNotional = (double)renewTrade.OriginalStockEqvNotional; } - + renewTrade.Notional = renewTrade.OriginalNotional ?? renewTrade.TradeAmount; renewTrade.TradeAmount = renewTrade.OriginalNotional ?? renewTrade.TradeAmount; + // 结算标识 — 源交易可能为"延期结算",续做时重置为正常结算 + renewTrade.SettlementFlag = 0; + renewTrade.SettlementFlagOptId = null; + // trade_swap — 重置源交易遗留的 PK/FK 和运行时字段 + if (renewTrade.trade_swap != null) + { + renewTrade.trade_swap.id = 0; + renewTrade.trade_swap.TradeId = 0; + renewTrade.trade_swap.FlowId = null; + } renewTrade.trade_extend = sourceTrade.trade_extend?.Clone() ?? defaultTrade.trade_extend; renewTrade.trade_extend.TradeId = 0; renewTrade.trade_Initial_Margin = sourceTrade.trade_Initial_Margin?.Clone() ?? new trade_initial_margin(); @@ -172,6 +182,11 @@ namespace YLErp.Web.Controllers position.InterestMode == (int)YLErp.DBModels.InterestModeEnum.初始预付金 ? defaultTrade.StartDate : null; + // 清空运行时累计字段(这些字段在源交易存续期间可能被累计) + renewPosition.InterestAmount = 0; + renewPosition.InterestFeePending = 0; + renewPosition.PosiDividendIncome = 0; + renewPosition.PosiTradingFeePending = 0; renewPosition.InterestSwapInterval = null; renewPosition.Obervation = null; return renewPosition; From c9071a4eff5c4a39b3a368889a97d29408318ecc Mon Sep 17 00:00:00 2001 From: tengyufan <1532636164@qq.com> Date: Fri, 24 Jul 2026 17:32:07 +0800 Subject: [PATCH 10/22] =?UTF-8?q?feat(swap):=20=E6=96=B0=E5=A2=9E=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E8=B4=B9=E7=8E=87=E5=AD=97=E6=AE=B5=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E7=99=BE=E5=88=86=E6=AF=94/=E5=8D=95=E4=BD=8D?= =?UTF-8?q?=E6=95=B0=E9=87=8F=E6=A8=A1=E5=BC=8F=E5=88=87=E6=8D=A2=E5=8F=8A?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E8=AE=A1=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 浮动收益端新增基础费率列(原单位交易费用),位于数量与交易费用后付之间 - 支持百分比模式(4位小数)与单位数量模式(2位小数)切换,默认百分比 - 填入基础费率后自动计算交易费用后付,手动修改后反算基础费率 - 平仓时根据 PosiFeeType 自动计算平仓交易费用,支持全部/部分平仓联动 - 收益结算(手动互换)不自动填充平仓交易费用 - 查看交易页面新增基础费率展示(百分比带%后缀4位,单位数量2位) - swap_position 表新增 PosiFeeType 字段标识费率模式(0=百分比 1=单位数量) - 切换模式时基础费率和交易费用归零,避免精度冲突 - 修复 TradeView.cshtml 引用了不存在字段 category_tag 的编译错误 --- .../YLErp.Core/DBModels/SwapFlowEvent.cs | 10 +++ Framework/YLErp.Core/DBModels/SwapPosition.cs | 5 ++ .../Modules/SwapModule/SwapDealService.cs | 21 ++++++ .../Modules/SwapModule/SwapTradeService.cs | 1 + YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml | 11 +++ YLErpWeb/Views/SwapTrade2/TradeView.cshtml | 15 +++- .../Scripts/app/swaptrade/swapTradeEdit.js | 65 ++++++++++++----- .../Scripts/app/swaptrade/unwindSwapTrade.js | 72 ++++++++++--------- 8 files changed, 146 insertions(+), 54 deletions(-) diff --git a/Framework/YLErp.Core/DBModels/SwapFlowEvent.cs b/Framework/YLErp.Core/DBModels/SwapFlowEvent.cs index b9079e27..675154f7 100644 --- a/Framework/YLErp.Core/DBModels/SwapFlowEvent.cs +++ b/Framework/YLErp.Core/DBModels/SwapFlowEvent.cs @@ -228,6 +228,16 @@ namespace YLErp.DBModels [NotMapped] public decimal BeforeCloseFee { get; set; } /// + /// 单位交易费用(仅前端展示,不存库) + /// + [NotMapped] + public decimal PosiTradingFeeUnit { get; set; } + /// + /// 单位交易费用模式 0=百分比 1=单位数量(仅前端展示,不存库) + /// + [NotMapped] + public int PosiFeeType { get; set; } + /// /// 持仓腿id /// [DisplayName("持仓腿id")] diff --git a/Framework/YLErp.Core/DBModels/SwapPosition.cs b/Framework/YLErp.Core/DBModels/SwapPosition.cs index 44fb89d9..04a306f9 100644 --- a/Framework/YLErp.Core/DBModels/SwapPosition.cs +++ b/Framework/YLErp.Core/DBModels/SwapPosition.cs @@ -113,6 +113,11 @@ namespace YLErp.DBModels [DataChange] public decimal PosiTradingFeeUnit { get; set; } /// + /// 单位交易费用模式 0=百分比 1=单位数量 + /// + [DataChange] + public int PosiFeeType { get; set; } + /// /// 起始日 /// [DisplayName("起始日")] diff --git a/YLErpDAL/Modules/SwapModule/SwapDealService.cs b/YLErpDAL/Modules/SwapModule/SwapDealService.cs index 05182f5f..fdde80ea 100644 --- a/YLErpDAL/Modules/SwapModule/SwapDealService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapDealService.cs @@ -291,6 +291,11 @@ namespace YLErp.Modules.SwapModule floatEvent.UnderlyingInstrumentType = position.UnderlyingInstrumentType; floatEvent.CloseFee = 0; floatEvent.BeforeCloseFee = oriPosition.PosiTradingFeePending; + //自动计算平仓交易费用 + floatEvent.TradingFee = CalcInitTradingFee(oriPosition, unwindData); + //向前端传递单位交易费用和模式标记(NotMapped,不存库) + floatEvent.PosiTradingFeeUnit = oriPosition.PosiTradingFeeUnit; + floatEvent.PosiFeeType = oriPosition.PosiFeeType; floatEvent.MarkClosePnl = 0; floatEvent.PayDirection = position.PosiDirection; floatEvent.PosiGrossPrice = position.PosiGrossPrice; @@ -314,6 +319,22 @@ namespace YLErp.Modules.SwapModule return unwindData; } /// + /// 计算初始平仓交易费用 + /// + private decimal CalcInitTradingFee(swap_position oriPosition, UnwindData unwindData) + { + if (oriPosition.PosiFeeType == 0) + { + //百分比模式:费率%/100 × 平仓名义本金 + return Math.Round(oriPosition.PosiTradingFeeUnit / 100m * (decimal)unwindData.CloseNotionalValue, 2, MidpointRounding.AwayFromZero); + } + else + { + //单位数量模式:单位费率 × 平仓数量 + return Math.Round(oriPosition.PosiTradingFeeUnit * (decimal)unwindData.CloseQty, 2, MidpointRounding.AwayFromZero); + } + } + /// /// 校验上日是否收盘 /// /// diff --git a/YLErpDAL/Modules/SwapModule/SwapTradeService.cs b/YLErpDAL/Modules/SwapModule/SwapTradeService.cs index ca343cbc..f32f97fe 100644 --- a/YLErpDAL/Modules/SwapModule/SwapTradeService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapTradeService.cs @@ -1375,6 +1375,7 @@ namespace YLErp.Modules.SwapModule position.PosiTradingFee = swap.PosiTradingFee; position.PosiTradingFee=Math.Round(position.PosiTradingFee, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); position.PosiTradingFeeUnit = swap.PosiTradingFeeUnit; + position.PosiFeeType = swap.PosiFeeType; position.PosiTradingFeePending = swap.PosiTradingFeePending; position.PosiTradingFeePending = Math.Round(position.PosiTradingFeePending, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); position.UnderlyingCode = swap.UnderlyingCode; diff --git a/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml b/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml index 6409fe53..9c2a9196 100644 --- a/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml +++ b/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml @@ -450,6 +450,7 @@ 期初标的成交收益率% 期初标的价格 数量 + 基础费率 交易费用后付 @@ -487,6 +488,16 @@ {{item.underlying!=null?item.underlying.QuoteUnitString:''}} + + + +
我方{{item.PosiDirection==1?"支付":"收取"}}交易费用
diff --git a/YLErpWeb/Views/SwapTrade2/TradeView.cshtml b/YLErpWeb/Views/SwapTrade2/TradeView.cshtml index 9172e401..ed573429 100644 --- a/YLErpWeb/Views/SwapTrade2/TradeView.cshtml +++ b/YLErpWeb/Views/SwapTrade2/TradeView.cshtml @@ -349,7 +349,7 @@ @item.interest_rest_days @((item.interest_rule != null) ? (SwapInterestRule)item.interest_rule : "") - @(string.IsNullOrEmpty(item.category_tag) ? "互换利率" : item.category_tag) + 互换利率 @@ -384,6 +384,7 @@ @initYtmTitle } 数量 + 基础费率 交易费用后付 @foreach (var item in paySwapPositions) @@ -409,6 +410,16 @@ @item.PosiQuantity.OtcFormat(OtcFormatFlag.StockEqvNotional) + + @if (item.PosiFeeType == 0) + { + @(item.PosiTradingFeeUnit.ToString("0.0000") + "%") + } + else + { + @item.PosiTradingFeeUnit.OtcFormat(OtcFormatFlag.StockEqvNotional) + } + @item.PosiTradingFeePending.OtcFormat(OtcFormatFlag.StockEqvNotional) @@ -552,7 +563,7 @@ @(item.InterestType == 0 ? "单利" : "复利") @item.interest_rest_days @((item.interest_rule != null) ? (SwapInterestRule)item.interest_rule : "") - @(string.IsNullOrEmpty(item.category_tag) ? "互换利率" : item.category_tag) + 互换利率 } diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js index fa6b7011..70a91ff2 100644 --- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js +++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js @@ -12,6 +12,8 @@ const inputFormatTradeAmount = Object.freeze({ precision: otcformat.trading.noti const inputFormatSwapRate = Object.freeze({ precision: otcformat.trading.premiumRateP.precision, negative: true, append: '%' }); const inputFormatTradePrice = Object.freeze({ precision: otcformat.trading.tradePrice.precision, negative: true, append: '' }); const inputFormatTradeSinglePrice = Object.freeze({ precision: otcformat.trading.tradeSinglePrice.precision, negative: true, append: '', percent: false }); +const inputFormatPosiFeePercent = Object.freeze({ precision: 4, negative: true, append: '' });//单位交易费用-百分比模式(精度4) +const inputFormatPosiFeeUnit = Object.freeze({ precision: 2, negative: true, append: '' });//单位交易费用-单位数量模式(精度2) const inputFormatMarginRate = Object.freeze({ precision: otcformat.trading.marginRateP.precision, append: '%' }); const inputFormatSwapDeliveryPrice = Object.freeze({ precision: 9, negative: true, append: '', percent: false }); const inputFormatSwapBondDeliveryPrice = Object.freeze({ precision: 9, negative: true, append: '', percent: true }); @@ -153,6 +155,7 @@ const vue = new Vue({ currencys: page.currencys, getNotionalSingleFee: 0, isSingleFee: page.Trade.trade_extend.ExtendObj.OpenFeeType == 0, + posiFeeModePercent: true,//单位交易费用模式: true=百分比模式, false=单位数量模式 observation: {//互换观察日 ObservationInterval: "", IntervalList: [], @@ -507,28 +510,50 @@ const vue = new Vue({ } }, - //变更单位交易费用 + //变更单位交易费用(正算:单位交易费用→交易费用后付) changeTradingFeeUnit(item) { - //计算交易费用 - //if (this.trade.trade_extend.ExtendObj.OpenFeeType == 0) {//按手数收费 - // item.PosiTradingFee = item.ContractSize == 0 ? 0 : otcformat.trading.tradeSinglePrice(item.PosiQuantity * item.PosiTradingFeeUnit / item.ContractSize); - //} else { - // item.PosiTradingFee = otcformat.trading.tradeSinglePrice(item.PosiQuantity * item.PosiTradingFeeUnit); - //} - + if (this.posiFeeModePercent) { + //百分比模式: 交易费用后付 = round(费率%/100 × 名义本金, 2) + var stockEqvNotional = Number(this.trade.StockEqvNotional) || 0; + item.PosiTradingFeePending = stockEqvNotional == 0 ? 0 : otcformat.trading.tradeSinglePrice(Number(item.PosiTradingFeeUnit) / 100 * stockEqvNotional); + } else { + //单位数量模式: 交易费用后付 = round(单位费率 × 数量, 2) + var quantity = Number(item.PosiQuantity) || 0; + item.PosiTradingFeePending = otcformat.trading.tradeSinglePrice(Number(item.PosiTradingFeeUnit) * quantity); + } }, - //变更交易费用 + //变更交易费用(反算:交易费用后付→单位交易费用) changeTradingFee(item) { - //计算单位交易费用 - //if (item.PosiQuantity == 0) { - // item.PosiTradingFeeUnit = 0; - // return - //} - //if (this.trade.trade_extend.ExtendObj.OpenFeeType == 0) {//按手数收费 - // item.PosiTradingFeeUnit = item.ContractSize == 0 ? 0 : otcformat.trading.tradeSinglePrice(item.PosiTradingFee * item.ContractSize / item.PosiQuantity); - //} else { - // item.PosiTradingFeeUnit = otcformat.trading.tradeSinglePrice(item.PosiTradingFee / item.PosiQuantity); - //} + if (item.PosiQuantity == 0) { + item.PosiTradingFeeUnit = 0; + return; + } + if (this.posiFeeModePercent) { + //百分比模式反算: 单位交易费用 = round(交易费用后付 / 名义本金 × 100, 4) + var stockEqvNotional = Number(this.trade.StockEqvNotional) || 0; + item.PosiTradingFeeUnit = stockEqvNotional == 0 ? 0 : _.round(Number(item.PosiTradingFeePending) / stockEqvNotional * 100, 4); + } else { + //单位数量模式反算: 单位交易费用 = round(交易费用后付 / 数量, 2) + item.PosiTradingFeeUnit = _.round(Number(item.PosiTradingFeePending) / Number(item.PosiQuantity), 2); + } + }, + //切换到单位数量模式 + showPayAbsPrice() { + this.posiFeeModePercent = false; + this.paySwapList.forEach(item => { + item.PosiFeeType = 1; + item.PosiTradingFeeUnit = 0; + item.PosiTradingFeePending = 0; + }); + }, + //切换到百分比模式 + showPayPercentPrice() { + this.posiFeeModePercent = true; + this.paySwapList.forEach(item => { + item.PosiFeeType = 0; + item.PosiTradingFeeUnit = 0; + item.PosiTradingFeePending = 0; + }); }, savetrade() { if (!this.checkSubmitData()) { @@ -618,6 +643,7 @@ const vue = new Vue({ x.PosiGrossPrice = thisObj.roundStorageDeliveryPrice(x, x.PosiGrossPrice); x.PosiNetNoFeePrice = thisObj.roundStorageBondNetPriceAndYtm(x.PosiNetNoFeePrice); x.InitYtm = x.InitYtm == null ? null : thisObj.roundStorageBondNetPriceAndYtm(x.InitYtm); + x.PosiFeeType = x.PosiFeeType ?? (this.posiFeeModePercent ? 0 : 1); thisObj.trade.swap_positions.push(x); }); } else { @@ -1576,6 +1602,7 @@ const vue = new Vue({ PosiTradingFee: 0,//交易费用 PosiTradingFeePending: 0,//交易费用后付 PosiTradingFeeUnit: 0,//单位交易费用 + PosiFeeType: 0,//单位交易费用模式 0=百分比 1=单位数量 InterestDirection: 0,//利息收支方式 InterestRateDefault: 0,//计息利率 InterestMode: 0,//计息基本类型 diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js index 3838b7ea..3204b525 100644 --- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js +++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js @@ -60,9 +60,12 @@ const vue = new Vue({ this.ratio = this.floatPosition.PayDirection == 1 ? -1 : 1; this.shortRatio = this.floatPosition.PositionType == 1 ? 1 : -1; this.TradeStartDate = model.TradeStartDate; - // 最多可平比例(占期初口径) = 剩余名义本金 / 期初名义本金;分母为 0 时兜底为 1 - this.oriClosePercent = (this.deal.NotionalValue && this.deal.PosiNotionalValue) - ? this.deal.PosiNotionalValue / this.deal.NotionalValue : 1; + // 多次部分平仓后 ClosePercent 语义为"占剩余持仓比例"。 + // 仅当"全部平仓"(CloseMethod==1) 时修正旧口径(model.ClosePercent 可能=剩余/原始<1)为 1; + // "部分平仓"(CloseMethod==2) 时保留已提交比例(平仓待复核场景),避免覆盖用户已提交的 closePercent + if (this.deal.CloseMethod === 1) { + this.deal.ClosePercent = 1; + } // 转换期末标的价格为百分比形式 if (this.floatPosition.TradingAmountAvg) { this.floatPosition.TradingAmountAvg = this.floatPosition.TradingAmountAvg * this.multiplier; @@ -136,39 +139,46 @@ const vue = new Vue({ this.deal.CloseNotionalValue = otcformat.trading.StockEqvNotional(parseFloat(this.deal.PosiNotionalValue)); this.deal.CloseQty = this.deal.PositionQty; } else { - // ClosePercent 是占期初口径(A),需除以 oriClosePercent 转占剩余(B) 再乘剩余数量 - this.deal.CloseQty = this.calcCloseQtyByPercent(this.deal.ClosePercent); + this.deal.CloseQty = otcformat.trading.notional(parseFloat(this.deal.PositionQty) * parseFloat(this.deal.ClosePercent)); } this.calcTradingFeePending(); + this.calcTradingFee(); this.getInterestList(); this.calcFloatClosePnl(); }, - // 按"占期初口径(A)"的 ClosePercent 反算平仓数量:CloseQty = PositionQty × (ClosePercent / oriClosePercent) - // 多次部分平仓后必须这样转换,否则全部↔部分切换时 ClosePercent 没变但 CloseQty 会变(不自洽) - // 使用 swapCalc.calcCloseQtyByOriginalPercent 的 roundHalfAwayFromZero 避免 JS 浮点精度偏差 - // (如 32500000*(0.5/0.65)=24999999.999999996 而非 25000000) - calcCloseQtyByPercent(closePercent) { - return SwapCalc.calcCloseQtyByOriginalPercent(closePercent, this.oriClosePercent, this.deal.PositionQty); - }, calcTradingFeePending() { this.floatPosition.TradingFeePending = this.floatPosition.BeforeCloseFee * parseFloat(this.deal.ClosePercent); }, + //自动计算平仓交易费用(根据单位交易费用 + 模式) + calcTradingFee() { + var unit = parseFloat(this.floatPosition.PosiTradingFeeUnit) || 0; + var type = parseInt(this.floatPosition.PosiFeeType) || 0; + if (type === 0) { + //百分比模式:费率%/100 × 平仓名义本金 + this.floatPosition.TradingFee = _.round(unit / 100 * parseFloat(this.deal.CloseNotionalValue), 2); + } else { + //单位数量模式:单位费率 × 平仓数量 + this.floatPosition.TradingFee = _.round(unit * parseFloat(this.deal.CloseQty), 2); + } + //用格式化函数确保显示一致 + this.floatPosition.TradingFee = otcformat.trading.StockEqvNotional(this.floatPosition.TradingFee); + this.calcFloatClosePnl(); + }, changeCloseQty() {//修改平仓数量 if (parseFloat(this.deal.CloseQty) > parseFloat(this.deal.PositionQty)) { main.message("平仓数量不能超过持仓数量"); return; } - // CloseQty/PositionQty 得占剩余(B),× oriClosePercent 转回占期初(A) - var ori = parseFloat(this.oriClosePercent) || 0; - this.deal.ClosePercent = otcformat.fixed6((parseFloat(this.deal.CloseQty) / parseFloat(this.deal.PositionQty)) * ori); + this.deal.ClosePercent = otcformat.fixed6(parseFloat(this.deal.CloseQty) / parseFloat(this.deal.PositionQty)); if (parseFloat(this.deal.CloseQty) == parseFloat(this.deal.PositionQty)) { this.deal.CloseMethod = 1; } else { this.deal.CloseMethod = 2; } - // 占期初口径:平仓名义本金 = 平仓比例 × 期初名义本金(NotionalValue) - this.deal.CloseNotionalValue = otcformat.trading.StockEqvNotional(parseFloat(this.deal.ClosePercent) * parseFloat(this.deal.NotionalValue)); + // 多次部分平仓后 PosiNotionalValue 才是剩余本金,不能用原始 NotionalValue,否则平仓名义本金偏大 + this.deal.CloseNotionalValue = otcformat.trading.StockEqvNotional(parseFloat(this.deal.ClosePercent) * parseFloat(this.deal.PosiNotionalValue)); this.calcTradingFeePending(); + this.calcTradingFee(); this.getInterestList(); this.calcFloatClosePnl(); }, @@ -178,15 +188,16 @@ const vue = new Vue({ this.deal.ClosePercent = this.oriClosePercent; return; } - this.deal.CloseQty = this.calcCloseQtyByPercent(this.deal.ClosePercent); - // 占期初口径:平仓名义本金 = 平仓比例 × 期初名义本金(NotionalValue) - this.deal.CloseNotionalValue = otcformat.trading.StockEqvNotional(parseFloat(this.deal.ClosePercent) * parseFloat(this.deal.NotionalValue)); - if (parseFloat(this.deal.ClosePercent) == parseFloat(this.oriClosePercent)) { - this.deal.CloseMethod = 1; + this.deal.CloseQty = otcformat.trading.notional(parseFloat(this.deal.PositionQty) * parseFloat(this.deal.ClosePercent)); + // 多次部分平仓后 PosiNotionalValue 才是剩余本金,不能用原始 NotionalValue,否则平仓名义本金偏大 + this.deal.CloseNotionalValue = otcformat.trading.StockEqvNotional(parseFloat(this.deal.ClosePercent) * parseFloat(this.deal.PosiNotionalValue)); + if (parseFloat(this.deal.CloseNotionalValue) == parseFloat(this.deal.PosiNotionalValue)) { + this.floatPosition.CloseMethod = 1; } else { - this.deal.CloseMethod = 2; + this.floatPosition.CloseMethod = 2; } this.calcTradingFeePending(); + this.calcTradingFee(); this.getInterestList(); this.calcFloatClosePnl(); }, @@ -196,15 +207,11 @@ const vue = new Vue({ this.deal.CloseNotionalValue = this.deal.PosiNotionalValue; return; } - // 占期初口径:平仓比例 = 平仓名义本金 / 期初名义本金(NotionalValue) - this.deal.ClosePercent = otcformat.fixed6(parseFloat(this.deal.CloseNotionalValue) / parseFloat(this.deal.NotionalValue)); - this.deal.CloseQty = this.calcCloseQtyByPercent(this.deal.ClosePercent); - if (parseFloat(this.deal.ClosePercent) == parseFloat(this.oriClosePercent)) { - this.deal.CloseMethod = 1; - } else { - this.deal.CloseMethod = 2; - } + // 多次部分平仓后应以 PosiNotionalValue(剩余) 为分母,否则 ClosePercent 偏小,导致后端预付金返还本金计算错误 + this.deal.ClosePercent = otcformat.fixed6(parseFloat(this.deal.CloseNotionalValue) / parseFloat(this.deal.PosiNotionalValue)); + this.deal.CloseQty = otcformat.trading.notional(parseFloat(this.deal.PositionQty) * parseFloat(this.deal.ClosePercent)); this.calcTradingFeePending(); + this.calcTradingFee(); this.getInterestList(); this.calcFloatClosePnl(); }, @@ -289,8 +296,7 @@ const vue = new Vue({ }, getInterestList() {//根据平仓日期获取利息腿信息 var thisObj = this; - // closePercent 按"占期初(original)"语义(A)传给后端,由 GetUnwindInterestList 转为"占剩余(B)"计算 - var postData = { valueDate: thisObj.deal.ValueDate, unwindDate: thisObj.deal.UnwindDate, tradeId: thisObj.deal.SwapTradeId, closePercent: thisObj.deal.ClosePercent, eventType: 2, notionalValue: thisObj.deal.NotionalValue, posiNotionalValue: thisObj.deal.PosiNotionalValue } + var postData = { valueDate: thisObj.deal.ValueDate, unwindDate: thisObj.deal.UnwindDate, tradeId: thisObj.deal.SwapTradeId, closePercent: thisObj.deal.ClosePercent, eventType: 2 } main.post("/swaptrade2/GetUnwindInterestList", postData, { async: true }).done(function (resp) { thisObj.interestList = resp.obj.filter((item) => { return item.InterestMode == 1 || item.InterestMode == 2 || item.InterestMode == 7 || item.InterestMode == 8 || item.InterestMode == 9; From a575de7bbec84755f2e77a6a460a0cdf309c4a28 Mon Sep 17 00:00:00 2001 From: tengyufan <1532636164@qq.com> Date: Mon, 27 Jul 2026 09:45:41 +0800 Subject: [PATCH 11/22] =?UTF-8?q?Revert=20"feat(swap):=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=9F=BA=E7=A1=80=E8=B4=B9=E7=8E=87=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=EF=BC=8C=E6=94=AF=E6=8C=81=E7=99=BE=E5=88=86=E6=AF=94/?= =?UTF-8?q?=E5=8D=95=E4=BD=8D=E6=95=B0=E9=87=8F=E6=A8=A1=E5=BC=8F=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E5=8F=8A=E8=87=AA=E5=8A=A8=E8=AE=A1=E7=AE=97"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit c9071a4eff5c4a39b3a368889a97d29408318ecc. --- .../YLErp.Core/DBModels/SwapFlowEvent.cs | 10 --- Framework/YLErp.Core/DBModels/SwapPosition.cs | 5 -- .../Modules/SwapModule/SwapDealService.cs | 21 ------ .../Modules/SwapModule/SwapTradeService.cs | 1 - YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml | 11 --- YLErpWeb/Views/SwapTrade2/TradeView.cshtml | 15 +--- .../Scripts/app/swaptrade/swapTradeEdit.js | 65 +++++------------ .../Scripts/app/swaptrade/unwindSwapTrade.js | 72 +++++++++---------- 8 files changed, 54 insertions(+), 146 deletions(-) diff --git a/Framework/YLErp.Core/DBModels/SwapFlowEvent.cs b/Framework/YLErp.Core/DBModels/SwapFlowEvent.cs index 675154f7..b9079e27 100644 --- a/Framework/YLErp.Core/DBModels/SwapFlowEvent.cs +++ b/Framework/YLErp.Core/DBModels/SwapFlowEvent.cs @@ -228,16 +228,6 @@ namespace YLErp.DBModels [NotMapped] public decimal BeforeCloseFee { get; set; } /// - /// 单位交易费用(仅前端展示,不存库) - /// - [NotMapped] - public decimal PosiTradingFeeUnit { get; set; } - /// - /// 单位交易费用模式 0=百分比 1=单位数量(仅前端展示,不存库) - /// - [NotMapped] - public int PosiFeeType { get; set; } - /// /// 持仓腿id /// [DisplayName("持仓腿id")] diff --git a/Framework/YLErp.Core/DBModels/SwapPosition.cs b/Framework/YLErp.Core/DBModels/SwapPosition.cs index 04a306f9..44fb89d9 100644 --- a/Framework/YLErp.Core/DBModels/SwapPosition.cs +++ b/Framework/YLErp.Core/DBModels/SwapPosition.cs @@ -113,11 +113,6 @@ namespace YLErp.DBModels [DataChange] public decimal PosiTradingFeeUnit { get; set; } /// - /// 单位交易费用模式 0=百分比 1=单位数量 - /// - [DataChange] - public int PosiFeeType { get; set; } - /// /// 起始日 /// [DisplayName("起始日")] diff --git a/YLErpDAL/Modules/SwapModule/SwapDealService.cs b/YLErpDAL/Modules/SwapModule/SwapDealService.cs index fdde80ea..05182f5f 100644 --- a/YLErpDAL/Modules/SwapModule/SwapDealService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapDealService.cs @@ -291,11 +291,6 @@ namespace YLErp.Modules.SwapModule floatEvent.UnderlyingInstrumentType = position.UnderlyingInstrumentType; floatEvent.CloseFee = 0; floatEvent.BeforeCloseFee = oriPosition.PosiTradingFeePending; - //自动计算平仓交易费用 - floatEvent.TradingFee = CalcInitTradingFee(oriPosition, unwindData); - //向前端传递单位交易费用和模式标记(NotMapped,不存库) - floatEvent.PosiTradingFeeUnit = oriPosition.PosiTradingFeeUnit; - floatEvent.PosiFeeType = oriPosition.PosiFeeType; floatEvent.MarkClosePnl = 0; floatEvent.PayDirection = position.PosiDirection; floatEvent.PosiGrossPrice = position.PosiGrossPrice; @@ -319,22 +314,6 @@ namespace YLErp.Modules.SwapModule return unwindData; } /// - /// 计算初始平仓交易费用 - /// - private decimal CalcInitTradingFee(swap_position oriPosition, UnwindData unwindData) - { - if (oriPosition.PosiFeeType == 0) - { - //百分比模式:费率%/100 × 平仓名义本金 - return Math.Round(oriPosition.PosiTradingFeeUnit / 100m * (decimal)unwindData.CloseNotionalValue, 2, MidpointRounding.AwayFromZero); - } - else - { - //单位数量模式:单位费率 × 平仓数量 - return Math.Round(oriPosition.PosiTradingFeeUnit * (decimal)unwindData.CloseQty, 2, MidpointRounding.AwayFromZero); - } - } - /// /// 校验上日是否收盘 /// /// diff --git a/YLErpDAL/Modules/SwapModule/SwapTradeService.cs b/YLErpDAL/Modules/SwapModule/SwapTradeService.cs index f32f97fe..ca343cbc 100644 --- a/YLErpDAL/Modules/SwapModule/SwapTradeService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapTradeService.cs @@ -1375,7 +1375,6 @@ namespace YLErp.Modules.SwapModule position.PosiTradingFee = swap.PosiTradingFee; position.PosiTradingFee=Math.Round(position.PosiTradingFee, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); position.PosiTradingFeeUnit = swap.PosiTradingFeeUnit; - position.PosiFeeType = swap.PosiFeeType; position.PosiTradingFeePending = swap.PosiTradingFeePending; position.PosiTradingFeePending = Math.Round(position.PosiTradingFeePending, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); position.UnderlyingCode = swap.UnderlyingCode; diff --git a/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml b/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml index 9c2a9196..6409fe53 100644 --- a/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml +++ b/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml @@ -450,7 +450,6 @@ 期初标的成交收益率% 期初标的价格 数量 - 基础费率 交易费用后付 @@ -488,16 +487,6 @@ {{item.underlying!=null?item.underlying.QuoteUnitString:''}} - - - -
我方{{item.PosiDirection==1?"支付":"收取"}}交易费用
diff --git a/YLErpWeb/Views/SwapTrade2/TradeView.cshtml b/YLErpWeb/Views/SwapTrade2/TradeView.cshtml index ed573429..9172e401 100644 --- a/YLErpWeb/Views/SwapTrade2/TradeView.cshtml +++ b/YLErpWeb/Views/SwapTrade2/TradeView.cshtml @@ -349,7 +349,7 @@ @item.interest_rest_days @((item.interest_rule != null) ? (SwapInterestRule)item.interest_rule : "") - 互换利率 + @(string.IsNullOrEmpty(item.category_tag) ? "互换利率" : item.category_tag) @@ -384,7 +384,6 @@ @initYtmTitle } 数量 - 基础费率 交易费用后付 @foreach (var item in paySwapPositions) @@ -410,16 +409,6 @@ @item.PosiQuantity.OtcFormat(OtcFormatFlag.StockEqvNotional) - - @if (item.PosiFeeType == 0) - { - @(item.PosiTradingFeeUnit.ToString("0.0000") + "%") - } - else - { - @item.PosiTradingFeeUnit.OtcFormat(OtcFormatFlag.StockEqvNotional) - } - @item.PosiTradingFeePending.OtcFormat(OtcFormatFlag.StockEqvNotional) @@ -563,7 +552,7 @@ @(item.InterestType == 0 ? "单利" : "复利") @item.interest_rest_days @((item.interest_rule != null) ? (SwapInterestRule)item.interest_rule : "") - 互换利率 + @(string.IsNullOrEmpty(item.category_tag) ? "互换利率" : item.category_tag) } diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js index 70a91ff2..fa6b7011 100644 --- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js +++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js @@ -12,8 +12,6 @@ const inputFormatTradeAmount = Object.freeze({ precision: otcformat.trading.noti const inputFormatSwapRate = Object.freeze({ precision: otcformat.trading.premiumRateP.precision, negative: true, append: '%' }); const inputFormatTradePrice = Object.freeze({ precision: otcformat.trading.tradePrice.precision, negative: true, append: '' }); const inputFormatTradeSinglePrice = Object.freeze({ precision: otcformat.trading.tradeSinglePrice.precision, negative: true, append: '', percent: false }); -const inputFormatPosiFeePercent = Object.freeze({ precision: 4, negative: true, append: '' });//单位交易费用-百分比模式(精度4) -const inputFormatPosiFeeUnit = Object.freeze({ precision: 2, negative: true, append: '' });//单位交易费用-单位数量模式(精度2) const inputFormatMarginRate = Object.freeze({ precision: otcformat.trading.marginRateP.precision, append: '%' }); const inputFormatSwapDeliveryPrice = Object.freeze({ precision: 9, negative: true, append: '', percent: false }); const inputFormatSwapBondDeliveryPrice = Object.freeze({ precision: 9, negative: true, append: '', percent: true }); @@ -155,7 +153,6 @@ const vue = new Vue({ currencys: page.currencys, getNotionalSingleFee: 0, isSingleFee: page.Trade.trade_extend.ExtendObj.OpenFeeType == 0, - posiFeeModePercent: true,//单位交易费用模式: true=百分比模式, false=单位数量模式 observation: {//互换观察日 ObservationInterval: "", IntervalList: [], @@ -510,50 +507,28 @@ const vue = new Vue({ } }, - //变更单位交易费用(正算:单位交易费用→交易费用后付) + //变更单位交易费用 changeTradingFeeUnit(item) { - if (this.posiFeeModePercent) { - //百分比模式: 交易费用后付 = round(费率%/100 × 名义本金, 2) - var stockEqvNotional = Number(this.trade.StockEqvNotional) || 0; - item.PosiTradingFeePending = stockEqvNotional == 0 ? 0 : otcformat.trading.tradeSinglePrice(Number(item.PosiTradingFeeUnit) / 100 * stockEqvNotional); - } else { - //单位数量模式: 交易费用后付 = round(单位费率 × 数量, 2) - var quantity = Number(item.PosiQuantity) || 0; - item.PosiTradingFeePending = otcformat.trading.tradeSinglePrice(Number(item.PosiTradingFeeUnit) * quantity); - } + //计算交易费用 + //if (this.trade.trade_extend.ExtendObj.OpenFeeType == 0) {//按手数收费 + // item.PosiTradingFee = item.ContractSize == 0 ? 0 : otcformat.trading.tradeSinglePrice(item.PosiQuantity * item.PosiTradingFeeUnit / item.ContractSize); + //} else { + // item.PosiTradingFee = otcformat.trading.tradeSinglePrice(item.PosiQuantity * item.PosiTradingFeeUnit); + //} + }, - //变更交易费用(反算:交易费用后付→单位交易费用) + //变更交易费用 changeTradingFee(item) { - if (item.PosiQuantity == 0) { - item.PosiTradingFeeUnit = 0; - return; - } - if (this.posiFeeModePercent) { - //百分比模式反算: 单位交易费用 = round(交易费用后付 / 名义本金 × 100, 4) - var stockEqvNotional = Number(this.trade.StockEqvNotional) || 0; - item.PosiTradingFeeUnit = stockEqvNotional == 0 ? 0 : _.round(Number(item.PosiTradingFeePending) / stockEqvNotional * 100, 4); - } else { - //单位数量模式反算: 单位交易费用 = round(交易费用后付 / 数量, 2) - item.PosiTradingFeeUnit = _.round(Number(item.PosiTradingFeePending) / Number(item.PosiQuantity), 2); - } - }, - //切换到单位数量模式 - showPayAbsPrice() { - this.posiFeeModePercent = false; - this.paySwapList.forEach(item => { - item.PosiFeeType = 1; - item.PosiTradingFeeUnit = 0; - item.PosiTradingFeePending = 0; - }); - }, - //切换到百分比模式 - showPayPercentPrice() { - this.posiFeeModePercent = true; - this.paySwapList.forEach(item => { - item.PosiFeeType = 0; - item.PosiTradingFeeUnit = 0; - item.PosiTradingFeePending = 0; - }); + //计算单位交易费用 + //if (item.PosiQuantity == 0) { + // item.PosiTradingFeeUnit = 0; + // return + //} + //if (this.trade.trade_extend.ExtendObj.OpenFeeType == 0) {//按手数收费 + // item.PosiTradingFeeUnit = item.ContractSize == 0 ? 0 : otcformat.trading.tradeSinglePrice(item.PosiTradingFee * item.ContractSize / item.PosiQuantity); + //} else { + // item.PosiTradingFeeUnit = otcformat.trading.tradeSinglePrice(item.PosiTradingFee / item.PosiQuantity); + //} }, savetrade() { if (!this.checkSubmitData()) { @@ -643,7 +618,6 @@ const vue = new Vue({ x.PosiGrossPrice = thisObj.roundStorageDeliveryPrice(x, x.PosiGrossPrice); x.PosiNetNoFeePrice = thisObj.roundStorageBondNetPriceAndYtm(x.PosiNetNoFeePrice); x.InitYtm = x.InitYtm == null ? null : thisObj.roundStorageBondNetPriceAndYtm(x.InitYtm); - x.PosiFeeType = x.PosiFeeType ?? (this.posiFeeModePercent ? 0 : 1); thisObj.trade.swap_positions.push(x); }); } else { @@ -1602,7 +1576,6 @@ const vue = new Vue({ PosiTradingFee: 0,//交易费用 PosiTradingFeePending: 0,//交易费用后付 PosiTradingFeeUnit: 0,//单位交易费用 - PosiFeeType: 0,//单位交易费用模式 0=百分比 1=单位数量 InterestDirection: 0,//利息收支方式 InterestRateDefault: 0,//计息利率 InterestMode: 0,//计息基本类型 diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js index 3204b525..3838b7ea 100644 --- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js +++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js @@ -60,12 +60,9 @@ const vue = new Vue({ this.ratio = this.floatPosition.PayDirection == 1 ? -1 : 1; this.shortRatio = this.floatPosition.PositionType == 1 ? 1 : -1; this.TradeStartDate = model.TradeStartDate; - // 多次部分平仓后 ClosePercent 语义为"占剩余持仓比例"。 - // 仅当"全部平仓"(CloseMethod==1) 时修正旧口径(model.ClosePercent 可能=剩余/原始<1)为 1; - // "部分平仓"(CloseMethod==2) 时保留已提交比例(平仓待复核场景),避免覆盖用户已提交的 closePercent - if (this.deal.CloseMethod === 1) { - this.deal.ClosePercent = 1; - } + // 最多可平比例(占期初口径) = 剩余名义本金 / 期初名义本金;分母为 0 时兜底为 1 + this.oriClosePercent = (this.deal.NotionalValue && this.deal.PosiNotionalValue) + ? this.deal.PosiNotionalValue / this.deal.NotionalValue : 1; // 转换期末标的价格为百分比形式 if (this.floatPosition.TradingAmountAvg) { this.floatPosition.TradingAmountAvg = this.floatPosition.TradingAmountAvg * this.multiplier; @@ -139,46 +136,39 @@ const vue = new Vue({ this.deal.CloseNotionalValue = otcformat.trading.StockEqvNotional(parseFloat(this.deal.PosiNotionalValue)); this.deal.CloseQty = this.deal.PositionQty; } else { - this.deal.CloseQty = otcformat.trading.notional(parseFloat(this.deal.PositionQty) * parseFloat(this.deal.ClosePercent)); + // ClosePercent 是占期初口径(A),需除以 oriClosePercent 转占剩余(B) 再乘剩余数量 + this.deal.CloseQty = this.calcCloseQtyByPercent(this.deal.ClosePercent); } this.calcTradingFeePending(); - this.calcTradingFee(); this.getInterestList(); this.calcFloatClosePnl(); }, + // 按"占期初口径(A)"的 ClosePercent 反算平仓数量:CloseQty = PositionQty × (ClosePercent / oriClosePercent) + // 多次部分平仓后必须这样转换,否则全部↔部分切换时 ClosePercent 没变但 CloseQty 会变(不自洽) + // 使用 swapCalc.calcCloseQtyByOriginalPercent 的 roundHalfAwayFromZero 避免 JS 浮点精度偏差 + // (如 32500000*(0.5/0.65)=24999999.999999996 而非 25000000) + calcCloseQtyByPercent(closePercent) { + return SwapCalc.calcCloseQtyByOriginalPercent(closePercent, this.oriClosePercent, this.deal.PositionQty); + }, calcTradingFeePending() { this.floatPosition.TradingFeePending = this.floatPosition.BeforeCloseFee * parseFloat(this.deal.ClosePercent); }, - //自动计算平仓交易费用(根据单位交易费用 + 模式) - calcTradingFee() { - var unit = parseFloat(this.floatPosition.PosiTradingFeeUnit) || 0; - var type = parseInt(this.floatPosition.PosiFeeType) || 0; - if (type === 0) { - //百分比模式:费率%/100 × 平仓名义本金 - this.floatPosition.TradingFee = _.round(unit / 100 * parseFloat(this.deal.CloseNotionalValue), 2); - } else { - //单位数量模式:单位费率 × 平仓数量 - this.floatPosition.TradingFee = _.round(unit * parseFloat(this.deal.CloseQty), 2); - } - //用格式化函数确保显示一致 - this.floatPosition.TradingFee = otcformat.trading.StockEqvNotional(this.floatPosition.TradingFee); - this.calcFloatClosePnl(); - }, changeCloseQty() {//修改平仓数量 if (parseFloat(this.deal.CloseQty) > parseFloat(this.deal.PositionQty)) { main.message("平仓数量不能超过持仓数量"); return; } - this.deal.ClosePercent = otcformat.fixed6(parseFloat(this.deal.CloseQty) / parseFloat(this.deal.PositionQty)); + // CloseQty/PositionQty 得占剩余(B),× oriClosePercent 转回占期初(A) + var ori = parseFloat(this.oriClosePercent) || 0; + this.deal.ClosePercent = otcformat.fixed6((parseFloat(this.deal.CloseQty) / parseFloat(this.deal.PositionQty)) * ori); if (parseFloat(this.deal.CloseQty) == parseFloat(this.deal.PositionQty)) { this.deal.CloseMethod = 1; } else { this.deal.CloseMethod = 2; } - // 多次部分平仓后 PosiNotionalValue 才是剩余本金,不能用原始 NotionalValue,否则平仓名义本金偏大 - this.deal.CloseNotionalValue = otcformat.trading.StockEqvNotional(parseFloat(this.deal.ClosePercent) * parseFloat(this.deal.PosiNotionalValue)); + // 占期初口径:平仓名义本金 = 平仓比例 × 期初名义本金(NotionalValue) + this.deal.CloseNotionalValue = otcformat.trading.StockEqvNotional(parseFloat(this.deal.ClosePercent) * parseFloat(this.deal.NotionalValue)); this.calcTradingFeePending(); - this.calcTradingFee(); this.getInterestList(); this.calcFloatClosePnl(); }, @@ -188,16 +178,15 @@ const vue = new Vue({ this.deal.ClosePercent = this.oriClosePercent; return; } - this.deal.CloseQty = otcformat.trading.notional(parseFloat(this.deal.PositionQty) * parseFloat(this.deal.ClosePercent)); - // 多次部分平仓后 PosiNotionalValue 才是剩余本金,不能用原始 NotionalValue,否则平仓名义本金偏大 - this.deal.CloseNotionalValue = otcformat.trading.StockEqvNotional(parseFloat(this.deal.ClosePercent) * parseFloat(this.deal.PosiNotionalValue)); - if (parseFloat(this.deal.CloseNotionalValue) == parseFloat(this.deal.PosiNotionalValue)) { - this.floatPosition.CloseMethod = 1; + this.deal.CloseQty = this.calcCloseQtyByPercent(this.deal.ClosePercent); + // 占期初口径:平仓名义本金 = 平仓比例 × 期初名义本金(NotionalValue) + this.deal.CloseNotionalValue = otcformat.trading.StockEqvNotional(parseFloat(this.deal.ClosePercent) * parseFloat(this.deal.NotionalValue)); + if (parseFloat(this.deal.ClosePercent) == parseFloat(this.oriClosePercent)) { + this.deal.CloseMethod = 1; } else { - this.floatPosition.CloseMethod = 2; + this.deal.CloseMethod = 2; } this.calcTradingFeePending(); - this.calcTradingFee(); this.getInterestList(); this.calcFloatClosePnl(); }, @@ -207,11 +196,15 @@ const vue = new Vue({ this.deal.CloseNotionalValue = this.deal.PosiNotionalValue; return; } - // 多次部分平仓后应以 PosiNotionalValue(剩余) 为分母,否则 ClosePercent 偏小,导致后端预付金返还本金计算错误 - this.deal.ClosePercent = otcformat.fixed6(parseFloat(this.deal.CloseNotionalValue) / parseFloat(this.deal.PosiNotionalValue)); - this.deal.CloseQty = otcformat.trading.notional(parseFloat(this.deal.PositionQty) * parseFloat(this.deal.ClosePercent)); + // 占期初口径:平仓比例 = 平仓名义本金 / 期初名义本金(NotionalValue) + this.deal.ClosePercent = otcformat.fixed6(parseFloat(this.deal.CloseNotionalValue) / parseFloat(this.deal.NotionalValue)); + this.deal.CloseQty = this.calcCloseQtyByPercent(this.deal.ClosePercent); + if (parseFloat(this.deal.ClosePercent) == parseFloat(this.oriClosePercent)) { + this.deal.CloseMethod = 1; + } else { + this.deal.CloseMethod = 2; + } this.calcTradingFeePending(); - this.calcTradingFee(); this.getInterestList(); this.calcFloatClosePnl(); }, @@ -296,7 +289,8 @@ const vue = new Vue({ }, getInterestList() {//根据平仓日期获取利息腿信息 var thisObj = this; - var postData = { valueDate: thisObj.deal.ValueDate, unwindDate: thisObj.deal.UnwindDate, tradeId: thisObj.deal.SwapTradeId, closePercent: thisObj.deal.ClosePercent, eventType: 2 } + // closePercent 按"占期初(original)"语义(A)传给后端,由 GetUnwindInterestList 转为"占剩余(B)"计算 + var postData = { valueDate: thisObj.deal.ValueDate, unwindDate: thisObj.deal.UnwindDate, tradeId: thisObj.deal.SwapTradeId, closePercent: thisObj.deal.ClosePercent, eventType: 2, notionalValue: thisObj.deal.NotionalValue, posiNotionalValue: thisObj.deal.PosiNotionalValue } main.post("/swaptrade2/GetUnwindInterestList", postData, { async: true }).done(function (resp) { thisObj.interestList = resp.obj.filter((item) => { return item.InterestMode == 1 || item.InterestMode == 2 || item.InterestMode == 7 || item.InterestMode == 8 || item.InterestMode == 9; From b1c91bca347a115cb62935376f7fcf38108a06d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Mon, 27 Jul 2026 11:16:50 +0800 Subject: [PATCH 12/22] =?UTF-8?q?feat(swaptrade):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=97=A5=E7=BB=88=E6=8C=81=E4=BB=93=E9=A3=8E=E9=99=A9=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=E5=8A=9F=E8=83=BD=E5=92=8C=E7=B2=BE=E5=BA=A6=E6=8E=A7?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为价格字段添加9位小数精度的导出格式化器 - 实现自定义导出函数exportEodPositionRows以支持完整数据导出 - 在jqGrid列配置中添加exportFormatter和exportNumberFormat属性 - 更新utils.js中的导出逻辑以支持自定义格式化器和数字格式 - 在数据库上下文中添加ClientBalanceDaily实体的货币精度转换配置 - 修复导出时价格精度丢失问题,确保财务数据准确性 --- YLErpDAL/DataBase/YLContext.cs | 18 +++++++++- .../Scripts/app/swaptrade/EodPositionRisks.js | 36 +++++++++++++++++-- YLErpWeb/wwwroot/Scripts/utils.js | 8 +++-- 3 files changed, 57 insertions(+), 5 deletions(-) diff --git a/YLErpDAL/DataBase/YLContext.cs b/YLErpDAL/DataBase/YLContext.cs index 78936a0c..b9eb9d20 100644 --- a/YLErpDAL/DataBase/YLContext.cs +++ b/YLErpDAL/DataBase/YLContext.cs @@ -1,4 +1,7 @@ using BaseOUDAL; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using System.ComponentModel.DataAnnotations.Schema; +using System.Reflection; using YLErp.Core.DBModels; using YLErp.Model; @@ -14,6 +17,19 @@ namespace YLErp.BLL protected override void OnModelCreating(ModelBuilder modelBuilder) { + var clientBalanceMoneyConverter = new ValueConverter( + value => value.HasValue ? Convert.ToDecimal(value.Value) : null, + value => value.HasValue ? (double)value.Value : null); + var clientBalanceDaily = modelBuilder.Entity(); + foreach (var property in typeof(ClientBalanceDaily).GetProperties() + .Where(property => property.PropertyType == typeof(double?) + && property.GetCustomAttribute() == null)) + { + clientBalanceDaily.Property(property.Name) + .HasConversion(clientBalanceMoneyConverter) + .HasColumnType("decimal(20,6)"); + } + modelBuilder.Entity().HasKey(c => new { c.PGroup, c.PName }); modelBuilder.Entity().HasKey(c => new { c.ValueDate, c.OptionCode }); modelBuilder.Entity().HasKey(c => new { c.From, c.Key }); @@ -414,4 +430,4 @@ namespace YLErp.BLL public DbSet glms_risk_variable { get; set; } } -} \ No newline at end of file +} diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/EodPositionRisks.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/EodPositionRisks.js index 99067de4..ca12a59f 100644 --- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/EodPositionRisks.js +++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/EodPositionRisks.js @@ -168,7 +168,9 @@ function colModelGridEodPosition() { index: 'eodPosition.PosiNetPrice', width: 90, align: 'center', - formatter: PriceFormat + formatter: PriceFormat, + exportFormatter: ExportPriceNineDecimalFormat, + exportNumberFormat: '0.000000000' }, { name: 'eodPosition.PosiGrossPrice', label: '期初价格-不含费', @@ -176,6 +178,8 @@ function colModelGridEodPosition() { width: 90, align: 'center', formatter: PriceFormat, + exportFormatter: ExportPriceNineDecimalFormat, + exportNumberFormat: '0.000000000' }, { name: 'eodPosition.PosiQuantity', label: '名义数量', @@ -743,7 +747,7 @@ function exportVisibleColumns() { var tabName = page.tabIndex == 2 ? '框架合约' : '日终持仓'; var fileName = '日终持仓风险_互换_' + tabName + (dateStr ? '_' + dateStr : ''); if (page.tabIndex != 2) { - main.exportVisibleColumnsToExcel(jgrid, fileName, null); + exportEodPositionRows(jgrid, fileName); return; } @@ -770,6 +774,26 @@ function exportVisibleColumns() { }); } +function exportEodPositionRows(jgrid, fileName) { + var exportPostData = $.extend({}, GetPostData(), { + page: jgrid.jqGrid('getGridParam', 'page'), + rows: jgrid.jqGrid('getGridParam', 'rowNum'), + sidx: jgrid.jqGrid('getGridParam', 'sortname'), + sord: jgrid.jqGrid('getGridParam', 'sortorder') + }); + $.ajax({ + url: queryurl, + type: 'POST', + dataType: 'json', + traditional: true, + data: exportPostData + }).done(function (result) { + main.exportVisibleColumnsToExcel(jgrid, fileName, null, result && result.rows ? result.rows : []); + }).fail(function () { + main.message && main.message('导出失败,无法获取日终持仓数据'); + }); +} + function getVisibleEodSwapBusinessColumnNames(jgrid) { var colModel = jgrid.jqGrid('getGridParam', 'colModel') || []; return colModel.filter(function (col) { @@ -803,6 +827,14 @@ function PriceFormat(cellValue, options, rowObject) { return otcformat.trading.umprice(cellValue); } +function ExportPriceNineDecimalFormat(cellValue) { + if (cellValue === null || cellValue === undefined || cellValue === '') { + return ''; + } + var price = Number(cellValue); + return isFinite(price) ? price.toFixed(9) : cellValue; +} + function RealizedPnlFormat(cellValue, options, rowObject) { return otcformat.trading.tradePrice(cellValue); } diff --git a/YLErpWeb/wwwroot/Scripts/utils.js b/YLErpWeb/wwwroot/Scripts/utils.js index a615eda2..95c78d12 100644 --- a/YLErpWeb/wwwroot/Scripts/utils.js +++ b/YLErpWeb/wwwroot/Scripts/utils.js @@ -1236,7 +1236,9 @@ main.exportVisibleColumnsToExcel = function (jgrid, fileName, groupConfig, expor exportCols.forEach(function (col) { var colIndex = colModel.indexOf(col); var rawValue = $.jgrid.getAccessor(row, col.name); - var formattedValue = gridElement && gridElement.formatter + var formattedValue = typeof col.exportFormatter === 'function' + ? col.exportFormatter(rawValue, row) + : gridElement && gridElement.formatter ? gridElement.formatter(rowIndex + 1, rawValue, colIndex, row, 'add') : rawValue; formattedRow[col.name] = $('
').html(formattedValue == null ? '' : String(formattedValue)).text().replace(/\u00a0/g, ''); @@ -1263,7 +1265,9 @@ main.exportVisibleColumnsToExcel = function (jgrid, fileName, groupConfig, expor for (var c = 0; c < exportCols.length; c++) { var val = rows[i][exportCols[c].name]; if (val === undefined || val === null) val = ''; - html += '' + escapeXml(String(val)) + ''; + var numberFormat = exportCols[c].exportNumberFormat; + var style = numberFormat ? ' style="mso-number-format:\'' + escapeXml(String(numberFormat)) + '\';"' : ''; + html += '' + escapeXml(String(val)) + ''; } html += ''; } From 4987962aca5b549f9398e53210c68c9dbfe351e1 Mon Sep 17 00:00:00 2001 From: tengyufan <1532636164@qq.com> Date: Mon, 27 Jul 2026 11:57:20 +0800 Subject: [PATCH 13/22] =?UTF-8?q?#EQD-6597=20=E5=9B=BD=E8=81=94=E6=B0=91?= =?UTF-8?q?=E7=94=9F-=E7=B0=BF=E8=AE=B0=E6=97=B6=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E8=B4=B9=E7=8E=87=E5=AD=97=E6=AE=B5=EF=BC=88?= =?UTF-8?q?=E5=BC=80=E5=B9=B3=E4=BB=93=E8=B4=B9=E7=94=A8=EF=BC=89=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E4=B8=94=E8=87=AA=E5=8A=A8=E8=AE=A1=E7=AE=97=20feat:?= =?UTF-8?q?=20=E5=AE=8C=E5=96=84=E4=BA=92=E6=8D=A2=E5=B9=B3=E4=BB=93?= =?UTF-8?q?=E4=BA=A4=E6=98=93=E8=B4=B9=E7=94=A8=E8=87=AA=E5=8A=A8=E8=AE=A1?= =?UTF-8?q?=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 支持百分比和单位数量模式自动计算平仓交易费用 - 支持全部平仓和部分平仓联动重算 - 保持手动互换不自动填充平仓交易费用 - 补充前后端相关单测 --- .../YLErp.Core/DBModels/SwapFlowEvent.cs | 10 ++ Framework/YLErp.Core/DBModels/SwapPosition.cs | 5 + .../SwapModule/InitUnwindTradingFeeTest.cs | 65 +++++++++++ .../Modules/SwapModule/SwapDealService.cs | 17 +++ .../Modules/SwapModule/SwapTradeService.cs | 1 + YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml | 11 ++ YLErpWeb/Views/SwapTrade2/TradeView.cshtml | 11 ++ YLErpWeb/fe-tests/unwindSwapTrade.test.js | 92 +++++++++++++++ .../Scripts/app/swaptrade/swapTradeEdit.js | 107 +++++++++++++++--- .../Scripts/app/swaptrade/unwindSwapTrade.js | 27 +++++ 10 files changed, 329 insertions(+), 17 deletions(-) create mode 100644 UnitTestProject/Modules/SwapModule/InitUnwindTradingFeeTest.cs create mode 100644 YLErpWeb/fe-tests/unwindSwapTrade.test.js diff --git a/Framework/YLErp.Core/DBModels/SwapFlowEvent.cs b/Framework/YLErp.Core/DBModels/SwapFlowEvent.cs index b9079e27..632ed8c3 100644 --- a/Framework/YLErp.Core/DBModels/SwapFlowEvent.cs +++ b/Framework/YLErp.Core/DBModels/SwapFlowEvent.cs @@ -228,6 +228,16 @@ namespace YLErp.DBModels [NotMapped] public decimal BeforeCloseFee { get; set; } /// + /// 基础费率(仅前端展示,不存库) + /// + [NotMapped] + public decimal PosiTradingFeeUnit { get; set; } + /// + /// 基础费率模式 0=百分比 1=单位数量(仅前端展示,不存库) + /// + [NotMapped] + public int PosiFeeType { get; set; } + /// /// 持仓腿id /// [DisplayName("持仓腿id")] diff --git a/Framework/YLErp.Core/DBModels/SwapPosition.cs b/Framework/YLErp.Core/DBModels/SwapPosition.cs index 44fb89d9..04a306f9 100644 --- a/Framework/YLErp.Core/DBModels/SwapPosition.cs +++ b/Framework/YLErp.Core/DBModels/SwapPosition.cs @@ -113,6 +113,11 @@ namespace YLErp.DBModels [DataChange] public decimal PosiTradingFeeUnit { get; set; } /// + /// 单位交易费用模式 0=百分比 1=单位数量 + /// + [DataChange] + public int PosiFeeType { get; set; } + /// /// 起始日 /// [DisplayName("起始日")] diff --git a/UnitTestProject/Modules/SwapModule/InitUnwindTradingFeeTest.cs b/UnitTestProject/Modules/SwapModule/InitUnwindTradingFeeTest.cs new file mode 100644 index 00000000..8691bb11 --- /dev/null +++ b/UnitTestProject/Modules/SwapModule/InitUnwindTradingFeeTest.cs @@ -0,0 +1,65 @@ +using System.Reflection; +using YLErp.DBModels; + +namespace YLErp.Modules.SwapModule +{ + [TestClass] + public class InitUnwindTradingFeeTest + { + private static decimal InvokeCalcInitTradingFee(swap_position position, UnwindData unwindData) + { + var method = typeof(SwapDealService).GetMethod( + "CalcInitTradingFee", + BindingFlags.NonPublic | BindingFlags.Static); + + Assert.IsNotNull(method, "未找到 CalcInitTradingFee 私有静态方法"); + + return (decimal)method.Invoke(null, new object[] { position, unwindData }); + } + + [TestMethod] + public void 百分比模式_按平仓名义本金计算并四舍五入到两位() + { + var position = new swap_position + { + PosiFeeType = 0, + PosiTradingFeeUnit = 0.1234m + }; + var unwindData = new UnwindData + { + CloseNotionalValue = 1_000_000m, + CloseQty = 8888m + }; + + var fee = InvokeCalcInitTradingFee(position, unwindData); + + Assert.AreEqual(1234.00m, fee); + } + + [TestMethod] + public void 单位数量模式_按平仓数量计算并四舍五入到两位() + { + var position = new swap_position + { + PosiFeeType = 1, + PosiTradingFeeUnit = 1.235m + }; + var unwindData = new UnwindData + { + CloseNotionalValue = 1_000_000m, + CloseQty = 10m + }; + + var fee = InvokeCalcInitTradingFee(position, unwindData); + + Assert.AreEqual(12.35m, fee); + } + + [TestMethod] + public void 空入参_返回零() + { + Assert.AreEqual(0m, InvokeCalcInitTradingFee(null, new UnwindData())); + Assert.AreEqual(0m, InvokeCalcInitTradingFee(new swap_position(), null)); + } + } +} diff --git a/YLErpDAL/Modules/SwapModule/SwapDealService.cs b/YLErpDAL/Modules/SwapModule/SwapDealService.cs index 05182f5f..7bf7f637 100644 --- a/YLErpDAL/Modules/SwapModule/SwapDealService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapDealService.cs @@ -291,6 +291,9 @@ namespace YLErp.Modules.SwapModule floatEvent.UnderlyingInstrumentType = position.UnderlyingInstrumentType; floatEvent.CloseFee = 0; floatEvent.BeforeCloseFee = oriPosition.PosiTradingFeePending; + floatEvent.TradingFee = CalcInitTradingFee(oriPosition, unwindData); + floatEvent.PosiTradingFeeUnit = oriPosition?.PosiTradingFeeUnit ?? 0; + floatEvent.PosiFeeType = oriPosition?.PosiFeeType ?? 0; floatEvent.MarkClosePnl = 0; floatEvent.PayDirection = position.PosiDirection; floatEvent.PosiGrossPrice = position.PosiGrossPrice; @@ -313,6 +316,20 @@ namespace YLErp.Modules.SwapModule } return unwindData; } + private static decimal CalcInitTradingFee(swap_position oriPosition, UnwindData unwindData) + { + if (oriPosition == null || unwindData == null) + { + return 0; + } + + if (oriPosition.PosiFeeType == 1) + { + return Math.Round(oriPosition.PosiTradingFeeUnit * unwindData.CloseQty, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); + } + + return Math.Round(oriPosition.PosiTradingFeeUnit / 100m * unwindData.CloseNotionalValue, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); + } /// /// 校验上日是否收盘 /// diff --git a/YLErpDAL/Modules/SwapModule/SwapTradeService.cs b/YLErpDAL/Modules/SwapModule/SwapTradeService.cs index ca343cbc..f32f97fe 100644 --- a/YLErpDAL/Modules/SwapModule/SwapTradeService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapTradeService.cs @@ -1375,6 +1375,7 @@ namespace YLErp.Modules.SwapModule position.PosiTradingFee = swap.PosiTradingFee; position.PosiTradingFee=Math.Round(position.PosiTradingFee, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); position.PosiTradingFeeUnit = swap.PosiTradingFeeUnit; + position.PosiFeeType = swap.PosiFeeType; position.PosiTradingFeePending = swap.PosiTradingFeePending; position.PosiTradingFeePending = Math.Round(position.PosiTradingFeePending, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); position.UnderlyingCode = swap.UnderlyingCode; diff --git a/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml b/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml index 6409fe53..0eaf1224 100644 --- a/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml +++ b/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml @@ -450,6 +450,7 @@ 期初标的成交收益率% 期初标的价格 数量 + 基础费率 交易费用后付 @@ -487,6 +488,16 @@ {{item.underlying!=null?item.underlying.QuoteUnitString:''}} + + + +
我方{{item.PosiDirection==1?"支付":"收取"}}交易费用
diff --git a/YLErpWeb/Views/SwapTrade2/TradeView.cshtml b/YLErpWeb/Views/SwapTrade2/TradeView.cshtml index 9172e401..4cdf1c8e 100644 --- a/YLErpWeb/Views/SwapTrade2/TradeView.cshtml +++ b/YLErpWeb/Views/SwapTrade2/TradeView.cshtml @@ -384,6 +384,7 @@ @initYtmTitle } 数量 + 基础费率 交易费用后付 @foreach (var item in paySwapPositions) @@ -409,6 +410,16 @@ @item.PosiQuantity.OtcFormat(OtcFormatFlag.StockEqvNotional) + + @if (item.PosiFeeType == 0) + { + @(item.PosiTradingFeeUnit.ToString("0.0000") + "%") + } + else + { + @item.PosiTradingFeeUnit.ToString("0.00") + } + @item.PosiTradingFeePending.OtcFormat(OtcFormatFlag.StockEqvNotional) diff --git a/YLErpWeb/fe-tests/unwindSwapTrade.test.js b/YLErpWeb/fe-tests/unwindSwapTrade.test.js new file mode 100644 index 00000000..b977b41e --- /dev/null +++ b/YLErpWeb/fe-tests/unwindSwapTrade.test.js @@ -0,0 +1,92 @@ +const fs = require('fs'); +const path = require('path'); +const vm = require('vm'); + +function createNumberFormat(precision) { + const formatter = (value) => Number(Number(value || 0).toFixed(precision)); + formatter.precision = precision; + return formatter; +} + +function loadUnwindHelpers() { + const filePath = path.join(__dirname, '../wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js'); + const code = fs.readFileSync(filePath, 'utf8') + '\nmodule.exports = { swapPosiFeeCalc, consPosiFeeType };'; + + const stockEqvNotional = createNumberFormat(2); + const sandbox = { + module: { exports: {} }, + exports: {}, + console, + require, + window: { otcformat: { options: {} } }, + otcformat: { + options: {}, + trading: { + premiumRateP: { precision: 4 }, + tradePrice: { precision: 4 }, + notional: { precision: 6 }, + StockEqvNotional: stockEqvNotional, + marginRateP: { precision: 4 }, + umpriceP: { precision: 4 } + }, + fixed6: createNumberFormat(6) + }, + model: { + ValueDate: '2026-07-27', + FlowEvents: [], + StructureType: '', + TradeStartDate: '' + }, + isUseApproval: false, + Vue: function (options) { return options; }, + FastVue: { + vueDatePicker() { return {}; }, + vueNumberInput() { return {}; } + }, + tradeHelper: { IsBond() { return false; } }, + main: { + post() { + return { + done() { return this; } + }; + }, + message() { } + }, + SwapCalc: { + roundHalfAwayFromZero(value) { return value; }, + calcCloseQtyByOriginalPercent() { return 0; } + }, + _: { + round(value, precision) { + return Number(Number(value || 0).toFixed(precision || 0)); + } + } + }; + + sandbox.window.otcformat = sandbox.otcformat; + vm.runInNewContext(code, sandbox, { filename: filePath }); + return sandbox.module.exports; +} + +function expectClose(actual, expected, tolerance) { + expect(Math.abs(actual - expected)).toBeLessThanOrEqual(tolerance || 1e-6); +} + +describe('unwindSwapTrade 基础费率计算', () => { + const { swapPosiFeeCalc, consPosiFeeType } = loadUnwindHelpers(); + + test('百分比模式按平仓名义本金计算并保留两位', () => { + const result = swapPosiFeeCalc.calcTradingFee(consPosiFeeType.Percent, 0.1234, 1000000, 5000); + expectClose(result, 1234.00); + }); + + test('单位数量模式按平仓数量计算并保留两位', () => { + const result = swapPosiFeeCalc.calcTradingFee(consPosiFeeType.Unit, 1.235, 1000000, 10); + expectClose(result, 12.35); + }); + + test('未知模式默认按百分比模式处理', () => { + const result = swapPosiFeeCalc.calcTradingFee(99, 0.1, 200000, 10); + expectClose(result, 200.00); + }); +}); diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js index fa6b7011..c3eecede 100644 --- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js +++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js @@ -12,11 +12,39 @@ const inputFormatTradeAmount = Object.freeze({ precision: otcformat.trading.noti const inputFormatSwapRate = Object.freeze({ precision: otcformat.trading.premiumRateP.precision, negative: true, append: '%' }); const inputFormatTradePrice = Object.freeze({ precision: otcformat.trading.tradePrice.precision, negative: true, append: '' }); const inputFormatTradeSinglePrice = Object.freeze({ precision: otcformat.trading.tradeSinglePrice.precision, negative: true, append: '', percent: false }); +const inputFormatPosiFeePercent = Object.freeze({ precision: 4, negative: true, append: '%' }); +const inputFormatPosiFeeUnit = Object.freeze({ precision: 2, 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 inputFormatSwapBondDeliveryPrice = Object.freeze({ precision: 9, negative: true, append: '', percent: true }); const inputFormatSwapBondNetPriceAndYtm = Object.freeze({ precision: 9, negative: true, append: '', percent: true }); const swapBondStoragePricePrecision = inputFormatSwapBondDeliveryPrice.precision + 2; +const consPosiFeeType = Object.freeze({ Percent: 0, Unit: 1 }); +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; @@ -153,6 +181,7 @@ const vue = new Vue({ currencys: page.currencys, getNotionalSingleFee: 0, isSingleFee: page.Trade.trade_extend.ExtendObj.OpenFeeType == 0, + posiFeeModePercent: true, observation: {//互换观察日 ObservationInterval: "", IntervalList: [], @@ -236,6 +265,41 @@ const vue = new Vue({ const isBond = tradeHelper.IsBond(item && item.UnderlyingInstrumentType); return `${index}-${field}-${isBond ? 'bond' : 'other'}`; }, + 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; @@ -385,6 +449,7 @@ const vue = new Vue({ //变更名义本金(仅格式化,不反算数量) changeStockEqvNotional() { this.trade.StockEqvNotional = otcformat.trading.StockEqvNotional(this.trade.StockEqvNotional); + this.refreshPayTradingFeesByUnit(); //计算数量 // if (this.paySwapList.length > 0) { // var item = this.paySwapList[0]; @@ -474,6 +539,7 @@ const vue = new Vue({ var stockEqvNotional = SwapCalc.calcStockEqvNotional(deliveryPrice, national);//名义本金=期初价格*数量*乘数 this.trade.StockEqvNotional = otcformat.trading.stockEqvNotional(stockEqvNotional); payItem.PosiNotionalValue = this.trade.StockEqvNotional; + this.refreshPayTradingFeesByUnit(); } }, //变更到期日 @@ -509,26 +575,27 @@ const vue = new Vue({ }, //变更单位交易费用 changeTradingFeeUnit(item) { - //计算交易费用 - //if (this.trade.trade_extend.ExtendObj.OpenFeeType == 0) {//按手数收费 - // item.PosiTradingFee = item.ContractSize == 0 ? 0 : otcformat.trading.tradeSinglePrice(item.PosiQuantity * item.PosiTradingFeeUnit / item.ContractSize); - //} else { - // item.PosiTradingFee = otcformat.trading.tradeSinglePrice(item.PosiQuantity * item.PosiTradingFeeUnit); - //} - + this.refreshTradingFeePendingByUnit(item); }, //变更交易费用 changeTradingFee(item) { - //计算单位交易费用 - //if (item.PosiQuantity == 0) { - // item.PosiTradingFeeUnit = 0; - // return - //} - //if (this.trade.trade_extend.ExtendObj.OpenFeeType == 0) {//按手数收费 - // item.PosiTradingFeeUnit = item.ContractSize == 0 ? 0 : otcformat.trading.tradeSinglePrice(item.PosiTradingFee * item.ContractSize / item.PosiQuantity); - //} else { - // item.PosiTradingFeeUnit = otcformat.trading.tradeSinglePrice(item.PosiTradingFee / item.PosiQuantity); - //} + 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()) { @@ -618,6 +685,7 @@ const vue = new Vue({ x.PosiGrossPrice = thisObj.roundStorageDeliveryPrice(x, x.PosiGrossPrice); x.PosiNetNoFeePrice = thisObj.roundStorageBondNetPriceAndYtm(x.PosiNetNoFeePrice); x.InitYtm = x.InitYtm == null ? null : thisObj.roundStorageBondNetPriceAndYtm(x.InitYtm); + x.PosiFeeType = thisObj.normalizePosiFeeType(x.PosiFeeType); thisObj.trade.swap_positions.push(x); }); } else { @@ -1439,6 +1507,7 @@ const vue = new Vue({ 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; // D2 修复:重开(审批重开/刷新)已保存的债券成交单时,三字段互算的手动标志随页面重置而丢失; // 若不锁,用户一旦编辑任一价格字段就会以它为源重新反算、覆盖当初保存的其他两格。 @@ -1450,6 +1519,9 @@ const vue = new Vue({ thisObj.$set(arr[num], 'bondManual', { CP: true, DP: true, YD: true }); } }); + if (thisObj.paySwapList.length > 0) { + thisObj.syncPosiFeeModeByItem(thisObj.paySwapList[0]); + } } if (thisObj.paySwapList.length == 0) { @@ -1576,6 +1648,7 @@ const vue = new Vue({ PosiTradingFee: 0,//交易费用 PosiTradingFeePending: 0,//交易费用后付 PosiTradingFeeUnit: 0,//单位交易费用 + PosiFeeType: thisObj.getCurrentPosiFeeType(),//单位交易费用模式 InterestDirection: 0,//利息收支方式 InterestRateDefault: 0,//计息利率 InterestMode: 0,//计息基本类型 diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js index 3838b7ea..554329ac 100644 --- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js +++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js @@ -8,6 +8,22 @@ const inputFormatEqvNotional = Object.freeze({ precision: otcformat.trading.Stoc const inputFormatMarginRate = Object.freeze({ precision: otcformat.trading.marginRateP.precision, append: '%' }); const inputFormatMarginRateNoPercent = Object.freeze({ precision: otcformat.trading.umpriceP.precision, append: '', percent: true }); const inputFormatSwapDeliveryPrice = Object.freeze({ precision: 9, append: '', negative: true }); +const consPosiFeeType = Object.freeze({ Percent: 0, Unit: 1 }); +const swapPosiFeeCalc = { + normalizeFeeType(feeType) { + return Number(feeType) === consPosiFeeType.Unit ? consPosiFeeType.Unit : consPosiFeeType.Percent; + }, + calcTradingFee(feeType, feeUnit, closeNotionalValue, closeQty) { + const normalizedFeeType = this.normalizeFeeType(feeType); + const normalizedFeeUnit = Number(feeUnit) || 0; + const normalizedCloseNotionalValue = Number(closeNotionalValue) || 0; + const normalizedCloseQty = Number(closeQty) || 0; + const tradingFee = normalizedFeeType === consPosiFeeType.Unit + ? normalizedFeeUnit * normalizedCloseQty + : normalizedFeeUnit / 100 * normalizedCloseNotionalValue; + return otcformat.trading.StockEqvNotional(_.round(tradingFee, 2)); + } +}; let ValueDate = model.ValueDate; const vue = new Vue({ el: '#vueDiv', @@ -140,6 +156,7 @@ const vue = new Vue({ this.deal.CloseQty = this.calcCloseQtyByPercent(this.deal.ClosePercent); } this.calcTradingFeePending(); + this.refreshTradingFeeByUnit(); this.getInterestList(); this.calcFloatClosePnl(); }, @@ -153,6 +170,13 @@ const vue = new Vue({ calcTradingFeePending() { this.floatPosition.TradingFeePending = this.floatPosition.BeforeCloseFee * parseFloat(this.deal.ClosePercent); }, + refreshTradingFeeByUnit() { + this.floatPosition.TradingFee = swapPosiFeeCalc.calcTradingFee( + this.floatPosition.PosiFeeType, + this.floatPosition.PosiTradingFeeUnit, + this.deal.CloseNotionalValue, + this.deal.CloseQty); + }, changeCloseQty() {//修改平仓数量 if (parseFloat(this.deal.CloseQty) > parseFloat(this.deal.PositionQty)) { main.message("平仓数量不能超过持仓数量"); @@ -169,6 +193,7 @@ const vue = new Vue({ // 占期初口径:平仓名义本金 = 平仓比例 × 期初名义本金(NotionalValue) this.deal.CloseNotionalValue = otcformat.trading.StockEqvNotional(parseFloat(this.deal.ClosePercent) * parseFloat(this.deal.NotionalValue)); this.calcTradingFeePending(); + this.refreshTradingFeeByUnit(); this.getInterestList(); this.calcFloatClosePnl(); }, @@ -187,6 +212,7 @@ const vue = new Vue({ this.deal.CloseMethod = 2; } this.calcTradingFeePending(); + this.refreshTradingFeeByUnit(); this.getInterestList(); this.calcFloatClosePnl(); }, @@ -205,6 +231,7 @@ const vue = new Vue({ this.deal.CloseMethod = 2; } this.calcTradingFeePending(); + this.refreshTradingFeeByUnit(); this.getInterestList(); this.calcFloatClosePnl(); }, From bf6b27ba19e498cf338f0fc646b7e3af322f7c8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Mon, 27 Jul 2026 13:17:39 +0800 Subject: [PATCH 14/22] =?UTF-8?q?fix(swaptrade):=20=E4=BA=92=E6=8D=A2?= =?UTF-8?q?=E5=BC=80=E5=B9=B3=E4=BB=93=E4=BA=8B=E4=BB=B6=E6=B5=81=E6=B0=B4?= =?UTF-8?q?=20=E5=B0=8F=E6=95=B0=E7=82=B9=E7=B2=BE=E5=BA=A6=E8=B0=83?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将事件列表中的价格字段格式化统一为两位小数或九位小数 - 替换原有的otcformat.trading.umprice格式化方法 - 在前端JavaScript中添加EventTwoDecimalFormat和EventNineDecimalFormat函数 - 修改后端C#代码中的OtcFormat调用改为OtcFormatMoney并调整精度参数 - 为事件列表脚本添加版本参数以确保缓存更新 --- .../Modules/SwapModule/SwapFlowService.cs | 16 +++---- YLErpWeb/Views/SwapTrade2/EventList.cshtml | 4 +- .../Scripts/app/swaptrade/eventlist.js | 44 ++++++++++++------- 3 files changed, 38 insertions(+), 26 deletions(-) diff --git a/YLErpDAL/Modules/SwapModule/SwapFlowService.cs b/YLErpDAL/Modules/SwapModule/SwapFlowService.cs index 430a5e53..30a40623 100644 --- a/YLErpDAL/Modules/SwapModule/SwapFlowService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapFlowService.cs @@ -372,16 +372,16 @@ namespace YLErp.Modules.SwapModule exportModel.PositionType = item.PositionType==1?"多头":"空头"; exportModel.UnderlyingCode = item.UnderlyingCode; exportModel.MatuirityDate = item.MatuirityDate.OtcFormatDate(); - exportModel.TradingAmountAvg = item.TradingAmountAvg.OtcFormat(OtcFormatFlag.umprice); - exportModel.TradingAmountFeeAvg = item.TradingAmountFeeAvg.OtcFormat(OtcFormatFlag.umprice); + exportModel.TradingAmountAvg = item.TradingAmountAvg.OtcFormatMoney(grouping: false); + exportModel.TradingAmountFeeAvg = item.TradingAmountFeeAvg.OtcFormatMoney(grouping: false); exportModel.Quantity = item.Quantity.OtcFormatMoney(false, 4); - exportModel.TradingAmount = item.TradingAmount.OtcFormatMoney(false, 4); + exportModel.TradingAmount = item.TradingAmount.OtcFormatMoney(false, 2); exportModel.ContractSize = item.ContractSize.ToString(); - exportModel.TradingFee = item.TradingFee.OtcFormatMoney(false, 4); - exportModel.TradingFeePending = item.TradingFeePending.OtcFormatMoney(false, 4); - exportModel.DividendPending = item.DividendPending.OtcFormatMoney(false, 4); - exportModel.MarkClosePnl = item.MarkClosePnl.OtcFormatMoney(false, 4); - exportModel.DividendIn = item.DividendIn.OtcFormatMoney(false, 4); + exportModel.TradingFee = item.TradingFee.OtcFormatMoney(false, 2); + exportModel.TradingFeePending = item.TradingFeePending.OtcFormatMoney(false, 2); + exportModel.DividendPending = item.DividendPending.OtcFormatMoney(false, 2); + exportModel.MarkClosePnl = item.MarkClosePnl.OtcFormatMoney(false, 2); + exportModel.DividendIn = item.DividendIn.OtcFormatMoney(false, 2); exportModel.OptLog = item.OptLog; exportModel.InitYtm = item.InitYtm; list.Add(exportModel); diff --git a/YLErpWeb/Views/SwapTrade2/EventList.cshtml b/YLErpWeb/Views/SwapTrade2/EventList.cshtml index cfbe53fc..c3905e38 100644 --- a/YLErpWeb/Views/SwapTrade2/EventList.cshtml +++ b/YLErpWeb/Views/SwapTrade2/EventList.cshtml @@ -12,7 +12,7 @@ - + }
@@ -28,4 +28,4 @@
-
\ No newline at end of file + diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/eventlist.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/eventlist.js index e9f8f6b7..c8c32767 100644 --- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/eventlist.js +++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/eventlist.js @@ -176,36 +176,31 @@ var getColModelGrid = function () { label: '成交全价', width: 90, align: 'center', - formatter: otcformat.trading.umprice + formatter: EventTwoDecimalFormat }, { name: 'TradingAmountFeeAvg', label: '成交全价(含费)', width: 90, align: 'center', - formatter: otcformat.trading.umprice + formatter: EventTwoDecimalFormat }, { name: 'TradingAmountNetAvg', label: '成交净价', width: 90, align: 'center', - formatter: otcformat.trading.umprice + formatter: EventTwoDecimalFormat }, { name: 'InitYtm', label: '成交收益率', width: 90, align: 'center', - formatter: function (cellValue, options, rowObject) { - if (cellValue == null) { - return ""; - } - return otcformat.trading.premiumRateP(cellValue); - } + formatter: EventNineDecimalFormat }, { name: 'TradingAmountNetFeeAvg', label: '成交净价(含费)', width: 90, align: 'center', - formatter: otcformat.trading.umprice + formatter: EventTwoDecimalFormat }, { name: 'Quantity', label: '成交数量/张数', @@ -217,7 +212,7 @@ var getColModelGrid = function () { label: '成交金额(元)', width: 90, align: 'center', - formatter: otcformat.trading.umprice + formatter: EventTwoDecimalFormat }, { name: 'ContractSize', label: '乘数', @@ -230,31 +225,31 @@ var getColModelGrid = function () { label: '交易费用佣金', width: 90, align: 'center', - formatter: otcformat.trading.umprice + formatter: EventTwoDecimalFormat }, { name: 'TradingFeePending', label: '待结算交易费用佣金', width: 90, align: 'center', - formatter: otcformat.trading.umprice + formatter: EventTwoDecimalFormat }, { name: 'DividendPending', label: '待结算分红收益', width: 90, align: 'center', - formatter: otcformat.trading.umprice + formatter: EventTwoDecimalFormat }, { name: 'MarkClosePnl', label: '浮动端平仓盈亏·浮动', width: 160, align: 'center', - formatter: otcformat.trading.umprice + formatter: EventTwoDecimalFormat }, { name: 'DividendIn', label: '浮动端平仓盈亏·分红', width: 160, align: 'center', - formatter: otcformat.trading.umprice + formatter: EventTwoDecimalFormat } ]; return col; @@ -262,6 +257,23 @@ var getColModelGrid = function () { var colModelGrid = getColModelGrid(); +function EventTwoDecimalFormat(cellValue) { + if (cellValue === null || cellValue === undefined || cellValue === '') { + return ''; + } + var price = Number(cellValue); + return isFinite(price) ? price.toFixed(2) : cellValue; +} + +function EventNineDecimalFormat(cellValue) { + if (cellValue === null || cellValue === undefined || cellValue === '') { + return ''; + } + var price = Number(cellValue); + return isFinite(price) ? price.toFixed(9) : cellValue; +} + + function gridComplete() { $('.ui-jqgrid-bdiv', '#gbox_listGrid').floatingScroll(); } From e188ba5038a62e4ede9238cccdc48a0cb87e46d6 Mon Sep 17 00:00:00 2001 From: hjhan Date: Mon, 27 Jul 2026 13:18:13 +0800 Subject: [PATCH 15/22] =?UTF-8?q?test(swap):=20=E6=96=B0=E5=A2=9E=20CloseP?= =?UTF-8?q?ercent=20=E5=8F=A3=E5=BE=84A=E4=BA=A7=E5=93=81=E5=A5=91?= =?UTF-8?q?=E7=BA=A6=E6=B5=8B=E8=AF=95=EF=BC=8C=E9=98=B2=E6=AD=A2=E5=B9=B3?= =?UTF-8?q?=E4=BB=93=E6=AF=94=E4=BE=8B=E8=A2=AB=E6=94=B9=E4=B8=BA=E5=8D=A0?= =?UTF-8?q?=E5=89=A9=E4=BD=99=E5=8F=A3=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增前后端回归测试,锁定产品需求:ClosePercent 永远是占期初名义本金的比例。后端 CPC_001~010 装配测试验证 SwapDealService 真实执行 ToRemainingClosePercent/ToOriginalClosePercent 转换;前端 16 项接线测试验证 unwindSwapTrade.js 使用 NotionalValue 作为分母且 postData 携带 notionalValue 字段。测试背景:c9071a4e 曾将 ClosePercent 改为占剩余口径导致回归但现有测试全通过,本测试通过源码装配与契约验证弥补该缺口。 --- .../ClosePercentProductContractTest.cs | 357 ++++++++++++++++++ .../fe-tests/closePercentContract.test.js | 168 +++++++++ 2 files changed, 525 insertions(+) create mode 100644 UnitTestProject/Modules/SwapModule/ClosePercentProductContractTest.cs create mode 100644 YLErpWeb/fe-tests/closePercentContract.test.js diff --git a/UnitTestProject/Modules/SwapModule/ClosePercentProductContractTest.cs b/UnitTestProject/Modules/SwapModule/ClosePercentProductContractTest.cs new file mode 100644 index 00000000..ad44bead --- /dev/null +++ b/UnitTestProject/Modules/SwapModule/ClosePercentProductContractTest.cs @@ -0,0 +1,357 @@ +namespace YLErp.Modules.SwapModule +{ + /// + /// 平仓比例口径A(占期初名义本金)产品契约测试 + /// ================================================================================ + /// 产品需求(不可违背): + /// ClosePercent 永远是"占期初名义本金(NotionalValue)的比例",即口径A。 + /// 例:期初本金 5000 万,首次平 35%(ClosePercent=0.35),剩余 3250 万。 + /// 第二次想平掉剩余的 50%,ClosePercent 应 = 1625万/5000万 = 0.325(占期初), + /// 而非 0.50(占剩余)。 + /// + /// 前后端协作约定: + /// - 前端(unwindSwapTrade.js):ClosePercent 始终以 NotionalValue 为分母计算/显示 + /// - 前端传给后端:ClosePercent 为口径A + /// - 后端入口(SwapUnwind/ApplySwapTrade):ToRemainingClosePercent 将 A→B 供内部计算 + /// - 后端落库(SaveSwapDealInternal):ToOriginalClosePercent 将 B→A 还原存储 + /// - 后端 InitUnwind 默认值:CalcDefaultInitClosePercent = PosiNotionalValue/NotionalValue(口径A) + /// + /// 本测试守护的回归场景(c9071a4e 曾犯的错误): + /// 1. 前端把 oriClosePercent 硬编码为 1(应为 PosiNotionalValue/NotionalValue) + /// 2. 前端把 CloseNotionalValue 分母从 NotionalValue 改为 PosiNotionalValue + /// 3. 前端把 ClosePercent 分母从 NotionalValue 改为 PosiNotionalValue + /// 4. 前端把 getInterestList 的 notionalValue/posiNotionalValue 参数去掉 + /// 5. 前端把 calcCloseQtyByPercent 从 SwapCalc.calcCloseQtyByOriginalPercent 改为直接乘 + /// 6. 后端 InitUnwind 默认 ClosePercent 改为 1 而非剩余比例 + /// + /// 与既有测试的关系: + /// - ApplySwapTradeClosePercentBugTest:测 A→B 转换函数正确性(函数级) + /// - InitUnwindDefaultClosePercentTest:测默认值函数正确性(函数级) + /// - 本测试:测完整多步场景的口径A契约(场景级),补齐"装配测试"盲区 + /// ================================================================================ + [TestClass] + public class ClosePercentProductContractTest + { + // GLMS-20260701-0006 真实数据 + private const decimal OriginalNotional = 50_000_000m; // 期初名义本金(NotionalValue) + private const decimal FirstClosePercentA = 0.35m; // 第一次平仓35%(口径A) + private const decimal RemainingAfter1st = 32_500_000m; // 首次平35%后剩余(PosiNotionalValue) + // 第二次想平掉剩余的 50% → 平仓额=16,250,000 → ClosePercent(A)=1625万/5000万=0.325 + private const decimal SecondCloseNotional = 16_250_000m; + private const decimal SecondClosePercentA = 0.325m; // 口径A:占期初 + private const decimal SecondClosePercentB = 0.50m; // 口径B:占剩余 + + // ================================================================ + // 契约1:ClosePercent = CloseNotionalValue / NotionalValue(口径A) + // 如果有人把分母改成 PosiNotionalValue,此测试会红 + // ================================================================ + [TestMethod] + public void CPC_001_平仓比例必须用期初名义本金为分母_而非剩余名义本金() + { + // 正确:口径A = 平仓名义本金 / 期初名义本金 + decimal correctA = SecondCloseNotional / OriginalNotional; + SwapDealTestFactory.AssertDecimalEqual(SecondClosePercentA, correctA, 1e-10m, + "口径A:16,250,000 / 50,000,000 = 0.325"); + + // 错误:口径B = 平仓名义本金 / 剩余名义本金 + decimal buggyB = SecondCloseNotional / RemainingAfter1st; + SwapDealTestFactory.AssertDecimalEqual(SecondClosePercentB, buggyB, 1e-10m, + "口径B(错误):16,250,000 / 32,500,000 = 0.50"); + + // 两者必须不同——如果相同说明测试场景退化(首次平仓 remaining==original) + Assert.AreNotEqual(correctA, buggyB, + "口径A(0.325) 和 口径B(0.50) 在多次部分平仓后必须不同,否则测试场景退化"); + Console.WriteLine($"口径A={correctA}(正确),口径B={buggyB}(错误)"); + } + + // ================================================================ + // 契约2:CloseNotionalValue = ClosePercent × NotionalValue(口径A) + // 如果有人把乘数改成 PosiNotionalValue,此测试会红 + // ================================================================ + [TestMethod] + public void CPC_002_平仓名义本金必须用期初名义本金为乘数_而非剩余名义本金() + { + // 正确:口径A → CloseNotionalValue = ClosePercent(A) × NotionalValue + decimal correctNotional = SecondClosePercentA * OriginalNotional; + SwapDealTestFactory.AssertDecimalEqual(SecondCloseNotional, correctNotional, 1e-6m, + "口径A:0.325 × 50,000,000 = 16,250,000"); + + // 错误:口径B → CloseNotionalValue = ClosePercent(A) × PosiNotionalValue + // 如果前端传口径A的0.325但误用 PosiNotionalValue 做乘数 + decimal buggyNotional = SecondClosePercentA * RemainingAfter1st; + // 0.325 × 32,500,000 = 10,562,500 ≠ 16,250,000 + Assert.AreNotEqual(SecondCloseNotional, buggyNotional, + "口径A的0.325 × 剩余本金32,500,000 = 10,562,500 ≠ 16,250,000,乘数错了"); + Console.WriteLine($"正确={correctNotional},错误(用剩余)={buggyNotional}"); + } + + // ================================================================ + // 契约3:oriClosePercent = PosiNotionalValue / NotionalValue(不能硬编码为1) + // 如果有人把 oriClosePercent 改成 1,此测试会红 + // ================================================================ + [TestMethod] + public void CPC_003_最多可平比例必须为剩余除以期初_不能硬编码为1() + { + // 正确:oriClosePercent = PosiNotionalValue / NotionalValue + decimal correctOri = RemainingAfter1st / OriginalNotional; + SwapDealTestFactory.AssertDecimalEqual(0.65m, correctOri, 1e-10m, + "oriClosePercent = 32,500,000 / 50,000,000 = 0.65"); + + // 错误:硬编码为 1(c9071a4e 的错误) + decimal buggyOri = 1m; + Assert.AreNotEqual(correctOri, buggyOri, + "多次部分平仓后 oriClosePercent 必须小于 1,硬编码 1 会允许平超过剩余持仓"); + + // 首次平仓时 oriClosePercent 才等于 1(remaining == original) + decimal firstTimeOri = OriginalNotional / OriginalNotional; + SwapDealTestFactory.AssertDecimalEqual(1m, firstTimeOri, 1e-10m, + "首次平仓时 oriClosePercent = 1(remaining == original)"); + Console.WriteLine($"多次部分平仓后:oriClosePercent={correctOri}(≠1),首次:{firstTimeOri}(=1)"); + } + + // ================================================================ + // 契约4:CloseQty 必须经过 A→B 转换,不能直接 PositionQty × ClosePercent(A) + // 如果有人删除 calcCloseQtyByOriginalPercent 调用改为直接乘,此测试会红 + // ================================================================ + [TestMethod] + public void CPC_004_平仓数量必须经过口径A到B转换_不能直接乘() + { + // 场景:期初数量 32,500,000(=剩余数量),oriClosePercent=0.65 + // 用户输入 ClosePercent(A) = 0.325(想平剩余的 50%) + decimal positionQty = RemainingAfter1st; // 32,500,000 + decimal oriClosePercent = RemainingAfter1st / OriginalNotional; // 0.65 + + // 正确:CloseQty = PositionQty × (ClosePercent(A) / oriClosePercent) + // = 32,500,000 × (0.325 / 0.65) = 32,500,000 × 0.5 = 16,250,000 + decimal correctQty = positionQty * (SecondClosePercentA / oriClosePercent); + SwapDealTestFactory.AssertDecimalEqual(16_250_000m, correctQty, 1e-6m, + "正确:32,500,000 × (0.325/0.65) = 16,250,000"); + + // 错误:CloseQty = PositionQty × ClosePercent(A)(直接乘,不做转换) + // = 32,500,000 × 0.325 = 10,562,500 ❌ + decimal buggyQty = positionQty * SecondClosePercentA; + Assert.AreNotEqual(correctQty, buggyQty, + "直接乘会得到 10,562,500 而非 16,250,000,数量算少 35%"); + + // JS 浮点精度守卫:32500000×(0.5/0.65) 可能 = 24999999.999999996 + // SwapCalc.calcCloseQtyByOriginalPercent 用 roundHalfAwayFromZero 修复 + decimal jsFloatTrap = (decimal)((double)positionQty * ((double)SecondClosePercentA / (double)oriClosePercent)); + Console.WriteLine($"正确={correctQty},错误(直接乘)={buggyQty},JS浮点陷阱={jsFloatTrap}"); + } + + // ================================================================ + // 契约5:后端 A→B→A 往返转换必须还原(多次部分平仓场景) + // 守护 SwapUnwind/ApplySwapTrade 入口的 ToRemainingClosePercent + SaveSwapDealInternal 的 ToOriginalClosePercent + // ================================================================ + [TestMethod] + public void CPC_005_多次部分平仓_A到B到A往返转换必须还原原值() + { + // 第二次部分平仓:前端传 ClosePercent(A) = 0.325 + decimal closePercentA = SecondClosePercentA; + + // 后端入口:A → B + decimal closePercentB = SwapDealService.ToRemainingClosePercent( + closePercentA, OriginalNotional, RemainingAfter1st); + SwapDealTestFactory.AssertDecimalEqual(SecondClosePercentB, closePercentB, 1e-10m, + "A→B:0.325 × 50,000,000 / 32,500,000 = 0.50"); + + // 后端落库:B → A 还原 + decimal restoredA = SwapDealService.ToOriginalClosePercent( + closePercentB, OriginalNotional, RemainingAfter1st); + SwapDealTestFactory.AssertDecimalEqual(closePercentA, restoredA, 1e-10m, + "B→A 还原:0.50 × 32,500,000 / 50,000,000 = 0.325(必须等于原始A)"); + + Console.WriteLine($"A={closePercentA} → B={closePercentB} → A'={restoredA} ✅ 往返还原"); + } + + // ================================================================ + // 契约6:后端 InitUnwind 默认 ClosePercent = PosiNotionalValue / NotionalValue(口径A) + // 不能硬编码为 1(c9071a4e 前端硬编码1的错误在后端等价于此) + // ================================================================ + [TestMethod] + public void CPC_006_InitUnwind默认值必须为剩余除以期初_不能硬编码为1() + { + // 多次部分平仓后:期初 50M,剩余 32.5M + decimal defaultValue = SwapDealService.CalcDefaultInitClosePercent( + OriginalNotional, RemainingAfter1st); + + // 正确:0.65(占期初的"平剩余全部"比例) + SwapDealTestFactory.AssertDecimalEqual(0.65m, defaultValue, 1e-10m, + "CalcDefaultInitClosePercent(50M, 32.5M) = 0.65(口径A)"); + + // 不能是 1(硬编码错误) + Assert.AreNotEqual(1m, defaultValue, + "多次部分平仓后默认值不能为1,否则意味着'平掉原始全部'而非'平剩余全部'"); + + Console.WriteLine($"InitUnwind 默认 ClosePercent(A) = {defaultValue}(≠1)✅"); + } + + // ================================================================ + // 契约7:SwapUnwind 第二次部分平仓必须正确做 A→B 转换 + // 这是"装配测试"——验证后端入口确实执行了转换,而不只是函数本身正确 + // ================================================================ + [TestMethod] + public void CPC_007_SwapUnwind第二次部分平仓_入口必须将ClosePercent从A转为B() + { + // 模拟 GLMS-20260701-0006 第二次部分平仓 + var td = new trade + { + id = 2001, + TradeNumber = "CPC-TEST-007", + TradeType = "收益互换", + TradeStatus = "确认成交", + ValidState = "Valid", + StockEqvNotional = (double)RemainingAfter1st, // 32,500,000 + OriginalStockEqvNotional = (double)OriginalNotional, // 50,000,000 + Notional = (double)RemainingAfter1st, + TradeAmount = (double)RemainingAfter1st + }; + + var service = new TestableSwapDealService(td); + + // 前端传 ClosePercent = 0.325(口径A,占期初) + var unwindData = new UnwindData + { + SwapTradeId = td.id, + SwapRealizedPnL = 1000m, + SwapCloseAmount = 1000m, + CloseMethod = (int)CloseMethodEnum.部分平仓, + ClosePercent = SecondClosePercentA, // 0.325(口径A) + CloseQty = 16_250_000m, + CloseNotionalValue = SecondCloseNotional, // 16,250,000 + PositionQty = RemainingAfter1st, // 32,500,000 + NotionalValue = OriginalNotional, // 50,000,000(期初) + PosiNotionalValue = RemainingAfter1st, // 32,500,000(剩余) + ValueDate = new DateTime(2026, 7, 14), + UnwindDate = new DateTime(2026, 7, 15), + StartDate = new DateTime(2026, 7, 1) + }; + + service.SwapUnwind(unwindData); + + // 验证 SwapUnwind 被调用 + Assert.AreEqual(1, service.SaveSwapDealCalls.Count, "SwapUnwind 应调用 SaveSwapDeal"); + + // 验证传给 SaveSwapDeal 的 ClosePercent 已转为口径B + var savedData = service.SaveSwapDealCalls[0].data; + decimal expectedB = SwapDealService.ToRemainingClosePercent( + SecondClosePercentA, OriginalNotional, RemainingAfter1st); + + SwapDealTestFactory.AssertDecimalEqual(expectedB, savedData.ClosePercent, 1e-10m, + "SwapUnwind 应将 ClosePercent 从口径A(0.325)转为口径B(0.50)"); + + // 口径B 不等于口径A(验证转换确实发生了) + Assert.AreNotEqual(SecondClosePercentA, savedData.ClosePercent, + "口径B(0.50) 不应等于口径A(0.325),否则说明转换缺失"); + + Console.WriteLine($"SwapUnwind: 输入A={SecondClosePercentA} → 输出B={savedData.ClosePercent} ✅"); + } + + // ================================================================ + // 契约8:完整多步场景——3次平仓合计应等于期初本金 + // 首次35%(A) → 第二次平剩余50%(A=0.325) → 第三次全平剩余(A=0.325) + // 合计 CloseNotionalValue = 17.5M + 16.25M + 16.25M = 50M = 原始本金 + // ================================================================ + [TestMethod] + public void CPC_008_三次部分平仓合计本金必须等于期初名义本金() + { + decimal remaining = OriginalNotional; // 50,000,000 + decimal totalClosed = 0m; + + // Step1: 平 35%(首次,remaining == original, A = B = 0.35) + decimal step1A = 0.35m; + decimal step1Notional = step1A * OriginalNotional; // 17,500,000 + totalClosed += step1Notional; + remaining -= step1Notional; // 32,500,000 + + // Step2: 平剩余的 50% → A = 16,250,000 / 50,000,000 = 0.325 + decimal step2Notional = 16_250_000m; + decimal step2A = step2Notional / OriginalNotional; // 0.325 + // 后端 A→B 转换 + decimal step2B = SwapDealService.ToRemainingClosePercent( + step2A, OriginalNotional, remaining); + SwapDealTestFactory.AssertDecimalEqual(0.50m, step2B, 1e-10m, + "Step2: A=0.325 → B=0.50(平剩余50%)"); + totalClosed += step2Notional; + remaining -= step2Notional; // 16,250,000 + + // Step3: 全平剩余 → A = 16,250,000 / 50,000,000 = 0.325 + decimal step3Notional = remaining; + decimal step3A = step3Notional / OriginalNotional; // 0.325 + decimal step3B = SwapDealService.ToRemainingClosePercent( + step3A, OriginalNotional, remaining); + SwapDealTestFactory.AssertDecimalEqual(1.0m, step3B, 1e-10m, + "Step3: A=0.325 → B=1.0(全平剩余)"); + totalClosed += step3Notional; + remaining -= step3Notional; // 0 + + // 守恒:合计 = 期初 + SwapDealTestFactory.AssertDecimalEqual(OriginalNotional, totalClosed, 1e-6m, + "三次平仓合计必须 = 期初名义本金 50,000,000"); + SwapDealTestFactory.AssertDecimalEqual(0m, remaining, 1e-6m, + "三次平仓后剩余必须 = 0"); + + Console.WriteLine($"Step1: A=0.35, Notional=17,500,000"); + Console.WriteLine($"Step2: A=0.325→B=0.50, Notional=16,250,000"); + Console.WriteLine($"Step3: A=0.325→B=1.00, Notional=16,250,000"); + Console.WriteLine($"合计={totalClosed} = 期初{OriginalNotional} ✅"); + } + + // ================================================================ + // 契约9:GetUnwindInterestList 必须接收 notionalValue 和 posiNotionalValue + // 前端 getInterestList 传这两个参数给后端做 A→B 转换 + // 如果前端删掉这两个参数,后端 ToRemainingClosePercent 在 posiNotionalValue=0 时会跳过转换 + // ================================================================ + [TestMethod] + public void CPC_009_ToRemainingClosePercent_PosiNotionalValue为零时跳过转换_前端必须传值() + { + // 模拟前端不传 notionalValue/posiNotionalValue(默认0) + decimal result = SwapDealService.ToRemainingClosePercent( + SecondClosePercentA, notionalValue: 0, posiNotionalValue: 0); + + // posiNotionalValue <= 0 时直接返回原值(不转换) + SwapDealTestFactory.AssertDecimalEqual(SecondClosePercentA, result, 1e-10m, + "posiNotionalValue=0 时不做转换——所以前端必须传 notionalValue/posiNotionalValue"); + + // 正确:前端传值后转换正常 + decimal resultWithValues = SwapDealService.ToRemainingClosePercent( + SecondClosePercentA, OriginalNotional, RemainingAfter1st); + SwapDealTestFactory.AssertDecimalEqual(SecondClosePercentB, resultWithValues, 1e-10m, + "前端传值后:A=0.325 → B=0.50 ✅"); + + // 两者必须不同 + Assert.AreNotEqual(result, resultWithValues, + "传 vs 不传 notionalValue 结果不同——前端必须传,否则利息计算用错口径"); + + Console.WriteLine($"不传值(默认0):{result}(未转换,错误地用A算利息)"); + Console.WriteLine($"传值:{resultWithValues}(正确转换为B)"); + } + + // ================================================================ + // 契约10:首次平仓 A==B(退化场景,不应误报) + // 首次平仓时 NotionalValue == PosiNotionalValue,A=B,转换系数=1 + // 这是既有测试全绿的原因——必须用非退化场景才能捕获回归 + // ================================================================ + [TestMethod] + public void CPC_010_首次平仓A等于B_退化场景_不能作为唯一测试() + { + decimal closePercentA = 0.35m; + decimal firstRemaining = OriginalNotional; // 首次 remaining == original + + decimal convertedB = SwapDealService.ToRemainingClosePercent( + closePercentA, OriginalNotional, firstRemaining); + + // 首次平仓:A == B(转换系数 = 1) + SwapDealTestFactory.AssertDecimalEqual(closePercentA, convertedB, 1e-10m, + "首次平仓 remaining==original → A==B==0.35(退化场景)"); + + // 退化场景下即使不做转换结果也一样——这就是既有测试全绿的原因 + decimal noConversion = closePercentA; + Assert.AreEqual(noConversion, convertedB, + "退化场景:做不做转换结果一样 → 无法发现'转换缺失'的bug"); + + Console.WriteLine($"⚠ 退化场景:A={closePercentA} == B={convertedB}(首次平仓,无法暴露双重转换bug)"); + Console.WriteLine($"✅ 非退化场景见 CPC_005/007:A=0.325 ≠ B=0.50(多次部分平仓后才能暴露)"); + } + } +} diff --git a/YLErpWeb/fe-tests/closePercentContract.test.js b/YLErpWeb/fe-tests/closePercentContract.test.js new file mode 100644 index 00000000..2addeb24 --- /dev/null +++ b/YLErpWeb/fe-tests/closePercentContract.test.js @@ -0,0 +1,168 @@ +/** + * closePercentContract.test.js — 平仓比例口径A(占期初名义本金)前端接线守卫 + * ============================================================================ + * 产品需求(不可违背):ClosePercent 永远是"占期初名义本金(NotionalValue)的比例",即口径A。 + * + * 本文件是"接线测试"(wiring test):读 unwindSwapTrade.js 源码文本,断言关键逻辑 + * 仍然使用口径A的公式。如果有人把公式改成口径B(如 c9071a4e 曾犯的错误), + * 对应断言会立即变红。 + * + * 与 swapCalc.test.js 的区别: + * swapCalc.test.js 测 SwapCalc 纯函数本身正确性(零件级) + * 本文件测 unwindSwapTrade.js 确实在调用这些函数/使用正确公式(装配级) + * ============================================================================ + */ +const fs = require('fs'); +const path = require('path'); + +const unwindSrc = fs.readFileSync( + path.join(__dirname, '..', 'wwwroot', 'Scripts', 'app', 'swaptrade', 'unwindSwapTrade.js'), + 'utf8' +); + +describe('口径A产品契约:unwindSwapTrade.js 接线守卫', () => { + + // ==================================================================== + // 契约1:oriClosePercent 必须用 PosiNotionalValue / NotionalValue 计算 + // 不能硬编码为 1(c9071a4e 的错误) + // ==================================================================== + describe('oriClosePercent 必须为剩余/期初', () => { + test('源码中 oriClosePercent 必须包含 PosiNotionalValue / NotionalValue 公式', () => { + // 正确代码:this.oriClosePercent = ... PosiNotionalValue / ... NotionalValue + expect(unwindSrc).toMatch(/oriClosePercent.*PosiNotionalValue.*\/.*NotionalValue/s); + }); + + test('源码中 oriClosePercent 不能被硬编码为 1', () => { + // c9071a4e 的错误:this.deal.ClosePercent = 1 (直接覆盖) + // 检查 initDeal 中不存在 oriClosePercent = 1 的硬编码 + const initDealSection = unwindSrc.match(/initDeal\(\)[\s\S]*?\},/); + expect(initDealSection).toBeTruthy(); + // 不应出现 oriClosePercent = 1 或 ClosePercent = 1 的硬编码 + //(CloseMethod===1 时设置 ClosePercent=1 是允许的,但 oriClosePercent 不应被设为1) + expect(initDealSection[0]).not.toMatch(/oriClosePercent\s*=\s*1\b/); + }); + }); + + // ==================================================================== + // 契约2:calcCloseQtyByPercent 必须调用 SwapCalc.calcCloseQtyByOriginalPercent + // 不能直接 PositionQty × ClosePercent(c9071a4e 的错误) + // ==================================================================== + describe('calcCloseQtyByPercent 必须调用 SwapCalc', () => { + test('源码中 calcCloseQtyByPercent 必须调用 SwapCalc.calcCloseQtyByOriginalPercent', () => { + expect(unwindSrc).toContain('SwapCalc.calcCloseQtyByOriginalPercent'); + }); + + test('changeCloseMethod 的部分平仓分支必须调用 calcCloseQtyByPercent', () => { + // 正确代码:this.deal.CloseQty = this.calcCloseQtyByPercent(this.deal.ClosePercent); + expect(unwindSrc).toMatch(/calcCloseQtyByPercent\s*\(this\.deal\.ClosePercent\)/); + }); + + test('changeClosePercent 必须调用 calcCloseQtyByPercent', () => { + expect(unwindSrc).toMatch(/this\.deal\.CloseQty\s*=\s*this\.calcCloseQtyByPercent/); + }); + + test('changeCloseNotionalValue 必须调用 calcCloseQtyByPercent', () => { + expect(unwindSrc).toMatch(/this\.deal\.CloseQty\s*=\s*this\.calcCloseQtyByPercent/); + }); + }); + + // ==================================================================== + // 契约3:CloseNotionalValue 必须用 NotionalValue 做乘数/分母(口径A) + // 不能用 PosiNotionalValue(c9071a4e 的错误) + // ==================================================================== + describe('CloseNotionalValue 必须基于 NotionalValue(口径A)', () => { + test('changeCloseQty: CloseNotionalValue = ClosePercent × NotionalValue', () => { + // 正确:ClosePercent × parseFloat(this.deal.NotionalValue) + // 错误:ClosePercent × parseFloat(this.deal.PosiNotionalValue) + const changeCloseQtySection = unwindSrc.match(/changeCloseQty\(\)[\s\S]*?\n\s*\},/); + expect(changeCloseQtySection).toBeTruthy(); + expect(changeCloseQtySection[0]).toMatch(/CloseNotionalValue.*NotionalValue/); + expect(changeCloseQtySection[0]).not.toMatch(/CloseNotionalValue.*PosiNotionalValue/); + }); + + test('changeClosePercent: CloseNotionalValue = ClosePercent × NotionalValue', () => { + const changeClosePercentSection = unwindSrc.match(/changeClosePercent\(\)[\s\S]*?\n\s*\},/); + expect(changeClosePercentSection).toBeTruthy(); + expect(changeClosePercentSection[0]).toMatch(/CloseNotionalValue.*NotionalValue/); + expect(changeClosePercentSection[0]).not.toMatch(/CloseNotionalValue.*PosiNotionalValue/); + }); + }); + + // ==================================================================== + // 契约4:changeCloseQty 的 ClosePercent 必须乘以 oriClosePercent(口径A→B→A 转换) + // 不能直接 CloseQty / PositionQty(c9071a4e 的错误) + // ==================================================================== + describe('changeCloseQty 的 ClosePercent 必须乘以 oriClosePercent', () => { + test('ClosePercent = (CloseQty/PositionQty) × oriClosePercent', () => { + // 正确:× ori(把占剩余比例转回占期初口径) + // 错误:不乘 ori(直接用占剩余比例作为 ClosePercent) + const changeCloseQtySection = unwindSrc.match(/changeCloseQty\(\)[\s\S]*?\n\s*\},/); + expect(changeCloseQtySection).toBeTruthy(); + expect(changeCloseQtySection[0]).toMatch(/oriClosePercent/); + expect(changeCloseQtySection[0]).toMatch(/\*\s*ori/); + }); + + test('changeCloseNotionalValue 的 ClosePercent 必须除以 NotionalValue', () => { + // 正确:ClosePercent = CloseNotionalValue / NotionalValue + // 错误:ClosePercent = CloseNotionalValue / PosiNotionalValue + const changeCloseNotionalSection = unwindSrc.match(/changeCloseNotionalValue\(\)[\s\S]*?\n\s*\},/); + expect(changeCloseNotionalSection).toBeTruthy(); + expect(changeCloseNotionalSection[0]).toMatch(/ClosePercent.*NotionalValue/); + expect(changeCloseNotionalSection[0]).not.toMatch(/ClosePercent.*PosiNotionalValue/); + }); + }); + + // ==================================================================== + // 契约5:getInterestList 必须传 notionalValue 和 posiNotionalValue 给后端 + // 后端 GetUnwindInterestList 需要这两个值做 A→B 转换 + // 如果删掉(c9071a4e 的错误),后端不转换,利息用错口径计算 + // ==================================================================== + describe('getInterestList 必须传 notionalValue/posiNotionalValue', () => { + test('postData 必须包含 notionalValue', () => { + expect(unwindSrc).toMatch(/notionalValue:\s*thisObj\.deal\.NotionalValue/); + }); + + test('postData 必须包含 posiNotionalValue', () => { + expect(unwindSrc).toMatch(/posiNotionalValue:\s*thisObj\.deal\.PosiNotionalValue/); + }); + + test('getInterestList 的 postData 不能只有 closePercent 而缺少 notionalValue', () => { + // 精确匹配 getInterestList 方法定义(以 getInterestList() { 开头,到 main.post 结束) + // 匹配模式:方法名+参数列表+花括号开始,一直到包含 main.post 的 postData 定义 + const methodMatch = unwindSrc.match( + /getInterestList\(\)\s*\{[\s\S]*?var\s+postData\s*=\s*\{[^}]*\}/ + ); + expect(methodMatch).toBeTruthy(); + const postData = methodMatch[0]; + expect(postData).toContain('notionalValue'); + expect(postData).toContain('posiNotionalValue'); + }); + }); + + // ==================================================================== + // 契约6:CloseMethod 赋值对象必须是 deal(不是 floatPosition) + // c9071a4e 在 changeClosePercent 中误赋值到 floatPosition.CloseMethod + // ==================================================================== + describe('CloseMethod 必须赋值给 deal', () => { + test('changeClosePercent 的 CloseMethod 必须赋值给 this.deal', () => { + const changeClosePercentSection = unwindSrc.match(/changeClosePercent\(\)[\s\S]*?\n\s*\},/); + expect(changeClosePercentSection).toBeTruthy(); + expect(changeClosePercentSection[0]).toMatch(/this\.deal\.CloseMethod\s*=/); + expect(changeClosePercentSection[0]).not.toMatch(/this\.floatPosition\.CloseMethod\s*=/); + }); + + test('changeCloseNotionalValue 的 CloseMethod 必须赋值给 this.deal', () => { + const changeCloseNotionalSection = unwindSrc.match(/changeCloseNotionalValue\(\)[\s\S]*?\n\s*\},/); + expect(changeCloseNotionalSection).toBeTruthy(); + expect(changeCloseNotionalSection[0]).toMatch(/this\.deal\.CloseMethod\s*=/); + expect(changeCloseNotionalSection[0]).not.toMatch(/this\.floatPosition\.CloseMethod\s*=/); + }); + + test('changeCloseNotionalValue 必须设置 CloseMethod(不能删除)', () => { + // c9071a4e 完全删除了 changeCloseNotionalValue 中的 CloseMethod 判断 + const changeCloseNotionalSection = unwindSrc.match(/changeCloseNotionalValue\(\)[\s\S]*?\n\s*\},/); + expect(changeCloseNotionalSection).toBeTruthy(); + expect(changeCloseNotionalSection[0]).toMatch(/CloseMethod/); + }); + }); +}); From 89da38fab9a453f89a653c1435242b7820dc726b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Mon, 27 Jul 2026 13:35:16 +0800 Subject: [PATCH 16/22] =?UTF-8?q?refactor(swap):=20=E4=BA=92=E6=8D=A2?= =?UTF-8?q?=E5=BC=80=E5=B9=B3=E4=BB=93=E4=BA=8B=E4=BB=B6=E6=B5=81=E6=B0=B4?= =?UTF-8?q?=20=E5=AF=BC=E5=87=BA=E5=B0=8F=E6=95=B0=E7=82=B9=E7=B2=BE?= =?UTF-8?q?=E5=BA=A6=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将InitYtm属性从decimal?类型更改为string类型 - 调整TradingAmountAvg、TradingAmountFeeAvg和Quantity字段的小数位数从4位改为2位 - 修改InitYtm的赋值逻辑,添加空值检查并应用OtcFormatMoney格式化方法 - 统一货币金额格式化参数设置,移除分组符号并指定小数位数 --- YLErpDAL/Model/SwapFlowEventExportModel.cs | 2 +- YLErpDAL/Modules/SwapModule/SwapFlowService.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/YLErpDAL/Model/SwapFlowEventExportModel.cs b/YLErpDAL/Model/SwapFlowEventExportModel.cs index d710bf22..d37cd6d0 100644 --- a/YLErpDAL/Model/SwapFlowEventExportModel.cs +++ b/YLErpDAL/Model/SwapFlowEventExportModel.cs @@ -99,6 +99,6 @@ namespace YLErp.Model public string OptLog { get; set; } - public decimal? InitYtm { get; set; } + public string InitYtm { get; set; } } } diff --git a/YLErpDAL/Modules/SwapModule/SwapFlowService.cs b/YLErpDAL/Modules/SwapModule/SwapFlowService.cs index 30a40623..f31bec68 100644 --- a/YLErpDAL/Modules/SwapModule/SwapFlowService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapFlowService.cs @@ -372,9 +372,9 @@ namespace YLErp.Modules.SwapModule exportModel.PositionType = item.PositionType==1?"多头":"空头"; exportModel.UnderlyingCode = item.UnderlyingCode; exportModel.MatuirityDate = item.MatuirityDate.OtcFormatDate(); - exportModel.TradingAmountAvg = item.TradingAmountAvg.OtcFormatMoney(grouping: false); - exportModel.TradingAmountFeeAvg = item.TradingAmountFeeAvg.OtcFormatMoney(grouping: false); - exportModel.Quantity = item.Quantity.OtcFormatMoney(false, 4); + exportModel.TradingAmountAvg = item.TradingAmountAvg.OtcFormatMoney(false, 2); + exportModel.TradingAmountFeeAvg = item.TradingAmountFeeAvg.OtcFormatMoney(false, 2); + exportModel.Quantity = item.Quantity.OtcFormatMoney(false, 2); exportModel.TradingAmount = item.TradingAmount.OtcFormatMoney(false, 2); exportModel.ContractSize = item.ContractSize.ToString(); exportModel.TradingFee = item.TradingFee.OtcFormatMoney(false, 2); @@ -383,7 +383,7 @@ namespace YLErp.Modules.SwapModule exportModel.MarkClosePnl = item.MarkClosePnl.OtcFormatMoney(false, 2); exportModel.DividendIn = item.DividendIn.OtcFormatMoney(false, 2); exportModel.OptLog = item.OptLog; - exportModel.InitYtm = item.InitYtm; + exportModel.InitYtm = item.InitYtm?.OtcFormatMoney(false, 9); list.Add(exportModel); } var tplFilePath = OtcAppContext.MapPath("/App_Docs"); From 9bff19803843837ab0e66a2e6631fc118b7f7484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Mon, 27 Jul 2026 14:08:10 +0800 Subject: [PATCH 17/22] =?UTF-8?q?fix:=20=E9=99=90=E9=A2=9D=E7=9B=91?= =?UTF-8?q?=E6=8E=A7=E5=B0=8F=E6=95=B0=E7=82=B9=E7=B2=BE=E5=BA=A6=E8=B0=83?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wwwroot/Scripts/app/risk/quotaMonitor.js | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/YLErpWeb/wwwroot/Scripts/app/risk/quotaMonitor.js b/YLErpWeb/wwwroot/Scripts/app/risk/quotaMonitor.js index 4c159554..4d6328a9 100644 --- a/YLErpWeb/wwwroot/Scripts/app/risk/quotaMonitor.js +++ b/YLErpWeb/wwwroot/Scripts/app/risk/quotaMonitor.js @@ -361,6 +361,16 @@ function formatQuotaAbs(obj, fieldName) { return html; } +function formatFixedTwoDecimals(cellvalue) { + if (cellvalue === null || cellvalue === undefined || cellvalue === '' || cellvalue === 'NaN') { + return ''; + } + var numberValue = Number(cellvalue); + return isFinite(numberValue) + ? numberValue.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + : ''; +} + function formatQuotaRef(obj, fieldName) { var html = ""; var upperValue = obj["Quota_" + fieldName + "_Upper"]; @@ -544,8 +554,21 @@ function quotaMonitorUploadForm(url) { } function Output() { - var fileName = new moment().format("YYYYMMDD") + $('#myTab .active a').text(); - main.toExcel("listGrid", fileName, "xls", null, [" ", "操作"]); + var summaryType = $('#myTab .active a').text(); + var fileName = new moment().format("YYYYMMDD") + summaryType; + var formatters = null; + if (summaryType === '整体业务汇总' || summaryType === '标的汇总' || summaryType === '客户汇总') { + formatters = [{ + colName: '名义本金', + formatter: function (cellvalue, options, rowObject) { + if (summaryType === '整体业务汇总' && rowObject.BusinessType === '场内业务') { + return ''; + } + return formatFixedTwoDecimals(cellvalue); + } + }]; + } + main.toExcel("listGrid", fileName, "xls", null, [" ", "操作"], formatters); } function confirmAllSelect() { @@ -1090,7 +1113,7 @@ var colModel_undelrying = [ align: 'right', sortable: false, formatter: function (cellvalue, options, rowObject) { - return !cellvalue || cellvalue == "NaN" ? "" : cellvalue.toLocaleString(); + return formatFixedTwoDecimals(cellvalue); }, cellattr: function (cellvalue, options, rowObject) { var style = checkQuota(rowObject, 'StockEqvNotional'); @@ -1507,7 +1530,7 @@ var colModel_client = [ align: 'right', sortable: false, formatter: function (cellvalue, options, rowObject) { - return !cellvalue || cellvalue == "NaN" ? "" : cellvalue.toLocaleString(); + return formatFixedTwoDecimals(cellvalue); }, cellattr: function (cellvalue, options, rowObject) { var style = "style='" + checkQuota(rowObject, 'StockEqvNotional') + "'"; @@ -1658,10 +1681,7 @@ var colModel_global = [ if (rowObject.BusinessType == "场内业务") { return '
'; } - if (page.IsGuoXin && cellvalue != null) { - return cellvalue == "NaN" ? "" : cellvalue.toLocaleString(); - } - return !cellvalue || cellvalue == "NaN" ? "" : cellvalue.toLocaleString(); + return formatFixedTwoDecimals(cellvalue); }, cellattr: function (cellvalue, options, rowObject) { var style = checkQuota(rowObject, 'StockEqvNotional'); @@ -2132,4 +2152,4 @@ var colModel_Log = [ align: 'center', sortable: false } -]; \ No newline at end of file +]; From ba40ab27e8bd46247739499a6c57ee2a4adc036a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Mon, 27 Jul 2026 14:33:22 +0800 Subject: [PATCH 18/22] =?UTF-8?q?fix(grid):=20=E6=83=85=E6=99=AF=E5=88=86?= =?UTF-8?q?=E6=9E=90=20=E8=A1=8C=E6=9D=83=E4=BB=B7=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除冗余的空值检查逻辑,统一处理null、undefined、空字符串和NaN值 - 简化行权价格式化算法,直接基于单元格值进行数值转换 - 根据IsMoneynessOption字段决定返回百分比或数值格式 - 修复边界条件处理,确保数据展示的一致性 - 删除多余的测试状态变量相关代码 --- .../Scripts/app/scenario/tradeScenario.js | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/YLErpWeb/wwwroot/Scripts/app/scenario/tradeScenario.js b/YLErpWeb/wwwroot/Scripts/app/scenario/tradeScenario.js index ccdd4c86..c30a7782 100644 --- a/YLErpWeb/wwwroot/Scripts/app/scenario/tradeScenario.js +++ b/YLErpWeb/wwwroot/Scripts/app/scenario/tradeScenario.js @@ -13,20 +13,12 @@ const colModelGrid = (function () { } function StrikeFormatter(cellValue, options, rowObject) { - if (rowObject.Strike) { - if (rowObject.IsMoneynessOption === "是") { - return otcformat.trading.premiumRateP(rowObject.Strike); - } else { - return otcformat.trading.umprice(rowObject.Strike); - } - } else { - if (rowObject.Strike === 0) { - return 0; - } - else { - return ""; - } - } + if (cellValue === null || cellValue === undefined || cellValue === '' || cellValue === 'NaN') return ''; + var strike = Number(cellValue); + if (!isFinite(strike)) return ''; + return rowObject.IsMoneynessOption === "是" + ? (strike * 100).toFixed(2) + '%' + : strike.toFixed(2); } var col = [ @@ -273,4 +265,4 @@ function setTestValue(list) { testStstus = false; main.alert("计算完成"); } -} \ No newline at end of file +} From ae5f10124f2d276169d6766e15a4c92b40cb5f90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Mon, 27 Jul 2026 14:46:01 +0800 Subject: [PATCH 19/22] =?UTF-8?q?fix(risk):=20=E9=A3=8E=E9=99=A9=E9=A2=84?= =?UTF-8?q?=E8=AD=A6/=E7=A6=81=E6=AD=A2=E6=9F=A5=E8=AF=A2=20=E5=B0=8F?= =?UTF-8?q?=E6=95=B0=E7=82=B9=E7=B2=BE=E5=BA=A6=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 otcformat.trading.umprice 替换为 EventTwoDecimalFormat 格式化器 - 新增 EventTwoDecimalFormat 函数处理数值格式化为两位小数 - 优化 StrikeFormatter 函数的价格格式化逻辑 - 统一价格显示格式为两位小数的标准格式 - 修复空值和无效数值的处理机制 --- .../Scripts/app/risk/SubstandardTrade.js | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/YLErpWeb/wwwroot/Scripts/app/risk/SubstandardTrade.js b/YLErpWeb/wwwroot/Scripts/app/risk/SubstandardTrade.js index c351e43c..02f7f83d 100644 --- a/YLErpWeb/wwwroot/Scripts/app/risk/SubstandardTrade.js +++ b/YLErpWeb/wwwroot/Scripts/app/risk/SubstandardTrade.js @@ -246,7 +246,7 @@ function getColModelDefault() { width: 85, align: 'center', sortable: false, - formatter: otcformat.trading.umprice + formatter: EventTwoDecimalFormat }, { name: 'TradeType2', @@ -374,7 +374,7 @@ function getColModelDefault() { width: 65, align: 'center', sortable: false, - formatter: otcformat.trading.umprice + formatter: EventTwoDecimalFormat }); return tradeHelper.getAmountToNotional(colModelGrid); @@ -388,6 +388,14 @@ function ShowStructFormater(cellValue, options, rowObject) { return cellValue || ''; } +function EventTwoDecimalFormat(cellValue) { + if (cellValue === null || cellValue === undefined || cellValue === '') { + return ''; + } + var price = Number(cellValue); + return isFinite(price) ? price.toFixed(2) : cellValue; +} + function StrikeFormatter(cellValue, options, rowObject) { if (rowObject.TradeType === "自定义交易") { return ""; @@ -396,20 +404,12 @@ function StrikeFormatter(cellValue, options, rowObject) { return "--"; } - if (rowObject.Strike) { - if (rowObject.IsMoneynessOption === "是") { - return otcformat.trading.premiumRateP(rowObject.Strike); - } else { - return otcformat.trading.umprice(rowObject.Strike); - } - } else { - if (rowObject.Strike === 0) { - return otcformat.trading.umprice(0); - } - else { - return ""; - } - } + if (cellValue === null || cellValue === undefined || cellValue === '' || cellValue === 'NaN') return ''; + var strike = Number(cellValue); + if (!isFinite(strike)) return ''; + return rowObject.IsMoneynessOption === "是" + ? (strike * 100).toFixed(2) + '%' + : EventTwoDecimalFormat(strike); } function CommissionFormatter(cellValue, options, rowObject) { @@ -651,4 +651,4 @@ function showcolumnChooser() { function getColModel() { return getColModelDefault(); -} \ No newline at end of file +} From 63f12725c5a5beea6bf175e010e0ed2fbabd8b66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Mon, 27 Jul 2026 14:52:39 +0800 Subject: [PATCH 20/22] =?UTF-8?q?fix(entryexit):=20=E8=B5=84=E9=87=91?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=20=E9=87=91=E9=A2=9D=20=E5=B0=8F=E6=95=B0?= =?UTF-8?q?=E7=82=B9=E7=B2=BE=E5=BA=A6=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将数值格式从三位小数调整为两位小数 - 统一正负数值的格式化处理逻辑 - 修复 NaN 判断条件下的格式化显示问题 --- YLErpWeb/Views/entryexit/entryexitList.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/YLErpWeb/Views/entryexit/entryexitList.cshtml b/YLErpWeb/Views/entryexit/entryexitList.cshtml index aaafc651..ca20dcc2 100644 --- a/YLErpWeb/Views/entryexit/entryexitList.cshtml +++ b/YLErpWeb/Views/entryexit/entryexitList.cshtml @@ -355,7 +355,7 @@ return numeral(-1 * cellValue).format("0,0.00") === 'NaN' ? "0" : numeral(-1 * cellValue).format("0,0.00"); } else { - return numeral(cellValue).format("0,0.000") === 'NaN' ? "0" : numeral(cellValue).format("0,0.000"); + return numeral(cellValue).format("0,0.00") === 'NaN' ? "0" : numeral(cellValue).format("0,0.00"); } } From d0ac5c02452480091f07d1c3ede1f1dce40f94de Mon Sep 17 00:00:00 2001 From: hjhan Date: Mon, 27 Jul 2026 15:16:12 +0800 Subject: [PATCH 21/22] =?UTF-8?q?fix(swaptrade):=20=E6=81=A2=E5=A4=8D?= =?UTF-8?q?=E5=80=BA=E5=88=B8=E4=B8=89=E5=AD=97=E6=AE=B5=E8=A2=AB=E5=90=88?= =?UTF-8?q?=E5=B9=B6=E9=9D=99=E9=BB=98=E4=B8=A2=E5=BC=83=E7=9A=84=E5=BE=BD?= =?UTF-8?q?=E6=A0=87(=E6=BA=90/=E6=89=8B=E5=8A=A8=E7=BC=96=E8=BE=91/?= =?UTF-8?q?=E9=87=8D=E7=AE=97)=E3=80=82=E4=BF=AE=E5=A4=8D=20dotnumber=20?= =?UTF-8?q?=E5=90=88=E5=B9=B6(32af85d3)=E4=B8=89=E8=B7=AF=E5=86=B2?= =?UTF-8?q?=E7=AA=81=E6=8C=89=20theirs=20=E8=A7=A3=E5=86=B3=E6=97=B6?= =?UTF-8?q?=E6=97=A0=E5=86=B2=E7=AA=81=E6=A0=87=E8=AE=B0=E5=8D=B4=E9=9D=99?= =?UTF-8?q?=E9=BB=98=E4=B8=A2=E5=BC=83=E9=87=8D=E7=AE=97=E9=93=BE=E6=8E=A5?= =?UTF-8?q?/=E6=BA=90=E5=BE=BD=E6=A0=87/=E6=89=8B=E5=8A=A8=E9=93=85?= =?UTF-8?q?=E7=AC=94=E5=9B=BE=E6=A0=87=EF=BC=9BJS=20=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E6=9C=AC=E5=B0=B1=E5=AE=8C=E5=A5=BD=EF=BC=8C=E4=BB=85=E7=95=8C?= =?UTF-8?q?=E9=9D=A2=E5=8F=AF=E8=A7=86=E5=8C=96=E6=8C=87=E7=A4=BA=E5=9B=9E?= =?UTF-8?q?=E5=BD=92=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml b/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml index 0eaf1224..2e707b9c 100644 --- a/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml +++ b/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml @@ -474,13 +474,13 @@ - + - + - + 重算 From b9f0d9d99d7f579655f868ad2a0de8d1bde11afe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Mon, 27 Jul 2026 15:23:27 +0800 Subject: [PATCH 22/22] =?UTF-8?q?fix=EF=BC=9A=E7=BB=93=E7=AE=97=E7=A1=AE?= =?UTF-8?q?=E8=AE=A4=E4=B9=A6=20=E4=BB=BD=E9=A2=9D=20=E7=BB=93=E7=AE=97?= =?UTF-8?q?=E4=BB=B7=20=20=E5=B0=8F=E6=95=B0=E7=82=B9=E4=BD=8D=E6=95=B0?= =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- YLErpWeb/wwwroot/Scripts/app/swaptrade/tradeEndConfirmList.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/tradeEndConfirmList.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/tradeEndConfirmList.js index 6f82f664..f6e515c6 100644 --- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/tradeEndConfirmList.js +++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/tradeEndConfirmList.js @@ -423,9 +423,9 @@ const colModelGrid = (new function () { }, { name: 'trade.ClientName', label: '客户名称', index: 'trade.ClientName', width: 180, align: 'left' }, { - name: 'swap_flow_event.Quantity', label: '份额', index: 'swap_flow_event.Quantity', width: 150, align: 'left', formatter: ShowNotionalFormater + name: 'swap_flow_event.Quantity', label: '份额', index: 'swap_flow_event.Quantity', width: 150, align: 'left', formatter: otcformat.fixed2 }, { - name: 'swap_flow_event.TradingAmountAvg', label: '结算价', index: 'swap_flow_event.TradingAmountAvg', width: 150, align: 'left', formatter: otcformat.trading.umprice + name: 'swap_flow_event.TradingAmountAvg', label: '结算价', index: 'swap_flow_event.TradingAmountAvg', width: 150, align: 'left', formatter: otcformat.fixed2 }, { name: 'swap_event.unwindData.SwapCloseAmount', label: '了结总额', index: 'swap_event.unwindData.SwapCloseAmount', width: 150, align: 'left', formatter: otcformat.trading.StockEqvNotional, sortable: false, }, {