您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

177 行
8.0KB

  1. using Campaign_Tracker.Server.Seed.Models;
  2. namespace Campaign_Tracker.Server.Seed;
  3. public sealed class SeedService : ISeedService
  4. {
  5. internal const string ElectionCycleJobEntityType = "ElectionCycleJob";
  6. internal const string ReadinessFeatureKey = "FR29.ReadinessStatus";
  7. internal const string OverdueMilestoneScenario = "OverdueMilestoneAlert";
  8. private static readonly string[] ExpectedReferenceSeedKeys =
  9. [
  10. "operational-status.not-started",
  11. "operational-status.in-progress",
  12. "operational-status.blocked",
  13. "operational-status.complete",
  14. "service-template.addressing",
  15. "service-template.sorting",
  16. "service-template.transportation",
  17. "service-template.office-copy",
  18. "extension-reference.election-cycle.primary",
  19. "extension-reference.election-cycle.general",
  20. "extension-reference.mail-class.first-class",
  21. "extension-reference.mail-class.standard",
  22. ];
  23. private static readonly string[] ExpectedRequiredFieldSeedKeys =
  24. [
  25. "required-field.election-cycle-job.municipality-profile-id",
  26. "required-field.election-cycle-job.legacy-jurisdiction-j-code",
  27. "required-field.election-cycle-job.election-date",
  28. "required-field.election-cycle-job.mail-date",
  29. "required-field.election-cycle-job.service-template",
  30. ];
  31. private static readonly string[] ExpectedEscalationSeedKeys =
  32. [
  33. "escalation.overdue-milestone.operations-lead",
  34. ];
  35. private readonly ISeedDataStore _store;
  36. private readonly TimeProvider _timeProvider;
  37. public SeedService(ISeedDataStore store, TimeProvider timeProvider)
  38. {
  39. _store = store;
  40. _timeProvider = timeProvider;
  41. }
  42. public Task SeedAsync(CancellationToken cancellationToken = default) =>
  43. _store.UpsertSeedDataAsync(CreateDefaults(_timeProvider.GetUtcNow()), cancellationToken);
  44. public async Task<bool> IsSeededAsync(CancellationToken cancellationToken = default)
  45. {
  46. var referenceValues = await _store.GetReferenceValuesAsync(cancellationToken);
  47. var requiredRules = await _store.GetRequiredFieldRulesAsync(cancellationToken);
  48. var escalationRules = await _store.GetEscalationRulesAsync(cancellationToken);
  49. return ContainsAll(referenceValues.Select(value => value.SeedKey), ExpectedReferenceSeedKeys)
  50. && ContainsAll(requiredRules.Select(rule => rule.SeedKey), ExpectedRequiredFieldSeedKeys)
  51. && ContainsAll(escalationRules.Select(rule => rule.SeedKey), ExpectedEscalationSeedKeys);
  52. }
  53. private static SeedDataSet CreateDefaults(DateTimeOffset now) => new()
  54. {
  55. ReferenceValues =
  56. [
  57. Reference("operational-status.not-started", "OperationalStatus", "Not Started", "not-started",
  58. "Election-cycle job work has not started.", now),
  59. Reference("operational-status.in-progress", "OperationalStatus", "In Progress", "in-progress",
  60. "Election-cycle job work is actively in progress.", now),
  61. Reference("operational-status.blocked", "OperationalStatus", "Blocked", "blocked",
  62. "Election-cycle job work is blocked and needs intervention.", now),
  63. Reference("operational-status.complete", "OperationalStatus", "Complete", "complete",
  64. "Election-cycle job work is complete.", now),
  65. Reference("service-template.addressing", "ServiceTemplate", "Addressing", "addressing",
  66. "Default service template for addressing work.", now),
  67. Reference("service-template.sorting", "ServiceTemplate", "Sorting", "sorting",
  68. "Default service template for sorting work.", now),
  69. Reference("service-template.transportation", "ServiceTemplate", "Transportation", "transportation",
  70. "Default service template for transportation work.", now),
  71. Reference("service-template.office-copy", "ServiceTemplate", "Office Copy", "office-copy",
  72. "Default service template for office-copy work.", now),
  73. Reference("extension-reference.election-cycle.primary", "ElectionCycleType", "Primary", "primary",
  74. "Extension-layer election-cycle reference value for primary elections.", now),
  75. Reference("extension-reference.election-cycle.general", "ElectionCycleType", "General", "general",
  76. "Extension-layer election-cycle reference value for general elections.", now),
  77. Reference("extension-reference.mail-class.first-class", "MailClass", "First Class", "first-class",
  78. "Extension-layer mail-class reference value.", now),
  79. Reference("extension-reference.mail-class.standard", "MailClass", "Standard", "standard",
  80. "Extension-layer mail-class reference value.", now),
  81. ],
  82. RequiredFieldRules =
  83. [
  84. RequiredField("required-field.election-cycle-job.municipality-profile-id",
  85. "Municipality Profile", "municipalityProfileId",
  86. "Election-cycle jobs must be linked to a municipality profile.", now),
  87. RequiredField("required-field.election-cycle-job.legacy-jurisdiction-j-code",
  88. "Legacy Jurisdiction Code", "legacyJurisdictionJCode",
  89. "Election-cycle jobs must keep the legacy jurisdiction bridge required by Story 1.8.", now),
  90. RequiredField("required-field.election-cycle-job.election-date",
  91. "Election Date", "electionDate",
  92. "Election-cycle jobs need an election date before readiness can pass.", now),
  93. RequiredField("required-field.election-cycle-job.mail-date",
  94. "Mail Date", "mailDate",
  95. "Election-cycle jobs need a planned mail date before readiness can pass.", now),
  96. RequiredField("required-field.election-cycle-job.service-template",
  97. "Service Template", "serviceTemplate",
  98. "Election-cycle jobs need a selected service template before readiness can pass.", now),
  99. ],
  100. EscalationRules =
  101. [
  102. new()
  103. {
  104. SeedKey = "escalation.overdue-milestone.operations-lead",
  105. Name = "Overdue Milestone Operations Lead Alert",
  106. Description = "Escalates election-cycle jobs whose active milestone is overdue.",
  107. Scenario = OverdueMilestoneScenario,
  108. TriggerCondition = "activeMilestone.dueDate < today && job.status != 'complete'",
  109. Action = "NotifyOperationsLead",
  110. MilestoneBasis = "activeMilestone.dueDate",
  111. AlertWindow = TimeSpan.Zero,
  112. Priority = 1,
  113. Source = SeedRecordSource.SystemSeed,
  114. IsActive = true,
  115. CreatedAt = now,
  116. UpdatedAt = now,
  117. },
  118. ],
  119. };
  120. private static ReferenceValue Reference(
  121. string seedKey,
  122. string category,
  123. string name,
  124. string value,
  125. string description,
  126. DateTimeOffset now) => new()
  127. {
  128. SeedKey = seedKey,
  129. Category = category,
  130. Name = name,
  131. Value = value,
  132. Description = description,
  133. Source = SeedRecordSource.SystemSeed,
  134. IsActive = true,
  135. CreatedAt = now,
  136. UpdatedAt = now,
  137. };
  138. private static RequiredFieldRule RequiredField(
  139. string seedKey,
  140. string name,
  141. string fieldPath,
  142. string description,
  143. DateTimeOffset now) => new()
  144. {
  145. SeedKey = seedKey,
  146. Name = name,
  147. Description = description,
  148. EntityType = ElectionCycleJobEntityType,
  149. FieldPath = fieldPath,
  150. ReadinessFeatureKey = ReadinessFeatureKey,
  151. IsRequired = true,
  152. Source = SeedRecordSource.SystemSeed,
  153. IsActive = true,
  154. CreatedAt = now,
  155. UpdatedAt = now,
  156. };
  157. private static bool ContainsAll(IEnumerable<string> actual, IEnumerable<string> expected)
  158. {
  159. var actualSet = actual.ToHashSet(StringComparer.OrdinalIgnoreCase);
  160. return expected.All(actualSet.Contains);
  161. }
  162. }

Powered by TurnKey Linux.