109 lines
3.8 KiB
C#
109 lines
3.8 KiB
C#
using Dapper;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Npgsql;
|
|
|
|
public class OperationsService : IOperationsService
|
|
{
|
|
private readonly AppDbContext _db;
|
|
private readonly IClinicalSyncService _sync;
|
|
|
|
public OperationsService(AppDbContext db, IClinicalSyncService sync)
|
|
{
|
|
_db = db;
|
|
_sync = sync;
|
|
}
|
|
|
|
public async Task<IReadOnlyList<GatewayFleetItem>> GetGatewayFleetAsync(
|
|
GatewayFleetFilter filter, CancellationToken ct)
|
|
{
|
|
var query = _db.WardGateways
|
|
.AsNoTracking()
|
|
.Include(g => g.Site)
|
|
.AsQueryable();
|
|
|
|
if (!string.IsNullOrEmpty(filter.Status))
|
|
{
|
|
var status = GatewayStatusExtensions.FromDbString(filter.Status);
|
|
query = query.Where(g => g.Status == status);
|
|
}
|
|
|
|
if (filter.SiteId is Guid siteId)
|
|
query = query.Where(g => g.SiteId == siteId);
|
|
|
|
var now = DateTimeOffset.UtcNow;
|
|
return await query
|
|
.OrderByDescending(g => g.Status)
|
|
.ThenByDescending(g => g.LastHeartbeatAt == null)
|
|
.ThenByDescending(g => g.LastHeartbeatAt)
|
|
.Select(g => new GatewayFleetItem(
|
|
g.Id,
|
|
g.GatewayCode,
|
|
g.Department,
|
|
g.Site.SiteCode,
|
|
g.Site.Name,
|
|
g.Status.ToDbString(),
|
|
g.ReportedBufferDepth,
|
|
g.LastHeartbeatAt,
|
|
g.LastSyncAt,
|
|
g.LastHeartbeatAt == null
|
|
? null
|
|
: (double?)(now - g.LastHeartbeatAt.Value).TotalMinutes))
|
|
.ToListAsync(ct);
|
|
}
|
|
|
|
public async Task<GatewayDetailResponse> GetGatewayDetailAsync(Guid gatewayId, CancellationToken ct)
|
|
{
|
|
var fleet = await GetGatewayFleetAsync(new GatewayFleetFilter(null, null), ct);
|
|
var gateway = fleet.FirstOrDefault(g => g.Id == gatewayId)
|
|
?? throw new NotFoundException("Gateway not found.", "GATEWAY_NOT_FOUND");
|
|
|
|
var siteId = await _db.WardGateways
|
|
.Where(g => g.Id == gatewayId)
|
|
.Select(g => g.SiteId)
|
|
.FirstAsync(ct);
|
|
|
|
var history = await _sync.GetSyncHistoryAsync(siteId, gatewayId, 10, ct);
|
|
|
|
var lastConflictBatch = await _db.ClinicalSyncBatches
|
|
.AsNoTracking()
|
|
.Where(b => b.GatewayId == gatewayId && b.Status == ClinicalSyncBatchStatus.Conflict)
|
|
.OrderByDescending(b => b.SubmittedAt)
|
|
.Include(b => b.Conflicts)
|
|
.FirstOrDefaultAsync(ct);
|
|
|
|
var pendingConflicts = lastConflictBatch?.Conflicts.Count ?? 0;
|
|
|
|
return new GatewayDetailResponse(gateway, history, pendingConflicts);
|
|
}
|
|
|
|
public async Task<SiteGatewaySummaryResponse> GetSiteSummaryAsync(Guid siteId, CancellationToken ct)
|
|
{
|
|
var siteExists = await _db.ClinicalSites.AnyAsync(s => s.Id == siteId, ct);
|
|
if (!siteExists)
|
|
throw new NotFoundException("Site not found.", "SITE_NOT_FOUND");
|
|
|
|
await using var conn = new NpgsqlConnection(_db.Database.GetConnectionString());
|
|
await conn.OpenAsync(ct);
|
|
|
|
const string sql = """
|
|
SELECT
|
|
COUNT(*) AS TotalGateways,
|
|
COUNT(*) FILTER (WHERE status = 'ONLINE') AS Online,
|
|
COUNT(*) FILTER (WHERE status = 'DEGRADED') AS Degraded,
|
|
COUNT(*) FILTER (WHERE status = 'OFFLINE') AS Offline,
|
|
COALESCE(SUM(reported_buffer_depth), 0) AS TotalBufferedEvents
|
|
FROM ward_gateways
|
|
WHERE site_id = @SiteId
|
|
""";
|
|
|
|
var row = await conn.QuerySingleAsync(sql, new { SiteId = siteId });
|
|
|
|
return new SiteGatewaySummaryResponse(
|
|
siteId,
|
|
(int)row.totalgateways,
|
|
(int)row.online,
|
|
(int)row.degraded,
|
|
(int)row.offline,
|
|
(int)row.totalbufferedevents);
|
|
}
|
|
} |