diff --git a/Framework/YLErp.Core/DBModels/underlying_manager.cs b/Framework/YLErp.Core/DBModels/underlying_manager.cs index 2808b5f5..b1187b6e 100644 --- a/Framework/YLErp.Core/DBModels/underlying_manager.cs +++ b/Framework/YLErp.Core/DBModels/underlying_manager.cs @@ -137,6 +137,20 @@ namespace YLErp.DBModels [DisplayName("标的名称")] public string UnderlyingName { set; get; } + /// + /// 基金及基金专户的基金管理人名称 + /// + [Column("investadvisorname")] + [DisplayName("基金管理人")] + public string InvestAdvisorName { get; set; } + + /// + /// 基金及基金专户所属的 ETF 子类,保存“ETF 子类”字典项名称 + /// + [Column("etf_sub_type")] + [DisplayName("ETF 子类")] + public string EtfSubType { get; set; } + /// /// 标的英文名 diff --git a/Framework/YLErp.Resources/Dictionary/db_dictionaries.xml b/Framework/YLErp.Resources/Dictionary/db_dictionaries.xml index 97770296..b56ab947 100644 --- a/Framework/YLErp.Resources/Dictionary/db_dictionaries.xml +++ b/Framework/YLErp.Resources/Dictionary/db_dictionaries.xml @@ -42,4 +42,17 @@ + + + + + + + + + + + + + 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 573b1546..030e6c1c 100644 Binary files a/Plugins/YLErp.Plugins.GuoLian/App_Docs/contract_template/国联民生-收益互换交易确认书-境内模板-【客户看多】-【债券ETF】-清洁版.docx and b/Plugins/YLErp.Plugins.GuoLian/App_Docs/contract_template/国联民生-收益互换交易确认书-境内模板-【客户看多】-【债券ETF】-清洁版.docx differ 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 f5034526..c6da85b2 100644 Binary files a/Plugins/YLErp.Plugins.GuoLian/App_Docs/contract_template/国联民生-收益互换交易确认书-境内模板-【客户看空】-【债券ETF】-清洁版.docx and b/Plugins/YLErp.Plugins.GuoLian/App_Docs/contract_template/国联民生-收益互换交易确认书-境内模板-【客户看空】-【债券ETF】-清洁版.docx differ diff --git a/Plugins/YLErp.Plugins.GuoLian/DocumentGenerator/TradeConfirmationGenerator.cs b/Plugins/YLErp.Plugins.GuoLian/DocumentGenerator/TradeConfirmationGenerator.cs index a4047271..e89a5ed7 100644 --- a/Plugins/YLErp.Plugins.GuoLian/DocumentGenerator/TradeConfirmationGenerator.cs +++ b/Plugins/YLErp.Plugins.GuoLian/DocumentGenerator/TradeConfirmationGenerator.cs @@ -508,7 +508,8 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator dic["参考标的名义份额"] = swapPosition != null ? ((double)swapPosition.PosiQuantity).ToString("0.##") : "0"; - dic["参考标的基金管理人"] = ""; + + dic["参考标的基金管理人"] = underlying?.InvestAdvisorName ?? ""; var contractTypeId = (Context.GetContractTypes().FirstOrDefault(O => O.ContactType == "交易确认书接收")?.id) ?? 0; // 乙方联系人信息 var clientDuties = Context.GetClientDuties().Where(O => O.ContactTypeIdsInt.Contains(contractTypeId)).ToList(); diff --git a/UnitTestProject/Modules/UnderlyingModule/UnderlyingFundManagerMappingTest.cs b/UnitTestProject/Modules/UnderlyingModule/UnderlyingFundManagerMappingTest.cs new file mode 100644 index 00000000..6825c5ac --- /dev/null +++ b/UnitTestProject/Modules/UnderlyingModule/UnderlyingFundManagerMappingTest.cs @@ -0,0 +1,20 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations.Schema; +using System.Reflection; + +namespace YLErp.UnitTestProject.Modules.UnderlyingModule +{ + [TestClass] + public class UnderlyingFundManagerMappingTest + { + [TestMethod] + public void InvestAdvisorName_MapsExistingFundManagerColumn() + { + var property = typeof(underlying_manager).GetProperty("InvestAdvisorName"); + + Assert.IsNotNull(property, "underlying_manager 应公开基金管理人属性 InvestAdvisorName"); + Assert.AreEqual("investadvisorname", property.GetCustomAttribute()?.Name); + Assert.AreEqual("基金管理人", property.GetCustomAttribute()?.DisplayName); + } + } +} diff --git a/YLErpDAL/Modules/AppModule/AppUpgrader.cs b/YLErpDAL/Modules/AppModule/AppUpgrader.cs index 583afcb1..8fe40d72 100644 --- a/YLErpDAL/Modules/AppModule/AppUpgrader.cs +++ b/YLErpDAL/Modules/AppModule/AppUpgrader.cs @@ -423,6 +423,8 @@ namespace YLErp.Modules.AppModule var exists = adminDb.Dictionaries.Select(n => n.Name).ToArray() .Select(n => n.Trim()).ToHashSet(); + // 记录本次新建的字典,确保初始项只写入一次,不覆盖后续人工维护结果。 + var addedDictionaryNames = new HashSet(); var root = XElement.Parse(YLErp.Resources.Properties.Resources.db_dictionaries); var itemsAll = root.Elements(); @@ -450,10 +452,51 @@ namespace YLErp.Modules.AppModule Name = name, Catalog = catalog }); + addedDictionaryNames.Add(name); } adminDb.SaveChanges(); + // 重点功能:解析 XML 中的初始项,仅为本次首次创建的字典生成 DictionaryItem。 + foreach (var dictionaryElement in itemsAll.Where(item => + item.Elements().Any() && addedDictionaryNames.Contains(item.Attribute("name")?.Value.TrimToEmpty()))) + { + var dictionaryName = dictionaryElement.Attribute("name")?.Value.TrimToNull(); + var dictionary = adminDb.Dictionaries.FirstOrDefault(item => item.Name == dictionaryName); + if (dictionary == null) + { + continue; + } + + var existingItemNames = adminDb.DictionaryItems + .Where(item => item.DictId == dictionary.Id) + .Select(item => item.Name) + .ToHashSet(); + var nextDictionaryItemIndex = adminDb.DictionaryItems + .Where(item => item.DictId == dictionary.Id) + .Max(item => (int?)item.IndexNum) ?? -1; + + // XML 中的排列顺序写入 IndexNum,页面下拉按该顺序展示。 + foreach (var itemElement in dictionaryElement.Elements()) + { + var itemName = itemElement.Attribute("name")?.Value.TrimToNull(); + if (itemName == null || existingItemNames.Contains(itemName)) + { + continue; + } + + adminDb.DictionaryItems.Add(new BaseOUDAL.DictionaryItem + { + DictId = dictionary.Id, + Name = itemName, + ShortName = itemElement.Attribute("short_name")?.Value.TrimToNull() ?? itemName, + IndexNum = ++nextDictionaryItemIndex + }); + existingItemNames.Add(itemName); + } + } + adminDb.SaveChanges(); + var marginTemplateDictionary = adminDb.Dictionaries.FirstOrDefault(item => item.Name == YLErp.Modules.SwapModule.SwapMarginTemplateConfigService.DictionaryName); if (marginTemplateDictionary == null) { diff --git a/YLErpWeb/Controllers/underlying_managerController.cs b/YLErpWeb/Controllers/underlying_managerController.cs index 851b449b..60dda498 100644 --- a/YLErpWeb/Controllers/underlying_managerController.cs +++ b/YLErpWeb/Controllers/underlying_managerController.cs @@ -473,6 +473,26 @@ namespace YLErp.Web.Controllers { return JsonError("资产类型 必须填写"); } + + model.EtfSubType = model.EtfSubType?.Trim(); + if (model.UnderlyingInstrumentType == ConsGlobal.InstrumentType.Fund && string.IsNullOrEmpty(model.EtfSubType)) + { + return JsonError("ETF 子类 必须填写"); + } + if (model.UnderlyingInstrumentType == ConsGlobal.InstrumentType.Fund) + { + + var isValidEtfSubType = DictionaryBLL.GetDictionaryItems("ETF 子类", model.EtfSubType).Any(); + if (!isValidEtfSubType) + { + return JsonError("ETF 子类 无效,请从字典选项中选择"); + } + } + if (model.UnderlyingInstrumentType != ConsGlobal.InstrumentType.Fund) + { + + model.EtfSubType = null; + } if (ConsGlobal.InstrumentType.IsBond(model.UnderlyingInstrumentType)) { model.ExJson = JsonHelper.Serialize(model.Bond); diff --git a/YLErpWeb/Views/underlying_manager/underlying_managerEdit.cshtml b/YLErpWeb/Views/underlying_manager/underlying_managerEdit.cshtml index 6ea28715..67e3501d 100644 --- a/YLErpWeb/Views/underlying_manager/underlying_managerEdit.cshtml +++ b/YLErpWeb/Views/underlying_manager/underlying_managerEdit.cshtml @@ -8,6 +8,8 @@ showDeltaS_S = PS.Config.Is长江 }; var bond = Model.Bond ?? new UnderlyingBond(); + // ETF 子类下拉直接读取前端可维护字典;空选项用于新增页面的必填提示。 + var etfSubtypeItems = YLErp.BLL.DictionaryBLL.GetList("ETF 子类", true, underlying.EtfSubType); var blocks = new[] { underlying.Block1, underlying.Block2, underlying.Block3, underlying.Block4, underlying.Block5 }; for (var i = 1; i <= 5; i++) { @@ -28,13 +30,14 @@ } } - .None, .Stock, .CommodityFutures, .Bonds { + .None, .Stock, .CommodityFutures, .Bonds, .Fund { display: none; } .form-Stock .Stock, .form-CommodityFutures .CommodityFutures, - .form-Bonds .Bonds { + .form-Bonds .Bonds, + .form-Fund .Fund { display: block; } @@ -323,6 +326,31 @@ + + @* 仅基金及基金专户维护基金管理人 *@ +
+ + +
+ + @* 重点功能:ETF 子类由系统字典维护,使用 Fund 类控制显隐,并固定放在表单最后 *@ +
+ + * +
@@ -334,4 +362,4 @@
- \ No newline at end of file + diff --git a/YLErpWeb/Views/underlying_manager/underlying_managerView.cshtml b/YLErpWeb/Views/underlying_manager/underlying_managerView.cshtml index 731c26d4..2b17d61a 100644 --- a/YLErpWeb/Views/underlying_manager/underlying_managerView.cshtml +++ b/YLErpWeb/Views/underlying_manager/underlying_managerView.cshtml @@ -49,6 +49,14 @@ @Html.MyDisplayFor(m => m.UnderlyingEnName) @Html.MyDisplayFor(m => m.BBGTicker) + + @if (Model.UnderlyingInstrumentType == ConsGlobal.InstrumentType.Fund) + { + + @Html.MyDisplayFor(m => m.InvestAdvisorName) + @Html.MyDisplayFor(m => m.EtfSubType) + + } @Html.MyDisplayFor(m => m.UnderlyingType) @if (pageObj.showDeltaS_S) @@ -226,4 +234,4 @@ } -} \ No newline at end of file +} diff --git a/YLErpWeb/wwwroot/Scripts/app/underlying/underlyingedit.js b/YLErpWeb/wwwroot/Scripts/app/underlying/underlyingedit.js index 7a8de500..a5a2724d 100644 --- a/YLErpWeb/wwwroot/Scripts/app/underlying/underlyingedit.js +++ b/YLErpWeb/wwwroot/Scripts/app/underlying/underlyingedit.js @@ -13,7 +13,8 @@ }()); -const consSelect = ['MarketCode', 'UnderlyingInstrumentType', 'UnderlyingState', 'UnderlyingStatus', 'UpDownLimitType']; + +const consSelect = ['MarketCode', 'UnderlyingInstrumentType', 'UnderlyingState', 'UnderlyingStatus', 'UpDownLimitType', 'EtfSubType']; var autoUpDownLimit, autoVariety; @@ -102,11 +103,15 @@ $(function () { case "OtherBonds": classType = "Bonds"; break; + case "Fund": + // 重点功能:资产类型为基金及基金专户时,显示所有 Fund 专属字段。 + classType = "Fund"; + break; default: classType = type; break; } - $('#editForm').removeClass("form-None form-Stock form-CommodityFutures form-CommoditySpot form-Bonds").addClass("form-" + classType); + $('#editForm').removeClass("form-None form-Stock form-CommodityFutures form-CommoditySpot form-Bonds form-Fund").addClass("form-" + classType); }).trigger('change'); }); @@ -130,6 +135,11 @@ function saveData() { return main.alert("资产品种类型 必须填写!"); } + // 重点功能:ETF 子类只对基金及基金专户显示并必填,先在前端阻止无效提交。 + if (data.UnderlyingInstrumentType === "Fund" && !data.EtfSubType) { + return main.alert("ETF 子类 必须填写!"); + } + if (data.UnderlyingInstrumentType === "CommodityFutures" && !data.MaturityDate) { return main.alert("到期日 必须填写!"); } @@ -144,4 +154,4 @@ function saveData() { main.parentReloadData(); window.location.href = "/underlying_manager/underlying_managerView?enid=" + resp.obj.EncryptId; }); -} \ No newline at end of file +}