GET/POST/PATCH /api/admin/centers
PATCH /api/admin/center-change-requests/{id}
校级数据范围、班级管理员禁用
省市区县及结构化考场校验
正式档案和待审申请代码去重
多级工作流分配、通过与拒绝
终审时原子更新流程、审计、考点及考场
新增开关 ADMIN_NATIVE_CENTERS_ENABLED=false
189 lines
7.8 KiB
C#
189 lines
7.8 KiB
C#
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);
|
|
}
|