feat(margin): 添加预付金模板簿记账户范围控制功能

- 在 margin_template_v2 实体中新增 BookIds 字段存储适用簿记账户ID列表
- 实现簿记账户ID的规范化、解析和范围重叠判断逻辑
- 在客户端和默认模板编辑页面添加适用簿记账户选择控件
- 修改预付金模板解析逻辑以考虑簿记账户适用性
- 更新交易绑定模板验证以检查簿记账户范围匹配
- 优化模板冲突检测算法以支持簿记账户范围重叠验证
- 添加簿记账户ID存在性校验防止无效引用
- 实现前端动态筛选适用当前资产的模板选项
This commit is contained in:
张名锐
2026-08-27 19:38:10 +08:00
parent c843012cdc
commit 134e07c628
13 changed files with 415 additions and 100 deletions
@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.Globalization;
using YLErp.Enums;
namespace YLErp.DBModels
@@ -17,6 +18,109 @@ namespace YLErp.DBModels
[DisplayName("模板名称")]
public string Name { get; set; }
/// <summary>
/// 适用簿记账户ID列表,逗号分隔;为空表示适用全部簿记账户。
/// </summary>
[DisplayName("适用簿记账户组")]
[Column("book_id")]
public string BookIds { get; set; }
/// <summary>
/// 将簿记账户ID列表规范化为去空格、去重的逗号分隔字符串。
/// </summary>
public static string NormalizeBookIds(string bookIds)
{
if (string.IsNullOrWhiteSpace(bookIds))
{
return null;
}
var ids = new List<int>();
foreach (var rawId in bookIds.Split(new[] { ',' }, StringSplitOptions.None))
{
var token = rawId.Trim();
if (token.Length == 0)
{
throw new ArgumentException("适用簿记账户组包含空账户ID。", nameof(bookIds));
}
if (!int.TryParse(token, NumberStyles.None, CultureInfo.InvariantCulture, out var id) || id <= 0)
{
throw new ArgumentException("适用簿记账户组ID必须为正整数:" + token, nameof(bookIds));
}
if (!ids.Contains(id))
{
ids.Add(id);
}
}
return ids.Count == 0 ? null : string.Join(",", ids);
}
/// <summary>
/// 解析簿记账户ID列表。结果使用完整token,避免"1"误命中"11"。
/// </summary>
public static HashSet<string> ParseBookIds(string bookIds)
{
var ids = new HashSet<string>(StringComparer.Ordinal);
if (string.IsNullOrWhiteSpace(bookIds))
{
return ids;
}
foreach (var rawId in bookIds.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
var token = rawId.Trim();
if (int.TryParse(token, NumberStyles.None, CultureInfo.InvariantCulture, out var id) && id > 0)
{
ids.Add(id.ToString(CultureInfo.InvariantCulture));
}
}
return ids;
}
/// <summary>
/// 判断模板是否适用于指定簿记账户;空范围表示全部。
/// </summary>
public bool IsApplicableToBook(int? bookId)
{
if (string.IsNullOrWhiteSpace(BookIds))
{
return true;
}
return bookId.HasValue && ParseBookIds(BookIds).Contains(bookId.Value.ToString(CultureInfo.InvariantCulture));
}
/// <summary>
/// 判断两个簿记账户范围是否重叠;任一空范围均视为全部。
/// </summary>
public static bool AreBookScopesOverlapping(string firstBookIds, string secondBookIds)
{
if (string.IsNullOrWhiteSpace(firstBookIds) || string.IsNullOrWhiteSpace(secondBookIds))
{
return true;
}
var first = ParseBookIds(firstBookIds);
var second = ParseBookIds(secondBookIds);
return first.Count > 0 && second.Count > 0 && first.Overlaps(second);
}
/// <summary>
/// 收集与新模板簿记账户范围重叠的所有模板适用结构。
/// </summary>
public static IEnumerable<string> GetOverlappingTradeTypes(IEnumerable<margin_template_v2> candidates, string bookIds)
{
if (candidates == null)
{
return Enumerable.Empty<string>();
}
return candidates
.Where(x => x != null && AreBookScopesOverlapping(x.BookIds, bookIds))
.SelectMany(x => (x.TradeTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries));
}
/// <summary>
/// 单腿/多腿
/// </summary>