219 lines
7.4 KiB
C#
219 lines
7.4 KiB
C#
using System.Net.Http;
|
|
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
using UtopiaCanteenSystem.Api;
|
|
using UtopiaCanteenSystem.Models;
|
|
|
|
namespace UtopiaCanteenSystem.Services;
|
|
|
|
/// <summary>
|
|
/// Scanner PC: forwards scans and dashboard queries to the central server HTTP API (no local transaction DB).
|
|
/// </summary>
|
|
public class ClientRfidService : IRfidService
|
|
{
|
|
private readonly HttpClient _http;
|
|
private readonly IConfigService _config;
|
|
|
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
PropertyNameCaseInsensitive = true
|
|
};
|
|
|
|
public ClientRfidService(HttpClient http, IConfigService config)
|
|
{
|
|
_http = http;
|
|
_config = config;
|
|
}
|
|
|
|
private string BaseUrl => (_config.GetCentralServerBaseUrl() ?? string.Empty).TrimEnd('/');
|
|
|
|
public (bool Success, string Message) ProcessScan(string cardId)
|
|
{
|
|
var r = ProcessScanDetailed(cardId);
|
|
return (r.Success, r.Message);
|
|
}
|
|
|
|
public ScanResult ProcessScanDetailed(string cardId)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(BaseUrl))
|
|
return new ScanResult(false, "Central server URL is not configured.", 0);
|
|
|
|
try
|
|
{
|
|
var url = $"{BaseUrl}/api/rfid/scan";
|
|
var body = new RfidScanRequestDto
|
|
{
|
|
CardId = cardId?.Trim() ?? string.Empty,
|
|
DeviceId = _config.GetDeviceId(),
|
|
SiteId = _config.GetSiteId(),
|
|
IpAddress = null
|
|
};
|
|
|
|
using var response = _http.PostAsJsonAsync(url, body, JsonOptions).GetAwaiter().GetResult();
|
|
if (!response.IsSuccessStatusCode)
|
|
{
|
|
var err = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
|
return new ScanResult(false,
|
|
$"Central server error ({(int)response.StatusCode}): {Truncate(err, 200)}", 0);
|
|
}
|
|
|
|
var dto = response.Content.ReadFromJsonAsync<RfidScanResponseDto>(JsonOptions).GetAwaiter().GetResult();
|
|
if (dto == null)
|
|
return new ScanResult(false, "Invalid response from central server.", 0);
|
|
|
|
return new ScanResult(
|
|
dto.Success,
|
|
dto.Message ?? string.Empty,
|
|
dto.CooldownSecondsRemaining,
|
|
dto.Employee?.ToModel(),
|
|
(MealSession)dto.MealSession,
|
|
dto.EmployeeSiteId,
|
|
dto.CurrentSiteId);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Log(ex, "ClientRfidService.ProcessScanDetailed");
|
|
return new ScanResult(false, "Cannot reach central server: " + ex.Message, 0);
|
|
}
|
|
}
|
|
|
|
public ScanRecord? GetLastScan()
|
|
{
|
|
if (string.IsNullOrWhiteSpace(BaseUrl))
|
|
return null;
|
|
try
|
|
{
|
|
var url = $"{BaseUrl}/api/rfid/scans/last";
|
|
var dto = _http.GetFromJsonAsync<ScanRecordDto?>(url, JsonOptions).GetAwaiter().GetResult();
|
|
return dto?.ToEntity();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Log(ex, "ClientRfidService.GetLastScan");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public IReadOnlyList<ScanRecord> GetLastScans(int count)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(BaseUrl) || count <= 0)
|
|
return Array.Empty<ScanRecord>();
|
|
try
|
|
{
|
|
var url = $"{BaseUrl}/api/rfid/scans/recent?count={count}";
|
|
var list = _http.GetFromJsonAsync<List<ScanRecordDto>>(url, JsonOptions).GetAwaiter().GetResult();
|
|
return list?.Select(d => d.ToEntity()).ToList() ?? new List<ScanRecord>();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Log(ex, "ClientRfidService.GetLastScans");
|
|
return Array.Empty<ScanRecord>();
|
|
}
|
|
}
|
|
|
|
public IReadOnlyList<ScanRecord> GetScansForToday()
|
|
{
|
|
if (string.IsNullOrWhiteSpace(BaseUrl))
|
|
return Array.Empty<ScanRecord>();
|
|
try
|
|
{
|
|
var url = $"{BaseUrl}/api/rfid/scans/today";
|
|
var list = _http.GetFromJsonAsync<List<ScanRecordDto>>(url, JsonOptions).GetAwaiter().GetResult();
|
|
return list?.Select(d => d.ToEntity()).ToList() ?? new List<ScanRecord>();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Log(ex, "ClientRfidService.GetScansForToday");
|
|
return Array.Empty<ScanRecord>();
|
|
}
|
|
}
|
|
|
|
public int GetTodayScanCount() => GetTodayScanCountAsync().GetAwaiter().GetResult();
|
|
|
|
public async Task<int> GetTodayScanCountAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(BaseUrl))
|
|
return 0;
|
|
try
|
|
{
|
|
var url = $"{BaseUrl}/api/rfid/stats/today";
|
|
var wrap = await _http.GetFromJsonAsync<CountDto>(url, JsonOptions, cancellationToken).ConfigureAwait(false);
|
|
return wrap?.Count ?? 0;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Log(ex, "ClientRfidService.GetTodayScanCountAsync");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public async Task<int> GetTotalScanCountAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(BaseUrl))
|
|
return 0;
|
|
try
|
|
{
|
|
var url = $"{BaseUrl}/api/rfid/stats/total";
|
|
var wrap = await _http.GetFromJsonAsync<CountDto>(url, JsonOptions, cancellationToken).ConfigureAwait(false);
|
|
return wrap?.Count ?? 0;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Log(ex, "ClientRfidService.GetTotalScanCountAsync");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public async Task<int> GetTotalScanCountForCardAsync(string cardId, CancellationToken cancellationToken = default)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(BaseUrl) || string.IsNullOrWhiteSpace(cardId))
|
|
return 0;
|
|
try
|
|
{
|
|
var url = $"{BaseUrl}/api/rfid/stats/total-for-card?cardId={Uri.EscapeDataString(cardId.Trim())}";
|
|
var wrap = await _http.GetFromJsonAsync<CountDto>(url, JsonOptions, cancellationToken).ConfigureAwait(false);
|
|
return wrap?.Count ?? 0;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Log(ex, "ClientRfidService.GetTotalScanCountForCardAsync");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public async Task<int> GetTodayScanCountForCardAsync(string cardId, CancellationToken cancellationToken = default)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(BaseUrl) || string.IsNullOrWhiteSpace(cardId))
|
|
return 0;
|
|
try
|
|
{
|
|
var url = $"{BaseUrl}/api/rfid/stats/today-for-card?cardId={Uri.EscapeDataString(cardId.Trim())}";
|
|
var wrap = await _http.GetFromJsonAsync<CountDto>(url, JsonOptions, cancellationToken).ConfigureAwait(false);
|
|
return wrap?.Count ?? 0;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Log(ex, "ClientRfidService.GetTodayScanCountForCardAsync");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public void UpdateLastScanMealInfo(string mealLabel, string mealItems, double totalPrice)
|
|
{
|
|
// Server owns SQLite; client has nothing to update locally.
|
|
}
|
|
|
|
private static string Truncate(string s, int max)
|
|
{
|
|
if (string.IsNullOrEmpty(s) || s.Length <= max)
|
|
return s;
|
|
return s.Substring(0, max) + "…";
|
|
}
|
|
|
|
private sealed class CountDto
|
|
{
|
|
public int Count { get; set; }
|
|
}
|
|
}
|