支持五级混编、稳定种子、容量预检、固定考点、逐科考场/座位及四种准考证号规则。
正式编排会事务性替换整场计划、准考证、座位和审计记录。 支持三级管理员范围读取、批量 HTML 准考证、准考证信息 Excel,以及校级考点桌贴、门贴和签名单。 核心实现见 [AdminArrangementPlanner.cs (line 7)](C:/Users/BI/Documents/EIS-dotnet/src/Eis.Infrastructure/Administration/AdminArrangementPlanner.cs:7)、[AdminArrangementService.cs (line 11)](C:/Users/BI/Documents/EIS-dotnet/src/Eis.Infrastructure/Administration/AdminArrangementService.cs:11) 和 [AdminArrangementExporter.cs (line 5)](C:/Users/BI/Documents/EIS-dotnet/src/Eis.Infrastructure/Administration/AdminArrangementExporter.cs:5)。 新增开关 ADMIN_NATIVE_ARRANGEMENTS_ENABLED=true,说明已更新到 [MIGRATION.md (line 57)](C:/Users/BI/Documents/EIS-dotnet/MIGRATION.md:57)。 Excel 由 .NET 直接生成,使用 ClosedXML 0.105.0;该库支持服务端生成 Excel 2007+ 工作簿。ClosedXML NuGet 页面
This commit is contained in:
@@ -148,6 +148,28 @@ function Assert-CandidateAdmissionsEquivalent {
|
||||
}
|
||||
}
|
||||
|
||||
function Assert-ArrangementPreviewEquivalent {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string] $Expected,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[string] $Actual
|
||||
)
|
||||
|
||||
$expectedNode = [System.Text.Json.Nodes.JsonNode]::Parse($Expected)
|
||||
$actualNode = [System.Text.Json.Nodes.JsonNode]::Parse($Actual)
|
||||
foreach ($node in @($expectedNode, $actualNode)) {
|
||||
foreach ($sample in $node['samples'].AsArray()) {
|
||||
$sample.AsObject().Remove('generatedAt') | Out-Null
|
||||
}
|
||||
}
|
||||
if (-not [System.Text.Json.Nodes.JsonNode]::DeepEquals($expectedNode, $actualNode)) {
|
||||
$difference = Find-JsonDifference -Expected $expectedNode -Actual $actualNode -Path '$'
|
||||
throw "Arrangement preview JSON payload differs from the legacy API at $difference"
|
||||
}
|
||||
}
|
||||
|
||||
function Find-JsonDifference {
|
||||
param(
|
||||
[AllowNull()]
|
||||
@@ -249,6 +271,20 @@ try {
|
||||
}
|
||||
$registrationSwitchProcess.Dispose()
|
||||
|
||||
$arrangementCapacityProcess = Start-TestProcess -FileName $nodeExecutable -ArgumentList @(
|
||||
'tests/helpers/prepare-admin-arrangement-smoke.mjs', $smokeDatabasePath
|
||||
) -Environment $nodeEnvironment
|
||||
if (-not $arrangementCapacityProcess.WaitForExit(10000)) {
|
||||
$arrangementCapacityProcess.Kill($true)
|
||||
throw 'Timed out while preparing arrangement capacity in the smoke-test database'
|
||||
}
|
||||
$arrangementCapacityOutput = $arrangementCapacityProcess.StandardOutput.ReadToEnd()
|
||||
$arrangementCapacityError = $arrangementCapacityProcess.StandardError.ReadToEnd()
|
||||
if ($arrangementCapacityProcess.ExitCode -ne 0) {
|
||||
throw "Could not prepare arrangement capacity in the smoke-test database`n$arrangementCapacityOutput`n$arrangementCapacityError"
|
||||
}
|
||||
$arrangementCapacityProcess.Dispose()
|
||||
|
||||
$projectPath = Join-Path $repositoryRoot 'src\Eis.Web\Eis.Web.csproj'
|
||||
$buildProcess = Start-TestProcess -FileName $dotnetExecutable -ArgumentList @(
|
||||
'build', $projectPath,
|
||||
@@ -447,6 +483,7 @@ try {
|
||||
ADMIN_NATIVE_REGISTRATION_PAYMENT_WRITES_ENABLED = 'true'
|
||||
ADMIN_NATIVE_WORKFLOW_OPERATIONS_ENABLED = 'true'
|
||||
ADMIN_NATIVE_EXAM_MANAGEMENT_ENABLED = 'true'
|
||||
ADMIN_NATIVE_ARRANGEMENTS_ENABLED = 'true'
|
||||
ADMIN_NATIVE_ALLOW_MEMORY = 'true'
|
||||
LegacyNode__Enabled = 'true'
|
||||
LegacyNode__BaseUrl = $legacyBaseUrl
|
||||
@@ -807,6 +844,94 @@ try {
|
||||
throw 'Native admins route did not preserve the class-admin boundary'
|
||||
}
|
||||
|
||||
$arrangementSessions = @(
|
||||
@{ Level = 'super'; Legacy = $session; Native = $nativeSession },
|
||||
@{ Level = 'school'; Legacy = $legacySchoolSession; Native = $nativeSchoolSession },
|
||||
@{ Level = 'class'; Legacy = $legacyClassSession; Native = $nativeClassSession }
|
||||
)
|
||||
foreach ($arrangementSession in $arrangementSessions) {
|
||||
$legacyArrangementRead = Invoke-WebRequest -Uri "$legacyBaseUrl/api/admin/admission-arrangements" -WebSession $arrangementSession.Legacy
|
||||
$nativeArrangementRead = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/admission-arrangements" -WebSession $arrangementSession.Native
|
||||
if ($nativeArrangementRead.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
|
||||
throw "Arrangement read for $($arrangementSession.Level) admin did not use ASP.NET Core"
|
||||
}
|
||||
Assert-JsonEquivalent -Expected $legacyArrangementRead.Content -Actual $nativeArrangementRead.Content -Label "Arrangement read for $($arrangementSession.Level) admin"
|
||||
}
|
||||
$arrangementState = Invoke-RestMethod -Uri "$nativeAuthBaseUrl/api/admin/admission-arrangements" -WebSession $nativeSession
|
||||
$arrangementExam = @($arrangementState.exams | Where-Object { $_.approvedCount -gt 0 -and -not $_.archivedAt })[0]
|
||||
$arrangementRule = @($arrangementState.rules | Where-Object { $_.code -eq 'district_room_seat' })[0]
|
||||
if ($null -eq $arrangementExam -or $null -eq $arrangementRule) {
|
||||
throw 'Seed data did not provide an exam and number rule for arrangement smoke testing'
|
||||
}
|
||||
$arrangementBody = @{
|
||||
mixingScope = 'province'
|
||||
numberRuleId = $arrangementRule.id
|
||||
seed = 'native-arrangement-parity'
|
||||
} | ConvertTo-Json -Compress
|
||||
$schoolArrangementForbidden = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/exams/$($arrangementExam.id)/admission-arrangement/preview" -Method Post -ContentType 'application/json' -Body $arrangementBody -WebSession $nativeSchoolSession -SkipHttpErrorCheck
|
||||
if ($schoolArrangementForbidden.StatusCode -ne 403 -or $schoolArrangementForbidden.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
|
||||
throw 'Native arrangement preview did not preserve the super-admin boundary'
|
||||
}
|
||||
$invalidArrangementBody = @{
|
||||
mixingScope = 'invalid'
|
||||
numberRuleId = $arrangementRule.id
|
||||
seed = 'native-arrangement-parity'
|
||||
} | ConvertTo-Json -Compress
|
||||
$invalidArrangement = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/exams/$($arrangementExam.id)/admission-arrangement/preview" -Method Post -ContentType 'application/json' -Body $invalidArrangementBody -WebSession $nativeSession -SkipHttpErrorCheck
|
||||
if ($invalidArrangement.StatusCode -ne 400) {
|
||||
throw 'Native arrangement preview accepted an invalid mixing scope'
|
||||
}
|
||||
$legacyArrangementPreview = Invoke-WebRequest -Uri "$legacyBaseUrl/api/admin/exams/$($arrangementExam.id)/admission-arrangement/preview" -Method Post -ContentType 'application/json' -Body $arrangementBody -WebSession $session
|
||||
$nativeArrangementPreview = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/exams/$($arrangementExam.id)/admission-arrangement/preview" -Method Post -ContentType 'application/json' -Body $arrangementBody -WebSession $nativeSession
|
||||
if ($nativeArrangementPreview.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
|
||||
throw 'Native arrangement preview did not use ASP.NET Core'
|
||||
}
|
||||
Assert-ArrangementPreviewEquivalent -Expected $legacyArrangementPreview.Content -Actual $nativeArrangementPreview.Content
|
||||
|
||||
$arrangementApplyResponse = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/exams/$($arrangementExam.id)/admission-arrangement" -Method Post -ContentType 'application/json' -Body $arrangementBody -WebSession $nativeSession
|
||||
$appliedArrangement = $arrangementApplyResponse.Content | ConvertFrom-Json
|
||||
if ($arrangementApplyResponse.Headers['X-EIS-Implementation'] -ne 'aspnet-core' -or
|
||||
$appliedArrangement.plan.examId -ne $arrangementExam.id -or
|
||||
$appliedArrangement.summary.candidateCount -le 0 -or
|
||||
@($appliedArrangement.cards).Count -ne $appliedArrangement.summary.candidateCount) {
|
||||
throw 'Native arrangement application did not persist the complete exam plan'
|
||||
}
|
||||
foreach ($arrangementSession in $arrangementSessions) {
|
||||
$legacyArrangementAfterApply = Invoke-WebRequest -Uri "$legacyBaseUrl/api/admin/admission-arrangements" -WebSession $arrangementSession.Legacy
|
||||
$nativeArrangementAfterApply = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/admission-arrangements" -WebSession $arrangementSession.Native
|
||||
Assert-JsonEquivalent -Expected $legacyArrangementAfterApply.Content -Actual $nativeArrangementAfterApply.Content -Label "Arrangement application follow-up for $($arrangementSession.Level) admin"
|
||||
}
|
||||
$legacySingleAdmit = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/registrations/$($appliedArrangement.cards[0].registrationId)/admit-card" -Method Post -WebSession $nativeSession -SkipHttpErrorCheck
|
||||
if ($legacySingleAdmit.StatusCode -ne 410 -or $legacySingleAdmit.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
|
||||
throw 'Native arrangement routes did not retire single-candidate admit-card generation'
|
||||
}
|
||||
$anonymousSingleAdmit = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/registrations/$($appliedArrangement.cards[0].registrationId)/admit-card" -Method Post -SkipHttpErrorCheck
|
||||
if ($anonymousSingleAdmit.StatusCode -ne 401) {
|
||||
throw 'Native retired admit-card route did not preserve administrator authentication'
|
||||
}
|
||||
$batchAdmitExport = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/admission-exports/admit-cards?examId=$($arrangementExam.id)" -WebSession $nativeSession
|
||||
if ($batchAdmitExport.Headers['X-EIS-Implementation'] -ne 'aspnet-core' -or
|
||||
$batchAdmitExport.Headers['Content-Type'] -notmatch '^text/html' -or
|
||||
$batchAdmitExport.Content -notmatch '衡准 · 准考证') {
|
||||
throw 'Native batch admit-card HTML export is invalid'
|
||||
}
|
||||
$admitInfoExport = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/admission-exports/info?examId=$($arrangementExam.id)" -WebSession $nativeSession
|
||||
if ($admitInfoExport.Headers['X-EIS-Implementation'] -ne 'aspnet-core' -or
|
||||
$admitInfoExport.Headers['Content-Type'] -notmatch 'spreadsheetml' -or
|
||||
$admitInfoExport.RawContentLength -lt 1000) {
|
||||
throw 'Native admit-card information workbook export is invalid'
|
||||
}
|
||||
$superCenterMaterialsForbidden = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/admission-exports/center-materials?examId=$($arrangementExam.id)" -WebSession $nativeSession -SkipHttpErrorCheck
|
||||
if ($superCenterMaterialsForbidden.StatusCode -ne 403) {
|
||||
throw 'Native center-material export did not preserve the school-admin boundary'
|
||||
}
|
||||
$centerMaterialsExport = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/admission-exports/center-materials?examId=$($arrangementExam.id)" -WebSession $nativeSchoolSession
|
||||
if ($centerMaterialsExport.Headers['X-EIS-Implementation'] -ne 'aspnet-core' -or
|
||||
$centerMaterialsExport.Headers['Content-Type'] -notmatch 'spreadsheetml' -or
|
||||
$centerMaterialsExport.RawContentLength -lt 1000) {
|
||||
throw 'Native center-material workbook export is invalid'
|
||||
}
|
||||
|
||||
$operationalSessions = @(
|
||||
@{ Level = 'super'; Legacy = $session; Native = $nativeSession },
|
||||
@{ Level = 'school'; Legacy = $legacySchoolSession; Native = $nativeSchoolSession },
|
||||
@@ -1443,6 +1568,10 @@ try {
|
||||
if ($archivedExamUpdate.StatusCode -ne 409) {
|
||||
throw 'Native exam update did not keep archived exams locked'
|
||||
}
|
||||
$archivedExamArrangement = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/exams/$($createdExam.id)/admission-arrangement/preview" -Method Post -ContentType 'application/json' -Body $arrangementBody -WebSession $nativeSession -SkipHttpErrorCheck
|
||||
if ($archivedExamArrangement.StatusCode -ne 409) {
|
||||
throw 'Native arrangement preview did not keep archived exams locked'
|
||||
}
|
||||
$legacyExamsAfterArchive = Invoke-WebRequest -Uri "$legacyBaseUrl/api/admin/exams" -WebSession $session
|
||||
$nativeExamsAfterArchive = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/exams" -WebSession $nativeSession
|
||||
Assert-JsonEquivalent -Expected $legacyExamsAfterArchive.Content -Actual $nativeExamsAfterArchive.Content -Label 'Exam archive follow-up'
|
||||
@@ -1582,6 +1711,7 @@ try {
|
||||
NativeAdminRegistrationPaymentWrites = 'passed'
|
||||
NativeAdminWorkflowOperations = 'passed'
|
||||
NativeAdminExamManagement = 'passed'
|
||||
NativeAdminArrangements = 'passed'
|
||||
} | Format-List
|
||||
}
|
||||
finally {
|
||||
|
||||
Reference in New Issue
Block a user