本轮完成“考点与考场变更审批”原生迁移:
GET/POST/PATCH /api/admin/centers
PATCH /api/admin/center-change-requests/{id}
校级数据范围、班级管理员禁用
省市区县及结构化考场校验
正式档案和待审申请代码去重
多级工作流分配、通过与拒绝
终审时原子更新流程、审计、考点及考场
新增开关 ADMIN_NATIVE_CENTERS_ENABLED=false
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
using System.Data.Common;
|
||||
using System.Globalization;
|
||||
using Eis.Infrastructure.Data;
|
||||
|
||||
namespace Eis.Infrastructure.Administration;
|
||||
|
||||
internal sealed record AdminCenterSnapshot(
|
||||
IReadOnlyList<AdminCenter> Centers,
|
||||
IReadOnlyList<AdminCenterRoom> Rooms,
|
||||
IReadOnlyList<AdminCenterChange> Changes,
|
||||
IReadOnlyList<AdminCenterChangeRoom> ChangeRooms);
|
||||
|
||||
internal sealed record AdminCenter(
|
||||
string Id,
|
||||
string SchoolId,
|
||||
string Code,
|
||||
string Name,
|
||||
string ProvinceCode,
|
||||
string ProvinceName,
|
||||
string CityCode,
|
||||
string CityName,
|
||||
string DistrictCode,
|
||||
string DistrictName,
|
||||
string Address,
|
||||
string Contact,
|
||||
string ManagerName,
|
||||
string ManagerPhone,
|
||||
string EmergencyPhone,
|
||||
string GateOpenTime,
|
||||
string Transport,
|
||||
string Status,
|
||||
string Notes,
|
||||
string Rooms,
|
||||
string UpdatedAt);
|
||||
|
||||
internal sealed record AdminCenterRoom(
|
||||
string Id,
|
||||
string CenterId,
|
||||
string Code,
|
||||
string Name,
|
||||
string Building,
|
||||
string Floor,
|
||||
int Capacity,
|
||||
string SeatPlan,
|
||||
int SeatStart,
|
||||
int SeatEnd,
|
||||
string RoomType,
|
||||
string Status,
|
||||
string Notes);
|
||||
|
||||
internal sealed record AdminCenterChange(
|
||||
string Id,
|
||||
string? CenterId,
|
||||
string SchoolId,
|
||||
string RequestType,
|
||||
string Code,
|
||||
string Name,
|
||||
string ProvinceCode,
|
||||
string ProvinceName,
|
||||
string CityCode,
|
||||
string CityName,
|
||||
string DistrictCode,
|
||||
string DistrictName,
|
||||
string Address,
|
||||
string Contact,
|
||||
string ManagerName,
|
||||
string ManagerPhone,
|
||||
string EmergencyPhone,
|
||||
string GateOpenTime,
|
||||
string Transport,
|
||||
string CenterStatus,
|
||||
string Notes,
|
||||
string Status,
|
||||
string ReviewNote,
|
||||
string? RequestedBy,
|
||||
string CreatedAt,
|
||||
string? ReviewedAt);
|
||||
|
||||
internal sealed record AdminCenterChangeRoom(
|
||||
string Id,
|
||||
string RequestId,
|
||||
string? RoomId,
|
||||
string Code,
|
||||
string Name,
|
||||
string Building,
|
||||
string Floor,
|
||||
int Capacity,
|
||||
string SeatPlan,
|
||||
int SeatStart,
|
||||
int SeatEnd,
|
||||
string RoomType,
|
||||
string Status,
|
||||
string Notes);
|
||||
|
||||
internal sealed class AdminCenterSnapshotLoader(IRelationalConnectionFactory connectionFactory)
|
||||
{
|
||||
public async Task<AdminCenterSnapshot> LoadAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await using var connection = await connectionFactory.OpenAsync(cancellationToken);
|
||||
var centers = await QueryAsync(connection,
|
||||
"""
|
||||
SELECT id, school_id, code, name, province_code, province_name, city_code, city_name,
|
||||
district_code, district_name, address, contact, manager_name, manager_phone, emergency_phone,
|
||||
gate_open_time, transport, status, notes, rooms, updated_at
|
||||
FROM test_centers ORDER BY school_id, name, id
|
||||
""",
|
||||
ReadCenter,
|
||||
cancellationToken);
|
||||
var rooms = await QueryAsync(connection,
|
||||
"""
|
||||
SELECT id, center_id, code, name, building, floor, capacity, seat_plan, seat_start, seat_end,
|
||||
room_type, status, notes FROM test_rooms ORDER BY center_id, code, id
|
||||
""",
|
||||
ReadRoom,
|
||||
cancellationToken);
|
||||
var changes = await QueryAsync(connection,
|
||||
"""
|
||||
SELECT id, center_id, school_id, request_type, code, name, province_code, province_name,
|
||||
city_code, city_name, district_code, district_name, address, contact, manager_name,
|
||||
manager_phone, emergency_phone, gate_open_time, transport, center_status, notes, status,
|
||||
review_note, requested_by, created_at, reviewed_at
|
||||
FROM center_change_requests ORDER BY created_at DESC, id
|
||||
""",
|
||||
ReadChange,
|
||||
cancellationToken);
|
||||
var changeRooms = await QueryAsync(connection,
|
||||
"""
|
||||
SELECT id, request_id, room_id, code, name, building, floor, capacity, seat_plan, seat_start,
|
||||
seat_end, room_type, status, notes FROM center_change_rooms ORDER BY request_id, code, id
|
||||
""",
|
||||
ReadChangeRoom,
|
||||
cancellationToken);
|
||||
return new(centers, rooms, changes, changeRooms);
|
||||
}
|
||||
|
||||
private static AdminCenter ReadCenter(DbDataReader reader) => new(
|
||||
Text(reader, "id"), Text(reader, "school_id"), Text(reader, "code"), Text(reader, "name"),
|
||||
Text(reader, "province_code"), Text(reader, "province_name"), Text(reader, "city_code"), Text(reader, "city_name"),
|
||||
Text(reader, "district_code"), Text(reader, "district_name"), Text(reader, "address"), Optional(reader, "contact") ?? "",
|
||||
Optional(reader, "manager_name") ?? "", Optional(reader, "manager_phone") ?? "", Optional(reader, "emergency_phone") ?? "",
|
||||
Optional(reader, "gate_open_time") ?? "", Optional(reader, "transport") ?? "", Text(reader, "status"),
|
||||
Optional(reader, "notes") ?? "", Text(reader, "rooms"), Text(reader, "updated_at"));
|
||||
|
||||
private static AdminCenterRoom ReadRoom(DbDataReader reader) => new(
|
||||
Text(reader, "id"), Text(reader, "center_id"), Text(reader, "code"), Text(reader, "name"), Text(reader, "building"),
|
||||
Optional(reader, "floor") ?? "", Integer(reader, "capacity"), Optional(reader, "seat_plan") ?? "",
|
||||
Integer(reader, "seat_start"), Integer(reader, "seat_end"), Text(reader, "room_type"), Text(reader, "status"), Optional(reader, "notes") ?? "");
|
||||
|
||||
private static AdminCenterChange ReadChange(DbDataReader reader) => new(
|
||||
Text(reader, "id"), Optional(reader, "center_id"), Text(reader, "school_id"), Text(reader, "request_type"),
|
||||
Text(reader, "code"), Text(reader, "name"), Text(reader, "province_code"), Text(reader, "province_name"),
|
||||
Text(reader, "city_code"), Text(reader, "city_name"), Text(reader, "district_code"), Text(reader, "district_name"),
|
||||
Text(reader, "address"), Optional(reader, "contact") ?? "", Optional(reader, "manager_name") ?? "",
|
||||
Optional(reader, "manager_phone") ?? "", Optional(reader, "emergency_phone") ?? "", Optional(reader, "gate_open_time") ?? "",
|
||||
Optional(reader, "transport") ?? "", Text(reader, "center_status"), Optional(reader, "notes") ?? "", Text(reader, "status"),
|
||||
Optional(reader, "review_note") ?? "", Optional(reader, "requested_by"), Text(reader, "created_at"), Optional(reader, "reviewed_at"));
|
||||
|
||||
private static AdminCenterChangeRoom ReadChangeRoom(DbDataReader reader) => new(
|
||||
Text(reader, "id"), Text(reader, "request_id"), Optional(reader, "room_id"), Text(reader, "code"), Text(reader, "name"),
|
||||
Text(reader, "building"), Optional(reader, "floor") ?? "", Integer(reader, "capacity"), Optional(reader, "seat_plan") ?? "",
|
||||
Integer(reader, "seat_start"), Integer(reader, "seat_end"), Text(reader, "room_type"), Text(reader, "status"), Optional(reader, "notes") ?? "");
|
||||
|
||||
private static async Task<IReadOnlyList<T>> QueryAsync<T>(
|
||||
DbConnection connection,
|
||||
string sql,
|
||||
Func<DbDataReader, T> map,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await using var command = connection.CreateCommand();
|
||||
command.CommandText = sql;
|
||||
await using var reader = await command.ExecuteReaderAsync(cancellationToken);
|
||||
var output = new List<T>();
|
||||
while (await reader.ReadAsync(cancellationToken)) output.Add(map(reader));
|
||||
return output;
|
||||
}
|
||||
|
||||
private static string Text(DbDataReader reader, string name) =>
|
||||
Convert.ToString(reader.GetValue(reader.GetOrdinal(name)), CultureInfo.InvariantCulture) ?? "";
|
||||
|
||||
private static string? Optional(DbDataReader reader, string name)
|
||||
{
|
||||
var ordinal = reader.GetOrdinal(name);
|
||||
return reader.IsDBNull(ordinal) ? null : Convert.ToString(reader.GetValue(ordinal), CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private static int Integer(DbDataReader reader, string name) =>
|
||||
Convert.ToInt32(reader.GetValue(reader.GetOrdinal(name)), CultureInfo.InvariantCulture);
|
||||
}
|
||||
Reference in New Issue
Block a user