feature:#EQD-7093 国联民生-交易确认书校验(2)针对ETF标的获取新增信息并且支持配置分类。 ETF交易确认书中填充参考标的证券全称、参考标的基金管理人,

This commit is contained in:
马冰冰
2026-08-21 14:16:05 +08:00
parent 952295a78a
commit f16634534c
11 changed files with 165 additions and 8 deletions
@@ -137,6 +137,20 @@ namespace YLErp.DBModels
[DisplayName("标的名称")] [DisplayName("标的名称")]
public string UnderlyingName { set; get; } public string UnderlyingName { set; get; }
/// <summary>
/// 基金及基金专户的基金管理人名称
/// </summary>
[Column("investadvisorname")]
[DisplayName("基金管理人")]
public string InvestAdvisorName { get; set; }
/// <summary>
/// 基金及基金专户所属的 ETF 子类,保存“ETF 子类”字典项名称
/// </summary>
[Column("etf_sub_type")]
[DisplayName("ETF 子类")]
public string EtfSubType { get; set; }
/// <summary> /// <summary>
/// 标的英文名 /// 标的英文名
@@ -42,4 +42,17 @@
<dictionary name="可取系数" catalog="客户"></dictionary> <dictionary name="可取系数" catalog="客户"></dictionary>
<dictionary name="最小转账金额" catalog="客户"></dictionary> <dictionary name="最小转账金额" catalog="客户"></dictionary>
<dictionary name="累计转远期到期日类型" catalog="客户"></dictionary> <dictionary name="累计转远期到期日类型" catalog="客户"></dictionary>
<dictionary name="ETF 子类" catalog="标的">
<item name="国债 ETF" short_name="国债 ETF"></item>
<item name="政金债 ETF" short_name="政金债 ETF"></item>
<item name="地方债 ETF" short_name="地方债 ETF"></item>
<item name="国债政金债 ETF" short_name="国债政金债 ETF"></item>
<item name="短融 ETF" short_name="短融 ETF"></item>
<item name="公司债 ETF" short_name="公司债 ETF"></item>
<item name="城投债 ETF" short_name="城投债 ETF"></item>
<item name="做市信用债 ETF" short_name="做市信用债 ETF"></item>
<item name="科创债 ETF" short_name="科创债 ETF"></item>
<item name="可转债 ETF" short_name="可转债 ETF"></item>
</dictionary>
</dictionaries> </dictionaries>
@@ -508,7 +508,8 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
dic["参考标的名义份额"] = swapPosition != null dic["参考标的名义份额"] = swapPosition != null
? ((double)swapPosition.PosiQuantity).ToString("0.##") ? ((double)swapPosition.PosiQuantity).ToString("0.##")
: "0"; : "0";
dic["参考标的基金管理人"] = "";
dic["参考标的基金管理人"] = underlying?.InvestAdvisorName ?? "";
var contractTypeId = (Context.GetContractTypes().FirstOrDefault(O => O.ContactType == "交易确认书接收")?.id) ?? 0; var contractTypeId = (Context.GetContractTypes().FirstOrDefault(O => O.ContactType == "交易确认书接收")?.id) ?? 0;
// 乙方联系人信息 // 乙方联系人信息
var clientDuties = Context.GetClientDuties().Where(O => O.ContactTypeIdsInt.Contains(contractTypeId)).ToList(); var clientDuties = Context.GetClientDuties().Where(O => O.ContactTypeIdsInt.Contains(contractTypeId)).ToList();
@@ -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<ColumnAttribute>()?.Name);
Assert.AreEqual("基金管理人", property.GetCustomAttribute<DisplayNameAttribute>()?.DisplayName);
}
}
}
+43
View File
@@ -423,6 +423,8 @@ namespace YLErp.Modules.AppModule
var exists = adminDb.Dictionaries.Select(n => n.Name).ToArray() var exists = adminDb.Dictionaries.Select(n => n.Name).ToArray()
.Select(n => n.Trim()).ToHashSet(); .Select(n => n.Trim()).ToHashSet();
// 记录本次新建的字典,确保初始项只写入一次,不覆盖后续人工维护结果。
var addedDictionaryNames = new HashSet<string>();
var root = XElement.Parse(YLErp.Resources.Properties.Resources.db_dictionaries); var root = XElement.Parse(YLErp.Resources.Properties.Resources.db_dictionaries);
var itemsAll = root.Elements(); var itemsAll = root.Elements();
@@ -450,10 +452,51 @@ namespace YLErp.Modules.AppModule
Name = name, Name = name,
Catalog = catalog Catalog = catalog
}); });
addedDictionaryNames.Add(name);
} }
adminDb.SaveChanges(); 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); var marginTemplateDictionary = adminDb.Dictionaries.FirstOrDefault(item => item.Name == YLErp.Modules.SwapModule.SwapMarginTemplateConfigService.DictionaryName);
if (marginTemplateDictionary == null) if (marginTemplateDictionary == null)
{ {
@@ -473,6 +473,26 @@ namespace YLErp.Web.Controllers
{ {
return JsonError("资产类型 必须填写"); 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)) if (ConsGlobal.InstrumentType.IsBond(model.UnderlyingInstrumentType))
{ {
model.ExJson = JsonHelper.Serialize(model.Bond); model.ExJson = JsonHelper.Serialize(model.Bond);
@@ -8,6 +8,8 @@
showDeltaS_S = PS.Config.Is长江 showDeltaS_S = PS.Config.Is长江
}; };
var bond = Model.Bond ?? new UnderlyingBond(); 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 }; var blocks = new[] { underlying.Block1, underlying.Block2, underlying.Block3, underlying.Block4, underlying.Block5 };
for (var i = 1; i <= 5; i++) for (var i = 1; i <= 5; i++)
{ {
@@ -28,13 +30,14 @@
} }
} }
.None, .Stock, .CommodityFutures, .Bonds { .None, .Stock, .CommodityFutures, .Bonds, .Fund {
display: none; display: none;
} }
.form-Stock .Stock, .form-Stock .Stock,
.form-CommodityFutures .CommodityFutures, .form-CommodityFutures .CommodityFutures,
.form-Bonds .Bonds { .form-Bonds .Bonds,
.form-Fund .Fund {
display: block; display: block;
} }
</style> </style>
@@ -323,6 +326,31 @@
<label class='formlabel'>增值税率</label> <label class='formlabel'>增值税率</label>
<input id='ValueAddedTax' class='text-box' type='number' value='@(underlying.ValueAddedTax)' name='ValueAddedTax' /> <input id='ValueAddedTax' class='text-box' type='number' value='@(underlying.ValueAddedTax)' name='ValueAddedTax' />
</div> </div>
@* 仅基金及基金专户维护基金管理人 *@
<div class='form-group col-6 Fund'>
<label class='formlabel'>基金管理人</label>
<input id='InvestAdvisorName' class='text-box' type='text' value='@(underlying.InvestAdvisorName)' name='InvestAdvisorName' maxlength='100' />
</div>
@* 重点功能:ETF 子类由系统字典维护,使用 Fund 类控制显隐,并固定放在表单最后 *@
<div class='form-group col-6 Fund'>
<label class='formlabel'>ETF 子类</label>
<select id='EtfSubType' name='EtfSubType'>
@foreach (var item in etfSubtypeItems)
{
@* 编辑页面按实体中的字典名称回显当前选项 *@
if (item.Value == underlying.EtfSubType)
{
<option value='@item.Value' selected>@item.Text</option>
}
else
{
<option value='@item.Value'>@item.Text</option>
}
}
</select><span style='color:red'>*</span>
</div>
</div> </div>
<div class="Stock" style="padding-left:130px;"> <div class="Stock" style="padding-left:130px;">
@@ -334,4 +362,4 @@
<button class="btn btn-primary" type="button" onclick="saveData()">保存</button> <button class="btn btn-primary" type="button" onclick="saveData()">保存</button>
<button class="btn btn-primary" type="button" onclick="layer.closeMe()">关闭</button> <button class="btn btn-primary" type="button" onclick="layer.closeMe()">关闭</button>
</div> </div>
</form> </form>
@@ -49,6 +49,14 @@
@Html.MyDisplayFor(m => m.UnderlyingEnName) @Html.MyDisplayFor(m => m.UnderlyingEnName)
@Html.MyDisplayFor(m => m.BBGTicker) @Html.MyDisplayFor(m => m.BBGTicker)
</tr> </tr>
@if (Model.UnderlyingInstrumentType == ConsGlobal.InstrumentType.Fund)
{
<tr>
@Html.MyDisplayFor(m => m.InvestAdvisorName)
@Html.MyDisplayFor(m => m.EtfSubType)
</tr>
}
<tr> <tr>
@Html.MyDisplayFor(m => m.UnderlyingType) @Html.MyDisplayFor(m => m.UnderlyingType)
@if (pageObj.showDeltaS_S) @if (pageObj.showDeltaS_S)
@@ -226,4 +234,4 @@
</table> </table>
</div> </div>
} }
} }
@@ -13,7 +13,8 @@
}()); }());
const consSelect = ['MarketCode', 'UnderlyingInstrumentType', 'UnderlyingState', 'UnderlyingStatus', 'UpDownLimitType'];
const consSelect = ['MarketCode', 'UnderlyingInstrumentType', 'UnderlyingState', 'UnderlyingStatus', 'UpDownLimitType', 'EtfSubType'];
var autoUpDownLimit, autoVariety; var autoUpDownLimit, autoVariety;
@@ -102,11 +103,15 @@ $(function () {
case "OtherBonds": case "OtherBonds":
classType = "Bonds"; classType = "Bonds";
break; break;
case "Fund":
// 重点功能:资产类型为基金及基金专户时,显示所有 Fund 专属字段。
classType = "Fund";
break;
default: default:
classType = type; classType = type;
break; 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'); }).trigger('change');
}); });
@@ -130,6 +135,11 @@ function saveData() {
return main.alert("资产品种类型 必须填写!"); return main.alert("资产品种类型 必须填写!");
} }
// 重点功能:ETF 子类只对基金及基金专户显示并必填,先在前端阻止无效提交。
if (data.UnderlyingInstrumentType === "Fund" && !data.EtfSubType) {
return main.alert("ETF 子类 必须填写!");
}
if (data.UnderlyingInstrumentType === "CommodityFutures" && !data.MaturityDate) { if (data.UnderlyingInstrumentType === "CommodityFutures" && !data.MaturityDate) {
return main.alert("到期日 必须填写!"); return main.alert("到期日 必须填写!");
} }
@@ -144,4 +154,4 @@ function saveData() {
main.parentReloadData(); main.parentReloadData();
window.location.href = "/underlying_manager/underlying_managerView?enid=" + resp.obj.EncryptId; window.location.href = "/underlying_manager/underlying_managerView?enid=" + resp.obj.EncryptId;
}); });
} }