using System; using System.Net.Http; using System.Threading; using System.Threading.Tasks; namespace Yavsc.Api.Client; /// /// HTTP client for the front-office query routes /// (api/v1/front/query/accept / .../reject, served by /// ). Same /// DTO↔path mapper shape as : transport /// (base URL, JSON, Bearer auth, silent refresh on 401) is delegated /// to . /// /// The server resolves the caller's role (provider vs client vs admin) /// from the authenticated user compared to the query's /// PerformerId/ClientId; the client does not declare a /// role. is the estimate's /// and /// is its . /// public sealed class FrontOfficeApiClient { private const string PathPrefix = "front"; private readonly IYavscApiClient _api; private readonly Func _businessBaseAddress; public FrontOfficeApiClient(IYavscApiClient api, string businessBaseAddress) : this(api, () => businessBaseAddress) { } public FrontOfficeApiClient(IYavscApiClient api, Func businessBaseAddress) { _api = api ?? throw new ArgumentNullException(nameof(api)); _businessBaseAddress = businessBaseAddress ?? throw new ArgumentNullException(nameof(businessBaseAddress)); _ = ResolveBusinessBaseAddress(); } /// /// Accept (validate) the query under /// billing code . When /// carries a non-empty signature, the /// server stages it on the query's linked estimate and stamps the /// matching validation date. /// public async Task AcceptQueryAsync( string billingCode, long queryId, QueryAcceptanceRequestDto body, CancellationToken ct = default) { if (string.IsNullOrWhiteSpace(billingCode)) throw new ArgumentException("billingCode is required.", nameof(billingCode)); if (queryId <= 0) throw new ArgumentOutOfRangeException(nameof(queryId)); if (body is null) throw new ArgumentNullException(nameof(body)); var path = $"{PathPrefix}/query/accept?billingCode={Uri.EscapeDataString(billingCode)}&queryId={queryId}"; var response = await _api.CallAsync( HttpMethod.Post, Absolute(path), body: body, ct: ct).ConfigureAwait(false); return response ?? new QueryAcceptanceResponseDto { QueryId = queryId }; } /// /// Reject the query under billing code /// . A signature is not meaningful on /// a rejection; the server refuses one with 400, so this sends no /// body. /// public async Task RejectQueryAsync( string billingCode, long queryId, CancellationToken ct = default) { if (string.IsNullOrWhiteSpace(billingCode)) throw new ArgumentException("billingCode is required.", nameof(billingCode)); if (queryId <= 0) throw new ArgumentOutOfRangeException(nameof(queryId)); var path = $"{PathPrefix}/query/reject?billingCode={Uri.EscapeDataString(billingCode)}&queryId={queryId}"; var response = await _api.CallAsync( HttpMethod.Post, Absolute(path), body: null, ct: ct).ConfigureAwait(false); return response ?? new QueryAcceptanceResponseDto { QueryId = queryId }; } private string Absolute(string relativePath) => new Uri(ResolveBusinessBaseAddress(), relativePath).ToString(); private Uri ResolveBusinessBaseAddress() { var raw = _businessBaseAddress(); if (string.IsNullOrWhiteSpace(raw)) throw new InvalidOperationException("Business base address is required."); return new Uri(raw, UriKind.Absolute); } }