From cc4ad423bcd25ed0519b85149e50fc33da8bdd78 Mon Sep 17 00:00:00 2001 From: "gorbenko.pavel" Date: Wed, 26 Aug 2026 11:10:06 +0500 Subject: [PATCH] =?UTF-8?q?Revert:=20=D0=94=D0=B8=D1=80=D0=B5=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=20Tansu=20=E2=80=94=20=D1=81=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=20=D1=83=D0=B4=D0=B0=D0=BB=D1=8F=D0=B5=D0=BC=20=D0=B4?= =?UTF-8?q?=D1=83=D0=B1=D0=BB=D0=B8=D0=BA=D0=B0=D1=82=20=D0=B8=D0=B7=20Com?= =?UTF-8?q?panyDirectors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Возврат к удалению совпавшей строки CompanyDirectors вместо последовательной постановки после блока (см. 40d741c) — решили не показывать одного и того же человека дважды в списке согласования. Co-Authored-By: Claude Sonnet 5 --- .../TemplateRoutes/TemplateRouteService.cs | 65 ++++++------------- .../TemplateRouteServiceTests.cs | 28 ++++---- 2 files changed, 30 insertions(+), 63 deletions(-) diff --git a/backend/Tnsu.ApprovalRoute.Application/TemplateRoutes/TemplateRouteService.cs b/backend/Tnsu.ApprovalRoute.Application/TemplateRoutes/TemplateRouteService.cs index c418d4f..321d0ca 100644 --- a/backend/Tnsu.ApprovalRoute.Application/TemplateRoutes/TemplateRouteService.cs +++ b/backend/Tnsu.ApprovalRoute.Application/TemplateRoutes/TemplateRouteService.cs @@ -221,11 +221,8 @@ public sealed class TemplateRouteService( HasUnevaluatableManagerCondition(step, positionMissing) ? PositionMissingWarning : null, cancellationToken))); - var flattenedSteps = stepResults.SelectMany(x => x).ToList(); - var (toCollapse, directorTansuAfterCompanyDirectors) = SplitDirectorTansuAfterCompanyDirectors(flattenedSteps); - var collapsedSteps = CollapseByExecutorEmail(toCollapse) - .Concat(directorTansuAfterCompanyDirectors) - .ToList(); + var flattenedSteps = RemoveCompanyDirectorsDuplicatedByDirectorTansu(stepResults.SelectMany(x => x).ToList()); + var collapsedSteps = CollapseByExecutorEmail(flattenedSteps); return Result.Ok(new ResolveTemplateRouteResultDto(route.Id, route.RequiresControl, collapsedSteps)); } @@ -233,53 +230,29 @@ public sealed class TemplateRouteService( /// /// Директор Tansu (HeadType.DirectorTansu) сплошь и рядом совпадает с директором /// ОДНОЙ из компаний инициатора (HeadType.CompanyDirectors) — обычно с ТОО TANSU - /// Construction. Оба шага в маршруте законны сами по себе, и оба должны попасть в - /// результат — это НЕ дубликат одного согласования, а два разных: сначала блок - /// директоров компаний параллельно, а затем — отдельно — Директор Tansu, даже если - /// физически это тот же человек. Поэтому строку DirectorTansu с таким совпадением - /// НЕ пускаем через обычное схлопывание по email (см. CollapseByExecutorEmail) — - /// оно бы решило, что это конфликт PositionCode одного согласующего, и либо - /// смержило бы её с строкой CompanyDirectors, либо просто выбрало одну из них. - /// Вместо этого явно переставляем её OrderNo на «после всего блока - /// CompanyDirectors» — не параллельно ему, а следом. + /// Construction. Оба шага в маршруте законны сами по себе, но если так, из + /// CompanyDirectors нужно убрать именно ЭТУ строку — не смешивать её с шагом + /// DirectorTansu через обычное схлопывание по email (см. CollapseByExecutorEmail): + /// тот считает разные PositionCode у одного email конфликтом и пишет + /// предупреждение, а здесь это не конфликт, а один и тот же человек — оставляем + /// только шаг DirectorTansu, а дубликат из CompanyDirectors удаляем. /// - /// - /// (toCollapse, directorTansuAfterCompanyDirectors): первый список идёт через - /// обычное схлопывание по email, второй — строки DirectorTansu, которые нужно - /// просто дописать в конец результата без схлопывания. - /// - private static (IReadOnlyList ToCollapse, IReadOnlyList DirectorTansuAfterCompanyDirectors) - SplitDirectorTansuAfterCompanyDirectors(IReadOnlyList steps) + private static IReadOnlyList RemoveCompanyDirectorsDuplicatedByDirectorTansu( + IReadOnlyList steps) { - var companyDirectorSteps = steps.Where(s => s.PositionCode == HeadType.CompanyDirectors).ToList(); - var companyDirectorEmails = companyDirectorSteps - .Where(s => !string.IsNullOrWhiteSpace(s.ExecutorEmail)) + var directorTansuEmails = steps + .Where(s => s.PositionCode == HeadType.DirectorTansu && !string.IsNullOrWhiteSpace(s.ExecutorEmail)) .Select(s => s.ExecutorEmail!) .ToHashSet(StringComparer.OrdinalIgnoreCase); - if (companyDirectorEmails.Count == 0) - return (steps, []); + if (directorTansuEmails.Count == 0) + return steps; - var maxCompanyDirectorsOrderNo = companyDirectorSteps.Max(s => s.OrderNo); - - var toCollapse = new List(); - var afterBlock = new List(); - - foreach (var step in steps) - { - if (step.PositionCode == HeadType.DirectorTansu && - !string.IsNullOrWhiteSpace(step.ExecutorEmail) && - companyDirectorEmails.Contains(step.ExecutorEmail)) - { - afterBlock.Add(step with { OrderNo = maxCompanyDirectorsOrderNo + 1 }); - } - else - { - toCollapse.Add(step); - } - } - - return (toCollapse, afterBlock); + return steps + .Where(s => s.PositionCode != HeadType.CompanyDirectors + || string.IsNullOrWhiteSpace(s.ExecutorEmail) + || !directorTansuEmails.Contains(s.ExecutorEmail)) + .ToList(); } /// diff --git a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/TemplateRouteServiceTests.cs b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/TemplateRouteServiceTests.cs index 47b0af1..7e3d092 100644 --- a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/TemplateRouteServiceTests.cs +++ b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/TemplateRouteServiceTests.cs @@ -900,12 +900,12 @@ public sealed class TemplateRouteServiceTests : IClassFixture(); portalClient .Setup(x => x.GetEmployeeByEmailAsync(It.IsAny(), It.IsAny())) @@ -949,21 +949,15 @@ public sealed class TemplateRouteServiceTests : IClassFixture s.PositionCode == HeadType.DirectorTansu); - tansuStep.ExecutorEmail.ShouldBe("director.tansu@tnsu.kz"); - tansuStep.OrderNo.ShouldBe(6); + resolved.Value.Steps.Count.ShouldBe(2); + var tansuStep = resolved.Value.Steps.Single(s => s.ExecutorEmail == "director.tansu@tnsu.kz"); + tansuStep.PositionCode.ShouldBe(HeadType.DirectorTansu); + tansuStep.OrderNo.ShouldBe(10); tansuStep.Warning.ShouldBeNull(); - - var companyDirectorSteps = resolved.Value.Steps.Where(s => s.PositionCode == HeadType.CompanyDirectors).ToList(); - companyDirectorSteps.Count.ShouldBe(2); - companyDirectorSteps.ShouldAllBe(s => s.OrderNo == 5); - companyDirectorSteps.ShouldContain(s => s.ExecutorEmail == "director.tansu@tnsu.kz"); - companyDirectorSteps.ShouldContain(s => s.ExecutorEmail == "mechanic.kps@tnsu.kz"); + resolved.Value.Steps.ShouldContain(s => s.ExecutorEmail == "mechanic.kps@tnsu.kz" && s.PositionCode == HeadType.CompanyDirectors); } [Fact(DisplayName = "Резолв шага с типом руководителя Chief возвращает warning, если руководитель не найден")]