From 0fcbe870b94082eab91d56c2a564773d7b1440fa Mon Sep 17 00:00:00 2001 From: Mikhail Kuznetsov Date: Thu, 15 Oct 2026 10:39:33 +1000 Subject: [PATCH] =?UTF-8?q?feat(shortcodesService):=20=D0=B4=D0=BE=D0=B1?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=20=D1=88=D0=BE=D1=80=D1=82=D0=BA?= =?UTF-8?q?=D0=BE=D0=B4=20%=D0=A0=D0=9E=D0=94=5F=D0=AD=D0=9A=5F=D0=9F?= =?UTF-8?q?=D0=95=D0=A0=D0=92=D0=AB=D0=99:=D0=9E=D0=A3-*:=D0=9A=D0=A0?= =?UTF-8?q?=D0=90=D0=A2=D0=9A=D0=9E=D0=95=5F=D0=9D=D0=90=D0=98=D0=9C=D0=95?= =?UTF-8?q?=D0=9D=D0=9E=D0=92=D0=90=D0=9D=D0=98=D0=95%.=20-=20=D0=9F=D0=BE?= =?UTF-8?q?=D0=B7=D0=B2=D0=BE=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D1=83=D1=87=D0=B0=D1=82=D1=8C=20=D0=B7=D0=BD=D0=B0=D1=87=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=B0=D1=82=D1=80=D0=B8=D0=B1=D1=83=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=20=D1=80=D0=BE=D0=B4=D0=B8=D1=82=D0=B5=D0=BB=D1=8C?= =?UTF-8?q?=D1=81=D0=BA=D0=B8=D1=85=20=D0=AD=D0=9A.=20-=20=D0=A0=D0=BE?= =?UTF-8?q?=D0=B4=D0=B8=D1=82=D0=B5=D0=BB=D1=8C=20=D0=BE=D0=BF=D1=80=D0=B5?= =?UTF-8?q?=D0=B4=D0=B5=D0=BB=D1=8F=D0=B5=D1=82=D1=81=D1=8F=20=D0=BF=D0=BE?= =?UTF-8?q?=20=D0=BC=D0=B0=D1=81=D0=BA=D0=B5,=20=D0=B2=D1=8B=D0=B1=D0=B8?= =?UTF-8?q?=D1=80=D0=B0=D0=B5=D1=82=D1=81=D1=8F=20=D0=BF=D0=B5=D1=80=D0=B2?= =?UTF-8?q?=D1=8B=D0=B9=20=D0=BD=D0=B0=D0=B9=D0=B4=D0=B5=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D0=B9,=20=D0=BE=D1=82=D1=81=D1=82=D0=BE=D1=80=D0=B8=D1=82?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=BD=D1=8B=D0=B9=20=D0=BF=D0=BE?= =?UTF-8?q?=20=D0=B0=D0=BB=D1=84=D0=B0=D0=B2=D0=B8=D1=82=D1=83=20-=20?= =?UTF-8?q?=D0=98=D0=BC=D1=8F=20=D0=B0=D1=82=D1=80=D0=B8=D0=B1=D1=83=D1=82?= =?UTF-8?q?=D0=B0=20=D0=BE=D0=BF=D1=80=D0=B5=D0=B4=D0=B5=D0=BB=D1=8F=D0=B5?= =?UTF-8?q?=D1=82=D1=81=D1=8F=20=D0=BF=D0=BE=20=D0=BF=D0=BE=D0=BB=D1=8E=20?= =?UTF-8?q?AihitName?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PARR.Core/DependencyInjection.cs | 1 + .../Enums/ShortcodeDataRequirementsEnum.cs | 3 +- .../ParentUnitFirstShortcodeHandler.cs | 125 ++++++++++++ .../Models/ParentUnitForShortcode.cs | 7 + .../Shortcodes/Models/TemplateForShortcode.cs | 3 +- .../Services/Shortcodes/ShortcodesService.cs | 184 ++++++++++++++---- 6 files changed, 281 insertions(+), 42 deletions(-) create mode 100644 PARR.Core/Services/Shortcodes/Handlers/ParentUnitFirstShortcodeHandler.cs create mode 100644 PARR.Core/Services/Shortcodes/Models/ParentUnitForShortcode.cs diff --git a/PARR.Core/DependencyInjection.cs b/PARR.Core/DependencyInjection.cs index 460495db..878984d8 100644 --- a/PARR.Core/DependencyInjection.cs +++ b/PARR.Core/DependencyInjection.cs @@ -109,6 +109,7 @@ namespace PARR.Core services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); #endregion diff --git a/PARR.Core/Services/Shortcodes/Enums/ShortcodeDataRequirementsEnum.cs b/PARR.Core/Services/Shortcodes/Enums/ShortcodeDataRequirementsEnum.cs index efe5d23a..e2afa63b 100644 --- a/PARR.Core/Services/Shortcodes/Enums/ShortcodeDataRequirementsEnum.cs +++ b/PARR.Core/Services/Shortcodes/Enums/ShortcodeDataRequirementsEnum.cs @@ -11,6 +11,7 @@ UnitName = 16, UnitsInTemplate = 32, UnitTags = 64, - UnitsInTemplateTags = 128 + UnitsInTemplateTags = 128, + ParentUnits = 256 } } diff --git a/PARR.Core/Services/Shortcodes/Handlers/ParentUnitFirstShortcodeHandler.cs b/PARR.Core/Services/Shortcodes/Handlers/ParentUnitFirstShortcodeHandler.cs new file mode 100644 index 00000000..305dde1e --- /dev/null +++ b/PARR.Core/Services/Shortcodes/Handlers/ParentUnitFirstShortcodeHandler.cs @@ -0,0 +1,125 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using PARR.Core.Repositories.Interfaces.Unit; +using PARR.Core.Services.Shortcodes.Enums; +using PARR.Core.Services.Shortcodes.Models; +using System.Text.RegularExpressions; + +namespace PARR.Core.Services.Shortcodes.Handlers; + +internal class ParentUnitFirstShortcodeHandler : IShortcodeHandler +{ + private readonly IUnitInValueRepository unitInValueRepo; + private readonly ILogger logger; + + public int Order => 55; // После RelationshipUnitShortcodeHandler + + // Паттерн: %РОД_ЭК_ПЕРВЫЙ:МАСКА:ИМЯ_АТРИБУТА% + private static readonly Regex _pattern = new( + @"%РОД_ЭК_ПЕРВЫЙ:([^:]+):([^%]+)%", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public Regex Pattern => _pattern; + + public ShortcodeDataRequirementsEnum Requirements => ShortcodeDataRequirementsEnum.ParentUnits; + + public ParentUnitFirstShortcodeHandler( + IUnitInValueRepository unitInValueRepo, + ILogger logger) + { + this.unitInValueRepo = unitInValueRepo; + this.logger = logger; + } + + public async Task ResolveAsync(string input, TemplateForShortcode template, string caller, CancellationToken ct = default) + { + var matches = Pattern.Matches(input); + if (matches.Count == 0) return input; + + // Группируем уникальные запросы, чтобы не делать повторные БД-запросы для одинаковых шорткодов в одной строке + var uniqueRequests = matches.Cast() + .Select(m => new + { + FullMatch = m.Value, + Mask = m.Groups[1].Value.Trim(), + FieldName = m.Groups[2].Value.Trim() + }) + .GroupBy(x => new { x.Mask, x.FieldName }) + .Select(g => g.First()) + .ToList(); + + var resolved = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var request in uniqueRequests) + { + var value = await GetParentUnitFieldValueAsync(template, request.Mask, request.FieldName, caller, ct); + resolved[request.FullMatch] = value; + } + + var result = input; + foreach (Match m in matches) + { + if (resolved.TryGetValue(m.Value, out var val)) + result = result.Replace(m.Value, val, StringComparison.OrdinalIgnoreCase); + } + + return result; + } + + private async Task GetParentUnitFieldValueAsync( + TemplateForShortcode template, + string mask, + string fieldName, + string caller, + CancellationToken ct) + { + // 1. Проверяем наличие родителей в контексте + if (template.ParentUnits == null || template.ParentUnits.Count == 0) + { + logger.LogDebug("[{Caller}] Родителей нет. Возвращаю 'Нет данных'.", caller); + return "Нет данных"; + } + + // 2. Ищем ПЕРВОГО родителя, подходящего под маску + var matchingParent = template.ParentUnits + .FirstOrDefault(p => MatchesMask(p.UnitName, mask)); + + if (matchingParent == null) + { + logger.LogDebug("[{Caller}] Не найден родитель по маске {Mask}. Возвращаю 'Нет данных'.", caller, mask); + return "Нет данных"; + } + + // 3. Запрашиваем значение атрибута у найденного родителя + var fieldValue = await unitInValueRepo.Get() + .AsNoTracking() + .Include(uv => uv.Field) + .Include(uv => uv.Value) + .Where(uv => uv.UnitId == matchingParent.UnitId && + uv.Field!.AihitName == fieldName && + uv.Value != null && + !string.IsNullOrEmpty(uv.Value.Value)) + .Select(uv => uv.Value!.Value) + .FirstOrDefaultAsync(ct) + .ConfigureAwait(false); + + return fieldValue ?? "Нет данных"; + } + + private bool MatchesMask(string value, string mask) + { + if (string.IsNullOrEmpty(value) || string.IsNullOrEmpty(mask)) + return false; + + // Поддержка wildcard * + if (mask.Contains('*')) + { + // Экранируем спецсимволы regex, кроме *, который заменяем на .* + var regexPattern = "^" + Regex.Escape(mask).Replace("\\*", ".*") + "$"; + return Regex.IsMatch(value, regexPattern, RegexOptions.IgnoreCase); + } + + // Точное совпадение + return string.Equals(value, mask, StringComparison.OrdinalIgnoreCase); + } +} \ No newline at end of file diff --git a/PARR.Core/Services/Shortcodes/Models/ParentUnitForShortcode.cs b/PARR.Core/Services/Shortcodes/Models/ParentUnitForShortcode.cs new file mode 100644 index 00000000..c64ef7ec --- /dev/null +++ b/PARR.Core/Services/Shortcodes/Models/ParentUnitForShortcode.cs @@ -0,0 +1,7 @@ +namespace PARR.Core.Services.Shortcodes.Models +{ + public record ParentUnitForShortcode( + Guid UnitId, + string UnitName + ); +} diff --git a/PARR.Core/Services/Shortcodes/Models/TemplateForShortcode.cs b/PARR.Core/Services/Shortcodes/Models/TemplateForShortcode.cs index bdf8de61..c7bae97d 100644 --- a/PARR.Core/Services/Shortcodes/Models/TemplateForShortcode.cs +++ b/PARR.Core/Services/Shortcodes/Models/TemplateForShortcode.cs @@ -9,6 +9,7 @@ JobForShortcode? Job, List UnitsInTemplate, List UnitTags, - Dictionary> RelatedUnitTags + Dictionary> RelatedUnitTags, + List ParentUnits ); } diff --git a/PARR.Core/Services/Shortcodes/ShortcodesService.cs b/PARR.Core/Services/Shortcodes/ShortcodesService.cs index 280d21fe..caf90d3c 100644 --- a/PARR.Core/Services/Shortcodes/ShortcodesService.cs +++ b/PARR.Core/Services/Shortcodes/ShortcodesService.cs @@ -25,34 +25,37 @@ internal class ShortcodesService : IShortcodesService private const int MaxIterations = 3; - private readonly ILogger logger; - private readonly IEnumerable handlers; - private readonly IRedisCacheService cacheService; - private readonly SettingsFromDb settingsFromDb; - private readonly IJobRepository jobService; - private readonly IUnitRepository unitService; - private readonly IUnitFieldRepository unitFieldRepo; - private readonly ITemplateRepository templateRepo; + private readonly ILogger _logger; + private readonly IEnumerable _handlers; + private readonly IRedisCacheService _cacheService; + private readonly SettingsFromDb _settingsFromDb; + private readonly IJobRepository _jobService; + private readonly IUnitRepository _unitRepo; + private readonly IUnitFieldRepository _unitFieldRepo; + private readonly IUnitInUnitRepository _unitInUnitRepo; + private readonly ITemplateRepository _templateRepo; public ShortcodesService( ILogger logger, IEnumerable handlers, IRedisCacheService cacheService, SettingsFromDb settingsFromDb, - IJobRepository jobService, - IUnitRepository unitService, - IUnitFieldRepository unitFieldService, + IJobRepository jobRepo, + IUnitRepository unitRepo, + IUnitFieldRepository unitFieldRepo, + IUnitInUnitRepository unitInUnitRepo, ITemplateRepository templateRepo ) { - this.logger = logger; - this.handlers = handlers.OrderBy(h => h.Order).ToList(); - this.cacheService = cacheService; - this.settingsFromDb = settingsFromDb; - this.jobService = jobService; - this.unitService = unitService; - this.unitFieldRepo = unitFieldService; - this.templateRepo = templateRepo; + this._logger = logger; + this._handlers = handlers.OrderBy(h => h.Order).ToList(); + this._cacheService = cacheService; + this._settingsFromDb = settingsFromDb; + this._jobService = jobRepo; + this._unitRepo = unitRepo; + this._unitFieldRepo = unitFieldRepo; + this._unitInUnitRepo = unitInUnitRepo; + this._templateRepo = templateRepo; } public async Task ApplyShortcodesAsync(string str, Template template, [CallerMemberName] string? caller = null) @@ -61,8 +64,8 @@ internal class ShortcodesService : IShortcodesService var callerName = caller ?? "Unknown"; // Кэш результата (проверяем один раз на входе) - var cacheKey = cacheService.GetKey(new[] { "shortcodes", "result", template.Id.ToString("N") }, new[] { str }); - var cachedResult = await cacheService.GetCachedDataAsync(cacheKey).ConfigureAwait(false); + var cacheKey = _cacheService.GetKey(new[] { "shortcodes", "result", template.Id.ToString("N") }, new[] { str }); + var cachedResult = await _cacheService.GetCachedDataAsync(cacheKey).ConfigureAwait(false); if (cachedResult != null) return cachedResult.Data.Result; // Начальный контекст @@ -83,7 +86,8 @@ internal class ShortcodesService : IShortcodesService Job: initialJob, UnitsInTemplate: initialUnitsInTemplate, UnitTags: new List(), - RelatedUnitTags: new Dictionary>() + RelatedUnitTags: new Dictionary>(), + ParentUnits: new List() ); var result = str; @@ -92,7 +96,7 @@ internal class ShortcodesService : IShortcodesService while (iteration < MaxIterations) { // 1. Находим хендлеры, которые сработают для ТЕКУЩЕЙ версии строки - var matchingHandlers = handlers + var matchingHandlers = _handlers .Where(h => h.Pattern.IsMatch(result)) .ToList(); @@ -121,13 +125,13 @@ internal class ShortcodesService : IShortcodesService if (iteration == MaxIterations && result != str) { - logger.LogWarning("[{Caller}] Достигнут лимит итераций для шаблона {TemplateId}. Возможна циклическая зависимость.", callerName, template.Id); + _logger.LogWarning("[{Caller}] Достигнут лимит итераций для шаблона {TemplateId}. Возможна циклическая зависимость.", callerName, template.Id); } // Сохранение в кэш if (!string.IsNullOrEmpty(result) && result != str && template.Id != Guid.Empty) { - await cacheService.SetCachedDataAsync(cacheKey, new ShortcodesResult + await _cacheService.SetCachedDataAsync(cacheKey, new ShortcodesResult { Data = new ShortcodesResultDto { Result = result }, Timestamp = DateTimeOffset.UtcNow, @@ -149,8 +153,8 @@ internal class ShortcodesService : IShortcodesService if (string.IsNullOrEmpty(unitName) && requirements.HasFlag(ShortcodeDataRequirementsEnum.UnitName)) { // Логирование и загрузка только если нужно и еще не загружено - logger.LogWarning(MissingUnitNameWarningMsg, caller, template.Id); - var unit = await unitService.Get().AsNoTracking().FirstOrDefaultAsync(u => u.Id == template.UnitId).ConfigureAwait(false); + _logger.LogWarning(MissingUnitNameWarningMsg, caller, template.Id); + var unit = await _unitRepo.Get().AsNoTracking().FirstOrDefaultAsync(u => u.Id == template.UnitId).ConfigureAwait(false); unitName = unit?.Name ?? throw new InvalidOperationException($"Unit {template.UnitId} не найден."); } @@ -181,7 +185,7 @@ internal class ShortcodesService : IShortcodesService if (requirements.HasFlag(ShortcodeDataRequirementsEnum.JobTnk)) missing.Add("Tnk"); // Логируем только если реально пошли в БД - logger.LogWarning( + _logger.LogWarning( "[{Caller}] Шаблон {TemplateId} требует догрузки данных ({Missing}). Данные догружены.", caller, template.Id, string.Join(", ", missing)); @@ -193,11 +197,11 @@ internal class ShortcodesService : IShortcodesService if ((unitsInTemplate == null || unitsInTemplate.Count == 0) && requirements.HasFlag(ShortcodeDataRequirementsEnum.UnitsInTemplate)) { - logger.LogWarning( + _logger.LogWarning( "[{Caller}] Шаблон {TemplateId} передан без UnitsInTemplate. Данные догружены автоматически.", caller, template.Id); - var fullTemplate = await templateRepo.Get() + var fullTemplate = await _templateRepo.Get() .AsNoTracking() .Include(t => t.UnitsInTemplate) .FirstOrDefaultAsync(t => t.Id == template.Id) @@ -214,11 +218,11 @@ internal class ShortcodesService : IShortcodesService if ((unitTags == null || unitTags.Count == 0) && requirements.HasFlag(ShortcodeDataRequirementsEnum.UnitTags)) { - logger.LogWarning( + _logger.LogWarning( "[{Caller}] Шаблон {TemplateId} требует теги (UnitTags). Данные догружены автоматически.", caller, template.Id); - unitTags = await unitService.Get() + unitTags = await _unitRepo.Get() .AsNoTracking() .Where(u => u.Id == template.UnitId) .SelectMany(u => u.UnitValues @@ -252,11 +256,11 @@ internal class ShortcodesService : IShortcodesService requirements.HasFlag(ShortcodeDataRequirementsEnum.UnitsInTemplateTags) && relatedUnitIds.Count > 0) { - logger.LogWarning( + _logger.LogWarning( "[{Caller}] Шаблон {TemplateId} требует теги связанных ЭК (UnitsInTemplateTags). Данные догружены автоматически.", caller, template.Id); - var rawTags = await unitService.Get() + var rawTags = await _unitRepo.Get() .AsNoTracking() .Where(u => relatedUnitIds.Contains(u.Id)) .SelectMany(u => u.UnitValues @@ -273,21 +277,56 @@ internal class ShortcodesService : IShortcodesService .ToDictionary(g => g.Key, g => g.Select(x => x.Tag).ToList()); } - // 6. Возвращаем обновленный рекорд + // 6. Проверка ParentUnits (Только непосредственные родители) + var parentUnits = currentData.ParentUnits; + if ((parentUnits == null || parentUnits.Count == 0) && + requirements.HasFlag(ShortcodeDataRequirementsEnum.ParentUnits)) + { + _logger.LogWarning( + "[{Caller}] Шаблон {TemplateId} требует данные о родителях (ParentUnits). Данные догружены автоматически.", + caller, template.Id); + + // Получаем ID всех непосредственных родителей текущего ЭК + var parentIds = await _unitInUnitRepo.Get() + .AsNoTracking() + .Where(link => link.ChildUnitId == template.UnitId) + .Select(link => link.ParentUnitId) + .ToListAsync() + .ConfigureAwait(false); + + if (parentIds.Count > 0) + { + // Получаем имена этих родителей одним запросом + parentUnits = await _unitRepo.Get() + .AsNoTracking() + .Where(u => parentIds.Contains(u.Id)) + .Select(u => new ParentUnitForShortcode(u.Id, u.Name)) + .OrderBy(u => u.UnitName) + .ToListAsync() + .ConfigureAwait(false); + } + else + { + parentUnits = new List(); + } + } + + // 7. Возвращаем обновленный рекорд return currentData with { UnitName = unitName ?? string.Empty, Job = jobData, UnitsInTemplate = unitsInTemplate ?? currentData.UnitsInTemplate ?? new List(), - UnitTags = unitTags ?? currentData.UnitTags, - RelatedUnitTags = relatedUnitTags ?? currentData.RelatedUnitTags + UnitTags = unitTags ?? currentData.UnitTags ?? new List(), + RelatedUnitTags = relatedUnitTags ?? currentData.RelatedUnitTags ?? new Dictionary>(), + ParentUnits = parentUnits ?? currentData.ParentUnits ?? new List() }; } private async Task LoadFullJobAsync(Guid jobId, string caller) { - var job = await jobService.Get().AsNoTracking().AsSingleQuery() + var job = await _jobService.Get().AsNoTracking().AsSingleQuery() .Include(j => j.Group).ThenInclude(g => g!.GroupType) .Include(j => j.Tnk) .FirstOrDefaultAsync(j => j.Id == jobId) @@ -323,7 +362,7 @@ internal class ShortcodesService : IShortcodesService { var result = new List(); - foreach (var constant in settingsFromDb.TemplateNameConstantPartsList) + foreach (var constant in _settingsFromDb.TemplateNameConstantPartsList) { result.Add(new ShortcodeInfoDto { @@ -367,7 +406,14 @@ internal class ShortcodesService : IShortcodesService new ShortcodeInfoDto { Shortcode = "%СВЯЗИ-ПН%", Description = "Связанные ЭК с нумерацией", Type = ShortcodeTypeEnum.Relationship } }); - var fieldNames = await unitFieldRepo.Get().AsNoTracking().Select(t => new { t.AihitName, t.DisplayName }).ToListAsync().ConfigureAwait(false); + result.Add(new ShortcodeInfoDto + { + Shortcode = "%РОД_ЭК_ПЕРВЫЙ:МАСКА:ИМЯ_АТРИБУТА%", + Description = "Значение атрибута первого родительского ЭК, подходящего под маску (например, %РОД_ЭК_ПЕРВЫЙ:ОУ-*:КРАТКОЕ_НАИМЕНОВАНИЕ%)", + Type = ShortcodeTypeEnum.Relationship + }); + + var fieldNames = await _unitFieldRepo.Get().AsNoTracking().Select(t => new { t.AihitName, t.DisplayName }).ToListAsync().ConfigureAwait(false); foreach (var fieldName in fieldNames.OrderBy(n => n.AihitName)) { result.Add(new ShortcodeInfoDto @@ -380,4 +426,62 @@ internal class ShortcodesService : IShortcodesService return result; } + + private async Task> LoadParentUnitsHierarchyAsync(Guid unitId, string caller) + { + var result = new List(); + var visited = new HashSet(); + var currentUnitId = unitId; + var maxDepth = 100; // Защита от циклических зависимостей + var depth = 0; + + while (depth < maxDepth) + { + // Получаем всех родителей текущего юнита + var parentIds = await _unitInUnitRepo.Get() + .AsNoTracking() + .Where(link => link.ChildUnitId == currentUnitId) + .Select(link => link.ParentUnitId) + .ToListAsync() + .ConfigureAwait(false); + + if (parentIds.Count == 0) + break; // Достигли корня + + // Берем первого родителя (если их несколько) + var parentId = parentIds.First(); + + // Защита от циклов + if (visited.Contains(parentId)) + { + _logger.LogWarning("[{Caller}] Обнаружена циклическая зависимость в иерархии юнитов для UnitId {UnitId}", + caller, unitId); + break; + } + + visited.Add(parentId); + + // Получаем информацию о родителе + var parentUnit = await _unitRepo.Get() + .AsNoTracking() + .Where(u => u.Id == parentId) + .Select(u => new ParentUnitForShortcode(u.Id, u.Name)) + .FirstOrDefaultAsync() + .ConfigureAwait(false); + + if (parentUnit != null) + { + result.Add(parentUnit); + currentUnitId = parentId; + } + else + { + break; + } + + depth++; + } + + return result; + } } \ No newline at end of file