feat(margin_template_v2): 添加簿记账户显示功能

- 在 margin_template_v2 模型中新增 BookScopeName 属性用于显示簿记账户名称
- 更新客户端和服务端列表视图以显示适用簿记账户列
- 实现 SetBookScopeNames 方法将簿记账户ID转换为名称显示
- 添加 ParseBookIdsForDisplay 方法解析簿记账户ID列表
- 新增 MarginTemplateV2BookScopeTest 单元测试验证簿记账户范围逻辑
This commit is contained in:
张名锐
2026-08-28 09:44:47 +08:00
parent 134e07c628
commit 9717905245
5 changed files with 189 additions and 2 deletions
@@ -25,6 +25,19 @@ namespace YLErp.DBModels
[Column("book_id")]
public string BookIds { get; set; }
private string _bookScopeName;
/// <summary>
/// 适用簿记账户名称,仅用于列表展示,不持久化。
/// </summary>
[NotMapped]
[DisplayName("适用簿记账户")]
public string BookScopeName
{
get => string.IsNullOrWhiteSpace(BookIds) ? "全部" : _bookScopeName ?? string.Empty;
set => _bookScopeName = value;
}
/// <summary>
/// 将簿记账户ID列表规范化为去空格、去重的逗号分隔字符串。
/// </summary>
@@ -0,0 +1,110 @@
using YLErp.DBModels;
using YLErp.Modules.MarginModule;
using YLErp.Modules.SwapModule;
namespace YLErp.Modules.CalcModules
{
[TestClass]
public class MarginTemplateV2BookScopeTest
{
[TestMethod]
public void BS_001_空范围适用全部簿记账户()
{
var template = new margin_template_v2 { BookIds = null };
Assert.IsTrue(template.IsApplicableToBook(1));
Assert.IsTrue(template.IsApplicableToBook(11));
}
[TestMethod]
public void BS_002_按完整token精确匹配避免1命中11()
{
var template = new margin_template_v2 { BookIds = "11, 12,11" };
Assert.IsFalse(template.IsApplicableToBook(1));
Assert.IsTrue(template.IsApplicableToBook(11));
Assert.IsTrue(template.IsApplicableToBook(12));
Assert.AreEqual("11,12", margin_template_v2.NormalizeBookIds(template.BookIds));
}
[TestMethod]
public void BS_003_账户范围只接受正整数并规范前导零()
{
Assert.IsNull(margin_template_v2.NormalizeBookIds(null));
Assert.IsNull(margin_template_v2.NormalizeBookIds(" "));
Assert.AreEqual("1,2", margin_template_v2.NormalizeBookIds("001, 2,001"));
Assert.ThrowsException<ArgumentException>(() => margin_template_v2.NormalizeBookIds("abc"));
Assert.ThrowsException<ArgumentException>(() => margin_template_v2.NormalizeBookIds("0"));
Assert.ThrowsException<ArgumentException>(() => margin_template_v2.NormalizeBookIds("-1"));
Assert.ThrowsException<ArgumentException>(() => margin_template_v2.NormalizeBookIds("1,,2"));
}
[TestMethod]
public void BS_004_范围重叠空范围视为全部()
{
Assert.IsTrue(margin_template_v2.AreBookScopesOverlapping(null, "11"));
Assert.IsTrue(margin_template_v2.AreBookScopesOverlapping("11,12", "12,13"));
Assert.IsFalse(margin_template_v2.AreBookScopesOverlapping("1", "11"));
}
[TestMethod]
public void BS_005_收集所有重叠范围的适用结构()
{
var candidates = new[]
{
new margin_template_v2 { BookIds = "1", TradeTypes = "收益互换" },
new margin_template_v2 { BookIds = "2", TradeTypes = "香草期权" },
new margin_template_v2 { BookIds = "3", TradeTypes = "远期" }
};
var tradeTypes = margin_template_v2.GetOverlappingTradeTypes(candidates, "1,2").ToList();
CollectionAssert.AreEquivalent(new[] { "收益互换", "香草期权" }, tradeTypes);
}
[TestMethod]
public void BS_006_空交易ID不触发簿记账户查询()
{
Assert.IsNull(MarginTemplateV2RateHelper.GetTradeAssetId(null, null));
Assert.IsNull(MarginTemplateV2RateHelper.GetTradeAssetId(0, null));
}
[TestMethod]
public void LEGACY_001_存量原样未知名称可保留()
{
Assert.IsTrue(SwapTradeService.CanKeepLegacyMarginTemplateName(
false, "历史模板", "历史模板", false));
}
[TestMethod]
public void LEGACY_002_新增交易的未知名称不可保留()
{
Assert.IsFalse(SwapTradeService.CanKeepLegacyMarginTemplateName(
true, "历史模板", "历史模板", false));
}
[TestMethod]
public void LEGACY_003_修改为不同未知名称不可保留()
{
Assert.IsFalse(SwapTradeService.CanKeepLegacyMarginTemplateName(
false, "历史模板", "另一历史模板", false));
}
[TestMethod]
public void LEGACY_004_已存在V2名称不作为历史值()
{
Assert.IsFalse(SwapTradeService.CanKeepLegacyMarginTemplateName(
false, "模板V2", "模板V2", true));
}
[TestMethod]
public void LEGACY_005_空名称不作为历史值()
{
Assert.IsFalse(SwapTradeService.CanKeepLegacyMarginTemplateName(
false, null, null, false));
Assert.IsFalse(SwapTradeService.CanKeepLegacyMarginTemplateName(
false, " ", " ", false));
}
}
}
@@ -1,4 +1,5 @@
using BaseOUDAL;
using System.Globalization;
using YLErp.Model;
namespace YLErp.Modules.MarginModule
@@ -77,8 +78,67 @@ namespace YLErp.Modules.MarginModule
}
var retListResult = query.ToSearchList(req);
SetBookScopeNames(retListResult);
return retListResult;
}
private void SetBookScopeNames(SearchListResult<margin_template_v2> result)
{
var rows = result?.rows?.ToList();
if (rows == null || rows.Count == 0)
{
return;
}
var parsedRows = rows.Select(row => new
{
Row = row,
BookIds = row == null ? new List<int>() : ParseBookIdsForDisplay(row.BookIds)
}).ToList();
var bookIds = parsedRows.SelectMany(item => item.BookIds).Distinct().ToList();
var bookNames = bookIds.Count == 0
? new Dictionary<int, string>()
: DbContext.assetunit
.AsNoTracking()
.Where(book => bookIds.Contains(book.id))
.Select(book => new { book.id, book.Name })
.ToDictionary(book => book.id, book => book.Name);
foreach (var item in parsedRows)
{
if (item.Row == null)
{
continue;
}
item.Row.BookScopeName = string.IsNullOrWhiteSpace(item.Row.BookIds)
? "全部"
: string.Join("", item.BookIds
.Where(bookId => bookNames.ContainsKey(bookId))
.Select(bookId => bookNames[bookId]));
}
result.rows = rows;
}
private static List<int> ParseBookIdsForDisplay(string bookIds)
{
var ids = new List<int>();
if (string.IsNullOrWhiteSpace(bookIds))
{
return ids;
}
foreach (var rawId in bookIds.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
if (int.TryParse(rawId.Trim(), NumberStyles.None, CultureInfo.InvariantCulture, out var id) && id > 0)
{
ids.Add(id);
}
}
return ids;
}
}
}
@@ -50,6 +50,8 @@
name: 'RuleType', label: '预付金规则', index: 'RuleType', width: 150, formatter: ruleTypeFormat
}, {
name: 'TradeTypes', label: '适用结构', index: 'TradeTypes', width: 460
}, {
name: 'BookScopeName', label: '适用簿记账户', index: 'BookScopeName', width: 240
}];
function showToolName(cellValue, options, rowObject) {
@@ -197,4 +199,4 @@
</ul>
</div>
@Html.Raw(JqGridSimple.OutTable())
@Html.Raw(JqGridSimple.OutTable())
@@ -49,6 +49,8 @@
name: 'RuleType', label: '预付金规则', index: 'RuleType', width: 150, formatter: ruleTypeFormat
}, {
name: 'TradeTypes', label: '适用结构', index: 'TradeTypes', width: 460
}, {
name: 'BookScopeName', label: '适用簿记账户', index: 'BookScopeName', width: 240
}, {
name: 'BuySellTypeName', label: '适用买卖方向', index: 'BuySellTypeName', width: 150
}, {
@@ -201,4 +203,4 @@
</ul>
</div>
@Html.Raw(JqGridSimple.OutTable())
@Html.Raw(JqGridSimple.OutTable())