Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

169 lines
6.2KB

  1. using Campaign_Tracker.Server.Municipalities;
  2. namespace Campaign_Tracker.Server.ElectionCycles;
  3. public interface IElectionCycleKanbanReadModel
  4. {
  5. Task<ElectionCycleKanbanBoard> GetAsync(
  6. CancellationToken cancellationToken = default);
  7. }
  8. public sealed class ElectionCycleKanbanReadModel : IElectionCycleKanbanReadModel
  9. {
  10. public const string UnassignedCycleId = "__unassigned__";
  11. public const string UnassignedCycleName = "Unassigned";
  12. private readonly IMunicipalityProfileRepository _profiles;
  13. private readonly IElectionCycleJobRepository _jobs;
  14. public ElectionCycleKanbanReadModel(
  15. IMunicipalityProfileRepository profiles,
  16. IElectionCycleJobRepository jobs)
  17. {
  18. _profiles = profiles;
  19. _jobs = jobs;
  20. }
  21. public async Task<ElectionCycleKanbanBoard> GetAsync(
  22. CancellationToken cancellationToken = default)
  23. {
  24. var profiles = await _profiles.GetAllAsync(cancellationToken);
  25. var activeJobs = (await _jobs.GetAllAsync(cancellationToken))
  26. .Where(job => job.IsActive)
  27. .ToArray();
  28. var jobsByJCode = activeJobs
  29. .GroupBy(job => job.JCode, StringComparer.OrdinalIgnoreCase)
  30. .ToDictionary(group => group.Key, group => group.ToArray(), StringComparer.OrdinalIgnoreCase);
  31. var cardsByCycle = new Dictionary<string, List<ElectionCycleKanbanCard>>(
  32. StringComparer.OrdinalIgnoreCase);
  33. var lanesByCycle = activeJobs
  34. .GroupBy(job => job.CycleId, StringComparer.OrdinalIgnoreCase)
  35. .ToDictionary(
  36. group => group.Key,
  37. group => new ElectionCycleLaneHeader(
  38. group.Key,
  39. group.Select(job => job.CycleName)
  40. .OrderBy(name => name, StringComparer.OrdinalIgnoreCase)
  41. .First()),
  42. StringComparer.OrdinalIgnoreCase);
  43. var unassignedCards = new List<ElectionCycleKanbanCard>();
  44. var matchedJobIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
  45. foreach (var profile in profiles)
  46. {
  47. if (!jobsByJCode.TryGetValue(profile.Profile.JCode, out var profileJobs))
  48. {
  49. unassignedCards.Add(BuildUnassignedCard(profile));
  50. continue;
  51. }
  52. foreach (var job in profileJobs.OrderBy(job => job.CycleName, StringComparer.OrdinalIgnoreCase))
  53. {
  54. if (!cardsByCycle.TryGetValue(job.CycleId, out var cycleCards))
  55. {
  56. cycleCards = [];
  57. cardsByCycle[job.CycleId] = cycleCards;
  58. }
  59. cycleCards.Add(BuildAssignedCard(profile, job));
  60. matchedJobIds.Add(job.JobId);
  61. }
  62. }
  63. foreach (var orphan in activeJobs.Where(job => !matchedJobIds.Contains(job.JobId)))
  64. {
  65. if (!cardsByCycle.TryGetValue(orphan.CycleId, out var cycleCards))
  66. {
  67. cycleCards = [];
  68. cardsByCycle[orphan.CycleId] = cycleCards;
  69. }
  70. cycleCards.Add(BuildOrphanedCard(orphan));
  71. }
  72. var lanes = cardsByCycle
  73. .OrderBy(pair => lanesByCycle[pair.Key].CycleName, StringComparer.OrdinalIgnoreCase)
  74. .Select(pair => new ElectionCycleKanbanLane(
  75. pair.Key,
  76. lanesByCycle[pair.Key].CycleName,
  77. SortCards(pair.Value)))
  78. .Append(new ElectionCycleKanbanLane(
  79. UnassignedCycleId,
  80. UnassignedCycleName,
  81. SortCards(unassignedCards)))
  82. .ToArray();
  83. return new ElectionCycleKanbanBoard(lanes);
  84. }
  85. private static IReadOnlyList<ElectionCycleKanbanCard> SortCards(
  86. IReadOnlyList<ElectionCycleKanbanCard> cards)
  87. => cards
  88. .OrderBy(card => card.MunicipalityName, StringComparer.OrdinalIgnoreCase)
  89. .ThenBy(card => card.JCode, StringComparer.OrdinalIgnoreCase)
  90. .ToArray();
  91. private static ElectionCycleKanbanCard BuildAssignedCard(
  92. MunicipalityProfileView profile,
  93. ElectionCycleJobAssignment job)
  94. => new(
  95. CardId: job.JobId,
  96. MunicipalityName: MunicipalityName(profile),
  97. JCode: profile.Profile.JCode,
  98. CycleId: job.CycleId,
  99. CycleName: job.CycleName,
  100. CycleJobStatus: job.Status,
  101. LegacyJoinKey: profile.Profile.JCode,
  102. QuickOpenHref: $"/election-cycles/jobs/{job.JobId}");
  103. private static ElectionCycleKanbanCard BuildOrphanedCard(
  104. ElectionCycleJobAssignment job)
  105. => new(
  106. CardId: job.JobId,
  107. MunicipalityName: $"(unmapped JCode {job.JCode})",
  108. JCode: job.JCode,
  109. CycleId: job.CycleId,
  110. CycleName: job.CycleName,
  111. CycleJobStatus: job.Status,
  112. LegacyJoinKey: job.JCode,
  113. QuickOpenHref: $"/election-cycles/jobs/{job.JobId}");
  114. private static ElectionCycleKanbanCard BuildUnassignedCard(
  115. MunicipalityProfileView profile)
  116. => new(
  117. CardId: $"unassigned-{profile.Profile.JCode}",
  118. MunicipalityName: MunicipalityName(profile),
  119. JCode: profile.Profile.JCode,
  120. CycleId: UnassignedCycleId,
  121. CycleName: UnassignedCycleName,
  122. CycleJobStatus: UnassignedCycleName,
  123. LegacyJoinKey: profile.Profile.JCode,
  124. QuickOpenHref: $"/election-cycles/jobs/new?jCode={Uri.EscapeDataString(profile.Profile.JCode)}");
  125. private static string MunicipalityName(MunicipalityProfileView profile)
  126. => profile.Profile.DisplayName
  127. ?? profile.LegacyName
  128. ?? profile.Profile.JCode;
  129. private sealed record ElectionCycleLaneHeader(string CycleId, string CycleName);
  130. }
  131. public sealed record ElectionCycleKanbanBoard(
  132. IReadOnlyList<ElectionCycleKanbanLane> Lanes);
  133. public sealed record ElectionCycleKanbanLane(
  134. string CycleId,
  135. string CycleName,
  136. IReadOnlyList<ElectionCycleKanbanCard> Cards);
  137. public sealed record ElectionCycleKanbanCard(
  138. string CardId,
  139. string MunicipalityName,
  140. string JCode,
  141. string CycleId,
  142. string CycleName,
  143. string CycleJobStatus,
  144. string LegacyJoinKey,
  145. string QuickOpenHref);

Powered by TurnKey Linux.